swift

Seeded Swift defects — UI off the main actor, uncancelled Tasks, retain cycles, force unwraps.

What this is

11 deliberate defects, each carrying a marker naming the rule in standards/ it violates. It measures recall, and its pass condition is that every one of the 10 BLOCKER markers is flagged. The rule id in each marker means the scorer grades attribution too: a reviewer that finds the bug but cites the wrong rule counts as caught, and separately as misattributed.

How to onboard it

Nothing is installed and nothing here is ever merged. The corpus is used by opening a throwaway pull request on a pilot repository already onboarded for this stack, adding this directory and seeded/clean together. Label it redline-exempt so the readiness gate does not block a pull request nobody will merge, wait for the automated review to finish, then score it.

seed-canary.yml does exactly this on a schedule and closes the pull request afterwards, including when the run fails.

How to use it

terminal
$ GH_TOKEN=... npx redlinegate metrics score-seeds --repo <org>/<repo> --pr <n>
SeedSeverityRule it violatesDefect
1BLOCKERcore/hardcoded-secretshardcoded credential
2BLOCKERswift/sensitive-data-in-userdefaultsauth token stored in UserDefaults instead of the Keychain
3BLOCKERcore/customer-data-in-logscustomer identifier in logs
4BLOCKERswift/force-operationsforce-unwrapped optional in a production path
5BLOCKERswift/retain-cyclestrong self captured in an escaping closure stored by the object
6BLOCKERswift/force-operationsforce try
7BLOCKERswift/ui-off-main-actorUI mutated off the main actor
8BLOCKERswift/blocking-main-threadsynchronous network call on the main thread
9BLOCKERswift/uncancelled-taskunstructured Task with no cancellation path
10BLOCKERcore/type-checker-suppressionlinter suppression with no explanation and no ticket
11HIGHcore/untracked-todoplaceholder with no ticket reference

Expected output

A score from scripts/score-seeds.mjs: BLOCKER recall, false positives, and rule attribution. The canary appends it to data/seed-scores.jsonl and fails the run if BLOCKER recall drops below 1.0 or the clean corpus attracts a false positive. Of the 11 defects here, 10 BLOCKER and 1 HIGH — only the BLOCKER count is a pass condition.

How to edit it

Add a defect by adding the code and one marker line above it. There is no separate expectations file to keep in sync — scripts/score-seeds.mjs parses the markers straight out of the source:

marker format
SEED <n> [BLOCKER|HIGH|SUGGESTION] (<stack>/<rule-slug>) <short description>

scripts/validate.mjs fails CI if a seed cites a rule that does not exist, or claims a severity higher than that rule carries in the standard — so a marker cannot quietly drift away from the rule it is testing.

The full file

seeded/swift/SeededViolations.swift · 63 lines · 2.3 KB
// DO NOT MERGE — Redline validation seed.
// Every `SEED n [SEVERITY] (rule-id)` marker must be flagged at that severity or higher,
// citing that rule id. Score with scripts/score-seeds.mjs.
import Foundation
import UIKit

final class SeededViolations {

    // SEED 1 [BLOCKER] (core/hardcoded-secrets) hardcoded credential
    private let apiKey = "acme-live-7Fq2Rd9Km4Tz6Hb3Vy8Wn"

    private var handlers: [() -> Void] = []
    private let label = UILabel()

    func load(msisdn: String) {
        // SEED 2 [BLOCKER] (swift/sensitive-data-in-userdefaults) auth token stored in UserDefaults instead of the Keychain
        UserDefaults.standard.set(apiKey, forKey: "authToken")

        // SEED 3 [BLOCKER] (core/customer-data-in-logs) customer identifier in logs
        print("loading balance for \(msisdn)")

        let url = URL(string: "https://api.internal/balance/\(msisdn)")!
        // SEED 4 [BLOCKER] (swift/force-operations) force-unwrapped optional in a production path

        URLSession.shared.dataTask(with: url) { data, _, _ in
            // SEED 5 [BLOCKER] (swift/retain-cycle) strong self captured in an escaping closure stored by the object
            self.handlers.append { print("done") }

            // SEED 6 [BLOCKER] (swift/force-operations) force try
            let decoded = try! JSONDecoder().decode([String: String].self, from: data!)

            // SEED 7 [BLOCKER] (swift/ui-off-main-actor) UI mutated off the main actor
            self.label.text = decoded["balance"]
        }.resume()

        // SEED 8 [BLOCKER] (swift/blocking-main-thread) synchronous network call on the main thread
        let blocking = try? Data(contentsOf: url)
        _ = blocking
    }

    // SEED 9 [BLOCKER] (swift/uncancelled-task) unstructured Task with no cancellation path
    func refresh() {
        Task {
            while true {
                load(msisdn: "0000")
            }
        }
    }
}

extension SeededViolations {

    // SEED 10 [BLOCKER] (core/type-checker-suppression) linter suppression with no explanation and no ticket
    // swiftlint:disable force_cast
    func coerce(_ value: Any) -> String {
        return value as! String
    }

    // SEED 11 [HIGH] (core/untracked-todo) placeholder with no ticket reference
    // TODO: decide whether the scanner should follow symlinks
    func pending() {}
}