• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines.validator
6 
7 import org.junit.Test
8 import org.objectweb.asm.*
9 import org.objectweb.asm.ClassReader.*
10 import org.objectweb.asm.ClassWriter.*
11 import org.objectweb.asm.Opcodes.*
12 import java.util.jar.*
13 import kotlin.test.*
14 
15 class MavenPublicationAtomicfuValidator {
16     private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
17     private val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"
18 
19     @Test
testNoAtomicfuInClasspathnull20     fun testNoAtomicfuInClasspath() {
21         val result = runCatching { Class.forName("kotlinx.atomicfu.AtomicInt") }
22         assertTrue(result.exceptionOrNull() is ClassNotFoundException)
23     }
24 
25     @Test
testNoAtomicfuInMppJarnull26     fun testNoAtomicfuInMppJar() {
27         val clazz = Class.forName("kotlinx.coroutines.Job")
28         JarFile(clazz.protectionDomain.codeSource.location.file).checkForAtomicFu()
29     }
30 
31     @Test
testNoAtomicfuInAndroidJarnull32     fun testNoAtomicfuInAndroidJar() {
33         val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
34         JarFile(clazz.protectionDomain.codeSource.location.file).checkForAtomicFu()
35     }
36 
checkForAtomicFunull37     private fun JarFile.checkForAtomicFu() {
38         val foundClasses = mutableListOf<String>()
39         for (e in entries()) {
40             if (!e.name.endsWith(".class")) continue
41             val bytes = getInputStream(e).use { it.readBytes() }
42             // The atomicfu compiler plugin does not remove atomic properties from metadata,
43             // so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata.
44             // This may be reverted after the fix in the compiler plugin transformer (for Kotlin 1.8.0).
45             val outBytes = bytes.eraseMetadata()
46             if (outBytes.checkBytes()) {
47                 foundClasses += e.name // report error at the end with all class names
48             }
49         }
50         if (foundClasses.isNotEmpty()) {
51             error("Found references to atomicfu in jar file $name in the following class files: ${
52                 foundClasses.joinToString("") { "\n\t\t" + it }
53             }")
54         }
55         close()
56     }
57 
checkBytesnull58     private fun ByteArray.checkBytes(): Boolean {
59         loop@for (i in 0 until this.size - ATOMIC_FU_REF.size) {
60             for (j in 0 until ATOMIC_FU_REF.size) {
61                 if (this[i + j] != ATOMIC_FU_REF[j]) continue@loop
62             }
63             return true
64         }
65         return false
66     }
67 
ByteArraynull68     private fun ByteArray.eraseMetadata(): ByteArray {
69         val cw = ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES)
70         ClassReader(this).accept(object : ClassVisitor(ASM9, cw) {
71             override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
72                 return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible)
73             }
74         }, SKIP_FRAMES)
75         return cw.toByteArray()
76     }
77 }
78