From 299a6d71739ce5b352c9daee0bf2f1176d7d2133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:24:29 +0000 Subject: [PATCH 1/6] Initial plan From b061477c5ef6ad63b2118842358f35703199f591 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:41:27 +0000 Subject: [PATCH 2/6] Initial plan Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- .../jdeb/shaded/objectweb/asm/Opcodes.class | Bin 0 -> 7826 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class diff --git a/org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class b/org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class new file mode 100644 index 0000000000000000000000000000000000000000..e037ee5dd076acdc1392debdf49c195a1f545d13 GIT binary patch literal 7826 zcmZwL2YA#*76tzG?rz?tY}@+THo2) zwXELPq@+n$rZ!61PtICquw7`LW*N&(n8C(`o}Ms6;&?{F42|QN36qTDSqW1U$Fmb= zn8f2y*ObX+XJLzzcbgqAuyYA(xSW}gtM(;bS=Cc%YwfGFA7Gi`OS^mWm6hFVT00ZC zQKXJ7_{Dt6DW{#5ypys6Cyf*6pcxUJXtoRU(9hD8@|1*0n0>>zK`!4fjHgOmAI8%p zo*c%wFBwa{7WZXLLm1<}jF}q7xG!TcAcIP{D`TcxCQ+I)S;83_qD^CFYN$=l(okER zt)Vu!zlPc#ZW1+Ua|ddutsSJHHa15?ZS7zQ8??2#8ft5{hT2-AhT2+6Lv1asp|<8| zsI6rzGptm`AeLNbL1ODx8O7mtDphVNHs;)v+y@rP2rbFGC5*Hb4YJJ0u*eUbR5wD)V?<6-E1ZXdLzy#0FYP6`gzS8W|<*D z8KWT=4cDZc?=^`=po7_RlM`fICxK0i8(T1);sth#(q zv~#$fxa5CQ3N22`m6UPWvga%auj?-y8(-HyXXtZA60w-KZbhFn$hl7u$$$sGi3?z*e3!xu?@Y7+^)XLvPusg(WErefb@fL~`@Dhqcyo2Hp zFQJ-TSew)kg;GPNS{jA=#;C2H($~gFI31YhvE{P)A%q{EOC^0ct7>*5y&ho zu(ifAV%JXVG#rUI7say2XQFrs;&c?ZNq(W&D4lGV{3xzSeiSd2{3u=~`OQUr*)Ny; zDDIH_C|)7?QGAr-r`?j&-zoV~yi)R`xJ&Y*xLfk`j=VR`D#?%Hqa{Czdn7-KdnMm< z^p({o`BA)D@}qc-vXxOL3?$Eh8d~$ObxY#v!uQT ze4ZI|wuYK;j)t0WF2YQhaGr)*+4&l3!UYJ^VZwzPYQjYtYGoHAbQ^RfT%w`&>{1PN zYrRZ5pK8{1b~%P3gIFuMB7)Asl|VW^vp#~Jxk_$0c?m(ZK{J@v{xf8>5NI~cOE`)hUE4>@rL11GOE-Y|^R8|u2!N)+(_vaN`s%7`$ zdB&m-ih22dF!zC2_c8AWvGij;AP;|)4}x};58)kB)dqAKTX^+@?O_!3Z`0D&M(hFc zCNF*j=AHdfeD?g;PA*B0^%q9lv=ly$8;kbt{_n$0mKhzE_3z%2`b7Wr=|!I19DYk~ z#<0n*j`L5V6A0~m-d-er3ca?u;Az=rdT4*nc-#n`;KHjkt_HrlvpRbZy z>qYCO;noY*%eCfebImY(o4;CXt_`EtYt40G^k%KOK8)V3H8+IOyS3)VFnYh%+!RJ1 zV(e4hUA=v+U412K8Us`PEoPaz{OYd0j+K>CM{mcH&WhdD)!o zbXC07E0z|9hC=u1p0-M+qq8y;Kbqt>r1{F!;WwlF1pOEqgx`sE_62rnWph7Tnl1|L4aM+|UzfR7~0c(e}ik^ycbw!x)Zg-Jo-0mjJxZOjRal4l+^L!s!=J|f|1Mq|7 zhu{t5hvALnN8m@v^4vd0ejMIJegfW1eiD9){4~6U{0#go`8oJ`@(b{b!-Z@U*I}>F^BlOn4S~HoQOi0Qf-iLGT>%!SGzN4L6cgaGLDE8FCYxCA;t;MS0|rx>#8i&L6pqIf V?u4n^9aFcrwH{etAZw5r@*kFopj7|> literal 0 HcmV?d00001 From e34a5fdddfa90326642c44f80e7ff9f5a9639394 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 16:29:24 +0800 Subject: [PATCH 3/6] 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" From 8f775809df9a49abfb26235772c6cc5845884f42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:52:05 +0000 Subject: [PATCH 4/6] Migrate BytecodeRemappingTest from java.lang.classfile to ASM Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- .../jdeb/shaded/objectweb/asm/Opcodes.class | Bin 7826 -> 0 bytes .../shadow/internal/BytecodeRemappingTest.kt | 224 +++++++++++------- 2 files changed, 138 insertions(+), 86 deletions(-) delete mode 100644 org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class diff --git a/org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class b/org/vafer/jdeb/shaded/objectweb/asm/Opcodes.class deleted file mode 100644 index e037ee5dd076acdc1392debdf49c195a1f545d13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7826 zcmZwL2YA#*76tzG?rz?tY}@+THo2) zwXELPq@+n$rZ!61PtICquw7`LW*N&(n8C(`o}Ms6;&?{F42|QN36qTDSqW1U$Fmb= zn8f2y*ObX+XJLzzcbgqAuyYA(xSW}gtM(;bS=Cc%YwfGFA7Gi`OS^mWm6hFVT00ZC zQKXJ7_{Dt6DW{#5ypys6Cyf*6pcxUJXtoRU(9hD8@|1*0n0>>zK`!4fjHgOmAI8%p zo*c%wFBwa{7WZXLLm1<}jF}q7xG!TcAcIP{D`TcxCQ+I)S;83_qD^CFYN$=l(okER zt)Vu!zlPc#ZW1+Ua|ddutsSJHHa15?ZS7zQ8??2#8ft5{hT2-AhT2+6Lv1asp|<8| zsI6rzGptm`AeLNbL1ODx8O7mtDphVNHs;)v+y@rP2rbFGC5*Hb4YJJ0u*eUbR5wD)V?<6-E1ZXdLzy#0FYP6`gzS8W|<*D z8KWT=4cDZc?=^`=po7_RlM`fICxK0i8(T1);sth#(q zv~#$fxa5CQ3N22`m6UPWvga%auj?-y8(-HyXXtZA60w-KZbhFn$hl7u$$$sGi3?z*e3!xu?@Y7+^)XLvPusg(WErefb@fL~`@Dhqcyo2Hp zFQJ-TSew)kg;GPNS{jA=#;C2H($~gFI31YhvE{P)A%q{EOC^0ct7>*5y&ho zu(ifAV%JXVG#rUI7say2XQFrs;&c?ZNq(W&D4lGV{3xzSeiSd2{3u=~`OQUr*)Ny; zDDIH_C|)7?QGAr-r`?j&-zoV~yi)R`xJ&Y*xLfk`j=VR`D#?%Hqa{Czdn7-KdnMm< z^p({o`BA)D@}qc-vXxOL3?$Eh8d~$ObxY#v!uQT ze4ZI|wuYK;j)t0WF2YQhaGr)*+4&l3!UYJ^VZwzPYQjYtYGoHAbQ^RfT%w`&>{1PN zYrRZ5pK8{1b~%P3gIFuMB7)Asl|VW^vp#~Jxk_$0c?m(ZK{J@v{xf8>5NI~cOE`)hUE4>@rL11GOE-Y|^R8|u2!N)+(_vaN`s%7`$ zdB&m-ih22dF!zC2_c8AWvGij;AP;|)4}x};58)kB)dqAKTX^+@?O_!3Z`0D&M(hFc zCNF*j=AHdfeD?g;PA*B0^%q9lv=ly$8;kbt{_n$0mKhzE_3z%2`b7Wr=|!I19DYk~ z#<0n*j`L5V6A0~m-d-er3ca?u;Az=rdT4*nc-#n`;KHjkt_HrlvpRbZy z>qYCO;noY*%eCfebImY(o4;CXt_`EtYt40G^k%KOK8)V3H8+IOyS3)VFnYh%+!RJ1 zV(e4hUA=v+U412K8Us`PEoPaz{OYd0j+K>CM{mcH&WhdD)!o zbXC07E0z|9hC=u1p0-M+qq8y;Kbqt>r1{F!;WwlF1pOEqgx`sE_62rnWph7Tnl1|L4aM+|UzfR7~0c(e}ik^ycbw!x)Zg-Jo-0mjJxZOjRal4l+^L!s!=J|f|1Mq|7 zhu{t5hvALnN8m@v^4vd0ejMIJegfW1eiD9){4~6U{0#go`8oJ`@(b{b!-Z@U*I}>F^BlOn4S~HoQOi0Qf-iLGT>%!SGzN4L6cgaGLDE8FCYxCA;t;MS0|rx>#8i&L6pqIf V?u4n^9aFcrwH{etAZw5r@*kFopj7|> 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..353277c8b 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 @@ -4,15 +4,10 @@ import assertk.assertThat import assertk.assertions.contains import assertk.assertions.doesNotContain import assertk.assertions.isEqualTo -import assertk.assertions.isTrue 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 +17,13 @@ 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.vafer.jdeb.shaded.objectweb.asm.AnnotationVisitor +import org.vafer.jdeb.shaded.objectweb.asm.ClassReader +import org.vafer.jdeb.shaded.objectweb.asm.ClassVisitor +import org.vafer.jdeb.shaded.objectweb.asm.FieldVisitor +import org.vafer.jdeb.shaded.objectweb.asm.Label +import org.vafer.jdeb.shaded.objectweb.asm.MethodVisitor +import org.vafer.jdeb.shaded.objectweb.asm.Opcodes /** * The cases reflect the cases in @@ -62,8 +64,7 @@ class BytecodeRemappingTest { fun classNameIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.thisClass().asInternalName()) + assertThat(ClassReader(result).className) .isEqualTo($$"com/example/relocated/BytecodeRemappingTest$FixtureSubject") } @@ -71,12 +72,7 @@ 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() } - assertThat(annotationDescriptors) + assertThat(result.classInfo().annotationDescriptors) .contains($$"Lcom/example/relocated/BytecodeRemappingTest$FixtureAnnotation;") } @@ -88,61 +84,50 @@ class BytecodeRemappingTest { val result = details.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase) + assertThat(ClassReader(result).className).isEqualTo(relocatedFixtureBase) } @Test fun superclassIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - assertThat(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase) + assertThat(ClassReader(result).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() } - assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;") + assertThat(result.classInfo().fieldDescriptors).contains("L$relocatedFixtureBase;") } @Test fun arrayFieldDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } - assertThat(fieldDescriptors).contains("[L$relocatedFixtureBase;") + assertThat(result.classInfo().fieldDescriptors).contains("[L$relocatedFixtureBase;") } @Test fun array2dFieldDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } - assertThat(fieldDescriptors).contains("[[L$relocatedFixtureBase;") + assertThat(result.classInfo().fieldDescriptors).contains("[[L$relocatedFixtureBase;") } @Test fun methodDescriptorIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } - assertThat(methodDescriptors).contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;") + assertThat(result.classInfo().methodDescriptors) + .contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @Test fun methodMultipleArgsIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } - assertThat(methodDescriptors) + assertThat(result.classInfo().methodDescriptors) .contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @@ -151,9 +136,7 @@ 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() } - assertThat(methodDescriptors) + assertThat(result.classInfo().methodDescriptors) .contains("(${primitiveDescriptor}L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @@ -161,13 +144,7 @@ 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 - } - assertThat(stringConstants) + assertThat(result.classInfo().stringConstants) .contains($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") } @@ -183,12 +160,7 @@ 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 - } - assertThat(stringConstants) + assertThat(result.classInfo().stringConstants) .doesNotContain($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") } @@ -196,14 +168,9 @@ 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 - } // Verify that two adjacent class references in a single string constant are both relocated // (regression test for the issue-1403 pattern). - assertThat(stringConstants) + assertThat(result.classInfo().stringConstants) .contains( $$"()Lcom/example/relocated/BytecodeRemappingTest$FixtureBase;Lcom/example/relocated/BytecodeRemappingTest$FixtureBase;" ) @@ -213,9 +180,7 @@ class BytecodeRemappingTest { fun interfaceIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) - val classModel = ClassFile.of().parse(result) - val interfaces = classModel.interfaces().map { it.asInternalName() } - assertThat(interfaces) + assertThat(ClassReader(result).interfaces.toList()) .contains($$"com/example/relocated/BytecodeRemappingTest$FixtureInterface") } @@ -223,47 +188,25 @@ 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 method = result.classInfo().methodData.first { it.name == "methodWithGeneric" } + assertThat(checkNotNull(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() } - assertThat(descriptors).contains("L$relocatedFixtureBase;") + val method = result.classInfo().methodData.first { it.name == "method" } + assertThat(method.localVarDescriptors).contains("L$relocatedFixtureBase;") } @Test 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 hasRelocatedCheckCast = - code.elementStream().anyMatch { element -> - element is TypeCheckInstruction && element.type().asInternalName() == relocatedFixtureBase - } - assertThat(hasRelocatedCheckCast).isTrue() - - val hasRelocatedInvoke = - code.elementStream().anyMatch { element -> - element is InvokeInstruction && element.owner().asInternalName() == relocatedFixtureBase - } - assertThat(hasRelocatedInvoke).isTrue() + val method = result.classInfo().methodData.first { it.name == "methodWithCheckCast" } + assertThat(method.checkcastTargets).contains(relocatedFixtureBase) + assertThat(method.invokeOwners).contains(relocatedFixtureBase) } private fun KClass<*>.toFileCopyDetails() = @@ -281,6 +224,115 @@ class BytecodeRemappingTest { override fun getFile(): File = _file } + private data class ClassBytecodeInfo( + val annotationDescriptors: List, + val fieldDescriptors: List, + val methodData: List, + ) { + val methodDescriptors + get() = methodData.map { it.descriptor } + + val stringConstants + get() = methodData.flatMap { it.stringConstants } + } + + private data class MethodBytecodeInfo( + val name: String, + val descriptor: String, + val signature: String?, + val localVarDescriptors: List, + val checkcastTargets: List, + val invokeOwners: List, + val stringConstants: List, + ) + + private fun ByteArray.classInfo(): ClassBytecodeInfo { + val annotationDescs = mutableListOf() + val fieldDescs = mutableListOf() + val methods = mutableListOf() + + ClassReader(this).accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + annotationDescs.add(descriptor) + return null + } + + override fun visitField( + access: Int, + name: String, + descriptor: String, + signature: String?, + value: Any?, + ): FieldVisitor? { + fieldDescs.add(descriptor) + return null + } + + override fun visitMethod( + access: Int, + name: String, + descriptor: String, + signature: String?, + exceptions: Array?, + ): MethodVisitor { + val localVarDescs = mutableListOf() + val checkcastTargets = mutableListOf() + val invokeOwners = mutableListOf() + val stringConsts = mutableListOf() + + return object : MethodVisitor(Opcodes.ASM9) { + override fun visitLocalVariable( + name: String, + descriptor: String, + signature: String?, + start: Label, + end: Label, + index: Int, + ) { + localVarDescs.add(descriptor) + } + + override fun visitTypeInsn(opcode: Int, type: String) { + if (opcode == Opcodes.CHECKCAST) checkcastTargets.add(type) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + invokeOwners.add(owner) + } + + override fun visitLdcInsn(value: Any) { + if (value is String) stringConsts.add(value) + } + + override fun visitEnd() { + methods.add( + MethodBytecodeInfo( + name, + descriptor, + signature, + localVarDescs.toList(), + checkcastTargets.toList(), + invokeOwners.toList(), + stringConsts.toList(), + ) + ) + } + } + } + }, + 0, + ) + + return ClassBytecodeInfo(annotationDescs, fieldDescs, methods) + } + // --------------------------------------------------------------------------- // Fixture classes – declared as nested classes so their bytecode is compiled // into the test output directory and can be fetched via requireResourceAsPath. From b96c58d88a8775d212cfde1b8082b8e8cd6f5b98 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 16:54:23 +0800 Subject: [PATCH 5/6] Remove asm-commons --- build.gradle.kts | 7 +------ gradle/libs.versions.toml | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ff532367b..339664556 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -138,12 +138,7 @@ dependencies { } testing.suites { - getByName("test") { - dependencies { - implementation(libs.xmlunit) - implementation(libs.asm.commons) - } - } + getByName("test") { dependencies { implementation(libs.xmlunit) } } register("documentTest") { targets.configureEach { testTask { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9e381268..65ff22f83 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,6 @@ 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" From c2d606ea07281ea5d04b0e2b4837d27540ec1c72 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 16:56:54 +0800 Subject: [PATCH 6/6] Cleanups --- .../shadow/internal/BytecodeRemappingTest.kt | 143 +++++++++--------- 1 file changed, 71 insertions(+), 72 deletions(-) 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 353277c8b..f3bc50921 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 @@ -224,34 +224,86 @@ class BytecodeRemappingTest { override fun getFile(): File = _file } - private data class ClassBytecodeInfo( - val annotationDescriptors: List, - val fieldDescriptors: List, - val methodData: List, - ) { - val methodDescriptors - get() = methodData.map { it.descriptor } - - val stringConstants - get() = methodData.flatMap { it.stringConstants } + // --------------------------------------------------------------------------- + // Fixture classes – declared as nested classes so their bytecode is compiled + // into the test output directory and can be fetched via requireResourceAsPath. + // --------------------------------------------------------------------------- + + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS) + annotation class FixtureAnnotation + + interface FixtureInterface + + open class FixtureBase + + @Suppress("unused") // Used by parsing bytecode. + @FixtureAnnotation + class FixtureSubject : FixtureBase(), FixtureInterface { + val field: FixtureBase = FixtureBase() + val arrayField: Array = emptyArray() + val array2dField: Array> = emptyArray() + val stringConstant: String = + $$"com.github.jengelman.gradle.plugins.shadow.internal.BytecodeRemappingTest$FixtureBase" + val multiClassDescriptor: String = + $$"()Lcom/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest$FixtureBase;Lcom/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest$FixtureBase;" + + fun method(arg: FixtureBase): FixtureBase = arg + + fun methodMultiArgs(a: FixtureBase, b: FixtureBase): FixtureBase = a + + fun methodWithPrimitivePlusClass(b: Byte, arg: FixtureBase): FixtureBase = arg + + fun methodWithCharPlusClass(c: Char, arg: FixtureBase): FixtureBase = arg + + fun methodWithDoublePlusClass(d: Double, arg: FixtureBase): FixtureBase = arg + + fun methodWithFloatPlusClass(f: Float, arg: FixtureBase): FixtureBase = arg + + fun methodWithIntPlusClass(i: Int, arg: FixtureBase): FixtureBase = arg + + fun methodWithLongPlusClass(l: Long, arg: FixtureBase): FixtureBase = arg + + fun methodWithShortPlusClass(s: Short, arg: FixtureBase): FixtureBase = arg + + fun methodWithBooleanPlusClass(z: Boolean, arg: FixtureBase): FixtureBase = arg + + fun methodWithCheckCast(arg: Any): FixtureBase { + (arg as FixtureBase).toString() + return arg + } + + fun methodWithGeneric(list: List): FixtureBase = list[0] } +} + +private data class ClassBytecodeInfo( + val annotationDescriptors: List, + val fieldDescriptors: List, + val methodData: List, +) { + val methodDescriptors = methodData.map { it.descriptor } + val stringConstants = methodData.flatMap { it.stringConstants } - private data class MethodBytecodeInfo( + data class MethodBytecodeInfo( val name: String, val descriptor: String, val signature: String?, val localVarDescriptors: List, - val checkcastTargets: List, + @Suppress("SpellCheckingInspection") val checkcastTargets: List, val invokeOwners: List, val stringConstants: List, ) +} - private fun ByteArray.classInfo(): ClassBytecodeInfo { - val annotationDescs = mutableListOf() - val fieldDescs = mutableListOf() - val methods = mutableListOf() +@Suppress("SpellCheckingInspection") +private fun ByteArray.classInfo(): ClassBytecodeInfo { + val annotationDescs = mutableListOf() + val fieldDescs = mutableListOf() + val methods = mutableListOf() - ClassReader(this).accept( + ClassReader(this) + .accept( object : ClassVisitor(Opcodes.ASM9) { override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { annotationDescs.add(descriptor) @@ -313,7 +365,7 @@ class BytecodeRemappingTest { override fun visitEnd() { methods.add( - MethodBytecodeInfo( + ClassBytecodeInfo.MethodBytecodeInfo( name, descriptor, signature, @@ -330,58 +382,5 @@ class BytecodeRemappingTest { 0, ) - return ClassBytecodeInfo(annotationDescs, fieldDescs, methods) - } - - // --------------------------------------------------------------------------- - // Fixture classes – declared as nested classes so their bytecode is compiled - // into the test output directory and can be fetched via requireResourceAsPath. - // --------------------------------------------------------------------------- - - @Retention(AnnotationRetention.RUNTIME) - @Target(AnnotationTarget.CLASS) - annotation class FixtureAnnotation - - interface FixtureInterface - - open class FixtureBase - - @Suppress("unused") // Used by parsing bytecode. - @FixtureAnnotation - class FixtureSubject : FixtureBase(), FixtureInterface { - val field: FixtureBase = FixtureBase() - val arrayField: Array = emptyArray() - val array2dField: Array> = emptyArray() - val stringConstant: String = - $$"com.github.jengelman.gradle.plugins.shadow.internal.BytecodeRemappingTest$FixtureBase" - val multiClassDescriptor: String = - $$"()Lcom/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest$FixtureBase;Lcom/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest$FixtureBase;" - - fun method(arg: FixtureBase): FixtureBase = arg - - fun methodMultiArgs(a: FixtureBase, b: FixtureBase): FixtureBase = a - - fun methodWithPrimitivePlusClass(b: Byte, arg: FixtureBase): FixtureBase = arg - - fun methodWithCharPlusClass(c: Char, arg: FixtureBase): FixtureBase = arg - - fun methodWithDoublePlusClass(d: Double, arg: FixtureBase): FixtureBase = arg - - fun methodWithFloatPlusClass(f: Float, arg: FixtureBase): FixtureBase = arg - - fun methodWithIntPlusClass(i: Int, arg: FixtureBase): FixtureBase = arg - - fun methodWithLongPlusClass(l: Long, arg: FixtureBase): FixtureBase = arg - - fun methodWithShortPlusClass(s: Short, arg: FixtureBase): FixtureBase = arg - - fun methodWithBooleanPlusClass(z: Boolean, arg: FixtureBase): FixtureBase = arg - - fun methodWithCheckCast(arg: Any): FixtureBase { - (arg as FixtureBase).toString() - return arg - } - - fun methodWithGeneric(list: List): FixtureBase = list[0] - } + return ClassBytecodeInfo(annotationDescs, fieldDescs, methods) }