Simplify local class name serialization, don't go through extension

Also fix it for the case of a class in a non-default package
This commit is contained in:
Alexander Udalov
2015-09-25 18:13:19 +03:00
parent 1036506b25
commit 3b9d90429b
10 changed files with 66 additions and 78 deletions
@@ -25,7 +25,10 @@ import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.load.java.lazy.types.RawTypeCapabilities; import org.jetbrains.kotlin.load.java.lazy.types.RawTypeCapabilities;
import org.jetbrains.kotlin.serialization.*; import org.jetbrains.kotlin.serialization.AnnotationSerializer;
import org.jetbrains.kotlin.serialization.ProtoBuf;
import org.jetbrains.kotlin.serialization.SerializerExtension;
import org.jetbrains.kotlin.serialization.StringTable;
import org.jetbrains.kotlin.serialization.deserialization.NameResolver; import org.jetbrains.kotlin.serialization.deserialization.NameResolver;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor;
@@ -34,18 +37,17 @@ import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.org.objectweb.asm.Type; import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.Method; import org.jetbrains.org.objectweb.asm.commons.Method;
import static org.jetbrains.kotlin.codegen.AsmUtil.shortNameByAsmType;
import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*; import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*;
public class JvmSerializerExtension extends SerializerExtension { public class JvmSerializerExtension extends SerializerExtension {
private final JvmSerializationBindings bindings; private final JvmSerializationBindings bindings;
private final JetTypeMapper typeMapper; private final StringTable stringTable;
private final StringTable stringTable = new JvmStringTable(this); private final AnnotationSerializer annotationSerializer;
private final AnnotationSerializer annotationSerializer = new AnnotationSerializer(stringTable);
public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) { public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) {
this.bindings = bindings; this.bindings = bindings;
this.typeMapper = typeMapper; this.stringTable = new JvmStringTable(typeMapper);
this.annotationSerializer = new AnnotationSerializer(stringTable);
} }
@NotNull @NotNull
@@ -91,12 +93,6 @@ public class JvmSerializerExtension extends SerializerExtension {
} }
} }
@Override
@NotNull
public String getLocalClassName(@NotNull ClassDescriptor descriptor) {
return shortNameByAsmType(typeMapper.mapClass(descriptor));
}
private void saveSignature(@NotNull CallableMemberDescriptor callable, @NotNull ProtoBuf.Callable.Builder proto) { private void saveSignature(@NotNull CallableMemberDescriptor callable, @NotNull ProtoBuf.Callable.Builder proto) {
SignatureSerializer signatureSerializer = new SignatureSerializer(); SignatureSerializer signatureSerializer = new SignatureSerializer();
if (callable instanceof FunctionDescriptor) { if (callable instanceof FunctionDescriptor) {
@@ -16,13 +16,12 @@
package org.jetbrains.kotlin.codegen.serialization package org.jetbrains.kotlin.codegen.serialization
import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.serialization.SerializerExtension import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.StringTable import org.jetbrains.kotlin.serialization.StringTable
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record
@@ -30,7 +29,7 @@ import org.jetbrains.kotlin.types.ErrorUtils
import java.io.OutputStream import java.io.OutputStream
import java.util.* import java.util.*
class JvmStringTable(private val extension: SerializerExtension) : StringTable { class JvmStringTable(private val typeMapper: JetTypeMapper) : StringTable {
public val strings = ArrayList<String>() public val strings = ArrayList<String>()
private val records = ArrayList<Record>() private val records = ArrayList<Record>()
private val map = HashMap<String, Int>() private val map = HashMap<String, Int>()
@@ -52,43 +51,35 @@ class JvmStringTable(private val extension: SerializerExtension) : StringTable {
throw IllegalStateException("Cannot get FQ name of error class: " + descriptor) throw IllegalStateException("Cannot get FQ name of error class: " + descriptor)
} }
val string: String // We use the following format to encode ClassId: "pkg/Outer.Inner".
val storeAsLiteral: Boolean // It represents a unique name, but such names don't usually appear in the constant pool, so we're writing "Lpkg/Outer$Inner;"
// instead and an instruction to drop the first and the last character in this string and replace all '$' with '.'.
// This works most of the time, except in two rare cases:
// - the name of the class or any of its outer classes contains dollars. In this case we're just storing the described
// string literally: "pkg/Outer.Inner$with$dollars"
// - the class is local or nested in local. In this case we're also storing the literal string, and also storing the fact that
// this name represents a local class in a separate list
val parent = sequence(descriptor, DeclarationDescriptor::getContainingDeclaration).first { it !is ClassDescriptor } val classId = descriptor.classId
if (parent is PackageFragmentDescriptor) { val string = classId.asString()
val classId = descriptor.classId
val packageName = classId.packageFqName
val className = classId.relativeClassName.asString()
string =
if (packageName.isRoot) className
else packageName.asString().replace('.', '/') + "/" + className
// If any of the class names contains '$', we can't simply replace all '$' with '.' upon deserialization.
// This case is rather rare, so we're storing a literal string
storeAsLiteral = '$' in string && classId.relativeClassName.pathSegments().any { '$' in it.asString() }
}
else {
storeAsLiteral = true
string = descriptor.localClassName()
}
val isLocal = descriptor.containingDeclaration !is ClassOrPackageFragmentDescriptor
map[string]?.let { recordedIndex -> map[string]?.let { recordedIndex ->
if (isLocal == (recordedIndex in localNames)) { // If we already recorded such string, we only return its index if it's local and our name is local
// OR it's not local and our name is not local as well
if (classId.isLocal == (recordedIndex in localNames)) {
return recordedIndex return recordedIndex
} }
} }
val index = strings.size() val index = strings.size()
if (isLocal) { if (classId.isLocal) {
localNames.add(index) localNames.add(index)
} }
val record = Record.newBuilder() val record = Record.newBuilder()
if (storeAsLiteral) { // If the class is local or any of its outer class names contains '$', store a literal string
if (classId.isLocal || '$' in string) {
strings.add(string) strings.add(string)
// TODO: optimize, don't always store the operation // TODO: optimize, don't always store the operation
record.setOperation(Record.Operation.NONE) record.setOperation(Record.Operation.NONE)
@@ -103,7 +94,7 @@ class JvmStringTable(private val extension: SerializerExtension) : StringTable {
} }
else { else {
record.setOperation(Record.Operation.DESC_TO_CLASS_ID) record.setOperation(Record.Operation.DESC_TO_CLASS_ID)
strings.add("L$string;") strings.add("L${string.replace('.', '$')};")
} }
} }
@@ -114,13 +105,18 @@ class JvmStringTable(private val extension: SerializerExtension) : StringTable {
return index return index
} }
private fun ClassDescriptor.localClassName(): String { private val ClassDescriptor.classId: ClassId
val container = containingDeclaration get() {
return when (container) { val container = containingDeclaration
is ClassDescriptor -> container.localClassName() + "." + name.asString() return when (container) {
else -> extension.getLocalClassName(this) is ClassDescriptor -> container.classId.createNestedClassId(name)
is PackageFragmentDescriptor -> ClassId(container.fqName, name)
else -> {
val fqName = FqName(typeMapper.mapClass(this).internalName.replace('/', '.'))
ClassId(fqName.parent(), FqName.topLevel(fqName.shortName()), /* isLocal = */ true)
}
}
} }
}
override fun serializeTo(output: OutputStream) { override fun serializeTo(output: OutputStream) {
with(JvmProtoBuf.StringTableTypes.newBuilder()) { with(JvmProtoBuf.StringTableTypes.newBuilder()) {
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.serialization.* import org.jetbrains.kotlin.serialization.*
public class BuiltInsSerializerExtension : SerializerExtension() { public class BuiltInsSerializerExtension : SerializerExtension() {
private val stringTable = StringTableImpl(this) private val stringTable = StringTableImpl()
private val annotationSerializer = AnnotationSerializer(stringTable) private val annotationSerializer = AnnotationSerializer(stringTable)
override fun getStringTable(): StringTable = stringTable override fun getStringTable(): StringTable = stringTable
@@ -46,9 +46,4 @@ public abstract class SerializerExtension {
public void serializeType(@NotNull JetType type, @NotNull ProtoBuf.Type.Builder proto) { public void serializeType(@NotNull JetType type, @NotNull ProtoBuf.Type.Builder proto) {
} }
@NotNull
public String getLocalClassName(@NotNull ClassDescriptor descriptor) {
throw new UnsupportedOperationException("Local classes are unsupported: " + descriptor);
}
} }
@@ -61,11 +61,6 @@ public class StringTableImpl implements StringTable {
private final Interner<String> strings = new Interner<String>(); private final Interner<String> strings = new Interner<String>();
private final Interner<FqNameProto> qualifiedNames = new Interner<FqNameProto>(); private final Interner<FqNameProto> qualifiedNames = new Interner<FqNameProto>();
private final SerializerExtension extension;
public StringTableImpl(@NotNull SerializerExtension extension) {
this.extension = extension;
}
public int getSimpleNameIndex(@NotNull Name name) { public int getSimpleNameIndex(@NotNull Name name) {
return getStringIndex(name.asString()); return getStringIndex(name.asString());
@@ -86,25 +81,21 @@ public class StringTableImpl implements StringTable {
builder.setKind(QualifiedName.Kind.CLASS); builder.setKind(QualifiedName.Kind.CLASS);
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
String shortName;
if (containingDeclaration instanceof PackageFragmentDescriptor) { if (containingDeclaration instanceof PackageFragmentDescriptor) {
shortName = descriptor.getName().asString();
FqName packageFqName = ((PackageFragmentDescriptor) containingDeclaration).getFqName(); FqName packageFqName = ((PackageFragmentDescriptor) containingDeclaration).getFqName();
if (!packageFqName.isRoot()) { if (!packageFqName.isRoot()) {
builder.setParentQualifiedName(getPackageFqNameIndex(packageFqName)); builder.setParentQualifiedName(getPackageFqNameIndex(packageFqName));
} }
} }
else if (containingDeclaration instanceof ClassDescriptor) { else if (containingDeclaration instanceof ClassDescriptor) {
shortName = descriptor.getName().asString();
ClassDescriptor outerClass = (ClassDescriptor) containingDeclaration; ClassDescriptor outerClass = (ClassDescriptor) containingDeclaration;
builder.setParentQualifiedName(getFqNameIndex(outerClass)); builder.setParentQualifiedName(getFqNameIndex(outerClass));
} }
else { else {
builder.setKind(QualifiedName.Kind.LOCAL); throw new IllegalStateException("Cannot get FQ name of local class: " + descriptor);
shortName = extension.getLocalClassName(descriptor);
} }
builder.setShortName(getStringIndex(shortName)); builder.setShortName(getStringIndex(descriptor.getName().asString()));
return qualifiedNames.intern(new FqNameProto(builder)); return qualifiedNames.intern(new FqNameProto(builder));
} }
@@ -1,3 +1,5 @@
package test
// CLASS_NAME_SUFFIX: Deepest // CLASS_NAME_SUFFIX: Deepest
fun main() { fun main() {
+4 -4
View File
@@ -1,7 +1,7 @@
local final inner class Deepest local final inner class Deepest
public constructor Deepest() public constructor Deepest()
public final fun deep(): `DeepInnerChainKt$main$Local$Inner$prop$1$foo$1$DeepLocal` public final fun deep(): test.`DeepInnerChainKt$main$Local$Inner$prop$1$foo$1$DeepLocal`
public final fun deepest(): `DeepInnerChainKt$main$Local$Inner$prop$1$foo$1$DeepLocal`.Deepest? public final fun deepest(): test.`DeepInnerChainKt$main$Local$Inner$prop$1$foo$1$DeepLocal`.Deepest?
public final fun inner(): `DeepInnerChainKt$main$Local`.Inner public final fun inner(): test.`DeepInnerChainKt$main$Local`.Inner
public final fun local(): `DeepInnerChainKt$main$Local` public final fun local(): test.`DeepInnerChainKt$main$Local`
@@ -22,11 +22,11 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.getModuleName import org.jetbrains.kotlin.cli.jvm.config.getModuleName
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.frontend.java.di.createContainerForTopDownAnalyzerForJvm import org.jetbrains.kotlin.frontend.java.di.createContainerForTopDownAnalyzerForJvm
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.load.java.structure.reflect.classId import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
@@ -38,20 +38,20 @@ import java.net.URLClassLoader
public abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() { public abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
protected fun doTest(filename: String) { protected fun doTest(filename: String) {
val source = File(filename) val source = File(filename)
LoadDescriptorUtil.compileKotlinToDirAndGetAnalysisResult(listOf(source), tmpdir, getTestRootDisposable(), ConfigurationKind.ALL) LoadDescriptorUtil.compileKotlinToDirAndGetAnalysisResult(listOf(source), tmpdir, testRootDisposable, ConfigurationKind.ALL)
val classNameSuffix = InTextDirectivesUtils.findStringWithPrefixes(source.readText(), "// CLASS_NAME_SUFFIX: ") val classNameSuffix = InTextDirectivesUtils.findStringWithPrefixes(source.readText(), "// CLASS_NAME_SUFFIX: ")
?: error("CLASS_NAME_SUFFIX directive not found in test data") ?: error("CLASS_NAME_SUFFIX directive not found in test data")
val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader()) val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader())
val classFile = tmpdir.listFiles().singleOrNull { it.getPath().endsWith("$classNameSuffix.class") } val classFile = tmpdir.walkTopDown().singleOrNull { it.path.endsWith("$classNameSuffix.class") }
?: error("Local class with suffix `$classNameSuffix` is not found in: ${tmpdir.listFiles().toList()}") ?: error("Local class with suffix `$classNameSuffix` is not found in: ${tmpdir.listFiles().toList()}")
val clazz = classLoader.loadClass(classFile.name.substringBeforeLast(".class")) val clazz = classLoader.loadClass(classFile.relativeTo(tmpdir).substringBeforeLast(".class").replace('/', '.').replace('\\', '.'))
assertHasAnnotationData(clazz) assertHasAnnotationData(clazz)
val environment = KotlinCoreEnvironment.createForTests( val environment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(), testRootDisposable,
JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, tmpdir), JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, tmpdir),
EnvironmentConfigFiles.JVM_CONFIG_FILES EnvironmentConfigFiles.JVM_CONFIG_FILES
) )
@@ -79,7 +79,7 @@ public abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
private fun assertHasAnnotationData(clazz: Class<*>) { private fun assertHasAnnotationData(clazz: Class<*>) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val annotation = clazz.getAnnotation( val annotation = clazz.getAnnotation(
clazz.getClassLoader().loadClass(JvmAnnotationNames.KOTLIN_CLASS.asString()) as Class<Annotation> clazz.classLoader.loadClass(JvmAnnotationNames.KOTLIN_CLASS.asString()) as Class<Annotation>
) )
assert(annotation != null) { "KotlinClass annotation is not found for class $clazz" } assert(annotation != null) { "KotlinClass annotation is not found for class $clazz" }
@@ -88,6 +88,15 @@ public final class ClassId {
return new FqName(packageFqName.asString() + "." + relativeClassName.asString()); return new FqName(packageFqName.asString() + "." + relativeClassName.asString());
} }
/**
* @return a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
*/
@NotNull
public String asString() {
if (packageFqName.isRoot()) return relativeClassName.asString();
return packageFqName.asString().replace('.', '/') + "/" + relativeClassName.asString();
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@@ -110,7 +119,6 @@ public final class ClassId {
@Override @Override
public String toString() { public String toString() {
if (packageFqName.isRoot()) return "/" + relativeClassName; return packageFqName.isRoot() ? "/" + asString() : asString();
return packageFqName.toString().replace('.', '/') + "/" + relativeClassName;
} }
} }
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.serialization.StringTableImpl
import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.JetType
public class KotlinJavascriptSerializerExtension : SerializerExtension() { public class KotlinJavascriptSerializerExtension : SerializerExtension() {
private val stringTable = StringTableImpl(this) private val stringTable = StringTableImpl()
private val annotationSerializer = AnnotationSerializer(stringTable) private val annotationSerializer = AnnotationSerializer(stringTable)
override fun getStringTable() = stringTable override fun getStringTable() = stringTable