Write "pre-release" flag to class files, do not allow usages in release
This commit is contained in:
+1
-1
@@ -57,7 +57,7 @@ object JvmPlatformConfigurator : PlatformConfigurator(
|
||||
ProtectedInSuperClassCompanionCallChecker(),
|
||||
UnsupportedSyntheticCallableReferenceChecker(),
|
||||
SuperCallWithDefaultArgumentsChecker(),
|
||||
MissingDependencyClassChecker(),
|
||||
MissingDependencyClassChecker,
|
||||
ProtectedSyntheticExtensionCallChecker,
|
||||
ReifiedTypeParameterSubstitutionChecker()
|
||||
),
|
||||
|
||||
@@ -82,6 +82,7 @@ public interface Errors {
|
||||
DiagnosticFactory2<PsiElement, String, String> API_NOT_AVAILABLE = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
DiagnosticFactory1<PsiElement, FqName> MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, FqName> 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
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+20
-10
@@ -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<ClassId> {
|
||||
val result: MutableSet<ClassId> = newLinkedHashSetWithExpectedSize(1)
|
||||
private fun collectDiagnostics(reportOn: PsiElement, descriptor: CallableDescriptor): Set<Diagnostic> {
|
||||
val result: MutableSet<Diagnostic> = 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
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package a
|
||||
|
||||
open class A {
|
||||
class Nested
|
||||
}
|
||||
+16
@@ -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
|
||||
+10
@@ -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() {}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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<String, ExitCode> 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);
|
||||
|
||||
+5
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -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)
|
||||
|
||||
+7
-2
@@ -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"
|
||||
}
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user