Properly generate inner classes when compiling source for inline

KT-19175: Compiler generates different bytecode when classes are compiled separately or together

 #KT-19175 Fixed
This commit is contained in:
Mikhael Bogdanov
2017-07-26 13:32:46 +02:00
parent 7da6ff78c1
commit ff676c050f
24 changed files with 312 additions and 32 deletions
@@ -192,6 +192,6 @@ public abstract class ClassBodyCodegen extends MemberCodegen<KtPureClassOrObject
@Nullable
@Override
protected ClassDescriptor classForInnerClassRecord() {
return DescriptorUtils.isTopLevelDeclaration(descriptor) ? null : descriptor;
return InnerClassConsumer.Companion.classForInnerClassRecord(descriptor, false);
}
}
@@ -17,7 +17,35 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import java.util.*
interface InnerClassConsumer {
fun addInnerClassInfoFromAnnotation(classDescriptor: ClassDescriptor)
companion object {
fun classForInnerClassRecord(descriptor: ClassDescriptor, defaultImpls: Boolean): ClassDescriptor? {
if (defaultImpls) {
if (DescriptorUtils.isLocal(descriptor)) return null
val classDescriptorImpl = ClassDescriptorImpl(
descriptor, Name.identifier(JvmAbi.DEFAULT_IMPLS_CLASS_NAME),
Modality.FINAL, ClassKind.CLASS, Collections.emptyList(), SourceElement.NO_SOURCE,
/* isExternal = */ false)
classDescriptorImpl.initialize(MemberScope.Empty, emptySet(), null)
return classDescriptorImpl
}
else {
return if (DescriptorUtils.isTopLevelDeclaration(descriptor)) null else descriptor
}
}
}
}
@@ -60,14 +60,7 @@ class InterfaceImplBodyCodegen(
override fun classForInnerClassRecord(): ClassDescriptor? {
if (!isAnythingGenerated) return null
if (DescriptorUtils.isLocal(descriptor)) return null
val classDescriptorImpl = ClassDescriptorImpl(
descriptor, Name.identifier(JvmAbi.DEFAULT_IMPLS_CLASS_NAME),
Modality.FINAL, ClassKind.CLASS, Collections.emptyList(), SourceElement.NO_SOURCE,
/* isExternal = */ false)
classDescriptorImpl.initialize(MemberScope.Empty, emptySet(), null)
return classDescriptorImpl
return InnerClassConsumer.classForInnerClassRecord(descriptor, true)
}
override fun generateSyntheticPartsAfterBody() {
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl;
import org.jetbrains.kotlin.fileClasses.FileClasses;
import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
@@ -50,7 +49,6 @@ import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.synthetics.SyntheticClassOrObjectDescriptor;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
@@ -344,12 +342,7 @@ public abstract class MemberCodegen<T extends KtPureElement/* TODO: & KtDeclarat
parentCodegen.innerClasses.add(classDescriptor);
}
for (MemberCodegen<?> codegen = this; codegen != null; codegen = codegen.getParentCodegen()) {
ClassDescriptor outerClass = codegen.classForInnerClassRecord();
if (outerClass != null) {
innerClasses.add(outerClass);
}
}
addParentsToInnerClassesIfNeeded(innerClasses);
}
for (ClassDescriptor innerClass : innerClasses) {
@@ -357,6 +350,18 @@ public abstract class MemberCodegen<T extends KtPureElement/* TODO: & KtDeclarat
}
}
protected void addParentsToInnerClassesIfNeeded(@NotNull Collection<ClassDescriptor> innerClasses) {
ClassDescriptor outerClass = classForInnerClassRecord();
if (outerClass != null) {
innerClasses.add(outerClass);
}
MemberCodegen<?> parentCodegen = getParentCodegen();
if (parentCodegen != null) {
parentCodegen.addParentsToInnerClassesIfNeeded(innerClasses);
}
}
// It's necessary for proper recovering of classId by plain string JVM descriptor when loading annotations
// See FileBasedKotlinClass.convertAnnotationVisitor
@Override
@@ -460,7 +460,7 @@ abstract class InlineCodegen<out T: BaseExpressionCodegen>(
if (isBuiltInArrayIntrinsic(callableDescriptor)) {
val classId = classId
val bytes = state.inlineCache.classBytes.getOrPut(classId) { bytecode }
return getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, classId.asString())
return getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, AsmUtil.asmTypeByClassId(classId))
}
assert(callableDescriptor is DeserializedCallableMemberDescriptor) { "Not a deserialized function or proper: " + callableDescriptor }
@@ -474,7 +474,7 @@ abstract class InlineCodegen<out T: BaseExpressionCodegen>(
throw IllegalStateException("Couldn't find declaration file for " + containerId)
}
val methodNode = getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, containerId.asString()) ?: return null
val methodNode = getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, AsmUtil.asmTypeByClassId(containerId)) ?: return null
// KLUDGE: Inline suspend function built with compiler version less than 1.1.4/1.2-M1 did not contain proper
// before/after suspension point marks, so we detect those functions here and insert the corresponding marks
@@ -135,7 +135,7 @@ class DefaultLambda(
classReader.b,
"<init>",
descriptor,
lambdaClassType.internalName)?.node
lambdaClassType)?.node
assert(constructor != null || capturedArgs.isEmpty()) {
"Can't find non-default constructor <init>$descriptor for default lambda $lambdaClassType"
@@ -164,7 +164,7 @@ class DefaultLambda(
classReader.b,
invokeMethod.name,
invokeMethod.descriptor,
lambdaClassType.internalName)!!
lambdaClassType)!!
if (needReification) {
//nested classes could also require reification
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -94,6 +95,8 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
private var context by Delegates.notNull<CodegenContext<*>>()
private var additionalInnerClasses = mutableListOf<ClassDescriptor>()
override val lookupLocation = KotlinLookupLocation(callElement)
@@ -164,7 +167,9 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
if (isLambda)
codegen.parentCodegen.className
else
state.typeMapper.mapImplementationOwner(descriptor).internalName
state.typeMapper.mapImplementationOwner(descriptor).internalName,
if (isLambda) emptyList() else additionalInnerClasses,
isLambda
)
val strategy = when (expression) {
@@ -222,7 +227,9 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
internal val delegate: MemberCodegen<*>,
declaration: KtElement,
codegenContext: FieldOwnerContext<*>,
private val className: String
private val className: String,
private val parentAsInnerClasses: List<ClassDescriptor>,
private val isInlineLambdaCodegen: Boolean
) : MemberCodegen<KtPureElement>(delegate as MemberCodegen<KtPureElement>, declaration, codegenContext) {
override fun generateDeclaration() {
@@ -246,6 +253,14 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
return className
}
override fun addParentsToInnerClassesIfNeeded(innerClasses: MutableCollection<ClassDescriptor>) {
if (isInlineLambdaCodegen) {
super.addParentsToInnerClassesIfNeeded(innerClasses)
}
else {
innerClasses.addAll(parentAsInnerClasses)
}
}
}
override fun doCreateMethodNodeFromSource(
@@ -278,7 +293,9 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
val implementationOwner = state.typeMapper.mapImplementationOwner(callableDescriptor)
val parentCodegen = FakeMemberCodegen(
codegen.parentCodegen, inliningFunction!!, methodContext.parentContext as FieldOwnerContext<*>,
implementationOwner.internalName
implementationOwner.internalName,
additionalInnerClasses,
false
)
if (element !is KtNamedFunction) {
throw IllegalStateException("Property accessors with default parameters not supported " + callableDescriptor)
@@ -389,22 +406,25 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
}
override fun initializeInlineFunctionContext(functionDescriptor: FunctionDescriptor) {
context = getContext(functionDescriptor, state, DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor)?.containingFile as? KtFile)
context = getContext(functionDescriptor, functionDescriptor, state, DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor)?.containingFile as? KtFile, additionalInnerClasses)
}
companion object {
fun getContext(
descriptor: DeclarationDescriptor, state: GenerationState, sourceFile: KtFile?
descriptor: DeclarationDescriptor, innerDescriptor: DeclarationDescriptor, state: GenerationState, sourceFile: KtFile?, additionalInners: MutableList<ClassDescriptor>
): CodegenContext<*> {
if (descriptor is PackageFragmentDescriptor) {
//no inners
return PackageContext(descriptor, state.rootContext, null, sourceFile)
}
val container = descriptor.containingDeclaration ?: error("No container for descriptor: " + descriptor)
val parent = getContext(
container,
descriptor,
state,
sourceFile
sourceFile,
additionalInners
)
return when (descriptor) {
@@ -417,7 +437,14 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
)
}
is ClassDescriptor -> {
val kind = if (DescriptorUtils.isInterface(descriptor)) OwnerKind.DEFAULT_IMPLS else OwnerKind.IMPLEMENTATION
val kind =
if (DescriptorUtils.isInterface(descriptor) && innerDescriptor !is ClassDescriptor)
OwnerKind.DEFAULT_IMPLS
else OwnerKind.IMPLEMENTATION
additionalInners.addIfNotNull(
InnerClassConsumer.classForInnerClassRecord(descriptor, kind == OwnerKind.DEFAULT_IMPLS)
)
parent.intoClass(descriptor, kind, state)
}
is FunctionDescriptor -> {
@@ -89,12 +89,13 @@ private const val INLINE_MARKER_FINALLY_START = "finallyStart"
private const val INLINE_MARKER_FINALLY_END = "finallyEnd"
private const val INLINE_MARKER_BEFORE_SUSPEND_ID = 0
private const val INLINE_MARKER_AFTER_SUSPEND_ID = 1
private val INTRINSIC_ARRAY_CONSTRUCTOR_TYPE = AsmUtil.asmTypeByClassId(classId)
internal fun getMethodNode(
classData: ByteArray,
methodName: String,
methodDescriptor: String,
classInternalName: String
classType: Type
): SMAPAndMethodNode? {
val cr = ClassReader(classData)
var node: MethodNode? = null
@@ -136,12 +137,12 @@ internal fun getMethodNode(
return null
}
if (classId.asString() == classInternalName) {
if (INTRINSIC_ARRAY_CONSTRUCTOR_TYPE == classType) {
// Don't load source map for intrinsic array constructors
debugInfo[0] = null
}
val smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classInternalName, lines[0], lines[1])
val smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classType.internalName, lines[0], lines[1])
return SMAPAndMethodNode(node!!, smap)
}
@@ -0,0 +1,79 @@
// FILE: 1.kt
package test
abstract class Introspector {
abstract inner class SchemaRetriever(val transaction: String) {
inline fun inSchema(crossinline modifier: (String) -> Unit) =
{ modifier.invoke(transaction) }()
}
}
// FILE: 2.kt
import test.*
var result = "fail"
class IntrospectorImpl() : Introspector() {
inner class SchemaRetriever(transaction: String) : Introspector.SchemaRetriever(transaction) {
internal fun retrieve() {
inSchema { schema -> result = schema }
}
}
}
fun box(): String {
IntrospectorImpl().SchemaRetriever("OK").retrieve()
return result
}
// FILE: 1.smap
SMAP
1.kt
Kotlin
*S Kotlin
*F
+ 1 1.kt
test/Introspector$SchemaRetriever$inSchema$1
*L
1#1,11:1
*E
// FILE: 2.smap
SMAP
2.kt
Kotlin
*S Kotlin
*F
+ 1 2.kt
IntrospectorImpl$SchemaRetriever
+ 2 1.kt
test/Introspector$SchemaRetriever
*L
1#1,21:1
7#2:22
*E
*S KotlinDebug
*F
+ 1 2.kt
IntrospectorImpl$SchemaRetriever
*L
9#1:22
*E
SMAP
1.kt
Kotlin
*S Kotlin
*F
+ 1 1.kt
test/Introspector$SchemaRetriever$inSchema$1
+ 2 2.kt
IntrospectorImpl$SchemaRetriever
*L
1#1,11:1
9#2:12
*E
+14
View File
@@ -0,0 +1,14 @@
class Introspector {
inner class SchemaRetriever(val transaction: String) {
inline fun inSchema(crossinline modifier: (String) -> Unit) =
{ modifier(transaction) }()
internal fun retrieve() {
inSchema { schema -> "OK" }
}
}
}
// TESTED_OBJECT_KIND: innerClass
// TESTED_OBJECTS: Introspector$SchemaRetriever$inSchema$1, SchemaRetriever
// FLAGS: ACC_FINAL, ACC_PUBLIC
+20
View File
@@ -0,0 +1,20 @@
interface Introspector {
class SchemaRetriever(val transaction: String) {
inline fun inSchema(crossinline modifier: (String) -> Unit) =
{ modifier(transaction) }()
internal fun retrieve() {
inSchema { schema -> "OK" }
}
}
}
// TESTED_OBJECT_KIND: innerClass
// TESTED_OBJECTS: Introspector$SchemaRetriever$inSchema$1, SchemaRetriever
// FLAGS: ACC_FINAL, ACC_PUBLIC, ACC_STATIC
// TESTED_OBJECT_KIND: innerClass
// TESTED_OBJECTS: Introspector$SchemaRetriever$inSchema$1, DefaultImpls
// ABSENT: true
+20
View File
@@ -0,0 +1,20 @@
interface Introspector {
fun test() {
class SchemaRetriever(val transaction: String) {
inline fun inSchema(crossinline modifier: (String) -> Unit) =
{ modifier(transaction) }()
internal fun retrieve() {
inSchema { schema -> "OK" }
}
}
}
}
// TESTED_OBJECT_KIND: innerClass
// TESTED_OBJECTS: Introspector$test$SchemaRetriever, SchemaRetriever
// FLAGS: ACC_FINAL, ACC_PUBLIC, ACC_STATIC
// TESTED_OBJECT_KIND: innerClass
// TESTED_OBJECTS: Introspector$test$SchemaRetriever, DefaultImpls
// FLAGS: ACC_FINAL, ACC_PUBLIC, ACC_STATIC
@@ -261,7 +261,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
@Override
public void visitInnerClass(@NotNull String innerClassInternalName, String outerClassInternalName, String name, int access) {
if (name.equals(innerClassName)) {
if (innerClassName.equals(name)) {
this.access = access;
isExists = true;
}
@@ -2755,6 +2755,12 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/smap/anonymous"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("kt19175.kt")
public void testKt19175() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/kt19175.kt");
doTest(fileName);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/lambda.kt");
@@ -2755,6 +2755,12 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/smap/anonymous"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("kt19175.kt")
public void testKt19175() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/kt19175.kt");
doTest(fileName);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/lambda.kt");
@@ -2755,6 +2755,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/smap/anonymous"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("kt19175.kt")
public void testKt19175() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/kt19175.kt");
doTest(fileName);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/lambda.kt");
@@ -2755,6 +2755,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/smap/anonymous"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("kt19175.kt")
public void testKt19175() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/kt19175.kt");
doTest(fileName);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/smap/anonymous/lambda.kt");
@@ -609,6 +609,33 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/inline")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Inline extends AbstractWriteFlagsTest {
public void testAllFilesPresentInInline() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeFlags/inline"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("lostInnerClass.kt")
public void testLostInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeFlags/inline/lostInnerClass.kt");
doTest(fileName);
}
@TestMetadata("lostInnerClass2.kt")
public void testLostInnerClass2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeFlags/inline/lostInnerClass2.kt");
doTest(fileName);
}
@TestMetadata("lostInnerClass3.kt")
public void testLostInnerClass3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeFlags/inline/lostInnerClass3.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/writeFlags/innerClass")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -1466,6 +1466,12 @@ public class ExperimentalIncrementalJpsTestGenerated extends AbstractExperimenta
doTest(fileName);
}
@TestMetadata("innerClassNotGeneratedWhenRebuilding")
public void testInnerClassNotGeneratedWhenRebuilding() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/innerClassNotGeneratedWhenRebuilding/");
doTest(fileName);
}
@TestMetadata("jvmNameChanged")
public void testJvmNameChanged() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/jvmNameChanged/");
@@ -0,0 +1,10 @@
abstract class Introspector<M : Model>(protected val model: Model) {
protected abstract inner class Retriever(protected val transaction: Any) {
protected var model: Model = this@Introspector.model
}
protected abstract inner class SchemaRetriever(transaction: Any): Retriever(transaction) {
protected inline fun inSchema(crossinline modifier: (Any) -> Unit) =
model.modify { schema -> modifier.invoke(schema) }
}
}
@@ -0,0 +1,12 @@
class IntrospectorImpl(model: ModuleImpl) : Introspector<ModuleImpl>(model) {
private inner class SchemaRetriever(transaction: Any) : Introspector<ModuleImpl>.SchemaRetriever(transaction) {
internal fun retrieve() {
inSchema { schema -> println(schema) }
}
}
}
class ModuleImpl : Model {
override fun modify(modifier: ModelModifier) {}
}
@@ -0,0 +1,4 @@
public interface Model {
void modify(ModelModifier modifier);
}
@@ -0,0 +1,3 @@
public interface ModelModifier {
void perform(Object element);
}
@@ -0,0 +1,7 @@
================ Step #1 =================
Compiling files:
src/IntrospectorImpl.kt
End of files
Exit code: OK
------------------------------------------