• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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