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').
This commit is contained in:
Dmitry Petrov
2020-05-28 16:26:11 +03:00
parent ffdab473e2
commit 94509bdb4e
10 changed files with 113 additions and 29 deletions
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructors
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils 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.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.inline.isInlineOnly 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.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
@@ -629,8 +632,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
?: throw IllegalStateException("Couldn't find declaration file for $containerId") ?: throw IllegalStateException("Couldn't find declaration file for $containerId")
} }
val methodNode = val methodNode = getMethodNodeInner(containerId, bytes, asmMethod, callableDescriptor) ?: return null
getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, AsmUtil.asmTypeByClassId(containerId)) ?: return null
// KLUDGE: Inline suspend function built with compiler version less than 1.1.4/1.2-M1 did not contain proper // 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 // before/after suspension point marks, so we detect those functions here and insert the corresponding marks
@@ -641,6 +643,28 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
return methodNode 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 { private fun isBuiltInArrayIntrinsic(callableDescriptor: CallableMemberDescriptor): Boolean {
if (callableDescriptor is FictitiousArrayConstructor) return true if (callableDescriptor is FictitiousArrayConstructor) return true
val name = callableDescriptor.name.asString() val name = callableDescriptor.name.asString()
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi
import org.jetbrains.kotlin.resolve.descriptorUtil.nonSourceAnnotations import org.jetbrains.kotlin.resolve.descriptorUtil.nonSourceAnnotations
import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmDefaultAnnotation 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
import org.jetbrains.kotlin.serialization.DescriptorSerializer.Companion.writeVersionRequirement import org.jetbrains.kotlin.serialization.DescriptorSerializer.Companion.writeVersionRequirement
import org.jetbrains.kotlin.serialization.SerializerExtension import org.jetbrains.kotlin.serialization.SerializerExtension
@@ -54,6 +55,7 @@ class JvmSerializerExtension @JvmOverloads constructor(
private val languageVersionSettings = state.languageVersionSettings private val languageVersionSettings = state.languageVersionSettings
private val isParamAssertionsDisabled = state.isParamAssertionsDisabled private val isParamAssertionsDisabled = state.isParamAssertionsDisabled
private val unifiedNullChecks = state.unifiedNullChecks private val unifiedNullChecks = state.unifiedNullChecks
private val functionsWithInlineClassReturnTypesMangled = state.functionsWithInlineClassReturnTypesMangled
override val metadataVersion = state.metadataVersion override val metadataVersion = state.metadataVersion
private val jvmDefaultMode = state.jvmDefaultMode private val jvmDefaultMode = state.jvmDefaultMode
@@ -75,17 +77,17 @@ class JvmSerializerExtension @JvmOverloads constructor(
} }
override fun serializeClass( override fun serializeClass(
descriptor: ClassDescriptor, descriptor: ClassDescriptor,
proto: ProtoBuf.Class.Builder, proto: ProtoBuf.Class.Builder,
versionRequirementTable: MutableVersionRequirementTable, versionRequirementTable: MutableVersionRequirementTable,
childSerializer: DescriptorSerializer childSerializer: DescriptorSerializer
) { ) {
if (moduleName != JvmProtoBufUtil.DEFAULT_MODULE_NAME) { if (moduleName != JvmProtoBufUtil.DEFAULT_MODULE_NAME) {
proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName)) proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName))
} }
//TODO: support local delegated properties in new defaults scheme //TODO: support local delegated properties in new defaults scheme
val containerAsmType = 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) writeLocalProperties(proto, containerAsmType, JvmProtoBuf.classLocalVariable)
writeVersionRequirementForJvmDefaultIfNeeded(descriptor, proto, versionRequirementTable) writeVersionRequirementForJvmDefaultIfNeeded(descriptor, proto, versionRequirementTable)
@@ -198,6 +200,10 @@ class JvmSerializerExtension @JvmOverloads constructor(
if (descriptor.needsInlineParameterNullCheckRequirement()) { if (descriptor.needsInlineParameterNullCheckRequirement()) {
versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement) versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement)
} }
if (requiresFunctionNameManglingForReturnType(descriptor)) {
versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement)
}
} }
private fun MutableVersionRequirementTable.writeInlineParameterNullCheckRequirement(add: (Int) -> Unit) { 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 = private fun FunctionDescriptor.needsInlineParameterNullCheckRequirement(): Boolean =
isInline && !isSuspend && !isParamAssertionsDisabled && isInline && !isSuspend && !isParamAssertionsDisabled &&
!Visibilities.isPrivate(visibility) && !Visibilities.isPrivate(visibility) &&
@@ -253,6 +265,10 @@ class JvmSerializerExtension @JvmOverloads constructor(
if (getter?.needsInlineParameterNullCheckRequirement() == true || setter?.needsInlineParameterNullCheckRequirement() == true) { if (getter?.needsInlineParameterNullCheckRequirement() == true || setter?.needsInlineParameterNullCheckRequirement() == true) {
versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement) versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement)
} }
if (requiresFunctionNameManglingForReturnType(descriptor)) {
versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement)
}
} }
private fun PropertyDescriptor.isJvmFieldPropertyInInterfaceCompanion(): Boolean { private fun PropertyDescriptor.isJvmFieldPropertyInInterfaceCompanion(): Boolean {
@@ -262,7 +278,7 @@ class JvmSerializerExtension @JvmOverloads constructor(
if (!DescriptorUtils.isCompanionObject(container)) return false if (!DescriptorUtils.isCompanionObject(container)) return false
val grandParent = (container as ClassDescriptor).containingDeclaration 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) { override fun serializeErrorType(type: KotlinType, builder: ProtoBuf.Type.Builder) {
@@ -251,6 +251,7 @@ class GenerationState private constructor(
val isInlineDisabled: Boolean = configuration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE) val isInlineDisabled: Boolean = configuration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE)
val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE) val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE)
val unifiedNullChecks: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4 val unifiedNullChecks: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
val functionsWithInlineClassReturnTypesMangled: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
val rootContext: CodegenContext<*> = RootContext(this) val rootContext: CodegenContext<*> = RootContext(this)
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspend
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe 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.requiresFunctionNameManglingForParameterTypes
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
import org.jetbrains.kotlin.types.KotlinType 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. // 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', // 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. // should unwrap it and take original return type instead.
if (descriptor.containingDeclaration is ClassDescriptor) { val unwrappedDescriptor = descriptor.unwrapInitialDescriptorForSuspendFunction()
val returnType = descriptor.unwrapInitialDescriptorForSuspendFunction().returnType!! if (requiresFunctionNameManglingForReturnType(unwrappedDescriptor)) {
if (requiresFunctionNameManglingForReturnType(returnType)) { return "-" + md5base64(":" + getSignatureElementForMangling(unwrappedDescriptor.returnType!!))
return "-" + md5base64(":" + getSignatureElementForMangling(returnType))
}
} }
return null return null
} }
@@ -151,7 +151,7 @@ class JvmNameAnnotationChecker : DeclarationChecker {
diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry))
} else if (descriptor.containingDeclaration.isInlineClassThatRequiresMangling() || } else if (descriptor.containingDeclaration.isInlineClassThatRequiresMangling() ||
requiresFunctionNameManglingForParameterTypes(descriptor.valueParameters.map { it.type }) || requiresFunctionNameManglingForParameterTypes(descriptor.valueParameters.map { it.type }) ||
descriptor.containingDeclaration is ClassDescriptor && requiresFunctionNameManglingForReturnType(descriptor.returnType) requiresFunctionNameManglingForReturnType(descriptor)
) { ) {
diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry))
} }
@@ -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)
}
@@ -33,33 +33,49 @@ abstract class AbstractVersionRequirementTest : TestCaseWithTmpdir() {
fqNamesWithRequirements: List<String>, fqNamesWithRequirements: List<String>,
fqNamesWithoutRequirement: List<String> = emptyList() fqNamesWithoutRequirement: List<String> = 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) val module = loadModule(tmpdir)
for (fqName in fqNamesWithRequirements) { for (fqName in fqNamesWithRequirements) {
val descriptor = module.findUnambiguousDescriptorByFqName(fqName) 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) requirements.firstOrNull {
assertEquals("Incorrect level for $fqName", expectedLevel, requirement.level) expectedVersionRequirement == it.version &&
assertEquals("Incorrect message for $fqName", expectedMessage, requirement.message) expectedLevel == it.level &&
assertEquals("Incorrect versionKind for $fqName", expectedVersionKind, requirement.kind) expectedMessage == it.message &&
assertEquals("Incorrect errorCode for $fqName", expectedErrorCode, requirement.errorCode) 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) { for (fqName in fqNamesWithoutRequirement) {
val descriptor = module.findUnambiguousDescriptorByFqName(fqName) val descriptor = module.findUnambiguousDescriptorByFqName(fqName)
val requirenment = extractRequirement(descriptor) val requirement = extractRequirement(descriptor)
assertNull("Expecting absence of any requirements for $fqName, but `$requirenment`", requirenment) assertTrue("Expecting absence of any requirements for $fqName, but `$requirement`", requirement.isEmpty())
} }
} }
private fun extractRequirement(descriptor: DeclarationDescriptor): VersionRequirement? { private fun extractRequirement(descriptor: DeclarationDescriptor): List<VersionRequirement> {
return when (descriptor) { return when (descriptor) {
is DeserializedMemberDescriptor -> descriptor.versionRequirements.singleOrNull() is DeserializedMemberDescriptor -> descriptor.versionRequirements
is DeserializedClassDescriptor -> descriptor.versionRequirements.singleOrNull() is DeserializedClassDescriptor -> descriptor.versionRequirements
else -> throw AssertionError("Unknown descriptor: $descriptor") else -> throw AssertionError("Unknown descriptor: $descriptor")
} }
} }
@@ -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() { fun testSuspendFun_1_2() {
doTest( doTest(
VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, LANGUAGE_VERSION, null, VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, LANGUAGE_VERSION, null,
@@ -29,8 +29,11 @@ fun requiresFunctionNameManglingForParameterTypes(valueParameterTypes: List<Kotl
} }
// NB functions returning all inline classes (including our special 'kotlin.Result') should be mangled. // NB functions returning all inline classes (including our special 'kotlin.Result') should be mangled.
fun requiresFunctionNameManglingForReturnType(returnType: KotlinType?) = fun requiresFunctionNameManglingForReturnType(descriptor: CallableMemberDescriptor): Boolean {
returnType != null && returnType.isInlineClassType() if (descriptor.containingDeclaration !is ClassDescriptor) return false
val returnType = descriptor.returnType ?: return false
return returnType.isInlineClassType()
}
fun DeclarationDescriptor.isInlineClassThatRequiresMangling(): Boolean = fun DeclarationDescriptor.isInlineClassThatRequiresMangling(): Boolean =
isInlineClass() && !isDontMangleClass(this as ClassDescriptor) isInlineClass() && !isDontMangleClass(this as ClassDescriptor)
+8
View File
@@ -79,41 +79,49 @@ public final annotation class A : kotlin/Annotation {
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ub()B // getter: ub()B
public final val ub: kotlin/UByte public final val ub: kotlin/UByte
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ub_max()B // getter: ub_max()B
public final val ub_max: kotlin/UByte public final val ub_max: kotlin/UByte
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ui()I // getter: ui()I
public final val ui: kotlin/UInt public final val ui: kotlin/UInt
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ui_max()I // getter: ui_max()I
public final val ui_max: kotlin/UInt public final val ui_max: kotlin/UInt
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ul()J // getter: ul()J
public final val ul: kotlin/ULong public final val ul: kotlin/ULong
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ul_max()J // getter: ul_max()J
public final val ul_max: kotlin/ULong public final val ul_max: kotlin/ULong
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: us()S // getter: us()S
public final val us: kotlin/UShort public final val us: kotlin/UShort
public final get public final get
// requires language version 1.3.0 (level=ERROR) // requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: us_max()S // getter: us_max()S
public final val us_max: kotlin/UShort public final val us_max: kotlin/UShort
public final get public final get