swlkr / allyourpasswords

The offline macOS password manager
https://allyourpasswords.com
65 stars 5 forks source link

Can't retrieve login table count #3

Closed cognophile closed 5 years ago

cognophile commented 5 years ago

Hey!

Really enjoying your blog series and this project. I'm tinkering in Swift/Objective-C with your project to learn (and hopefully contribute).

Sadly, I've encountered a few issues with the database communication. I'm not 100% where the problem lies (my environment, allyourpasswords, SQLite.swift).

Information

Issue type: bug

macOS: 10.14 Xcode: 10.2 SQLite.swift: 0.11.5 SQLCipher: 4.1.0

This issue was present in my forked version of the project at 383946f.

Description and Analysis

The following error occurs when running the application and entering the master password, then hitting ‘Enter’.

CREATE TABLE IF NOT EXISTS “prop” (“id” INTEGER PRIMARY KEY NOT NULL, “login” INTEGER NOT NULL, “name” TEXT, “value” TEXT)**
**SELECT * FROM “login” ORDER BY “name”, “url”**
**Fatal error: ‘try!’ expression unexpectedly raised an error: unrecognized token: “:” (code: 1): file ../allyourpasswords/allyourpasswords/ContainerViewController.swift, line 30**
**2019-04-13 16:19:30.081910+0100 allyourpasswords[16167:112381] Fatal error: ‘try!’ expression unexpectedly raised an error: unrecognized token: “:” (code: 1): file ../allyourpasswords/allyourpasswords/ContainerViewController.swift, line 30

As this is the first time I’ve run the application, my suspicion is it’s caused by the following line when it receives a 0 value as I’ve no logins saved yet:

let rowCount = try! db?.scalar(login.table.count)
...
if (rowCount ?? 0 > 0) {
    row = firstRow
    showDetailViewController()
} else {
    showEmptyViewController()
}

I'm new to Swift/Objective-C but my research/experimentation shows that the following guard protects against this unhandled exception by allowing the nil-coalescing operator in the if statement to take the (now optional due to the ?) rowCount as nil thus using the default value 0 when logging in for the first time (no logins yet). This obviously doesn't fix the bug itself as this just returns from the function immediately, however, it will allow us to log in to our development versions of the app to find a proper solution.

guard let rowCount = try? db?.scalar(login.table.count) else {
    return
}

A very similar bug occurs when creating or editing a login, clicking the Cancel button will throw the following unhandled exception:

CREATE TABLE IF NOT EXISTS "prop" ("id" INTEGER PRIMARY KEY NOT NULL, "login" INTEGER NOT NULL, "name" TEXT, "value" TEXT)
SELECT * FROM "login" ORDER BY "name", "url"
Fatal error: 'try!' expression unexpectedly raised an error: unrecognized token: ":" (code: 1): file ../allyourpasswords/allyourpasswords/EditViewController.swift, line 140
2019-04-13 16:31:36.558772+0100 allyourpasswords[16433:116736] Fatal error: 'try!' expression unexpectedly raised an error: unrecognized token: ":" (code: 1): file ../allyourpasswords/allyourpasswords/EditViewController.swift, line 140

This stems from the cancelButtonClicked function and appears to be for the same reason as the ContainerViewController::viewWillAppear() function.

    @IBAction func cancelButtonClicked(_ sender: NSButton) {
        let container = self.parent as! ContainerViewController
        let rowCount = try! db?.scalar(login.table.count)

        if rowCount ?? 0 > 0 {
            container.row = self.row
            container.showDetailViewController()
        } else {
            container.showEmptyViewController()
        }
    }

Upon investigation, let rowCount = try! db?.scalar(“SELECT COUNT(*) FROM login”) will return the correct number of rows, but login.table.count always returns 0 if using a guard, and throws an unhandled exception if not using an optional try! (in other words, this throws: let rowCount = try! db?.scalar(login.table.count)). Suggests to me an issue within SQLite.swift.

Thanks!

cognophile commented 5 years ago

Update (22/04/19)

Having taken a few days for further Swift study and further research into the bug, I've found this to be an issue within SQLite.swift. I was watching the open issues on its GitHub repository this past week. A fix was recently published for this problem. See the issue threads below.

The solution was to update the SQLite.swift pod reference to the latest working commit on master (currently 1a908a7da11852f252e7c6b6366a4d9f8a7d5272)

I've tested this on my own fork and on a freshly downloaded copy of your origin repository at 383946f9 by doing the following in the project folder:

pod cache clean --all
rm -rf Pods/
rm Podfile.lock
open -a Xcode Podfile   # Updated the SQLite.swift project ref
pod install

In both, I was then able to login without the guard in the ContainerViewController::viewWillAppear() when no logins were stored, and could now Edit a login and click 'Cancel' without it throwing the unhandled exception.