Report error on generating calls to binary incompatible classes

This commit is contained in:
Alexander Udalov
2016-01-13 22:11:23 +03:00
parent 4bd1d064bb
commit e17cd12c3c
15 changed files with 315 additions and 49 deletions
@@ -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)
}
@@ -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() {
@@ -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)
@@ -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<String>()
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<String, IncompatibleVersionErrorData> = Slices.createCollectiveSlice()
}
}
@@ -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<ValueParameterDescriptor> valueParameters) {
public JvmMethodSignature mapSignature(
@NotNull FunctionDescriptor f,
@NotNull OwnerKind kind,
@NotNull List<ValueParameterDescriptor> 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,
@@ -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<MetadataVersionErrorData> getAbiVersionErrors() {
private List<IncompatibleVersionErrorData> getAbiVersionErrors() {
assert analysisResult != null;
BindingContext bindingContext = analysisResult.getBindingContext();
Collection<String> errorClasses = bindingContext.getKeys(TraceBasedErrorReporter.METADATA_VERSION_ERRORS);
List<MetadataVersionErrorData> result = new ArrayList<MetadataVersionErrorData>(errorClasses.size());
List<IncompatibleVersionErrorData> result = new ArrayList<IncompatibleVersionErrorData>(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<MetadataVersionErrorData> 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<IncompatibleVersionErrorData> 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<String> 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<KtFile> files, @NotNull Function0<AnalysisResult> analyzer) {
analysisResult = analyzer.invoke();
reportSyntaxErrors(files);
List<MetadataVersionErrorData> abiVersionErrors = getAbiVersionErrors();
List<IncompatibleVersionErrorData> abiVersionErrors = getAbiVersionErrors();
reportDiagnostics(analysisResult.getBindingContext().getDiagnostics(), messageCollector, !abiVersionErrors.isEmpty());
if (hasErrors()) {
reportMetadataVersionErrors(abiVersionErrors);
@@ -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
}
@@ -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
)
@@ -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<String, MetadataVersionErrorData> = Slices.createCollectiveSlice()
val METADATA_VERSION_ERRORS: WritableSlice<String, IncompatibleVersionErrorData> = 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<ClassDescriptor, MutableList<String>> = 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<String>) {
+7
View File
@@ -0,0 +1,7 @@
package library
class A {
fun member() {}
}
fun topLevel() {}
+6
View File
@@ -0,0 +1,6 @@
import library.*
fun test() {
A().member()
topLevel()
}
+3
View File
@@ -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
@@ -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<String, ExitCode>(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<String, ExitCode> 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);
}
@@ -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")
}
}
@@ -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<String, ExitCode> 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<String, ExitCode> pair1 = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList(
Pair<String, ExitCode> 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<String, ExitCode> pair2 = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList(
Pair<String, ExitCode> 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<String, ExitCode> pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList(
Pair<String, ExitCode> 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<String, ExitCode> pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList(
Pair<String, ExitCode> 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*/