From 1b2da6b558fd64d3b20e3ebe718223c523b20e7c Mon Sep 17 00:00:00 2001 From: Mikhael Bogdanov Date: Mon, 11 Feb 2013 17:16:46 +0400 Subject: [PATCH] Write proper enclosingMethod/enclosingClass information to bytecode --- .../codegen/ImplementationBodyCodegen.java | 57 +++++++-- .../outerClassInfo/outerClassInfo.java | 36 ++++++ .../codegen/outerClassInfo/outerClassInfo.kt | 34 ++++++ .../class/ClassObject.java | 21 ++++ .../class/ClassObject.kt | 25 ++++ .../jet/codegen/OuterClassGenTest.java | 115 ++++++++++++++++++ .../AbstractCompileJavaAgainstKotlinTest.java | 3 +- ...CompileJavaAgainstKotlinTestGenerated.java | 5 + 8 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 compiler/testData/codegen/outerClassInfo/outerClassInfo.java create mode 100644 compiler/testData/codegen/outerClassInfo/outerClassInfo.kt create mode 100644 compiler/testData/compileJavaAgainstKotlin/class/ClassObject.java create mode 100644 compiler/testData/compileJavaAgainstKotlin/class/ClassObject.kt create mode 100644 compiler/tests/org/jetbrains/jet/codegen/OuterClassGenTest.java diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index a847342af33..282bc9ad8c6 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -39,12 +39,14 @@ import org.jetbrains.jet.codegen.state.JetTypeMapperMode; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.OverridingUtil; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant; import org.jetbrains.jet.lang.resolve.java.*; import org.jetbrains.jet.lang.resolve.java.kt.DescriptorKindUtils; +import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; @@ -184,9 +186,50 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { } private void writeOuterClass() { - ClassDescriptor container = getContainingClassDescriptor(descriptor); + //JVMS7: A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. + DeclarationDescriptor parentDescriptor = descriptor.getContainingDeclaration(); + + boolean isObjectLiteral = descriptor.getName().isSpecial() && descriptor.getKind() == ClassKind.OBJECT; + + boolean isLocalOrAnonymousClass = isObjectLiteral || + !(parentDescriptor instanceof NamespaceDescriptor || parentDescriptor instanceof ClassDescriptor); + if (isLocalOrAnonymousClass) { + String outerClassName = getOuterClassName(descriptor, typeMapper, bindingContext, state); + FunctionDescriptor function = DescriptorUtils.getParentOfType(descriptor, FunctionDescriptor.class); + + //Function descriptor could be null only for object literal in package namespace + assert (!isObjectLiteral && function != null) || isObjectLiteral: + "Function descriptor should be present: " + descriptor.getName(); + + Name functionName = function != null ? function.getName() : null; + + v.visitOuterClass(outerClassName, + functionName != null ? functionName.getName() : null, + functionName != null ? typeMapper.mapSignature(functionName, function).getAsmMethod().getDescriptor() : null); + + } + } + + @NotNull + public static String getOuterClassName( + @NotNull ClassDescriptor classDescriptor, + @NotNull JetTypeMapper typeMapper, + @NotNull BindingContext bindingContext, + @NotNull GenerationState state + ) { + ClassDescriptor container = DescriptorUtils.getParentOfType(classDescriptor, ClassDescriptor.class); if (container != null) { - v.visitOuterClass(typeMapper.mapType(container.getDefaultType(), JetTypeMapperMode.IMPL).getInternalName(), null, null); + return typeMapper.mapType(container.getDefaultType(), JetTypeMapperMode.IMPL).getInternalName(); + } + else { + NamespaceDescriptor namespaceDescriptor = DescriptorUtils.getParentOfType(classDescriptor, NamespaceDescriptor.class); + assert namespaceDescriptor != null : "Namespace descriptor should be present: " + classDescriptor.getName(); + FqName namespaceQN = namespaceDescriptor.getQualifiedName(); + boolean isMultiFile = CodegenBinding.isMultiFileNamespace(state.getBindingContext(), namespaceQN); + return isMultiFile + ? NamespaceCodegen.getNamespacePartInternalName( + BindingContextUtils.getContainingFile(bindingContext, classDescriptor)) + : NamespaceCodegen.getJVMClassNameForKotlinNs(namespaceQN).getInternalName(); } } @@ -242,16 +285,6 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { annotationVisitor.visitEnd(); } - @Nullable - private static ClassDescriptor getContainingClassDescriptor(ClassDescriptor decl) { - DeclarationDescriptor container = decl.getContainingDeclaration(); - while (container != null && !(container instanceof NamespaceDescriptor)) { - if (container instanceof ClassDescriptor) return (ClassDescriptor) container; - container = container.getContainingDeclaration(); - } - return null; - } - private JvmClassSignature signature() { List superInterfaces; diff --git a/compiler/testData/codegen/outerClassInfo/outerClassInfo.java b/compiler/testData/codegen/outerClassInfo/outerClassInfo.java new file mode 100644 index 00000000000..70192613b5e --- /dev/null +++ b/compiler/testData/codegen/outerClassInfo/outerClassInfo.java @@ -0,0 +1,36 @@ +package foo; + +import java.lang.Object; + +class Foo { + + class object { } + + class InnerClass { } + + class InnerObject { } + + void foo(Foo f) { + class LocalClass {} + class LocalObject { } + } + + + Object objectLiteral = new Object() { + void objectLiteralFoo() { } + }; + +} + +class PackageInnerObject { } + +class FooPackage { + Object packageObjectLiteral = new Object() { + void objectLiteralFoo() { } + }; + + void packageMethod(Foo f) { + class PackageLocalClass {} + class PackageLocalObject {} + } +} diff --git a/compiler/testData/codegen/outerClassInfo/outerClassInfo.kt b/compiler/testData/codegen/outerClassInfo/outerClassInfo.kt new file mode 100644 index 00000000000..ffdd71855ec --- /dev/null +++ b/compiler/testData/codegen/outerClassInfo/outerClassInfo.kt @@ -0,0 +1,34 @@ +package foo; + +class Foo { + + class object { + fun objectFoo() { } + } + + class InnerClass { } + + object InnerObject { } + + fun foo(f : Foo) { + class LocalClass {} + class LocalObject {} + } + + val objectLiteral = object { + fun objectLiteralFoo() { } + } +} + +object PackageInnerObject { + fun PackageInnerObjectFoo() { } +} + +val packageObjectLiteral = object { + fun objectLiteralFoo() { } +} + +fun packageMethod(f : Foo) { + class PackageLocalClass {} + class PackageLocalObject {} +} \ No newline at end of file diff --git a/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.java b/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.java new file mode 100644 index 00000000000..f21a0a33215 --- /dev/null +++ b/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.java @@ -0,0 +1,21 @@ +class ClassObject { + void accessToClassObject() { + WithClassObject.object.$instance.foo(); + WithClassObject.object.$instance.getValue(); + WithClassObject.object.$instance.getValueWithGetter(); + WithClassObject.object.$instance.getVariable(); + WithClassObject.object.$instance.setVariable(0); + WithClassObject.object.$instance.getVariableWithAccessors(); + WithClassObject.object.$instance.setVariableWithAccessors(0); + } + + void accessToPackageObject() { + PackageInner.$instance.foo(); + PackageInner.$instance.getValue(); + } + + void accessToInnerClass() { + new WithClassObject.MyInner().foo(); + new WithClassObject.MyInner().getValue(); + } +} \ No newline at end of file diff --git a/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.kt b/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.kt new file mode 100644 index 00000000000..c86ec4a716d --- /dev/null +++ b/compiler/testData/compileJavaAgainstKotlin/class/ClassObject.kt @@ -0,0 +1,25 @@ +class WithClassObject { + class object { + fun foo() {} + + val value: Int = 0 + val valueWithGetter: Int + get() = 1 + + var variable: Int = 0 + var variableWithAccessors: Int + get() = 0 + set(v) {} + + } + + class MyInner { + fun foo() {} + val value: Int = 0 + } +} + +object PackageInner { + fun foo() {} + val value: Int = 0 +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/codegen/OuterClassGenTest.java b/compiler/tests/org/jetbrains/jet/codegen/OuterClassGenTest.java new file mode 100644 index 00000000000..75d227d450f --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/codegen/OuterClassGenTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010-2013 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.jet.codegen; + +import com.intellij.util.lang.UrlClassLoader; +import org.jetbrains.asm4.ClassReader; +import org.jetbrains.asm4.ClassVisitor; +import org.jetbrains.asm4.Opcodes; +import org.jetbrains.jet.ConfigurationKind; +import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.TestJdkKind; +import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import static org.jetbrains.jet.codegen.CodegenTestUtil.compileJava; + +public class OuterClassGenTest extends CodegenTestCase { + + private UrlClassLoader javaClassLoader; + + private ClassFileFactory classFileFactory; + + private final String [] INFO_PARTS = new String [] {"class", "method", "descriptor"}; + + protected void setUp() throws Exception { + super.setUp(); + File javaClassesTempDirectory = compileJava("outerClassInfo/outerClassInfo.java"); + myEnvironment = new JetCoreEnvironment(getTestRootDisposable(), JetTestUtils.compilerConfigurationForTests( + ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, JetTestUtils.getAnnotationsJar(), javaClassesTempDirectory)); + javaClassLoader = new UrlClassLoader(new URL[] {javaClassesTempDirectory.toURI().toURL()}, getClass().getClassLoader()); + loadFiles("outerClassInfo/outerClassInfo.kt"); + classFileFactory = generateClassesInFile(); + } + + public void testOuterClassInfo() throws ClassNotFoundException, IOException { + //CLASS SECTION + compareOuterClassInfo("foo.Foo"); + //class object + compareOuterClassInfo("foo.Foo$object"); + //inner class + compareOuterClassInfo("foo.Foo$InnerClass"); + //inner object + compareOuterClassInfo("foo.Foo$InnerObject"); + //local class in function + compareOuterClassInfo("foo.Foo$foo$LocalClass", "foo.Foo$1LocalClass"); + //local object in function + compareOuterClassInfo("foo.Foo$foo$LocalObject", "foo.Foo$1LocalObject"); + + //PACKAGE SECTION + //package object + compareOuterClassInfo("foo.PackageInnerObject"); + + //object literal in package + compareOuterClassInfo("foo$packageObjectLiteral$1", "foo.FooPackage$1"); + //local class in package function + compareOuterClassInfo("foo.FooPackage$packageMethod$PackageLocalClass", "foo.FooPackage$1PackageLocalClass"); + //local object in package function + compareOuterClassInfo("foo.FooPackage$packageMethod$PackageLocalObject", "foo.FooPackage$1PackageLocalObject"); + } + + private void compareOuterClassInfo(String clazzName) throws ClassNotFoundException, IOException { + compareOuterClassInfo(clazzName, clazzName); + } + + private void compareOuterClassInfo(String kotlinName, String javaName) throws ClassNotFoundException, IOException { + String javaClassPath = javaName.replaceAll("\\.", File.separator) + ".class"; + InputStream javaClassStream = javaClassLoader.getResourceAsStream(javaClassPath); + ClassReader javaReader = new ClassReader(javaClassStream); + + ClassReader kotlinReader = new ClassReader(classFileFactory.asBytes(kotlinName.replaceAll("\\.", File.separator) + ".class")); + + checkInfo(kotlinReader, javaReader); + } + + + private void checkInfo(ClassReader kotlinReader, ClassReader javaReader) { + String [] kotlinInfo = getOuterClasInfo(kotlinReader); + String [] javaInfo = getOuterClasInfo(javaReader); + for (int i = 0; i < kotlinInfo.length; i++) { + String info = kotlinInfo[i]; + assertEquals("Error in enclosingMethodInfo info for: " + kotlinReader.getClassName() + " class in " + INFO_PARTS[i] + " part", javaInfo[i], info); + } + } + + public String [] getOuterClasInfo(ClassReader reader) { + final String [] info = new String [3]; + reader.accept(new ClassVisitor(Opcodes.ASM4) { + @Override + public void visitOuterClass(String owner, String name, String desc) { + info[0] = owner; + info[1] = name; + info[2] = desc; + } + }, 0); + return info; + } +} diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractCompileJavaAgainstKotlinTest.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractCompileJavaAgainstKotlinTest.java index f160537c358..79b2d1f6c8a 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractCompileJavaAgainstKotlinTest.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractCompileJavaAgainstKotlinTest.java @@ -18,6 +18,7 @@ package org.jetbrains.jet.jvm.compiler; import org.jetbrains.jet.ConfigurationKind; import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.jet.test.TestCaseWithTmpdir; import org.junit.Assert; @@ -40,7 +41,7 @@ public abstract class AbstractCompileJavaAgainstKotlinTest extends TestCaseWithT compileKotlinToDirAndGetAnalyzeExhaust(ktFile, tmpdir, getTestRootDisposable(), ConfigurationKind.JDK_ONLY); List options = Arrays.asList( - "-classpath", tmpdir.getPath() + System.getProperty("path.separator") + "out/production/stdlib", + "-classpath", tmpdir.getPath() + System.getProperty("path.separator") + ForTestCompileRuntime.runtimeJarForTests(), "-d", tmpdir.getPath() ); JetTestUtils.compileJavaFiles(Collections.singleton(javaFile), options); diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileJavaAgainstKotlinTestGenerated.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileJavaAgainstKotlinTestGenerated.java index 5b9307d78ca..528680394b7 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileJavaAgainstKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileJavaAgainstKotlinTestGenerated.java @@ -43,6 +43,11 @@ public class CompileJavaAgainstKotlinTestGenerated extends AbstractCompileJavaAg JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/compileJavaAgainstKotlin/class"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("ClassObject.kt") + public void testClassObject() throws Exception { + doTest("compiler/testData/compileJavaAgainstKotlin/class/ClassObject.kt"); + } + @TestMetadata("DefaultConstructor.kt") public void testDefaultConstructor() throws Exception { doTest("compiler/testData/compileJavaAgainstKotlin/class/DefaultConstructor.kt");