Add null checks in constructors taking value class types

^KT-53492: Fixed
This commit is contained in:
vladislav.grechko
2022-11-22 16:13:43 +01:00
committed by teamcity
parent cd6e865fb3
commit 50e92d2238
6 changed files with 111 additions and 45 deletions
@@ -32585,6 +32585,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt"); runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt");
} }
@Test
@TestMetadata("constructorWithMangledParams.kt")
public void testConstructorWithMangledParams() throws Exception {
runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test @Test
@TestMetadata("definitelyNotNullTypes.kt") @TestMetadata("definitelyNotNullTypes.kt")
public void testDefinitelyNotNullTypes() throws Exception { public void testDefinitelyNotNullTypes() throws Exception {
@@ -322,10 +322,12 @@ class ExpressionCodegen(
// * Operator functions require non-null assertions on parameters even if they are private. // * Operator functions require non-null assertions on parameters even if they are private.
// * Local function for lambda survives at this stage if it was used in 'invokedynamic'-based code. // * Local function for lambda survives at this stage if it was used in 'invokedynamic'-based code.
// Such functions require non-null assertions on parameters. // * Hidden constructors with mangled parameters require non-null assertions (see KT-53492)
private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction) = private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction): Boolean {
irFunction is IrSimpleFunction && irFunction.isOperator || if (irFunction is IrSimpleFunction && irFunction.isOperator || irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) return true
irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA if (context.hiddenConstructorsWithMangledParams.containsKey(irFunction)) return true
return false
}
private fun generateNonNullAssertion(param: IrValueParameter) { private fun generateNonNullAssertion(param: IrValueParameter) {
if (param.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_OUTER_THIS || if (param.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_OUTER_THIS ||
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Opcodes
import java.util.concurrent.ConcurrentHashMap
internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass { internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
@@ -142,8 +143,10 @@ private class SyntheticAccessorTransformer(
} }
val accessor = when { val accessor = when {
callee is IrConstructor && callee.isOrShouldBeHidden -> callee is IrConstructor && callee.isOrShouldBeHiddenAsSealedClassConstructor ->
handleHiddenConstructor(callee).symbol handleHiddenConstructorOfSealedClass(callee).symbol
callee is IrConstructor && callee.isOrShouldBeHiddenSinceHasMangledParams ->
handleHiddenConstructorWithMangledParams(callee).symbol
!expression.symbol.isAccessible(withSuper, thisSymbol) -> !expression.symbol.isAccessible(withSuper, thisSymbol) ->
createAccessor(expression) createAccessor(expression)
else -> else ->
@@ -331,59 +334,71 @@ private class SyntheticAccessorTransformer(
} }
override fun visitConstructor(declaration: IrConstructor): IrStatement { override fun visitConstructor(declaration: IrConstructor): IrStatement {
if (declaration.isOrShouldBeHidden) { when {
pendingAccessorsToAdd.add(handleHiddenConstructor(declaration)) declaration.isOrShouldBeHiddenSinceHasMangledParams -> {
declaration.visibility = DescriptorVisibilities.PRIVATE pendingAccessorsToAdd.add(handleHiddenConstructorWithMangledParams(declaration))
declaration.visibility = DescriptorVisibilities.PRIVATE
}
declaration.isOrShouldBeHiddenAsSealedClassConstructor -> {
pendingAccessorsToAdd.add(handleHiddenConstructorOfSealedClass(declaration))
declaration.visibility = DescriptorVisibilities.PRIVATE
}
} }
return super.visitConstructor(declaration) return super.visitConstructor(declaration)
} }
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
val function = expression.symbol.owner val function = expression.symbol.owner
if (!expression.origin.isLambda && function is IrConstructor && function.isOrShouldBeHidden) { if (!expression.origin.isLambda && function is IrConstructor
handleHiddenConstructor(function).let { accessor -> && (function.isOrShouldBeHiddenSinceHasMangledParams || function.isOrShouldBeHiddenAsSealedClassConstructor)
expression.transformChildrenVoid() ) {
return IrFunctionReferenceImpl( val accessor =
expression.startOffset, expression.endOffset, expression.type, if (function.isOrShouldBeHiddenSinceHasMangledParams)
accessor.symbol, accessor.typeParameters.size, handleHiddenConstructorWithMangledParams(function)
accessor.valueParameters.size, accessor.symbol, expression.origin else
) handleHiddenConstructorOfSealedClass(function)
} expression.transformChildrenVoid()
return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type,
accessor.symbol, accessor.typeParameters.size,
accessor.valueParameters.size, accessor.symbol, expression.origin
)
} }
return super.visitFunctionReference(expression) return super.visitFunctionReference(expression)
} }
private val IrConstructor.isOrShouldBeHidden: Boolean private val IrConstructor.isOrShouldBeHiddenSinceHasMangledParams: Boolean
get() { get() {
if (this in context.hiddenConstructors.keys) if (this in context.hiddenConstructorsWithMangledParams.keys) return true
return true return isOrShouldBeHiddenDueToOrigin && !DescriptorVisibilities.isPrivate(visibility)
&& !constructedClass.isValue && hasMangledParameters() && !constructedClass.isAnonymousObject
if (origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR ||
origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
) {
return false
}
val constructedClass = constructedClass
if (!DescriptorVisibilities.isPrivate(visibility) && !constructedClass.isValue && hasMangledParameters() &&
!constructedClass.isAnonymousObject
) return true
if (visibility != DescriptorVisibilities.PUBLIC && constructedClass.modality == Modality.SEALED)
return true
return false
} }
private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructor { private val IrConstructor.isOrShouldBeHiddenAsSealedClassConstructor: Boolean
require(declaration.isOrShouldBeHidden, declaration::render) get() {
return context.hiddenConstructors.getOrPut(declaration) { if (this in context.hiddenConstructorsOfSealedClasses.keys) return true
return isOrShouldBeHiddenDueToOrigin && visibility != DescriptorVisibilities.PUBLIC && constructedClass.modality == Modality.SEALED
}
private val IrConstructor.isOrShouldBeHiddenDueToOrigin: Boolean
get() = !(origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR ||
origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB)
private fun handleHiddenConstructorWithMangledParams(declaration: IrConstructor) =
handleHiddenConstructor(declaration, context.hiddenConstructorsWithMangledParams)
private fun handleHiddenConstructorOfSealedClass(declaration: IrConstructor) =
handleHiddenConstructor(declaration, context.hiddenConstructorsOfSealedClasses)
private fun handleHiddenConstructor(
declaration: IrConstructor,
constructorToAccessorMap: ConcurrentHashMap<IrConstructor, IrConstructor>
): IrConstructor {
return constructorToAccessorMap.getOrPut(declaration) {
declaration.makeConstructorAccessor(JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR).also { accessor -> declaration.makeConstructorAccessor(JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR).also { accessor ->
if (declaration.constructedClass.modality != Modality.SEALED) { if (declaration.constructedClass.modality != Modality.SEALED) {
// There's a special case in the JVM backend for serializing the metadata of hidden // There's a special case in the JVM backend for serializing the metadata of hidden
@@ -114,7 +114,8 @@ class JvmBackendContext(
val multifileFacadeClassForPart = mutableMapOf<IrClass, IrClass>() val multifileFacadeClassForPart = mutableMapOf<IrClass, IrClass>()
val multifileFacadeMemberToPartMember = mutableMapOf<IrSimpleFunction, IrSimpleFunction>() val multifileFacadeMemberToPartMember = mutableMapOf<IrSimpleFunction, IrSimpleFunction>()
val hiddenConstructors = ConcurrentHashMap<IrConstructor, IrConstructor>() val hiddenConstructorsWithMangledParams = ConcurrentHashMap<IrConstructor, IrConstructor>()
val hiddenConstructorsOfSealedClasses = ConcurrentHashMap<IrConstructor, IrConstructor>()
val collectionStubComputer = CollectionStubComputer(this) val collectionStubComputer = CollectionStubComputer(this)
@@ -0,0 +1,36 @@
// WITH_REFLECT
// FULL_JDK
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses
// TARGET_BACKEND: JVM_IR
import java.lang.NullPointerException
import java.lang.reflect.InvocationTargetException
import kotlin.reflect.jvm.isAccessible
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC(val str: String)
class A(val a: IC, val x : String) {
fun foo() = "$a$x"
private constructor(x: IC) : this(IC(""), "")
}
inline fun assertThrowsExpectedException(block: () -> Unit): Boolean {
try {
block()
} catch (t: Throwable) {
return t is InvocationTargetException && t.targetException is NullPointerException
}
return false
}
fun box(): String {
if (!assertThrowsExpectedException { ::A.call(null, "").foo() }) return "Fail 1"
if (!assertThrowsExpectedException { ::A.call(IC(""), null).foo() }) return "Fail 2"
val privateConstructor = A::class.constructors.single { it.parameters.size == 1 }
privateConstructor.also { it.isAccessible = true }.call(null).foo()
return "OK"
}
@@ -32585,6 +32585,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt"); runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt");
} }
@Test
@TestMetadata("constructorWithMangledParams.kt")
public void testConstructorWithMangledParams() throws Exception {
runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test @Test
@TestMetadata("definitelyNotNullTypes.kt") @TestMetadata("definitelyNotNullTypes.kt")
public void testDefinitelyNotNullTypes() throws Exception { public void testDefinitelyNotNullTypes() throws Exception {