Fix serialization of type parameters inner generic class

The call to `createTopLevel` instead of `create` (which creates
serializers for outer classes properly, with correct type parameter
contexts) caused MetadataSerializer to write type parameter metadata
incorrectly.  For example, in the following case:

    class A<E> {
        inner class B<T, E> { ... }
    }

A's type parameter E would get id 0, and B's type parameters T and E
would get ids 0 and 1. This is a problem because ids are supposed to be
unique for each class including its outer classes, and deserializer,
decompiler and stub builder rely on this assumption.

JVM metadata is unaffected because `create` is called correctly there,
see MemberCodegen#generateKotlinClassMetadataAnnotation

 #KT-24944 Fixed
This commit is contained in:
Alexander Udalov
2018-06-19 15:55:46 +02:00
parent c05285a23c
commit 9309570752
11 changed files with 97 additions and 39 deletions
@@ -33,44 +33,57 @@ import java.nio.file.Paths
abstract class AbstractMultiPlatformIntegrationTest : KtUsefulTestCase() {
fun doTest(directoryPath: String) {
val root = File(directoryPath).apply { assert(exists()) }
val commonSrc = File(root, "common.kt")
val jsSrc = File(root, "js.kt")
val jvmSrc = File(root, "jvm.kt")
val commonSrc = File(root, "common.kt").apply { assert(exists()) }
val jsSrc = File(root, "js.kt").takeIf(File::exists)
val jvmSrc = File(root, "jvm.kt").takeIf(File::exists)
// TODO: consider inventing a more clever scheme
val jvm2Src = File(root, "jvm2.kt")
val common2Src = File(root, "common2.kt").takeIf(File::exists)
val jvm2Src = File(root, "jvm2.kt").takeIf(File::exists)
val tmpdir = KotlinTestUtils.tmpDir(getTestName(true))
val optionalStdlibCommon =
if (InTextDirectivesUtils.isDirectiveDefined(commonSrc.readText(), "WITH_RUNTIME"))
arrayOf("-cp", findStdlibCommon().absolutePath)
else emptyArray()
if (InTextDirectivesUtils.isDirectiveDefined(commonSrc.readText(), "WITH_RUNTIME"))
arrayOf("-cp", findStdlibCommon().absolutePath)
else emptyArray()
val commonDest = File(tmpdir, "common").absolutePath
val jvmDest = File(tmpdir, "jvm").absolutePath
val jsDest = File(File(tmpdir, "js"), "output.js").absolutePath
val jvm2Dest = File(tmpdir, "jvm2").absolutePath
val jvmDest = File(tmpdir, "jvm").absolutePath.takeIf { jvmSrc != null }
val jsDest = File(File(tmpdir, "js"), "output.js").absolutePath.takeIf { jsSrc != null }
val common2Dest = File(tmpdir, "common2").absolutePath.takeIf { common2Src != null }
val jvm2Dest = File(tmpdir, "jvm2").absolutePath.takeIf { jvm2Src != null }
val result = buildString {
appendln("-- Common --")
appendln(K2MetadataCompiler().compile(listOf(commonSrc), "-d", commonDest, *optionalStdlibCommon))
if (jvmSrc.exists()) {
if (jvmSrc != null) {
appendln()
appendln("-- JVM --")
appendln(K2JVMCompiler().compileBothWays(commonSrc, jvmSrc, "-d", jvmDest))
appendln(K2JVMCompiler().compileBothWays(commonSrc, jvmSrc, "-d", jvmDest!!))
}
if (jsSrc.exists()) {
if (jsSrc != null) {
appendln()
appendln("-- JS --")
appendln(K2JSCompiler().compileBothWays(commonSrc, jsSrc, "-output", jsDest))
appendln(K2JSCompiler().compileBothWays(commonSrc, jsSrc, "-output", jsDest!!))
}
if (jvm2Src.exists()) {
if (common2Src != null) {
appendln()
appendln("-- Common (2) --")
appendln(K2MetadataCompiler().compile(listOf(common2Src), "-d", common2Dest!!, "-cp", commonDest, *optionalStdlibCommon))
}
if (jvm2Src != null) {
appendln()
appendln("-- JVM (2) --")
appendln(K2JVMCompiler().compile(listOf(jvm2Src), "-d", jvm2Dest, "-cp", listOf(commonDest, jvmDest).joinToString(File.pathSeparator)))
appendln(
K2JVMCompiler().compile(
listOf(jvm2Src), "-d", jvm2Dest!!,
"-cp", listOfNotNull(commonDest, common2Dest, jvmDest).joinToString(File.pathSeparator)
)
)
}
}
@@ -89,17 +102,17 @@ abstract class AbstractMultiPlatformIntegrationTest : KtUsefulTestCase() {
private fun CLICompiler<*>.compileBothWays(commonSource: File, platformSource: File, vararg mainArguments: String): String {
val configurations = listOf(
listOf(platformSource, commonSource),
listOf(commonSource, platformSource)
listOf(platformSource, commonSource),
listOf(commonSource, platformSource)
)
val (platformFirst, commonFirst) = configurations.map { compile(it, *mainArguments) }
if (platformFirst != commonFirst) {
assertEquals(
"Compilation results are different when compiling [platform-specific, common] compared to when compiling [common, platform-specific]",
"// Compiling [platform-specific, common]\n\n$platformFirst",
"// Compiling [common, platform-specific]\n\n$commonFirst"
"Compilation results are different when compiling [platform-specific, common] compared to when compiling [common, platform-specific]",
"// Compiling [platform-specific, common]\n\n$platformFirst",
"// Compiling [common, platform-specific]\n\n$commonFirst"
)
}
return platformFirst
@@ -107,8 +120,8 @@ abstract class AbstractMultiPlatformIntegrationTest : KtUsefulTestCase() {
private fun CLICompiler<*>.compile(sources: List<File>, vararg mainArguments: String): String = buildString {
val (output, exitCode) = AbstractCliTest.executeCompilerGrabOutput(
this@compile,
sources.map(File::getAbsolutePath) + listOf("-Xmulti-platform") + mainArguments + loadExtraArguments(sources)
this@compile,
sources.map(File::getAbsolutePath) + listOf("-Xmulti-platform") + mainArguments + loadExtraArguments(sources)
)
appendln("Exit code: $exitCode")
appendln("Output:")
@@ -89,6 +89,11 @@ public class MultiPlatformIntegrationTestGenerated extends AbstractMultiPlatform
runTest("compiler/testData/multiplatform/inlineClasses/");
}
@TestMetadata("innerGenericClass")
public void testInnerGenericClass() throws Exception {
runTest("compiler/testData/multiplatform/innerGenericClass/");
}
@TestMetadata("jsNameClash")
public void testJsNameClash() throws Exception {
runTest("compiler/testData/multiplatform/jsNameClash/");
@@ -558,6 +563,19 @@ public class MultiPlatformIntegrationTestGenerated extends AbstractMultiPlatform
}
}
@TestMetadata("compiler/testData/multiplatform/innerGenericClass")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InnerGenericClass extends AbstractMultiPlatformIntegrationTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInInnerGenericClass() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/multiplatform/innerGenericClass"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
}
}
@TestMetadata("compiler/testData/multiplatform/jsNameClash")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)