[Test] Migrate AbstractBlackBoxCodegenTest to new infrastructure
This commit is contained in:
committed by
TeamCityServer
parent
f01122d8dc
commit
85c87f7df9
+2
@@ -26,6 +26,8 @@ import static org.jetbrains.kotlin.test.KotlinTestUtils.assertEqualsToFile;
|
||||
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getBoxMethodOrNull;
|
||||
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getGeneratedClass;
|
||||
|
||||
// Prefer using new test runner: org.jetbrains.kotlin.test.runners.codegen.AbstractBlackBoxCodegenTest
|
||||
@Deprecated
|
||||
public abstract class AbstractBlackBoxCodegenTest extends CodegenTestCase {
|
||||
protected void doMultiFileTest(
|
||||
@NotNull File wholeFile,
|
||||
|
||||
-289
@@ -8,11 +8,7 @@ package org.jetbrains.kotlin.codegen
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
||||
import java.io.File
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
abstract class AbstractBytecodeListingTest : CodegenTestCase() {
|
||||
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>) {
|
||||
@@ -57,288 +53,3 @@ abstract class AbstractBytecodeListingTest : CodegenTestCase() {
|
||||
private val IGNORE_ANNOTATIONS = Regex.fromLiteral("// IGNORE_ANNOTATIONS")
|
||||
}
|
||||
}
|
||||
|
||||
class BytecodeListingTextCollectingVisitor(
|
||||
val filter: Filter,
|
||||
val withSignatures: Boolean,
|
||||
api: Int = API_VERSION,
|
||||
val withAnnotations: Boolean = true
|
||||
) : ClassVisitor(api) {
|
||||
companion object {
|
||||
@JvmOverloads
|
||||
fun getText(
|
||||
factory: ClassFileFactory,
|
||||
filter: Filter = Filter.EMPTY,
|
||||
withSignatures: Boolean = false,
|
||||
withAnnotations: Boolean = true
|
||||
) = factory.getClassFiles()
|
||||
.sortedBy { it.relativePath }
|
||||
.mapNotNull {
|
||||
val cr = ClassReader(it.asByteArray())
|
||||
val visitor = BytecodeListingTextCollectingVisitor(filter, withSignatures, withAnnotations = withAnnotations)
|
||||
cr.accept(visitor, ClassReader.SKIP_CODE)
|
||||
|
||||
if (!filter.shouldWriteClass(cr.access, cr.className)) null else visitor.text
|
||||
}.joinToString("\n\n", postfix = "\n")
|
||||
|
||||
private val CLASS_OR_FIELD_OR_METHOD = setOf(ModifierTarget.CLASS, ModifierTarget.FIELD, ModifierTarget.METHOD)
|
||||
private val CLASS_OR_METHOD = setOf(ModifierTarget.CLASS, ModifierTarget.METHOD)
|
||||
private val FIELD_ONLY = setOf(ModifierTarget.FIELD)
|
||||
private val METHOD_ONLY = setOf(ModifierTarget.METHOD)
|
||||
private val FIELD_OR_METHOD = setOf(ModifierTarget.FIELD, ModifierTarget.METHOD)
|
||||
|
||||
// TODO ACC_MANDATED - requires reading Parameters attribute, which we don't generate by default
|
||||
internal val MODIFIERS =
|
||||
arrayOf(
|
||||
Modifier("public", ACC_PUBLIC, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("protected", ACC_PROTECTED, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("private", ACC_PRIVATE, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("synthetic", ACC_SYNTHETIC, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("bridge", ACC_BRIDGE, METHOD_ONLY),
|
||||
Modifier("volatile", ACC_VOLATILE, FIELD_ONLY),
|
||||
Modifier("synchronized", ACC_SYNCHRONIZED, METHOD_ONLY),
|
||||
Modifier("varargs", ACC_VARARGS, METHOD_ONLY),
|
||||
Modifier("transient", ACC_TRANSIENT, FIELD_ONLY),
|
||||
Modifier("native", ACC_NATIVE, METHOD_ONLY),
|
||||
Modifier("deprecated", ACC_DEPRECATED, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("final", ACC_FINAL, CLASS_OR_FIELD_OR_METHOD),
|
||||
Modifier("strict", ACC_STRICT, METHOD_ONLY),
|
||||
Modifier("enum", ACC_ENUM, FIELD_ONLY), // ACC_ENUM modifier on class is handled in 'classOrInterface'
|
||||
Modifier("abstract", ACC_ABSTRACT, CLASS_OR_METHOD, excludedMask = ACC_INTERFACE),
|
||||
Modifier("static", ACC_STATIC, FIELD_OR_METHOD)
|
||||
)
|
||||
}
|
||||
|
||||
interface Filter {
|
||||
fun shouldWriteClass(access: Int, name: String): Boolean
|
||||
fun shouldWriteMethod(access: Int, name: String, desc: String): Boolean
|
||||
fun shouldWriteField(access: Int, name: String, desc: String): Boolean
|
||||
fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?): Boolean
|
||||
|
||||
object EMPTY : Filter {
|
||||
override fun shouldWriteClass(access: Int, name: String) = true
|
||||
override fun shouldWriteMethod(access: Int, name: String, desc: String) = true
|
||||
override fun shouldWriteField(access: Int, name: String, desc: String) = true
|
||||
override fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?) = true
|
||||
}
|
||||
|
||||
object ForCodegenTests : Filter {
|
||||
override fun shouldWriteClass(access: Int, name: String): Boolean = !name.startsWith("helpers/")
|
||||
override fun shouldWriteMethod(access: Int, name: String, desc: String): Boolean = true
|
||||
override fun shouldWriteField(access: Int, name: String, desc: String): Boolean = true
|
||||
override fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?): Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
private class Declaration(val text: String, val annotations: MutableList<String> = arrayListOf())
|
||||
|
||||
private val declarationsInsideClass = arrayListOf<Declaration>()
|
||||
private val classAnnotations = arrayListOf<String>()
|
||||
private var className = ""
|
||||
private var classAccess = 0
|
||||
private var classSignature: String? = ""
|
||||
|
||||
private fun addAnnotation(desc: String, list: MutableList<String> = declarationsInsideClass.last().annotations) {
|
||||
val name = Type.getType(desc).className
|
||||
list.add("@$name ")
|
||||
}
|
||||
|
||||
private fun addModifier(text: String, list: MutableList<String>) {
|
||||
list.add("$text ")
|
||||
}
|
||||
|
||||
internal enum class ModifierTarget {
|
||||
CLASS, FIELD, METHOD
|
||||
}
|
||||
|
||||
internal class Modifier(
|
||||
val text: String,
|
||||
private val mask: Int,
|
||||
private val applicableTo: Set<ModifierTarget>,
|
||||
private val excludedMask: Int = 0
|
||||
) {
|
||||
fun hasModifier(access: Int, target: ModifierTarget) =
|
||||
access and mask != 0 &&
|
||||
access and excludedMask == 0 &&
|
||||
applicableTo.contains(target)
|
||||
}
|
||||
|
||||
private fun handleModifiers(
|
||||
target: ModifierTarget,
|
||||
access: Int,
|
||||
list: MutableList<String> = declarationsInsideClass.last().annotations
|
||||
) {
|
||||
for (modifier in MODIFIERS) {
|
||||
if (modifier.hasModifier(access, target)) {
|
||||
addModifier(modifier.text, list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getModifiers(target: ModifierTarget, access: Int) =
|
||||
MODIFIERS.filter { it.hasModifier(access, target) }.joinToString(separator = " ") { it.text }
|
||||
|
||||
private fun classOrInterface(access: Int): String {
|
||||
return when {
|
||||
access and ACC_ANNOTATION != 0 -> "annotation class"
|
||||
access and ACC_ENUM != 0 -> "enum class"
|
||||
access and ACC_INTERFACE != 0 -> "interface"
|
||||
else -> "class"
|
||||
}
|
||||
}
|
||||
|
||||
val text: String
|
||||
get() = StringBuilder().apply {
|
||||
if (classAnnotations.isNotEmpty()) {
|
||||
append(classAnnotations.joinToString("\n", postfix = "\n"))
|
||||
}
|
||||
arrayListOf<String>().apply { handleModifiers(ModifierTarget.CLASS, classAccess, this) }.forEach { append(it) }
|
||||
append(classOrInterface(classAccess))
|
||||
if (withSignatures) {
|
||||
append("<$classSignature> ")
|
||||
}
|
||||
append(" ")
|
||||
append(className)
|
||||
if (declarationsInsideClass.isNotEmpty()) {
|
||||
append(" {\n")
|
||||
for (declaration in declarationsInsideClass.sortedBy { it.text }) {
|
||||
append(" ").append(declaration.annotations.joinToString("")).append(declaration.text).append("\n")
|
||||
}
|
||||
append("}")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
override fun visitSource(source: String?, debug: String?) {
|
||||
if (source != null) {
|
||||
declarationsInsideClass.add(Declaration("// source: '$source'"))
|
||||
} else {
|
||||
declarationsInsideClass.add(Declaration("// source: null"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
|
||||
if (!filter.shouldWriteMethod(access, name, desc)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val returnType = Type.getReturnType(desc).className
|
||||
val parameterTypes = Type.getArgumentTypes(desc).map { it.className }
|
||||
val methodAnnotations = arrayListOf<String>()
|
||||
val parameterAnnotations = hashMapOf<Int, MutableList<String>>()
|
||||
|
||||
handleModifiers(ModifierTarget.METHOD, access, methodAnnotations)
|
||||
val methodParamCount = Type.getArgumentTypes(desc).size
|
||||
|
||||
return object : MethodVisitor(API_VERSION) {
|
||||
private var visibleAnnotableParameterCount = methodParamCount
|
||||
private var invisibleAnnotableParameterCount = methodParamCount
|
||||
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (withAnnotations) {
|
||||
val type = Type.getType(desc).className
|
||||
methodAnnotations += "@$type "
|
||||
}
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
|
||||
override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (withAnnotations) {
|
||||
val type = Type.getType(desc).className
|
||||
parameterAnnotations.getOrPut(
|
||||
parameter + methodParamCount - (if (visible) visibleAnnotableParameterCount else invisibleAnnotableParameterCount),
|
||||
{ arrayListOf() }).add("@$type ")
|
||||
}
|
||||
return super.visitParameterAnnotation(parameter, desc, visible)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
val parameterWithAnnotations = parameterTypes.mapIndexed { index, parameter ->
|
||||
val annotations = parameterAnnotations.getOrElse(index, { emptyList() }).joinToString("")
|
||||
"${annotations}p$index: $parameter"
|
||||
}.joinToString()
|
||||
val signatureIfRequired = if (withSignatures) "<$signature> " else ""
|
||||
declarationsInsideClass.add(
|
||||
Declaration("${signatureIfRequired}method $name($parameterWithAnnotations): $returnType", methodAnnotations)
|
||||
)
|
||||
super.visitEnd()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_OVERRIDE")
|
||||
override fun visitAnnotableParameterCount(parameterCount: Int, visible: Boolean) {
|
||||
if (visible)
|
||||
visibleAnnotableParameterCount = parameterCount
|
||||
else {
|
||||
invisibleAnnotableParameterCount = parameterCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
if (!filter.shouldWriteField(access, name, desc)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = Type.getType(desc).className
|
||||
val fieldSignature = if (withSignatures) "<$signature> " else ""
|
||||
val fieldDeclaration = Declaration("field $fieldSignature$name: $type")
|
||||
declarationsInsideClass.add(fieldDeclaration)
|
||||
handleModifiers(ModifierTarget.FIELD, access)
|
||||
if (access and ACC_VOLATILE != 0) addModifier("volatile", fieldDeclaration.annotations)
|
||||
|
||||
return object : FieldVisitor(API_VERSION) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (withAnnotations) {
|
||||
addAnnotation(desc)
|
||||
}
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (withAnnotations) {
|
||||
val name = Type.getType(desc).className
|
||||
classAnnotations.add("@$name")
|
||||
}
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
|
||||
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<out String>?) {
|
||||
className = name
|
||||
classAccess = access
|
||||
classSignature = signature
|
||||
}
|
||||
|
||||
override fun visitOuterClass(owner: String, name: String?, descriptor: String?) {
|
||||
if (name == null) {
|
||||
assertNull(descriptor)
|
||||
declarationsInsideClass.add(Declaration("enclosing class $owner"))
|
||||
} else {
|
||||
assertNotNull(descriptor)
|
||||
declarationsInsideClass.add(Declaration("enclosing method $owner.$name$descriptor"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
|
||||
if (!filter.shouldWriteInnerClass(name, outerName, innerName)) {
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
innerName == null -> {
|
||||
assertNull(outerName, "Anonymous classes should have neither innerName nor outerName. Name=$name, outerName=$outerName")
|
||||
declarationsInsideClass.add(Declaration("inner (anonymous) class $name"))
|
||||
}
|
||||
outerName == null -> {
|
||||
declarationsInsideClass.add(Declaration("inner (local) class $name $innerName"))
|
||||
}
|
||||
name == "$outerName$$innerName" -> {
|
||||
declarationsInsideClass.add(Declaration("${getModifiers(ModifierTarget.CLASS, access)} inner class $name"))
|
||||
}
|
||||
else -> {
|
||||
declarationsInsideClass.add(Declaration("inner (unrecognized) class $name $outerName $innerName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
||||
private static final String DEFAULT_TEST_FILE_NAME = "a_test";
|
||||
private static final String DEFAULT_JVM_TARGET = System.getProperty("kotlin.test.default.jvm.target");
|
||||
public static final String BOX_IN_SEPARATE_PROCESS_PORT = System.getProperty("kotlin.test.box.in.separate.process.port");
|
||||
private static final String JAVA_COMPILATION_TARGET = System.getProperty("kotlin.test.java.compilation.target");
|
||||
|
||||
protected KotlinCoreEnvironment myEnvironment;
|
||||
protected CodegenTestFiles myFiles;
|
||||
@@ -203,7 +202,7 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
||||
|
||||
initializedClassLoader = createClassLoader();
|
||||
|
||||
if (!verifyAllFilesWithAsm(generateClassesInFile(reportProblems), initializedClassLoader, reportProblems)) {
|
||||
if (!CodegenTestUtil.verifyAllFilesWithAsm(generateClassesInFile(reportProblems), initializedClassLoader, reportProblems)) {
|
||||
fail("Verification failed: see exceptions above");
|
||||
}
|
||||
|
||||
@@ -388,50 +387,6 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean verifyAllFilesWithAsm(ClassFileFactory factory, ClassLoader loader, boolean reportProblems) {
|
||||
boolean noErrors = true;
|
||||
for (OutputFile file : ClassFileUtilsKt.getClassFiles(factory)) {
|
||||
noErrors &= verifyWithAsm(file, loader, reportProblems);
|
||||
}
|
||||
return noErrors;
|
||||
}
|
||||
|
||||
private static boolean verifyWithAsm(@NotNull OutputFile file, ClassLoader loader, boolean reportProblems) {
|
||||
ClassNode classNode = new ClassNode();
|
||||
new ClassReader(file.asByteArray()).accept(classNode, 0);
|
||||
|
||||
SimpleVerifier verifier = new SimpleVerifier();
|
||||
verifier.setClassLoader(loader);
|
||||
Analyzer<BasicValue> analyzer = new Analyzer<>(verifier);
|
||||
|
||||
boolean noErrors = true;
|
||||
for (MethodNode method : classNode.methods) {
|
||||
try {
|
||||
analyzer.analyze(classNode.name, method);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (reportProblems) {
|
||||
System.err.println(file.asText());
|
||||
System.err.println(classNode.name + "::" + method.name + method.desc);
|
||||
|
||||
//noinspection InstanceofCatchParameter
|
||||
if (e instanceof AnalyzerException) {
|
||||
// Print the erroneous instruction
|
||||
TraceMethodVisitor tmv = new TraceMethodVisitor(new Textifier());
|
||||
((AnalyzerException) e).node.accept(tmv);
|
||||
PrintWriter pw = new PrintWriter(System.err);
|
||||
tmv.p.print(pw);
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
noErrors = false;
|
||||
}
|
||||
}
|
||||
return noErrors;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Method generateFunction() {
|
||||
Class<?> aClass = generateFacadeClass();
|
||||
@@ -575,7 +530,7 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
||||
return javacOptions;
|
||||
}
|
||||
|
||||
String javaTarget = computeJavaTarget(javacOptions, kotlinTarget);
|
||||
String javaTarget = CodegenTestUtil.computeJavaTarget(javacOptions, kotlinTarget);
|
||||
if (javaTarget != null) {
|
||||
javacOptions.add("-source");
|
||||
javacOptions.add(javaTarget);
|
||||
@@ -585,20 +540,6 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
||||
return javacOptions;
|
||||
}
|
||||
|
||||
private static final boolean IS_SOURCE_6_STILL_SUPPORTED =
|
||||
// JDKs up to 11 do support -source/target 1.6, but later -- don't.
|
||||
Arrays.asList("1.6", "1.7", "1.8", "9", "10", "11").contains(System.getProperty("java.specification.version"));
|
||||
|
||||
private static String computeJavaTarget(@NotNull List<String> javacOptions, @Nullable JvmTarget kotlinTarget) {
|
||||
if (JAVA_COMPILATION_TARGET != null && !javacOptions.contains("-target"))
|
||||
return JAVA_COMPILATION_TARGET;
|
||||
if (kotlinTarget != null && kotlinTarget.compareTo(JvmTarget.JVM_1_6) > 0)
|
||||
return kotlinTarget.getDescription();
|
||||
if (IS_SOURCE_6_STILL_SUPPORTED)
|
||||
return "1.6";
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected TargetBackend getBackend() {
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.codegen;
|
||||
|
||||
import com.android.tools.r8.*;
|
||||
import com.android.tools.r8.origin.PathOrigin;
|
||||
import kotlin.Pair;
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class D8Checker {
|
||||
|
||||
private D8Checker() {
|
||||
}
|
||||
|
||||
public static void check(ClassFileFactory outputFiles) {
|
||||
runD8(builder -> {
|
||||
for (OutputFile file : ClassFileUtilsKt.getClassFiles(outputFiles)) {
|
||||
byte[] bytes = file.asByteArray();
|
||||
builder.addClassProgramData(bytes, new PathOrigin(Paths.get(file.getRelativePath())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void checkFilesWithD8(Collection<Pair<byte[], String>> classFiles) {
|
||||
runD8(builder -> {
|
||||
classFiles.forEach(pair -> {
|
||||
builder.addClassProgramData(pair.getFirst(), new PathOrigin(Paths.get(pair.getSecond())));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Compilation with D8 should proceed with no output. There should be no info, warnings, or errors.
|
||||
static class TestDiagnosticsHandler implements DiagnosticsHandler {
|
||||
@Override
|
||||
public void error(Diagnostic diagnostic) {
|
||||
Assert.fail("D8 dexing error: " + diagnostic.getDiagnosticMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(Diagnostic diagnostic) {
|
||||
Assert.fail("D8 dexing warning: " + diagnostic.getDiagnosticMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Diagnostic diagnostic) {
|
||||
Assert.fail("D8 dexing info: " + diagnostic.getDiagnosticMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void runD8(Consumer<D8Command.Builder> addInput) {
|
||||
ProgramConsumer ignoreOutputConsumer = new DexIndexedConsumer.ForwardingConsumer(null);
|
||||
D8Command.Builder builder = D8Command.builder(new TestDiagnosticsHandler())
|
||||
.setMinApiLevel(28)
|
||||
.setMode(CompilationMode.DEBUG)
|
||||
.setProgramConsumer(ignoreOutputConsumer);
|
||||
addInput.accept(builder);
|
||||
try {
|
||||
D8.run(builder.build(), Executors.newSingleThreadExecutor());
|
||||
}
|
||||
catch (CompilationFailedException e) {
|
||||
Assert.fail(generateExceptionMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateExceptionMessage(Throwable e) {
|
||||
StringWriter writer = new StringWriter();
|
||||
try (PrintWriter printWriter = new PrintWriter(writer)) {
|
||||
e.printStackTrace(printWriter);
|
||||
String stackTrace = writer.toString();
|
||||
return stackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.codegen;
|
||||
|
||||
import com.android.dx.cf.direct.DirectClassFile;
|
||||
import com.android.dx.cf.direct.StdAttributeFactory;
|
||||
import com.android.dx.command.dexer.Main;
|
||||
import com.android.dx.dex.cf.CfTranslator;
|
||||
import com.android.dx.dex.file.DexFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile;
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DxChecker {
|
||||
|
||||
public static final boolean RUN_DX_CHECKER = true;
|
||||
private static final Pattern STACK_TRACE_PATTERN = Pattern.compile("[\\s]+at .*");
|
||||
|
||||
private DxChecker() {
|
||||
}
|
||||
|
||||
public static void check(ClassFileFactory outputFiles) {
|
||||
Main.Arguments arguments = new Main.Arguments();
|
||||
String[] array = new String[1];
|
||||
array[0] = "testArgs";
|
||||
arguments.parse(array);
|
||||
|
||||
for (OutputFile file : ClassFileUtilsKt.getClassFiles(outputFiles)) {
|
||||
try {
|
||||
byte[] bytes = file.asByteArray();
|
||||
if (isJava6Class(bytes)) {
|
||||
checkFileWithDx(bytes, file.getRelativePath(), arguments);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Assert.fail(generateExceptionMessage(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJava6Class(byte[] bytes) throws IOException {
|
||||
try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
|
||||
int header = stream.readInt();
|
||||
if (0xCAFEBABE != header) {
|
||||
throw new IOException("Invalid header class header: " + header);
|
||||
}
|
||||
int minor = stream.readUnsignedShort();
|
||||
int major = stream.readUnsignedShort();
|
||||
return major == Opcodes.V1_6;
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkFileWithDx(byte[] bytes, @NotNull String relativePath) {
|
||||
Main.Arguments arguments = new Main.Arguments();
|
||||
String[] array = new String[1];
|
||||
array[0] = "testArgs";
|
||||
arguments.parse(array);
|
||||
checkFileWithDx(bytes, relativePath, arguments);
|
||||
}
|
||||
|
||||
private static void checkFileWithDx(byte[] bytes, @NotNull String relativePath, @NotNull Main.Arguments arguments) {
|
||||
DirectClassFile cf = new DirectClassFile(bytes, relativePath, true);
|
||||
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
|
||||
CfTranslator.translate(
|
||||
cf,
|
||||
bytes,
|
||||
arguments.cfOptions,
|
||||
arguments.dexOptions,
|
||||
new DexFile(arguments.dexOptions)
|
||||
);
|
||||
}
|
||||
|
||||
private static String generateExceptionMessage(Throwable e) {
|
||||
StringWriter writer = new StringWriter();
|
||||
try (PrintWriter printWriter = new PrintWriter(writer)) {
|
||||
e.printStackTrace(printWriter);
|
||||
String stackTrace = writer.toString();
|
||||
Matcher matcher = STACK_TRACE_PATTERN.matcher(stackTrace);
|
||||
return matcher.replaceAll("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.codegen
|
||||
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
|
||||
fun clearReflectionCache(classLoader: ClassLoader) {
|
||||
try {
|
||||
val klass = classLoader.loadClass(JvmAbi.REFLECTION_FACTORY_IMPL.asSingleFqName().asString())
|
||||
val method = klass.getDeclaredMethod("clearCaches")
|
||||
method.invoke(null)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
// This is OK for a test without kotlin-reflect in the dependencies
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassLoader?.extractUrls(): List<URL> {
|
||||
return (this as? URLClassLoader)?.let {
|
||||
it.urLs.toList() + it.parent.extractUrls()
|
||||
} ?: emptyList()
|
||||
}
|
||||
Reference in New Issue
Block a user