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:
+57
-22
@@ -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,21 +69,53 @@ 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) {
|
||||||
|
UninitializedStoresProcessor(methodNode, forCoroutines = true).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
class UninitializedStoresProcessor(private val methodNode: MethodNode, private val forCoroutines: Boolean) {
|
||||||
|
private val isInSpecialMethod = methodNode.name == "<init>" || methodNode.name == "<clinit>"
|
||||||
|
|
||||||
|
fun run() {
|
||||||
val interpreter = UninitializedNewValueMarkerInterpreter()
|
val interpreter = UninitializedNewValueMarkerInterpreter()
|
||||||
val frames = CustomFramesMethodAnalyzer("fake", methodNode, interpreter, ::UninitializedNewValueFrame).analyze()
|
|
||||||
|
val frames = CustomFramesMethodAnalyzer(
|
||||||
|
"fake", methodNode, interpreter,
|
||||||
|
this::UninitializedNewValueFrame
|
||||||
|
).analyze()
|
||||||
|
|
||||||
for ((index, insn) in methodNode.instructions.toArray().withIndex()) {
|
for ((index, insn) in methodNode.instructions.toArray().withIndex()) {
|
||||||
val frame = frames[index] ?: continue
|
val frame = frames[index] ?: continue
|
||||||
val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue
|
val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue
|
||||||
|
|
||||||
val copyUsages: Set<AbstractInsnNode> = interpreter.uninitializedValuesToCopyUsages[uninitializedValue.newInsn]!!
|
val newInsn = uninitializedValue.newInsn
|
||||||
assert(copyUsages.size > 0) { "At least DUP copy operation expected" }
|
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)
|
// Value generated by NEW wasn't store to local/field (only DUPed)
|
||||||
if (copyUsages.size == 1) continue
|
if (copyUsages.size == 1) continue
|
||||||
|
|
||||||
(copyUsages + uninitializedValue.newInsn).forEach {
|
methodNode.instructions.run {
|
||||||
methodNode.instructions.remove(it)
|
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 indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size
|
||||||
@@ -102,7 +132,7 @@ internal fun processUninitializedStores(methodNode: MethodNode) {
|
|||||||
methodNode.maxLocals = Math.max(methodNode.maxLocals, nextVarIndex)
|
methodNode.maxLocals = Math.max(methodNode.maxLocals, nextVarIndex)
|
||||||
|
|
||||||
methodNode.instructions.insertBefore(insn, insnListOf(
|
methodNode.instructions.insertBefore(insn, insnListOf(
|
||||||
TypeInsnNode(Opcodes.NEW, uninitializedValue.newInsn.desc),
|
TypeInsnNode(Opcodes.NEW, newInsn.desc),
|
||||||
InsnNode(Opcodes.DUP)
|
InsnNode(Opcodes.DUP)
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -113,13 +143,7 @@ internal fun processUninitializedStores(methodNode: MethodNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UninitializedNewValue(
|
private inner class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
||||||
val newInsn: TypeInsnNode, val internalName: String
|
|
||||||
) : StrictBasicValue(Type.getObjectType(internalName)) {
|
|
||||||
override fun toString() = "UninitializedNewValue(internalName='$internalName')"
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
|
||||||
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>?) {
|
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>?) {
|
||||||
val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null
|
val replaceTopValueWithInitialized = getUninitializedValueForConstructorCall(insn) != null
|
||||||
|
|
||||||
@@ -138,22 +162,32 @@ private class UninitializedNewValueFrame(nLocals: Int, nStack: Int) : Frame<Basi
|
|||||||
/**
|
/**
|
||||||
* @return value generated by NEW that used as 0-th argument of constructor call or null if current instruction is not constructor call
|
* @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(
|
private fun Frame<BasicValue>.getUninitializedValueForConstructorCall(insn: AbstractInsnNode): UninitializedNewValue? {
|
||||||
insn: AbstractInsnNode
|
|
||||||
): UninitializedNewValue? {
|
|
||||||
if (!insn.isConstructorCall()) return null
|
if (!insn.isConstructorCall()) return null
|
||||||
|
|
||||||
assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for <init>, but ${insn.opcode} found" }
|
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 paramsCountIncludingReceiver = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + 1
|
||||||
val newValue = getStack(stackSize - (paramsCountIncludingReceiver + 1)) as? UninitializedNewValue ?: error("Expected value generated with NEW")
|
val newValue = peek(paramsCountIncludingReceiver) as? UninitializedNewValue ?:
|
||||||
|
if (isInSpecialMethod)
|
||||||
|
return null
|
||||||
|
else
|
||||||
|
error("Expected value generated with NEW")
|
||||||
|
|
||||||
assert(getStack(stackSize - paramsCountIncludingReceiver) is UninitializedNewValue) {
|
assert(peek(paramsCountIncludingReceiver - 1) is UninitializedNewValue) {
|
||||||
"Next value after NEW should be one generated by DUP"
|
"Next value after NEW should be one generated by DUP"
|
||||||
}
|
}
|
||||||
|
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class UninitializedNewValue(
|
||||||
|
val newInsn: TypeInsnNode,
|
||||||
|
val internalName: String
|
||||||
|
) : StrictBasicValue(Type.getObjectType(internalName)) {
|
||||||
|
override fun toString() = "UninitializedNewValue(internalName='$internalName')"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "<init>"
|
private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "<init>"
|
||||||
|
|
||||||
private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpreter() {
|
private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpreter() {
|
||||||
@@ -192,3 +226,4 @@ private class UninitializedNewValueMarkerInterpreter : OptimizationBasicInterpre
|
|||||||
return super.merge(v, w)
|
return super.merge(v, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
-2
@@ -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;
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -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(),
|
||||||
|
|||||||
+27
@@ -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)
|
||||||
|
}
|
||||||
+8
-1
@@ -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 {
|
||||||
|
|||||||
+40
@@ -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>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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>;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -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>;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -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>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -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>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user