From 94509bdb4e44efe2249d69eab6290c3ad03d6039 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 28 May 2020 16:26:11 +0300 Subject: [PATCH] KT-39228 Fix inliner when latest 1.4 compiler used with 1.3 stdlib Since 1.4.0-dev-8774, we mangle functions returning inline class values, including functions with return type 'kotlin.Result'. This causes incompatibility when 1.4 compiler is used with 1.3 (or just some pre-1.4.0-dev-8774) standard library. Also, write "message from the future" on functions returning inline class values indicating that they can be used since compiler version 1.4 (otherwise 1.3 compiler using 1.4 stdlib would fail to find some @InlineOnly functions such as 'Result.success' and 'Result.failure'). --- .../kotlin/codegen/inline/InlineCodegen.kt | 28 ++++++++++++- .../serialization/JvmSerializerExtension.kt | 28 ++++++++++--- .../kotlin/codegen/state/GenerationState.kt | 1 + .../codegen/state/inlineClassManglingUtils.kt | 9 ++--- .../jvm/checkers/declarationCheckers.kt | 2 +- .../inlineClassReturnTypeMangled.kt | 8 ++++ .../AbstractVersionRequirementTest.kt | 40 +++++++++++++------ .../JvmVersionRequirementTest.kt | 11 +++++ .../resolve/jvm/inlineClassManglingRules.kt | 7 +++- .../tools/kotlinp/testData/Annotations.txt | 8 ++++ 10 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 compiler/testData/versionRequirement/inlineClassReturnTypeMangled.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index 38d12ebb8bd..52461e77bf2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructors import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils @@ -24,7 +25,9 @@ import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.isInlineOnly +import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource import org.jetbrains.kotlin.types.KotlinType @@ -629,8 +632,7 @@ abstract class InlineCodegen( ?: throw IllegalStateException("Couldn't find declaration file for $containerId") } - val methodNode = - getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, AsmUtil.asmTypeByClassId(containerId)) ?: return null + val methodNode = getMethodNodeInner(containerId, bytes, asmMethod, callableDescriptor) ?: return null // KLUDGE: Inline suspend function built with compiler version less than 1.1.4/1.2-M1 did not contain proper // before/after suspension point marks, so we detect those functions here and insert the corresponding marks @@ -641,6 +643,28 @@ abstract class InlineCodegen( return methodNode } + private fun getMethodNodeInner( + containerId: ClassId, + bytes: ByteArray, + asmMethod: Method, + callableDescriptor: CallableMemberDescriptor + ): SMAPAndMethodNode? { + val classType = AsmUtil.asmTypeByClassId(containerId) + var methodNode = getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, classType) + if (methodNode == null && requiresFunctionNameManglingForReturnType(callableDescriptor)) { + val nameWithoutManglingSuffix = asmMethod.name.stripManglingSuffixOrNull() + if (nameWithoutManglingSuffix != null) { + methodNode = getMethodNode(bytes, nameWithoutManglingSuffix, asmMethod.descriptor, classType) + } + } + return methodNode + } + + private fun String.stripManglingSuffixOrNull(): String? { + val dashIndex = indexOf('-') + return if (dashIndex < 0) null else substring(0, dashIndex) + } + private fun isBuiltInArrayIntrinsic(callableDescriptor: CallableMemberDescriptor): Boolean { if (callableDescriptor is FictitiousArrayConstructor) return true val name = callableDescriptor.name.asString() diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/serialization/JvmSerializerExtension.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/serialization/JvmSerializerExtension.kt index 1c55e80c49a..a3290f8cc42 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/serialization/JvmSerializerExtension.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/serialization/JvmSerializerExtension.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi import org.jetbrains.kotlin.resolve.descriptorUtil.nonSourceAnnotations import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmDefaultAnnotation +import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType import org.jetbrains.kotlin.serialization.DescriptorSerializer import org.jetbrains.kotlin.serialization.DescriptorSerializer.Companion.writeVersionRequirement import org.jetbrains.kotlin.serialization.SerializerExtension @@ -54,6 +55,7 @@ class JvmSerializerExtension @JvmOverloads constructor( private val languageVersionSettings = state.languageVersionSettings private val isParamAssertionsDisabled = state.isParamAssertionsDisabled private val unifiedNullChecks = state.unifiedNullChecks + private val functionsWithInlineClassReturnTypesMangled = state.functionsWithInlineClassReturnTypesMangled override val metadataVersion = state.metadataVersion private val jvmDefaultMode = state.jvmDefaultMode @@ -75,17 +77,17 @@ class JvmSerializerExtension @JvmOverloads constructor( } override fun serializeClass( - descriptor: ClassDescriptor, - proto: ProtoBuf.Class.Builder, - versionRequirementTable: MutableVersionRequirementTable, - childSerializer: DescriptorSerializer + descriptor: ClassDescriptor, + proto: ProtoBuf.Class.Builder, + versionRequirementTable: MutableVersionRequirementTable, + childSerializer: DescriptorSerializer ) { if (moduleName != JvmProtoBufUtil.DEFAULT_MODULE_NAME) { proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName)) } //TODO: support local delegated properties in new defaults scheme val containerAsmType = - if (DescriptorUtils.isInterface(descriptor)) typeMapper.mapDefaultImpls(descriptor) else typeMapper.mapClass(descriptor) + if (isInterface(descriptor)) typeMapper.mapDefaultImpls(descriptor) else typeMapper.mapClass(descriptor) writeLocalProperties(proto, containerAsmType, JvmProtoBuf.classLocalVariable) writeVersionRequirementForJvmDefaultIfNeeded(descriptor, proto, versionRequirementTable) @@ -198,6 +200,10 @@ class JvmSerializerExtension @JvmOverloads constructor( if (descriptor.needsInlineParameterNullCheckRequirement()) { versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement) } + + if (requiresFunctionNameManglingForReturnType(descriptor)) { + versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement) + } } private fun MutableVersionRequirementTable.writeInlineParameterNullCheckRequirement(add: (Int) -> Unit) { @@ -208,6 +214,12 @@ class JvmSerializerExtension @JvmOverloads constructor( } } + private fun MutableVersionRequirementTable.writeFunctionNameManglingForReturnTypeRequirement(add: (Int) -> Unit) { + if (functionsWithInlineClassReturnTypesMangled) { + add(writeVersionRequirement(1, 4, 0, ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION, this)) + } + } + private fun FunctionDescriptor.needsInlineParameterNullCheckRequirement(): Boolean = isInline && !isSuspend && !isParamAssertionsDisabled && !Visibilities.isPrivate(visibility) && @@ -253,6 +265,10 @@ class JvmSerializerExtension @JvmOverloads constructor( if (getter?.needsInlineParameterNullCheckRequirement() == true || setter?.needsInlineParameterNullCheckRequirement() == true) { versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement) } + + if (requiresFunctionNameManglingForReturnType(descriptor)) { + versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement) + } } private fun PropertyDescriptor.isJvmFieldPropertyInInterfaceCompanion(): Boolean { @@ -262,7 +278,7 @@ class JvmSerializerExtension @JvmOverloads constructor( if (!DescriptorUtils.isCompanionObject(container)) return false val grandParent = (container as ClassDescriptor).containingDeclaration - return DescriptorUtils.isInterface(grandParent) || DescriptorUtils.isAnnotationClass(grandParent) + return isInterface(grandParent) || DescriptorUtils.isAnnotationClass(grandParent) } override fun serializeErrorType(type: KotlinType, builder: ProtoBuf.Type.Builder) { 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 e13118c2e24..9bbb1bacfe5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -251,6 +251,7 @@ class GenerationState private constructor( val isInlineDisabled: Boolean = configuration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE) val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE) val unifiedNullChecks: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4 + val functionsWithInlineClassReturnTypesMangled: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4 val rootContext: CodegenContext<*> = RootContext(this) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt index 09e0b1d0f7d..de2f1272cf8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt @@ -9,7 +9,6 @@ import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspend import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe -import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForParameterTypes import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType import org.jetbrains.kotlin.types.KotlinType @@ -31,11 +30,9 @@ fun getManglingSuffixBasedOnKotlinSignature(descriptor: CallableMemberDescriptor // If a class member function returns inline class value, mangle its name. // NB here function can be a suspend function JVM view with return type replaced with 'Any', // should unwrap it and take original return type instead. - if (descriptor.containingDeclaration is ClassDescriptor) { - val returnType = descriptor.unwrapInitialDescriptorForSuspendFunction().returnType!! - if (requiresFunctionNameManglingForReturnType(returnType)) { - return "-" + md5base64(":" + getSignatureElementForMangling(returnType)) - } + val unwrappedDescriptor = descriptor.unwrapInitialDescriptorForSuspendFunction() + if (requiresFunctionNameManglingForReturnType(unwrappedDescriptor)) { + return "-" + md5base64(":" + getSignatureElementForMangling(unwrappedDescriptor.returnType!!)) } return null } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt index 3854b6335ee..4ee2beb2eed 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt @@ -151,7 +151,7 @@ class JvmNameAnnotationChecker : DeclarationChecker { diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) } else if (descriptor.containingDeclaration.isInlineClassThatRequiresMangling() || requiresFunctionNameManglingForParameterTypes(descriptor.valueParameters.map { it.type }) || - descriptor.containingDeclaration is ClassDescriptor && requiresFunctionNameManglingForReturnType(descriptor.returnType) + requiresFunctionNameManglingForReturnType(descriptor) ) { diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) } diff --git a/compiler/testData/versionRequirement/inlineClassReturnTypeMangled.kt b/compiler/testData/versionRequirement/inlineClassReturnTypeMangled.kt new file mode 100644 index 00000000000..4a61bffc516 --- /dev/null +++ b/compiler/testData/versionRequirement/inlineClassReturnTypeMangled.kt @@ -0,0 +1,8 @@ +package test + +inline class IC(val x: Int) + +class C { + fun returnsInlineClassType(): IC = IC(42) + val propertyOfInlineClassType: IC get() = IC(42) +} diff --git a/compiler/tests/org/jetbrains/kotlin/serialization/AbstractVersionRequirementTest.kt b/compiler/tests/org/jetbrains/kotlin/serialization/AbstractVersionRequirementTest.kt index 1bdac9ccf45..f4d261a09a6 100644 --- a/compiler/tests/org/jetbrains/kotlin/serialization/AbstractVersionRequirementTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/serialization/AbstractVersionRequirementTest.kt @@ -33,33 +33,49 @@ abstract class AbstractVersionRequirementTest : TestCaseWithTmpdir() { fqNamesWithRequirements: List, fqNamesWithoutRequirement: List = emptyList() ) { - compileFiles(listOf(File("compiler/testData/versionRequirement/${getTestName(true)}.kt")), tmpdir, customLanguageVersion, analysisFlags) + compileFiles( + listOf(File("compiler/testData/versionRequirement/${getTestName(true)}.kt")), + tmpdir, customLanguageVersion, analysisFlags + ) val module = loadModule(tmpdir) for (fqName in fqNamesWithRequirements) { val descriptor = module.findUnambiguousDescriptorByFqName(fqName) - val requirement = extractRequirement(descriptor) ?: throw AssertionError("No VersionRequirement for $descriptor") + val requirements = extractRequirement(descriptor) + if (requirements.isEmpty()) throw AssertionError("No VersionRequirement for $descriptor") - assertEquals("Incorrect version for $fqName", expectedVersionRequirement, requirement.version) - assertEquals("Incorrect level for $fqName", expectedLevel, requirement.level) - assertEquals("Incorrect message for $fqName", expectedMessage, requirement.message) - assertEquals("Incorrect versionKind for $fqName", expectedVersionKind, requirement.kind) - assertEquals("Incorrect errorCode for $fqName", expectedErrorCode, requirement.errorCode) + requirements.firstOrNull { + expectedVersionRequirement == it.version && + expectedLevel == it.level && + expectedMessage == it.message && + expectedVersionKind == it.kind && + expectedErrorCode == it.errorCode + } + ?: throw AssertionError( + "Version requirement not found:\n" + + "expectedVersionRequirement=" + expectedVersionRequirement + + "; expectedLevel=" + expectedLevel + + "; expectedMessage=" + expectedMessage + + "; expectedVersionKind=" + expectedVersionKind + + "; expectedErrorCode=" + expectedErrorCode + + "\nActual requirements:\n" + + requirements.joinToString(separator = "\n") { it.toString() } + ) } for (fqName in fqNamesWithoutRequirement) { val descriptor = module.findUnambiguousDescriptorByFqName(fqName) - val requirenment = extractRequirement(descriptor) - assertNull("Expecting absence of any requirements for $fqName, but `$requirenment`", requirenment) + val requirement = extractRequirement(descriptor) + assertTrue("Expecting absence of any requirements for $fqName, but `$requirement`", requirement.isEmpty()) } } - private fun extractRequirement(descriptor: DeclarationDescriptor): VersionRequirement? { + private fun extractRequirement(descriptor: DeclarationDescriptor): List { return when (descriptor) { - is DeserializedMemberDescriptor -> descriptor.versionRequirements.singleOrNull() - is DeserializedClassDescriptor -> descriptor.versionRequirements.singleOrNull() + is DeserializedMemberDescriptor -> descriptor.versionRequirements + is DeserializedClassDescriptor -> descriptor.versionRequirements else -> throw AssertionError("Unknown descriptor: $descriptor") } } diff --git a/compiler/tests/org/jetbrains/kotlin/serialization/JvmVersionRequirementTest.kt b/compiler/tests/org/jetbrains/kotlin/serialization/JvmVersionRequirementTest.kt index c421ab38907..65bea2d6978 100644 --- a/compiler/tests/org/jetbrains/kotlin/serialization/JvmVersionRequirementTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/serialization/JvmVersionRequirementTest.kt @@ -128,6 +128,17 @@ class JvmVersionRequirementTest : AbstractVersionRequirementTest() { ) } + fun testInlineClassReturnTypeMangled() { + doTest( + VersionRequirement.Version(1, 4, 0), DeprecationLevel.ERROR, null, COMPILER_VERSION, null, + fqNamesWithRequirements = listOf( + "test.C.returnsInlineClassType", + "test.C.propertyOfInlineClassType" + ), + customLanguageVersion = LanguageVersion.KOTLIN_1_4 + ) + } + fun testSuspendFun_1_2() { doTest( VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, LANGUAGE_VERSION, null, diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/resolve/jvm/inlineClassManglingRules.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/resolve/jvm/inlineClassManglingRules.kt index 3179b231efb..1088d8ac532 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/resolve/jvm/inlineClassManglingRules.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/resolve/jvm/inlineClassManglingRules.kt @@ -29,8 +29,11 @@ fun requiresFunctionNameManglingForParameterTypes(valueParameterTypes: List