diff --git a/compiler/testData/mockJDK/jre/lib/rt.jar b/compiler/testData/mockJDK/jre/lib/rt.jar index c210032e0bf..a09584149bd 100644 Binary files a/compiler/testData/mockJDK/jre/lib/rt.jar and b/compiler/testData/mockJDK/jre/lib/rt.jar differ diff --git a/generators/tests/org/jetbrains/kotlin/generators/mockJDK/GenerateMockJdk.java b/generators/tests/org/jetbrains/kotlin/generators/mockJDK/GenerateMockJdk.java index d397c6f602d..bc404d05bab 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/mockJDK/GenerateMockJdk.java +++ b/generators/tests/org/jetbrains/kotlin/generators/mockJDK/GenerateMockJdk.java @@ -276,8 +276,6 @@ public class GenerateMockJdk { "META-INF/MANIFEST.MF", }; - - private static void generateFilteredJar( String jdkPath, String childName, @@ -331,7 +329,7 @@ public class GenerateMockJdk { return entrySet; } - private static Set getClassFileEntries() { + /* package-private */ static Set getClassFileEntries() { return new HashSet<>(Arrays.asList(ENTRIES)); } @@ -360,11 +358,12 @@ public class GenerateMockJdk { } } } + File mockJdkRuntimeJarPath = new File("compiler/testData/mockJDK/jre/lib/rt.jar"); generateFilteredJar( openjdk7Path, "jre/lib/rt.jar", - new File("compiler/testData/mockJDK/jre/lib/rt.jar"), + mockJdkRuntimeJarPath, getClassFileEntries(), true); generateFilteredJar( @@ -373,6 +372,7 @@ public class GenerateMockJdk { new File("compiler/testData/mockJDK/src.zip"), sourceFileEntries, false); + FilterMockJdkKt.removeInterfacesFromMockJdkClassfiles(mockJdkRuntimeJarPath); } private GenerateMockJdk() { diff --git a/generators/tests/org/jetbrains/kotlin/generators/mockJDK/filterMockJdk.kt b/generators/tests/org/jetbrains/kotlin/generators/mockJDK/filterMockJdk.kt new file mode 100644 index 00000000000..eaf7f15ead7 --- /dev/null +++ b/generators/tests/org/jetbrains/kotlin/generators/mockJDK/filterMockJdk.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2019 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.generators.mockJDK + +import com.intellij.openapi.util.io.FileUtil +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 java.io.File +import java.io.FileOutputStream +import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry + +fun main() = removeInterfacesFromMockJdkClassfiles( + mockJdkRuntimeJar = File("compiler/testData/mockJDK/jre/lib/rt.jar") +) + +internal fun removeInterfacesFromMockJdkClassfiles(mockJdkRuntimeJar: File) { + if (!mockJdkRuntimeJar.exists()) { + throw AssertionError("$mockJdkRuntimeJar doesn't exist") + } + + val tmpdir = FileUtil.createTempDirectory( + File(System.getProperty("java.io.tmpdir")), + "mockJdk", + "", + true + ) + val copyJar = File(tmpdir, "rt.jar") + FileUtil.copy(mockJdkRuntimeJar, copyJar) + + JarFile(mockJdkRuntimeJar).use { sourceJar -> + JarOutputStream(FileOutputStream(copyJar)) + .use { targetJar -> + transformJar(sourceJar, targetJar) + } + } + + FileUtil.copy(copyJar, mockJdkRuntimeJar) + tmpdir.delete() +} + +private fun transformJar(sourceJar: JarFile, targetJar: JarOutputStream) { + val sourceEntries = sourceJar.entries().toList() + for (entry in sourceEntries) { + if (entry.name.endsWith(".class")) { + val inputByteArray = sourceJar.getInputStream(entry).use { it.readBytes() } + val classReader = ClassReader(inputByteArray) + val classWriter = ClassWriter(classReader, 0) // Neither stack frames nor stack size changes + + classReader.accept( + InterfacesFilter(classWriter), + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES // Only implemented interfaces matter + ) + + targetJar.putNextEntry(ZipEntry(entry.name)) + targetJar.write(classWriter.toByteArray()) + + } else { + targetJar.putNextEntry(ZipEntry(entry.name)) + FileUtil.copy(sourceJar.getInputStream(entry), targetJar) + } + } +} + +internal class InterfacesFilter(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.API_VERSION, classVisitor) { + private val mockJdkEntries = + GenerateMockJdk.getClassFileEntries().mapNotNull { entry -> + val classSuffix = ".class" + if (entry.endsWith(classSuffix)) + entry.substring(0 until (entry.length - classSuffix.length)) + else null + }.toSet() + + override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { + if (name !in mockJdkEntries) { + super.visit(version, access, name, signature, superName, interfaces) + return + } + + val allowedInterfaces = filterInterfaces(interfaces) + val allowedSuperclass = filterSuperclass(superName) + super.visit(version, access, name, signature, allowedSuperclass, allowedInterfaces) + } + + private fun filterSuperclass(oldSuperclass: String?): String? = + oldSuperclass?.let { + if (it !in mockJdkEntries) + "java/lang/Object" + else it + } ?: oldSuperclass + + private fun filterInterfaces(oldInterfaces: Array?): Array? = + oldInterfaces + ?.filter { it in mockJdkEntries } + ?.toTypedArray() +}