[JS IR BE] Enable IrValidator
This commit is contained in:
+45
-6
@@ -20,22 +20,31 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
|||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrDynamicType
|
||||||
|
import org.jetbrains.kotlin.ir.types.isUnit
|
||||||
import org.jetbrains.kotlin.ir.types.makeNullable
|
import org.jetbrains.kotlin.ir.types.makeNullable
|
||||||
import org.jetbrains.kotlin.ir.types.toKotlinType
|
import org.jetbrains.kotlin.ir.types.toKotlinType
|
||||||
import org.jetbrains.kotlin.ir.util.isAnnotationClass
|
import org.jetbrains.kotlin.ir.util.isAnnotationClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
typealias ReportError = (element: IrElement, message: String) -> Unit
|
typealias ReportError = (element: IrElement, message: String) -> Unit
|
||||||
|
|
||||||
class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: ReportError, val ensureAllNodesAreDifferent: Boolean) : IrElementVisitorVoid {
|
class CheckIrElementVisitor(
|
||||||
|
val builtIns: KotlinBuiltIns,
|
||||||
|
val reportError: ReportError,
|
||||||
|
val config: IrValidatorConfig
|
||||||
|
) : IrElementVisitorVoid {
|
||||||
|
|
||||||
val set = mutableSetOf<IrElement>()
|
val set = mutableSetOf<IrElement>()
|
||||||
|
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
if (ensureAllNodesAreDifferent) {
|
if (config.ensureAllNodesAreDifferent) {
|
||||||
if (set.contains(element))
|
if (set.contains(element))
|
||||||
reportError(element, "Duplicate IR node")
|
reportError(element, "Duplicate IR node")
|
||||||
set.add(element)
|
set.add(element)
|
||||||
@@ -44,6 +53,9 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun IrExpression.ensureTypeIs(expectedType: KotlinType) {
|
private fun IrExpression.ensureTypeIs(expectedType: KotlinType) {
|
||||||
|
if (!config.checkTypes)
|
||||||
|
return
|
||||||
|
|
||||||
// TODO: compare IR types instead.
|
// TODO: compare IR types instead.
|
||||||
if (expectedType != type.toKotlinType()) {
|
if (expectedType != type.toKotlinType()) {
|
||||||
reportError(this, "unexpected expression.type: expected $expectedType, got ${type.toKotlinType()}")
|
reportError(this, "unexpected expression.type: expected $expectedType, got ${type.toKotlinType()}")
|
||||||
@@ -51,7 +63,7 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun IrSymbol.ensureBound(expression: IrExpression) {
|
private fun IrSymbol.ensureBound(expression: IrExpression) {
|
||||||
if (!this.isBound) {
|
if (!this.isBound && expression.type !is IrDynamicType) {
|
||||||
reportError(expression, "Unbound symbol ${this}")
|
reportError(expression, "Unbound symbol ${this}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,8 +175,8 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
|||||||
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> builtIns.booleanType
|
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> builtIns.booleanType
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT && typeOperand != builtIns.unitType) {
|
if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT && !typeOperand.isUnit()) {
|
||||||
reportError(expression, "typeOperand is $typeOperand")
|
reportError(expression, "typeOperand is ${typeOperand.render()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check IMPLICIT_NOTNULL's argument type.
|
// TODO: check IMPLICIT_NOTNULL's argument type.
|
||||||
@@ -200,7 +212,7 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
|||||||
override fun visitClass(declaration: IrClass) {
|
override fun visitClass(declaration: IrClass) {
|
||||||
super.visitClass(declaration)
|
super.visitClass(declaration)
|
||||||
|
|
||||||
if (!declaration.isAnnotationClass) {
|
if (config.checkDescriptors && !declaration.isAnnotationClass) {
|
||||||
// Check that all functions and properties from memberScope are present in IR
|
// Check that all functions and properties from memberScope are present in IR
|
||||||
// (including FAKE_OVERRIDE ones).
|
// (including FAKE_OVERRIDE ones).
|
||||||
|
|
||||||
@@ -218,9 +230,36 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(declaration: IrFunction) {
|
||||||
|
super.visitFunction(declaration)
|
||||||
|
|
||||||
|
for ((i, p) in declaration.valueParameters.withIndex()) {
|
||||||
|
if (p.index != i) {
|
||||||
|
reportError(declaration, "Inconsistent index of value parameter ${p.index} != $i")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((i, p) in declaration.typeParameters.withIndex()) {
|
||||||
|
if (p.index != i) {
|
||||||
|
reportError(declaration, "Inconsistent index of type parameter ${p.index} != $i")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitDeclarationReference(expression: IrDeclarationReference) {
|
override fun visitDeclarationReference(expression: IrDeclarationReference) {
|
||||||
super.visitDeclarationReference(expression)
|
super.visitDeclarationReference(expression)
|
||||||
|
|
||||||
|
// TODO: Fix unbound external declarations
|
||||||
|
if (expression.descriptor.isEffectivelyExternal())
|
||||||
|
return
|
||||||
|
|
||||||
|
// TODO: Fix unbound dynamic filed declarations
|
||||||
|
if (expression is IrFieldAccessExpression) {
|
||||||
|
val receiverType = expression.receiver?.type
|
||||||
|
if (receiverType is IrDynamicType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
expression.symbol.ensureBound(expression)
|
expression.symbol.ensureBound(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,18 +24,21 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
|||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
|
|
||||||
fun validateIrFile(context: CommonBackendContext, irFile: IrFile) {
|
fun validateIrFile(context: CommonBackendContext, irFile: IrFile) {
|
||||||
val visitor = IrValidator(context, false)
|
val visitor = IrValidator(context, IrValidatorConfig(abortOnError = false, ensureAllNodesAreDifferent = false))
|
||||||
irFile.acceptVoid(visitor)
|
irFile.acceptVoid(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateIrModule(context: CommonBackendContext, irModule: IrModuleFragment) {
|
fun validateIrModule(context: CommonBackendContext, irModule: IrModuleFragment) {
|
||||||
val visitor = IrValidator(context, true) // TODO: consider taking the boolean from settings.
|
val visitor = IrValidator(
|
||||||
|
context,
|
||||||
|
IrValidatorConfig(abortOnError = false, ensureAllNodesAreDifferent = true)
|
||||||
|
) // TODO: consider taking the boolean from settings.
|
||||||
irModule.acceptVoid(visitor)
|
irModule.acceptVoid(visitor)
|
||||||
|
|
||||||
// TODO: also check that all referenced symbol targets are reachable.
|
// TODO: also check that all referenced symbol targets are reachable.
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CommonBackendContext.reportIrValidationError(message: String, irFile: IrFile, irElement: IrElement) {
|
private fun CommonBackendContext.reportIrValidationError(message: String, irFile: IrFile?, irElement: IrElement) {
|
||||||
try {
|
try {
|
||||||
this.reportWarning("[IR VALIDATION] $message", irFile, irElement)
|
this.reportWarning("[IR VALIDATION] $message", irFile, irElement)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
@@ -45,10 +48,17 @@ private fun CommonBackendContext.reportIrValidationError(message: String, irFile
|
|||||||
// TODO: throw an exception after fixing bugs leading to invalid IR.
|
// TODO: throw an exception after fixing bugs leading to invalid IR.
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IrValidator(val context: CommonBackendContext, performHeavyValidations: Boolean) : IrElementVisitorVoid {
|
data class IrValidatorConfig(
|
||||||
|
val abortOnError: Boolean,
|
||||||
|
val ensureAllNodesAreDifferent: Boolean,
|
||||||
|
val checkTypes: Boolean = true,
|
||||||
|
val checkDescriptors: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
class IrValidator(val context: CommonBackendContext, val config: IrValidatorConfig) : IrElementVisitorVoid {
|
||||||
|
|
||||||
val builtIns = context.builtIns
|
val builtIns = context.builtIns
|
||||||
lateinit var currentFile: IrFile
|
var currentFile: IrFile? = null
|
||||||
|
|
||||||
override fun visitFile(declaration: IrFile) {
|
override fun visitFile(declaration: IrFile) {
|
||||||
currentFile = declaration
|
currentFile = declaration
|
||||||
@@ -58,12 +68,17 @@ private class IrValidator(val context: CommonBackendContext, performHeavyValidat
|
|||||||
private fun error(element: IrElement, message: String) {
|
private fun error(element: IrElement, message: String) {
|
||||||
// TODO: render all element's parents.
|
// TODO: render all element's parents.
|
||||||
context.reportIrValidationError(
|
context.reportIrValidationError(
|
||||||
"$message\n" +
|
"$message\n" + element.render(),
|
||||||
element.render(),
|
currentFile,
|
||||||
currentFile, element)
|
element
|
||||||
|
)
|
||||||
|
|
||||||
|
if (config.abortOnError) {
|
||||||
|
error("Validation failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val elementChecker = CheckIrElementVisitor(builtIns, this::error, performHeavyValidations)
|
private val elementChecker = CheckIrElementVisitor(builtIns, this::error, config)
|
||||||
|
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
element.acceptVoid(elementChecker)
|
element.acceptVoid(elementChecker)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.backend.common
|
|||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
|
||||||
fun CommonBackendContext.reportWarning(message: String, irFile: IrFile, irElement: IrElement) {
|
fun CommonBackendContext.reportWarning(message: String, irFile: IrFile?, irElement: IrElement) {
|
||||||
report(irElement, irFile, message, false)
|
report(irElement, irFile, message, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
|||||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||||
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
|
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
|
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
|
||||||
@@ -101,6 +102,11 @@ private fun JsIrBackendContext.performInlining(moduleFragment: IrModuleFragment)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependencies: List<IrModuleFragment>) {
|
private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependencies: List<IrModuleFragment>) {
|
||||||
|
val validateIr = {
|
||||||
|
val visitor = IrValidator(this, validatorConfig)
|
||||||
|
moduleFragment.acceptVoid(visitor)
|
||||||
|
}
|
||||||
|
validateIr()
|
||||||
ThrowableSuccessorsLowering(this).lower(moduleFragment)
|
ThrowableSuccessorsLowering(this).lower(moduleFragment)
|
||||||
TailrecLowering(this).runOnFilesPostfix(moduleFragment)
|
TailrecLowering(this).runOnFilesPostfix(moduleFragment)
|
||||||
UnitMaterializationLowering(this).lower(moduleFragment)
|
UnitMaterializationLowering(this).lower(moduleFragment)
|
||||||
@@ -113,6 +119,7 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
|
|||||||
LocalDeclarationsLowering(this).runOnFilesPostfix(moduleFragment)
|
LocalDeclarationsLowering(this).runOnFilesPostfix(moduleFragment)
|
||||||
InnerClassesLowering(this).runOnFilesPostfix(moduleFragment)
|
InnerClassesLowering(this).runOnFilesPostfix(moduleFragment)
|
||||||
InnerClassConstructorCallsLowering(this).runOnFilesPostfix(moduleFragment)
|
InnerClassConstructorCallsLowering(this).runOnFilesPostfix(moduleFragment)
|
||||||
|
validateIr()
|
||||||
SuspendFunctionsLowering(this).lower(moduleFragment)
|
SuspendFunctionsLowering(this).lower(moduleFragment)
|
||||||
CallableReferenceLowering(this).lower(moduleFragment)
|
CallableReferenceLowering(this).lower(moduleFragment)
|
||||||
DefaultArgumentStubGenerator(this).runOnFilesPostfix(moduleFragment)
|
DefaultArgumentStubGenerator(this).runOnFilesPostfix(moduleFragment)
|
||||||
@@ -134,15 +141,24 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
|
|||||||
inlineClassDeclarationLowering.runOnFilesPostfix(moduleFragment)
|
inlineClassDeclarationLowering.runOnFilesPostfix(moduleFragment)
|
||||||
inlineClassUsageLowering.lower(moduleFragment)
|
inlineClassUsageLowering.lower(moduleFragment)
|
||||||
}
|
}
|
||||||
|
validateIr()
|
||||||
AutoboxingTransformer(this).lower(moduleFragment)
|
AutoboxingTransformer(this).lower(moduleFragment)
|
||||||
BlockDecomposerLowering(this).runOnFilesPostfix(moduleFragment)
|
BlockDecomposerLowering(this).runOnFilesPostfix(moduleFragment)
|
||||||
|
|
||||||
ClassReferenceLowering(this).lower(moduleFragment)
|
ClassReferenceLowering(this).lower(moduleFragment)
|
||||||
PrimitiveCompanionLowering(this).lower(moduleFragment)
|
PrimitiveCompanionLowering(this).lower(moduleFragment)
|
||||||
ConstLowering(this).lower(moduleFragment)
|
ConstLowering(this).lower(moduleFragment)
|
||||||
|
validateIr()
|
||||||
CallsLowering(this).lower(moduleFragment)
|
CallsLowering(this).lower(moduleFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val validatorConfig = IrValidatorConfig(
|
||||||
|
abortOnError = true,
|
||||||
|
ensureAllNodesAreDifferent = true,
|
||||||
|
checkTypes = false,
|
||||||
|
checkDescriptors = false
|
||||||
|
)
|
||||||
|
|
||||||
private fun FileLoweringPass.lower(moduleFragment: IrModuleFragment) = moduleFragment.files.forEach { lower(it) }
|
private fun FileLoweringPass.lower(moduleFragment: IrModuleFragment) = moduleFragment.files.forEach { lower(it) }
|
||||||
|
|
||||||
private fun DeclarationContainerLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) =
|
private fun DeclarationContainerLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) =
|
||||||
|
|||||||
Reference in New Issue
Block a user