Fix for KT-7490: Bad bytecode generated by delegates and lambdas

#KT-7490 Fixed
This commit is contained in:
Michael Bogdanov
2015-05-09 14:36:22 +03:00
parent aed6ed0f9b
commit d6964525b2
9 changed files with 148 additions and 22 deletions
@@ -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
}
}
@@ -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")
@@ -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")