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