Skip to content

Commit 9a80572

Browse files
ddolovovSpace Team
authored andcommitted
IR Validator: New checker for nested IR element offsets
^KT-81475
1 parent 7471f03 commit 9a80572

File tree

8 files changed

+579
-3
lines changed

8 files changed

+579
-3
lines changed

compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/pipeline/convertToIr.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.ir.util.KotlinMangler
4141
import org.jetbrains.kotlin.ir.util.SymbolTable
4242
import org.jetbrains.kotlin.ir.validation.IrValidationError
4343
import org.jetbrains.kotlin.ir.validation.IrValidatorConfig
44+
import org.jetbrains.kotlin.ir.validation.checkers.IrNestedOffsetRangeChecker
4445
import org.jetbrains.kotlin.ir.validation.checkers.symbol.IrVisibilityChecker
4546
import org.jetbrains.kotlin.ir.validation.checkers.declaration.IrFieldVisibilityChecker
4647
import org.jetbrains.kotlin.ir.validation.checkers.declaration.IrExpressionBodyInFunctionChecker
@@ -513,6 +514,9 @@ private class Fir2IrPipeline(
513514
// so visibility checks are only performed if requested via a flag, and in tests.
514515
withCheckers(IrVisibilityChecker.Strict)
515516
}
517+
.applyIf(fir2IrConfiguration.irVerificationSettings.enableIrNestedOffsetsChecks) {
518+
withCheckers(IrNestedOffsetRangeChecker)
519+
}
516520
.applyIf(extension == null) {
517521
// KT-80065: This checker is known to trigger on a lot of internal and external compiler plugins,
518522
// while most of them, somehow, work. It is disabled for now, not to cause too much breakage.

compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConfiguration.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Fir2IrConfiguration private constructor(
4747
val validateForKlibSerialization: Boolean,
4848
val enableIrVisibilityChecks: Boolean,
4949
val enableIrVarargTypesChecks: Boolean,
50+
val enableIrNestedOffsetsChecks: Boolean,
5051
)
5152

5253
companion object {
@@ -70,6 +71,7 @@ class Fir2IrConfiguration private constructor(
7071
mode = compilerConfiguration.get(CommonConfigurationKeys.VERIFY_IR, IrVerificationMode.NONE),
7172
enableIrVisibilityChecks = compilerConfiguration.enableIrVisibilityChecks,
7273
enableIrVarargTypesChecks = compilerConfiguration.enableIrVarargTypesChecks,
74+
enableIrNestedOffsetsChecks = compilerConfiguration.enableIrNestedOffsetsChecks,
7375
validateForKlibSerialization = false,
7476
),
7577
carefulApproximationOfContravariantProjectionForSam = compilerConfiguration.get(JVMConfigurationKeys.SAM_CONVERSIONS) != JvmClosureGenerationScheme.CLASS
@@ -95,6 +97,7 @@ class Fir2IrConfiguration private constructor(
9597
mode = compilerConfiguration.get(CommonConfigurationKeys.VERIFY_IR, IrVerificationMode.NONE),
9698
enableIrVisibilityChecks = compilerConfiguration.enableIrVisibilityChecks,
9799
enableIrVarargTypesChecks = compilerConfiguration.enableIrVarargTypesChecks,
100+
enableIrNestedOffsetsChecks = compilerConfiguration.enableIrNestedOffsetsChecks,
98101
validateForKlibSerialization = true,
99102
),
100103
carefulApproximationOfContravariantProjectionForSam = false,
@@ -121,6 +124,7 @@ class Fir2IrConfiguration private constructor(
121124
mode = compilerConfiguration.get(CommonConfigurationKeys.VERIFY_IR, IrVerificationMode.NONE),
122125
enableIrVisibilityChecks = compilerConfiguration.enableIrVisibilityChecks,
123126
enableIrVarargTypesChecks = compilerConfiguration.enableIrVarargTypesChecks,
127+
enableIrNestedOffsetsChecks = compilerConfiguration.enableIrNestedOffsetsChecks,
124128
validateForKlibSerialization = false,
125129
),
126130
carefulApproximationOfContravariantProjectionForSam = compilerConfiguration.get(JVMConfigurationKeys.SAM_CONVERSIONS) != JvmClosureGenerationScheme.CLASS,

compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/IrValidationPhase.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.ModuleLoweringPass
1010
import org.jetbrains.kotlin.config.*
1111
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
1212
import org.jetbrains.kotlin.ir.validation.*
13+
import org.jetbrains.kotlin.ir.validation.checkers.IrNestedOffsetRangeChecker
1314
import org.jetbrains.kotlin.ir.validation.checkers.declaration.IrExpressionBodyInFunctionChecker
1415
import org.jetbrains.kotlin.ir.validation.checkers.declaration.IrFieldVisibilityChecker
1516
import org.jetbrains.kotlin.ir.validation.checkers.expression.InlineFunctionUseSiteChecker
@@ -45,6 +46,9 @@ abstract class IrValidationBeforeLoweringPhase<Context : LoweringContext>(contex
4546
.applyIf(context.configuration.enableIrVisibilityChecks) {
4647
withCheckers(IrVisibilityChecker.Strict)
4748
}
49+
.applyIf(context.configuration.enableIrNestedOffsetsChecks) {
50+
withCheckers(IrNestedOffsetRangeChecker)
51+
}
4852
.applyIf(context.configuration.enableIrVarargTypesChecks) {
4953
withVarargChecks()
5054
}
@@ -74,6 +78,9 @@ class IrValidationAfterInliningOnlyPrivateFunctionsPhase<Context : LoweringConte
7478
.applyIf(context.configuration.enableIrVisibilityChecks) {
7579
withCheckers(IrVisibilityChecker.Strict)
7680
}
81+
.applyIf(context.configuration.enableIrNestedOffsetsChecks) {
82+
withCheckers(IrNestedOffsetRangeChecker)
83+
}
7784
//.withTypeChecks() // TODO: Re-enable checking types (KT-68663)
7885
.applyIf(context.configuration.enableIrVarargTypesChecks) {
7986
withVarargChecks()
@@ -92,6 +99,9 @@ class IrValidationAfterInliningAllFunctionsOnTheSecondStagePhase<Context : Lower
9299
.applyIf(context.configuration.enableIrVisibilityChecks) {
93100
withCheckers(IrVisibilityChecker.Relaxed, IrCrossFileFieldUsageChecker, IrValueAccessScopeChecker)
94101
}
102+
.applyIf(context.configuration.enableIrNestedOffsetsChecks) {
103+
withCheckers(IrNestedOffsetRangeChecker)
104+
}
95105
.applyIf(context.configuration.enableIrVarargTypesChecks) {
96106
withVarargChecks()
97107
}
@@ -112,4 +122,7 @@ open class IrValidationAfterLoweringPhase<Context : LoweringContext>(context: Co
112122
override val defaultValidationConfig: IrValidatorConfig
113123
get() = IrValidatorConfig(checkTreeConsistency = true)
114124
.withBasicChecks()
125+
.applyIf(context.configuration.enableIrNestedOffsetsChecks) {
126+
withCheckers(IrNestedOffsetRangeChecker)
127+
}
115128
}

compiler/ir/ir.validation/src/org/jetbrains/kotlin/ir/validation/IrValidatorConfig.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.jetbrains.kotlin.ir.validation
77

88
import org.jetbrains.kotlin.ir.validation.checkers.IrChecker
9+
import org.jetbrains.kotlin.ir.validation.checkers.IrNestedOffsetRangeChecker
910
import org.jetbrains.kotlin.ir.validation.checkers.IrOffsetsChecker
1011
import org.jetbrains.kotlin.ir.validation.checkers.declaration.*
1112
import org.jetbrains.kotlin.ir.validation.checkers.expression.*
@@ -64,5 +65,6 @@ fun IrValidatorConfig.withAllChecks() = withBasicChecks()
6465
IrTypeParameterScopeChecker,
6566
IrCrossFileFieldUsageChecker,
6667
IrFieldVisibilityChecker,
67-
IrExpressionBodyInFunctionChecker
68+
IrExpressionBodyInFunctionChecker,
69+
IrNestedOffsetRangeChecker,
6870
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.ir.validation.checkers
7+
8+
import org.jetbrains.kotlin.ir.IrElement
9+
import org.jetbrains.kotlin.ir.util.render
10+
import org.jetbrains.kotlin.ir.validation.checkers.context.CheckerContext
11+
import org.jetbrains.kotlin.ir.validation.checkers.context.ContextUpdater
12+
import org.jetbrains.kotlin.ir.validation.checkers.context.OffsetRange
13+
import org.jetbrains.kotlin.ir.validation.checkers.context.OffsetRangeChainUpdater
14+
15+
object IrNestedOffsetRangeChecker : IrElementChecker<IrElement>(IrElement::class) {
16+
override val requiredContextUpdaters: Set<ContextUpdater>
17+
get() = setOf(OffsetRangeChainUpdater)
18+
19+
override fun check(
20+
element: IrElement,
21+
context: CheckerContext,
22+
) {
23+
val outerRange = context.offsetRanges.lastOrNull() ?: return
24+
val currentRange = OffsetRange.createIfRealValidOffsets(element, useOwnOffsetsOfInlinedFunctionBlock = true) ?: return
25+
26+
if (currentRange !in outerRange)
27+
context.error(
28+
element,
29+
"The offsets range $currentRange is not within the outer offsets range $outerRange (owner = ${outerRange.owner.render()})"
30+
)
31+
}
32+
}

compiler/ir/ir.validation/src/org/jetbrains/kotlin/ir/validation/checkers/context/CheckerContext.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class CheckerContext(
2626
val parentChain: MutableList<IrElement> = mutableListOf()
2727
val typeParameterScopeStack = ScopeStack<IrTypeParameterSymbol>()
2828
val valueSymbolScopeStack = ScopeStack<IrValueSymbol>()
29+
val offsetRanges: MutableList<OffsetRange> = mutableListOf()
2930

3031
var withinAnnotationUsageSubTree: Boolean = false
3132
private set
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.ir.validation.checkers.context
7+
8+
import org.jetbrains.kotlin.ir.IrElement
9+
import org.jetbrains.kotlin.ir.expressions.IrInlinedFunctionBlock
10+
import org.jetbrains.kotlin.ir.validation.temporarilyPushing
11+
12+
object OffsetRangeChainUpdater : ContextUpdater {
13+
override fun runInNewContext(
14+
context: CheckerContext,
15+
element: IrElement,
16+
block: () -> Unit,
17+
) {
18+
OffsetRange.createIfRealValidOffsets(element, useOwnOffsetsOfInlinedFunctionBlock = false)?.let { newOffsetBoundaries ->
19+
context.offsetRanges.temporarilyPushing(newOffsetBoundaries) {
20+
block()
21+
}
22+
} ?: block()
23+
}
24+
}
25+
26+
class OffsetRange private constructor(val owner: IrElement, val startOffset: Int, val endOffset: Int) {
27+
operator fun contains(other: OffsetRange): Boolean = startOffset <= other.startOffset && endOffset >= other.endOffset
28+
29+
override fun toString() = "[$startOffset:$endOffset]"
30+
31+
companion object {
32+
fun createIfRealValidOffsets(element: IrElement, useOwnOffsetsOfInlinedFunctionBlock: Boolean): OffsetRange? =
33+
if (useOwnOffsetsOfInlinedFunctionBlock || element !is IrInlinedFunctionBlock)
34+
createIfRealValidOffsets(element, element.startOffset, element.endOffset)
35+
else
36+
createIfRealValidOffsets(element, element.inlinedFunctionStartOffset, element.inlinedFunctionEndOffset)
37+
38+
private fun createIfRealValidOffsets(owner: IrElement, startOffset: Int, endOffset: Int): OffsetRange? =
39+
if (startOffset in 0..endOffset) OffsetRange(owner, startOffset, endOffset) else null
40+
}
41+
}

0 commit comments

Comments
 (0)