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.<init>') 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

<initialize class C using Class.forName>
<evaluate constructor arguments>
<store constructor arguments to variables>
NEW C
DUP
<load constructor arguments from variables>
INVOKESPECIAL C.<init>(...)

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.
This commit is contained in:
Dmitry Petrov
2017-07-28 16:20:10 +03:00
parent 3d8486e8a6
commit c0a83c3c8a
17 changed files with 494 additions and 108 deletions
@@ -16,10 +16,8 @@
package org.jetbrains.kotlin.codegen.coroutines package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.optimization.common.CustomFramesMethodAnalyzer import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
import org.jetbrains.kotlin.codegen.optimization.common.insnListOf
import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.* import org.jetbrains.org.objectweb.asm.tree.*
@@ -71,124 +69,161 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
* - restore constructor arguments * - restore constructor arguments
*/ */
internal fun processUninitializedStores(methodNode: MethodNode) { internal fun processUninitializedStores(methodNode: MethodNode) {
val interpreter = UninitializedNewValueMarkerInterpreter() UninitializedStoresProcessor(methodNode, forCoroutines = true).run()
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<AbstractInsnNode> = 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<Type>()
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))
}
}
} }
private class UninitializedNewValue( class UninitializedStoresProcessor(private val methodNode: MethodNode, private val forCoroutines: Boolean) {
val newInsn: TypeInsnNode, val internalName: String private val isInSpecialMethod = methodNode.name == "<init>" || methodNode.name == "<clinit>"
) : StrictBasicValue(Type.getObjectType(internalName)) {
override fun toString() = "UninitializedNewValue(internalName='$internalName')"
}
private class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) { fun run() {
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>?) { val interpreter = UninitializedNewValueMarkerInterpreter()
val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null
super.execute(insn, interpreter) val frames = CustomFramesMethodAnalyzer(
"fake", methodNode, interpreter,
this::UninitializedNewValueFrame
).analyze()
if (replaceTopValueWithInitialized) { for ((index, insn) in methodNode.instructions.toArray().withIndex()) {
// Drop top value val frame = frames[index] ?: continue
val value = pop() as UninitializedNewValue val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue
// uninitialized value become initialized after <init> call val newInsn = uninitializedValue.newInsn
push(StrictBasicValue(value.type)) val copyUsages: Set<AbstractInsnNode> = 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<Type>()
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))
}
} }
} }
}
/** private inner class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
* @return value generated by NEW that used as 0-th argument of constructor call or null if current instruction is not constructor call override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>?) {
*/ val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null
private fun Frame<BasicValue>.getUninitializedValueForConstructorCall(
insn: AbstractInsnNode
): UninitializedNewValue? {
if (!insn.isConstructorCall()) return null
assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for <init>, but ${insn.opcode} found" } super.execute(insn, interpreter)
val paramsCountIncludingReceiver = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + 1
val newValue = getStack(stackSize - (paramsCountIncludingReceiver + 1)) as? UninitializedNewValue ?: error("Expected value generated with NEW")
assert(getStack(stackSize - paramsCountIncludingReceiver) is UninitializedNewValue) { if (replaceTopValueWithInitialized) {
"Next value after NEW should be one generated by DUP" // Drop top value
val value = pop() as UninitializedNewValue
// uninitialized value become initialized after <init> 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<BasicValue>.getUninitializedValueForConstructorCall(insn: AbstractInsnNode): UninitializedNewValue? {
if (!insn.isConstructorCall()) return null
private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "<init>" assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for <init>, 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() { assert(peek(paramsCountIncludingReceiver - 1) is UninitializedNewValue) {
val uninitializedValuesToCopyUsages = hashMapOf<AbstractInsnNode, MutableSet<AbstractInsnNode>>() "Next value after NEW should be one generated by DUP"
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)
return newValue
} }
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? { private class UninitializedNewValue(
if (value is UninitializedNewValue) { val newInsn: TypeInsnNode,
uninitializedValuesToCopyUsages[value.newInsn]!!.add(insn) val internalName: String
return value ) : StrictBasicValue(Type.getObjectType(internalName)) {
} override fun toString() = "UninitializedNewValue(internalName='$internalName')"
return super.copyOperation(insn, value)
} }
override fun merge(v: BasicValue, w: BasicValue): BasicValue {
if (v === w) return v private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "<init>"
if (v === StrictBasicValue.UNINITIALIZED_VALUE || w === StrictBasicValue.UNINITIALIZED_VALUE) {
return StrictBasicValue.UNINITIALIZED_VALUE private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpreter() {
val uninitializedValuesToCopyUsages = hashMapOf<AbstractInsnNode, MutableSet<AbstractInsnNode>>()
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) { override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? {
if ((v as? UninitializedNewValue)?.newInsn !== (w as? UninitializedNewValue)?.newInsn) { if (value is UninitializedNewValue) {
// Merge of two different ANEW result is possible, but such values should not be used further 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 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)
}
} }
} }
@@ -17,9 +17,7 @@
package org.jetbrains.kotlin.codegen.optimization; package org.jetbrains.kotlin.codegen.optimization;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.ClassBuilder;
import org.jetbrains.kotlin.codegen.ClassBuilderFactory; import org.jetbrains.kotlin.codegen.ClassBuilderFactory;
import org.jetbrains.kotlin.codegen.ClassBuilderMode;
import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory; import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
@@ -17,9 +17,9 @@
package org.jetbrains.kotlin.codegen.optimization package org.jetbrains.kotlin.codegen.optimization
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor 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.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.common.prepareForEmitting
import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer
@@ -35,11 +35,10 @@ class OptimizationMethodVisitor(
signature: String?, signature: String?,
exceptions: Array<String>? exceptions: Array<String>?
) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { ) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) {
override fun performTransformations(methodNode: MethodNode) { override fun performTransformations(methodNode: MethodNode) {
MANDATORY_METHOD_TRANSFORMER.transform("fake", methodNode) normalizationMethodTransformer.transform("fake", methodNode)
if (canBeOptimized(methodNode) && !disableOptimization) { if (canBeOptimized(methodNode) && !disableOptimization) {
OPTIMIZATION_TRANSFORMER.transform("fake", methodNode) optimizationTransformer.transform("fake", methodNode)
} }
methodNode.prepareForEmitting() methodNode.prepareForEmitting()
} }
@@ -47,12 +46,13 @@ class OptimizationMethodVisitor(
companion object { companion object {
private val MEMORY_LIMIT_BY_METHOD_MB = 50 private val MEMORY_LIMIT_BY_METHOD_MB = 50
private val MANDATORY_METHOD_TRANSFORMER = CompositeMethodTransformer( val normalizationMethodTransformer = CompositeMethodTransformer(
FixStackWithLabelNormalizationMethodTransformer(), FixStackWithLabelNormalizationMethodTransformer(),
UninitializedStoresMethodTransformer(),
MethodVerifier("AFTER mandatory stack transformations") MethodVerifier("AFTER mandatory stack transformations")
) )
private val OPTIMIZATION_TRANSFORMER = CompositeMethodTransformer( val optimizationTransformer = CompositeMethodTransformer(
CapturedVarsOptimizationMethodTransformer(), CapturedVarsOptimizationMethodTransformer(),
RedundantNullCheckMethodTransformer(), RedundantNullCheckMethodTransformer(),
RedundantCheckCastEliminationMethodTransformer(), RedundantCheckCastEliminationMethodTransformer(),
@@ -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()
}
}
@@ -214,3 +214,6 @@ internal inline fun <reified T : AbstractInsnNode> AbstractInsnNode.isInsn(opcod
internal inline fun <reified T : AbstractInsnNode> AbstractInsnNode.takeInsnIf(opcode: Int, condition: T.() -> Boolean): T? = internal inline fun <reified T : AbstractInsnNode> AbstractInsnNode.takeInsnIf(opcode: Int, condition: T.() -> Boolean): T? =
takeIf { it.opcode == opcode }?.safeAs<T>()?.takeIf { it.condition() } takeIf { it.opcode == opcode }?.safeAs<T>()?.takeIf { it.condition() }
fun InsnList.removeAll(nodes: Collection<AbstractInsnNode>) {
for (node in nodes) remove(node)
}
@@ -18,8 +18,15 @@ package org.jetbrains.kotlin.codegen.optimization.transformer
import org.jetbrains.org.objectweb.asm.tree.MethodNode 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>) : MethodTransformer() {
constructor(vararg transformers: MethodTransformer?) : this(transformers.filterNotNull())
override fun transform(internalClassName: String, methodNode: MethodNode) { override fun transform(internalClassName: String, methodNode: MethodNode) {
transformers.forEach { it.transform(internalClassName, methodNode) } transformers.forEach { it.transform(internalClassName, methodNode) }
} }
companion object {
inline fun build(builder: MutableList<MethodTransformer>.() -> Unit) =
CompositeMethodTransformer(ArrayList<MethodTransformer>().apply { builder() })
}
} }
@@ -179,7 +179,6 @@ class GenerationState @JvmOverloads constructor(
val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA) val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA)
val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
init { init {
@@ -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 != "<clinit>ik<init>j<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Foo?) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -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.<clinit>;i;j;Foo.<init>;Inner.<init>;") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
open class Foo {
init {
log.append("Foo.<init>;")
}
companion object {
init {
log.append("Foo.<clinit>;")
}
}
}
class Outer {
inner class Inner(val x: Int, val y: Int) : Foo() {
init {
log.append("Inner.<init>;")
}
}
}
@@ -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.<init>;")
}
}
Local(
logged("i;", 1.let { it }),
logged("j;", 2.let { it })
)
val result = log.toString()
if (result != "Foo.<clinit>;i;j;Foo.<init>;Local.<init>;") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
open class Foo {
init {
log.append("Foo.<init>;")
}
companion object {
init {
log.append("Foo.<clinit>;")
}
}
}
@@ -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 != "<clinit>ij<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Int) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -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 != "<clinit>ij<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Int) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -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") @TestMetadata("compiler/testData/codegen/box/controlStructures")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -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") @TestMetadata("compiler/testData/codegen/box/controlStructures")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -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") @TestMetadata("compiler/testData/codegen/box/controlStructures")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -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") @TestMetadata("compiler/testData/codegen/box/controlStructures")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)