[Test] Migrate AbstractBlackBoxCodegenTest to new infrastructure

This commit is contained in:
Dmitriy Novozhilov
2020-12-25 12:41:20 +03:00
committed by TeamCityServer
parent f01122d8dc
commit 85c87f7df9
47 changed files with 7856 additions and 2932 deletions
@@ -29,6 +29,17 @@ object KtAssert {
}
}
@JvmStatic
@OptIn(ExperimentalContracts::class)
fun assertNull(message: String, value: Any?) {
contract {
returns() implies (value == null)
}
if (value != null) {
fail(message)
}
}
@JvmStatic
fun assertTrue(message: String, value: Boolean) {
if (!value) {
@@ -19,7 +19,8 @@ enum class TargetBackend(
JS_IR(true, JS),
JS_IR_ES6(true, JS_IR),
WASM(true),
ANDROID(false, JVM);
ANDROID(false, JVM),
NATIVE(true);
val compatibleWith get() = compatibleWithTargetBackend ?: ANY
}
@@ -4,9 +4,9 @@
// EXPECTED_REACHABLE_NODES: 1304
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND: NATIVE
// IGNORE_LIGHT_ANALYSIS
// MODULE: lib1
// FILE: lib1.kt
package pkg
@@ -31,4 +31,4 @@ fun box(): String {
if (foo() != "42") return "FAIL: ${foo()}"
return bar()
}
}
@@ -0,0 +1,3 @@
package helpers
fun isIR(): Boolean = true
@@ -0,0 +1,3 @@
package helpers
fun isIR(): Boolean = false
@@ -1,4 +1,5 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// JVM_TARGET: 1.8
// FILE: A.java
@@ -14,6 +14,7 @@ dependencies {
testCompileOnly(project(":kotlin-reflect-api"))
testRuntimeOnly(project(":kotlin-reflect"))
testRuntimeOnly(project(":core:descriptors.runtime"))
testRuntimeOnly(androidDxJar())
testImplementation(projectTests(":generators:test-generator"))
@@ -9,6 +9,8 @@ import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.DefaultCodegenFactory
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.model.ArtifactKinds
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
@@ -18,12 +20,17 @@ import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
class ClassicJvmBackendFacade(
testServices: TestServices
) : ClassicBackendFacade<BinaryArtifacts.Jvm>(testServices, ArtifactKinds.Jvm) {
private val javaCompilerFacade = JavaCompilerFacade(testServices)
override val additionalDirectives: List<DirectivesContainer>
get() = listOf(CodegenTestDirectives)
override fun transform(
module: TestModule,
inputArtifact: ClassicBackendInput
): BinaryArtifacts.Jvm {
val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val (psiFiles, bindingContext, moduleDescriptor, project, languageVersionSettings) = inputArtifact
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val (psiFiles, bindingContext, moduleDescriptor, project, _) = inputArtifact
// TODO: add configuring classBuilderFactory
val generationState = GenerationState.Builder(
project,
@@ -31,11 +38,11 @@ class ClassicJvmBackendFacade(
moduleDescriptor,
bindingContext,
psiFiles.toList(),
compilerConfiguration
configuration
).codegenFactory(DefaultCodegenFactory).build()
KotlinCodegenFacade.compileCorrectFiles(generationState)
javaCompilerFacade.compileJavaFiles(module, configuration, generationState.factory)
return BinaryArtifacts.Jvm(generationState.factory)
}
}
@@ -0,0 +1,91 @@
/*
* 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.test.backend.classic
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.kotlin.codegen.CodegenTestUtil
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.test.compileJavaFilesExternally
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import org.jetbrains.kotlin.test.services.javaFiles
import org.jetbrains.kotlin.test.services.jvm.compiledClassesManager
import org.jetbrains.kotlin.test.services.sourceFileProvider
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File
import java.nio.file.Paths
class JavaCompilerFacade(private val testServices: TestServices) {
@OptIn(ExperimentalStdlibApi::class)
fun compileJavaFiles(module: TestModule, configuration: CompilerConfiguration, classFileFactory: ClassFileFactory) {
if (module.javaFiles.isEmpty()) return
val javaClasspath = buildList {
add(testServices.compiledClassesManager.getCompiledKotlinDirForModule(module, classFileFactory).path)
addAll(configuration.jvmClasspathRoots.map { it.absolutePath })
if (JvmEnvironmentConfigurationDirectives.ANDROID_ANNOTATIONS in module.directives) {
add(ForTestCompileRuntime.androidAnnotationsForTests().path)
}
}
val javaClassesOutputDirectory = testServices.compiledClassesManager.getOrCreateCompiledJavaDirForModule(module)
val javacOptions = extractJavacOptions(
module,
configuration[JVMConfigurationKeys.JVM_TARGET],
configuration.getBoolean(JVMConfigurationKeys.ENABLE_JVM_PREVIEW)
)
val finalJavacOptions = CodegenTestUtil.prepareJavacOptions(
javaClasspath,
javacOptions,
javaClassesOutputDirectory
)
val javaFiles = module.javaFiles.map { testServices.sourceFileProvider.getRealFileForSourceFile(it) }
compileJavaFiles(configuration[JVMConfigurationKeys.JVM_TARGET] ?: JvmTarget.DEFAULT, javaFiles, finalJavacOptions)
}
@OptIn(ExperimentalStdlibApi::class)
private fun extractJavacOptions(module: TestModule, kotlinTarget: JvmTarget?, isJvmPreviewEnabled: Boolean): List<String> {
return buildList {
addAll(module.directives[CodegenTestDirectives.JAVAC_OPTIONS])
if (kotlinTarget != null && isJvmPreviewEnabled) {
add("--release")
add(kotlinTarget.description)
add("--enable-preview")
return@buildList
}
CodegenTestUtil.computeJavaTarget(this, kotlinTarget)?.let { javaTarget ->
add("-source")
add(javaTarget)
add("-target")
add(javaTarget)
}
}
}
private fun compileJavaFiles(jvmTarget: JvmTarget, files: List<File>, javacOptions: List<String>) {
val targetIsJava8OrLower = System.getProperty("java.version").startsWith("1.")
if (targetIsJava8OrLower) {
org.jetbrains.kotlin.test.compileJavaFiles(files, javacOptions, assertions = testServices.assertions)
return
}
val jdkHome = when (jvmTarget) {
JvmTarget.JVM_9 -> KtTestUtil.getJdk9Home()
JvmTarget.JVM_11 -> KtTestUtil.getJdk11Home()
JvmTarget.JVM_15 -> KtTestUtil.getJdk15Home()
else -> null
} ?: error("JDK for $jvmTarget is not found")
compileJavaFilesExternally(files, javacOptions, jdkHome)
}
}
@@ -0,0 +1,43 @@
/*
* 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.test.backend.handlers
import org.jetbrains.kotlin.codegen.BytecodeListingTextCollectingVisitor
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.CHECK_BYTECODE_LISTING
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.defaultsProvider
import org.jetbrains.kotlin.test.services.moduleStructure
import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumperImpl
import org.jetbrains.kotlin.test.utils.withSuffixAndExtension
class BytecodeListingHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
override val directivesContainers: List<DirectivesContainer>
get() = listOf(CodegenTestDirectives)
private val multiModuleInfoDumper = MultiModuleInfoDumperImpl()
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
if (CHECK_BYTECODE_LISTING !in module.directives) return
val dump = BytecodeListingTextCollectingVisitor.getText(
info.classFileFactory,
BytecodeListingTextCollectingVisitor.Filter.ForCodegenTests
)
multiModuleInfoDumper.builderForModule(module).append(dump)
}
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (multiModuleInfoDumper.isEmpty()) return
val suffix = if (testServices.defaultsProvider.defaultTargetBackend?.isIR == true) "_ir" else ""
val file = testServices.moduleStructure.originalTestDataFiles.first()
.withSuffixAndExtension(suffix, ".txt")
assertions.assertEqualsToFile(file, multiModuleInfoDumper.generateResultingDump())
}
}
@@ -0,0 +1,29 @@
/*
* 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.test.backend.handlers
import org.jetbrains.kotlin.codegen.D8Checker
import org.jetbrains.kotlin.codegen.DxChecker
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.IGNORE_DEXING
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.RUN_DEX_CHECKER
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
class DxCheckerHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
override val directivesContainers: List<DirectivesContainer>
get() = listOf(CodegenTestDirectives)
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
if (RUN_DEX_CHECKER !in module.directives || IGNORE_DEXING in module.directives) return
DxChecker.check(info.classFileFactory)
D8Checker.check(info.classFileFactory)
}
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
}
@@ -9,20 +9,24 @@ import junit.framework.TestCase
import org.jetbrains.kotlin.backend.common.CodegenUtil.getMemberDeclarationsToGenerate
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.kotlin.codegen.CodegenTestUtil
import org.jetbrains.kotlin.codegen.GeneratedClassLoader
import org.jetbrains.kotlin.codegen.clearReflectionCache
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil.getFileClassInfoNoResolve
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator.Companion.TEST_CONFIGURATION_KIND_KEY
import org.jetbrains.kotlin.test.services.jvm.compiledClassesManager
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.lang.reflect.Method
import java.net.URLClassLoader
class JvmBoxRunner(
testServices: TestServices
) : JvmBinaryArtifactHandler(testServices) {
class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
companion object {
private val BOX_IN_SEPARATE_PROCESS_PORT = System.getProperty("kotlin.test.box.in.separate.process.port")
}
@@ -37,17 +41,45 @@ class JvmBoxRunner(
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
val ktFiles = info.classFileFactory.inputFiles
val classLoader = createClassLoader(module, info.classFileFactory)
val reportProblems = module.targetBackend !in module.directives[CodegenTestDirectives.IGNORE_BACKEND]
val classLoader = createAndVerifyClassLoader(module, info.classFileFactory, reportProblems)
for (ktFile in ktFiles) {
val className = ktFile.getFacadeFqName() ?: continue
val clazz = classLoader.getGeneratedClass(className)
val method = clazz.getBoxMethodOrNull() ?: continue
boxMethodFound = true
callBoxMethodAndCheckResult(classLoader, clazz, method, unexpectedBehaviour = false)
callBoxMethodAndCheckResultWithCleanup(
info.classFileFactory,
classLoader,
clazz,
method,
unexpectedBehaviour = false,
reportProblems = reportProblems
)
return
}
}
private fun callBoxMethodAndCheckResultWithCleanup(
factory: ClassFileFactory,
classLoader: URLClassLoader,
clazz: Class<*>?,
method: Method,
unexpectedBehaviour: Boolean,
reportProblems: Boolean
) {
try {
callBoxMethodAndCheckResult(classLoader, clazz, method, unexpectedBehaviour)
} catch (e: Throwable) {
if (reportProblems) {
println(factory.createText())
}
throw e
} finally {
clearReflectionCache(classLoader)
}
}
private fun callBoxMethodAndCheckResult(
classLoader: URLClassLoader,
clazz: Class<*>?,
@@ -78,11 +110,34 @@ class JvmBoxRunner(
}
}
private fun createAndVerifyClassLoader(
module: TestModule,
classFileFactory: ClassFileFactory,
reportProblems: Boolean
): GeneratedClassLoader {
val classLoader = createClassLoader(module, classFileFactory)
val verificationSucceeded = CodegenTestUtil.verifyAllFilesWithAsm(classFileFactory, classLoader, reportProblems)
if (!verificationSucceeded ) {
assertions.fail { "Verification failed: see exceptions above" }
}
return classLoader
}
@OptIn(ExperimentalStdlibApi::class)
private fun createClassLoader(module: TestModule, classFileFactory: ClassFileFactory): GeneratedClassLoader {
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val urls = configuration.jvmClasspathRoots.map { it.toURI().toURL() }
val classLoader = URLClassLoader(urls.toTypedArray())
return GeneratedClassLoader(classFileFactory, classLoader)
val urls = buildList {
addAll(configuration.jvmClasspathRoots)
testServices.compiledClassesManager.getCompiledJavaDirForModule(module)?.let {
add(it)
}
}.map { it.toURI().toURL() }
val parentClassLoader = if (configuration[TEST_CONFIGURATION_KIND_KEY]?.withReflection == true) {
ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
} else {
ForTestCompileRuntime.runtimeJarClassLoader()
}
return GeneratedClassLoader(classFileFactory, parentClassLoader, *urls.toTypedArray())
}
private fun KtFile.getFacadeFqName(): String? {
@@ -0,0 +1,20 @@
/*
* 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.test.backend.handlers
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact
import org.jetbrains.kotlin.test.frontend.classic.handlers.ClassicFrontendAnalysisHandler
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
class NoCompilationErrorsHandler(testServices: TestServices) : ClassicFrontendAnalysisHandler(testServices) {
override fun processModule(module: TestModule, info: ClassicFrontendOutputArtifact) {
AnalyzingUtils.throwExceptionOnErrors(info.analysisResult.bindingContext)
}
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
}
@@ -0,0 +1,25 @@
/*
* 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.test.backend.handlers
import org.jetbrains.kotlin.TestsCompiletimeError
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
class NoJvmSpecificCompilationErrorsHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
val generationState = info.classFileFactory.generationState
try {
AnalyzingUtils.throwExceptionOnErrors(generationState.collectedExtraJvmDiagnostics)
} catch (e: Throwable) {
throw TestsCompiletimeError(e)
}
}
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
}
@@ -12,4 +12,27 @@ object CodegenTestDirectives : SimpleDirectivesContainer() {
val IGNORE_BACKEND by enumDirective<TargetBackend>(
description = "Ignore failures of test on target backend"
)
val JAVAC_OPTIONS by stringDirective(
description = "Specify javac options to compile java files"
)
val WITH_HELPERS by directive(
"""
Adds util functions for checking coroutines
See files in ./compiler/testData/codegen/helpers/
""".trimIndent()
)
val CHECK_BYTECODE_LISTING by directive(
description = "Dump resulting bytecode to .txt or _ir.txt file"
)
val RUN_DEX_CHECKER by directive(
description = "Run DxChecker and D8Checker"
)
val IGNORE_DEXING by directive(
description = "Ignore dex checkers"
)
}
@@ -24,8 +24,8 @@ import org.jetbrains.kotlin.test.frontend.classic.handlers.OldNewInferenceMetaIn
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.ScriptingEnvironmentConfigurator
@@ -15,8 +15,8 @@ import org.jetbrains.kotlin.test.frontend.classic.handlers.OldNewInferenceMetaIn
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
abstract class AbstractDiagnosticsNativeTest : AbstractKotlinCompilerTest() {
override fun TestConfigurationBuilder.configuration() {
@@ -16,8 +16,8 @@ import org.jetbrains.kotlin.test.frontend.classic.handlers.OldNewInferenceMetaIn
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractKotlinCompilerTest() {
@@ -21,8 +21,8 @@ import org.jetbrains.kotlin.test.frontend.classic.handlers.OldNewInferenceMetaIn
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
abstract class AbstractDiagnosticsTestWithJvmBackend : AbstractKotlinCompilerTest() {
@@ -21,8 +21,8 @@ import org.jetbrains.kotlin.test.frontend.fir.handlers.*
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.fir.FirOldFrontendMetaConfigurator
@@ -20,10 +20,10 @@ import org.jetbrains.kotlin.test.frontend.classic.handlers.OldNewInferenceMetaIn
import org.jetbrains.kotlin.test.model.BackendKind
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.services.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.CoroutineHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.configuration.*
import org.jetbrains.kotlin.test.services.jvm.ForeignAnnotationAgainstCompiledJavaTestSuppressor
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
abstract class AbstractForeignAnnotationsTestBase : AbstractKotlinCompilerTest() {
protected abstract val foreignAnnotationsConfigurator: Constructor<JvmForeignAnnotationsConfigurator>
@@ -76,7 +76,7 @@ abstract class AbstractKotlinCompilerTest {
builder.configuration()
}
fun runTest(@TestDataFile filePath: String) {
open fun runTest(@TestDataFile filePath: String) {
testRunner(filePath, configuration).runTest(filePath)
}
}
@@ -0,0 +1,22 @@
/*
* 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.test.runners
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
abstract class AbstractKotlinCompilerWithTargetBackendTest(
override val targetBackend: TargetBackend
) : AbstractKotlinCompilerTest(), RunnerWithTargetBackendForTestGeneratorMarker {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
with(builder) {
globalDefaults {
targetBackend = this@AbstractKotlinCompilerWithTargetBackendTest.targetBackend
}
}
}
}
@@ -0,0 +1,22 @@
/*
* 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.test.runners.codegen
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.classic.ClassicJvmBackendFacade
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2ClassicBackendConverter
import org.jetbrains.kotlin.test.model.BackendFacade
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.Frontend2BackendConverter
open class AbstractBlackBoxCodegenTest : AbstractJvmBlackBoxCodegenTestBase(TargetBackend.JVM) {
override val frontendToBackendConverter: Constructor<Frontend2BackendConverter<*, *>>
get() = ::ClassicFrontend2ClassicBackendConverter
override val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
get() = ::ClassicJvmBackendFacade
}
@@ -0,0 +1,70 @@
/*
* 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.test.runners.codegen
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.BlackBoxCodegenSuppressor
import org.jetbrains.kotlin.test.backend.handlers.*
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.RUN_DEX_CHECKER
import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives.REPORT_JVM_DIAGNOSTICS_ON_FRONTEND
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerWithTargetBackendTest
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CodegenHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
abstract class AbstractJvmBlackBoxCodegenTestBase(
targetBackend: TargetBackend
) : AbstractKotlinCompilerWithTargetBackendTest(targetBackend) {
abstract val frontendToBackendConverter: Constructor<Frontend2BackendConverter<*, *>>
abstract val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
override fun TestConfigurationBuilder.configuration() {
globalDefaults {
frontend = FrontendKinds.ClassicFrontend
targetPlatform = JvmPlatforms.defaultJvmPlatform
dependencyKind = DependencyKind.Binary
}
defaultDirectives {
+USE_PSI_CLASS_FILES_READING
+REPORT_JVM_DIAGNOSTICS_ON_FRONTEND
+RUN_DEX_CHECKER
}
useConfigurators(
::CommonEnvironmentConfigurator,
::JvmEnvironmentConfigurator
)
useAdditionalSourceProviders(
::AdditionalDiagnosticsSourceFilesProvider,
::CoroutineHelpersSourceFilesProvider,
::CodegenHelpersSourceFilesProvider,
)
useFrontendFacades(::ClassicFrontendFacade)
useFrontend2BackendConverters(frontendToBackendConverter)
useBackendFacades(backendFacade)
useFrontendHandlers(::NoCompilationErrorsHandler)
useArtifactsHandlers(
::JvmBoxRunner,
::NoJvmSpecificCompilationErrorsHandler,
::BytecodeListingHandler,
::DxCheckerHandler,
)
useAfterAnalysisCheckers(::BlackBoxCodegenSuppressor)
}
}
@@ -10,8 +10,10 @@ import com.intellij.openapi.util.SystemInfo
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.ConfigurationKind
@@ -24,18 +26,23 @@ import org.jetbrains.kotlin.test.model.DependencyDescription
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.jvm.CompiledJarManager
import org.jetbrains.kotlin.test.services.jvm.compiledJarManager
import org.jetbrains.kotlin.test.services.jvm.CompiledClassesManager
import org.jetbrains.kotlin.test.services.jvm.compiledClassesManager
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.test.util.joinToArrayString
import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File
class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) {
companion object {
val TEST_CONFIGURATION_KIND_KEY = CompilerConfigurationKey.create<ConfigurationKind>("ConfigurationKind")
}
override val directivesContainers: List<DirectivesContainer>
get() = listOf(JvmEnvironmentConfigurationDirectives)
override val additionalServices: List<ServiceRegistrationData>
get() = listOf(service(::CompiledJarManager))
get() = listOf(service(::CompiledClassesManager))
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule, project: MockProject) {
if (module.targetPlatform !in JvmPlatforms.allJvmPlatforms) return
@@ -77,7 +84,9 @@ class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfig
}
}
val configurationKind = extractConfigurationKind(registeredDirectives)
val configurationKind = extractConfigurationKind(registeredDirectives).also {
configuration.put(TEST_CONFIGURATION_KIND_KEY, it)
}
if (configurationKind.withRuntime) {
configuration.addJvmClasspathRoot(ForTestCompileRuntime.runtimeJarForTests())
@@ -119,6 +128,8 @@ class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfig
if (JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING in module.directives) {
configuration.put(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING, true)
}
initBinaryDependencies(module, configuration)
}
private fun extractJdkKind(registeredDirectives: RegisteredDirectives): TestJdkKind {
@@ -146,12 +157,9 @@ class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfig
if (noRuntime && withRuntime) {
error("NO_RUNTIME and WITH_RUNTIME can not be used together")
}
if (withReflect && !withRuntime) {
error("WITH_REFLECT may be used only with WITH_RUNTIME")
}
return when {
withRuntime && withReflect -> ConfigurationKind.ALL
withRuntime -> ConfigurationKind.NO_KOTLIN_REFLECT
withRuntime && !withReflect -> ConfigurationKind.NO_KOTLIN_REFLECT
withRuntime || withReflect -> ConfigurationKind.ALL
noRuntime -> ConfigurationKind.JDK_NO_RUNTIME
else -> ConfigurationKind.JDK_ONLY
}
@@ -164,9 +172,47 @@ class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfig
.map { dependencyProvider.getTestModule(it.moduleName) }
.takeIf { it.isNotEmpty() }
?: return
val jarManager = testServices.compiledJarManager
val dependenciesClassPath = modulesFromDependencies.map { jarManager.getCompiledJarForModule(it) }
val jarManager = testServices.compiledClassesManager
val dependenciesClassPath = modulesFromDependencies.map { jarManager.getCompiledKotlinDirForModule(it) }
addJvmClasspathRoots(dependenciesClassPath)
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun initBinaryDependencies(
module: TestModule,
configuration: CompilerConfiguration,
) {
val binaryDependencies = module.dependencies.filter { it.kind == DependencyKind.Binary }
val binaryFriends = module.friends.filter { it.kind == DependencyKind.Binary }
val dependencyProvider = testServices.dependencyProvider
val compiledClassesManager = testServices.compiledClassesManager
val compilerConfigurationProvider = testServices.compilerConfigurationProvider
fun addDependenciesToClasspath(dependencies: List<DependencyDescription>): List<File> {
val jvmClasspathRoots = buildList<File> {
dependencies.forEach {
val dependencyModule = dependencyProvider.getTestModule(it.moduleName)
add(compiledClassesManager.getCompiledKotlinDirForModule(dependencyModule))
addIfNotNull(compiledClassesManager.getCompiledJavaDirForModule(dependencyModule))
addAll(compilerConfigurationProvider.getCompilerConfiguration(dependencyModule).jvmClasspathRoots)
}
}
configuration.addJvmClasspathRoots(jvmClasspathRoots)
return jvmClasspathRoots
}
addDependenciesToClasspath(binaryDependencies)
addDependenciesToClasspath(binaryFriends)
if (binaryFriends.isNotEmpty()) {
configuration.put(JVMConfigurationKeys.FRIEND_PATHS, binaryFriends.flatMap {
val friendModule = dependencyProvider.getTestModule(it.moduleName)
listOfNotNull(
compiledClassesManager.getCompiledKotlinDirForModule(friendModule),
compiledClassesManager.getCompiledJavaDirForModule(friendModule)
)
}.map { it.absolutePath })
}
}
}
@@ -13,21 +13,8 @@ import org.jetbrains.kotlin.test.services.TestServices
class BackendKindExtractorImpl(testServices: TestServices) : BackendKindExtractor(testServices) {
override fun backendKind(targetBackend: TargetBackend?): BackendKind<*> {
return when (targetBackend) {
TargetBackend.ANY,
TargetBackend.JVM,
TargetBackend.JVM_OLD,
TargetBackend.ANDROID,
TargetBackend.JVM_MULTI_MODULE_OLD_AGAINST_IR -> BackendKinds.ClassicBackend
TargetBackend.JVM_IR,
TargetBackend.JVM_MULTI_MODULE_IR_AGAINST_OLD,
TargetBackend.JS,
TargetBackend.JS_IR,
TargetBackend.JS_IR_ES6,
TargetBackend.WASM -> BackendKinds.IrBackend
null -> BackendKind.NoBackend
}
if (targetBackend == null) return BackendKind.NoBackend
return if (targetBackend.isIR) BackendKinds.IrBackend
else BackendKinds.ClassicBackend
}
}
@@ -278,9 +278,14 @@ class ModuleStructureExtractorImpl(
}
private fun finishFile() {
val filename = currentFileName ?: defaultFileName
val actualDefaultFileName = if (currentModuleName == null) {
defaultFileName
} else {
"module_${currentModuleName}_$defaultFileName"
}
val filename = currentFileName ?: actualDefaultFileName
if (filesOfCurrentModule.any { it.name == filename }) {
error("File with name \"$filename\" already defined in module ${currentModuleName ?: defaultModuleName}")
error("File with name \"$filename\" already defined in module ${currentModuleName ?: actualDefaultFileName}")
}
filesOfCurrentModule.add(
TestFile(
@@ -0,0 +1,47 @@
/*
* 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.test.services.jvm
import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.output.writeAll
import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.kotlin.test.model.ArtifactKinds
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import java.io.File
class CompiledClassesManager(val testServices: TestServices) : TestService {
private val compiledKotlinCache = mutableMapOf<TestModule, File>()
private val compiledJavaCache = mutableMapOf<TestModule, File>()
fun getCompiledKotlinDirForModule(module: TestModule, classFileFactory: ClassFileFactory? = null): File {
return compiledKotlinCache.getOrPut(module) {
val outputDir = testServices.createTempDirectory("module_${module.name}_kotlin-classes")
@Suppress("NAME_SHADOWING")
val classFileFactory = classFileFactory
?: testServices.dependencyProvider.getArtifact(module, ArtifactKinds.Jvm).classFileFactory
val outputFileCollection = SimpleOutputFileCollection(classFileFactory.currentOutput)
val messageCollector = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
outputFileCollection.writeAll(outputDir, messageCollector, reportOutputFiles = false)
outputDir
}
}
fun getCompiledJavaDirForModule(module: TestModule): File? {
return compiledJavaCache[module]
}
fun getOrCreateCompiledJavaDirForModule(module: TestModule): File {
return compiledJavaCache.getOrPut(module) {
testServices.createTempDirectory("module_${module.name}_java-classes")
}
}
}
val TestServices.compiledClassesManager: CompiledClassesManager by TestServices.testServiceAccessor()
@@ -1,33 +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.test.services.jvm
import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.output.writeAll
import org.jetbrains.kotlin.test.model.ArtifactKinds
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import java.io.File
class CompiledJarManager(val testServices: TestServices) : TestService {
private val jarCache = mutableMapOf<TestModule, File>()
fun getCompiledJarForModule(module: TestModule): File {
return jarCache.getOrPut(module) {
val outputDir = testServices.createTempDirectory("module_${module.name}")
val classFileFactory = testServices.dependencyProvider.getArtifact(module, ArtifactKinds.Jvm).classFileFactory
val outputFileCollection = SimpleOutputFileCollection(classFileFactory.currentOutput)
val messageCollector = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
outputFileCollection.writeAll(outputDir, messageCollector, reportOutputFiles = false)
classFileFactory.releaseGeneratedOutput()
outputDir
}
}
}
val TestServices.compiledJarManager: CompiledJarManager by TestServices.testServiceAccessor()
@@ -3,13 +3,15 @@
* 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.test.services
package org.jetbrains.kotlin.test.services.sourceProviders
import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.AdditionalSourceProvider
import org.jetbrains.kotlin.test.services.TestServices
import java.io.File
class AdditionalDiagnosticsSourceFilesProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) {
@@ -0,0 +1,40 @@
/*
* 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.test.services.sourceProviders
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.AdditionalSourceProvider
import org.jetbrains.kotlin.test.services.TestServices
import java.io.File
class CodegenHelpersSourceFilesProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) {
companion object {
private const val HELPERS_PATH = "./compiler/testData/codegen/helpers"
private const val CLASSIC_BACKEND_PATH = "$HELPERS_PATH/CodegenTestHelpersOldBackend.kt"
private const val IR_BACKEND_PATH = "$HELPERS_PATH/CodegenTestHelpersIR.kt"
}
override val directives: List<DirectivesContainer> =
listOf(CodegenTestDirectives)
@OptIn(ExperimentalStdlibApi::class)
override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> {
if (CodegenTestDirectives.WITH_HELPERS !in module.directives) return emptyList()
return buildList {
val targetBackend = module.targetBackend ?: return@buildList
val helpersPath = if (targetBackend.isIR) {
IR_BACKEND_PATH
} else {
CLASSIC_BACKEND_PATH
}
add(File(helpersPath).toTestFile())
}
}
}
@@ -3,7 +3,7 @@
* 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.test.services
package org.jetbrains.kotlin.test.services.sourceProviders
import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives
import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives.CHECK_STATE_MACHINE
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.AdditionalSourceProvider
import org.jetbrains.kotlin.test.services.TestServices
import java.io.File
class CoroutineHelpersSourceFilesProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) {
@@ -28,9 +28,13 @@ val File.firTestDataFile: File
}
fun File.withExtension(extension: String): File {
return withSuffixAndExtension(suffix = "", extension)
}
fun File.withSuffixAndExtension(suffix: String, extension: String): File {
@Suppress("NAME_SHADOWING")
val extension = extension.removePrefix(".")
return parentFile.resolve("$nameWithoutExtension.$extension")
return parentFile.resolve("$nameWithoutExtension$suffix.$extension")
}
/*
@@ -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,
@@ -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() {
@@ -25,6 +25,8 @@ dependencies {
testCompile(project(":compiler:fir:entrypoint"))
testCompile(projectTests(":compiler:test-infrastructure-utils"))
testCompile(project(":kotlin-preloader"))
testCompile(androidDxJar()) { isTransitive = false }
testCompile(commonDep("com.android.tools:r8"))
testCompileOnly(intellijCoreDep()) { includeJars("intellij-core") }
testCompile(intellijDep()) {
@@ -0,0 +1,294 @@
/*
* 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 org.jetbrains.kotlin.test.KtAssert
import org.jetbrains.org.objectweb.asm.*
class BytecodeListingTextCollectingVisitor(
val filter: Filter,
val withSignatures: Boolean,
api: Int = Opcodes.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", Opcodes.ACC_PUBLIC, CLASS_OR_FIELD_OR_METHOD),
Modifier("protected", Opcodes.ACC_PROTECTED, CLASS_OR_FIELD_OR_METHOD),
Modifier("private", Opcodes.ACC_PRIVATE, CLASS_OR_FIELD_OR_METHOD),
Modifier("synthetic", Opcodes.ACC_SYNTHETIC, CLASS_OR_FIELD_OR_METHOD),
Modifier("bridge", Opcodes.ACC_BRIDGE, METHOD_ONLY),
Modifier("volatile", Opcodes.ACC_VOLATILE, FIELD_ONLY),
Modifier("synchronized", Opcodes.ACC_SYNCHRONIZED, METHOD_ONLY),
Modifier("varargs", Opcodes.ACC_VARARGS, METHOD_ONLY),
Modifier("transient", Opcodes.ACC_TRANSIENT, FIELD_ONLY),
Modifier("native", Opcodes.ACC_NATIVE, METHOD_ONLY),
Modifier("deprecated", Opcodes.ACC_DEPRECATED, CLASS_OR_FIELD_OR_METHOD),
Modifier("final", Opcodes.ACC_FINAL, CLASS_OR_FIELD_OR_METHOD),
Modifier("strict", Opcodes.ACC_STRICT, METHOD_ONLY),
Modifier("enum", Opcodes.ACC_ENUM, FIELD_ONLY), // ACC_ENUM modifier on class is handled in 'classOrInterface'
Modifier("abstract", Opcodes.ACC_ABSTRACT, CLASS_OR_METHOD, excludedMask = Opcodes.ACC_INTERFACE),
Modifier("static", Opcodes.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 Opcodes.ACC_ANNOTATION != 0 -> "annotation class"
access and Opcodes.ACC_ENUM != 0 -> "enum class"
access and Opcodes.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(Opcodes.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 Opcodes.ACC_VOLATILE != 0) addModifier("volatile", fieldDeclaration.annotations)
return object : FieldVisitor(Opcodes.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) {
KtAssert.assertNull("", descriptor)
declarationsInsideClass.add(Declaration("enclosing class $owner"))
} else {
KtAssert.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 -> {
KtAssert.assertNull("Anonymous classes should have neither innerName nor outerName. Name=$name, outerName=$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"))
}
}
}
}
@@ -11,17 +11,29 @@ import kotlin.collections.CollectionsKt;
import kotlin.io.FilesKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.test.Assertions;
import org.jetbrains.kotlin.test.JvmCompilationUtils;
import org.jetbrains.kotlin.test.KtAssert;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.StringsKt;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.tree.ClassNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.jetbrains.org.objectweb.asm.util.Textifier;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -165,4 +177,65 @@ public class CodegenTestUtil {
return javaFilePaths;
}
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 final String JAVA_COMPILATION_TARGET = System.getProperty("kotlin.test.java.compilation.target");
@Nullable
public 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;
}
public 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;
}
}
@@ -9,7 +9,7 @@ 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 org.jetbrains.kotlin.test.KtAssert;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -44,17 +44,17 @@ public class D8Checker {
static class TestDiagnosticsHandler implements DiagnosticsHandler {
@Override
public void error(Diagnostic diagnostic) {
Assert.fail("D8 dexing error: " + diagnostic.getDiagnosticMessage());
KtAssert.fail("D8 dexing error: " + diagnostic.getDiagnosticMessage());
}
@Override
public void warning(Diagnostic diagnostic) {
Assert.fail("D8 dexing warning: " + diagnostic.getDiagnosticMessage());
KtAssert.fail("D8 dexing warning: " + diagnostic.getDiagnosticMessage());
}
@Override
public void info(Diagnostic diagnostic) {
Assert.fail("D8 dexing info: " + diagnostic.getDiagnosticMessage());
KtAssert.fail("D8 dexing info: " + diagnostic.getDiagnosticMessage());
}
}
@@ -69,7 +69,7 @@ public class D8Checker {
D8.run(builder.build(), Executors.newSingleThreadExecutor());
}
catch (CompilationFailedException e) {
Assert.fail(generateExceptionMessage(e));
KtAssert.fail(generateExceptionMessage(e));
}
}
@@ -1,17 +1,6 @@
/*
* 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.
* 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;
@@ -23,8 +12,8 @@ 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.kotlin.test.KtAssert;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.junit.Assert;
import java.io.*;
import java.util.regex.Matcher;
@@ -52,7 +41,7 @@ public class DxChecker {
}
}
catch (Throwable e) {
Assert.fail(generateExceptionMessage(e));
KtAssert.fail(generateExceptionMessage(e));
}
}
}
@@ -1,17 +1,6 @@
/*
* 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.
* 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
@@ -94,10 +94,6 @@ fun generateJUnit3CompilerTests(args: Array<String>) {
model("parseCodeFragment/block", testMethod = "doBlockCodeFragmentParsingTest", extension = "kt")
}
testClass<AbstractBlackBoxCodegenTest> {
model("codegen/box", targetBackend = TargetBackend.JVM)
}
testClass<AbstractLightAnalysisModeTest> {
// "ranges/stepped" is excluded because it contains hundreds of generated tests and only have a box() method.
// There isn't much to be gained from running light analysis tests on them.
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.test.generators
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.runners.*
import org.jetbrains.kotlin.test.runners.codegen.AbstractBlackBoxCodegenTest
fun generateJUnit5CompilerTests(args: Array<String>) {
val excludedFirTestdataPattern = "^(.+)\\.fir\\.kts?\$"
@@ -53,6 +54,10 @@ fun generateJUnit5CompilerTests(args: Array<String>) {
model("foreignAnnotations/tests")
model("foreignAnnotations/java8Tests", excludeDirs = listOf("jspecify", "typeEnhancementOnCompiledJava"))
}
testClass<AbstractBlackBoxCodegenTest> {
model("codegen/box")
}
}
// ---------------------------------------------- FIR tests ----------------------------------------------
@@ -16223,11 +16223,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SerializationRegressions extends AbstractLightAnalysisModeTest {
@TestMetadata("transitiveClash.kt")
public void ignoreTransitiveClash() throws Exception {
runTest("compiler/testData/codegen/box/ir/serializationRegressions/transitiveClash.kt");
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
@@ -16261,6 +16256,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/ir/serializationRegressions/signatureClash.kt");
}
@TestMetadata("transitiveClash.kt")
public void testTransitiveClash() throws Exception {
runTest("compiler/testData/codegen/box/ir/serializationRegressions/transitiveClash.kt");
}
@TestMetadata("useImportedMember.kt")
public void testUseImportedMember() throws Exception {
runTest("compiler/testData/codegen/box/ir/serializationRegressions/useImportedMember.kt");