Fix for KT-7490: Bad bytecode generated by delegates and lambdas
#KT-7490 Fixed
This commit is contained in:
+47
-17
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.codegen.inline;
|
|||||||
|
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.util.ArrayUtil;
|
import com.intellij.util.ArrayUtil;
|
||||||
|
import kotlin.Function0;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil;
|
import org.jetbrains.kotlin.codegen.AsmUtil;
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilder;
|
import org.jetbrains.kotlin.codegen.ClassBuilder;
|
||||||
@@ -62,6 +63,8 @@ public class AnonymousObjectTransformer {
|
|||||||
|
|
||||||
private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
|
private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
|
||||||
|
|
||||||
|
private final TypeRemapper typeRemapper;
|
||||||
|
|
||||||
public AnonymousObjectTransformer(
|
public AnonymousObjectTransformer(
|
||||||
@NotNull String objectInternalName,
|
@NotNull String objectInternalName,
|
||||||
@NotNull InliningContext inliningContext,
|
@NotNull InliningContext inliningContext,
|
||||||
@@ -76,6 +79,7 @@ public class AnonymousObjectTransformer {
|
|||||||
this.newLambdaType = newLambdaType;
|
this.newLambdaType = newLambdaType;
|
||||||
|
|
||||||
reader = InlineCodegenUtil.buildClassReaderByInternalName(state, objectInternalName);
|
reader = InlineCodegenUtil.buildClassReaderByInternalName(state, objectInternalName);
|
||||||
|
typeRemapper = new TypeRemapper(inliningContext.typeMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) {
|
private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) {
|
||||||
@@ -181,13 +185,29 @@ public class AnonymousObjectTransformer {
|
|||||||
ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
|
ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
|
||||||
ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
|
ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
|
||||||
List<CapturedParamInfo> additionalFakeParams =
|
List<CapturedParamInfo> additionalFakeParams =
|
||||||
extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder, anonymousObjectGen);
|
extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder,
|
||||||
|
anonymousObjectGen);
|
||||||
|
List<MethodVisitor> deferringMethods = new ArrayList();
|
||||||
|
|
||||||
for (MethodNode next : methodsToTransform) {
|
for (MethodNode next : methodsToTransform) {
|
||||||
MethodVisitor visitor = newMethod(classBuilder, next);
|
MethodVisitor deferringVisitor = newMethod(classBuilder, next);
|
||||||
InlineResult funResult = inlineMethod(anonymousObjectGen, parentRemapper, visitor, next, allCapturedParamBuilder);
|
InlineResult funResult = inlineMethod(anonymousObjectGen, parentRemapper, deferringVisitor, next, allCapturedParamBuilder);
|
||||||
result.addAllClassesToRemove(funResult);
|
result.addAllClassesToRemove(funResult);
|
||||||
result.getReifiedTypeParametersUsages().mergeAll(funResult.getReifiedTypeParametersUsages());
|
result.getReifiedTypeParametersUsages().mergeAll(funResult.getReifiedTypeParametersUsages());
|
||||||
|
|
||||||
|
Type returnType = Type.getReturnType(next.desc);
|
||||||
|
if (!AsmUtil.isPrimitive(returnType)) {
|
||||||
|
String oldFunReturnType = returnType.getInternalName();
|
||||||
|
String newFunReturnType = funResult.getChangedTypes().get(oldFunReturnType);
|
||||||
|
if (newFunReturnType != null) {
|
||||||
|
typeRemapper.addAdditionalMappings(oldFunReturnType, newFunReturnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deferringMethods.add(deferringVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MethodVisitor method : deferringMethods) {
|
||||||
|
method.visitEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
InlineResult constructorResult =
|
InlineResult constructorResult =
|
||||||
@@ -207,7 +227,7 @@ public class AnonymousObjectTransformer {
|
|||||||
private InlineResult inlineMethod(
|
private InlineResult inlineMethod(
|
||||||
@NotNull AnonymousObjectGeneration anonymousObjectGen,
|
@NotNull AnonymousObjectGeneration anonymousObjectGen,
|
||||||
@NotNull FieldRemapper parentRemapper,
|
@NotNull FieldRemapper parentRemapper,
|
||||||
@NotNull MethodVisitor resultVisitor,
|
@NotNull MethodVisitor deferringVisitor,
|
||||||
@NotNull MethodNode sourceNode,
|
@NotNull MethodNode sourceNode,
|
||||||
@NotNull ParametersBuilder capturedBuilder
|
@NotNull ParametersBuilder capturedBuilder
|
||||||
) {
|
) {
|
||||||
@@ -223,10 +243,9 @@ public class AnonymousObjectTransformer {
|
|||||||
remapper, isSameModule, "Transformer for " + anonymousObjectGen.getOwnerInternalName(),
|
remapper, isSameModule, "Transformer for " + anonymousObjectGen.getOwnerInternalName(),
|
||||||
sourceMapper);
|
sourceMapper);
|
||||||
|
|
||||||
InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false, LabelOwner.NOT_APPLICABLE);
|
InlineResult result = inliner.doInline(deferringVisitor, new LocalVarRemapper(parameters, 0), false, LabelOwner.NOT_APPLICABLE);
|
||||||
result.getReifiedTypeParametersUsages().mergeAll(typeParametersToReify);
|
result.getReifiedTypeParametersUsages().mergeAll(typeParametersToReify);
|
||||||
resultVisitor.visitMaxs(-1, -1);
|
deferringVisitor.visitMaxs(-1, -1);
|
||||||
resultVisitor.visitEnd();
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,19 +357,30 @@ public class AnonymousObjectTransformer {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private ClassBuilder createClassBuilder() {
|
private ClassBuilder createClassBuilder() {
|
||||||
ClassBuilder classBuilder = state.getFactory().newVisitor(NO_ORIGIN, newLambdaType, inliningContext.getRoot().callElement.getContainingFile());
|
ClassBuilder classBuilder = state.getFactory().newVisitor(NO_ORIGIN, newLambdaType, inliningContext.getRoot().callElement.getContainingFile());
|
||||||
return new RemappingClassBuilder(classBuilder, new TypeRemapper(inliningContext.typeMapping));
|
return new RemappingClassBuilder(classBuilder, typeRemapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static MethodVisitor newMethod(@NotNull ClassBuilder builder, @NotNull MethodNode original) {
|
private static DeferredMethodVisitor newMethod(@NotNull final ClassBuilder builder, @NotNull final MethodNode original) {
|
||||||
return builder.newMethod(
|
return new DeferredMethodVisitor(
|
||||||
NO_ORIGIN,
|
new MethodNode(original.access,
|
||||||
original.access,
|
original.name,
|
||||||
original.name,
|
original.desc,
|
||||||
original.desc,
|
original.signature,
|
||||||
original.signature,
|
ArrayUtil.toStringArray(original.exceptions)),
|
||||||
ArrayUtil.toStringArray(original.exceptions)
|
|
||||||
);
|
new Function0<MethodVisitor>() {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor invoke() {
|
||||||
|
return builder.newMethod(
|
||||||
|
NO_ORIGIN,
|
||||||
|
original.access,
|
||||||
|
original.name,
|
||||||
|
original.desc,
|
||||||
|
original.signature,
|
||||||
|
ArrayUtil.toStringArray(original.exceptions));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
|
private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 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.inline
|
||||||
|
|
||||||
|
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
|
|
||||||
|
public class DeferredMethodVisitor(
|
||||||
|
val intermediate: MethodNode,
|
||||||
|
val resultNode: () -> MethodVisitor
|
||||||
|
) : MethodVisitor(InlineCodegenUtil.API, intermediate) {
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
super.visitEnd()
|
||||||
|
val resultVisitor = resultNode()
|
||||||
|
intermediate.accept(resultVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,12 +18,15 @@ package org.jetbrains.kotlin.codegen.inline;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class InlineResult {
|
public class InlineResult {
|
||||||
|
|
||||||
private final Set<String> classesToRemove = new HashSet<String>();
|
private final Set<String> classesToRemove = new HashSet<String>();
|
||||||
|
private final Map<String, String> changedTypes = new HashMap<String, String>();
|
||||||
private final ReifiedTypeParametersUsages reifiedTypeParametersUsages = new ReifiedTypeParametersUsages();
|
private final ReifiedTypeParametersUsages reifiedTypeParametersUsages = new ReifiedTypeParametersUsages();
|
||||||
|
|
||||||
private InlineResult() {
|
private InlineResult() {
|
||||||
@@ -44,11 +47,19 @@ public class InlineResult {
|
|||||||
classesToRemove.add(classInternalName);
|
classesToRemove.add(classInternalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addChangedType(@NotNull String oldClassInternalName, @NotNull String newClassInternalName) {
|
||||||
|
changedTypes.put(oldClassInternalName, newClassInternalName);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<String> getClassesToRemove() {
|
public Set<String> getClassesToRemove() {
|
||||||
return classesToRemove;
|
return classesToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getChangedTypes() {
|
||||||
|
return changedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public ReifiedTypeParametersUsages getReifiedTypeParametersUsages() {
|
public ReifiedTypeParametersUsages getReifiedTypeParametersUsages() {
|
||||||
return reifiedTypeParametersUsages;
|
return reifiedTypeParametersUsages;
|
||||||
|
|||||||
@@ -158,26 +158,28 @@ public class MethodInliner {
|
|||||||
|
|
||||||
if (anonymousObjectGen.shouldRegenerate()) {
|
if (anonymousObjectGen.shouldRegenerate()) {
|
||||||
//TODO: need poping of type but what to do with local funs???
|
//TODO: need poping of type but what to do with local funs???
|
||||||
Type newLambdaType = Type.getObjectType(inliningContext.nameGenerator.genLambdaClassName());
|
String oldClassName = anonymousObjectGen.getOwnerInternalName();
|
||||||
currentTypeMapping.put(anonymousObjectGen.getOwnerInternalName(), newLambdaType.getInternalName());
|
String newClassName = inliningContext.nameGenerator.genLambdaClassName();
|
||||||
|
currentTypeMapping.put(oldClassName, newClassName);
|
||||||
AnonymousObjectTransformer transformer =
|
AnonymousObjectTransformer transformer =
|
||||||
new AnonymousObjectTransformer(anonymousObjectGen.getOwnerInternalName(),
|
new AnonymousObjectTransformer(oldClassName,
|
||||||
inliningContext
|
inliningContext
|
||||||
.subInlineWithClassRegeneration(
|
.subInlineWithClassRegeneration(
|
||||||
inliningContext.nameGenerator,
|
inliningContext.nameGenerator,
|
||||||
currentTypeMapping,
|
currentTypeMapping,
|
||||||
anonymousObjectGen),
|
anonymousObjectGen),
|
||||||
isSameModule, newLambdaType
|
isSameModule, Type.getObjectType(newClassName)
|
||||||
);
|
);
|
||||||
|
|
||||||
InlineResult transformResult = transformer.doTransform(anonymousObjectGen, nodeRemapper);
|
InlineResult transformResult = transformer.doTransform(anonymousObjectGen, nodeRemapper);
|
||||||
result.addAllClassesToRemove(transformResult);
|
result.addAllClassesToRemove(transformResult);
|
||||||
|
result.addChangedType(oldClassName, newClassName);
|
||||||
|
|
||||||
if (inliningContext.isInliningLambda && !anonymousObjectGen.isStaticOrigin()) {
|
if (inliningContext.isInliningLambda && !anonymousObjectGen.isStaticOrigin()) {
|
||||||
// this class is transformed and original not used so we should remove original one after inlining
|
// this class is transformed and original not used so we should remove original one after inlining
|
||||||
// Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
|
// Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
|
||||||
// because it can be local function from outer scope
|
// because it can be local function from outer scope
|
||||||
result.addClassToRemove(anonymousObjectGen.getOwnerInternalName());
|
result.addClassToRemove(oldClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transformResult.getReifiedTypeParametersUsages().wereUsedReifiedParameters()) {
|
if (transformResult.getReifiedTypeParametersUsages().wereUsedReifiedParameters()) {
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ package org.jetbrains.kotlin.codegen.inline;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.org.objectweb.asm.commons.Remapper;
|
import org.jetbrains.org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class TypeRemapper extends Remapper {
|
public class TypeRemapper extends Remapper {
|
||||||
private final Map<String, String> typeMapping;
|
private final Map<String, String> typeMapping;
|
||||||
|
private Map<String, String> additionalMappings;
|
||||||
|
|
||||||
//typeMapping could be changed outside through method processing
|
//typeMapping could be changed outside through method processing
|
||||||
public TypeRemapper(@NotNull Map<String, String> typeMapping) {
|
public TypeRemapper(@NotNull Map<String, String> typeMapping) {
|
||||||
@@ -36,6 +38,18 @@ public class TypeRemapper extends Remapper {
|
|||||||
return newType;
|
return newType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (additionalMappings != null) {
|
||||||
|
newType = additionalMappings.get(type);
|
||||||
|
if (newType != null) {
|
||||||
|
return newType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addAdditionalMappings(String oldName, String newName) {
|
||||||
|
if (additionalMappings == null) additionalMappings = new HashMap<String, String>();
|
||||||
|
additionalMappings.put(oldName, newName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//NO_CHECK_LAMBDA_INLINING
|
||||||
|
//KT-7490
|
||||||
|
import test.*
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
return Entity("OK").directed().calc().value
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
open class Entity(val value: String)
|
||||||
|
|
||||||
|
public abstract class Task<T>() {
|
||||||
|
abstract fun calc(): T
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Self : Entity> nullableTask(factory: () -> Task<Self>): Task<Self> {
|
||||||
|
return factory()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun<reified Self : Entity> Self.directed(): Task<Self> =
|
||||||
|
nullableTask {
|
||||||
|
object : Task<Self>() {
|
||||||
|
override fun calc(): Self = this@directed
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -90,6 +90,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
|
|||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/capturedLambdaInInlineObject.1.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/capturedLambdaInInlineObject.1.kt");
|
||||||
doTestMultiFileWithInlineCheck(fileName);
|
doTestMultiFileWithInlineCheck(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("changingReturnType.1.kt")
|
||||||
|
public void testChangingReturnType() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.1.kt");
|
||||||
|
doTestMultiFileWithInlineCheck(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/boxInline/builders")
|
@TestMetadata("compiler/testData/codegen/boxInline/builders")
|
||||||
|
|||||||
+6
@@ -90,6 +90,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
|
|||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/capturedLambdaInInlineObject.1.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/capturedLambdaInInlineObject.1.kt");
|
||||||
doBoxTestWithInlineCheck(fileName);
|
doBoxTestWithInlineCheck(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("changingReturnType.1.kt")
|
||||||
|
public void testChangingReturnType() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.1.kt");
|
||||||
|
doBoxTestWithInlineCheck(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/boxInline/builders")
|
@TestMetadata("compiler/testData/codegen/boxInline/builders")
|
||||||
|
|||||||
Reference in New Issue
Block a user