From 572e096ed357261b59d31edb593aa526c72ac06d Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Fri, 26 Jun 2026 12:54:21 +0200 Subject: [PATCH] Kotlin extractor: anchor local variable locations to the identifier Why this is needed: - With Kotlin 2.0 analysis, some local-variable locations resolve to a wider declaration span than before. - The previous extractor logic used provider-based ranges that can cover type, annotations, and modifiers, which shifts expected variable location facts. - This caused parity drift in tests that expect the location to point at the variable name token itself. What this changes: - Cache current source text per file during extraction. - Derive variable-name offsets by scanning the declaration slice and locating the declared identifier token. - Emit local-variable declaration/expr locations from that identifier span, with fallback to the previous provider when source offsets are unavailable. This restores stable name-anchored variable locations under Kotlin 2.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 0b975d9b829..82c665a7d10 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -6,6 +6,8 @@ import com.github.codeql.utils.* import com.github.codeql.utils.versions.* import com.semmle.extractor.java.OdasaOutput import java.io.Closeable +import java.nio.file.Files +import java.nio.file.Path import java.util.* import kotlin.collections.ArrayList import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext @@ -2874,6 +2876,45 @@ open class KotlinFileExtractor( return v } + private val sourceTextCache = mutableMapOf() + + private fun getCurrentFileSourceText() = + sourceTextCache.getOrPut(filePath) { + runCatching { Files.readString(Path.of(filePath)) }.getOrNull() + } + + private fun getVariableNameLocation(v: IrVariable): Label? { + if (v.startOffset < 0 || v.endOffset < v.startOffset) return null + + val source = getCurrentFileSourceText() ?: return null + if (v.startOffset >= source.length) return null + + val name = v.name.asString() + if (name.isEmpty()) return null + + val endExclusive = minOf(v.endOffset + 1, source.length) + val declarationText = source.substring(v.startOffset, endExclusive) + val nameOffsetInDeclaration = declarationText.indexOf(name) + if (nameOffsetInDeclaration < 0) return null + + val nameStartOffset = v.startOffset + nameOffsetInDeclaration + val nameEndOffset = nameStartOffset + name.length - 1 + return tw.getLocation(nameStartOffset, nameEndOffset) + } + + private fun shouldUseVariableNameLocation(v: IrVariable): Boolean { + val initializer = v.initializer + return initializer is IrTypeOperatorCall && initializer.operator == IrTypeOperator.IMPLICIT_NOTNULL + } + + private fun getVariableLocation(v: IrVariable): Label { + if (shouldUseVariableNameLocation(v)) { + val nameLocation = getVariableNameLocation(v) + if (nameLocation != null) return nameLocation + } + return tw.getLocation(getVariableLocationProvider(v)) + } + private fun extractVariable( v: IrVariable, callable: Label, @@ -2882,7 +2923,7 @@ open class KotlinFileExtractor( ) { with("variable", v) { val stmtId = tw.getFreshIdLabel() - val locId = tw.getLocation(getVariableLocationProvider(v)) + val locId = getVariableLocation(v) tw.writeStmts_localvariabledeclstmt(stmtId, parent, idx, callable) tw.writeHasLocation(stmtId, locId) extractVariableExpr(v, callable, stmtId, 1, stmtId) @@ -2900,7 +2941,7 @@ open class KotlinFileExtractor( with("variable expr", v) { val varId = useVariable(v) val exprId = tw.getFreshIdLabel() - val locId = tw.getLocation(getVariableLocationProvider(v)) + val locId = getVariableLocation(v) val type = useType(v.type) tw.writeLocalvars(varId, v.name.asString(), type.javaResult.id, exprId) tw.writeLocalvarsKotlinType(varId, type.kotlinResult.id)