From 4e99349f1fa8d19484a320321b3d098c91931ea8 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 7 Dec 2016 14:22:29 +0300 Subject: [PATCH] Write "pre-release" flag to class files, do not allow usages in release --- .../jvm/platform/JvmPlatformConfigurator.kt | 2 +- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../checkers/MissingDependencyClassChecker.kt | 30 ++++++++----- .../library/a.kt | 5 +++ .../output.txt | 16 +++++++ .../source.kt | 10 +++++ .../kotlin/cli/WrongBytecodeVersionTest.kt | 45 ++++++++++--------- ...ompileKotlinAgainstCustomBinariesTest.java | 31 +++++++++++++ .../kotlin/DeserializedDescriptorResolver.kt | 6 ++- .../load/kotlin/JvmPackagePartSource.kt | 3 ++ .../kotlin/KotlinJvmBinarySourceElement.kt | 9 +++- .../DeserializedMemberDescriptor.kt | 2 + 13 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/library/a.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 3c68892441c..afea86451d0 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -57,7 +57,7 @@ object JvmPlatformConfigurator : PlatformConfigurator( ProtectedInSuperClassCompanionCallChecker(), UnsupportedSyntheticCallableReferenceChecker(), SuperCallWithDefaultArgumentsChecker(), - MissingDependencyClassChecker(), + MissingDependencyClassChecker, ProtectedSyntheticExtensionCallChecker, ReifiedTypeParameterSubstitutionChecker() ), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index c2b9c740b09..fbd2c5f13c2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -82,6 +82,7 @@ public interface Errors { DiagnosticFactory2 API_NOT_AVAILABLE = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 PRE_RELEASE_CLASS = DiagnosticFactory1.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 4f26bd1e153..2e3b2280b25 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -320,6 +320,7 @@ public class DefaultErrorMessages { MAP.put(API_NOT_AVAILABLE, "This declaration is only available since Kotlin {0} and cannot be used with the specified API version {1}", STRING, STRING); 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(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 fa6d1a03092..9d596565baf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MissingDependencyClassChecker.kt @@ -19,32 +19,42 @@ package org.jetbrains.kotlin.resolve.checkers import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.name.ClassId +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.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext import org.jetbrains.kotlin.resolve.calls.checkers.isComputingDeferredType import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize -class MissingDependencyClassChecker : CallChecker { +object MissingDependencyClassChecker : CallChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { - for (classId in collectNotFoundClasses(resolvedCall.resultingDescriptor)) { - context.trace.report(Errors.MISSING_DEPENDENCY_CLASS.on(reportOn, classId.asSingleFqName())) + for (diagnostic in collectDiagnostics(reportOn, resolvedCall.resultingDescriptor)) { + context.trace.report(diagnostic) } } - private fun collectNotFoundClasses(descriptor: CallableDescriptor): Set { - val result: MutableSet = newLinkedHashSetWithExpectedSize(1) + private fun collectDiagnostics(reportOn: PsiElement, descriptor: CallableDescriptor): Set { + val result: MutableSet = newLinkedHashSetWithExpectedSize(1) fun consider(classDescriptor: ClassDescriptor) { if (classDescriptor is NotFoundClasses.MockClassDescriptor) { - result.add(classDescriptor.classId!!) + result.add(MISSING_DEPENDENCY_CLASS.on(reportOn, classDescriptor.fqNameSafe)) return } + + val source = classDescriptor.source + if (source is DeserializedContainerSource && source.isPreReleaseInvisible) { + // TODO: if at least one PRE_RELEASE_CLASS is reported, display a hint to disable the diagnostic + result.add(PRE_RELEASE_CLASS.on(reportOn, classDescriptor.fqNameSafe)) + return + } + (classDescriptor.containingDeclaration as? ClassDescriptor)?.let(::consider) } @@ -58,6 +68,6 @@ class MissingDependencyClassChecker : CallChecker { descriptor.extensionReceiverParameter?.value?.type?.let(::consider) descriptor.valueParameters.forEach { consider(it.type) } - return result.orEmpty() + return result } } diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/library/a.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/library/a.kt new file mode 100644 index 00000000000..11e5dc8eda8 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/library/a.kt @@ -0,0 +1,5 @@ +package a + +open class A { + class Nested +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/output.txt new file mode 100644 index 00000000000..5add492b744 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/output.txt @@ -0,0 +1,16 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt:6:23: error: class 'a.A' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler + val constructor = A() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt:7:20: error: class 'a.A.Nested' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler + val nested = A.Nested() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt:8:22: error: class 'a.A' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler + val methodCall = param.method() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt:8:28: error: unresolved reference: method + val methodCall = param.method() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt:9:30: error: class 'a.A' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler + val supertype = object : A() {} + ^ +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt new file mode 100644 index 00000000000..6ecf1012539 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/releaseCompilerAgainstPreReleaseLibrary/source.kt @@ -0,0 +1,10 @@ +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() {} +} diff --git a/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt b/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt index 55c23ae1a57..1e0fba1159e 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/cli/WrongBytecodeVersionTest.kt @@ -41,7 +41,9 @@ class WrongBytecodeVersionTest : KtUsefulTestCase() { LoadDescriptorUtil.compileKotlinToDirAndGetModule(listOf(librarySource), tmpdir, environment) for (classFile in File(tmpdir, "library").listFiles { file -> file.extension == JavaClassFileType.INSTANCE.defaultExtension }) { - changeVersionInBytecode(classFile) + classFile.writeBytes(transformMetadataInClassFile(classFile.readBytes()) { name, _ -> + if (name == JvmAnnotationNames.BYTECODE_VERSION_FIELD_NAME) incompatibleVersion else null + }) } val (output, exitCode) = AbstractCliTest.executeCompilerGrabOutput(K2JVMCompiler(), listOf( @@ -57,28 +59,27 @@ class WrongBytecodeVersionTest : KtUsefulTestCase() { 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 == JvmAnnotationNames.METADATA_DESC) { - 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") } + + companion object { + fun transformMetadataInClassFile(bytes: ByteArray, transform: (fieldName: String, value: Any?) -> Any?): ByteArray { + val writer = ClassWriter(0) + ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5, writer) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor { + val superVisitor = super.visitAnnotation(desc, visible) + if (desc == JvmAnnotationNames.METADATA_DESC) { + return object : AnnotationVisitor(Opcodes.ASM5, superVisitor) { + override fun visit(name: String, value: Any) { + super.visit(name, transform(name, value) ?: value) + } + } + } + return superVisitor + } + }, 0) + return writer.toByteArray() + } + } } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index 8fd3537c36f..f6b1ef76bf6 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -34,8 +34,10 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; 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.kotlin.DeserializedDescriptorResolver; import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorUtils; @@ -226,6 +228,31 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output)); } + @SuppressWarnings("deprecation") + private void doTestPreReleaseKotlinLibrary(@NotNull String libraryName) throws Exception { + // Compiles the library with the "pre-release" flag, then compiles a usage of this library in the release mode + + File library; + try { + DeserializedDescriptorResolver.Companion.setIS_PRE_RELEASE(true); + library = compileLibrary(libraryName); + } + finally { + DeserializedDescriptorResolver.Companion.setIS_PRE_RELEASE(KotlinCompilerVersion.IS_PRE_RELEASE); + } + + Pair output; + try { + DeserializedDescriptorResolver.Companion.setIS_PRE_RELEASE(false); + output = compileKotlin("source.kt", tmpdir, library); + } + finally { + DeserializedDescriptorResolver.Companion.setIS_PRE_RELEASE(KotlinCompilerVersion.IS_PRE_RELEASE); + } + + KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output)); + } + // ------------------------------------------------------------------------------ public void testRawTypes() throws Exception { @@ -337,6 +364,10 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { doTestBrokenJavaLibrary("library", "test/A$Anno.class"); } + public void testReleaseCompilerAgainstPreReleaseLibrary() throws Exception { + doTestPreReleaseKotlinLibrary("library"); + } + /*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/kotlin/DeserializedDescriptorResolver.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/DeserializedDescriptorResolver.kt index 03e868d196c..a578a875f24 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 @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.load.kotlin +import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader @@ -49,7 +50,7 @@ class DeserializedDescriptorResolver(private val errorReporter: ErrorReporter) { val classData = parseProto(kotlinClass) { JvmProtoBufUtil.readClassDataFrom(data, strings) } - val sourceElement = KotlinJvmBinarySourceElement(kotlinClass) + val sourceElement = KotlinJvmBinarySourceElement(kotlinClass, !IS_PRE_RELEASE && kotlinClass.classHeader.isPreRelease) return ClassDataWithSource(classData, sourceElement) } @@ -92,5 +93,8 @@ class DeserializedDescriptorResolver(private val errorReporter: ErrorReporter) { private val KOTLIN_FILE_FACADE_OR_MULTIFILE_CLASS_PART = setOf(KotlinClassHeader.Kind.FILE_FACADE, KotlinClassHeader.Kind.MULTIFILE_CLASS_PART) + + var IS_PRE_RELEASE = KotlinCompilerVersion.IS_PRE_RELEASE + @Deprecated("Should only be used in tests") set } } 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 3544225630f..0c95aeb192e 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 @@ -30,6 +30,9 @@ class JvmPackagePartSource(val className: JvmClassName, val facadeClassName: Jvm } ) + // TODO + override val isPreReleaseInvisible: Boolean get() = false + val simpleName: Name get() = Name.identifier(className.internalName.substringAfterLast('/')) val classId: ClassId get() = ClassId(className.packageFqName, simpleName) 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 df99849bf12..95aeeea4d49 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,8 +18,13 @@ package org.jetbrains.kotlin.load.kotlin import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.descriptors.SourceFile +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource -class KotlinJvmBinarySourceElement(val binaryClass: KotlinJvmBinaryClass) : SourceElement { - override fun toString() = "${javaClass.simpleName}: $binaryClass" +class KotlinJvmBinarySourceElement( + val binaryClass: KotlinJvmBinaryClass, + override val isPreReleaseInvisible: Boolean = false +) : DeserializedContainerSource { override fun getContainingFile(): SourceFile = SourceFile.NO_SOURCE_FILE + + override fun toString() = "${javaClass.simpleName}: $binaryClass" } 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 feb15fb2b6f..51a3b810f86 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 @@ -42,6 +42,8 @@ interface DeserializedMemberDescriptor : MemberDescriptor { } interface DeserializedContainerSource : SourceElement { + // 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 } interface DeserializedCallableMemberDescriptor : DeserializedMemberDescriptor, CallableMemberDescriptor