diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/signature/KotlinToJvmSignatureMapperImpl.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/signature/KotlinToJvmSignatureMapperImpl.kt index 85f0a4d62ac..6b01f1f4bf0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/signature/KotlinToJvmSignatureMapperImpl.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/signature/KotlinToJvmSignatureMapperImpl.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.codegen.signature import org.jetbrains.kotlin.codegen.ClassBuilderMode +import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker import org.jetbrains.kotlin.codegen.state.JetTypeMapper import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider @@ -26,7 +27,8 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.KotlinToJvmSignatureMapper class KotlinToJvmSignatureMapperImpl : KotlinToJvmSignatureMapper { // We use empty BindingContext, because it is only used by JetTypeMapper for purposes irrelevant to the needs of this class - private val typeMapper: JetTypeMapper = JetTypeMapper(BindingContext.EMPTY, ClassBuilderMode.LIGHT_CLASSES, NoResolveFileClassesProvider, null, JvmAbi.DEFAULT_MODULE_NAME) + private val typeMapper = JetTypeMapper(BindingContext.EMPTY, ClassBuilderMode.LIGHT_CLASSES, NoResolveFileClassesProvider, null, + IncompatibleClassTracker.DoNothing, JvmAbi.DEFAULT_MODULE_NAME) override fun mapToJvmMethodSignature(function: FunctionDescriptor) = typeMapper.mapSignature(function) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt index 25d4a9a0c42..9d147e0c81b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt @@ -51,7 +51,8 @@ class BuilderFactoryForDuplicateSignatureDiagnostics( ) : SignatureCollectingClassBuilderFactory(builderFactory) { // Avoid errors when some classes are not loaded for some reason - private val typeMapper = JetTypeMapper(bindingContext, ClassBuilderMode.LIGHT_CLASSES, fileClassesProvider, incrementalCache, moduleName) + private val typeMapper = JetTypeMapper(bindingContext, ClassBuilderMode.LIGHT_CLASSES, fileClassesProvider, incrementalCache, + IncompatibleClassTracker.DoNothing, moduleName) private val reportDiagnosticsTasks = ArrayList<() -> Unit>() fun reportDiagnostics() { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 1df5fbdc654..0f5ce4215b3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -95,7 +95,7 @@ class GenerationState @JvmOverloads constructor( incrementalCompilationComponents.getIncrementalCache(targetId) else null - private val extraJvmDiagnosticsTrace: BindingTrace = DelegatingBindingTrace(bindingContext, false, "For extra diagnostics in ${this.javaClass}") + val extraJvmDiagnosticsTrace: BindingTrace = DelegatingBindingTrace(bindingContext, false, "For extra diagnostics in ${this.javaClass}") private val interceptedBuilderFactory: ClassBuilderFactory private var used = false @@ -109,7 +109,10 @@ class GenerationState @JvmOverloads constructor( val classBuilderMode: ClassBuilderMode = builderFactory.classBuilderMode val bindingTrace: BindingTrace = DelegatingBindingTrace(bindingContext, "trace in GenerationState") val bindingContext: BindingContext = bindingTrace.bindingContext - val typeMapper: JetTypeMapper = JetTypeMapper(this.bindingContext, classBuilderMode, fileClassesProvider, getIncrementalCacheForThisTarget(), this.moduleName) + val typeMapper: JetTypeMapper = JetTypeMapper( + this.bindingContext, classBuilderMode, fileClassesProvider, getIncrementalCacheForThisTarget(), + IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace), this.moduleName + ) val intrinsics: IntrinsicMethods = IntrinsicMethods() val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this) val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/IncompatibleClassTracker.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/IncompatibleClassTracker.kt new file mode 100644 index 00000000000..91667dd7c19 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/IncompatibleClassTracker.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2016 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.state + +import org.jetbrains.kotlin.load.java.components.IncompatibleVersionErrorData +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.util.slicedMap.Slices +import org.jetbrains.kotlin.util.slicedMap.WritableSlice + +interface IncompatibleClassTracker { + fun record(binaryClass: KotlinJvmBinaryClass) + + object DoNothing : IncompatibleClassTracker { + override fun record(binaryClass: KotlinJvmBinaryClass) { + } + } +} + +class IncompatibleClassTrackerImpl(val trace: BindingTrace) : IncompatibleClassTracker { + private val classes = linkedSetOf() + + override fun record(binaryClass: KotlinJvmBinaryClass) { + if (classes.add(binaryClass.location)) { + val errorData = IncompatibleVersionErrorData(binaryClass.classHeader.bytecodeVersion, binaryClass.location, binaryClass.classId) + trace.record(BYTECODE_VERSION_ERRORS, binaryClass.location, errorData) + } + } + + companion object { + @JvmField + val BYTECODE_VERSION_ERRORS: WritableSlice = Slices.createCollectiveSlice() + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java index 2cdb02d240f..c4ed2636dbf 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java @@ -18,8 +18,8 @@ package org.jetbrains.kotlin.codegen.state; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; -import kotlin.collections.CollectionsKt; import kotlin.Pair; +import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.BuiltinsPackageFragment; @@ -38,10 +38,15 @@ import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider; import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature; import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature.SpecialSignatureInfo; import org.jetbrains.kotlin.load.java.JvmAbi; +import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion; import org.jetbrains.kotlin.load.java.SpecialBuiltinMembers; import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor; import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor; +import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment; import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageScope; +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass; +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement; +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement; import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackageFragmentProvider; import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache; import org.jetbrains.kotlin.name.*; @@ -66,6 +71,7 @@ import org.jetbrains.kotlin.resolve.scopes.AbstractScopeAdapter; import org.jetbrains.kotlin.resolve.scopes.MemberScope; import org.jetbrains.kotlin.serialization.deserialization.DeserializedType; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor; +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor; import org.jetbrains.kotlin.types.*; import org.jetbrains.kotlin.util.OperatorNameConventions; import org.jetbrains.org.objectweb.asm.Type; @@ -90,6 +96,7 @@ public class JetTypeMapper { private final ClassBuilderMode classBuilderMode; private final JvmFileClassesProvider fileClassesProvider; private final IncrementalCache incrementalCache; + private final IncompatibleClassTracker incompatibleClassTracker; private final String moduleName; public JetTypeMapper( @@ -97,12 +104,14 @@ public class JetTypeMapper { @NotNull ClassBuilderMode classBuilderMode, @NotNull JvmFileClassesProvider fileClassesProvider, @Nullable IncrementalCache incrementalCache, + @NotNull IncompatibleClassTracker incompatibleClassTracker, @NotNull String moduleName ) { this.bindingContext = bindingContext; this.classBuilderMode = classBuilderMode; this.fileClassesProvider = fileClassesProvider; this.incrementalCache = incrementalCache; + this.incompatibleClassTracker = incompatibleClassTracker; this.moduleName = moduleName; } @@ -978,12 +987,17 @@ public class JetTypeMapper { } @NotNull - public JvmMethodSignature mapSignature(@NotNull FunctionDescriptor f, @NotNull OwnerKind kind, - List valueParameters) { + public JvmMethodSignature mapSignature( + @NotNull FunctionDescriptor f, + @NotNull OwnerKind kind, + @NotNull List valueParameters + ) { if (f instanceof FunctionImportedFromObject) { return mapSignature(((FunctionImportedFromObject) f).getCallableFromObject()); } + checkOwnerCompatibility(f); + BothSignatureWriter sw = new BothSignatureWriter(BothSignatureWriter.Mode.METHOD); if (f instanceof ConstructorDescriptor) { @@ -1036,6 +1050,33 @@ public class JetTypeMapper { return signature; } + private void checkOwnerCompatibility(@NotNull FunctionDescriptor descriptor) { + if (!(descriptor instanceof DeserializedCallableMemberDescriptor)) return; + + KotlinJvmBinaryClass ownerClass = null; + + DeclarationDescriptor container = descriptor.getContainingDeclaration(); + if (container instanceof DeserializedClassDescriptor) { + SourceElement source = ((DeserializedClassDescriptor) container).getSource(); + if (source instanceof KotlinJvmBinarySourceElement) { + ownerClass = ((KotlinJvmBinarySourceElement) source).getBinaryClass(); + } + } + else if (container instanceof LazyJavaPackageFragment) { + SourceElement source = ((LazyJavaPackageFragment) container).getSource(); + if (source instanceof KotlinJvmBinaryPackageSourceElement) { + ownerClass = ((KotlinJvmBinaryPackageSourceElement) source).getRepresentativeBinaryClass(); + } + } + + if (ownerClass != null) { + JvmBytecodeBinaryVersion version = ownerClass.getClassHeader().getBytecodeVersion(); + if (!version.isCompatible()) { + incompatibleClassTracker.record(ownerClass); + } + } + } + private boolean writeCustomParameter( @NotNull FunctionDescriptor f, @NotNull ValueParameterDescriptor parameter, diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java index b23fc13a66f..5992ed4af78 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java @@ -26,13 +26,15 @@ import kotlin.jvm.functions.Function0; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.analyzer.AnalysisResult; +import org.jetbrains.kotlin.codegen.state.IncompatibleClassTrackerImpl; import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages; import org.jetbrains.kotlin.load.java.JavaBindingContext; +import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion; +import org.jetbrains.kotlin.load.java.components.IncompatibleVersionErrorData; import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter; -import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter.MetadataVersionErrorData; import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.AnalyzingUtils; @@ -41,6 +43,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; import org.jetbrains.kotlin.resolve.jvm.JvmClassName; +import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion; import org.jetbrains.kotlin.utils.StringsKt; import java.util.ArrayList; @@ -146,12 +149,12 @@ public final class AnalyzerWithCompilerReport { } @NotNull - private List getAbiVersionErrors() { + private List getAbiVersionErrors() { assert analysisResult != null; BindingContext bindingContext = analysisResult.getBindingContext(); Collection errorClasses = bindingContext.getKeys(TraceBasedErrorReporter.METADATA_VERSION_ERRORS); - List result = new ArrayList(errorClasses.size()); + List result = new ArrayList(errorClasses.size()); for (String kotlinClass : errorClasses) { result.add(bindingContext.get(TraceBasedErrorReporter.METADATA_VERSION_ERRORS, kotlinClass)); } @@ -159,16 +162,43 @@ public final class AnalyzerWithCompilerReport { return result; } - private void reportMetadataVersionErrors(@NotNull List errors) { - for (MetadataVersionErrorData data : errors) { - String path = toSystemDependentName(data.getFilePath()); - messageCollector.report( - CompilerMessageSeverity.ERROR, - "Class '" + JvmClassName.byClassId(data.getClassId()) + "' was compiled with an incompatible version of Kotlin. " + - "The binary version of its metadata is " + data.getActualVersion() + - ", expected version is " + JvmMetadataVersion.INSTANCE, - CompilerMessageLocation.create(path, -1, -1, null) - ); + private void reportMetadataVersionErrors(@NotNull List errors) { + for (IncompatibleVersionErrorData data : errors) { + reportIncompatibleBinaryVersion(messageCollector, data, JvmMetadataVersion.INSTANCE, "metadata", CompilerMessageSeverity.ERROR); + } + } + + private static void reportIncompatibleBinaryVersion( + @NotNull MessageCollector messageCollector, + @NotNull IncompatibleVersionErrorData data, + @NotNull BinaryVersion expectedVersion, + @NotNull String versionSortText, + @NotNull CompilerMessageSeverity severity + ) { + messageCollector.report( + severity, + "Class '" + JvmClassName.byClassId(data.getClassId()) + "' was compiled with an incompatible version of Kotlin. " + + "The binary version of its " + versionSortText + " is " + data.getActualVersion() + ", " + + "expected version is " + expectedVersion, + CompilerMessageLocation.create(toSystemDependentName(data.getFilePath()), -1, -1, null) + ); + } + + public static void reportBytecodeVersionErrors(@NotNull BindingContext bindingContext, @NotNull MessageCollector messageCollector) { + // TODO: a -X command line option + CompilerMessageSeverity severity = + "true".equals(System.getProperty("kotlin.jvm.disable.bytecode.version.error")) + ? CompilerMessageSeverity.WARNING + : CompilerMessageSeverity.ERROR; + + Collection locations = bindingContext.getKeys(IncompatibleClassTrackerImpl.BYTECODE_VERSION_ERRORS); + if (locations.isEmpty()) return; + + for (String location : locations) { + IncompatibleVersionErrorData data = + bindingContext.get(IncompatibleClassTrackerImpl.BYTECODE_VERSION_ERRORS, location); + assert data != null : "Value is missing for key in binding context: " + location; + reportIncompatibleBinaryVersion(messageCollector, data, JvmBytecodeBinaryVersion.INSTANCE, "bytecode", severity); } } @@ -266,7 +296,7 @@ public final class AnalyzerWithCompilerReport { public void analyzeAndReport(@NotNull Collection files, @NotNull Function0 analyzer) { analysisResult = analyzer.invoke(); reportSyntaxErrors(files); - List abiVersionErrors = getAbiVersionErrors(); + List abiVersionErrors = getAbiVersionErrors(); reportDiagnostics(analysisResult.getBindingContext().getDiagnostics(), messageCollector, !abiVersionErrors.isEmpty()); if (hasErrors()) { reportMetadataVersionErrors(abiVersionErrors); diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt index 191910d3666..714bc6ed38f 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt @@ -363,8 +363,15 @@ object KotlinToJVMBytecodeCompiler { AnalyzerWithCompilerReport.reportDiagnostics( FilteredJvmDiagnostics( generationState.collectedExtraJvmDiagnostics, - result.bindingContext.diagnostics), - environment.messageCollector()) + result.bindingContext.diagnostics + ), + environment.messageCollector() + ) + + AnalyzerWithCompilerReport.reportBytecodeVersionErrors( + generationState.extraJvmDiagnosticsTrace.bindingContext, environment.messageCollector() + ); + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() return generationState } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/IncompatibleVersionErrorData.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/IncompatibleVersionErrorData.kt new file mode 100644 index 00000000000..4263741fbcf --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/IncompatibleVersionErrorData.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2016 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.java.components + +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion + +data class IncompatibleVersionErrorData( + val actualVersion: BinaryVersion, + val filePath: String, + val classId: ClassId +) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.kt index eeb795c833a..71a201691ce 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.kt @@ -33,21 +33,15 @@ class TraceBasedErrorReporter(private val trace: BindingTrace) : ErrorReporter { private val LOG = Logger.getInstance(TraceBasedErrorReporter::class.java) @JvmField - val METADATA_VERSION_ERRORS: WritableSlice = Slices.createCollectiveSlice() + val METADATA_VERSION_ERRORS: WritableSlice = Slices.createCollectiveSlice() // TODO: MutableList is a workaround for KT-5792 Covariant types in Kotlin translated to wildcard types in Java @JvmField val INCOMPLETE_HIERARCHY: WritableSlice> = Slices.createCollectiveSlice() } - data class MetadataVersionErrorData( - val actualVersion: BinaryVersion, - val filePath: String, - val classId: ClassId - ) - override fun reportIncompatibleMetadataVersion(classId: ClassId, filePath: String, actualVersion: BinaryVersion) { - trace.record(METADATA_VERSION_ERRORS, filePath, MetadataVersionErrorData(actualVersion, filePath, classId)) + trace.record(METADATA_VERSION_ERRORS, filePath, IncompatibleVersionErrorData(actualVersion, filePath, classId)) } override fun reportIncompleteHierarchy(descriptor: ClassDescriptor, unresolvedSuperClasses: List) { diff --git a/compiler/testData/bytecodeVersion/simple/A.kt b/compiler/testData/bytecodeVersion/simple/A.kt new file mode 100644 index 00000000000..cff1e79a68c --- /dev/null +++ b/compiler/testData/bytecodeVersion/simple/A.kt @@ -0,0 +1,7 @@ +package library + +class A { + fun member() {} +} + +fun topLevel() {} diff --git a/compiler/testData/bytecodeVersion/simple/B.kt b/compiler/testData/bytecodeVersion/simple/B.kt new file mode 100644 index 00000000000..6e62c2ec327 --- /dev/null +++ b/compiler/testData/bytecodeVersion/simple/B.kt @@ -0,0 +1,6 @@ +import library.* + +fun test() { + A().member() + topLevel() +} diff --git a/compiler/testData/bytecodeVersion/simple/output.txt b/compiler/testData/bytecodeVersion/simple/output.txt new file mode 100644 index 00000000000..7992e8beab7 --- /dev/null +++ b/compiler/testData/bytecodeVersion/simple/output.txt @@ -0,0 +1,3 @@ +$TESTDATA_DIR$/library/A.class: error: class 'library/A' was compiled with an incompatible version of Kotlin. The binary version of its bytecode is 42.0.0, expected version is $ABI_VERSION$ +$TESTDATA_DIR$/library/AKt.class: error: class 'library/AKt' was compiled with an incompatible version of Kotlin. The binary version of its bytecode is 42.0.0, expected version is $ABI_VERSION$ +COMPILATION_ERROR diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java b/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java index c4773f617a8..de15f64738d 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java @@ -16,12 +16,12 @@ package org.jetbrains.kotlin.cli; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import kotlin.Charsets; +import kotlin.Pair; import kotlin.io.FilesKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.cli.common.CLICompiler; @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.cli.common.KotlinVersion; import org.jetbrains.kotlin.cli.js.K2JSCompiler; import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion; +import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.Tmpdir; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; @@ -58,7 +59,7 @@ public class CliBaseTest { try { System.setErr(new PrintStream(bytes)); ExitCode exitCode = CLICompiler.doMainNoExit(compiler, ArrayUtil.toStringArray(args)); - return Pair.create(bytes.toString("utf-8"), exitCode); + return new Pair(bytes.toString("utf-8"), exitCode); } catch (Exception e) { throw ExceptionUtilsKt.rethrow(e); @@ -70,9 +71,19 @@ public class CliBaseTest { @NotNull public static String getNormalizedCompilerOutput(@NotNull String pureOutput, @NotNull ExitCode exitCode, @NotNull String testDataDir) { + return getNormalizedCompilerOutput(pureOutput, exitCode, testDataDir, JvmMetadataVersion.INSTANCE); + } + + @NotNull + public static String getNormalizedCompilerOutput( + @NotNull String pureOutput, + @NotNull ExitCode exitCode, + @NotNull String testDataDir, + @NotNull BinaryVersion version + ) { String normalizedOutputWithoutExitCode = pureOutput .replace(new File(testDataDir).getAbsolutePath(), "$TESTDATA_DIR$") - .replace("expected version is " + JvmMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$") + .replace("expected version is " + version, "expected version is $ABI_VERSION$") .replace("\\", "/") .replace(KotlinVersion.VERSION, "$VERSION$"); @@ -95,7 +106,7 @@ public class CliBaseTest { Pair outputAndExitCode = executeCompilerGrabOutput(compiler, readArgs(testDataDir + "/" + testName.getMethodName() + ".args", testDataDir, tmpdir.getTmpDir().getPath())); - String actual = getNormalizedCompilerOutput(outputAndExitCode.first, outputAndExitCode.second, testDataDir); + String actual = getNormalizedCompilerOutput(outputAndExitCode.getFirst(), outputAndExitCode.getSecond(), testDataDir); KotlinTestUtils.assertEqualsToFile(new File(testDataDir + "/" + testName.getMethodName() + ".out"), actual); } diff --git a/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt b/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt new file mode 100644 index 00000000000..5fea49d688c --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2016 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.cli + +import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.testFramework.UsefulTestCase +import org.jetbrains.kotlin.cli.common.ExitCode +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import org.jetbrains.kotlin.codegen.AsmUtil +import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.org.objectweb.asm.* +import java.io.File + +class WrongBytecodeVersionTest : UsefulTestCase() { + private val incompatibleVersion = JvmBytecodeBinaryVersion(42, 0, 0).toArray() + + private fun doTest(relativeDirectory: String) { + val directory = KotlinTestUtils.getTestDataPathBase() + relativeDirectory + val librarySource = File(directory, "A.kt") + val usageSource = File(directory, "B.kt") + + val tmpdir = KotlinTestUtils.tmpDir(javaClass.simpleName) + LoadDescriptorUtil.compileKotlinToDirAndGetAnalysisResult( + listOf(librarySource), tmpdir, testRootDisposable, ConfigurationKind.ALL, false + ) + + for (classFile in File(tmpdir, "library").listFiles { file -> file.extension == JavaClassFileType.INSTANCE.defaultExtension }) { + changeVersionInBytecode(classFile) + } + + val (output, exitCode) = CliBaseTest.executeCompilerGrabOutput(K2JVMCompiler(), listOf( + usageSource.path, + "-classpath", tmpdir.path, + "-d", tmpdir.path + )) + + assertEquals("Compilation error expected", ExitCode.COMPILATION_ERROR, exitCode) + + val normalized = CliBaseTest.getNormalizedCompilerOutput(output, exitCode, tmpdir.path, JvmBytecodeBinaryVersion.INSTANCE) + + KotlinTestUtils.assertEqualsToFile(File(directory, "output.txt"), normalized) + } + + private fun changeVersionInBytecode(file: File) { + val writer = ClassWriter(0) + ClassReader(file.inputStream()).accept(object : ClassVisitor(Opcodes.ASM5, writer) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + val superVisitor = super.visitAnnotation(desc, visible)!! + if (desc == AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.METADATA)) { + return object : AnnotationVisitor(Opcodes.ASM5, superVisitor) { + override fun visit(name: String?, value: Any) { + val updatedValue: Any = + if (name == JvmAnnotationNames.BYTECODE_VERSION_FIELD_NAME) incompatibleVersion + else value + super.visit(name, updatedValue) + } + } + } + return superVisitor + } + }, 0) + file.writeBytes(writer.toByteArray()) + } + + fun testSimple() { + doTest("/bytecodeVersion/simple") + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index c75894603d0..960099ff801 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -18,9 +18,9 @@ package org.jetbrains.kotlin.jvm.compiler; import com.google.common.collect.Iterables; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; +import kotlin.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cli.CliBaseTest; @@ -75,6 +75,11 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { ); } + @NotNull + private String normalizeOutput(@NotNull Pair output) { + return CliBaseTest.getNormalizedCompilerOutput(output.getFirst(), output.getSecond(), getTestDataDirectory().getPath()); + } + private void doTestWithTxt(@NotNull File... extraClassPath) throws Exception { PackageViewDescriptor packageView = analyzeFileToPackageView(extraClassPath); @@ -154,25 +159,23 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { File libSrc = new File(getTestDataDirectory(), "library/test/lib.kt"); - Pair pair1 = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( + Pair outputLib = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( libSrc.getPath(), "-classpath", tmpdir.getPath(), "-d", tmpdir.getPath() )); - String outputLib = CliBaseTest.getNormalizedCompilerOutput(pair1.first, pair1.second, getTestDataDirectory().getPath()); - File mainSrc = new File(getTestDataDirectory(), "main.kt"); - Pair pair2 = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( + Pair outputMain = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( mainSrc.getPath(), "-classpath", tmpdir.getPath(), "-d", tmpdir.getPath() )); - String outputMain = CliBaseTest.getNormalizedCompilerOutput(pair2.first, pair2.second, getTestDataDirectory().getPath()); - - KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), outputLib + "\n" + outputMain); + KotlinTestUtils.assertEqualsToFile( + new File(getTestDataDirectory(), "output.txt"), normalizeOutput(outputLib) + "\n" + normalizeOutput(outputMain) + ); } public void testDuplicateObjectInBinaryAndSources() throws Exception { @@ -237,14 +240,13 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { File source = new File(getTestDataDirectory(), "source.kt"); - Pair pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( + Pair output = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( source.getPath(), "-classpath", tmpdir.getPath(), "-d", tmpdir.getPath() )); - String output = CliBaseTest.getNormalizedCompilerOutput(pair.first, pair.second, getTestDataDirectory().getPath()); - KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), output); + KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output)); } public void testIncompleteHierarchyInKotlin() throws Exception { @@ -254,14 +256,13 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { File source = new File(getTestDataDirectory(), "source.kt"); - Pair pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( + Pair output = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( source.getPath(), "-classpath", library.getPath(), "-d", tmpdir.getPath() )); - String output = CliBaseTest.getNormalizedCompilerOutput(pair.first, pair.second, getTestDataDirectory().getPath()); - KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), output); + KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output)); } /*test source mapping generation when source info is absent*/