Fix jvm method reading when descriptor and signature do not match

#KT-38325 fixed
This commit is contained in:
Ilya Chernikov
2021-04-07 18:19:09 +02:00
committed by TeamCityServer
parent c75331bf2a
commit ca352c9556
3 changed files with 87 additions and 34 deletions
@@ -59,24 +59,36 @@ abstract class BinaryJavaMethodBase(
val isInnerClassConstructor = isConstructor && containingClass.outerClass != null && !containingClass.isStatic
val isEnumConstructor = containingClass.isEnum && isConstructor
val methodInfoFromDescriptor = parseMethodDescription(desc, parentContext, signatureParser).let {
when {
isEnumConstructor ->
// skip ordinal/name parameters for enum constructors
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(2))
isInnerClassConstructor ->
// omit synthetic inner class constructor parameter
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(1))
else -> it
}
}
val info: MethodInfo =
if (signature != null) {
val contextForMethod = parentContext.copyForMember()
parseMethodSignature(signature, signatureParser, contextForMethod).also {
val methodInforFromSignature = parseMethodSignature(signature, signatureParser, contextForMethod).also {
contextForMethod.addTypeParameters(it.typeParameters)
}
} else
parseMethodDescription(desc, parentContext, signatureParser).let {
when {
isEnumConstructor ->
// skip ordinal/name parameters for enum constructors
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(2))
isInnerClassConstructor ->
// omit synthetic inner class constructor parameter
MethodInfo(it.returnType, it.typeParameters, it.valueParameterTypes.drop(1))
else -> it
}
// JVM specs allows disagreements in parameters between signature and descriptor/serialized method. In particular the
// situation was detected on Scala stdlib (see #KT-3825 for some details).
// But in our implementation we are using the parameter infos read here as a "master" so if signature has less params
// than the descriptor, we need get missing parameter infos from somewhere.
// Since the known cases are rare, it was decided to keep it simple for now and only cover this particular case.
if (methodInforFromSignature.valueParameterTypes.count() < methodInfoFromDescriptor.valueParameterTypes.count()) {
methodInfoFromDescriptor
} else {
methodInforFromSignature
}
} else {
methodInfoFromDescriptor
}
val parameterTypes = info.valueParameterTypes
val paramCount = parameterTypes.size
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.jvm.compiler
import com.intellij.testFramework.BinaryLightVirtualFile
import junit.framework.TestCase
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.org.objectweb.asm.ClassWriter
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.junit.Test
class CustomGeneratedClassesTest : TestCase() {
@Test
fun testEmulatedScalaStdlibSyntheticMethodLoading() {
// #KT-38325 and #KT-39799
val classFqn = "org/jetbrains/kotlin/compiler/test/GeneratedScalalikeTraversableOncePart"
val classNode = ClassNode(Opcodes.API_VERSION).apply {
version = Opcodes.V1_6
access = Opcodes.ACC_PUBLIC
name = classFqn
signature = "L$classFqn;"
superName = "java/lang/Object"
methods.add(
// The root of the problem described in the #KT-38325 and #KT-39799 is the presence of a method with signature and descriptor
// disagreeing on the number of parameters. Here the method with the similar structure is created
MethodNode(
Opcodes.API_VERSION,
Opcodes.ACC_PRIVATE,
"reverser\$2",
"(Ljava/lang/Object;)L$classFqn\$reverser\$1\$;",
"()L$classFqn\$reverser\$1\$;",
null
).apply {
visitParameter("a", 0)
}
)
}
val classWriter = ClassWriter(0).also {
classNode.accept(it)
}
// This is the actual test. Without the relevant fix, this call throws "No parameter with index 0-0" error
BinaryJavaClass(
BinaryLightVirtualFile("$classFqn.class", classWriter.toByteArray()),
FqName(classFqn.replace('/', '.')),
ClassifierResolutionContext { null },
BinaryClassSignatureParser(),
outerClass = null
)
}
}