JVM_IR: add $assertionsDisabled when an inlined function uses it

NOTE: jvmCrossinlineLambdaDeclarationSite.kt is muted because the
inliner does not remap references to an anonymous object's parent
class after regenerating it. Unlike the JVM backend, JVM_IR uses the
top level named class' assertion status for all inner classes. (The
test used to pass because the lambda in `inline fun call` read the
`$assertionsDisabled` field of `CrossinlineLambdaContainer`, which
was not reloaded after changing the assertion status of package `test`.)
This commit is contained in:
pyos
2019-10-14 10:35:41 +02:00
committed by Ilmir Usmanov
parent e89aabbba1
commit 847e287bd6
18 changed files with 333 additions and 38 deletions

View File

@@ -200,9 +200,7 @@ private val jvmFilePhases =
propertiesPhase then
renameFieldsPhase then
anonymousObjectSuperConstructorPhase then
assertionPhase then
tailrecPhase then
returnableBlocksPhase then
jvmInlineClassPhase then
@@ -215,6 +213,8 @@ private val jvmFilePhases =
callableReferencePhase then
singleAbstractMethodPhase then
assertionPhase then
returnableBlocksPhase then
localDeclarationsPhase then
addContinuationPhase then

View File

@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.common.descriptors.WrappedClassDescriptor
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.buildAssertionsDisabledField
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.backend.jvm.lower.hasAssertionsDisabledField
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.inline.DefaultSourceMapper
@@ -20,7 +22,12 @@ import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.util.*
@@ -91,6 +98,9 @@ open class ClassCodegen protected constructor(
}
}
private var classInitializer: IrSimpleFunction? = null
private var generatingClInit: Boolean = false
fun generate(): ReifiedTypeParametersUsages {
if (withinInline) {
getOrCreateSourceMapper() //initialize default mapping that would be later written in class file
@@ -128,8 +138,16 @@ open class ClassCodegen protected constructor(
visitor.visitSource(shortName, null)
}
// Delay generation of <clinit> until the end because inline function calls
// might need to generate the `$assertionsDisabled` field initializer.
classInitializer = irClass.functions.singleOrNull { it.name.asString() == "<clinit>" }
for (declaration in irClass.declarations) {
generateDeclaration(declaration)
if (declaration != classInitializer)
generateDeclaration(declaration)
}
classInitializer?.let {
generatingClInit = true
generateMethod(it)
}
// Generate nested classes at the end, to ensure that codegen for companion object will have the necessary JVM signatures in its
@@ -148,6 +166,41 @@ open class ClassCodegen protected constructor(
return reifiedTypeParametersUsages
}
private var hasAssertField = irClass.hasAssertionsDisabledField(context)
fun generateAssertFieldIfNeeded(): IrExpression? {
if (hasAssertField)
return null
hasAssertField = true
val topLevelClass = generateSequence(this) { it.parentClassCodegen }.last().irClass
val field = irClass.buildAssertionsDisabledField(context, topLevelClass)
generateField(field)
// Normally, `InitializersLowering` would move the initializer to <clinit>, but
// it's obviously too late for that.
val init = IrSetFieldImpl(
field.startOffset, field.endOffset, field.symbol, null,
field.initializer!!.expression, context.irBuiltIns.unitType
)
if (classInitializer == null) {
classInitializer = buildFun {
name = Name.special("<clinit>")
returnType = context.irBuiltIns.unitType
}.apply {
parent = irClass
body = IrBlockBodyImpl(startOffset, endOffset)
}
// Do not add it to `irClass.declarations` to avoid a concurrent modification error.
} else if (generatingClInit) {
// Not only `classInitializer` is non-null, we're in fact generating it right now.
// Attempting to do `body.statements.add` will cause a concurrent modification error,
// so the currently active ExpressionCodegen needs to be asked to generate this
// initializer directly.
return init
}
(classInitializer!!.body as IrBlockBody).statements.add(0, init)
return null
}
private fun generateKotlinMetadataAnnotation() {
val localDelegatedProperties = (irClass.attributeOwnerId as? IrClass)?.let(context.localDelegatedProperties::get)
if (localDelegatedProperties != null && localDelegatedProperties.isNotEmpty()) {

View File

@@ -41,7 +41,13 @@ class IrInlineCodegen(
codegen, state, function, methodOwner, signature, typeParameterMappings, sourceCompiler, reifiedTypeInliner
), IrCallGenerator {
override fun generateAssertFieldIfNeeded(info: RootInliningContext) {
// TODO: JVM assertions are not implemented yet in IR backend
if (info.generateAssertField && (sourceCompiler as IrSourceCompilerForInline).isPrimaryCopy) {
codegen.classCodegen.generateAssertFieldIfNeeded()?.let {
// Generating <clinit> right now, so no longer can insert the initializer into it.
// Instead, ask ExpressionCodegen to generate the code for it directly.
it.accept(codegen, BlockInfo()).discard()
}
}
}
override fun putClosureParametersOnStack(next: LambdaInfo, functionReferenceReceiver: StackValue?) {

View File

@@ -164,6 +164,9 @@ class IrSourceCompilerForInline(
return setOf(name)
}
internal val isPrimaryCopy: Boolean
get() = codegen.classCodegen !is FakeClassCodegen
private class FakeClassCodegen(irFunction: IrFunction, codegen: ClassCodegen) :
ClassCodegen(irFunction.parent as IrClass, codegen.context) {

View File

@@ -28,6 +28,7 @@ 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.fields
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.getPackageFragment
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
@@ -38,7 +39,10 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
internal val assertionPhase = makeIrFilePhase(
::AssertionLowering,
name = "Assertion",
description = "Lower assert calls depending on the assertions mode"
description = "Lower assert calls depending on the assertions mode",
// Necessary to place the `$assertionsDisabled` field into the reference's class, not the
// class that contains it.
prerequisite = setOf(callableReferencePhase)
)
private class AssertionLowering(private val context: JvmBackendContext) :
@@ -125,34 +129,37 @@ private class AssertionLowering(private val context: JvmBackendContext) :
+irIfThen(irNot(assertCondition), throwError)
}
fun getAssertionDisabled(irBuilder: IrBuilderWithScope, data: ClassInfo): IrExpression {
private fun getAssertionDisabled(irBuilder: IrBuilderWithScope, data: ClassInfo): IrExpression {
if (data.assertionsDisabledField == null)
data.assertionsDisabledField = buildAssertionsDisabledField(data.irClass, data.topLevelClass)
data.assertionsDisabledField = data.irClass.buildAssertionsDisabledField(context, 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
}
private fun IrBuilderWithScope.getJavaClass(backendContext: JvmBackendContext, irClass: IrClass) =
with(CallableReferenceLowering) {
javaClassReference(irClass.typeWith(), backendContext)
}
fun IrClass.buildAssertionsDisabledField(backendContext: JvmBackendContext, topLevelClass: IrClass) =
buildField {
name = Name.identifier(ASSERTIONS_DISABLED_FIELD_NAME)
origin = JvmLoweredDeclarationOrigin.GENERATED_ASSERTION_ENABLED_FIELD
type = backendContext.irBuiltIns.booleanType
isFinal = true
isStatic = true
}.apply {
parent = this@buildAssertionsDisabledField
initializer = backendContext.createIrBuilder(this@buildAssertionsDisabledField.symbol).run {
at(this@apply)
irExprBody(irNot(irCall(backendContext.ir.symbols.desiredAssertionStatus).apply {
dispatchReceiver = getJavaClass(backendContext, topLevelClass)
}))
}
}
fun IrClass.hasAssertionsDisabledField(context: JvmBackendContext) =
fields.any { it.name.asString() == ASSERTIONS_DISABLED_FIELD_NAME && it.type == context.irBuiltIns.booleanType && it.isStatic }

View File

@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME
@@ -28,6 +26,7 @@ class Dummy
fun enableAssertions(): CheckerJvmAssertInlineFunctionAssertionsEnabled {
val loader = Dummy::class.java.classLoader
loader.setClassAssertionStatus("CheckerJvmAssertInlineFunctionAssertionsEnabled", true)
loader.setClassAssertionStatus("InlineKt", false)
val c = loader.loadClass("CheckerJvmAssertInlineFunctionAssertionsEnabled")
return c.newInstance() as CheckerJvmAssertInlineFunctionAssertionsEnabled
}

View File

@@ -1,6 +1,4 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME

View File

@@ -1,6 +1,4 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME

View File

@@ -1,4 +1,6 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME

View File

@@ -0,0 +1,73 @@
// TARGET_BACKEND: JVM
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME
// NO_CHECK_LAMBDA_INLINING
package test
object CrossinlineLambdaContainer {
inline fun call(crossinline c: () -> Boolean) {
val l = { assert(c()) }
l()
}
}
// FILE: inlineSite.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
import test.CrossinlineLambdaContainer.call
interface Checker {
fun checkTrue(): Boolean
fun checkFalse(): Boolean
}
class ShouldBeDisabled : Checker {
override fun checkTrue(): Boolean {
var hit = false
call { hit = true; true }
return hit
}
override fun checkFalse(): Boolean {
var hit = false
call { hit = true; false }
return hit
}
}
class ShouldBeEnabled : Checker {
override fun checkTrue(): Boolean {
var hit = false
call { hit = true; true }
return hit
}
override fun checkFalse(): Boolean {
var hit = false
call { hit = true; false }
return hit
}
}
fun setDesiredAssertionStatus(v: Boolean): Checker {
val loader = Checker::class.java.classLoader
loader.setDefaultAssertionStatus(v)
val c = loader.loadClass(if (v) "ShouldBeEnabled" else "ShouldBeDisabled")
return c.newInstance() as Checker
}
fun box(): String {
var c = setDesiredAssertionStatus(false)
if (c.checkTrue()) return "FAIL 0"
if (c.checkFalse()) return "FAIL 2"
c = setDesiredAssertionStatus(true)
if (!c.checkTrue()) return "FAIL 4"
try {
c.checkFalse()
return "FAIL 6"
} catch (ignore: AssertionError) {
}
return "OK"
}

View File

@@ -0,0 +1,72 @@
// TARGET_BACKEND: JVM
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME
// NO_CHECK_LAMBDA_INLINING
package test
object CrossinlineLambdaContainer {
inline fun call(crossinline c: () -> Boolean) {
Runnable { assert(c()) }.run()
}
}
// FILE: inlineSite.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
import test.CrossinlineLambdaContainer.call
interface Checker {
fun checkTrue(): Boolean
fun checkFalse(): Boolean
}
class ShouldBeDisabled : Checker {
override fun checkTrue(): Boolean {
var hit = false
call { hit = true; true }
return hit
}
override fun checkFalse(): Boolean {
var hit = false
call { hit = true; false }
return hit
}
}
class ShouldBeEnabled : Checker {
override fun checkTrue(): Boolean {
var hit = false
call { hit = true; true }
return hit
}
override fun checkFalse(): Boolean {
var hit = false
call { hit = true; false }
return hit
}
}
fun setDesiredAssertionStatus(v: Boolean): Checker {
val loader = Checker::class.java.classLoader
loader.setDefaultAssertionStatus(v)
val c = loader.loadClass(if (v) "ShouldBeEnabled" else "ShouldBeDisabled")
return c.newInstance() as Checker
}
fun box(): String {
var c = setDesiredAssertionStatus(false)
if (c.checkTrue()) return "FAIL 0"
if (c.checkFalse()) return "FAIL 2"
c = setDesiredAssertionStatus(true)
if (!c.checkTrue()) return "FAIL 4"
try {
c.checkFalse()
return "FAIL 6"
} catch (ignore: AssertionError) {
}
return "OK"
}

View File

@@ -1,6 +1,4 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: inline.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME

View File

@@ -0,0 +1,27 @@
// Not a multi-module test.
// IGNORE_BACKEND_MULTI_MODULE: JVM, JVM_IR
// FILE: A.kt
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
// WITH_RUNTIME
// FULL_JDK
class A {
inline fun inlineMe(crossinline c : () -> String) = {
assert(true)
c()
}
}
// FILE: B.java
import kotlin.jvm.functions.Function0;
public class B {
public static String check() {
return new A().inlineMe(new Function0<String>() {
@Override public String invoke() { return "OK"; }
}).invoke();
}
}
// FILE: box.kt
fun box(): String = B.check()

View File

@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm
inline fun inlineMe() = assert(true)

View File

@@ -665,10 +665,25 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSite.kt");
}
@TestMetadata("jvmCrossinlineLambdaDeclarationSiteOnly.kt")
public void testJvmCrossinlineLambdaDeclarationSiteOnly() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSiteOnly.kt");
}
@TestMetadata("jvmCrossinlineSAMDeclarationSite.kt")
public void testJvmCrossinlineSAMDeclarationSite() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineSAMDeclarationSite.kt");
}
@TestMetadata("jvmDoubleInline.kt")
public void testJvmDoubleInline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmDoubleInline.kt");
}
@TestMetadata("jvmInlineUsedAsNoinline.kt")
public void testJvmInlineUsedAsNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmInlineUsedAsNoinline.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/builders")

View File

@@ -665,10 +665,25 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSite.kt");
}
@TestMetadata("jvmCrossinlineLambdaDeclarationSiteOnly.kt")
public void testJvmCrossinlineLambdaDeclarationSiteOnly() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSiteOnly.kt");
}
@TestMetadata("jvmCrossinlineSAMDeclarationSite.kt")
public void testJvmCrossinlineSAMDeclarationSite() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineSAMDeclarationSite.kt");
}
@TestMetadata("jvmDoubleInline.kt")
public void testJvmDoubleInline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmDoubleInline.kt");
}
@TestMetadata("jvmInlineUsedAsNoinline.kt")
public void testJvmInlineUsedAsNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmInlineUsedAsNoinline.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/builders")

