diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java
index 0f2f919c1ec..7281a619e98 100644
--- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java
+++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java
@@ -43,6 +43,7 @@ import org.jetbrains.jet.codegen.state.JetTypeMapper;
import org.jetbrains.jet.codegen.state.JetTypeMapperMode;
import org.jetbrains.jet.descriptors.serialization.ClassData;
import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
+import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.descriptors.impl.MutableClassDescriptor;
@@ -227,7 +228,11 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
ClassData data = new ClassData(createNameResolver(serializer.getNameTable()), classProto);
AnnotationVisitor av = v.getVisitor().visitAnnotation(JvmStdlibNames.KOTLIN_INFO_CLASS.getDescriptor(), true);
- av.visit("data", data.toBytes());
+ AnnotationVisitor array = av.visitArray("data");
+ for (String string : JavaProtoBufUtil.encodeBytes(data.toBytes())) {
+ array.visit(null, string);
+ }
+ array.visitEnd();
av.visitEnd();
}
diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java
index da8cb5a43a7..3742e06a0b6 100644
--- a/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java
+++ b/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java
@@ -31,6 +31,7 @@ import org.jetbrains.jet.codegen.context.CodegenContext;
import org.jetbrains.jet.codegen.context.FieldOwnerContext;
import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
+import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
import org.jetbrains.jet.descriptors.serialization.PackageData;
import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
import org.jetbrains.jet.lang.descriptors.*;
@@ -161,7 +162,11 @@ public class NamespaceCodegen extends MemberCodegen {
PackageData data = new PackageData(createNameResolver(serializer.getNameTable()), packageProto.build());
AnnotationVisitor av = v.getClassBuilder().newAnnotation(JvmStdlibNames.KOTLIN_INFO_CLASS.getDescriptor(), true);
- av.visit("data", data.toBytes());
+ AnnotationVisitor array = av.visitArray("data");
+ for (String string : JavaProtoBufUtil.encodeBytes(data.toBytes())) {
+ array.visit(null, string);
+ }
+ array.visitEnd();
av.visitEnd();
}
diff --git a/compiler/frontend.java/serialization.java/src/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtil.java b/compiler/frontend.java/serialization.java/src/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtil.java
index 4b47d32129e..f4791650164 100644
--- a/compiler/frontend.java/serialization.java/src/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtil.java
+++ b/compiler/frontend.java/serialization.java/src/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtil.java
@@ -43,12 +43,71 @@ public class JavaProtoBufUtil {
}
@NotNull
- public static ClassData readClassDataFrom(@NotNull byte[] data) {
- return ClassData.read(data, getExtensionRegistry());
+ public static ClassData readClassDataFrom(@NotNull String[] data) {
+ return ClassData.read(decodeBytes(data), getExtensionRegistry());
}
@NotNull
- public static PackageData readPackageDataFrom(@NotNull byte[] data) {
- return PackageData.read(data, getExtensionRegistry());
+ public static PackageData readPackageDataFrom(@NotNull String[] data) {
+ return PackageData.read(decodeBytes(data), getExtensionRegistry());
+ }
+
+ /**
+ * Converts a byte array of serialized data to an array of {@code String} satisfying JVM annotation value argument restrictions:
+ *
+ * - Each string's length should be no more than 65535
+ * - UTF-8 representation of each string cannot contain byte 0x0 or bytes in the range 0xf0..0xff
+ *
+ */
+ @NotNull
+ public static String[] encodeBytes(@NotNull byte[] data) {
+ // Each byte of data is split into two 4-bit parts ('lo' and 'hi'), then lo + 1 and hi + 1 are appended to the string. Hence, each
+ // byte of the string is in the range 0x01..0x10 and this guarantees there's no byte 0x0 and no bytes in the range 0xf0..0xff
+ // TODO: use Scala's approach instead (break data into chunks of 7 bits)
+ int m = 32766;
+ assert 2 * m <= 65535 : m;
+
+ int n = data.length;
+ String[] result = new String[(n + m - 1) / m];
+ for (int offset = 0, resultIndex = 0; offset < n; offset += m, resultIndex++) {
+ int length = Math.min(n - offset, m);
+ byte[] a = new byte[length * 2];
+ for (int i = 0; i < length; i++) {
+ int lo = data[offset + i] & 0x0f;
+ int hi = (data[offset + i] & 0xf0) >>> 4;
+ a[2 * i] = (byte) (lo + 1);
+ a[2 * i + 1] = (byte) (hi + 1);
+ }
+ result[resultIndex] = new String(a);
+ }
+ return result;
+ }
+
+ /**
+ * Converts encoded array of {@code String} obtained by {@link JavaProtoBufUtil#encodeBytes(byte[])} back to a byte array.
+ */
+ @NotNull
+ public static byte[] decodeBytes(@NotNull String[] data) {
+ int length = 0;
+ for (String s : data) {
+ assert s.length() % 2 == 0 : s.length();
+ length += s.length() / 2;
+ }
+ byte[] result = new byte[length];
+
+ int p = 0;
+ for (String s : data) {
+ for (int i = 0, n = s.length(); i < n; i += 2) {
+ int lo = s.charAt(i) - 1;
+ int hi = s.charAt(i + 1) - 1;
+ assert 0 <= lo && lo < 0xf0 : lo;
+ assert 0 <= hi && hi < 0xf0 : hi;
+ result[p++] = (byte) (lo + (hi << 4));
+ }
+ }
+
+ assert p == length;
+
+ return result;
}
}
diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/DeserializedDescriptorResolver.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/DeserializedDescriptorResolver.java
index e723a284255..8e7b4115a34 100644
--- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/DeserializedDescriptorResolver.java
+++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/DeserializedDescriptorResolver.java
@@ -40,6 +40,8 @@ import org.jetbrains.jet.utils.ExceptionUtils;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
import static org.jetbrains.asm4.ClassReader.*;
import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.INCLUDE_KOTLIN;
@@ -136,25 +138,25 @@ public final class DeserializedDescriptorResolver {
@Nullable
private static ClassData readClassDataFromClassFile(@NotNull VirtualFile virtualFile) {
- byte[] data = getKotlinInfoDataFromClassFile(virtualFile);
+ String[] data = getKotlinInfoDataFromClassFile(virtualFile);
if (data == null) return null;
return JavaProtoBufUtil.readClassDataFrom(data);
}
@Nullable
private static PackageData readPackageDataFromClassFile(@NotNull VirtualFile virtualFile) {
- byte[] data = getKotlinInfoDataFromClassFile(virtualFile);
+ String[] data = getKotlinInfoDataFromClassFile(virtualFile);
if (data == null) return null;
return JavaProtoBufUtil.readPackageDataFrom(data);
}
@Nullable
- private static byte[] getKotlinInfoDataFromClassFile(@NotNull VirtualFile virtualFile) {
+ private static String[] getKotlinInfoDataFromClassFile(@NotNull VirtualFile virtualFile) {
GetKotlinInfoDataVisitor visitor = visitClassFile(virtualFile);
if (visitor == null) {
return null;
}
- byte[] data = visitor.getData();
+ String[] data = visitor.getData();
if (data == null) {
return null;
}
@@ -186,28 +188,43 @@ public final class DeserializedDescriptorResolver {
}
@Nullable
- private byte[] data = null;
+ private String[] data = null;
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (!desc.equals(KOTLIN_INFO_TYPE)) {
return null;
}
+
return new AnnotationVisitor(Opcodes.ASM4) {
@Override
- public void visit(String name, Object value) {
- if (name.equals("data")) {
- data = (byte[]) value;
- }
- else {
- throw new IllegalStateException("Unexpected property " + name + " for annotation " + KOTLIN_INFO_TYPE);
+ public AnnotationVisitor visitArray(String name) {
+ if (!name.equals("data")) {
+ throw new IllegalStateException("Unexpected argument " + name + " for annotation " + KOTLIN_INFO_TYPE);
}
+
+ final List strings = new ArrayList(1);
+ return new AnnotationVisitor(Opcodes.ASM4) {
+ @Override
+ public void visit(String name, Object value) {
+ if (!(value instanceof String)) {
+ throw new IllegalStateException("Unexpected argument value: " + value);
+ }
+
+ strings.add((String) value);
+ }
+
+ @Override
+ public void visitEnd() {
+ data = strings.toArray(new String[strings.size()]);
+ }
+ };
}
};
}
@Nullable
- public byte[] getData() {
+ public String[] getData() {
return data;
}
}
diff --git a/compiler/tests/compiler-tests.iml b/compiler/tests/compiler-tests.iml
index 2e820b9b71b..d275ff90aa4 100644
--- a/compiler/tests/compiler-tests.iml
+++ b/compiler/tests/compiler-tests.iml
@@ -17,6 +17,7 @@
+
diff --git a/compiler/tests/org/jetbrains/jet/codegen/KotlinInfoForPackageTest.java b/compiler/tests/org/jetbrains/jet/codegen/KotlinInfoForPackageTest.java
index 4d517ceafc8..0d2ae84d778 100644
--- a/compiler/tests/org/jetbrains/jet/codegen/KotlinInfoForPackageTest.java
+++ b/compiler/tests/org/jetbrains/jet/codegen/KotlinInfoForPackageTest.java
@@ -19,6 +19,7 @@ package org.jetbrains.jet.codegen;
import jet.KotlinInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.ConfigurationKind;
+import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
import org.jetbrains.jet.descriptors.serialization.PackageData;
import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
@@ -29,8 +30,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import static com.google.protobuf.ExtensionRegistryLite.getEmptyRegistry;
-
public class KotlinInfoForPackageTest extends CodegenTestCase {
public static final FqName NAMESPACE_NAME = new FqName("test");
@@ -54,7 +53,7 @@ public class KotlinInfoForPackageTest extends CodegenTestCase {
assertTrue(aClass.isAnnotationPresent(KotlinInfo.class));
KotlinInfo kotlinInfo = (KotlinInfo) aClass.getAnnotation(KotlinInfo.class);
- PackageData data = PackageData.read(kotlinInfo.data(), getEmptyRegistry());
+ PackageData data = JavaProtoBufUtil.readPackageDataFrom(kotlinInfo.data());
Set classNames = collectClassNames(data);
assertSameElements(Arrays.asList("A", "B", "C"), classNames);
diff --git a/compiler/tests/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtilTest.java b/compiler/tests/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtilTest.java
new file mode 100644
index 00000000000..b611c413c0d
--- /dev/null
+++ b/compiler/tests/org/jetbrains/jet/descriptors/serialization/JavaProtoBufUtilTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.descriptors.serialization;
+
+import com.intellij.testFramework.UsefulTestCase;
+
+import java.util.Random;
+
+import static org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil.decodeBytes;
+import static org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil.encodeBytes;
+import static org.junit.Assert.assertArrayEquals;
+
+public class JavaProtoBufUtilTest extends UsefulTestCase {
+ private static final int[] LENGTHS = new int[] {0, 1, 2, 3, 7, 10, 100, 1000, 32000, 33000, 65000, 65534, 65535, 65536, 65537,
+ 100000, 131074, 239017, 314159, 1000000};
+
+ private static void doTest(int randSeed, int length) {
+ byte[] a = new byte[length];
+ new Random(randSeed).nextBytes(a);
+
+ String message = "Failed randSeed = " + randSeed + ", length = " + length;
+
+ String[] b = encodeBytes(a);
+ byte[] c = decodeBytes(b);
+ assertArrayEquals(message, a, c);
+
+ String[] d = encodeBytes(c);
+ assertArrayEquals(message, d, b);
+
+ byte[] e = decodeBytes(d);
+ assertArrayEquals(message, a, e);
+ }
+
+ public void testEncodeDecode() {
+ for (int randSeed = 1; randSeed <= 3; randSeed++) {
+ for (int length : LENGTHS) {
+ doTest(randSeed, length);
+ }
+ }
+ }
+}
diff --git a/compiler/tests/org/jetbrains/jet/descriptors/serialization/KotlinInfoForClassTest.java b/compiler/tests/org/jetbrains/jet/descriptors/serialization/KotlinInfoForClassTest.java
index 844f4cc9878..cd1942c8843 100644
--- a/compiler/tests/org/jetbrains/jet/descriptors/serialization/KotlinInfoForClassTest.java
+++ b/compiler/tests/org/jetbrains/jet/descriptors/serialization/KotlinInfoForClassTest.java
@@ -32,8 +32,6 @@ import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
import java.io.IOException;
-import static com.google.protobuf.ExtensionRegistryLite.getEmptyRegistry;
-
public class KotlinInfoForClassTest extends CodegenTestCase {
public static final FqName NAMESPACE_NAME = new FqName("test");
public static final FqNameUnsafe CLASS_NAME = new FqNameUnsafe("A");
@@ -41,7 +39,7 @@ public class KotlinInfoForClassTest extends CodegenTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
- createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL);
+ createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY);
}
public void testClassKotlinInfo() throws Exception {
@@ -70,7 +68,7 @@ public class KotlinInfoForClassTest extends CodegenTestCase {
public KotlinInfoBasedDescriptorFinder(@NotNull KotlinInfo kotlinInfo) throws IOException {
super(new LockBasedStorageManager(), AnnotationDeserializer.UNSUPPORTED);
- this.classData = ClassData.read(kotlinInfo.data(), getEmptyRegistry());
+ this.classData = JavaProtoBufUtil.readClassDataFrom(kotlinInfo.data());
this.namespace = JetTestUtils.createTestNamespace(NAMESPACE_NAME.shortName());
}
diff --git a/runtime/src/jet/KotlinInfo.java b/runtime/src/jet/KotlinInfo.java
index 3319dcdeb69..8e9398a2f42 100644
--- a/runtime/src/jet/KotlinInfo.java
+++ b/runtime/src/jet/KotlinInfo.java
@@ -21,5 +21,5 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface KotlinInfo {
- byte[] data();
+ String[] data();
}