JVM IR: Handle -Xassertion modes
This commit is contained in:
committed by
max-kammerer
parent
7b2edc6de8
commit
70aee4f9e2
@@ -41,6 +41,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
|
||||
object GENERATED_SAM_IMPLEMENTATION : IrDeclarationOriginImpl("GENERATED_SAM_IMPLEMENTATION", isSynthetic = true)
|
||||
object ENUM_MAPPINGS_FOR_WHEN : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_WHEN", isSynthetic = true)
|
||||
object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
|
||||
object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true)
|
||||
}
|
||||
|
||||
interface JvmLoweredStatementOrigin : IrStatementOrigin {
|
||||
|
||||
@@ -127,6 +127,7 @@ private val jvmFilePhases =
|
||||
propertiesToFieldsPhase then
|
||||
propertiesPhase then
|
||||
renameFieldsPhase then
|
||||
assertionPhase then
|
||||
annotationPhase then
|
||||
tailrecPhase then
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.defaultType
|
||||
import org.jetbrains.kotlin.ir.types.isNullableAny
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.ReferenceSymbolTable
|
||||
import org.jetbrains.kotlin.ir.util.constructors
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -114,6 +116,15 @@ class JvmSymbols(
|
||||
val javaLangClass: IrClassSymbol =
|
||||
if (firMode) createClass(FqName("java.lang.Class")) {}.symbol else context.getTopLevelClass(FqName("java.lang.Class"))
|
||||
|
||||
val javaLangAssertionError: IrClassSymbol =
|
||||
context.getTopLevelClass(FqName("java.lang.AssertionError"))
|
||||
|
||||
val assertionErrorConstructor by lazy {
|
||||
context.ir.symbols.javaLangAssertionError.constructors.single {
|
||||
it.owner.valueParameters.size == 1 && it.owner.valueParameters[0].type.isNullableAny()
|
||||
}
|
||||
}
|
||||
|
||||
val lambdaClass: IrClassSymbol = createClass(FqName("kotlin.jvm.internal.Lambda")) { klass ->
|
||||
klass.addConstructor().apply {
|
||||
addValueParameter("arity", irBuiltIns.intType)
|
||||
@@ -270,14 +281,21 @@ class JvmSymbols(
|
||||
}
|
||||
}.symbol
|
||||
|
||||
private fun IrClassSymbol.functionByName(name: String): IrSimpleFunctionSymbol =
|
||||
functions.single { it.owner.name.asString() == name }
|
||||
|
||||
val getOrCreateKotlinPackage: IrSimpleFunctionSymbol =
|
||||
reflection.functions.single { it.owner.name.asString() == "getOrCreateKotlinPackage" }
|
||||
reflection.functionByName("getOrCreateKotlinPackage")
|
||||
|
||||
val getOrCreateKotlinClass: IrSimpleFunctionSymbol =
|
||||
reflection.functions.single { it.owner.name.asString() == "getOrCreateKotlinClass" }
|
||||
reflection.functionByName("getOrCreateKotlinClass")
|
||||
|
||||
val getOrCreateKotlinClasses: IrSimpleFunctionSymbol =
|
||||
reflection.functions.single { it.owner.name.asString() == "getOrCreateKotlinClasses" }
|
||||
reflection.functionByName("getOrCreateKotlinClasses")
|
||||
|
||||
val desiredAssertionStatus: IrSimpleFunctionSymbol by lazy {
|
||||
javaLangClass.functionByName("desiredAssertionStatus")
|
||||
}
|
||||
|
||||
private val unsafeCoerceIntrinsic =
|
||||
buildFun {
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
|
||||
import org.jetbrains.kotlin.backend.common.ir.inline
|
||||
import org.jetbrains.kotlin.backend.common.lower.*
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.codegen.ASSERTIONS_DISABLED_FIELD_NAME
|
||||
import org.jetbrains.kotlin.config.JVMAssertionsMode
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.getPackageFragment
|
||||
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
internal val assertionPhase = makeIrFilePhase(
|
||||
::AssertionLowering,
|
||||
name = "Assertion",
|
||||
description = "Lower assert calls depending on the assertions mode"
|
||||
)
|
||||
|
||||
private class AssertionLowering(private val context: JvmBackendContext) :
|
||||
FileLoweringPass,
|
||||
IrElementTransformer<AssertionLowering.ClassInfo?>
|
||||
{
|
||||
// Keeps track of the $assertionsDisabled field, which we generate lazily for classes containing
|
||||
// assertions when compiled with -Xassertions=jvm.
|
||||
private class ClassInfo(val irClass: IrClass, val topLevelClass: IrClass, var assertionsDisabledField: IrField? = null)
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
// In legacy mode we treat assertions as inline function calls
|
||||
if (context.state.assertionsMode != JVMAssertionsMode.LEGACY)
|
||||
irFile.transformChildren(this, null)
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass, data: ClassInfo?): IrStatement {
|
||||
val info = ClassInfo(declaration, data?.topLevelClass ?: declaration)
|
||||
|
||||
super.visitClass(declaration, info)
|
||||
|
||||
// Note that it's necessary to add this member at the beginning of the class, before all user-visible
|
||||
// initializers, which may contain assertions. At the same time, assertions are supposed to be enabled
|
||||
// for code which runs before class initialization. This is the reason why this field records whether
|
||||
// assertions are disabled rather than enabled. During initialization, $assertionsDisabled is going
|
||||
// to be false, meaning that assertions are checked.
|
||||
info.assertionsDisabledField?.let {
|
||||
declaration.declarations.add(0, it)
|
||||
|
||||
// Some parents of local declarations are not updated during ad-hoc inlining
|
||||
// TODO: Remove when generic inliner is used
|
||||
declaration.patchDeclarationParents(declaration.parent)
|
||||
}
|
||||
|
||||
return declaration
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: ClassInfo?): IrElement {
|
||||
val function = expression.symbol.owner
|
||||
if (!function.isAssert)
|
||||
return super.visitCall(expression, data)
|
||||
|
||||
val mode = context.state.assertionsMode
|
||||
if (mode == JVMAssertionsMode.ALWAYS_DISABLE)
|
||||
return IrCompositeImpl(expression.startOffset, expression.endOffset, context.irBuiltIns.unitType)
|
||||
|
||||
context.createIrBuilder(expression.symbol).run {
|
||||
at(expression)
|
||||
val assertCondition = expression.getValueArgument(0)!!
|
||||
val lambdaArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
|
||||
|
||||
return if (mode == JVMAssertionsMode.ALWAYS_ENABLE) {
|
||||
checkAssertion(assertCondition, lambdaArgument)
|
||||
} else {
|
||||
require(mode == JVMAssertionsMode.JVM && data != null)
|
||||
irIfThen(
|
||||
irNot(getAssertionDisabled(this, data)),
|
||||
checkAssertion(assertCondition, lambdaArgument)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.checkAssertion(assertCondition: IrExpression, lambdaArgument: IrExpression?) =
|
||||
irBlock {
|
||||
val lambda = lambdaArgument?.asSimpleLambda()
|
||||
val invokeVar = if (lambda == null && lambdaArgument != null) irTemporary(lambdaArgument) else null
|
||||
|
||||
val constructor = this@AssertionLowering.context.ir.symbols.assertionErrorConstructor
|
||||
val throwError = irThrow(irCall(constructor).apply {
|
||||
putValueArgument(
|
||||
0,
|
||||
when {
|
||||
lambda != null -> lambda.inline()
|
||||
lambdaArgument != null -> {
|
||||
val invoke =
|
||||
lambdaArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
||||
irCallOp(invoke.symbol, invoke.returnType, irGet(invokeVar!!))
|
||||
}
|
||||
else -> irString("Assertion failed")
|
||||
}
|
||||
)
|
||||
})
|
||||
+irIfThen(irNot(assertCondition), throwError)
|
||||
}
|
||||
|
||||
fun getAssertionDisabled(irBuilder: IrBuilderWithScope, data: ClassInfo): IrExpression {
|
||||
if (data.assertionsDisabledField == null)
|
||||
data.assertionsDisabledField = buildAssertionsDisabledField(data.irClass, data.topLevelClass)
|
||||
return irBuilder.irGetField(null, data.assertionsDisabledField!!)
|
||||
}
|
||||
|
||||
private fun buildAssertionsDisabledField(irClass: IrClass, topLevelClass: IrClass) =
|
||||
buildField {
|
||||
name = Name.identifier(ASSERTIONS_DISABLED_FIELD_NAME)
|
||||
origin = JvmLoweredDeclarationOrigin.GENERATED_ASSERTION_ENABLED_FIELD
|
||||
type = context.irBuiltIns.booleanType
|
||||
isFinal = true
|
||||
isStatic = true
|
||||
}.apply {
|
||||
parent = irClass
|
||||
initializer = context.createIrBuilder(irClass.symbol).run {
|
||||
at(this@apply)
|
||||
irExprBody(irNot(irCall(this@AssertionLowering.context.ir.symbols.desiredAssertionStatus).apply {
|
||||
dispatchReceiver = getJavaClass(topLevelClass)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.getJavaClass(irClass: IrClass) =
|
||||
with(CallableReferenceLowering) {
|
||||
javaClassReference(irClass.typeWith(), this@AssertionLowering.context)
|
||||
}
|
||||
|
||||
private val IrFunction.isAssert: Boolean
|
||||
get() = name.asString() == "assert" && getPackageFragment()?.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME
|
||||
}
|
||||
+24
-20
@@ -451,29 +451,33 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun IrBuilderWithScope.calculateOwner(irContainer: IrDeclarationParent, context: JvmBackendContext): IrExpression {
|
||||
val symbols = context.ir.symbols
|
||||
|
||||
val isContainerPackage =
|
||||
(irContainer as? IrClass)?.origin == IrDeclarationOrigin.FILE_CLASS || irContainer is IrPackageFragment
|
||||
|
||||
val clazzRef = IrClassReferenceImpl(
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
symbols.javaLangClass.typeWith(),
|
||||
symbols.javaLangClass,
|
||||
// For built-in members (i.e. top level `toString`) we don't know any meaningful container, so we're generating Any.
|
||||
// The non-IR backend generates equally meaningless "kotlin/KotlinPackage" in this case (see KT-17151).
|
||||
(irContainer as? IrClass)?.defaultType ?: context.irBuiltIns.anyNType
|
||||
private fun IrBuilderWithScope.kClassReference(classType: IrType) =
|
||||
IrClassReferenceImpl(
|
||||
startOffset,
|
||||
endOffset,
|
||||
context.irBuiltIns.kClassClass.typeWith(),
|
||||
context.irBuiltIns.kClassClass,
|
||||
classType
|
||||
)
|
||||
|
||||
if (!isContainerPackage) return clazzRef
|
||||
|
||||
val jClass = irGet(symbols.javaLangClass.typeWith(), null, symbols.kClassJava.owner.getter!!.symbol).apply {
|
||||
extensionReceiver = clazzRef
|
||||
private fun IrBuilderWithScope.kClassToJavaClass(kClassReference: IrExpression, context: JvmBackendContext) =
|
||||
irGet(context.ir.symbols.javaLangClass.typeWith(), null, context.ir.symbols.kClassJava.owner.getter!!.symbol).apply {
|
||||
extensionReceiver = kClassReference
|
||||
}
|
||||
return irCall(symbols.getOrCreateKotlinPackage).apply {
|
||||
putValueArgument(0, jClass)
|
||||
|
||||
internal fun IrBuilderWithScope.javaClassReference(classType: IrType, context: JvmBackendContext) =
|
||||
kClassToJavaClass(kClassReference(classType), context)
|
||||
|
||||
internal fun IrBuilderWithScope.calculateOwner(irContainer: IrDeclarationParent, context: JvmBackendContext): IrExpression {
|
||||
// For built-in members (i.e. top level `toString`) we don't know any meaningful container, so we're generating Any.
|
||||
// The non-IR backend generates equally meaningless "kotlin/KotlinPackage" in this case (see KT-17151).
|
||||
val kClass = kClassReference((irContainer as? IrClass)?.defaultType ?: context.irBuiltIns.anyNType)
|
||||
|
||||
if ((irContainer as? IrClass)?.origin != IrDeclarationOrigin.FILE_CLASS && irContainer !is IrPackageFragment)
|
||||
return kClass
|
||||
|
||||
return irCall(context.ir.symbols.getOrCreateKotlinPackage).apply {
|
||||
putValueArgument(0, kClassToJavaClass(kClass, context))
|
||||
// Note that this name is not used in reflection. There should be the name of the referenced declaration's
|
||||
// module instead, but there's no nice API to obtain that name here yet
|
||||
// TODO: write the referenced declaration's module name and use it in reflection
|
||||
|
||||
Reference in New Issue
Block a user