From b943ed26f3a8b3fc4d531063f3477f6ee711e2bc Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 6 Dec 2016 21:21:41 +0300 Subject: [PATCH] Report incompatible metadata version error correctly Similarly to pre-release classes, load metadata for the class anyway and allow the resolution to select it as the result and prohibit its usage in the end with the special diagnostic reported in MissingDependencyClassChecker --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 13 +++++ .../checkers/MissingDependencyClassChecker.kt | 15 +++-- compiler/testData/cli/jvm/wrongAbiVersion.out | 7 ++- .../cli/jvm/wrongAbiVersionNoErrors.out | 5 +- .../wrongMetadataVersion/library/a.kt | 11 ++++ .../wrongMetadataVersion/output.txt | 49 ++++++++++++++++ .../wrongMetadataVersion/source.kt | 15 +++++ .../library/a.kt | 11 ++++ .../output.txt | 1 + .../source.kt | 16 +++++ .../jetbrains/kotlin/cli/AbstractCliTest.java | 5 +- ...ompileKotlinAgainstCustomBinariesTest.java | 58 +++++++++++++++++-- .../lazy/descriptors/LazyJavaPackageScope.kt | 31 +++++----- .../kotlin/DeserializedDescriptorResolver.kt | 24 +++++--- .../load/kotlin/JvmPackagePartSource.kt | 9 ++- .../kotlin/KotlinJvmBinarySourceElement.kt | 2 + .../load/kotlin/header/KotlinClassHeader.kt | 1 + ...eadKotlinClassHeaderAnnotationVisitor.java | 6 ++ .../DeserializedMemberDescriptor.kt | 4 ++ 20 files changed, 243 insertions(+), 42 deletions(-) create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/library/a.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/library/a.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/source.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index fbd2c5f13c2..84be6b2747d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier; import org.jetbrains.kotlin.resolve.checkers.PlatformImplDeclarationChecker; +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData; import org.jetbrains.kotlin.types.KotlinType; import java.lang.reflect.Field; @@ -83,6 +84,7 @@ public interface Errors { DiagnosticFactory1 MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory2> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR); //Elements with "INVISIBLE_REFERENCE" error are marked as unresolved, unlike elements with "INVISIBLE_MEMBER" error //"INVISIBLE_REFERENCE" is used for invisible classes references and references in import diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 2e3b2280b25..08b67ac6aea 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.diagnostics.rendering; import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.util.io.FileUtil; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,6 +32,7 @@ import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtSimpleNameExpression; import org.jetbrains.kotlin.psi.KtTypeConstraint; import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData; +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.util.MappedExtensionProvider; import org.jetbrains.kotlin.util.OperatorNameConventions; @@ -321,6 +323,17 @@ public class DefaultErrorMessages { MAP.put(MISSING_DEPENDENCY_CLASS, "Cannot access class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING); MAP.put(PRE_RELEASE_CLASS, "Class ''{0}'' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler", TO_STRING); + MAP.put(INCOMPATIBLE_CLASS, + "Class ''{0}'' was compiled with an incompatible version of Kotlin. {1}", + TO_STRING, new DiagnosticParameterRenderer>() { + @NotNull + @Override + public String render(@NotNull IncompatibleVersionErrorData incompatibility, @NotNull RenderingContext renderingContext) { + return "The binary version of its metadata is " + incompatibility.getActualVersion() + + ", expected version is " + incompatibility.getExpectedVersion() + ".\n" + + "The class is loaded from " + FileUtil.toSystemIndependentName(incompatibility.getFilePath()); + } + }); MAP.put(LOCAL_OBJECT_NOT_ALLOWED, "Named object ''{0}'' is a singleton and cannot be local. Try to use anonymous object instead", NAME); MAP.put(LOCAL_INTERFACE_NOT_ALLOWED, "''{0}'' is an interface so it cannot be local. Try to use anonymous object or abstract class instead", NAME); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt index ba889ca9fbe..281ce51fd36 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt @@ -23,8 +23,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassifierDescriptor import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.diagnostics.Errors.MISSING_DEPENDENCY_CLASS -import org.jetbrains.kotlin.diagnostics.Errors.PRE_RELEASE_CLASS +import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext @@ -57,9 +56,15 @@ object MissingDependencyClassChecker : CallChecker { } private fun incompatibilityDiagnosticFor(source: SourceElement?, reportOn: PsiElement): Diagnostic? { - if (source is DeserializedContainerSource && source.isPreReleaseInvisible) { - // TODO: if at least one PRE_RELEASE_CLASS is reported, display a hint to disable the diagnostic - return PRE_RELEASE_CLASS.on(reportOn, source.presentableFqName) + if (source is DeserializedContainerSource) { + val incompatibility = source.incompatibility + if (incompatibility != null) { + return INCOMPATIBLE_CLASS.on(reportOn, source.presentableFqName, incompatibility) + } + if (source.isPreReleaseInvisible) { + // TODO: if at least one PRE_RELEASE_CLASS is reported, display a hint to disable the diagnostic + return PRE_RELEASE_CLASS.on(reportOn, source.presentableFqName) + } } return null diff --git a/compiler/testData/cli/jvm/wrongAbiVersion.out b/compiler/testData/cli/jvm/wrongAbiVersion.out index 08ec72137d5..8d4f58dd56d 100644 --- a/compiler/testData/cli/jvm/wrongAbiVersion.out +++ b/compiler/testData/cli/jvm/wrongAbiVersion.out @@ -1,11 +1,12 @@ +compiler/testData/cli/jvm/wrongAbiVersion.kt:3:12: error: class 'ClassWithWrongAbiVersion' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 0.30.0, expected version is $ABI_VERSION$. +The class is loaded from $TESTDATA_DIR$/wrongAbiVersionLib/bin/ClassWithWrongAbiVersion.class +fun foo(x: ClassWithWrongAbiVersion) { + ^ compiler/testData/cli/jvm/wrongAbiVersion.kt:4:5: error: unresolved reference: bar -(note: this may be caused by the fact that some classes compiled with an incompatible version of Kotlin were found in the classpath. Such classes cannot be loaded properly by this version of Kotlin compiler. See below for more information) bar() ^ compiler/testData/cli/jvm/wrongAbiVersion.kt:6:7: error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public fun String.replaceIndent(newIndent: String = ...): String defined in kotlin.text -(note: this may be caused by the fact that some classes compiled with an incompatible version of Kotlin were found in the classpath. Such classes cannot be loaded properly by this version of Kotlin compiler. See below for more information) 1.replaceIndent(2, 3) ^ -compiler/testData/cli/jvm/wrongAbiVersionLib/bin/ClassWithWrongAbiVersion.class: error: class 'ClassWithWrongAbiVersion' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 0.30.0, expected version is $ABI_VERSION$ COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/cli/jvm/wrongAbiVersionNoErrors.out b/compiler/testData/cli/jvm/wrongAbiVersionNoErrors.out index 16cbc5fb4ba..2ffc0d49882 100644 --- a/compiler/testData/cli/jvm/wrongAbiVersionNoErrors.out +++ b/compiler/testData/cli/jvm/wrongAbiVersionNoErrors.out @@ -1,2 +1,5 @@ -compiler/testData/cli/jvm/wrongAbiVersionLib/bin/wrong/ClassWithInnerLambda.class: error: class 'wrong/ClassWithInnerLambda' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 0.30.0, expected version is $ABI_VERSION$ +compiler/testData/cli/jvm/wrongAbiVersionNoErrors.kt:3:14: error: class 'wrong.ClassWithInnerLambda' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 0.30.0, expected version is $ABI_VERSION$. +The class is loaded from $TESTDATA_DIR$/wrongAbiVersionLib/bin/wrong/ClassWithInnerLambda.class +import wrong.ClassWithInnerLambda + ^ COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/library/a.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/library/a.kt new file mode 100644 index 00000000000..2dc03d6b5da --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/library/a.kt @@ -0,0 +1,11 @@ +package a + +open class A { + class Nested + + fun method() {} +} + +fun foo() {} +var bar = 42 +typealias TA = String diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/output.txt new file mode 100644 index 00000000000..caea6212ce9 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/output.txt @@ -0,0 +1,49 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:5:16: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class +fun baz(param: A, nested: A.Nested) { + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:5:27: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class +fun baz(param: A, nested: A.Nested) { + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:5:29: error: class 'a.A.Nested' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A$Nested.class +fun baz(param: A, nested: A.Nested) { + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:6:23: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class + val constructor = A() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:7:18: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class + val nested = A.Nested() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:7:20: error: class 'a.A.Nested' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A$Nested.class + val nested = A.Nested() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:8:22: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class + val methodCall = param.method() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:9:30: error: class 'a.A' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/A.class + val supertype = object : A() {} + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:11:13: error: class 'a.AKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/AKt.class + val x = foo() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:12:13: error: class 'a.AKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/AKt.class + val y = bar + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:13:5: error: class 'a.AKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/AKt.class + bar = 239 + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt:14:12: error: class 'a.AKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 42.0.0, expected version is $ABI_VERSION$. +The class is loaded from $TMP_DIR$/library-after.jar!/a/AKt.class + val z: TA = "" + ^ +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt new file mode 100644 index 00000000000..f15b2e0bd70 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersion/source.kt @@ -0,0 +1,15 @@ +package usage + +import a.* + +fun baz(param: A, nested: A.Nested) { + val constructor = A() + val nested = A.Nested() + val methodCall = param.method() + val supertype = object : A() {} + + val x = foo() + val y = bar + bar = 239 + val z: TA = "" +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/library/a.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/library/a.kt new file mode 100644 index 00000000000..2dc03d6b5da --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/library/a.kt @@ -0,0 +1,11 @@ +package a + +open class A { + class Nested + + fun method() {} +} + +fun foo() {} +var bar = 42 +typealias TA = String diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/output.txt new file mode 100644 index 00000000000..a0aba9318ad --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/output.txt @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/source.kt new file mode 100644 index 00000000000..cf47e098fb1 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/wrongMetadataVersionSkipVersionCheck/source.kt @@ -0,0 +1,16 @@ +@file:Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER") +package usage + +import a.* + +fun baz(param: A, nested: A.Nested) { + val constructor = A() + val nested2 = A.Nested() + val methodCall = param.method() + val supertype = object : A() {} + + val x = foo() + val y = bar + bar = 239 + val z: TA = "" +} diff --git a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java index d9eaddbbe55..971b23cc80b 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.cli; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import kotlin.Pair; @@ -72,8 +73,10 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { @NotNull String testDataDir, @NotNull BinaryVersion version ) { + String testDataAbsoluteDir = new File(testDataDir).getAbsolutePath(); String normalizedOutputWithoutExitCode = pureOutput - .replace(new File(testDataDir).getAbsolutePath(), "$TESTDATA_DIR$") + .replace(testDataAbsoluteDir, "$TESTDATA_DIR$") + .replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), "$TESTDATA_DIR$") .replace(PathUtil.getKotlinPathsForDistDirectory().getHomePath().getAbsolutePath(), "$PROJECT_DIR$") .replace("expected version is " + version, "expected version is $ABI_VERSION$") .replace("\\", "/") diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index 9daedb1e48e..032eaea819a 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -22,9 +22,11 @@ import com.intellij.openapi.util.io.FileUtil; import kotlin.Pair; import kotlin.collections.SetsKt; import kotlin.io.FilesKt; +import kotlin.jvm.functions.Function2; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cli.AbstractCliTest; +import org.jetbrains.kotlin.cli.WrongBytecodeVersionTest; import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; import org.jetbrains.kotlin.cli.common.messages.MessageRenderer; @@ -37,6 +39,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.config.KotlinCompilerVersion; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.PackageViewDescriptor; +import org.jetbrains.kotlin.load.java.JvmAnnotationNames; import org.jetbrains.kotlin.load.kotlin.DeserializedDescriptorResolver; import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion; import org.jetbrains.kotlin.resolve.BindingContext; @@ -92,7 +95,7 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { private String normalizeOutput(@NotNull Pair output) { return AbstractCliTest.getNormalizedCompilerOutput( output.getFirst(), output.getSecond(), getTestDataDirectory().getPath(), JvmMetadataVersion.INSTANCE - ); + ).replace(FileUtil.toSystemIndependentName(tmpdir.getAbsolutePath()), "$TMP_DIR$"); } private void doTestWithTxt(@NotNull File... extraClassPath) throws Exception { @@ -134,6 +137,20 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { @NotNull private static File copyJarFileWithoutEntry(@NotNull File jarPath, @NotNull String... entriesToDelete) { + return transformJar(jarPath, new Function2() { + @Override + public byte[] invoke(String s, byte[] bytes) { + return bytes; + } + }, entriesToDelete); + } + + @NotNull + private static File transformJar( + @NotNull File jarPath, + @NotNull Function2 transformEntry, + @NotNull String... entriesToDelete + ) { try { File outputFile = new File(jarPath.getParentFile(), FileUtil.getNameWithoutExtension(jarPath) + "-after.jar"); Set toDelete = SetsKt.setOf(entriesToDelete); @@ -144,11 +161,16 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { try { for (Enumeration enumeration = jar.entries(); enumeration.hasMoreElements(); ) { JarEntry jarEntry = enumeration.nextElement(); - if (toDelete.contains(jarEntry.getName())) { + String name = jarEntry.getName(); + if (toDelete.contains(name)) { continue; } - output.putNextEntry(jarEntry); - output.write(FileUtil.loadBytes(jar.getInputStream(jarEntry))); + byte[] bytes = FileUtil.loadBytes(jar.getInputStream(jarEntry)); + byte[] newBytes = name.endsWith(".class") ? transformEntry.invoke(name, bytes) : bytes; + JarEntry newEntry = new JarEntry(name); + newEntry.setSize(newBytes.length); + output.putNextEntry(newEntry); + output.write(newBytes); output.closeEntry(); } } @@ -228,6 +250,23 @@ 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 { + final int[] version = new JvmMetadataVersion(42, 0, 0).toArray(); + File library = transformJar(compileLibrary(libraryName), new Function2() { + @Override + public byte[] invoke(String name, byte[] bytes) { + return WrongBytecodeVersionTest.Companion.transformMetadataInClassFile(bytes, new Function2() { + @Override + public Object invoke(String name, Object value) { + return JvmAnnotationNames.METADATA_VERSION_FIELD_NAME.equals(name) ? version : null; + } + }); + } + }); + Pair output = compileKotlin("source.kt", tmpdir, Arrays.asList(additionalOptions), library); + KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output)); + } + @SuppressWarnings("deprecation") private void doTestPreReleaseKotlinLibrary(@NotNull String libraryName, @NotNull String... additionalOptions) throws Exception { // Compiles the library with the "pre-release" flag, then compiles a usage of this library in the release mode @@ -377,6 +416,17 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { } */ + public void testWrongMetadataVersion() throws Exception { + doTestKotlinLibraryWithWrongMetadataVersion("library"); + } + + /* + // TODO: refactor and uncomment + public void testWrongMetadataVersionSkipVersionCheck() throws Exception { + doTestKotlinLibraryWithWrongMetadataVersion("library", "-Xskip-metadata-version-check"); + } + */ + /*test source mapping generation when source info is absent*/ public void testInlineFunWithoutDebugInfo() throws Exception { compileKotlin("sourceInline.kt", tmpdir); diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaPackageScope.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaPackageScope.kt index 46a64d2ad3e..17a9d2201fe 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaPackageScope.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaPackageScope.kt @@ -99,25 +99,20 @@ class LazyJavaPackageScope( object SyntheticClass : KotlinClassLookupResult() } - private fun resolveKotlinBinaryClass(kotlinClass: KotlinJvmBinaryClass?): KotlinClassLookupResult { - if (kotlinClass == null) return KotlinClassLookupResult.NotFound - - val header = kotlinClass.classHeader - return when { - !header.metadataVersion.isCompatible() -> { - c.components.errorReporter.reportIncompatibleMetadataVersion(kotlinClass.classId, kotlinClass.location, header.metadataVersion) - KotlinClassLookupResult.NotFound + private fun resolveKotlinBinaryClass(kotlinClass: KotlinJvmBinaryClass?): KotlinClassLookupResult = + when { + kotlinClass == null -> { + KotlinClassLookupResult.NotFound + } + kotlinClass.classHeader.kind == KotlinClassHeader.Kind.CLASS -> { + val descriptor = c.components.deserializedDescriptorResolver.resolveClass(kotlinClass) + if (descriptor != null) KotlinClassLookupResult.Found(descriptor) else KotlinClassLookupResult.NotFound + } + else -> { + // This is a package or interface DefaultImpls or something like that + KotlinClassLookupResult.SyntheticClass + } } - header.kind == KotlinClassHeader.Kind.CLASS -> { - val descriptor = c.components.deserializedDescriptorResolver.resolveClass(kotlinClass) - if (descriptor != null) KotlinClassLookupResult.Found(descriptor) else KotlinClassLookupResult.NotFound - } - else -> { - // This is a package or interface DefaultImpls or something like that - KotlinClassLookupResult.SyntheticClass - } - } - } // javaClass here is only for sake of optimizations private class FindClassRequest(val name: Name, val javaClass: JavaClass?) { diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/DeserializedDescriptorResolver.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/DeserializedDescriptorResolver.kt index f0034d0e348..24967a7578f 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/DeserializedDescriptorResolver.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/DeserializedDescriptorResolver.kt @@ -25,8 +25,10 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.serialization.ClassDataWithSource import org.jetbrains.kotlin.serialization.deserialization.DeserializationComponents import org.jetbrains.kotlin.serialization.deserialization.ErrorReporter +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData 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 @@ -50,7 +52,11 @@ class DeserializedDescriptorResolver(private val errorReporter: ErrorReporter) { val classData = parseProto(kotlinClass) { JvmProtoBufUtil.readClassDataFrom(data, strings) } - val sourceElement = KotlinJvmBinarySourceElement(kotlinClass, !IS_PRE_RELEASE && kotlinClass.classHeader.isPreRelease) + val sourceElement = KotlinJvmBinarySourceElement( + kotlinClass, + kotlinClass.incompatibility, + !IS_PRE_RELEASE && kotlinClass.classHeader.isPreRelease + ) return ClassDataWithSource(classData, sourceElement) } @@ -62,6 +68,7 @@ class DeserializedDescriptorResolver(private val errorReporter: ErrorReporter) { } val source = JvmPackagePartSource( kotlinClass, + kotlinClass.incompatibility, isPreReleaseInvisible = !IS_PRE_RELEASE && kotlinClass.classHeader.isPreRelease ) return DeserializedPackageMemberScope(descriptor, packageProto, nameResolver, source, components) { @@ -70,16 +77,15 @@ class DeserializedDescriptorResolver(private val errorReporter: ErrorReporter) { } } - private fun readData(kotlinClass: KotlinJvmBinaryClass, expectedKinds: Set): Array? { - val header = kotlinClass.classHeader - if (!header.metadataVersion.isCompatible()) { - errorReporter.reportIncompatibleMetadataVersion(kotlinClass.classId, kotlinClass.location, header.metadataVersion) - } - else if (expectedKinds.contains(header.kind)) { - return header.data + private val KotlinJvmBinaryClass.incompatibility: IncompatibleVersionErrorData? + get() { + if (classHeader.metadataVersion.isCompatible()) return null + return IncompatibleVersionErrorData(classHeader.metadataVersion, JvmMetadataVersion.INSTANCE, location, classId) } - return null + internal fun readData(kotlinClass: KotlinJvmBinaryClass, expectedKinds: Set): Array? { + val header = kotlinClass.classHeader + return (header.data ?: header.incompatibleData)?.check { header.kind in expectedKinds } } private inline fun parseProto(klass: KotlinJvmBinaryClass, block: () -> T): T { diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/JvmPackagePartSource.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/JvmPackagePartSource.kt index 19dee966ddc..f2c505a9fe0 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/JvmPackagePartSource.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/JvmPackagePartSource.kt @@ -21,18 +21,25 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource class JvmPackagePartSource( val className: JvmClassName, val facadeClassName: JvmClassName?, + override val incompatibility: IncompatibleVersionErrorData? = null, override val isPreReleaseInvisible: Boolean = false ) : DeserializedContainerSource { - constructor(kotlinClass: KotlinJvmBinaryClass, isPreReleaseInvisible: Boolean = false) : this( + constructor( + kotlinClass: KotlinJvmBinaryClass, + incompatibility: IncompatibleVersionErrorData? = null, + isPreReleaseInvisible: Boolean = false + ) : this( JvmClassName.byClassId(kotlinClass.classId), kotlinClass.classHeader.multifileClassName?.let { if (it.isNotEmpty()) JvmClassName.byInternalName(it) else null }, + incompatibility, isPreReleaseInvisible ) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmBinarySourceElement.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmBinarySourceElement.kt index 419971c82d3..afcab1a6a9b 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmBinarySourceElement.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmBinarySourceElement.kt @@ -18,10 +18,12 @@ package org.jetbrains.kotlin.load.kotlin import org.jetbrains.kotlin.descriptors.SourceFile import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource class KotlinJvmBinarySourceElement( val binaryClass: KotlinJvmBinaryClass, + override val incompatibility: IncompatibleVersionErrorData? = null, override val isPreReleaseInvisible: Boolean = false ) : DeserializedContainerSource { override val presentableFqName: FqName diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt index 549599d757e..879c737a502 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt @@ -27,6 +27,7 @@ class KotlinClassHeader( val metadataVersion: JvmMetadataVersion, val bytecodeVersion: JvmBytecodeBinaryVersion, val data: Array?, + val incompatibleData: Array?, val strings: Array?, val extraString: String?, val extraInt: Int diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java index 018693b5ada..6c2a29a031a 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java @@ -54,6 +54,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor private int extraInt = 0; private String[] data = null; private String[] strings = null; + private String[] incompatibleData = null; private KotlinClassHeader.Kind headerKind = null; @Nullable @@ -62,6 +63,10 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor return null; } + if (!metadataVersion.isCompatible()) { + incompatibleData = data; + } + if (metadataVersion == null || !metadataVersion.isCompatible()) { data = null; } @@ -76,6 +81,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION, bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION, data, + incompatibleData, strings, extraString, extraInt diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedMemberDescriptor.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedMemberDescriptor.kt index cf44096cfb6..2c50bb0622d 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedMemberDescriptor.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedMemberDescriptor.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.protobuf.MessageLite import org.jetbrains.kotlin.serialization.Flags import org.jetbrains.kotlin.serialization.ProtoBuf +import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData import org.jetbrains.kotlin.serialization.deserialization.NameResolver import org.jetbrains.kotlin.serialization.deserialization.TypeTable import org.jetbrains.kotlin.types.SimpleType @@ -43,6 +44,9 @@ interface DeserializedMemberDescriptor : MemberDescriptor { } interface DeserializedContainerSource : SourceElement { + // Non-null if this container is loaded from a class with an incompatible binary version + val incompatibility: IncompatibleVersionErrorData<*>? + // True iff this is container is "invisible" because it's loaded from a pre-release class and this compiler is a release val isPreReleaseInvisible: Boolean