Add filtering during generation of mockJDK JAR

Adjustments for missing superclasses checker.
Some hierarchies in mock JDK are incomplete.
Absence of its .class file causes some tests to fail without an actual error.
To mitigate this problem, top-level classes and interfaces can only inherit from entries that belong to mock JDK.
Absent superclasses are replaced with java.lang.Object.
This only affects runtime jar of mock JDK.
This commit is contained in:
Pavel Kirpichenkov
2019-11-05 11:55:46 +03:00
parent 27ff2d7816
commit 388cd53105
3 changed files with 106 additions and 4 deletions
Binary file not shown.
@@ -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<String> getClassFileEntries() {
/* package-private */ static Set<String> 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() {
@@ -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<out String>?) {
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<out String>?): Array<out String>? =
oldInterfaces
?.filter { it in mockJdkEntries }
?.toTypedArray()
}