From e34a5fdddfa90326642c44f80e7ff9f5a9639394 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 16:29:24 +0800 Subject: [PATCH] Migrate BytecodeRemappingTest to using ASM --- build.gradle.kts | 22 ++- gradle/libs.versions.toml | 2 +- .../shadow/internal/BytecodeRemappingTest.kt | 182 +++++++++++------- 3 files changed, 125 insertions(+), 81 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index db3116c98..ff532367b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,12 +30,18 @@ dokka { dokkaPublications.html { outputDirectory = rootDir.resolve("docs/api") } kotlin { explicitApi() @OptIn(ExperimentalAbiValidation::class) abiValidation { enabled = true } + val jdkRelease = "17" compilerOptions { allWarningsAsErrors = true // https://docs.gradle.org/current/userguide/compatibility.html#kotlin apiVersion = KotlinVersion.KOTLIN_2_2 languageVersion = apiVersion + jvmTarget = JvmTarget.fromTarget(jdkRelease) jvmDefault = JvmDefaultMode.NO_COMPATIBILITY + freeCompilerArgs.add("-Xjdk-release=$jdkRelease") + } + target.compilations.configureEach { + compileJavaTaskProvider { options.release = jdkRelease.toInt() } } } @@ -132,7 +138,12 @@ dependencies { } testing.suites { - getByName("test") { dependencies { implementation(libs.xmlunit) } } + getByName("test") { + dependencies { + implementation(libs.xmlunit) + implementation(libs.asm.commons) + } + } register("documentTest") { targets.configureEach { testTask { @@ -216,15 +227,6 @@ kotlin.target.compilations { } } -tasks.compileKotlin { - compilerOptions { - jvmTarget = JvmTarget.fromTarget(libs.versions.jdkRelease.get()) - freeCompilerArgs.add("-Xjdk-release=${libs.versions.jdkRelease.get()}") - } -} - -tasks.compileJava { options.release = libs.versions.jdkRelease.get().toInt() } - tasks.pluginUnderTestMetadata { pluginClasspath.from(testPluginClasspath) } tasks.check { dependsOn(tasks.withType()) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79163c122..d9e381268 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,6 @@ minGradle = "9.0.0" kotlin = "2.3.20" moshi = "1.15.2" pluginPublish = "2.1.0" -jdkRelease = "17" [libraries] apache-ant = "org.apache.ant:ant:1.10.15" @@ -20,6 +19,7 @@ plexus-xml = "org.codehaus.plexus:plexus-xml:4.1.1" xmlunit = "org.xmlunit:xmlunit-legacy:2.11.0" moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } +asm-commons = "org.ow2.asm:asm-commons:9.9.1" foojayResolver = "org.gradle.toolchains.foojay-resolver-convention:org.gradle.toolchains.foojay-resolver-convention.gradle.plugin:1.0.0" develocity = "com.gradle:develocity-gradle-plugin:4.3.2" diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt index 4ff39de95..8f09e7f8a 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt @@ -9,10 +9,6 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator import com.github.jengelman.gradle.plugins.shadow.testkit.requireResourceAsPath import com.github.jengelman.gradle.plugins.shadow.util.noOpDelegate import java.io.File -import java.lang.classfile.Attributes -import java.lang.classfile.ClassFile -import java.lang.classfile.instruction.InvokeInstruction -import java.lang.classfile.instruction.TypeCheckInstruction import java.nio.file.Path import kotlin.io.path.copyTo import kotlin.io.path.createParentDirectories @@ -22,6 +18,12 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.FieldVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode /** * The cases reflect the cases in @@ -62,8 +64,8 @@ class BytecodeRemappingTest { fun classNameIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.thisClass().asInternalName()) + val classNode = result.toClassNode() + assertThat(classNode.name) .isEqualTo($$"com/example/relocated/BytecodeRemappingTest$FixtureSubject") } @@ -71,11 +73,8 @@ class BytecodeRemappingTest { fun annotationIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val annotationsAttr = classModel.findAttribute(Attributes.runtimeVisibleAnnotations()) - assertThat(annotationsAttr.isPresent).isTrue() - val annotationDescriptors = - annotationsAttr.get().annotations().map { it.className().stringValue() } + val classNode = result.toClassNode() + val annotationDescriptors = classNode.visibleAnnotations.orEmpty().map { it.desc } assertThat(annotationDescriptors) .contains($$"Lcom/example/relocated/BytecodeRemappingTest$FixtureAnnotation;") } @@ -88,24 +87,24 @@ class BytecodeRemappingTest { val result = details.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase) + val classNode = result.toClassNode() + assertThat(classNode.name).isEqualTo(relocatedFixtureBase) } @Test fun superclassIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase) + val classNode = result.toClassNode() + assertThat(classNode.superName).isEqualTo(relocatedFixtureBase) } @Test fun fieldDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + val classNode = result.toClassNode() + val fieldDescriptors = classNode.fields.map { it.desc } assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;") } @@ -113,8 +112,8 @@ class BytecodeRemappingTest { fun arrayFieldDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + val classNode = result.toClassNode() + val fieldDescriptors = classNode.fields.map { it.desc } assertThat(fieldDescriptors).contains("[L$relocatedFixtureBase;") } @@ -122,8 +121,8 @@ class BytecodeRemappingTest { fun array2dFieldDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + val classNode = result.toClassNode() + val fieldDescriptors = classNode.fields.map { it.desc } assertThat(fieldDescriptors).contains("[[L$relocatedFixtureBase;") } @@ -131,8 +130,8 @@ class BytecodeRemappingTest { fun methodDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + val classNode = result.toClassNode() + val methodDescriptors = classNode.methods.map { it.desc } assertThat(methodDescriptors).contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @@ -140,8 +139,8 @@ class BytecodeRemappingTest { fun methodMultipleArgsIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + val classNode = result.toClassNode() + val methodDescriptors = classNode.methods.map { it.desc } assertThat(methodDescriptors) .contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @@ -151,8 +150,8 @@ class BytecodeRemappingTest { fun primitivePlusClassMethodIsRelocated(primitiveDescriptor: Char) { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + val classNode = result.toClassNode() + val methodDescriptors = classNode.methods.map { it.desc } assertThat(methodDescriptors) .contains("(${primitiveDescriptor}L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @@ -161,12 +160,8 @@ class BytecodeRemappingTest { fun stringConstantIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - // Find the constant string in the bytecode. - val stringConstants = - classModel.constantPool().mapNotNull { entry -> - if (entry is java.lang.classfile.constantpool.StringEntry) entry.stringValue() else null - } + val classNode = result.toClassNode() + val stringConstants = classNode.allStringConstants() assertThat(stringConstants) .contains($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") } @@ -183,11 +178,8 @@ class BytecodeRemappingTest { ) val result = fixtureSubjectDetails.remapClass(skipRelocators) - val classModel = ClassFile.of().parse(result) - val stringConstants = - classModel.constantPool().mapNotNull { entry -> - if (entry is java.lang.classfile.constantpool.StringEntry) entry.stringValue() else null - } + val classNode = result.toClassNode() + val stringConstants = classNode.allStringConstants() assertThat(stringConstants) .doesNotContain($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") } @@ -196,11 +188,8 @@ class BytecodeRemappingTest { fun multiClassDescriptorStringConstantIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val stringConstants = - classModel.constantPool().mapNotNull { entry -> - if (entry is java.lang.classfile.constantpool.StringEntry) entry.stringValue() else null - } + val classNode = result.toClassNode() + val stringConstants = classNode.allStringConstants() // Verify that two adjacent class references in a single string constant are both relocated // (regression test for the issue-1403 pattern). assertThat(stringConstants) @@ -213,9 +202,8 @@ class BytecodeRemappingTest { fun interfaceIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val interfaces = classModel.interfaces().map { it.asInternalName() } - assertThat(interfaces) + val classNode = result.toClassNode() + assertThat(classNode.interfaces) .contains($$"com/example/relocated/BytecodeRemappingTest$FixtureInterface") } @@ -223,24 +211,18 @@ class BytecodeRemappingTest { fun signatureIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val method = classModel.methods().first { it.methodName().stringValue() == "methodWithGeneric" } - val signatureAttr = method.findAttribute(Attributes.signature()) - assertThat(signatureAttr.isPresent).isTrue() - val sig = signatureAttr.get().signature().stringValue() - assertThat(sig).contains("L$relocatedFixtureBase;") + val classNode = result.toClassNode() + val method = classNode.methods.first { it.name == "methodWithGeneric" } + assertThat(method.signature).contains("L$relocatedFixtureBase;") } @Test fun localVariableIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val method = classModel.methods().first { it.methodName().stringValue() == "method" } - val code = method.code().get() - val lvt = code.findAttribute(Attributes.localVariableTable()) - assertThat(lvt.isPresent).isTrue() - val descriptors = lvt.get().localVariables().map { it.type().stringValue() } + val classNode = result.toClassNode() + val method = classNode.methods.first { it.name == "method" } + val descriptors = method.localVariables.orEmpty().map { it.desc } assertThat(descriptors).contains("L$relocatedFixtureBase;") } @@ -248,24 +230,84 @@ class BytecodeRemappingTest { fun instructionIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val method = - classModel.methods().first { it.methodName().stringValue() == "methodWithCheckCast" } - val code = method.code().get() + val classReader = ClassReader(result) + var hasRelocatedCheckCast = false + var hasRelocatedInvoke = false + + classReader.accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visitMethod( + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array?, + ): MethodVisitor? { + if (name != "methodWithCheckCast") return null + return object : MethodVisitor(Opcodes.ASM9) { + override fun visitTypeInsn(opcode: Int, type: String) { + if (opcode == Opcodes.CHECKCAST && type == relocatedFixtureBase) { + hasRelocatedCheckCast = true + } + } + + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + if (owner == relocatedFixtureBase) { + hasRelocatedInvoke = true + } + } + } + } + }, + 0, + ) - val hasRelocatedCheckCast = - code.elementStream().anyMatch { element -> - element is TypeCheckInstruction && element.type().asInternalName() == relocatedFixtureBase - } assertThat(hasRelocatedCheckCast).isTrue() + assertThat(hasRelocatedInvoke).isTrue() + } - val hasRelocatedInvoke = - code.elementStream().anyMatch { element -> - element is InvokeInstruction && element.owner().asInternalName() == relocatedFixtureBase + private fun ClassNode.allStringConstants(): List { + val strings = mutableListOf() + this.accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visitMethod( + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array?, + ): MethodVisitor { + return object : MethodVisitor(Opcodes.ASM9) { + override fun visitLdcInsn(value: Any?) { + if (value is String) strings.add(value) + } + } + } + + override fun visitField( + access: Int, + name: String, + desc: String, + signature: String?, + value: Any?, + ): FieldVisitor? { + if (value is String) strings.add(value) + return null + } } - assertThat(hasRelocatedInvoke).isTrue() + ) + return strings } + private fun ByteArray.toClassNode(): ClassNode = + ClassNode().also { ClassReader(this).accept(it, 0) } + private fun KClass<*>.toFileCopyDetails() = object : FileCopyDetails by noOpDelegate() { private val _path = java.name.replace('.', '/') + ".class"