From c0a83c3c8a15ee0e10eff5bc5750f44459ac2d59 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 28 Jul 2017 16:20:10 +0300 Subject: [PATCH] KT-19251 Process uninitialized stores in mandatory bytecode pass See https://youtrack.jetbrains.com/issue/KT-19251 https://github.com/puniverse/quasar/issues/280 https://bugs.openjdk.java.net/browse/JDK-8046233 Inline function calls (as well as try/catch expressions) in constructor arguments produce bytecode that spills stack, and stores uninitialized objects (created by 'NEW C', but not initialized by 'C.') to local variables. Such bytecode is valid according to the JVM spec, but confuses Quasar (and other bytecode postprocessing tools), and fails to verify under some (buggy) versions of JDK 8. In order to avoid that, we apply 'processUnitializedStores' already implemented for coroutines. It moves 'NEW' instructions after the constructor arguments evaluation, producing code like NEW C DUP INVOKESPECIAL C.(...) NB some other expressions, such as break/continue in the constructor arguments, also can produce "weird" bytecode: object is created by a 'NEW C' instruction, but later (conditionally) POPped from stack and left uninitialized. This, as we know, also can screw bytecode postprocessing. However, it looks like we can get away with it ATM. Otherwise it looks like we'd have to analyze constructor arguments, see if the evaluation can "jump out", and perform argument linearization in codegen. --- .../coroutines/processUninitializedStores.kt | 229 ++++++++++-------- ...ithLabelNormalizationMethodTransformer.kt} | 0 .../OptimizationClassBuilderFactory.java | 2 - .../optimization/OptimizationMethodVisitor.kt | 14 +- .../UninitializedStoresMethodTransformer.kt | 27 +++ .../codegen/optimization/common/Util.kt | 3 + .../transformer/CompositeMethodTransformer.kt | 9 +- .../kotlin/codegen/state/GenerationState.kt | 1 - ...lineFunInConstructorCallEvaluationOrder.kt | 40 +++ .../inlineFunInInnerClassConstructorCall.kt | 43 ++++ .../inlineFunInLocalClassConstructorCall.kt | 41 ++++ .../regularConstructorCallEvaluationOrder.kt | 32 +++ ...ryCatchInConstructorCallEvaluationOrder.kt | 35 +++ .../ir/IrBlackBoxCodegenTestGenerated.java | 39 +++ .../codegen/BlackBoxCodegenTestGenerated.java | 39 +++ .../LightAnalysisModeTestGenerated.java | 39 +++ .../semantics/JsCodegenBoxTestGenerated.java | 9 + 17 files changed, 494 insertions(+), 108 deletions(-) rename compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/{MandatoryMethodTransforker.kt => FixStackWithLabelNormalizationMethodTransformer.kt} (100%) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt create mode 100644 compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt create mode 100644 compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt index 5e5983dc629..5e3c9641ab7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt @@ -16,10 +16,8 @@ package org.jetbrains.kotlin.codegen.coroutines -import org.jetbrains.kotlin.codegen.optimization.common.CustomFramesMethodAnalyzer -import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter -import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue -import org.jetbrains.kotlin.codegen.optimization.common.insnListOf +import org.jetbrains.kotlin.codegen.optimization.common.* +import org.jetbrains.kotlin.codegen.optimization.fixStack.peek import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.tree.* @@ -71,124 +69,161 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter * - restore constructor arguments */ internal fun processUninitializedStores(methodNode: MethodNode) { - val interpreter = UninitializedNewValueMarkerInterpreter() - val frames = CustomFramesMethodAnalyzer("fake", methodNode, interpreter, ::UninitializedNewValueFrame).analyze() - - for ((index, insn) in methodNode.instructions.toArray().withIndex()) { - val frame = frames[index] ?: continue - val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue - - val copyUsages: Set = interpreter.uninitializedValuesToCopyUsages[uninitializedValue.newInsn]!! - assert(copyUsages.size > 0) { "At least DUP copy operation expected" } - - // Value generated by NEW wasn't store to local/field (only DUPed) - if (copyUsages.size == 1) continue - - (copyUsages + uninitializedValue.newInsn).forEach { - methodNode.instructions.remove(it) - } - - val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size - val storedTypes = arrayListOf() - var nextVarIndex = methodNode.maxLocals - - for (i in 0 until indexOfConstructorArgumentFromTopOfStack) { - val value = frame.getStack(frame.stackSize - 1 - i) - val type = value.type - methodNode.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ISTORE), nextVarIndex)) - nextVarIndex += type.size - storedTypes.add(type) - } - methodNode.maxLocals = Math.max(methodNode.maxLocals, nextVarIndex) - - methodNode.instructions.insertBefore(insn, insnListOf( - TypeInsnNode(Opcodes.NEW, uninitializedValue.newInsn.desc), - InsnNode(Opcodes.DUP) - )) - - for (type in storedTypes.reversed()) { - nextVarIndex -= type.size - methodNode.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ILOAD), nextVarIndex)) - } - } + UninitializedStoresProcessor(methodNode, forCoroutines = true).run() } -private class UninitializedNewValue( - val newInsn: TypeInsnNode, val internalName: String -) : StrictBasicValue(Type.getObjectType(internalName)) { - override fun toString() = "UninitializedNewValue(internalName='$internalName')" -} +class UninitializedStoresProcessor(private val methodNode: MethodNode, private val forCoroutines: Boolean) { + private val isInSpecialMethod = methodNode.name == "" || methodNode.name == "" -private class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame(nLocals, nStack) { - override fun execute(insn: AbstractInsnNode, interpreter: Interpreter?) { - val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null + fun run() { + val interpreter = UninitializedNewValueMarkerInterpreter() - super.execute(insn, interpreter) + val frames = CustomFramesMethodAnalyzer( + "fake", methodNode, interpreter, + this::UninitializedNewValueFrame + ).analyze() - if (replaceTopValueWithInitialized) { - // Drop top value - val value = pop() as UninitializedNewValue + for ((index, insn) in methodNode.instructions.toArray().withIndex()) { + val frame = frames[index] ?: continue + val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue - // uninitialized value become initialized after call - push(StrictBasicValue(value.type)) + val newInsn = uninitializedValue.newInsn + val copyUsages: Set = interpreter.uninitializedValuesToCopyUsages[newInsn]!! + assert(copyUsages.isNotEmpty()) { "At least DUP copy operation expected" } + + // Value generated by NEW wasn't store to local/field (only DUPed) + if (copyUsages.size == 1) continue + + methodNode.instructions.run { + removeAll(copyUsages) + if (forCoroutines) { + remove(newInsn) + } + else { + // Replace 'NEW C' instruction with "manual" initialization of class 'C': + // LDC [typeName for C] + // INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class; + // POP + val typeNameForClass = newInsn.desc.replace('/', '.') + insertBefore(newInsn, LdcInsnNode(typeNameForClass)) + insertBefore( + newInsn, + MethodInsnNode( + Opcodes.INVOKESTATIC, + "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", + false + ) + ) + set(newInsn, InsnNode(Opcodes.POP)) + } + } + + val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + val storedTypes = arrayListOf() + var nextVarIndex = methodNode.maxLocals + + for (i in 0 until indexOfConstructorArgumentFromTopOfStack) { + val value = frame.getStack(frame.stackSize - 1 - i) + val type = value.type + methodNode.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ISTORE), nextVarIndex)) + nextVarIndex += type.size + storedTypes.add(type) + } + methodNode.maxLocals = Math.max(methodNode.maxLocals, nextVarIndex) + + methodNode.instructions.insertBefore(insn, insnListOf( + TypeInsnNode(Opcodes.NEW, newInsn.desc), + InsnNode(Opcodes.DUP) + )) + + for (type in storedTypes.reversed()) { + nextVarIndex -= type.size + methodNode.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ILOAD), nextVarIndex)) + } } } -} -/** - * @return value generated by NEW that used as 0-th argument of constructor call or null if current instruction is not constructor call - */ -private fun Frame.getUninitializedValueForConstructorCall( - insn: AbstractInsnNode -): UninitializedNewValue? { - if (!insn.isConstructorCall()) return null + private inner class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame(nLocals, nStack) { + override fun execute(insn: AbstractInsnNode, interpreter: Interpreter?) { + val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null - assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for , but ${insn.opcode} found" } - val paramsCountIncludingReceiver = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + 1 - val newValue = getStack(stackSize - (paramsCountIncludingReceiver + 1)) as? UninitializedNewValue ?: error("Expected value generated with NEW") + super.execute(insn, interpreter) - assert(getStack(stackSize - paramsCountIncludingReceiver) is UninitializedNewValue) { - "Next value after NEW should be one generated by DUP" + if (replaceTopValueWithInitialized) { + // Drop top value + val value = pop() as UninitializedNewValue + + // uninitialized value become initialized after call + push(StrictBasicValue(value.type)) + } + } } - return newValue -} + /** + * @return value generated by NEW that used as 0-th argument of constructor call or null if current instruction is not constructor call + */ + private fun Frame.getUninitializedValueForConstructorCall(insn: AbstractInsnNode): UninitializedNewValue? { + if (!insn.isConstructorCall()) return null -private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "" + assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for , but ${insn.opcode} found" } + val paramsCountIncludingReceiver = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + 1 + val newValue = peek(paramsCountIncludingReceiver) as? UninitializedNewValue ?: + if (isInSpecialMethod) + return null + else + error("Expected value generated with NEW") -private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpreter() { - val uninitializedValuesToCopyUsages = hashMapOf>() - override fun newOperation(insn: AbstractInsnNode): BasicValue? { - if (insn.opcode == Opcodes.NEW) { - uninitializedValuesToCopyUsages.getOrPut(insn) { mutableSetOf() } - return UninitializedNewValue(insn as TypeInsnNode, insn.desc) + assert(peek(paramsCountIncludingReceiver - 1) is UninitializedNewValue) { + "Next value after NEW should be one generated by DUP" } - return super.newOperation(insn) + + return newValue } - override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? { - if (value is UninitializedNewValue) { - uninitializedValuesToCopyUsages[value.newInsn]!!.add(insn) - return value - } - return super.copyOperation(insn, value) + private class UninitializedNewValue( + val newInsn: TypeInsnNode, + val internalName: String + ) : StrictBasicValue(Type.getObjectType(internalName)) { + override fun toString() = "UninitializedNewValue(internalName='$internalName')" } - override fun merge(v: BasicValue, w: BasicValue): BasicValue { - if (v === w) return v - if (v === StrictBasicValue.UNINITIALIZED_VALUE || w === StrictBasicValue.UNINITIALIZED_VALUE) { - return StrictBasicValue.UNINITIALIZED_VALUE + + private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "" + + private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpreter() { + val uninitializedValuesToCopyUsages = hashMapOf>() + override fun newOperation(insn: AbstractInsnNode): BasicValue? { + if (insn.opcode == Opcodes.NEW) { + uninitializedValuesToCopyUsages.getOrPut(insn) { mutableSetOf() } + return UninitializedNewValue(insn as TypeInsnNode, insn.desc) + } + return super.newOperation(insn) } - if (v is UninitializedNewValue || w is UninitializedNewValue) { - if ((v as? UninitializedNewValue)?.newInsn !== (w as? UninitializedNewValue)?.newInsn) { - // Merge of two different ANEW result is possible, but such values should not be used further + override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? { + if (value is UninitializedNewValue) { + uninitializedValuesToCopyUsages[value.newInsn]!!.add(insn) + return value + } + return super.copyOperation(insn, value) + } + + override fun merge(v: BasicValue, w: BasicValue): BasicValue { + if (v === w) return v + if (v === StrictBasicValue.UNINITIALIZED_VALUE || w === StrictBasicValue.UNINITIALIZED_VALUE) { return StrictBasicValue.UNINITIALIZED_VALUE } - return v - } + if (v is UninitializedNewValue || w is UninitializedNewValue) { + if ((v as? UninitializedNewValue)?.newInsn !== (w as? UninitializedNewValue)?.newInsn) { + // Merge of two different ANEW result is possible, but such values should not be used further + return StrictBasicValue.UNINITIALIZED_VALUE + } - return super.merge(v, w) + return v + } + + return super.merge(v, w) + } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackWithLabelNormalizationMethodTransformer.kt similarity index 100% rename from compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt rename to compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackWithLabelNormalizationMethodTransformer.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java index 3a8223e869e..812944c12a7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java @@ -17,9 +17,7 @@ package org.jetbrains.kotlin.codegen.optimization; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.codegen.ClassBuilder; import org.jetbrains.kotlin.codegen.ClassBuilderFactory; -import org.jetbrains.kotlin.codegen.ClassBuilderMode; import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 33c62a48c77..04663015d31 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -17,9 +17,9 @@ package org.jetbrains.kotlin.codegen.optimization import org.jetbrains.kotlin.codegen.TransformationMethodVisitor -import org.jetbrains.kotlin.codegen.optimization.boxing.StackPeepholeOptimizationsTransformer -import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer import org.jetbrains.kotlin.codegen.optimization.boxing.PopBackwardPropagationTransformer +import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.boxing.StackPeepholeOptimizationsTransformer import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer @@ -35,11 +35,10 @@ class OptimizationMethodVisitor( signature: String?, exceptions: Array? ) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { - override fun performTransformations(methodNode: MethodNode) { - MANDATORY_METHOD_TRANSFORMER.transform("fake", methodNode) + normalizationMethodTransformer.transform("fake", methodNode) if (canBeOptimized(methodNode) && !disableOptimization) { - OPTIMIZATION_TRANSFORMER.transform("fake", methodNode) + optimizationTransformer.transform("fake", methodNode) } methodNode.prepareForEmitting() } @@ -47,12 +46,13 @@ class OptimizationMethodVisitor( companion object { private val MEMORY_LIMIT_BY_METHOD_MB = 50 - private val MANDATORY_METHOD_TRANSFORMER = CompositeMethodTransformer( + val normalizationMethodTransformer = CompositeMethodTransformer( FixStackWithLabelNormalizationMethodTransformer(), + UninitializedStoresMethodTransformer(), MethodVerifier("AFTER mandatory stack transformations") ) - private val OPTIMIZATION_TRANSFORMER = CompositeMethodTransformer( + val optimizationTransformer = CompositeMethodTransformer( CapturedVarsOptimizationMethodTransformer(), RedundantNullCheckMethodTransformer(), RedundantCheckCastEliminationMethodTransformer(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt new file mode 100644 index 00000000000..aab689820fa --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization + +import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.org.objectweb.asm.tree.MethodNode + +class UninitializedStoresMethodTransformer : MethodTransformer() { + override fun transform(internalClassName: String, methodNode: MethodNode) { + UninitializedStoresProcessor(methodNode, forCoroutines = false).run() + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt index 77fb229f4d8..adb8fe84bcb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt @@ -214,3 +214,6 @@ internal inline fun AbstractInsnNode.isInsn(opcod internal inline fun AbstractInsnNode.takeInsnIf(opcode: Int, condition: T.() -> Boolean): T? = takeIf { it.opcode == opcode }?.safeAs()?.takeIf { it.condition() } +fun InsnList.removeAll(nodes: Collection) { + for (node in nodes) remove(node) +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/CompositeMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/CompositeMethodTransformer.kt index 5726321e506..e2e40b47b59 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/CompositeMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/CompositeMethodTransformer.kt @@ -18,8 +18,15 @@ package org.jetbrains.kotlin.codegen.optimization.transformer import org.jetbrains.org.objectweb.asm.tree.MethodNode -open class CompositeMethodTransformer(private vararg val transformers: MethodTransformer) : MethodTransformer() { +open class CompositeMethodTransformer(private val transformers: List) : MethodTransformer() { + constructor(vararg transformers: MethodTransformer?) : this(transformers.filterNotNull()) + override fun transform(internalClassName: String, methodNode: MethodNode) { transformers.forEach { it.transform(internalClassName, methodNode) } } + + companion object { + inline fun build(builder: MutableList.() -> Unit) = + CompositeMethodTransformer(ArrayList().apply { builder() }) + } } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 6a9269e3275..9da8888b465 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -179,7 +179,6 @@ class GenerationState @JvmOverloads constructor( val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA) - val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) init { diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt new file mode 100644 index 00000000000..6a07675236f --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt @@ -0,0 +1,40 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +fun box(): String { + Foo( + logged("i", 1.let { it }), + logged("j", + Foo( + logged("k", 2.let { it }), + null + ) + ) + ) + + val result = log.toString() + if (result != "ikj") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Foo?) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt new file mode 100644 index 00000000000..9292242e2b6 --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt @@ -0,0 +1,43 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +fun box(): String { + Outer().Inner( + logged("i;", 1.let { it }), + logged("j;", 2.let { it }) + ) + + val result = log.toString() + if (result != "Foo.;i;j;Foo.;Inner.;") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +open class Foo { + init { + log.append("Foo.;") + } + + companion object { + init { + log.append("Foo.;") + } + } +} + +class Outer { + inner class Inner(val x: Int, val y: Int) : Foo() { + init { + log.append("Inner.;") + } + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt new file mode 100644 index 00000000000..7061885f7aa --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +fun box(): String { + class Local(val i: Int, val j: Int) : Foo() { + init { + log.append("Local.;") + } + } + + Local( + logged("i;", 1.let { it }), + logged("j;", 2.let { it }) + ) + + val result = log.toString() + if (result != "Foo.;i;j;Foo.;Local.;") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +open class Foo { + init { + log.append("Foo.;") + } + + companion object { + init { + log.append("Foo.;") + } + } +} diff --git a/compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt b/compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt new file mode 100644 index 00000000000..dcba51be98f --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt @@ -0,0 +1,32 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +fun box(): String { + Foo(logged("i", 1), logged("j", 2)) + + val result = log.toString() + if (result != "ij") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Int) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt b/compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt new file mode 100644 index 00000000000..4e3e586d5c4 --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt @@ -0,0 +1,35 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +fun box(): String { + Foo( + logged("i", try { 1 } catch (e: Exception) { 42 }), + logged("j", 2) + ) + + val result = log.toString() + if (result != "ij") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Int) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 91a730010b4..da495a1c126 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -4275,6 +4275,45 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes } } + @TestMetadata("compiler/testData/codegen/box/constructorCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstructorCall extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInConstructorCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/constructorCall"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineFunInConstructorCallEvaluationOrder.kt") + public void testInlineFunInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") + public void testInlineFunInInnerClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInLocalClassConstructorCall.kt") + public void testInlineFunInLocalClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("regularConstructorCallEvaluationOrder.kt") + public void testRegularConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("tryCatchInConstructorCallEvaluationOrder.kt") + public void testTryCatchInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/controlStructures") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d893bfb2768..315fdb42ca4 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4275,6 +4275,45 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/constructorCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstructorCall extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInConstructorCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/constructorCall"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineFunInConstructorCallEvaluationOrder.kt") + public void testInlineFunInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") + public void testInlineFunInInnerClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInLocalClassConstructorCall.kt") + public void testInlineFunInLocalClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("regularConstructorCallEvaluationOrder.kt") + public void testRegularConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("tryCatchInConstructorCallEvaluationOrder.kt") + public void testTryCatchInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/controlStructures") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 5fff2fbb6f4..f4f8af9a1e3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -4275,6 +4275,45 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes } } + @TestMetadata("compiler/testData/codegen/box/constructorCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstructorCall extends AbstractLightAnalysisModeTest { + public void testAllFilesPresentInConstructorCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/constructorCall"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineFunInConstructorCallEvaluationOrder.kt") + public void testInlineFunInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") + public void testInlineFunInInnerClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInLocalClassConstructorCall.kt") + public void testInlineFunInLocalClassConstructorCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInLocalClassConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("regularConstructorCallEvaluationOrder.kt") + public void testRegularConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/regularConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + + @TestMetadata("tryCatchInConstructorCallEvaluationOrder.kt") + public void testTryCatchInConstructorCallEvaluationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/tryCatchInConstructorCallEvaluationOrder.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/controlStructures") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index ec688353b26..bd63ea2bcd9 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -4905,6 +4905,15 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { } } + @TestMetadata("compiler/testData/codegen/box/constructorCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstructorCall extends AbstractJsCodegenBoxTest { + public void testAllFilesPresentInConstructorCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/constructorCall"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + } + @TestMetadata("compiler/testData/codegen/box/controlStructures") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)