diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractBytecodeListingTest.kt b/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractBytecodeListingTest.kt index 96080a0d7a0..1132563e057 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractBytecodeListingTest.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractBytecodeListingTest.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.codegen +import com.intellij.openapi.Disposable import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.test.ConfigurationKind @@ -30,196 +31,181 @@ abstract class AbstractBytecodeListingTest : CodegenTestCase() { protected open val classBuilderFactory: ClassBuilderFactory get() = ClassBuilderFactories.TEST - open fun getTextFile(ktFile: File): File = File(ktFile.parentFile, ktFile.nameWithoutExtension + ".txt") - override fun doMultiFileTest(wholeFile: File, files: List, javaFilesDir: File?) { - val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray() - - var addRuntime = false - var addReflect = false - - for (file in files) { - if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_RUNTIME")) { - addRuntime = true - } - if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_REFLECT")) { - addReflect = true - } - } - - val configurationKind = if (addReflect) - ConfigurationKind.ALL - else if (addRuntime) - ConfigurationKind.NO_KOTLIN_REFLECT - else - ConfigurationKind.JDK_ONLY - - val jdkKind = getJdkKind(files) - if (myEnvironment != null) { - throw IllegalStateException("must not set up myEnvironment twice") - } - - val configuration = createConfiguration( - configurationKind, - jdkKind, - listOf(getAnnotationsJar()), - javaSources.filterNotNull(), - emptyList()) - - myEnvironment = KotlinCoreEnvironment.createForTests( - testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES - ) - - setupEnvironment(myEnvironment) - - loadMultiFiles(files) - - val txtFile = getTextFile(wholeFile) - val generatedFiles = CodegenTestUtil.generateFiles(myEnvironment, myFiles, classBuilderFactory) - .getClassFiles() - .sortedBy { it.relativePath } - .map { - val cr = ClassReader(it.asByteArray()) - val visitor = TextCollectingVisitor() - cr.accept(visitor, ClassReader.SKIP_CODE) - KotlinTestUtils.replaceHash(visitor.text, "HASH") - }.joinToString("\n\n", postfix = "\n") - - KotlinTestUtils.assertEqualsToFile(txtFile, generatedFiles) + val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".txt") + doTest(testRootDisposable, files, javaFilesDir, txtFile, classBuilderFactory, this::setupEnvironment) } protected open fun setupEnvironment(environment: KotlinCoreEnvironment) {} - private class TextCollectingVisitor : ClassVisitor(ASM5) { - private class Declaration(val text: String, val annotations: MutableList = arrayListOf()) - - private val declarationsInsideClass = arrayListOf() - private val classAnnotations = arrayListOf() - private var className = "" - private var classAccess = 0 - - private fun addAnnotation(desc: String, list: MutableList = declarationsInsideClass.last().annotations) { - val name = Type.getType(desc).className - list.add("@$name ") - } - - private fun addModifier(text: String, list: MutableList) { - list.add("$text ") - } - - private fun handleModifiers(access: Int, list: MutableList = declarationsInsideClass.last().annotations) { - if (access and ACC_PUBLIC != 0) addModifier("public", list) - if (access and ACC_PROTECTED != 0) addModifier("protected", list) - if (access and ACC_PRIVATE != 0) addModifier("private", list) - - if (access and ACC_SYNTHETIC != 0) addModifier("synthetic", list) - if (access and ACC_DEPRECATED != 0) addModifier("deprecated", list) - if (access and ACC_FINAL != 0) addModifier("final", list) - if (access and ACC_ABSTRACT != 0 && access and ACC_INTERFACE == 0) addModifier("abstract", list) - if (access and ACC_STATIC != 0) addModifier("static", list) - } - - 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().apply { handleModifiers(classAccess, this) }.forEach { append(it) } - append(classOrInterface(classAccess)) - 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 visitMethod( - access: Int, - name: String, - desc: String, - signature: String?, - exceptions: Array? - ): MethodVisitor? { - val returnType = Type.getReturnType(desc).className - val parameterTypes = Type.getArgumentTypes(desc).map { it.className } - val methodAnnotations = arrayListOf() - val parameterAnnotations = hashMapOf>() - - handleModifiers(access, methodAnnotations) - - return object : MethodVisitor(ASM5) { - override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { - val type = Type.getType(desc).className - methodAnnotations += "@$type " - return super.visitAnnotation(desc, visible) - } - - override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? { - val type = Type.getType(desc).className - parameterAnnotations.getOrPut(parameter, { 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() - declarationsInsideClass.add(Declaration("method $name($parameterWithAnnotations): $returnType", methodAnnotations)) - super.visitEnd() - } - } - } - - override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { - val type = Type.getType(desc).className - val fieldDeclaration = Declaration("field $name: $type") - declarationsInsideClass.add(fieldDeclaration) - handleModifiers(access) - if (access and ACC_VOLATILE != 0) addModifier("volatile", fieldDeclaration.annotations) - - return object : FieldVisitor(ASM5) { - override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { - addAnnotation(desc) - return super.visitAnnotation(desc, visible) - } - } - } - - override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { - 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? + companion object { + @JvmStatic + fun doTest( + disposable: Disposable, + files: List, + javaFilesDir: File?, + txtFile: File, + classBuilderFactory: ClassBuilderFactory, + setupEnvironment: (KotlinCoreEnvironment) -> Unit = {} ) { - className = name - classAccess = access - } + val addRuntime = files.any { InTextDirectivesUtils.isDirectiveDefined(it.content, "WITH_RUNTIME") } + val addReflect = files.any { InTextDirectivesUtils.isDirectiveDefined(it.content, "WITH_REFLECT") } - override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) { - declarationsInsideClass.add(Declaration("inner class $name")) + val configurationKind = when { + addReflect -> ConfigurationKind.ALL + addRuntime -> ConfigurationKind.NO_KOTLIN_REFLECT + else -> ConfigurationKind.JDK_ONLY + } + + val configuration = createConfiguration( + configurationKind, getJdkKind(files), listOf(getAnnotationsJar()), javaFilesDir?.let(::listOf).orEmpty(), emptyList() + ) + val environment = KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) + setupEnvironment(environment) + + val generatedFiles = CodegenTestUtil.generateFiles(environment, loadMultiFiles(files, environment.project), classBuilderFactory) + .getClassFiles() + .sortedBy { it.relativePath } + .map { + val cr = ClassReader(it.asByteArray()) + val visitor = BytecodeListingTextCollectingVisitor() + cr.accept(visitor, ClassReader.SKIP_CODE) + KotlinTestUtils.replaceHash(visitor.text, "HASH") + }.joinToString("\n\n", postfix = "\n") + + KotlinTestUtils.assertEqualsToFile(txtFile, generatedFiles) + } + } +} + +private class BytecodeListingTextCollectingVisitor : ClassVisitor(ASM5) { + private class Declaration(val text: String, val annotations: MutableList = arrayListOf()) + + private val declarationsInsideClass = arrayListOf() + private val classAnnotations = arrayListOf() + private var className = "" + private var classAccess = 0 + + private fun addAnnotation(desc: String, list: MutableList = declarationsInsideClass.last().annotations) { + val name = Type.getType(desc).className + list.add("@$name ") + } + + private fun addModifier(text: String, list: MutableList) { + list.add("$text ") + } + + private fun handleModifiers(access: Int, list: MutableList = declarationsInsideClass.last().annotations) { + if (access and ACC_PUBLIC != 0) addModifier("public", list) + if (access and ACC_PROTECTED != 0) addModifier("protected", list) + if (access and ACC_PRIVATE != 0) addModifier("private", list) + + if (access and ACC_SYNTHETIC != 0) addModifier("synthetic", list) + if (access and ACC_DEPRECATED != 0) addModifier("deprecated", list) + if (access and ACC_FINAL != 0) addModifier("final", list) + if (access and ACC_ABSTRACT != 0 && access and ACC_INTERFACE == 0) addModifier("abstract", list) + if (access and ACC_STATIC != 0) addModifier("static", list) + } + + 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().apply { handleModifiers(classAccess, this) }.forEach { append(it) } + append(classOrInterface(classAccess)) + 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 visitMethod( + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodVisitor? { + val returnType = Type.getReturnType(desc).className + val parameterTypes = Type.getArgumentTypes(desc).map { it.className } + val methodAnnotations = arrayListOf() + val parameterAnnotations = hashMapOf>() + + handleModifiers(access, methodAnnotations) + + return object : MethodVisitor(ASM5) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + val type = Type.getType(desc).className + methodAnnotations += "@$type " + return super.visitAnnotation(desc, visible) + } + + override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? { + val type = Type.getType(desc).className + parameterAnnotations.getOrPut(parameter, { 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() + declarationsInsideClass.add(Declaration("method $name($parameterWithAnnotations): $returnType", methodAnnotations)) + super.visitEnd() + } + } + } + + override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { + val type = Type.getType(desc).className + val fieldDeclaration = Declaration("field $name: $type") + declarationsInsideClass.add(fieldDeclaration) + handleModifiers(access) + if (access and ACC_VOLATILE != 0) addModifier("volatile", fieldDeclaration.annotations) + + return object : FieldVisitor(ASM5) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + addAnnotation(desc) + return super.visitAnnotation(desc, visible) + } + } + } + + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + 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? + ) { + className = name + classAccess = access + } + + override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) { + declarationsInsideClass.add(Declaration("inner class $name")) + } } diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeCodegenTest.kt b/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeCodegenTest.kt index f862541d8dc..5a9ac763dd5 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeCodegenTest.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeCodegenTest.kt @@ -16,23 +16,18 @@ package org.jetbrains.kotlin.codegen -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension import java.io.File abstract class AbstractLightAnalysisModeCodegenTest : AbstractBytecodeListingTest() { - override val classBuilderFactory: ClassBuilderFactory - get() = ClassBuilderFactories.TEST_KAPT3 - - override fun setupEnvironment(environment: KotlinCoreEnvironment) { - AnalysisHandlerExtension.registerExtension(environment.project, PartialAnalysisHandlerExtension()) - } - - - override fun getTextFile(ktFile: File): File { + override fun doMultiFileTest(wholeFile: File, files: List, javaFilesDir: File?) { val boxTestsDir = File("compiler/testData/codegen/box") - val outDir = File("compiler/testData/codegen/light-analysis", ktFile.toRelativeString(boxTestsDir)).parent - return File(outDir, ktFile.nameWithoutExtension + ".txt") + val outDir = File("compiler/testData/codegen/light-analysis", wholeFile.toRelativeString(boxTestsDir)).parent + val txtFile = File(outDir, wholeFile.nameWithoutExtension + ".txt") + + doTest(testRootDisposable, files, javaFilesDir, txtFile, ClassBuilderFactories.TEST_KAPT3) { environment -> + AnalysisHandlerExtension.registerExtension(environment.project, PartialAnalysisHandlerExtension()) + } } -} \ No newline at end of file +} diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java index f2fc63f925b..9b37055f4f5 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.testFramework.TestDataFile; @@ -278,17 +279,22 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { } protected void loadMultiFiles(@NotNull List files) { + myFiles = loadMultiFiles(files, myEnvironment.getProject()); + } + + @NotNull + public static CodegenTestFiles loadMultiFiles(@NotNull List files, @NotNull Project project) { Collections.sort(files); List ktFiles = new ArrayList(files.size()); for (TestFile file : files) { if (file.name.endsWith(".kt")) { String content = CheckerTestUtil.parseDiagnosedRanges(file.content, new ArrayList(0)); - ktFiles.add(KotlinTestUtils.createFile(file.name, content, myEnvironment.getProject())); + ktFiles.add(KotlinTestUtils.createFile(file.name, content, project)); } } - myFiles = CodegenTestFiles.create(ktFiles); + return CodegenTestFiles.create(ktFiles); } @NotNull