1 /*
<lambda>null2 * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 package kotlinx.atomicfu.gradle.plugin.test.framework.checker
6
7 import kotlinx.atomicfu.gradle.plugin.test.framework.runner.GradleBuild
8 import kotlinx.atomicfu.gradle.plugin.test.framework.runner.cleanAndBuild
9 import org.objectweb.asm.*
10 import java.io.File
11 import java.net.URLClassLoader
12 import kotlin.test.assertFalse
13
14 internal abstract class ArtifactChecker(private val targetDir: File) {
15
16 private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
17 protected val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"
18
19 protected val projectName = targetDir.name.substringBeforeLast("-")
20
21 val buildDir
22 get() = targetDir.resolve("build").also {
23 require(it.exists() && it.isDirectory) { "Could not find `build/` directory in the target directory of the project $projectName: ${targetDir.path}" }
24 }
25
26 abstract fun checkReferences()
27
28 protected fun ByteArray.findAtomicfuRef(): Boolean {
29 loop@for (i in 0 .. this.size - ATOMIC_FU_REF.size) {
30 for (j in ATOMIC_FU_REF.indices) {
31 if (this[i + j] != ATOMIC_FU_REF[j]) continue@loop
32 }
33 return true
34 }
35 return false
36 }
37 }
38
39 private class BytecodeChecker(targetDir: File) : ArtifactChecker(targetDir) {
40
checkReferencesnull41 override fun checkReferences() {
42 val atomicfuDir = buildDir.resolve("classes/atomicfu/")
43 (if (atomicfuDir.exists() && atomicfuDir.isDirectory) atomicfuDir else buildDir).let {
44 it.walkBottomUp().filter { it.isFile && it.name.endsWith(".class") }.forEach { clazz ->
45 assertFalse(clazz.readBytes().eraseMetadata().findAtomicfuRef(), "Found kotlinx/atomicfu in class file ${clazz.path}")
46 }
47 }
48 }
49
50 // The atomicfu compiler plugin does not remove atomic properties from metadata,
51 // so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata.
52 // This may be reverted after the fix in the compiler plugin transformer (See #254).
ByteArraynull53 private fun ByteArray.eraseMetadata(): ByteArray {
54 val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
55 ClassReader(this).accept(object : ClassVisitor(Opcodes.ASM9, cw) {
56 override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
57 return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible)
58 }
59 }, ClassReader.SKIP_FRAMES)
60 return cw.toByteArray()
61 }
62 }
63
64 private class KlibChecker(targetDir: File) : ArtifactChecker(targetDir) {
65
66 val nativeJar = System.getProperty("kotlin.native.jar")
67
68 val classLoader: ClassLoader = URLClassLoader(arrayOf(File(nativeJar).toURI().toURL()), this.javaClass.classLoader)
69
invokeKlibToolnull70 private fun invokeKlibTool(
71 kotlinNativeClassLoader: ClassLoader?,
72 klibFile: File,
73 functionName: String,
74 hasOutput: Boolean,
75 vararg args: Any
76 ): String {
77 val libraryClass = Class.forName("org.jetbrains.kotlin.cli.klib.Library", true, kotlinNativeClassLoader)
78 val entryPoint = libraryClass.declaredMethods.single { it.name == functionName }
79 val lib = libraryClass.getDeclaredConstructor(String::class.java, String::class.java, String::class.java)
80 .newInstance(klibFile.canonicalPath, null, "host")
81
82 val output = StringBuilder()
83
84 // This is a hack. It would be better to get entryPoint properly
85 if (args.isNotEmpty()) {
86 entryPoint.invoke(lib, output, *args)
87 } else if (hasOutput) {
88 entryPoint.invoke(lib, output)
89 } else {
90 entryPoint.invoke(lib)
91 }
92 return output.toString()
93 }
94
checkReferencesnull95 override fun checkReferences() {
96 val classesDir = buildDir.resolve("classes/kotlin/")
97 if (classesDir.exists() && classesDir.isDirectory) {
98 classesDir.walkBottomUp().singleOrNull { it.isFile && it.name == "$projectName.klib" }?.let { klib ->
99 val klibIr = invokeKlibTool(
100 kotlinNativeClassLoader = classLoader,
101 klibFile = klib,
102 functionName = "ir",
103 hasOutput = true,
104 false
105 )
106 assertFalse(klibIr.toByteArray().findAtomicfuRef(), "Found kotlinx/atomicfu in klib ${klib.path}:\n $klibIr")
107 } ?: error(" Native klib $projectName.klib is not found in $classesDir")
108 }
109 }
110 }
111
buildAndCheckBytecodenull112 internal fun GradleBuild.buildAndCheckBytecode() {
113 val buildResult = cleanAndBuild()
114 require(buildResult.isSuccessful) { "Build of the project $projectName failed:\n ${buildResult.output}" }
115 BytecodeChecker(this.targetDir).checkReferences()
116 }
117
118 // TODO: klib checks are skipped for now because of this problem KT-61143
buildAndCheckNativeKlibnull119 internal fun GradleBuild.buildAndCheckNativeKlib() {
120 val buildResult = cleanAndBuild()
121 require(buildResult.isSuccessful) { "Build of the project $projectName failed:\n ${buildResult.output}" }
122 KlibChecker(this.targetDir).checkReferences()
123 }
124