View File

@@ -665,10 +665,25 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSite.kt");
}
@TestMetadata("jvmCrossinlineLambdaDeclarationSiteOnly.kt")
public void testJvmCrossinlineLambdaDeclarationSiteOnly() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSiteOnly.kt");
}
@TestMetadata("jvmCrossinlineSAMDeclarationSite.kt")
public void testJvmCrossinlineSAMDeclarationSite() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineSAMDeclarationSite.kt");
}
@TestMetadata("jvmDoubleInline.kt")
public void testJvmDoubleInline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmDoubleInline.kt");
}
@TestMetadata("jvmInlineUsedAsNoinline.kt")
public void testJvmInlineUsedAsNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmInlineUsedAsNoinline.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/builders")

View File

@@ -665,10 +665,25 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSite.kt");
}
@TestMetadata("jvmCrossinlineLambdaDeclarationSiteOnly.kt")
public void testJvmCrossinlineLambdaDeclarationSiteOnly() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineLambdaDeclarationSiteOnly.kt");
}
@TestMetadata("jvmCrossinlineSAMDeclarationSite.kt")
public void testJvmCrossinlineSAMDeclarationSite() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmCrossinlineSAMDeclarationSite.kt");
}
@TestMetadata("jvmDoubleInline.kt")
public void testJvmDoubleInline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmDoubleInline.kt");
}
@TestMetadata("jvmInlineUsedAsNoinline.kt")
public void testJvmInlineUsedAsNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/assert/jvmInlineUsedAsNoinline.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxInline/builders")