Introduce new string table optimized for JVM class files
This format of the string table allows to reduce the size of the Kotlin metadata in JVM class files by reusing constants already present in the constant pool. Currently the string table produced by JvmStringTable is not fully optimized in serialization (in particular, the 'substring' operation which will be used to extract type names out of generic signature, is not used at all), but the format and its complete support in the deserialization (JvmNameResolver) allows future improvement without changing the binary version
This commit is contained in:
@@ -46,6 +46,8 @@ import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPackage;
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializer;
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable;
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding;
|
||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
@@ -842,7 +844,7 @@ public class AsmUtil {
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
public static void writeAnnotationData(@NotNull AnnotationVisitor av, @NotNull byte[] bytes) {
|
||||
public static void writeAnnotationData(@NotNull AnnotationVisitor av, @NotNull DescriptorSerializer serializer, @NotNull byte[] bytes) {
|
||||
JvmCodegenUtil.writeAbiVersion(av);
|
||||
AnnotationVisitor data = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
|
||||
for (String string : BitEncoding.encodeBytes(bytes)) {
|
||||
@@ -850,7 +852,9 @@ public class AsmUtil {
|
||||
}
|
||||
data.visitEnd();
|
||||
AnnotationVisitor strings = av.visitArray(JvmAnnotationNames.STRINGS_FIELD_NAME);
|
||||
// TODO: write the actual string table
|
||||
for (String string : ((JvmStringTable) serializer.getStringTable()).getStrings()) {
|
||||
strings.visit(null, string);
|
||||
}
|
||||
strings.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ public class ClosureCodegen extends MemberCodegen<JetElement> {
|
||||
ProtoBuf.Callable callableProto = serializer.callableProto(funDescriptor).build();
|
||||
|
||||
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CALLABLE), true);
|
||||
writeAnnotationData(av, serializer.serialize(callableProto));
|
||||
writeAnnotationData(av, serializer, serializer.serialize(callableProto));
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
ProtoBuf.Class classProto = serializer.classProto(descriptor).build();
|
||||
|
||||
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS), true);
|
||||
writeAnnotationData(av, serializer.serialize(classProto));
|
||||
writeAnnotationData(av, serializer, serializer.serialize(classProto));
|
||||
if (kind != null) {
|
||||
av.visitEnum(
|
||||
JvmAnnotationNames.KIND_FIELD_NAME,
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable;
|
||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
||||
@@ -39,7 +40,7 @@ import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*;
|
||||
public class JvmSerializerExtension extends SerializerExtension {
|
||||
private final JvmSerializationBindings bindings;
|
||||
private final JetTypeMapper typeMapper;
|
||||
private final StringTableImpl stringTable = new StringTableImpl(this);
|
||||
private final StringTable stringTable = new JvmStringTable(this);
|
||||
private final AnnotationSerializer annotationSerializer = new AnnotationSerializer(stringTable);
|
||||
|
||||
public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MultifileClassPartCodegen(
|
||||
if (packageProto.memberCount == 0) return
|
||||
|
||||
val av = v.newAnnotation(AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS_PART), true)
|
||||
AsmUtil.writeAnnotationData(av, serializer.serialize(packageProto))
|
||||
AsmUtil.writeAnnotationData(av, serializer, serializer.serialize(packageProto))
|
||||
av.visit(JvmAnnotationNames.MULTIFILE_CLASS_NAME_FIELD_NAME, multifileClassFqName.shortName().asString())
|
||||
av.visitEnd()
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public class PackageCodegen {
|
||||
ProtoBuf.Package packageProto = serializer.packageProtoWithoutDescriptors().build();
|
||||
|
||||
AnnotationVisitor av = v.newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_PACKAGE), true);
|
||||
AsmUtil.writeAnnotationData(av, serializer.serialize(packageProto));
|
||||
AsmUtil.writeAnnotationData(av, serializer, serializer.serialize(packageProto));
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ public class PackagePartCodegen extends MemberCodegen<JetFile> {
|
||||
if (packageProto.getMemberCount() == 0) return;
|
||||
|
||||
AnnotationVisitor av = v.newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_FILE_FACADE), true);
|
||||
writeAnnotationData(av, serializer.serialize(packageProto));
|
||||
writeAnnotationData(av, serializer, serializer.serialize(packageProto));
|
||||
av.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.serialization
|
||||
|
||||
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.load.kotlin.JvmNameResolver
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.serialization.SerializerExtension
|
||||
import org.jetbrains.kotlin.serialization.StringTable
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record
|
||||
import org.jetbrains.kotlin.types.ErrorUtils
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
class JvmStringTable(private val extension: SerializerExtension) : StringTable {
|
||||
public val strings = ArrayList<String>()
|
||||
private val records = ArrayList<Record>()
|
||||
private val map = HashMap<String, Int>()
|
||||
private val localNames = HashSet<Int>()
|
||||
|
||||
override fun getStringIndex(string: String): Int =
|
||||
map.getOrPut(string) {
|
||||
strings.size().apply {
|
||||
strings.add(string)
|
||||
records.add(Record.newBuilder().apply {
|
||||
// TODO: optimize, don't always store the operation
|
||||
setOperation(Record.Operation.NONE)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFqNameIndex(descriptor: ClassDescriptor): Int {
|
||||
if (ErrorUtils.isError(descriptor)) {
|
||||
throw IllegalStateException("Cannot get FQ name of error class: " + descriptor)
|
||||
}
|
||||
|
||||
val string: String
|
||||
val storeAsLiteral: Boolean
|
||||
|
||||
val parent = sequence(descriptor, DeclarationDescriptor::getContainingDeclaration).first { it !is ClassDescriptor }
|
||||
if (parent is PackageFragmentDescriptor) {
|
||||
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 ->
|
||||
if (isLocal == (recordedIndex in localNames)) {
|
||||
return recordedIndex
|
||||
}
|
||||
}
|
||||
|
||||
val index = strings.size()
|
||||
if (isLocal) {
|
||||
localNames.add(index)
|
||||
}
|
||||
|
||||
val record = Record.newBuilder()
|
||||
|
||||
if (storeAsLiteral) {
|
||||
strings.add(string)
|
||||
// TODO: optimize, don't always store the operation
|
||||
record.setOperation(Record.Operation.NONE)
|
||||
}
|
||||
else {
|
||||
val predefinedIndex = JvmNameResolver.getPredefinedStringIndex(string)
|
||||
if (predefinedIndex != null) {
|
||||
record.setPredefinedIndex(predefinedIndex)
|
||||
record.setOperation(Record.Operation.NONE)
|
||||
// TODO: move all records with predefined names to the end and do not write associated strings for them (since they are ignored)
|
||||
strings.add("")
|
||||
}
|
||||
else {
|
||||
record.setOperation(Record.Operation.DESC_TO_CLASS_ID)
|
||||
strings.add("L$string;")
|
||||
}
|
||||
}
|
||||
|
||||
records.add(record.build())
|
||||
|
||||
map[string] = index
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.localClassName(): String {
|
||||
val container = containingDeclaration
|
||||
return when (container) {
|
||||
is ClassDescriptor -> container.localClassName() + "." + name.asString()
|
||||
else -> extension.getLocalClassName(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeTo(output: OutputStream) {
|
||||
with(JvmProtoBuf.StringTableTypes.newBuilder()) {
|
||||
addAllRecord(records)
|
||||
addAllLocalName(localNames)
|
||||
build().writeDelimitedTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,6 +12,6 @@ interface A {
|
||||
}
|
||||
}
|
||||
|
||||
// 1 foo
|
||||
// 1 foo\(
|
||||
// 1 getProp
|
||||
// 1 defaultFun\$
|
||||
// 1 defaultFun\$
|
||||
|
||||
@@ -13042,7 +13042,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
boolean hasFlags();
|
||||
@@ -13059,7 +13061,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
int getFlags();
|
||||
@@ -14599,7 +14603,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasFlags() {
|
||||
@@ -14618,7 +14624,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public int getFlags() {
|
||||
@@ -15352,7 +15360,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasFlags() {
|
||||
@@ -15371,7 +15381,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public int getFlags() {
|
||||
@@ -15390,7 +15402,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public Builder setFlags(int value) {
|
||||
@@ -15412,7 +15426,9 @@ public final class DebugProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public Builder clearFlags() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.serialization.jvm
|
||||
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.DESC_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.INTERNAL_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.NONE
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class JvmNameResolverTest : UsefulTestCase() {
|
||||
private class Context {
|
||||
val types = JvmProtoBuf.StringTableTypes.newBuilder()
|
||||
val strings = ArrayList<String>()
|
||||
|
||||
fun string(
|
||||
string: String?,
|
||||
range: Int? = null,
|
||||
predefinedIndex: Int? = null,
|
||||
operation: Record.Operation? = null,
|
||||
substringIndex: List<Int>? = null,
|
||||
replaceChar: List<Char>? = null
|
||||
) {
|
||||
types.addRecord(Record.newBuilder().apply {
|
||||
range?.let { setRange(it) }
|
||||
predefinedIndex?.let { setPredefinedIndex(it) }
|
||||
operation?.let { setOperation(it) }
|
||||
substringIndex?.let { addAllSubstringIndex(it) }
|
||||
replaceChar?.let { addAllReplaceChar(it.map(Char::toInt)) }
|
||||
}.build())
|
||||
|
||||
string?.let { strings.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun create(init: Context.() -> Unit): JvmNameResolver {
|
||||
return Context().run {
|
||||
init()
|
||||
JvmNameResolver(types.build(), strings.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun str(string: String?, predefinedIndex: Int? = null, operation: Record.Operation? = null): String {
|
||||
return create { string(string, null, predefinedIndex, operation, null, null) }.getString(0)
|
||||
}
|
||||
|
||||
fun testSimpleString() {
|
||||
assertEquals("abc", str("abc"))
|
||||
}
|
||||
|
||||
fun testSimpleClassId() {
|
||||
assertEquals(
|
||||
ClassId.topLevel(FqName("foo.bar.Baz")),
|
||||
create { string("Lfoo/bar/Baz;", operation = DESC_TO_CLASS_ID) }.getClassId(0)
|
||||
)
|
||||
}
|
||||
|
||||
fun testBasicOperations() {
|
||||
assertEquals("java/util/Map.Entry", str("Ljava/util/Map\$Entry;", operation = DESC_TO_CLASS_ID))
|
||||
assertEquals("java/util/Map.Entry", str("java/util/Map\$Entry", operation = INTERNAL_TO_CLASS_ID))
|
||||
}
|
||||
|
||||
fun testPredefined() {
|
||||
for ((index, predefined) in JvmNameResolver.PREDEFINED_STRINGS.withIndex()) {
|
||||
assertEquals(predefined, str("ignored", predefinedIndex = index), "Predefined string failed: $predefined (index $index)")
|
||||
}
|
||||
}
|
||||
|
||||
fun testNotExistingPredefinedString() {
|
||||
assertEquals("not-ignored", str("not-ignored", predefinedIndex = 123456789))
|
||||
}
|
||||
|
||||
fun testOperationOnBadString() {
|
||||
assertEquals("X", str("X", operation = DESC_TO_CLASS_ID))
|
||||
assertEquals("", str("", operation = DESC_TO_CLASS_ID))
|
||||
}
|
||||
|
||||
fun testSubstring() {
|
||||
val n = create {
|
||||
string("kotlin", substringIndex = listOf(0, 6))
|
||||
string("kotlin", substringIndex = listOf(1, 4))
|
||||
string("kotlin", substringIndex = listOf(6, 6))
|
||||
|
||||
// Invalid operations
|
||||
string("kotlin", substringIndex = listOf(7, 5))
|
||||
string("kotlin", substringIndex = listOf(0, -2))
|
||||
string("kotlin", substringIndex = listOf(3, 1))
|
||||
}
|
||||
|
||||
assertEquals("kotlin", n.getString(0))
|
||||
assertEquals("otl", n.getString(1))
|
||||
assertEquals("", n.getString(2))
|
||||
|
||||
// All invalid operations should be ignored
|
||||
(3..5).forEach { assertEquals("kotlin", n.getString(it)) }
|
||||
}
|
||||
|
||||
fun testSubstringHappensAfterOperation() {
|
||||
assertEquals("tl", create {
|
||||
string("kotlin", substringIndex = listOf(1, 5), operation = DESC_TO_CLASS_ID)
|
||||
}.getString(0))
|
||||
}
|
||||
|
||||
fun testReplaceAll() {
|
||||
val n = create {
|
||||
string("kotlin", replaceChar = listOf('k', 'm'))
|
||||
string("java", replaceChar = listOf('a', 'o', 'a', 'b', 'c', 'd')) // All chars after the first two are ignored
|
||||
|
||||
// Invalid operations
|
||||
string("kotlin", replaceChar = listOf())
|
||||
string("kotlin", replaceChar = listOf('k'))
|
||||
}
|
||||
|
||||
assertEquals("motlin", n.getString(0))
|
||||
assertEquals("jovo", n.getString(1))
|
||||
|
||||
// All invalid operations should be ignored
|
||||
(2..3).forEach { assertEquals("kotlin", n.getString(it)) }
|
||||
}
|
||||
|
||||
fun testRange() {
|
||||
val n = create {
|
||||
string("a\$b\$c", operation = INTERNAL_TO_CLASS_ID, range = 2)
|
||||
string("d\$e\$f", operation = NONE, range = 2)
|
||||
string("abc")
|
||||
string("def")
|
||||
}
|
||||
|
||||
assertEquals("d.e.f", n.getString(1))
|
||||
assertEquals("def", n.getString(3))
|
||||
}
|
||||
|
||||
fun testRangeWithDifferentOperations() {
|
||||
val n = create {
|
||||
string("a\$b\$c", operation = INTERNAL_TO_CLASS_ID, range = 2)
|
||||
string("d\$e\$f", operation = NONE, substringIndex = listOf(2, 5))
|
||||
}
|
||||
|
||||
assertEquals("a.b.c", n.getString(0))
|
||||
assertEquals("d.e.f", n.getString(1))
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,45 @@ import "core/deserialization/src/descriptors.proto";
|
||||
option java_outer_classname = "JvmProtoBuf";
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message StringTableTypes {
|
||||
message Record {
|
||||
// The number of times this record should be repeated; this is used to collapse identical subsequent records in the list
|
||||
optional int32 range = 1 [default = 1];
|
||||
|
||||
// Index of the predefined constant. If this field is present, the associated string is ignored
|
||||
optional int32 predefined_index = 2;
|
||||
|
||||
enum Operation {
|
||||
NONE = 0;
|
||||
|
||||
// replaceAll('$', '.')
|
||||
// java/util/Map$Entry -> java/util/Map.Entry;
|
||||
INTERNAL_TO_CLASS_ID = 1;
|
||||
|
||||
// substring(1, length - 1) and then replaceAll('$', '.')
|
||||
// Ljava/util/Map$Entry; -> java/util/Map.Entry
|
||||
DESC_TO_CLASS_ID = 2;
|
||||
}
|
||||
|
||||
// Perform a described operation on the string
|
||||
optional Operation operation = 3 [default = NONE];
|
||||
|
||||
// If this field is present, the "substring" operation must be performed with the first element of this list as the start index,
|
||||
// and the second element as the end index.
|
||||
// If an operation is not NONE, it's applied _after_ this substring operation
|
||||
repeated int32 substring_index = 4 [packed = true];
|
||||
|
||||
// If this field is present, the "replaceAll" operation must be performed with the first element of this list as the code point
|
||||
// of the character to replace, and the second element as the code point of the replacement character
|
||||
repeated int32 replace_char = 5 [packed = true];
|
||||
}
|
||||
|
||||
repeated Record record = 1;
|
||||
|
||||
// Indices of strings which are names of local classes or anonymous objects
|
||||
repeated int32 local_name = 5 [packed = true];
|
||||
}
|
||||
|
||||
message JvmMethodSignature {
|
||||
required int32 name = 1 [(string_id_in_table) = true];
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.load.kotlin
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
|
||||
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.Operation.DESC_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.INTERNAL_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.NONE
|
||||
import java.util.*
|
||||
|
||||
class JvmNameResolver(
|
||||
private val types: JvmProtoBuf.StringTableTypes,
|
||||
private val strings: Array<String>
|
||||
) : NameResolver {
|
||||
private val localNameIndices = types.localNameList.run { if (isEmpty()) emptySet() else toSet() }
|
||||
|
||||
// Here we expand the 'range' field of the Record message for simplicity to a list of records
|
||||
private val records: List<Record> = ArrayList<Record>().apply {
|
||||
val records = types.recordList
|
||||
this.ensureCapacity(records.size())
|
||||
for (record in records) {
|
||||
repeat(record.range) {
|
||||
this.add(record)
|
||||
}
|
||||
}
|
||||
this.trimToSize()
|
||||
}
|
||||
|
||||
override fun getString(index: Int): String {
|
||||
val record = records[index]
|
||||
|
||||
var string =
|
||||
if (record.hasPredefinedIndex() && record.predefinedIndex in PREDEFINED_STRINGS.indices)
|
||||
PREDEFINED_STRINGS[record.predefinedIndex]
|
||||
else strings[index]
|
||||
|
||||
if (record.substringIndexCount >= 2) {
|
||||
val (begin, end) = record.substringIndexList
|
||||
if (0 <= begin && begin <= end && end <= string.length()) {
|
||||
string = string.substring(begin, end)
|
||||
}
|
||||
}
|
||||
|
||||
if (record.replaceCharCount >= 2) {
|
||||
val (from, to) = record.replaceCharList
|
||||
string = string.replace(from.toChar(), to.toChar())
|
||||
}
|
||||
|
||||
when (record.operation ?: NONE) {
|
||||
NONE -> {
|
||||
// Do nothing
|
||||
}
|
||||
INTERNAL_TO_CLASS_ID -> {
|
||||
string = string.replace('$', '.')
|
||||
}
|
||||
DESC_TO_CLASS_ID -> {
|
||||
if (string.length() >= 2) {
|
||||
string = string.substring(1, string.length() - 1)
|
||||
}
|
||||
string = string.replace('$', '.')
|
||||
}
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
override fun getName(index: Int) = Name.guess(getString(index))
|
||||
|
||||
override fun getClassId(index: Int): ClassId {
|
||||
val string = getString(index)
|
||||
val lastSlash = string.lastIndexOf('/')
|
||||
val packageName =
|
||||
if (lastSlash < 0) FqName.ROOT
|
||||
else FqName(string.substring(0, lastSlash).replace('/', '.'))
|
||||
val className = FqName(string.substring(lastSlash + 1))
|
||||
return ClassId(packageName, className, index in localNameIndices)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PREDEFINED_STRINGS = listOf(
|
||||
"kotlin/Any",
|
||||
"kotlin/Nothing",
|
||||
"kotlin/Unit",
|
||||
"kotlin/Throwable",
|
||||
"kotlin/Number",
|
||||
|
||||
"kotlin/Byte", "kotlin/Double", "kotlin/Float", "kotlin/Int",
|
||||
"kotlin/Long", "kotlin/Short", "kotlin/Boolean", "kotlin/Char",
|
||||
|
||||
"kotlin/CharSequence",
|
||||
"kotlin/String",
|
||||
"kotlin/Comparable",
|
||||
"kotlin/Enum",
|
||||
|
||||
"kotlin/Array",
|
||||
"kotlin/ByteArray", "kotlin/DoubleArray", "kotlin/FloatArray", "kotlin/IntArray",
|
||||
"kotlin/LongArray", "kotlin/ShortArray", "kotlin/BooleanArray", "kotlin/CharArray",
|
||||
|
||||
"kotlin/Cloneable",
|
||||
"kotlin/Annotation",
|
||||
|
||||
"kotlin/Iterable", "kotlin/MutableIterable",
|
||||
"kotlin/Collection", "kotlin/MutableCollection",
|
||||
"kotlin/List", "kotlin/MutableList",
|
||||
"kotlin/Set", "kotlin/MutableSet",
|
||||
"kotlin/Map", "kotlin/MutableMap",
|
||||
"kotlin/Map.Entry", "kotlin/MutableMap.MutableEntry",
|
||||
|
||||
"kotlin/Iterator", "kotlin/MutableIterator",
|
||||
"kotlin/ListIterator", "kotlin/MutableListIterator"
|
||||
)
|
||||
|
||||
private val PREDEFINED_STRINGS_MAP = PREDEFINED_STRINGS.withIndex().toMap({ it.value }, { it.index })
|
||||
|
||||
fun getPredefinedStringIndex(string: String): Int? = PREDEFINED_STRINGS_MAP[string]
|
||||
}
|
||||
}
|
||||
+1904
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -17,10 +17,10 @@
|
||||
package org.jetbrains.kotlin.serialization.jvm
|
||||
|
||||
import com.google.protobuf.ExtensionRegistryLite
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
|
||||
import org.jetbrains.kotlin.serialization.ClassData
|
||||
import org.jetbrains.kotlin.serialization.PackageData
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolverImpl
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
public object JvmProtoBufUtil {
|
||||
@@ -37,7 +37,7 @@ public object JvmProtoBufUtil {
|
||||
@JvmStatic
|
||||
public fun readClassDataFrom(bytes: ByteArray, strings: Array<String>): ClassData {
|
||||
val input = ByteArrayInputStream(bytes)
|
||||
val nameResolver = NameResolverImpl.read(input)
|
||||
val nameResolver = JvmNameResolver(JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY), strings)
|
||||
val classProto = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY)
|
||||
return ClassData(nameResolver, classProto)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public object JvmProtoBufUtil {
|
||||
@JvmStatic
|
||||
public fun readPackageDataFrom(bytes: ByteArray, strings: Array<String>): PackageData {
|
||||
val input = ByteArrayInputStream(bytes)
|
||||
val nameResolver = NameResolverImpl.read(input)
|
||||
val nameResolver = JvmNameResolver(JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY), strings)
|
||||
val packageProto = ProtoBuf.Package.parseFrom(input, EXTENSION_REGISTRY)
|
||||
return PackageData(nameResolver, packageProto)
|
||||
}
|
||||
|
||||
@@ -10043,7 +10043,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
boolean hasFlags();
|
||||
@@ -10060,7 +10062,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
int getFlags();
|
||||
@@ -11277,7 +11281,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasFlags() {
|
||||
@@ -11296,7 +11302,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public int getFlags() {
|
||||
@@ -11907,7 +11915,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasFlags() {
|
||||
@@ -11926,7 +11936,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public int getFlags() {
|
||||
@@ -11945,7 +11957,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public Builder setFlags(int value) {
|
||||
@@ -11967,7 +11981,9 @@ public final class ProtoBuf {
|
||||
*hasGetter
|
||||
*hasSetter
|
||||
*hasConstant
|
||||
*isConst
|
||||
*lateinit
|
||||
*isOperator
|
||||
* </pre>
|
||||
*/
|
||||
public Builder clearFlags() {
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
package kotlin.reflect.jvm
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
|
||||
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolverImpl
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
|
||||
import kotlin.jvm.internal.KotlinCallable
|
||||
import kotlin.reflect.KFunction
|
||||
@@ -37,7 +38,10 @@ import kotlin.reflect.jvm.internal.getOrCreateModule
|
||||
public fun <R> Function<R>.reflect(): KFunction<R>? {
|
||||
val callable = javaClass.getAnnotation(KotlinCallable::class.java) ?: return null
|
||||
val input = BitEncoding.decodeBytes(callable.data).inputStream()
|
||||
val nameResolver = NameResolverImpl.read(input)
|
||||
val nameResolver = JvmNameResolver(
|
||||
JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, JvmProtoBufUtil.EXTENSION_REGISTRY),
|
||||
callable.strings
|
||||
)
|
||||
val proto = ProtoBuf.Callable.parseFrom(input, JvmProtoBufUtil.EXTENSION_REGISTRY)
|
||||
val moduleData = javaClass.getOrCreateModule()
|
||||
val context = DeserializationContext(moduleData.deserialization, nameResolver, moduleData.module,
|
||||
|
||||
+17
-18
@@ -16,28 +16,28 @@
|
||||
|
||||
package org.jetbrains.kotlin.jps.build.classFilesComparison
|
||||
|
||||
import java.io.File
|
||||
import org.junit.Assert.*
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
|
||||
import java.io.PrintWriter
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import java.io.StringWriter
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import com.google.common.io.Files
|
||||
import com.google.common.hash.Hashing
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.google.common.collect.Sets
|
||||
import java.util.HashSet
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
import org.jetbrains.kotlin.serialization.jvm.DebugJvmProtoBuf
|
||||
import com.google.common.hash.Hashing
|
||||
import com.google.common.io.Files
|
||||
import com.google.protobuf.ExtensionRegistry
|
||||
import java.io.ByteArrayInputStream
|
||||
import org.jetbrains.kotlin.serialization.DebugProtoBuf
|
||||
import java.util.Arrays
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.jps.incremental.LocalFileKotlinClass
|
||||
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
|
||||
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
|
||||
import org.jetbrains.kotlin.load.kotlin.header.isCompatiblePackageFacadeKind
|
||||
import org.jetbrains.kotlin.serialization.DebugProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
import org.jetbrains.kotlin.serialization.jvm.DebugJvmProtoBuf
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.*
|
||||
|
||||
// Set this to true if you want to dump all bytecode (test will fail in this case)
|
||||
val DUMP_ALL = System.getProperty("comparison.dump.all") == "true"
|
||||
@@ -139,8 +139,7 @@ fun classFileToString(classFile: File): String {
|
||||
ByteArrayInputStream(BitEncoding.decodeBytes(annotationDataEncoded)).use {
|
||||
input ->
|
||||
|
||||
out.write("\n------ simpleNames proto -----\n${DebugProtoBuf.StringTable.parseDelimitedFrom(input)}")
|
||||
out.write("\n------ qualifiedNames proto -----\n${DebugProtoBuf.QualifiedNameTable.parseDelimitedFrom(input)}")
|
||||
out.write("\n------ string table types proto -----\n${DebugJvmProtoBuf.StringTableTypes.parseDelimitedFrom(input)}")
|
||||
|
||||
when {
|
||||
classHeader!!.isCompatiblePackageFacadeKind() ->
|
||||
|
||||
Reference in New Issue
Block a user