Skip to content

Kotlin: Add support for Kotlin 2.4.0#21952

Draft
andersfugmann wants to merge 11 commits into
mainfrom
andersfugmann/kotlin-2.4
Draft

Kotlin: Add support for Kotlin 2.4.0#21952
andersfugmann wants to merge 11 commits into
mainfrom
andersfugmann/kotlin-2.4

Conversation

@andersfugmann

Copy link
Copy Markdown
Contributor

Summary

Adds support for Kotlin 2.4.0 to the CodeQL Kotlin extractor. This follows the established two-phase upgrade process documented in java/kotlin-extractor/UPGRADING.md.

Changes

Phase 1 — Version limit

  • Raised the supported version ceiling from 2.3.30 to 2.4.10
  • Updated documentation (supported-versions-compilers.rst)

Phase 2 — API compatibility

  • Downloaded 2.4.0 compiler jars (LFS)
  • Created compatibility layer (v_2_4_0/IrCompat.kt) for the unified parameters model:
    • valueParametersparameters.filter { it.kind == Regular }
    • extensionReceiverParameterparameters.firstOrNull { it.kind == ExtensionReceiver }
    • getValueArgument(i) / putValueArgument(i, v) → offset-adjusted arguments[i + offset]
  • Migrated plugin registration to CompilerPluginRegistrar (service file is version-conditional in BUILD.bazel)
  • Fixed annotation creation to use IrAnnotationImpl.fromSymbolOwner() (2.4.0 type hierarchy change)

Test expectations

  • Updated exprs and reflection library test expectations for IrRichPropertyReference — a new IR node in 2.4.0 for bound callable references that is not yet handled by the extractor

Known limitations (follow-up PR)

Kotlin 2.4.0 introduces IrRichFunctionReference and IrRichPropertyReference nodes for bound callable references. The extractor does not yet visit these nodes, resulting in slightly reduced extraction coverage for bound property/function references. This causes:

  • 2 CONSISTENCY callArgs entries (parameter count mismatch on unhandled bound refs)
  • ~13 fewer extracted sub-expressions in delegated property references

This will be addressed in a dedicated follow-up PR.

Testing

  • CI "Check Kotlin versions" workflow: all versions 1.8.0–2.4.0 pass ✅
  • Local language-tests-2 (20 slices, 3,398 tests): all pass ✅
  • Backward compatibility: versions 1.8.0–2.3.21 unaffected ✅

andersfugmann and others added 6 commits June 4, 2026 12:58
Raise the acceptable version limit to 2.4.10 and update documentation
to reflect Kotlin 2.4.x support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Download kotlin-compiler-2.4.0.jar, kotlin-compiler-embeddable-2.4.0.jar,
and kotlin-stdlib-2.4.0.jar from Maven Central. Add 2.4.0 to the VERSIONS
list and update MODULE.bazel via bazel mod tidy.

The extractor does not yet compile against 2.4.0 due to removed APIs
(valueParameters, extensionReceiverParameter, getValueArgument, etc.).
Version-specific compatibility shims are needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add version-specific compatibility wrappers (v_2_4_0/IrCompat.kt) for
  removed APIs: valueParameters, extensionReceiverParameter, extensionReceiver,
  getValueArgument, putValueArgument, valueArgumentsCount, typeArgumentsCount,
  getTypeArgument, addAnnotations, setAnnotations, setDispatchReceiverParameter
- Add pre-2.4.0 pass-through implementations (v_1_8_0/IrCompat.kt)
- Migrate plugin registration from ComponentRegistrar to CompilerPluginRegistrar
  for 2.4.0 (v_2_4_0/Kotlin2ComponentRegistrar.kt)
- Add META-INF service file for CompilerPluginRegistrar
- Update all extractor source files to use codeQl* compat functions
- All versions (1.8.0 through 2.4.0) build successfully

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CompilerPluginRegistrar service file must only be included in the
2.4.0 jar. Older Kotlin versions (2.3.x and below) read this service
file and try to cast the class to CompilerPluginRegistrar, but the
older version extractor only implements ComponentRegistrar, causing a
ClassCastException at runtime.

For 2.4.0, the registrar implements both ComponentRegistrar (no-op, as
extensionArea was removed) and CompilerPluginRegistrar (actual
registration via ExtensionStorage).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In 2.4.0, IrMemberAccessExpression.arguments includes all parameters
(dispatch receiver, extension receiver, and regular value args). The
old getValueArgument/putValueArgument/valueArgumentsCount APIs indexed
only value arguments. Fix the compat layer to apply the correct offset
when translating between the old index-based API and the new unified
arguments list.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In 2.4.0, annotation lists are typed as List<IrAnnotation> and
IrConstructorCallImpl does not extend IrAnnotation. Replace all
IrConstructorCallImpl.fromSymbolOwner() calls that create annotations
with a compat wrapper codeQlAnnotationFromSymbolOwner() that uses
IrAnnotationImpl.fromSymbolOwner() in 2.4.0 (returning proper
IrAnnotation instances) and IrConstructorCallImpl.fromSymbolOwner()
in pre-2.4.0 versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andersfugmann

andersfugmann commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: Handle IrRichFunctionReference (minor)

Kotlin 2.4.0 introduces two new IR nodes for bound callable references.

Update: The IrRichPropertyReference issue was a red herring. The actual bug was that our codeQlExtensionReceiver compat function did not handle IrPropertyReference (where symbol.owner is IrProperty, not IrFunction). Fixed in the latest commit — no test expectation changes needed, full extraction parity maintained.

The IrRichFunctionReference and IrRichPropertyReference nodes are introduced in a later lowering phase (JVM backend) — they do NOT appear in the IR that reaches our IrGenerationExtension. Our plugin sees the pre-lowered IrPropertyReference/IrFunctionReference nodes with bound receivers stored in the unified arguments list.

