From cee0731cef8b5a14816720b72995fa52ea365146 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 9 Jun 2022 13:03:51 +0300 Subject: [PATCH] [Native][tests] Keep reflection package name when renaming packages The test engine renames packages in test files to allow group many tests into single compilation for performance reasons. As a result, reflection APIs return different package names. To deal with this, the test engine uses certain heuristics to detect tests using reflection APIs, and disables grouping (and thus package renaming) for them. This commit provides an alternative solution for the same problem -- now the test engine instructs the compiler to use original package names for reflection information, by means of the introduced `ReflectionPackageName` annotation. --- .../kotlin/backend/konan/KonanFqNames.kt | 1 + .../backend/konan/llvm/RTTIGenerator.kt | 15 ++++++--- .../kotlin/native/internal/Annotations.kt | 11 +++++++ .../support/group/ExtTestCaseGroupProvider.kt | 32 ++++++++++++------- .../konan/blackboxtest/support/util/Names.kt | 25 +++++++++++++++ 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt index f10c7c25822..68e970389da 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt @@ -38,4 +38,5 @@ object KonanFqNames { val eagerInitialization = FqName("kotlin.native.EagerInitialization") val noReorderFields = FqName("kotlin.native.internal.NoReorderFields") val objCName = FqName("kotlin.native.ObjCName") + val reflectionPackageName = FqName("kotlin.native.internal.ReflectionPackageName") } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index a8125617945..97bef846fb7 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -12,10 +12,7 @@ import org.jetbrains.kotlin.backend.konan.ir.getSuperClassNotAny import org.jetbrains.kotlin.backend.konan.ir.isAny import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference import org.jetbrains.kotlin.builtins.PrimitiveType -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrField -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName @@ -564,7 +561,15 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { } private fun getReflectionInfo(irClass: IrClass): ReflectionInfo { - val packageName: String = irClass.getPackageFragment().fqName.asString() // Compute and store package name in TypeInfo anyways. + val packageFragment = irClass.getPackageFragment() + val reflectionPackageName = if (packageFragment is IrFile) { + // This annotation is used by test infrastructure. + packageFragment.annotations.findAnnotation(KonanFqNames.reflectionPackageName)?.getAnnotationStringValue() + } else { + null + } + + val packageName: String = reflectionPackageName ?: packageFragment.fqName.asString() // Compute and store package name in TypeInfo anyways. val relativeName: String? val flags: Int diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index 1a6aaacd938..153e461386d 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -178,3 +178,14 @@ internal annotation class HasFreezeHook @Retention(value = AnnotationRetention.BINARY) @InternalForKotlinNative annotation class GCUnsafeCall(val callee: String) + +/** + * Marks a declaration that is internal for Kotlin/Native tests and shouldn't be used externally. + */ +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +@Retention(value = AnnotationRetention.BINARY) +internal annotation class InternalForKotlinNativeTests + +@InternalForKotlinNativeTests +@Target(AnnotationTarget.FILE) +public annotation class ReflectionPackageName(val name: String) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt index b0693322d55..3ab0f7ee51e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt @@ -152,6 +152,7 @@ private class ExtTestDataFile( val args = mutableListOf() testDataFileSettings.languageSettings.sorted().mapTo(args) { "-XXLanguage:$it" } testDataFileSettings.optInsForCompiler.sorted().mapTo(args) { "-opt-in=$it" } + args += "-opt-in=kotlin.native.internal.InternalForKotlinNativeTests" // for ReflectionPackageName if (testDataFileSettings.expectActualLinker) args += "-Xexpect-actual-linker" return TestCompilerArgs(args) } @@ -281,6 +282,12 @@ private class ExtTestDataFile( file.addAfter(newPackageDirective, file.fileAnnotationList).ensureSurroundedByWhiteSpace() } + // Add @ReflectionPackageName annotation to make the compiler use original package name in the reflective information. + val annotationText = + "kotlin.native.internal.ReflectionPackageName(${oldPackageName.asString().quoteAsKotlinStringLiteral()})" + val fileAnnotationList = handler.psiFactory.createFileAnnotationListWithAnnotation(annotationText) + file.addAnnotations(fileAnnotationList) + visitKtElement(file, file.collectAccessibleDeclarationNames()) } @@ -468,23 +475,13 @@ private class ExtTestDataFile( filesToTransform.forEach { handler -> handler.accept(object : KtTreeVisitorVoid() { override fun visitKtFile(file: KtFile) { - val oldFileAnnotationList = file.fileAnnotationList - val newFileAnnotationList = handler.psiFactory.createFile(buildString { testDataFileSettings.optInsForSourceCode.forEach { appendLine(getAnnotationText(it)) } }).fileAnnotationList!! - if (oldFileAnnotationList != null) { - // Add new annotations to the old ones. - newFileAnnotationList.annotationEntries.forEach { - oldFileAnnotationList.add(it).ensureSurroundedByWhiteSpace() - } - } else { - // Insert the annotations list immediately before package directive. - file.addBefore(newFileAnnotationList, file.packageDirective).ensureSurroundedByWhiteSpace() - } + file.addAnnotations(newFileAnnotationList) } }) } @@ -493,6 +490,19 @@ private class ExtTestDataFile( return allFileLevelAnnotationsSorted } + private fun KtFile.addAnnotations(fileAnnotationList: KtFileAnnotationList) { + val oldFileAnnotationList = this.fileAnnotationList + if (oldFileAnnotationList != null) { + // Add new annotations to the old ones. + fileAnnotationList.annotationEntries.forEach { + oldFileAnnotationList.add(it).ensureSurroundedByWhiteSpace() + } + } else { + // Insert the annotations list immediately before package directive. + this.addBefore(fileAnnotationList, packageDirective).ensureSurroundedByWhiteSpace() + } + } + /** Finds the fully-qualified name of the entry point function (aka `fun box(): String`). */ private fun findEntryPoint(): String = with(structure) { val result = mutableListOf() diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt index f11ce9ccd05..c6a09e78b74 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt @@ -31,3 +31,28 @@ internal const val LAUNCHER_MODULE_NAME = "__launcher__" // Used only in KLIB te internal const val STATIC_CACHE_DIR_NAME = "__static_cache__" internal fun prettyHash(hash: Int): String = hash.toUInt().toString(16).padStart(8, '0') + +/** + * Returns the expression to be parsed by Kotlin as string literal with given contents, + * i.e. transforms `foo$bar` to `"foo\$bar"`. + */ +internal fun String.quoteAsKotlinStringLiteral(): String = buildString { + append('"') + + this@quoteAsKotlinStringLiteral.forEach { c -> + when (c) { + in charactersAllowedInKotlinStringLiterals -> append(c) + '$' -> append("\\$") + else -> append("\\u" + "%04X".format(c.code)) + } + } + + append('"') +} + +private val charactersAllowedInKotlinStringLiterals: Set = mutableSetOf().apply { + addAll('a' .. 'z') + addAll('A' .. 'Z') + addAll('0' .. '9') + addAll(listOf('_', '@', ':', ';', '.', ',', '{', '}', '=', '[', ']', '^', '#', '*', ' ', '(', ')')) +}