[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.ir.IrElement
|
||||
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.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.toKotlinType
|
||||
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.resolve.descriptorUtil.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
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>()
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
if (ensureAllNodesAreDifferent) {
|
||||
if (config.ensureAllNodesAreDifferent) {
|
||||
if (set.contains(element))
|
||||
reportError(element, "Duplicate IR node")
|
||||
set.add(element)
|
||||
@@ -44,6 +53,9 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
||||
}
|
||||
|
||||
private fun IrExpression.ensureTypeIs(expectedType: KotlinType) {
|
||||
if (!config.checkTypes)
|
||||
return
|
||||
|
||||
// TODO: compare IR types instead.
|
||||
if (expectedType != 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) {
|
||||
if (!this.isBound) {
|
||||
if (!this.isBound && expression.type !is IrDynamicType) {
|
||||
reportError(expression, "Unbound symbol ${this}")
|
||||
}
|
||||
}
|
||||
@@ -163,8 +175,8 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
||||
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> builtIns.booleanType
|
||||
}
|
||||
|
||||
if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT && typeOperand != builtIns.unitType) {
|
||||
reportError(expression, "typeOperand is $typeOperand")
|
||||
if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT && !typeOperand.isUnit()) {
|
||||
reportError(expression, "typeOperand is ${typeOperand.render()}")
|
||||
}
|
||||
|
||||
// TODO: check IMPLICIT_NOTNULL's argument type.
|
||||
@@ -200,7 +212,7 @@ class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: Repor
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
super.visitClass(declaration)
|
||||
|
||||
if (!declaration.isAnnotationClass) {
|
||||
if (config.checkDescriptors && !declaration.isAnnotationClass) {
|
||||
// Check that all functions and properties from memberScope are present in IR
|
||||
// (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) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,18 +24,21 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
|
||||
fun validateIrFile(context: CommonBackendContext, irFile: IrFile) {
|
||||
val visitor = IrValidator(context, false)
|
||||
val visitor = IrValidator(context, IrValidatorConfig(abortOnError = false, ensureAllNodesAreDifferent = false))
|
||||
irFile.acceptVoid(visitor)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
this.reportWarning("[IR VALIDATION] $message", irFile, irElement)
|
||||
} 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.
|
||||
}
|
||||
|
||||
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
|
||||
lateinit var currentFile: IrFile
|
||||
var currentFile: IrFile? = null
|
||||
|
||||
override fun visitFile(declaration: IrFile) {
|
||||
currentFile = declaration
|
||||
@@ -58,12 +68,17 @@ private class IrValidator(val context: CommonBackendContext, performHeavyValidat
|
||||
private fun error(element: IrElement, message: String) {
|
||||
// TODO: render all element's parents.
|
||||
context.reportIrValidationError(
|
||||
"$message\n" +
|
||||
element.render(),
|
||||
currentFile, element)
|
||||
"$message\n" + element.render(),
|
||||
currentFile,
|
||||
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) {
|
||||
element.acceptVoid(elementChecker)
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.backend.common
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
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.name.FqName
|
||||
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>) {
|
||||
val validateIr = {
|
||||
val visitor = IrValidator(this, validatorConfig)
|
||||
moduleFragment.acceptVoid(visitor)
|
||||
}
|
||||
validateIr()
|
||||
ThrowableSuccessorsLowering(this).lower(moduleFragment)
|
||||
TailrecLowering(this).runOnFilesPostfix(moduleFragment)
|
||||
UnitMaterializationLowering(this).lower(moduleFragment)
|
||||
@@ -113,6 +119,7 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
|
||||
LocalDeclarationsLowering(this).runOnFilesPostfix(moduleFragment)
|
||||
InnerClassesLowering(this).runOnFilesPostfix(moduleFragment)
|
||||
InnerClassConstructorCallsLowering(this).runOnFilesPostfix(moduleFragment)
|
||||
validateIr()
|
||||
SuspendFunctionsLowering(this).lower(moduleFragment)
|
||||
CallableReferenceLowering(this).lower(moduleFragment)
|
||||
DefaultArgumentStubGenerator(this).runOnFilesPostfix(moduleFragment)
|
||||
@@ -134,15 +141,24 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
|
||||
inlineClassDeclarationLowering.runOnFilesPostfix(moduleFragment)
|
||||
inlineClassUsageLowering.lower(moduleFragment)
|
||||
}
|
||||
validateIr()
|
||||
AutoboxingTransformer(this).lower(moduleFragment)
|
||||
BlockDecomposerLowering(this).runOnFilesPostfix(moduleFragment)
|
||||
|
||||
ClassReferenceLowering(this).lower(moduleFragment)
|
||||
PrimitiveCompanionLowering(this).lower(moduleFragment)
|
||||
ConstLowering(this).lower(moduleFragment)
|
||||
validateIr()
|
||||
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 DeclarationContainerLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) =
|
||||
|
||||
Reference in New Issue
Block a user