Remaining (truly minor) follow-up

No follow-up PR is currently needed. All 3,398 kotlin2 language tests pass with the original expected files. If future Kotlin versions change when rich references appear in the pipeline, we may need to revisit.

In 2.4.0's unified parameters model, IrPropertyReference stores the bound
extension receiver in the arguments list (indexed via the getter's extension
receiver parameter). Our codeQlExtensionReceiver compat function only
handled the case where symbol.owner is an IrFunction, returning null for
IrPropertyReference (where symbol.owner is IrProperty).

Fix: when the direct cast to IrFunction fails, look at the getter/setter
function's parameters to find the extension receiver index.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andersfugmann andersfugmann force-pushed the andersfugmann/kotlin-2.4 branch from 86af0c8 to 5732a99 Compare June 8, 2026 09:51
andersfugmann and others added 3 commits June 8, 2026 15:02
Kotlin 2.4.0 no longer supports -language-version 1.9 (the last 1.x
version). Clamp get_language_version() in versions.bzl to minimum 2.0
for extractor builds, and update the supported versions documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l query style

- Fix BUILD.bazel formatting (buildifier line wrapping)
- Fix change-note category: 'deprecation' -> 'deprecated'
- Replace unsafe list casts with filterIsInstance to satisfy
  the possiblyThrowingExpressions internal query

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Kotlin 2.4.0 no longer supports -language-version 1.9. Update all
integration tests that explicitly pass this flag to use 2.0 instead.
Also update the extractor_information_kotlin1 expected output since
-language-version 2.0 causes the extractor to report 'Uses Kotlin 2: true'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andersfugmann andersfugmann force-pushed the andersfugmann/kotlin-2.4 branch from 0117e40 to f3037d6 Compare June 8, 2026 14:25
@andersfugmann

Copy link
Copy Markdown
Contributor Author

/qlucie

- Make registerExtractorExtension protected (called from subclass)
- Change codeQlExtensionReceiver from var to val (setter unused)
- Update integration test expected files for K2 behavior:
  - Add DB-CHECK.expected for known K2 cross-extractor consistency errors
  - Update result expectations for K2 type resolution differences
- Update language test expected files (pathsanitizer, CWE-312):
  - K2 resolves Path.toString() and CharSequence.toString() with
    different callable IDs than the Java extractor, causing
    callableBinding consistency errors and lost taint flow results

These are pre-existing K2 issues documented in
github/codeql-kotlin-team#196, originally worked around by pinning
tests to -language-version 1.9 in PR #16554 (May 2024). Kotlin 2.4.0
drops 1.9 support, forcing us to accept these known K2 differences.

Verified: the same DB-CHECK errors occur with the released CodeQL CLI
(v2.23.9) and Kotlin 2.3.20 when using -language-version 2.0,
confirming these are K2 behavioral differences unrelated to our 2.4.0
extractor changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andersfugmann andersfugmann force-pushed the andersfugmann/kotlin-2.4 branch from 4af0c1e to d0e6521 Compare June 10, 2026 13:12
@andersfugmann

Copy link
Copy Markdown
Contributor Author

Analysis: Test expected file changes (K2 behavioral differences)

Several integration tests and language tests now produce different results because Kotlin 2.4.0 drops -language-version 1.9 support, forcing us to use -language-version 2.0 (K2 frontend).

Background

These tests were originally pinned to -language-version 1.9 in PR #16554 (May 2024) by @igfoo as a known workaround. A follow-up issue was filed: codeql-kotlin-team#196 — "Look into integration test failures in Kotlin 2 mode" (still open, unresolved).

Root cause

The K2 compiler frontend resolves certain method calls differently than K1. Specifically, toString() on java.nio.file.Path and java.lang.CharSequence gets a different callable ID in the Kotlin extractor than what the Java extractor produces. This causes:

  1. DB-CHECK VALUE_NOT_IN_TYPE errors — the Kotlin extractor emits a callableBinding referencing a callable ID that the Java extractor never created
  2. Lost taint flow results (31 in pathsanitizer, 1 in CWE-312) — dataflow analysis cannot follow taint through calls whose callable IDs do not resolve

Verification

I verified this is a pre-existing K2 issue unrelated to our 2.4.0 extractor changes:

  • Ran the pathsanitizer test with the released CodeQL CLI (v2.23.9) + Kotlin 2.3.20 + -language-version 2.0 → same callableBinding errors
  • The callableBinding generation code (KotlinFileExtractor.kt) is unchanged by this PR — our changes are only API compatibility shims in the v_2_4_0 directory
  • The K2 frontend (not our extractor) determines which callable a method call resolves to

Disposition

Test Change Justification
enhanced-nullability DB-CHECK.expected added K2 enhanced nullability changes type resolution
kotlin_java_lowering_wildcards DB-CHECK.expected + result update K2 resolves wildcards more precisely
external-property-overloads Path format change K2 binary location format differs
file_classes Lost AKt.class K2 does not expose pre-compiled classpath file-classes in IR
java-interface-redeclares-tostring All results lost K2 does not synthesize IR stubs for Java interface methods
pathsanitizer DB-CHECK.expected + 31 lost taint flows K2 Path.toString() callable ID mismatch
CWE-312 DB-CHECK.expected + 1 lost result K2 CharSequence.toString() callable ID mismatch

Follow-up

The taint flow losses are a real quality regression for mixed Java/Kotlin projects using Kotlin 2.0+. This affects all users, not just our tests — it was simply hidden because tests used -language-version 1.9. This should be addressed in codeql-kotlin-team#196.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant