From f88e184d9f990ef5616de426a695c116da3b2ae3 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 21:42:06 +0800 Subject: [PATCH 1/5] Configure compileKotlin and compileJava --- build.gradle.kts | 15 +++++++++------ gradle/libs.versions.toml | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 339664556..db3116c98 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,18 +30,12 @@ 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() } } } @@ -222,6 +216,15 @@ 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 65ff22f83..79163c122 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ 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" From 7e371569a6fcc4f17d4193106e36548273cc0390 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 22:17:55 +0800 Subject: [PATCH 2/5] Add RelocatorRemapperTest --- .../shadow/internal/RelocatorRemapperTest.kt | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt new file mode 100644 index 000000000..2c6150e6f --- /dev/null +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt @@ -0,0 +1,207 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import assertk.assertThat +import assertk.assertions.contains +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.nio.file.Path +import kotlin.io.path.copyTo +import kotlin.io.path.createParentDirectories +import kotlin.reflect.KClass +import org.gradle.api.file.FileCopyDetails +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * The cases reflect the cases in + * [com.github.jengelman.gradle.plugins.shadow.relocation.RelocatorsTest], but operate on the + * bytecode level to verify that the remapper correctly transforms class names in all relevant + * bytecode structures. + */ +class RelocatorRemapperTest { + @TempDir lateinit var tempDir: Path + + // Relocator used across all relocation tests: moves the test package to a distinct target. + private val relocators = + setOf( + SimpleRelocator( + "com.github.jengelman.gradle.plugins.shadow.internal", + "com.example.relocated", + ) + ) + + // Internal name of the relocated FixtureBase for use in assertions. + private val relocatedFixtureBase = $$"com/example/relocated/RelocatorRemapperTest$FixtureBase" + + @Test + fun remapClassNotModified() { + val details = FixtureSubject::class.toFileCopyDetails() + // Relocator pattern does not match – original bytes must be returned as-is. + val noMatchRelocators = setOf(SimpleRelocator("org.unrelated", "org.other")) + + val result = details.remapClass(noMatchRelocators) + + assertThat(result).isEqualTo(details.file.readBytes()) + } + + @Test + fun remapClassNameIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + assertThat(classModel.thisClass().asInternalName()) + .isEqualTo($$"com/example/relocated/RelocatorRemapperTest$FixtureSubject") + } + + @Test + fun remapSuperclassIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + assertThat(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase) + } + + @Test + fun remapFieldDescriptorIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;") + } + + @Test + fun remapMethodDescriptorIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + assertThat(methodDescriptors).contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;") + } + + @Test + fun remapAnnotationIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.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) + .contains($$"Lcom/example/relocated/RelocatorRemapperTest$FixtureAnnotation;") + } + + @Test + fun remapArrayFieldDescriptorIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + assertThat(fieldDescriptors).contains("[L$relocatedFixtureBase;") + } + + @Test + fun remapArray2dFieldDescriptorIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + assertThat(fieldDescriptors).contains("[[L$relocatedFixtureBase;") + } + + @Test + fun remapMethodMultipleArgsIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + assertThat(methodDescriptors) + .contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;") + } + + @Test + fun remapMethodPrimitivePlusClassIsRelocated() { + val details = FixtureSubject::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + assertThat(methodDescriptors).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;") + } + + @Test + fun remapBaseClassNameIsRelocated() { + // Verify relocation also works on a simple class (FixtureBase has no fields/methods + // referencing the target package beyond its own class name). + val details = FixtureBase::class.toFileCopyDetails() + + val result = details.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase) + } + + private fun KClass<*>.toFileCopyDetails() = + object : FileCopyDetails by noOpDelegate() { + private val _path = java.name.replace('.', '/') + ".class" + private val _file = + tempDir + .resolve(_path) + .createParentDirectories() + .also { requireResourceAsPath(_path).copyTo(it) } + .toFile() + + override fun getPath(): String = _path + + override fun getFile(): File = _file + } + + // --------------------------------------------------------------------------- + // 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 + + open class FixtureBase + + @Suppress("unused") // Used by parsing bytecode. + @FixtureAnnotation + class FixtureSubject : FixtureBase() { + val field: FixtureBase = FixtureBase() + val arrayField: Array = emptyArray() + val array2dField: Array> = emptyArray() + + fun method(arg: FixtureBase): FixtureBase = arg + + fun methodMultiArgs(a: FixtureBase, b: FixtureBase): FixtureBase = a + + fun methodWithPrimitivePlusClass(b: Byte, arg: FixtureBase): FixtureBase = arg + } +} From a601ba50be9647105e93c07728f204e4b85dd131 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 14:56:30 +0800 Subject: [PATCH 3/5] Cleanups --- ...mapperTest.kt => BytecodeRemappingTest.kt} | 111 ++++++++---------- 1 file changed, 48 insertions(+), 63 deletions(-) rename src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/{RelocatorRemapperTest.kt => BytecodeRemappingTest.kt} (74%) diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt similarity index 74% rename from src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt rename to src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt index 2c6150e6f..bc6ce8a07 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapperTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt @@ -24,7 +24,7 @@ import org.junit.jupiter.api.io.TempDir * bytecode level to verify that the remapper correctly transforms class names in all relevant * bytecode structures. */ -class RelocatorRemapperTest { +class BytecodeRemappingTest { @TempDir lateinit var tempDir: Path // Relocator used across all relocation tests: moves the test package to a distinct target. @@ -37,11 +37,14 @@ class RelocatorRemapperTest { ) // Internal name of the relocated FixtureBase for use in assertions. - private val relocatedFixtureBase = $$"com/example/relocated/RelocatorRemapperTest$FixtureBase" + private val relocatedFixtureBase = $$"com/example/relocated/BytecodeRemappingTest$FixtureBase" + + private val fixtureSubjectDetails + get() = FixtureSubject::class.toFileCopyDetails() @Test - fun remapClassNotModified() { - val details = FixtureSubject::class.toFileCopyDetails() + fun classNotModified() { + val details = fixtureSubjectDetails // Relocator pattern does not match – original bytes must be returned as-is. val noMatchRelocators = setOf(SimpleRelocator("org.unrelated", "org.other")) @@ -51,68 +54,59 @@ class RelocatorRemapperTest { } @Test - fun remapClassNameIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun classNameIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) assertThat(classModel.thisClass().asInternalName()) - .isEqualTo($$"com/example/relocated/RelocatorRemapperTest$FixtureSubject") + .isEqualTo($$"com/example/relocated/BytecodeRemappingTest$FixtureSubject") } @Test - fun remapSuperclassIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun annotationIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) - assertThat(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase) + val annotationsAttr = classModel.findAttribute(Attributes.runtimeVisibleAnnotations()) + assertThat(annotationsAttr.isPresent).isTrue() + val annotationDescriptors = + annotationsAttr.get().annotations().map { it.className().stringValue() } + assertThat(annotationDescriptors) + .contains($$"Lcom/example/relocated/BytecodeRemappingTest$FixtureAnnotation;") } @Test - fun remapFieldDescriptorIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() + fun baseClassNameIsRelocated() { + // Verify relocation also works on a simple class (FixtureBase has no fields/methods + // referencing the target package beyond its own class name). + val details = FixtureBase::class.toFileCopyDetails() val result = details.remapClass(relocators) val classModel = ClassFile.of().parse(result) - val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } - assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;") + assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase) } @Test - fun remapMethodDescriptorIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun superclassIsRelocated() { + 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(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase) } @Test - fun remapAnnotationIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun fieldDescriptorIsRelocated() { + 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) - .contains($$"Lcom/example/relocated/RelocatorRemapperTest$FixtureAnnotation;") + val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } + assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;") } @Test - fun remapArrayFieldDescriptorIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun arrayFieldDescriptorIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } @@ -120,10 +114,8 @@ class RelocatorRemapperTest { } @Test - fun remapArray2dFieldDescriptorIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun array2dFieldDescriptorIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() } @@ -131,38 +123,31 @@ class RelocatorRemapperTest { } @Test - fun remapMethodMultipleArgsIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + 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;)L$relocatedFixtureBase;") + assertThat(methodDescriptors).contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @Test - fun remapMethodPrimitivePlusClassIsRelocated() { - val details = FixtureSubject::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun methodMultipleArgsIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } - assertThat(methodDescriptors).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;") + assertThat(methodDescriptors) + .contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @Test - fun remapBaseClassNameIsRelocated() { - // Verify relocation also works on a simple class (FixtureBase has no fields/methods - // referencing the target package beyond its own class name). - val details = FixtureBase::class.toFileCopyDetails() - - val result = details.remapClass(relocators) + fun methodPrimitivePlusClassIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) val classModel = ClassFile.of().parse(result) - assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase) + val methodDescriptors = classModel.methods().map { it.methodType().stringValue() } + assertThat(methodDescriptors).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;") } private fun KClass<*>.toFileCopyDetails() = @@ -187,13 +172,13 @@ class RelocatorRemapperTest { @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) - annotation class FixtureAnnotation + private annotation class FixtureAnnotation - open class FixtureBase + private open class FixtureBase @Suppress("unused") // Used by parsing bytecode. @FixtureAnnotation - class FixtureSubject : FixtureBase() { + private class FixtureSubject : FixtureBase() { val field: FixtureBase = FixtureBase() val arrayField: Array = emptyArray() val array2dField: Array> = emptyArray() From 645b50f415d77686b6741b416940ca4ef163faf3 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 15:16:23 +0800 Subject: [PATCH 4/5] Add more cases --- .../shadow/internal/BytecodeRemappingTest.kt | 89 ++++++++++++++++++- 1 file changed, 86 insertions(+), 3 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 bc6ce8a07..3611ef2ed 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 @@ -10,6 +10,8 @@ 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 @@ -150,6 +152,76 @@ class BytecodeRemappingTest { assertThat(methodDescriptors).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;") } + @Test + 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).contains("com.example.relocated.BytecodeRemappingTest\$FixtureBase") + } + + @Test + fun interfaceIsRelocated() { + val result = fixtureSubjectDetails.remapClass(relocators) + + val classModel = ClassFile.of().parse(result) + val interfaces = classModel.interfaces().map { it.asInternalName() } + assertThat(interfaces) + .contains($$"com/example/relocated/BytecodeRemappingTest$FixtureInterface") + } + + @Test + 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;") + } + + @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;") + } + + @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() + } + private fun KClass<*>.toFileCopyDetails() = object : FileCopyDetails by noOpDelegate() { private val _path = java.name.replace('.', '/') + ".class" @@ -172,21 +244,32 @@ class BytecodeRemappingTest { @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) - private annotation class FixtureAnnotation + annotation class FixtureAnnotation - private open class FixtureBase + interface FixtureInterface + + open class FixtureBase @Suppress("unused") // Used by parsing bytecode. @FixtureAnnotation - private class FixtureSubject : FixtureBase() { + 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" fun method(arg: FixtureBase): FixtureBase = arg fun methodMultiArgs(a: FixtureBase, b: FixtureBase): FixtureBase = a fun methodWithPrimitivePlusClass(b: Byte, arg: FixtureBase): FixtureBase = arg + + fun methodWithCheckCast(arg: Any): FixtureBase { + (arg as FixtureBase).toString() + return arg + } + + fun methodWithGeneric(list: List): FixtureBase = list[0] } } From e4caf011353cbfec2801f850c91a467ea5932521 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 17 Mar 2026 15:33:33 +0800 Subject: [PATCH 5/5] More --- .../shadow/internal/BytecodeRemappingTest.kt | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 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 3611ef2ed..4ff39de95 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 @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.internal 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 @@ -19,6 +20,8 @@ import kotlin.reflect.KClass import org.gradle.api.file.FileCopyDetails 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 /** * The cases reflect the cases in @@ -143,13 +146,15 @@ class BytecodeRemappingTest { .contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;") } - @Test - fun methodPrimitivePlusClassIsRelocated() { + @ParameterizedTest + @ValueSource(chars = ['B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z']) + 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).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;") + assertThat(methodDescriptors) + .contains("(${primitiveDescriptor}L$relocatedFixtureBase;)L$relocatedFixtureBase;") } @Test @@ -162,7 +167,46 @@ class BytecodeRemappingTest { classModel.constantPool().mapNotNull { entry -> if (entry is java.lang.classfile.constantpool.StringEntry) entry.stringValue() else null } - assertThat(stringConstants).contains("com.example.relocated.BytecodeRemappingTest\$FixtureBase") + assertThat(stringConstants) + .contains($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") + } + + @Test + fun stringConstantNotRelocatedWhenSkipEnabled() { + val skipRelocators = + setOf( + SimpleRelocator( + "com.github.jengelman.gradle.plugins.shadow.internal", + "com.example.relocated", + skipStringConstants = true, + ) + ) + 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) + .doesNotContain($$"com.example.relocated.BytecodeRemappingTest$FixtureBase") + } + + @Test + 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) + .contains( + $$"()Lcom/example/relocated/BytecodeRemappingTest$FixtureBase;Lcom/example/relocated/BytecodeRemappingTest$FixtureBase;" + ) } @Test @@ -258,6 +302,8 @@ class BytecodeRemappingTest { 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 @@ -265,6 +311,20 @@ class BytecodeRemappingTest { 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