diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 806f03ec61a..8d786ac9255 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -6292,6 +6292,22 @@ open class KotlinFileExtractor( else -> null } + /** + * For a desugared in-place update such as `v += e` (represented as `v = get(v).op(e)`), + * returns the `IrGetValue` receiver that reads `v`. Its source span is the left-hand-side + * variable reference (the identifier only) in both the K1 and K2 frontends, so it provides a + * frontend-independent location for the update's LHS `VarAccess`. Returns null when [e] is not + * such an in-place update, in which case callers keep the raw location. + */ + private fun getUpdateInPlaceReceiver(e: IrSetValue): IrGetValue? { + val op = getStatementOriginOperator(e.origin) ?: return null + val rhs = e.value + if (rhs !is IrCall || !isNumericFunction(rhs.symbol.owner, op)) return null + val receiver = rhs.dispatchReceiver + return if (receiver is IrGetValue && receiver.symbol.owner == e.symbol.owner) receiver + else null + } + private fun getUpdateInPlaceRHS( origin: IrStatementOrigin?, isExpectedLhs: (IrExpression?) -> Boolean, @@ -7065,7 +7081,22 @@ open class KotlinFileExtractor( extractExprContext(id, locId, callable, exprParent.enclosingStmt) val lhsId = tw.getFreshIdLabel() - val lhsLocId = tw.getLocation(e) + // For a desugared in-place update (`v += e`) the K2 frontend records the set + // operation's end offset past the whole assignment, so `getLocation(e)` would + // span `v += e` rather than just `v`. Locate the LHS `VarAccess` at the update's + // receiver read of `v` instead, whose span is the identifier in both frontends; + // fall back to the raw location when this is not such an in-place update or the + // receiver lacks a usable source span. + val lhsLocId = + (e as? IrSetValue) + ?.let { getUpdateInPlaceReceiver(it) } + ?.takeIf { + it.startOffset != UNDEFINED_OFFSET && + it.endOffset != UNDEFINED_OFFSET && + it.startOffset != SYNTHETIC_OFFSET && + it.endOffset != SYNTHETIC_OFFSET + } + ?.let { tw.getLocation(it) } ?: tw.getLocation(e) extractExprContext(lhsId, lhsLocId, callable, exprParent.enclosingStmt) when (e) { diff --git a/java/ql/test-kotlin2/library-tests/exprs/exprs.expected b/java/ql/test-kotlin2/library-tests/exprs/exprs.expected index 8c86fe5a1b2..59581c27fe2 100644 --- a/java/ql/test-kotlin2/library-tests/exprs/exprs.expected +++ b/java/ql/test-kotlin2/library-tests/exprs/exprs.expected @@ -1743,20 +1743,20 @@ | exprs.kt:267:1:276:1 | Unit | file://:0:0:0:0 | | TypeAccess | | exprs.kt:269:3:269:17 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | LocalVariableDeclExpr | | exprs.kt:269:17:269:17 | 0 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | +| exprs.kt:270:3:270:9 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:270:3:270:14 | ...+=... | exprs.kt:267:1:276:1 | inPlaceOperators | AssignAddExpr | -| exprs.kt:270:3:270:14 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:270:14:270:14 | 1 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | +| exprs.kt:271:3:271:9 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:271:3:271:14 | ...-=... | exprs.kt:267:1:276:1 | inPlaceOperators | AssignSubExpr | -| exprs.kt:271:3:271:14 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:271:14:271:14 | 1 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | +| exprs.kt:272:3:272:9 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:272:3:272:14 | ...*=... | exprs.kt:267:1:276:1 | inPlaceOperators | AssignMulExpr | -| exprs.kt:272:3:272:14 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:272:14:272:14 | 1 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | +| exprs.kt:273:3:273:9 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:273:3:273:14 | .../=... | exprs.kt:267:1:276:1 | inPlaceOperators | AssignDivExpr | -| exprs.kt:273:3:273:14 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:273:14:273:14 | 1 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | +| exprs.kt:274:3:274:9 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:274:3:274:14 | ...%=... | exprs.kt:267:1:276:1 | inPlaceOperators | AssignRemExpr | -| exprs.kt:274:3:274:14 | updated | exprs.kt:267:1:276:1 | inPlaceOperators | VarAccess | | exprs.kt:274:14:274:14 | 1 | exprs.kt:267:1:276:1 | inPlaceOperators | IntegerLiteral | | exprs.kt:278:8:278:66 | T | file://:0:0:0:0 | | TypeAccess | | exprs.kt:278:8:278:66 | T[] | file://:0:0:0:0 | | TypeAccess |