[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.
This commit is contained in:
Svyatoslav Scherbina
2022-06-09 13:03:51 +03:00
parent 38723816b7
commit cee0731cef
5 changed files with 68 additions and 16 deletions
@@ -38,4 +38,5 @@ object KonanFqNames {
val eagerInitialization = FqName("kotlin.native.EagerInitialization") val eagerInitialization = FqName("kotlin.native.EagerInitialization")
val noReorderFields = FqName("kotlin.native.internal.NoReorderFields") val noReorderFields = FqName("kotlin.native.internal.NoReorderFields")
val objCName = FqName("kotlin.native.ObjCName") val objCName = FqName("kotlin.native.ObjCName")
val reflectionPackageName = FqName("kotlin.native.internal.ReflectionPackageName")
} }
@@ -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.ir.isAny
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference
import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.*
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.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
@@ -564,7 +561,15 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
} }
private fun getReflectionInfo(irClass: IrClass): ReflectionInfo { 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 relativeName: String?
val flags: Int val flags: Int
@@ -178,3 +178,14 @@ internal annotation class HasFreezeHook
@Retention(value = AnnotationRetention.BINARY) @Retention(value = AnnotationRetention.BINARY)
@InternalForKotlinNative @InternalForKotlinNative
annotation class GCUnsafeCall(val callee: String) 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)
@@ -152,6 +152,7 @@ private class ExtTestDataFile(
val args = mutableListOf<String>() val args = mutableListOf<String>()
testDataFileSettings.languageSettings.sorted().mapTo(args) { "-XXLanguage:$it" } testDataFileSettings.languageSettings.sorted().mapTo(args) { "-XXLanguage:$it" }
testDataFileSettings.optInsForCompiler.sorted().mapTo(args) { "-opt-in=$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" if (testDataFileSettings.expectActualLinker) args += "-Xexpect-actual-linker"
return TestCompilerArgs(args) return TestCompilerArgs(args)
} }
@@ -281,6 +282,12 @@ private class ExtTestDataFile(
file.addAfter(newPackageDirective, file.fileAnnotationList).ensureSurroundedByWhiteSpace() 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()) visitKtElement(file, file.collectAccessibleDeclarationNames())
} }
@@ -468,23 +475,13 @@ private class ExtTestDataFile(
filesToTransform.forEach { handler -> filesToTransform.forEach { handler ->
handler.accept(object : KtTreeVisitorVoid() { handler.accept(object : KtTreeVisitorVoid() {
override fun visitKtFile(file: KtFile) { override fun visitKtFile(file: KtFile) {
val oldFileAnnotationList = file.fileAnnotationList
val newFileAnnotationList = handler.psiFactory.createFile(buildString { val newFileAnnotationList = handler.psiFactory.createFile(buildString {
testDataFileSettings.optInsForSourceCode.forEach { testDataFileSettings.optInsForSourceCode.forEach {
appendLine(getAnnotationText(it)) appendLine(getAnnotationText(it))
} }
}).fileAnnotationList!! }).fileAnnotationList!!
if (oldFileAnnotationList != null) { file.addAnnotations(newFileAnnotationList)
// 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()
}
} }
}) })
} }
@@ -493,6 +490,19 @@ private class ExtTestDataFile(
return allFileLevelAnnotationsSorted 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`). */ /** Finds the fully-qualified name of the entry point function (aka `fun box(): String`). */
private fun findEntryPoint(): String = with(structure) { private fun findEntryPoint(): String = with(structure) {
val result = mutableListOf<String>() val result = mutableListOf<String>()
@@ -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 const val STATIC_CACHE_DIR_NAME = "__static_cache__"
internal fun prettyHash(hash: Int): String = hash.toUInt().toString(16).padStart(8, '0') 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<Char> = mutableSetOf<Char>().apply {
addAll('a' .. 'z')
addAll('A' .. 'Z')
addAll('0' .. '9')
addAll(listOf('_', '@', ':', ';', '.', ',', '{', '}', '=', '[', ']', '^', '#', '*', ' ', '(', ')'))
}