Support inline suspend functions built with compiler version less than 1.1.4/1.2-M1
The error message is removed and is replaced with a code that adapts inline suspend functions produced by the old compiler with the suspension markers that new compiler expects.
This commit is contained in:
@@ -64,10 +64,6 @@ class WrongBytecodeVersionTest : KtUsefulTestCase() {
|
||||
doTest("/bytecodeVersion/simple")
|
||||
}
|
||||
|
||||
fun testObsoleteInlineSuspend() {
|
||||
doTest("/bytecodeVersion/obsoleteInlineSuspend", intArrayOf(1, 0, 1))
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun transformMetadataInClassFile(bytes: ByteArray, transform: (fieldName: String, value: Any?) -> Any?): ByteArray {
|
||||
val writer = ClassWriter(0)
|
||||
|
||||
+68
-4
@@ -26,7 +26,11 @@ import org.jetbrains.kotlin.cli.js.K2JSCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||
import org.jetbrains.kotlin.codegen.inline.GENERATE_SMAP
|
||||
import org.jetbrains.kotlin.codegen.inline.remove
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.intConstant
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion.TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||
@@ -41,11 +45,12 @@ import org.jetbrains.kotlin.test.MockLibraryUtil
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile
|
||||
import org.jetbrains.kotlin.utils.JsMetadataVersion
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
@@ -384,7 +389,66 @@ class CompileKotlinAgainstCustomBinariesTest : AbstractKotlinCompilerIntegration
|
||||
compileKotlin("source.kt", tmpdir, listOf(library))
|
||||
}
|
||||
|
||||
fun testObsoleteInlineSuspend() {
|
||||
val version = intArrayOf(1, 0, 1) // legacy coroutines metadata
|
||||
val options = listOf("-Xcoroutines=enable")
|
||||
val library = transformJar(
|
||||
compileLibrary("library", additionalOptions = options),
|
||||
{ _, bytes ->
|
||||
val (resultBytes, removedCounter) = stripSuspensionMarksToImitateLegacyCompiler(
|
||||
WrongBytecodeVersionTest.transformMetadataInClassFile(bytes) { name, _ ->
|
||||
if (name == JvmAnnotationNames.BYTECODE_VERSION_FIELD_NAME) version else null
|
||||
})
|
||||
// we expect 4 instructions to be removed in this test library
|
||||
assertEquals(4, removedCounter)
|
||||
resultBytes
|
||||
})
|
||||
compileKotlin("source.kt", tmpdir, listOf(library), K2JVMCompiler(),
|
||||
additionalOptions = options)
|
||||
val classLoader = URLClassLoader(arrayOf(library.toURI().toURL(), tmpdir.toURI().toURL()),
|
||||
ForTestCompileRuntime.runtimeJarClassLoader())
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = classLoader
|
||||
.loadClass("SourceKt")
|
||||
.getDeclaredMethod("run")
|
||||
.invoke(null) as Array<String>
|
||||
assertEquals(result[0], result[1])
|
||||
}
|
||||
|
||||
companion object {
|
||||
// compiler before 1.1.4 version did not include suspension marks into bytecode.
|
||||
private fun stripSuspensionMarksToImitateLegacyCompiler(bytes: ByteArray): Pair<ByteArray, Int> {
|
||||
val writer = ClassWriter(0)
|
||||
var removedCounter = 0
|
||||
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5, writer) {
|
||||
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor {
|
||||
val superMV = super.visitMethod(access, name, desc, signature, exceptions)
|
||||
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
|
||||
override fun visitEnd() {
|
||||
val removeList = instructions.asSequence()
|
||||
.flatMap { suspendMarkerInsns(it).asSequence() }.toList()
|
||||
remove(removeList)
|
||||
removedCounter += removeList.size
|
||||
accept(superMV)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
return writer.toByteArray() to removedCounter
|
||||
}
|
||||
|
||||
// KLUDGE: here is a simplified copy of compiler's logic for suspend markers
|
||||
|
||||
private fun suspendMarkerInsns(insn: AbstractInsnNode): List<AbstractInsnNode> =
|
||||
if (insn is MethodInsnNode
|
||||
&& insn.opcode == Opcodes.INVOKESTATIC
|
||||
&& insn.owner == "kotlin/jvm/internal/InlineMarker"
|
||||
&& insn.name == "mark"
|
||||
&& insn.previous.intConstant in 0..1) listOf(insn, insn.previous)
|
||||
else emptyList()
|
||||
|
||||
// -----
|
||||
|
||||
private fun copyJarFileWithoutEntry(jarPath: File, vararg entriesToDelete: String): File =
|
||||
transformJar(jarPath, { _, bytes -> bytes }, entriesToDelete.toSet())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user