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.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<out T : BaseExpressionCodegen>(
?: 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<out T : BaseExpressionCodegen>(
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()
@@ -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) {
@@ -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)
@@ -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
}
@@ -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))
}
@@ -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>,
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)
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<VersionRequirement> {
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")
}
}
@@ -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,
@@ -29,8 +29,11 @@ fun requiresFunctionNameManglingForParameterTypes(valueParameterTypes: List<Kotl
}
// NB functions returning all inline classes (including our special 'kotlin.Result') should be mangled.
fun requiresFunctionNameManglingForReturnType(returnType: KotlinType?) =
returnType != null && returnType.isInlineClassType()
fun requiresFunctionNameManglingForReturnType(descriptor: CallableMemberDescriptor): Boolean {
if (descriptor.containingDeclaration !is ClassDescriptor) return false
val returnType = descriptor.returnType ?: return false
return returnType.isInlineClassType()
}
fun DeclarationDescriptor.isInlineClassThatRequiresMangling(): Boolean =
isInlineClass() && !isDontMangleClass(this as ClassDescriptor)
+8
View File
@@ -79,41 +79,49 @@ public final annotation class A : kotlin/Annotation {
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ub()B
public final val ub: kotlin/UByte
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ub_max()B
public final val ub_max: kotlin/UByte
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ui()I
public final val ui: kotlin/UInt
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ui_max()I
public final val ui_max: kotlin/UInt
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ul()J
public final val ul: kotlin/ULong
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: ul_max()J
public final val ul_max: kotlin/ULong
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: us()S
public final val us: kotlin/UShort
public final get
// requires language version 1.3.0 (level=ERROR)
// requires compiler version 1.4.0 (level=ERROR)
// getter: us_max()S
public final val us_max: kotlin/UShort
public final get