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");
}
@Test
@TestMetadata("constructorWithMangledParams.kt")
public void testConstructorWithMangledParams() throws Exception {
runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("definitelyNotNullTypes.kt")
public void testDefinitelyNotNullTypes() throws Exception {
@@ -322,10 +322,12 @@ class ExpressionCodegen(
// * 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.
// Such functions require non-null assertions on parameters.
private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction) =
irFunction is IrSimpleFunction && irFunction.isOperator ||
irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
// * Hidden constructors with mangled parameters require non-null assertions (see KT-53492)
private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction): Boolean {
if (irFunction is IrSimpleFunction && irFunction.isOperator || irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) return true
if (context.hiddenConstructorsWithMangledParams.containsKey(irFunction)) return true
return false
}
private fun generateNonNullAssertion(param: IrValueParameter) {
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.name.Name
import org.jetbrains.org.objectweb.asm.Opcodes
import java.util.concurrent.ConcurrentHashMap
internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
@@ -142,8 +143,10 @@ private class SyntheticAccessorTransformer(
}
val accessor = when {
callee is IrConstructor && callee.isOrShouldBeHidden ->
handleHiddenConstructor(callee).symbol
callee is IrConstructor && callee.isOrShouldBeHiddenAsSealedClassConstructor ->
handleHiddenConstructorOfSealedClass(callee).symbol
callee is IrConstructor && callee.isOrShouldBeHiddenSinceHasMangledParams ->
handleHiddenConstructorWithMangledParams(callee).symbol
!expression.symbol.isAccessible(withSuper, thisSymbol) ->
createAccessor(expression)
else ->
@@ -331,59 +334,71 @@ private class SyntheticAccessorTransformer(
}
override fun visitConstructor(declaration: IrConstructor): IrStatement {
if (declaration.isOrShouldBeHidden) {
pendingAccessorsToAdd.add(handleHiddenConstructor(declaration))
declaration.visibility = DescriptorVisibilities.PRIVATE
when {
declaration.isOrShouldBeHiddenSinceHasMangledParams -> {
pendingAccessorsToAdd.add(handleHiddenConstructorWithMangledParams(declaration))
declaration.visibility = DescriptorVisibilities.PRIVATE
}
declaration.isOrShouldBeHiddenAsSealedClassConstructor -> {
pendingAccessorsToAdd.add(handleHiddenConstructorOfSealedClass(declaration))
declaration.visibility = DescriptorVisibilities.PRIVATE
}
}
return super.visitConstructor(declaration)
}
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
val function = expression.symbol.owner
if (!expression.origin.isLambda && function is IrConstructor && function.isOrShouldBeHidden) {
handleHiddenConstructor(function).let { accessor ->
expression.transformChildrenVoid()
return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type,
accessor.symbol, accessor.typeParameters.size,
accessor.valueParameters.size, accessor.symbol, expression.origin
)
}
if (!expression.origin.isLambda && function is IrConstructor
&& (function.isOrShouldBeHiddenSinceHasMangledParams || function.isOrShouldBeHiddenAsSealedClassConstructor)
) {
val accessor =
if (function.isOrShouldBeHiddenSinceHasMangledParams)
handleHiddenConstructorWithMangledParams(function)
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)
}
private val IrConstructor.isOrShouldBeHidden: Boolean
private val IrConstructor.isOrShouldBeHiddenSinceHasMangledParams: Boolean
get() {
if (this in context.hiddenConstructors.keys)
return true
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
if (this in context.hiddenConstructorsWithMangledParams.keys) return true
return isOrShouldBeHiddenDueToOrigin && !DescriptorVisibilities.isPrivate(visibility)
&& !constructedClass.isValue && hasMangledParameters() && !constructedClass.isAnonymousObject
}
private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructor {
require(declaration.isOrShouldBeHidden, declaration::render)
return context.hiddenConstructors.getOrPut(declaration) {
private val IrConstructor.isOrShouldBeHiddenAsSealedClassConstructor: Boolean
get() {
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 ->
if (declaration.constructedClass.modality != Modality.SEALED) {
// 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 multifileFacadeMemberToPartMember = mutableMapOf<IrSimpleFunction, IrSimpleFunction>()
val hiddenConstructors = ConcurrentHashMap<IrConstructor, IrConstructor>()
val hiddenConstructorsWithMangledParams = ConcurrentHashMap<IrConstructor, IrConstructor>()
val hiddenConstructorsOfSealedClasses = ConcurrentHashMap<IrConstructor, IrConstructor>()
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");
}
@Test
@TestMetadata("constructorWithMangledParams.kt")
public void testConstructorWithMangledParams() throws Exception {
runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("definitelyNotNullTypes.kt")
public void testDefinitelyNotNullTypes() throws Exception {