Do not fail when deserializing incompatible metadata

Catch all exceptions when deserializing metadata with an incompatible version
to prevent the compiler from failing on discovering incompatible classes on the
classpath. Note that this is not the perfect solution: any invariant may be
broken in the incompatible metadata and it may result in a later exception
This commit is contained in:
Alexander Udalov
2016-12-07 21:30:26 +03:00
parent 789483e1eb
commit 830d2f6603
9 changed files with 172 additions and 12 deletions
@@ -0,0 +1,13 @@
package a
open class A {
class Nested
fun method() {}
val quux = ""
}
fun foo() {}
var bar = 42
typealias TA = String
@@ -0,0 +1,13 @@
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata/source.kt:12:13: error: unresolved reference: foo
val x = foo()
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata/source.kt:13:13: error: unresolved reference: bar
val y = bar
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata/source.kt:14:5: error: unresolved reference: bar
bar = 239
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata/source.kt:15:12: error: unresolved reference: TA
val z: TA = ""
^
COMPILATION_ERROR
@@ -0,0 +1,16 @@
package usage
import a.*
fun baz(param: A, nested: A.Nested) {
val constructor = A()
val nested = A.Nested()
val quux = param.getQuux()
val methodCall = param.method()
val supertype = object : A() {}
val x = foo()
val y = bar
bar = 239
val z: TA = ""
}
@@ -0,0 +1,13 @@
package a
open class A {
class Nested
fun method() {}
val quux = ""
}
fun foo() {}
var bar = 42
typealias TA = String
@@ -0,0 +1,13 @@
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata2/source.kt:12:13: error: unresolved reference: foo
val x = foo()
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata2/source.kt:13:13: error: unresolved reference: bar
val y = bar
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata2/source.kt:14:5: error: unresolved reference: bar
bar = 239
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionBadMetadata2/source.kt:15:12: error: unresolved reference: TA
val z: TA = ""
^
COMPILATION_ERROR
@@ -0,0 +1,16 @@
package usage
import a.*
fun baz(param: A, nested: A.Nested) {
val constructor = A()
val nested = A.Nested()
val quux = param.getQuux()
val methodCall = param.method()
val supertype = object : A() {}
val x = foo()
val y = bar
bar = 239
val z: TA = ""
}
@@ -74,6 +74,28 @@ class WrongBytecodeVersionTest : KtUsefulTestCase() {
override fun visit(name: String, value: Any) {
super.visit(name, transform(name, value) ?: value)
}
override fun visitArray(name: String): AnnotationVisitor {
val entries = arrayListOf<String>()
val arrayVisitor = { super.visitArray(name) }
return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String?, value: Any) {
entries.add(value as String)
}
override fun visitEnd() {
@Suppress("UNCHECKED_CAST")
val result = transform(name, entries.toTypedArray()) as Array<String>? ?: entries.toTypedArray()
if (result.isEmpty()) return
with(arrayVisitor()) {
for (value in result) {
visit(null, value)
}
visitEnd()
}
}
}
}
}
}
return superVisitor
@@ -19,11 +19,13 @@ package org.jetbrains.kotlin.jvm.compiler;
import com.google.common.collect.Iterables;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import kotlin.Pair;
import kotlin.collections.SetsKt;
import kotlin.io.FilesKt;
import kotlin.jvm.functions.Function2;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.AbstractCliTest;
import org.jetbrains.kotlin.cli.WrongBytecodeVersionTest;
@@ -250,7 +252,11 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output));
}
private void doTestKotlinLibraryWithWrongMetadataVersion(@NotNull String libraryName, @NotNull String... additionalOptions) throws Exception {
private void doTestKotlinLibraryWithWrongMetadataVersion(
@NotNull String libraryName,
@Nullable final Function2<String, Object, Object> additionalTransformation,
@NotNull String... additionalOptions
) throws Exception {
final int[] version = new JvmMetadataVersion(42, 0, 0).toArray();
File library = transformJar(compileLibrary(libraryName), new Function2<String, byte[], byte[]>() {
@Override
@@ -258,6 +264,10 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
return WrongBytecodeVersionTest.Companion.transformMetadataInClassFile(bytes, new Function2<String, Object, Object>() {
@Override
public Object invoke(String name, Object value) {
if (additionalTransformation != null) {
Object result = additionalTransformation.invoke(name, value);
if (result != null) return result;
}
return JvmAnnotationNames.METADATA_VERSION_FIELD_NAME.equals(name) ? version : null;
}
});
@@ -417,13 +427,49 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
*/
public void testWrongMetadataVersion() throws Exception {
doTestKotlinLibraryWithWrongMetadataVersion("library");
doTestKotlinLibraryWithWrongMetadataVersion("library", null);
}
public void testWrongMetadataVersionBadMetadata() throws Exception {
doTestKotlinLibraryWithWrongMetadataVersion(
"library",
new Function2<String, Object, Object>() {
@Override
public Object invoke(String name, Object value) {
if (JvmAnnotationNames.METADATA_DATA_FIELD_NAME.equals(name)) {
String[] strings = (String[]) value;
for (int i = 0; i < strings.length; i++) {
byte[] bytes = strings[i].getBytes();
for (int j = 0; j < bytes.length; j++) bytes[j] ^= 42;
strings[i] = new String(bytes);
}
return strings;
}
return null;
}
}
);
}
public void testWrongMetadataVersionBadMetadata2() throws Exception {
doTestKotlinLibraryWithWrongMetadataVersion(
"library",
new Function2<String, Object, Object>() {
@Override
public Object invoke(String name, Object value) {
if (JvmAnnotationNames.METADATA_STRINGS_FIELD_NAME.equals(name)) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
return null;
}
}
);
}
/*
// TODO: refactor and uncomment
public void testWrongMetadataVersionSkipVersionCheck() throws Exception {
doTestKotlinLibraryWithWrongMetadataVersion("library", "-Xskip-metadata-version-check");
doTestKotlinLibraryWithWrongMetadataVersion("library", null, "-Xskip-metadata-version-check");
}
*/
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErr
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPackageMemberScope
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import org.jetbrains.kotlin.utils.addToStdlib.check
import org.jetbrains.kotlin.utils.sure
import javax.inject.Inject
class DeserializedDescriptorResolver {
@@ -47,10 +46,10 @@ class DeserializedDescriptorResolver {
internal fun readClassData(kotlinClass: KotlinJvmBinaryClass): ClassDataWithSource? {
val data = readData(kotlinClass, KOTLIN_CLASS) ?: return null
val strings = kotlinClass.classHeader.strings.sure { "String table not found in $kotlinClass" }
val strings = kotlinClass.classHeader.strings ?: return null
val classData = parseProto(kotlinClass) {
JvmProtoBufUtil.readClassDataFrom(data, strings)
}
} ?: return null
val sourceElement = KotlinJvmBinarySourceElement(
kotlinClass,
kotlinClass.incompatibility,
@@ -61,10 +60,10 @@ class DeserializedDescriptorResolver {
fun createKotlinPackagePartScope(descriptor: PackageFragmentDescriptor, kotlinClass: KotlinJvmBinaryClass): MemberScope? {
val data = readData(kotlinClass, KOTLIN_FILE_FACADE_OR_MULTIFILE_CLASS_PART) ?: return null
val strings = kotlinClass.classHeader.strings.sure { "String table not found in $kotlinClass" }
val strings = kotlinClass.classHeader.strings ?: return null
val (nameResolver, packageProto) = parseProto(kotlinClass) {
JvmProtoBufUtil.readPackageDataFrom(data, strings)
}
} ?: return null
val source = JvmPackagePartSource(
kotlinClass,
kotlinClass.incompatibility,
@@ -87,12 +86,21 @@ class DeserializedDescriptorResolver {
return (header.data ?: header.incompatibleData)?.check { header.kind in expectedKinds }
}
private inline fun <T> parseProto(klass: KotlinJvmBinaryClass, block: () -> T): T {
private inline fun <T : Any> parseProto(klass: KotlinJvmBinaryClass, block: () -> T): T? {
try {
return block()
try {
return block()
}
catch (e: InvalidProtocolBufferException) {
throw IllegalStateException("Could not read data from ${klass.location}", e)
}
}
catch (e: InvalidProtocolBufferException) {
throw IllegalStateException("Could not read data from ${klass.location}", e)
catch (e: Throwable) {
if (!klass.classHeader.metadataVersion.isCompatible()) {
// TODO: log.warn
return null
}
throw e
}
}