Open-Source Wikis

/

Kotlin

/

How to contribute

/

Debugging

JetBrains/kotlin

Debugging

Compilers are notoriously hard to debug. The Kotlin compiler is no exception, but the codebase has accumulated tooling that makes most pain points navigable once you know where to look.

Where errors come from

Symptom Likely source
Compile error or warning shown to the user A diagnostic. Search for the message ID in compiler/fir/checkers/gen/.../FirErrors.kt (K2) or compiler/frontend/.../diagnostics (K1).
java.lang.IllegalStateException from the compiler Usually a contract violation in IR (compiler/ir/ir.validation/) or FIR (FirResolvePhase invariants).
Wrong bytecode A JVM lowering or codegen pass in compiler/ir/backend.jvm/ or compiler/backend/.
Wrong JS/Wasm/Native output The corresponding backend (compiler/ir/backend.{js,wasm}/, kotlin-native/backend.native/).
Daemon-related issue compiler/daemon/.
Gradle / KGP issue libraries/tools/kotlin-gradle-plugin*/.
IDE issue The IntelliJ Kotlin plugin lives in a different repo (JetBrains/intellij-community). The Analysis API in this repo (analysis/) is the surface it uses.

Reproducing a compiler issue

  1. Get the smallest possible Kotlin source that triggers the problem.
  2. Drop it under an appropriate testData/ directory and add the right directives. Inspecting an existing fixture in the same directory is the fastest way to pick the right ones.
  3. Run the test class that processes that directory:
    ./gradlew :compiler:test --tests "<FQCN>" -q
  4. If the test runner doesn't see your file, run ./gradlew generateTests and re-run.

IR / FIR dumps

The fastest way to understand what the compiler is doing is to dump the intermediate representation:

  • FIR dump — many K2 tests already write .fir.txt files alongside the input. The dump shows the resolved FIR tree.
  • IR dump — most codegen tests have an .ir.txt companion showing IR before/after lowering.
  • For ad-hoc investigation, the standalone Fir* and Ir*Renderer classes in compiler/fir/tree/ and compiler/ir/ir.tree/ produce text dumps.

Run a test with -Pkotlin.test.update.test.data=true to write the dumps to disk, then git diff shows what changed. This is a key debugging technique — dump current behavior, change one thing, dump again, diff.

Diagnostics: K1 vs K2

K1 and K2 use different diagnostic registration paths:

  • K1 — diagnostics defined in compiler/frontend/.../diagnostics, surfaced via BindingContext. Usually edited together with the resolution code that reports them.
  • K2 — diagnostics defined in compiler/fir/checkers/checkers-component-generator/src/.../FirDiagnosticsList.kt, regenerated into compiler/fir/checkers/gen/.../FirErrors.kt and FirErrorsDefaultMessages.kt. The Analysis API mirrors them in analysis/analysis-api-fir/gen/.../KaFirDiagnostics*.

If you change the diagnostic surface, also run the regeneration tooling (compiler/fir/checkers/checkers-component-generator/) — the gen output is committed.

Common pitfalls

From .ai/guidelines.md:

  • Don't edit *Generated.java test files — regenerate them.
  • The IDE plugin is a different repoJetBrains/intellij-community. If your repro touches the IDE, you may need to bisect across both repos.
  • The K1 vs K2 boundary — many features only land in K2. If a behavior differs between kotlinc -language-version 1.9 and the default, you're hitting the frontend boundary.

Logging

The compiler uses org.jetbrains.kotlin.util.KotlinLogger (and area-specific loggers). Verbose output for a CLI invocation:

./dist/kotlinc/bin/kotlinc -verbose <args>

For the daemon, enable JMX/JVM args via KOTLIN_DAEMON_JVM_OPTIONS or the IDE's "Compiler" settings. The daemon writes logs to a per-session file under the system temp directory.

Attaching a debugger

When invoking kotlinc directly:

KOTLIN_OPTS="-Xdebug -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" \
  ./dist/kotlinc/bin/kotlinc <args>

When debugging tests in IntelliJ, just run the test class with the standard "Debug" action. For Gradle-launched processes (e.g., daemon-mode tests, KGP integration tests) use --debug-jvm:

./gradlew :compiler:test --tests "..." --debug-jvm

The Gradle worker waits on port 5005 until a debugger attaches.

When something is wrong with bytecode

If the compiler accepts the source but the produced .class file is wrong:

  1. Run a box test (in compiler/testData/codegen/box/) that exercises the runtime behavior — these compile + execute and assert outputs.
  2. Use javap -c -p -v on the produced class file to compare against expected bytecode.
  3. The "writeFlags" / "writeBytecode" trace (search for BytecodeListingTextCollectingVisitor) emits readable bytecode dumps.

When something is wrong with JS or Wasm output

  • For JS, run js/js.tests/ and inspect the .js output next to the test input.
  • For Wasm, run wasm/wasm.tests/. The Wasm IR is text-printable; see wasm/wasm.ir.

When something is wrong with Native

The Native backend is a different beast. Read kotlin-native/HACKING.md first. The most useful tool is kotlinc-native -Xverbose-phases=ALL to see each lowering pass, and the --print-ir family of compiler flags.

Asking for help

  • #compiler and #analysis-api channels on the Kotlin Slack are the fast paths for bug repros.
  • File a YouTrack ticket if you can isolate a reproducer. CI artifacts and test data can be attached.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Debugging – Kotlin wiki | Factory