diff --git a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/ClassMapperLite.kt b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/ClassMapperLite.kt index c954e0e40bb..db5dba08a88 100644 --- a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/ClassMapperLite.kt +++ b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/ClassMapperLite.kt @@ -13,6 +13,8 @@ package org.jetbrains.kotlin.metadata.jvm.deserialization // WARNING: improving the behavior of this class MAY BREAK BINARY COMPATIBILITY of code compiled by Kotlin, because it may make // the new compiler skip writing the signatures it now thinks are trivial, and the old compiler would recreate them incorrectly. object ClassMapperLite { + // Simply "kotlin", but to avoid being renamed by namespace relocation (e.g., Shadow.relocate gradle plugin) + private val kotlin = listOf('k', 'o', 't', 'l', 'i', 'n').joinToString(separator = "") // Kotlin ClassId -> JVM desc // e.g. "kotlin.IntArray" -> "[I" // "kotlin.String.Companion" -> "Lkotlin/jvm/internal/StringCompanionObject" @@ -30,14 +32,14 @@ object ClassMapperLite { ) for (i in primitives.indices step 2) { - put("kotlin/${primitives[i]}", primitives[i + 1]) - put("kotlin/${primitives[i]}Array", "[${primitives[i + 1]}") + put("$kotlin/${primitives[i]}", primitives[i + 1]) + put("$kotlin/${primitives[i]}Array", "[${primitives[i + 1]}") } - put("kotlin/Unit", "V") + put("$kotlin/Unit", "V") fun add(kotlinSimpleName: String, javaInternalName: String) { - put("kotlin/$kotlinSimpleName", "L$javaInternalName;") + put("$kotlin/$kotlinSimpleName", "L$javaInternalName;") } add("Any", "java/lang/Object") @@ -59,14 +61,14 @@ object ClassMapperLite { add("collections/MutableMap.MutableEntry", "java/util/Map\$Entry") for (i in 0..22) { - add("Function$i", "kotlin/jvm/functions/Function$i") - add("reflect/KFunction$i", "kotlin/reflect/KFunction") + add("Function$i", "$kotlin/jvm/functions/Function$i") + add("reflect/KFunction$i", "$kotlin/reflect/KFunction") } //Boolean is purposefully omitted from this list, even though it has a Companion Object. //This assures that an older compiler won't get confused by the new signature, preventing a bug in compatibility. for (klass in listOf("Char", "Byte", "Short", "Int", "Float", "Long", "Double", "String", "Enum")) { - add("$klass.Companion", "kotlin/jvm/internal/${klass}CompanionObject") + add("$klass.Companion", "$kotlin/jvm/internal/${klass}CompanionObject") } } diff --git a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmNameResolver.kt b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmNameResolver.kt index 2b3a00d2874..c32956b425a 100644 --- a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmNameResolver.kt +++ b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmNameResolver.kt @@ -75,37 +75,40 @@ class JvmNameResolver( index in localNameIndices companion object { + // Simply "kotlin", but to avoid being renamed by namespace relocation (e.g., Shadow.relocate gradle plugin) + private val kotlin = listOf('k', 'o', 't', 'l', 'i', 'n').joinToString(separator = "") + val PREDEFINED_STRINGS = listOf( - "kotlin/Any", - "kotlin/Nothing", - "kotlin/Unit", - "kotlin/Throwable", - "kotlin/Number", + "$kotlin/Any", + "$kotlin/Nothing", + "$kotlin/Unit", + "$kotlin/Throwable", + "$kotlin/Number", - "kotlin/Byte", "kotlin/Double", "kotlin/Float", "kotlin/Int", - "kotlin/Long", "kotlin/Short", "kotlin/Boolean", "kotlin/Char", + "$kotlin/Byte", "$kotlin/Double", "$kotlin/Float", "$kotlin/Int", + "$kotlin/Long", "$kotlin/Short", "$kotlin/Boolean", "$kotlin/Char", - "kotlin/CharSequence", - "kotlin/String", - "kotlin/Comparable", - "kotlin/Enum", + "$kotlin/CharSequence", + "$kotlin/String", + "$kotlin/Comparable", + "$kotlin/Enum", - "kotlin/Array", - "kotlin/ByteArray", "kotlin/DoubleArray", "kotlin/FloatArray", "kotlin/IntArray", - "kotlin/LongArray", "kotlin/ShortArray", "kotlin/BooleanArray", "kotlin/CharArray", + "$kotlin/Array", + "$kotlin/ByteArray", "$kotlin/DoubleArray", "$kotlin/FloatArray", "$kotlin/IntArray", + "$kotlin/LongArray", "$kotlin/ShortArray", "$kotlin/BooleanArray", "$kotlin/CharArray", - "kotlin/Cloneable", - "kotlin/Annotation", + "$kotlin/Cloneable", + "$kotlin/Annotation", - "kotlin/collections/Iterable", "kotlin/collections/MutableIterable", - "kotlin/collections/Collection", "kotlin/collections/MutableCollection", - "kotlin/collections/List", "kotlin/collections/MutableList", - "kotlin/collections/Set", "kotlin/collections/MutableSet", - "kotlin/collections/Map", "kotlin/collections/MutableMap", - "kotlin/collections/Map.Entry", "kotlin/collections/MutableMap.MutableEntry", + "$kotlin/collections/Iterable", "$kotlin/collections/MutableIterable", + "$kotlin/collections/Collection", "$kotlin/collections/MutableCollection", + "$kotlin/collections/List", "$kotlin/collections/MutableList", + "$kotlin/collections/Set", "$kotlin/collections/MutableSet", + "$kotlin/collections/Map", "$kotlin/collections/MutableMap", + "$kotlin/collections/Map.Entry", "$kotlin/collections/MutableMap.MutableEntry", - "kotlin/collections/Iterator", "kotlin/collections/MutableIterator", - "kotlin/collections/ListIterator", "kotlin/collections/MutableListIterator" + "$kotlin/collections/Iterator", "$kotlin/collections/MutableIterator", + "$kotlin/collections/ListIterator", "$kotlin/collections/MutableListIterator" ) private val PREDEFINED_STRINGS_MAP = PREDEFINED_STRINGS.withIndex().associateBy({ it.value }, { it.index }) diff --git a/libraries/kotlinx-metadata/jvm/build.gradle.kts b/libraries/kotlinx-metadata/jvm/build.gradle.kts index 651268caf58..a40cde3dc39 100644 --- a/libraries/kotlinx-metadata/jvm/build.gradle.kts +++ b/libraries/kotlinx-metadata/jvm/build.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile description = "Kotlin JVM metadata manipulation library" @@ -47,6 +48,10 @@ dependencies { testRuntime(project(":kotlin-reflect")) } +tasks.named("compileTestKotlin") { + jvmTarget = "1.8" + javaHome = rootProject.extra["JDK_18"] as String +} if (deployVersion != null) { publish() diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt new file mode 100644 index 00000000000..0c2fc6127c8 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2020 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 kotlinx.metadata.test + +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_FRAMES +import org.jetbrains.org.objectweb.asm.ClassVisitor +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes.ASM4 +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Test +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import java.util.zip.ZipFile +import kotlin.text.Charsets.UTF_8 + +class JarContentTest { + @Test + fun testJarContents() { + // println("Patterns: $PREDEFINED_STRINGS") + LIBS_PATH.toFile().walk().filter { it.name.endsWith(JAR_EXT) }.forEach(::checkJarContents) + } + + private fun checkJarContents(jar: File) { + val zipFile = ZipFile(jar, UTF_8) + zipFile.entries().asSequence().filter { it.name.endsWith(CLASS_EXT) }.forEach { + val loadedConstants = mutableListOf() + val classReader = ClassReader(zipFile.getInputStream(it)) + classReader.accept(object : ClassVisitor(ASM4) { + override fun visitMethod(p0: Int, p1: String?, p2: String?, p3: String?, p4: Array?): MethodVisitor { + return object : MethodVisitor(ASM4) { + override fun visitLdcInsn(cst: Any?) { + if (cst != null) loadedConstants.add(cst) + } + } + } + }, SKIP_FRAMES) + + for (constant in loadedConstants) { + if (constant !is String || constant.isNullOrEmpty()) { + continue + } + // println("checking: $constant") + // Explicitly checking types that are programmatically built don't appear as string constants. + assertNull("$constant found at ${it.name}", findPattern(constant)) + // Implicitly checking none of string constants starts with "kotlin/" prefix, just in case. + assertFalse("$constant found at ${it.name}", constant.startsWith("kotlin/")) + } + } + } + + private fun findPattern(constant: String): String? { + return PREDEFINED_STRINGS.find { it in constant } + } + + companion object { + const val JAR_EXT = ".jar" + const val CLASS_EXT = ".class" + val LIBS_PATH: Path = Paths.get("build", "libs") + + private val INTERNAL_COMPANIONS = + listOf("Char", "Byte", "Short", "Int", "Float", "Long", "Double", "String", "Enum") + .map { "kotlin/jvm/internal/${it}CompanionObject" } + val PREDEFINED_STRINGS = + JvmNameResolver.PREDEFINED_STRINGS + listOf("kotlin/jvm/functions", "kotlin/reflect/KFunction") + INTERNAL_COMPANIONS + } +}