kotlin

Seeded Kotlin defects — GlobalScope launches, swallowed CancellationException, leaked Context.

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 9 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
2BLOCKERkotlin/context-leakActivity Context held by a singleton — leaks the whole view tree
4BLOCKERkotlin/globalscopeGlobalScope leaks work past the owner's lifecycle
5BLOCKERkotlin/blocking-in-coroutineblocking call inside a coroutine
6BLOCKERcore/customer-data-in-logscustomer identifier in logs
7BLOCKERkotlin/broad-catch-cancellationbroad catch also swallows CancellationException
8BLOCKERkotlin/force-unwrap!! non-null assertion in a production path
9BLOCKERkotlin/blocking-in-coroutinerunBlocking inside a suspend function
11BLOCKERcore/type-checker-suppressioncompiler warning suppressed with no explanation and no ticket
3HIGHkotlin/public-mutable-statepublic MutableStateFlow — callers can mutate UI state directly
10HIGHkotlin/data-class-vardata class with var properties breaks copy/equality semantics

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, 9 BLOCKER and 2 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/kotlin/SeededViolations.kt · 57 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.
package com.redline.seed

import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

object SeededViolations {

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

    // SEED 2 [BLOCKER] (kotlin/context-leak) Activity Context held by a singleton — leaks the whole view tree
    var context: Context? = null

    // SEED 3 [HIGH] (kotlin/public-mutable-state) public MutableStateFlow — callers can mutate UI state directly
    val state = MutableStateFlow("")

    fun load(msisdn: String) {
        // SEED 4 [BLOCKER] (kotlin/globalscope) GlobalScope leaks work past the owner's lifecycle
        GlobalScope.launch {
            // SEED 5 [BLOCKER] (kotlin/blocking-in-coroutine) blocking call inside a coroutine
            Thread.sleep(1000)

            // SEED 6 [BLOCKER] (core/customer-data-in-logs) customer identifier in logs
            println("loading balance for $msisdn")

            try {
                fetch(msisdn)
            } catch (e: Exception) {
                // SEED 7 [BLOCKER] (kotlin/broad-catch-cancellation) broad catch also swallows CancellationException
            }
        }
    }

    // SEED 8 [BLOCKER] (kotlin/force-unwrap) !! non-null assertion in a production path
    fun title(map: Map<String, String>): String = map["title"]!!

    private suspend fun fetch(msisdn: String): String {
        // SEED 9 [BLOCKER] (kotlin/blocking-in-coroutine) runBlocking inside a suspend function
        return runBlocking(Dispatchers.Main) { "$API_KEY/$msisdn" }
    }
}

// SEED 10 [HIGH] (kotlin/data-class-var) data class with var properties breaks copy/equality semantics
data class Account(var id: String, var balance: Int)

// SEED 11 [BLOCKER] (core/type-checker-suppression) compiler warning suppressed with no explanation and no ticket
@Suppress("UNCHECKED_CAST")
fun <T> coerce(value: Any): T = value as T