From fe38154431e086823e5ca549a2a7df4ac492962e Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Tue, 14 Jul 2026 14:37:28 +0200 Subject: [PATCH] Kotlin: include `!!` operands in qualified-call locations under K1 For a call qualified through a not-null assertion (e.g. `s!!.foo()`), `TrapWriter.getStartOffset` derives the call's start from its receiver. Under K1 the receiver is the `CHECK_NOT_NULL`/EXCLEXCL intrinsic IrCall, whose own `startOffset` points at the first `!` character rather than at the operand `s`. The enclosing call therefore inherited the `!!` offset and omitted the receiver operand from its span. The `!!` intrinsic carries its operand as value argument 0 (not as a dispatch/extension receiver), so the existing receiver-based adjustment did not reach it. Extend `getStartOffset` to also consider value argument 0 of an EXCLEXCL call, recursively. This makes a `!!`-qualified call span from the operand, matching K2, which already anchors the receiver at the operand. The change affects source locations only; AST structure, call targets, and dataflow relationships are unchanged. K2 output is unaffected because its offsets already start at the operand. Fully converges dataflow/notnullexpr and exprs/binop between the K1 and K2 suites and removes 6 divergent rows overall. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86bfc022-3ecc-4746-ba23-3b76c4e4c3e4 --- .../kotlin-extractor/src/main/kotlin/TrapWriter.kt | 14 +++++++++++++- .../dataflow/notnullexpr/test.expected | 2 +- .../library-tests/exprs/binop.expected | 2 +- .../library-tests/exprs/exprs.expected | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt index 92a16c55724..9ea794b24d7 100644 --- a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt +++ b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt @@ -2,6 +2,7 @@ package com.github.codeql import com.github.codeql.KotlinUsesExtractor.LocallyVisibleFunctionLabels import com.github.codeql.utils.versions.codeQlExtensionReceiver +import com.github.codeql.utils.versions.codeQlGetValueArgument import com.semmle.extractor.java.PopulateFile import com.semmle.util.unicode.UTF8Util import java.io.BufferedWriter @@ -14,6 +15,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.declarations.path import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET /** @@ -347,7 +349,17 @@ open class FileTrapWriter( // Calls have incorrect startOffset, so we adjust them: val dr = e.dispatchReceiver?.let { getStartOffset(it) } val er = e.codeQlExtensionReceiver?.let { getStartOffset(it) } - offsetMinOf(e.startOffset, dr, er) + // The `!!` (CHECK_NOT_NULL / EXCLEXCL) intrinsic carries its operand as value + // argument 0 rather than as a dispatch/extension receiver, and in K1 its own + // startOffset points at the `!` token rather than the operand. Consider the + // operand's start so that a call qualified through a `!!` expression (e.g. + // `s!!.foo()`) spans from the operand. K2 already starts at the operand, so it is + // unaffected. + val exclOperand = + if (e.origin == IrStatementOrigin.EXCLEXCL) + e.codeQlGetValueArgument(0)?.let { getStartOffset(it) } + else null + offsetMinOf(e.startOffset, dr, er, exclOperand) } else -> e.startOffset } diff --git a/java/ql/test-kotlin1/library-tests/dataflow/notnullexpr/test.expected b/java/ql/test-kotlin1/library-tests/dataflow/notnullexpr/test.expected index e5f44bdd7a8..d2a154f6b0a 100644 --- a/java/ql/test-kotlin1/library-tests/dataflow/notnullexpr/test.expected +++ b/java/ql/test-kotlin1/library-tests/dataflow/notnullexpr/test.expected @@ -1 +1 @@ -| NotNullExpr.kt:7:14:7:20 | taint(...) | NotNullExpr.kt:8:12:8:33 | getQueryParameter(...) | +| NotNullExpr.kt:7:14:7:20 | taint(...) | NotNullExpr.kt:8:10:8:33 | getQueryParameter(...) | diff --git a/java/ql/test-kotlin1/library-tests/exprs/binop.expected b/java/ql/test-kotlin1/library-tests/exprs/binop.expected index c0d798a2679..83dad7f94b5 100644 --- a/java/ql/test-kotlin1/library-tests/exprs/binop.expected +++ b/java/ql/test-kotlin1/library-tests/exprs/binop.expected @@ -96,7 +96,7 @@ | exprs.kt:141:12:141:20 | ... + ... | exprs.kt:141:12:141:14 | 123 | exprs.kt:141:18:141:20 | 456 | | exprs.kt:167:8:167:16 | ... (value not-equals) ... | exprs.kt:167:8:167:8 | r | exprs.kt:167:13:167:16 | null | | exprs.kt:196:31:196:37 | ... + ... | exprs.kt:196:31:196:32 | getA1(...) | exprs.kt:196:36:196:37 | a2 | -| exprs.kt:211:20:211:29 | ... + ... | exprs.kt:211:19:211:21 | ...!! | exprs.kt:211:28:211:28 | 5 | +| exprs.kt:211:19:211:29 | ... + ... | exprs.kt:211:19:211:21 | ...!! | exprs.kt:211:28:211:28 | 5 | | exprs.kt:212:19:212:25 | ... + ... | exprs.kt:212:19:212:21 | ...!! | exprs.kt:212:25:212:25 | 5 | | exprs.kt:230:12:230:47 | ... (value equals) ... | exprs.kt:230:12:230:27 | notNullPrimitive | exprs.kt:230:32:230:47 | notNullPrimitive | | exprs.kt:231:12:231:48 | ... (value equals) ... | exprs.kt:231:12:231:27 | notNullPrimitive | exprs.kt:231:32:231:48 | nullablePrimitive | diff --git a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected index 60915f59a4d..b92d94021fb 100644 --- a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected +++ b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected @@ -1593,7 +1593,7 @@ | exprs.kt:211:9:211:29 | b2 | exprs.kt:206:5:217:5 | x | LocalVariableDeclExpr | | exprs.kt:211:19:211:19 | s | exprs.kt:206:5:217:5 | x | VarAccess | | exprs.kt:211:19:211:21 | ...!! | exprs.kt:206:5:217:5 | x | NotNullExpr | -| exprs.kt:211:20:211:29 | ... + ... | exprs.kt:206:5:217:5 | x | AddExpr | +| exprs.kt:211:19:211:29 | ... + ... | exprs.kt:206:5:217:5 | x | AddExpr | | exprs.kt:211:28:211:28 | 5 | exprs.kt:206:5:217:5 | x | IntegerLiteral | | exprs.kt:212:9:212:25 | b3 | exprs.kt:206:5:217:5 | x | LocalVariableDeclExpr | | exprs.kt:212:19:212:19 | s | exprs.kt:206:5:217:5 | x | VarAccess |