From 60d0f8b69a9b066448b053fbe859204c0ea9e2ab Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 3 Mar 2026 15:41:47 +0800 Subject: [PATCH 1/5] Migrate from ASM to Class-File API https://openjdk.org/jeps/484 --- build.gradle.kts | 2 +- .../gradle/plugins/shadow/RelocationTest.kt | 39 ++ .../shadow/internal/RelocatorRemapper.kt | 504 +++++++++++++++++- .../plugins/shadow/tasks/ShadowCopyAction.kt | 41 +- 4 files changed, 545 insertions(+), 41 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 339664556..9081e3490 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,7 +30,7 @@ dokka { dokkaPublications.html { outputDirectory = rootDir.resolve("docs/api") } kotlin { explicitApi() @OptIn(ExperimentalAbiValidation::class) abiValidation { enabled = true } - val jdkRelease = "17" + val jdkRelease = "24" compilerOptions { allWarningsAsErrors = true // https://docs.gradle.org/current/userguide/compatibility.html#kotlin diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 852ebcc79..e8efaf4b2 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -533,6 +533,45 @@ class RelocationTest : BasePluginTest() { } } + @Test + fun relocateAnnotationStringConstants() { + writeClass { + """ + package my; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + @Retention(RetentionPolicy.RUNTIME) + @interface MyAnnotation { + String value(); + } + @MyAnnotation("foo.Bar") + public class Main { + public static void main(String[] args) { + MyAnnotation ann = Main.class.getAnnotation(MyAnnotation.class); + System.out.println(ann.value()); + } + } + """ + .trimIndent() + } + projectScript.appendText( + """ + $shadowJarTask { + manifest { + attributes '$mainClassAttributeKey': 'my.Main' + } + relocate('foo', 'shadow.foo') + } + """ + .trimIndent() + ) + + runWithSuccess(shadowJarPath) + val result = runProcess("java", "-jar", outputShadowedJar.use { it.toString() }) + + assertThat(result).contains("shadow.foo.Bar") + } + @Issue("https://github.com/GradleUp/shadow/issues/1403") @Test fun relocateMultiClassSignatureStringConstants() { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index c10dddab5..3f22e3838 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -3,34 +3,508 @@ package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath +import java.lang.classfile.AnnotationElement +import java.lang.classfile.AnnotationValue +import java.lang.classfile.ClassSignature +import java.lang.classfile.ClassTransform +import java.lang.classfile.CodeModel +import java.lang.classfile.CodeTransform +import java.lang.classfile.FieldModel +import java.lang.classfile.FieldTransform +import java.lang.classfile.Interfaces +import java.lang.classfile.MethodModel +import java.lang.classfile.MethodSignature +import java.lang.classfile.MethodTransform +import java.lang.classfile.Signature +import java.lang.classfile.Superclass +import java.lang.classfile.TypeAnnotation +import java.lang.classfile.attribute.AnnotationDefaultAttribute +import java.lang.classfile.attribute.ConstantValueAttribute +import java.lang.classfile.attribute.EnclosingMethodAttribute +import java.lang.classfile.attribute.ExceptionsAttribute +import java.lang.classfile.attribute.InnerClassInfo +import java.lang.classfile.attribute.InnerClassesAttribute +import java.lang.classfile.attribute.ModuleAttribute +import java.lang.classfile.attribute.ModuleExportInfo +import java.lang.classfile.attribute.ModuleOpenInfo +import java.lang.classfile.attribute.ModuleProvideInfo +import java.lang.classfile.attribute.NestHostAttribute +import java.lang.classfile.attribute.NestMembersAttribute +import java.lang.classfile.attribute.PermittedSubclassesAttribute +import java.lang.classfile.attribute.RecordAttribute +import java.lang.classfile.attribute.RecordComponentInfo +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute +import java.lang.classfile.attribute.SignatureAttribute +import java.lang.classfile.constantpool.StringEntry +import java.lang.classfile.instruction.ConstantInstruction +import java.lang.classfile.instruction.ExceptionCatch +import java.lang.classfile.instruction.FieldInstruction +import java.lang.classfile.instruction.InvokeDynamicInstruction +import java.lang.classfile.instruction.InvokeInstruction +import java.lang.classfile.instruction.LocalVariable +import java.lang.classfile.instruction.LocalVariableType +import java.lang.classfile.instruction.NewMultiArrayInstruction +import java.lang.classfile.instruction.NewObjectInstruction +import java.lang.classfile.instruction.NewReferenceArrayInstruction +import java.lang.classfile.instruction.TypeCheckInstruction +import java.lang.constant.ClassDesc +import java.lang.constant.ConstantDesc +import java.lang.constant.DirectMethodHandleDesc +import java.lang.constant.DynamicCallSiteDesc +import java.lang.constant.DynamicConstantDesc +import java.lang.constant.MethodHandleDesc +import java.lang.constant.MethodTypeDesc +import java.lang.constant.PackageDesc import java.util.regex.Pattern -import org.vafer.jdeb.shaded.objectweb.asm.Opcodes -import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper /** - * Modified from - * [org.apache.maven.plugins.shade.DefaultShader.RelocatorRemapper](https://github.com/apache/maven-shade-plugin/blob/83c123d1f9c5f6927af2aca12ee322b5168a7c63/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L689-L772). - * Modified from - * [org.apache.maven.plugins.shade.DefaultShader.DefaultPackageMapper](https://github.com/apache/maven-shade-plugin/blob/199ffaecd26a912527173ed4edae366e48a00998/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L737-L774). + * Ported from ASM's Remapper and JDK's ClassRemapperImpl to use Java 24 Class-File API. * * @author John Engelman */ internal class RelocatorRemapper( private val relocators: Set, private val onModified: () -> Unit = {}, -) : Remapper(Opcodes.ASM9) { +) { - override fun mapValue(value: Any): Any { - return if (value is String) { - mapName(value, mapLiterals = true) - } else { - super.mapValue(value) + fun asClassTransform(): ClassTransform = ClassTransform { clb, cle -> + when (cle) { + is FieldModel -> + clb.withField( + cle.fieldName().stringValue(), + mapClassDesc(ClassDesc.ofDescriptor(cle.fieldType().stringValue())), + ) { fb -> + fb.withFlags(cle.flags().flagsMask()).transform(cle, asFieldTransform()) + } + is MethodModel -> + clb.withMethod( + cle.methodName().stringValue(), + mapMethodDesc(cle.methodTypeSymbol()), + cle.flags().flagsMask(), + ) { mb -> + mb.transform(cle, asMethodTransform()) + } + is Superclass -> clb.withSuperclass(mapClassDesc(cle.superclassEntry().asSymbol())) + is Interfaces -> + clb.withInterfaceSymbols(cle.interfaces().map { mapClassDesc(it.asSymbol())!! }) + is SignatureAttribute -> + clb.with(SignatureAttribute.of(mapClassSignature(cle.asClassSignature()))) + is InnerClassesAttribute -> + clb.with( + InnerClassesAttribute.of( + cle.classes().map { ici -> + InnerClassInfo.of( + mapClassDesc(ici.innerClass().asSymbol()), + ici.outerClass().map { mapClassDesc(it.asSymbol()) }, + ici.innerName().map { it.stringValue() }, + ici.flagsMask(), + ) + } + ) + ) + is EnclosingMethodAttribute -> + clb.with( + EnclosingMethodAttribute.of( + mapClassDesc(cle.enclosingClass().asSymbol()), + cle.enclosingMethodName().map { it.stringValue() }, + cle + .enclosingMethodType() + .map { MethodTypeDesc.ofDescriptor(it.stringValue()) } + .map(this::mapMethodDesc), + ) + ) + is RecordAttribute -> + clb.with(RecordAttribute.of(cle.components().map(this::mapRecordComponent))) + is ModuleAttribute -> + clb.with( + ModuleAttribute.of( + cle.moduleName(), + cle.moduleFlagsMask(), + cle.moduleVersion().orElse(null), + cle.requires(), + cle.exports().map { mei -> + ModuleExportInfo.of( + clb + .constantPool() + .packageEntry( + PackageDesc.ofInternalName(map(mei.exportedPackage().asSymbol().internalName())) + ), + mei.exportsFlagsMask(), + mei.exportsTo(), + ) + }, + cle.opens().map { moi -> + ModuleOpenInfo.of( + clb + .constantPool() + .packageEntry( + PackageDesc.ofInternalName(map(moi.openedPackage().asSymbol().internalName())) + ), + moi.opensFlagsMask(), + moi.opensTo(), + ) + }, + cle.uses().map { clb.constantPool().classEntry(mapClassDesc(it.asSymbol())!!) }, + cle.provides().map { mp -> + ModuleProvideInfo.of( + mapClassDesc(mp.provides().asSymbol()), + mp.providesWith().map { mapClassDesc(it.asSymbol())!! }, + ) + }, + ) + ) + is NestHostAttribute -> + clb.with(NestHostAttribute.of(mapClassDesc(cle.nestHost().asSymbol()))) + is NestMembersAttribute -> + clb.with( + NestMembersAttribute.ofSymbols(cle.nestMembers().map { mapClassDesc(it.asSymbol())!! }) + ) + is PermittedSubclassesAttribute -> + clb.with( + PermittedSubclassesAttribute.ofSymbols( + cle.permittedSubclasses().map { mapClassDesc(it.asSymbol())!! } + ) + ) + is RuntimeVisibleAnnotationsAttribute -> + clb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + clb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + is RuntimeVisibleTypeAnnotationsAttribute -> + clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + else -> clb.with(cle) } } - override fun map(internalName: String): String = mapName(internalName) + private fun asFieldTransform(): FieldTransform = FieldTransform { fb, fe -> + when (fe) { + is ConstantValueAttribute -> { + val constant = fe.constant() + if (constant is StringEntry) { + val remapped = map(constant.stringValue(), true) + fb.with(ConstantValueAttribute.of(fb.constantPool().stringEntry(remapped))) + } else { + fb.with(fe) + } + } + is SignatureAttribute -> fb.with(SignatureAttribute.of(mapSignature(fe.asTypeSignature()))) + is RuntimeVisibleAnnotationsAttribute -> + fb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + fb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + is RuntimeVisibleTypeAnnotationsAttribute -> + fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + else -> fb.with(fe) + } + } + + private fun asMethodTransform(): MethodTransform = MethodTransform { mb, me -> + when (me) { + is AnnotationDefaultAttribute -> + mb.with(AnnotationDefaultAttribute.of(mapAnnotationValue(me.defaultValue()))) + is CodeModel -> mb.transformCode(me, asCodeTransform()) + is ExceptionsAttribute -> + mb.with( + ExceptionsAttribute.ofSymbols(me.exceptions().map { mapClassDesc(it.asSymbol())!! }) + ) + is SignatureAttribute -> + mb.with(SignatureAttribute.of(mapMethodSignature(me.asMethodSignature()))) + is RuntimeVisibleAnnotationsAttribute -> + mb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + mb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + is RuntimeVisibleParameterAnnotationsAttribute -> + mb.with( + RuntimeVisibleParameterAnnotationsAttribute.of( + me.parameterAnnotations().map(this::mapAnnotations) + ) + ) + is RuntimeInvisibleParameterAnnotationsAttribute -> + mb.with( + RuntimeInvisibleParameterAnnotationsAttribute.of( + me.parameterAnnotations().map(this::mapAnnotations) + ) + ) + is RuntimeVisibleTypeAnnotationsAttribute -> + mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + else -> mb.with(me) + } + } + + private fun asCodeTransform(): CodeTransform = CodeTransform { cob, coe -> + when (coe) { + is FieldInstruction -> + cob.fieldAccess( + coe.opcode(), + mapClassDesc(coe.owner().asSymbol())!!, + coe.name().stringValue(), + mapClassDesc(coe.typeSymbol())!!, + ) + is InvokeInstruction -> + cob.invoke( + coe.opcode(), + mapClassDesc(coe.owner().asSymbol())!!, + coe.name().stringValue(), + mapMethodDesc(coe.typeSymbol()), + coe.isInterface(), + ) + is InvokeDynamicInstruction -> + cob.invokedynamic( + DynamicCallSiteDesc.of( + mapDirectMethodHandle(coe.bootstrapMethod()), + coe.name().stringValue(), + mapMethodDesc(coe.typeSymbol()), + *coe.bootstrapArgs().map(this::mapConstantValue).toTypedArray(), + ) + ) + is NewObjectInstruction -> cob.new_(mapClassDesc(coe.className().asSymbol())!!) + is NewReferenceArrayInstruction -> + cob.anewarray(mapClassDesc(coe.componentType().asSymbol())!!) + is NewMultiArrayInstruction -> + cob.multianewarray(mapClassDesc(coe.arrayType().asSymbol())!!, coe.dimensions()) + is TypeCheckInstruction -> + cob.with(TypeCheckInstruction.of(coe.opcode(), mapClassDesc(coe.type().asSymbol())!!)) + is ExceptionCatch -> + cob.exceptionCatch( + coe.tryStart(), + coe.tryEnd(), + coe.handler(), + coe.catchType().map { cob.constantPool().classEntry(mapClassDesc(it.asSymbol())!!) }, + ) + is LocalVariable -> + cob.localVariable( + coe.slot(), + coe.name().stringValue(), + mapClassDesc(coe.typeSymbol())!!, + coe.startScope(), + coe.endScope(), + ) + is LocalVariableType -> + cob.localVariableType( + coe.slot(), + coe.name().stringValue(), + mapSignature(coe.signatureSymbol()), + coe.startScope(), + coe.endScope(), + ) + is ConstantInstruction.LoadConstantInstruction -> { + val value = coe.constantEntry().constantValue() + val name = value.javaClass.name + if (name == "java.lang.String") { + val s = value.toString() + cob.ldc(cob.constantPool().stringEntry(map(s, mapLiterals = true))) + } else if (name == "java.lang.Integer") { + cob.ldc(cob.constantPool().intEntry(value as Int)) + } else if (name == "java.lang.Float") { + cob.ldc(cob.constantPool().floatEntry(value as Float)) + } else if (name == "java.lang.Long") { + cob.ldc(cob.constantPool().longEntry(value as Long)) + } else if (name == "java.lang.Double") { + cob.ldc(cob.constantPool().doubleEntry(value as Double)) + } else { + cob.ldc(mapConstantValue(value as ConstantDesc)) + } + } + is RuntimeVisibleTypeAnnotationsAttribute -> + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + else -> cob.with(coe) + } + } + + fun mapClassDesc(desc: ClassDesc?): ClassDesc? { + if (desc == null) return null + if (desc.isArray) return mapClassDesc(desc.componentType())!!.arrayType() + if (desc.isPrimitive) return desc + val internalName = desc.descriptorString().let { it.substring(1, it.length - 1) } + val mappedInternalName = map(internalName) + return if (internalName == mappedInternalName) desc + else ClassDesc.ofDescriptor("L$mappedInternalName;") + } + + private fun mapMethodDesc(desc: MethodTypeDesc): MethodTypeDesc { + return MethodTypeDesc.of( + mapClassDesc(desc.returnType()), + *desc.parameterList().map { mapClassDesc(it)!! }.toTypedArray(), + ) + } + + private fun mapClassSignature(signature: ClassSignature): ClassSignature { + val superclassSignature = signature.superclassSignature()?.let { mapSignature(it) } + return ClassSignature.of( + mapTypeParams(signature.typeParameters()), + superclassSignature, + *signature.superinterfaceSignatures().map { mapSignature(it) }.toTypedArray(), + ) + } + + private fun mapMethodSignature(signature: MethodSignature): MethodSignature { + return MethodSignature.of( + mapTypeParams(signature.typeParameters()), + signature.throwableSignatures().map { mapSignature(it) }, + mapSignature(signature.result()), + *signature.arguments().map { mapSignature(it) }.toTypedArray(), + ) + } + + private fun mapRecordComponent(component: RecordComponentInfo): RecordComponentInfo { + return RecordComponentInfo.of( + component.name().stringValue(), + mapClassDesc(component.descriptorSymbol())!!, + component.attributes().map { atr -> + when (atr) { + is SignatureAttribute -> SignatureAttribute.of(mapSignature(atr.asTypeSignature())) + is RuntimeVisibleAnnotationsAttribute -> + RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + is RuntimeInvisibleAnnotationsAttribute -> + RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + is RuntimeVisibleTypeAnnotationsAttribute -> + RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + is RuntimeInvisibleTypeAnnotationsAttribute -> + RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + else -> atr + } + }, + ) + } + + private fun mapDirectMethodHandle(dmhd: DirectMethodHandleDesc): DirectMethodHandleDesc { + return when (dmhd.kind()) { + DirectMethodHandleDesc.Kind.GETTER, + DirectMethodHandleDesc.Kind.SETTER, + DirectMethodHandleDesc.Kind.STATIC_GETTER, + DirectMethodHandleDesc.Kind.STATIC_SETTER -> + MethodHandleDesc.ofField( + dmhd.kind(), + mapClassDesc(dmhd.owner())!!, + dmhd.methodName(), + mapClassDesc(ClassDesc.ofDescriptor(dmhd.lookupDescriptor()))!!, + ) + else -> + MethodHandleDesc.ofMethod( + dmhd.kind(), + mapClassDesc(dmhd.owner())!!, + dmhd.methodName(), + mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor())), + ) + } + } + + private fun mapConstantValue(value: ConstantDesc): ConstantDesc { + return when (value) { + is ClassDesc -> mapClassDesc(value)!! + is DynamicConstantDesc<*> -> mapDynamicConstant(value) + is DirectMethodHandleDesc -> mapDirectMethodHandle(value) + is MethodTypeDesc -> mapMethodDesc(value) + else -> { + if (value.javaClass.name == "java.lang.String") + map(value.toString(), mapLiterals = true) as ConstantDesc + else value + } + } + } + + private fun mapDynamicConstant(dcd: DynamicConstantDesc<*>): DynamicConstantDesc<*> { + return DynamicConstantDesc.ofNamed( + mapDirectMethodHandle(dcd.bootstrapMethod()), + dcd.constantName(), + mapClassDesc(dcd.constantType())!!, + *dcd.bootstrapArgsList().map(this::mapConstantValue).toTypedArray(), + ) + } + + @Suppress("UNCHECKED_CAST") + private fun mapSignature(signature: S): S { + return when (signature) { + is Signature.ArrayTypeSig -> + Signature.ArrayTypeSig.of(mapSignature(signature.componentSignature())) as S + is Signature.ClassTypeSig -> { + val mappedOuter = signature.outerType().orElse(null)?.let { mapSignature(it) } + val mappedClass = mapClassDesc(signature.classDesc())!! + // Extract internal name simply bypassing util + val internalName = mappedClass.descriptorString().let { it.substring(1, it.length - 1) } + Signature.ClassTypeSig.of( + mappedOuter, + internalName, + *signature + .typeArgs() + .map { ta -> + when (ta) { + is Signature.TypeArg.Unbounded -> ta + is Signature.TypeArg.Bounded -> + Signature.TypeArg.bounded(ta.wildcardIndicator(), mapSignature(ta.boundType())) + } + } + .toTypedArray(), + ) as S + } + else -> signature + } + } + + private fun mapAnnotations( + annotations: List + ): List = annotations.map(this::mapAnnotation) + + private fun mapAnnotation(a: java.lang.classfile.Annotation): java.lang.classfile.Annotation = + java.lang.classfile.Annotation.of( + mapClassDesc(a.classSymbol())!!, + a.elements().map { el -> AnnotationElement.of(el.name(), mapAnnotationValue(el.value())) }, + ) + + private fun mapAnnotationValue(valObj: AnnotationValue): AnnotationValue { + return when (valObj) { + is AnnotationValue.OfAnnotation -> + AnnotationValue.ofAnnotation(mapAnnotation(valObj.annotation())) + is AnnotationValue.OfArray -> + AnnotationValue.ofArray(valObj.values().map(this::mapAnnotationValue)) + is AnnotationValue.OfConstant -> { + if (valObj is AnnotationValue.OfString) { + val str = valObj.stringValue() + // mapLiterals=true enables the skipStringConstants check in each relocator. + val mapped = map(str, mapLiterals = true) + if (mapped != str) AnnotationValue.ofString(mapped) else valObj + } else { + valObj + } + } + is AnnotationValue.OfClass -> AnnotationValue.ofClass(mapClassDesc(valObj.classSymbol())!!) + is AnnotationValue.OfEnum -> + AnnotationValue.ofEnum( + mapClassDesc(valObj.classSymbol())!!, + valObj.constantName().stringValue(), + ) + } + } + + private fun mapTypeAnnotations(typeAnnotations: List): List = + typeAnnotations.map { a -> + TypeAnnotation.of(a.targetInfo(), a.targetPath(), mapAnnotation(a.annotation())) + } + + private fun mapTypeParams(typeParams: List): List = + typeParams.map { tp -> + Signature.TypeParam.of( + tp.identifier(), + tp.classBound().orElse(null)?.let { mapSignature(it) }, + *tp.interfaceBounds().map { mapSignature(it) }.toTypedArray(), + ) + } + + fun map(name: String): String = map(name, false) - private fun mapName(name: String, mapLiterals: Boolean = false): String { + fun map(name: String, mapLiterals: Boolean = false): String { // Maybe a list of types. val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) } @@ -54,7 +528,7 @@ internal class RelocatorRemapper( for (relocator in relocators) { if (mapLiterals && relocator.skipStringConstants) { - return name + continue } else if (relocator.canRelocateClass(newName)) { return prefix + relocator.relocateClass(newName) + suffix } else if (relocator.canRelocatePath(newName)) { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt index b965e0459..4ca734184 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt @@ -13,6 +13,7 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext import java.io.File +import java.lang.classfile.ClassFile import java.util.GregorianCalendar import java.util.zip.ZipException import kotlin.metadata.jvm.KmModule @@ -32,9 +33,6 @@ import org.gradle.api.internal.file.copy.FileCopyDetailsInternal import org.gradle.api.logging.Logging import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.WorkResults -import org.vafer.jdeb.shaded.objectweb.asm.ClassReader -import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter -import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper /** * Modified from @@ -212,36 +210,29 @@ constructor( private fun FileCopyDetails.remapClass() = file.readBytes().let { bytes -> var modified = false + val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() val remapper = RelocatorRemapper(relocators) { modified = true } - // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant - // pool. - // Copying the original constant pool should be avoided because it would keep references - // to the original class names. This is not a problem at runtime (because these entries in - // the - // constant pool are never used), but confuses some tools such as Felix's - // maven-bundle-plugin - // that use the constant pool to determine the dependencies of a class. - val cw = ClassWriter(0) - val cr = ClassReader(bytes) - val cv = ClassRemapper(cw, remapper) - - try { - cr.accept(cv, ClassReader.EXPAND_FRAMES) - } catch (t: Throwable) { - throw GradleException("Error in ASM processing class $path", t) - } - val newBytes = + try { + val classFile = ClassFile.of() + val classModel = classFile.parse(bytes) + val originalClassDesc = classModel.thisClass().asSymbol() + val newClassDesc = remapper.mapClassDesc(originalClassDesc)!! + classFile.transformClass(classModel, newClassDesc, remapper.asClassTransform()) + } catch (t: Throwable) { + throw GradleException("Error in Class-File API processing class $path", t) + } + + val finalBytes = if (modified) { - cw.toByteArray() + newBytes } else { // If we didn't need to change anything, keep the original bytes as-is bytes } - // Temporarily remove the multi-release prefix. - val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() + // Multi-release prefix was calculated earlier. val newPath = path.replace(multiReleasePrefix, "") val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) try { @@ -251,7 +242,7 @@ constructor( } // Now we put it back on so the class file is written out with the right extension. zipOutStr.putNextEntry(entry) - zipOutStr.write(newBytes) + zipOutStr.write(finalBytes) zipOutStr.closeEntry() } catch (_: ZipException) { logger.warn("We have a duplicate $relocatedPath in source project") From 4c3bd6a919c3c8bc586cd9414eb10e6a6bcbd03f Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 14:21:49 +0800 Subject: [PATCH 2/5] Multi-Release for Class-File API --- build.gradle.kts | 25 +- gradle.properties | 5 + .../shadow/internal/RelocatorRemapper.kt | 602 ++++++++++++++++++ .../shadow/internal/RelocatorRemapper.kt | 572 +++-------------- .../plugins/shadow/tasks/ShadowCopyAction.kt | 58 +- 5 files changed, 722 insertions(+), 540 deletions(-) create mode 100644 src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9081e3490..da1eae961 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ import org.gradle.api.plugins.JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME import org.gradle.plugin.compatibility.compatibility import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation @@ -30,7 +31,7 @@ dokka { dokkaPublications.html { outputDirectory = rootDir.resolve("docs/api") } kotlin { explicitApi() @OptIn(ExperimentalAbiValidation::class) abiValidation { enabled = true } - val jdkRelease = "24" + val jdkRelease = "17" compilerOptions { allWarningsAsErrors = true // https://docs.gradle.org/current/userguide/compatibility.html#kotlin @@ -45,6 +46,8 @@ kotlin { } } +addMultiReleaseSourceSet(24) + lint { baseline = file("gradle/lint-baseline.xml") ignoreTestFixturesSources = true @@ -222,6 +225,8 @@ kotlin.target.compilations { } } +tasks.jar { manifest.attributes("Multi-Release" to true) } + tasks.pluginUnderTestMetadata { pluginClasspath.from(testPluginClasspath) } tasks.check { dependsOn(tasks.withType()) } @@ -236,3 +241,21 @@ tasks.clean { rootDir.resolve("site"), ) } + +fun addMultiReleaseSourceSet(version: Int) { + kotlin.target.compilations.create("java${version}") { + associateWith(kotlin.target.compilations.getByName("main")) + compileTaskProvider { + compilerOptions { + this@compilerOptions as KotlinJvmCompilerOptions + jvmTarget = JvmTarget.fromTarget(version.toString()) + freeCompilerArgs.add("-Xjdk-release=$version") + } + } + compileJavaTaskProvider { options.release = version } + + tasks.jar { from(output.allOutputs) { into("META-INF/versions/$version") } } + + tasks.withType().configureEach { inputs.files(output.allOutputs) } + } +} diff --git a/gradle.properties b/gradle.properties index 1bf430bd6..68b93d05d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,11 @@ # https://kotlinlang.org/docs/gradle-configure-project.html#dependency-on-the-standard-library kotlin.stdlib.default.dependency=false +# Keep associated Kotlin compilations from depending on archive tasks (e.g., jar), which can create circular task graphs in multi-release setups. +# https://kotlinlang.org/docs/gradle-configure-project.html//disable-use-of-artifact-in-compilation-task +# https://kotlinlang.org/docs/whatsnew2020.html#added-task-dependency-for-rare-cases-when-the-compile-task-lacks-one-on-an-artifact +kotlin.build.archivesTaskOutputAsFriendModule=false + org.gradle.caching=true org.gradle.configuration-cache=true org.gradle.configuration-cache.parallel=true diff --git a/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt new file mode 100644 index 000000000..40a5a117c --- /dev/null +++ b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -0,0 +1,602 @@ +@file:Suppress("DuplicatedCode") + +package com.github.jengelman.gradle.plugins.shadow.internal + +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass +import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath +import java.lang.classfile.Annotation +import java.lang.classfile.AnnotationElement +import java.lang.classfile.AnnotationValue +import java.lang.classfile.ClassFile +import java.lang.classfile.ClassSignature +import java.lang.classfile.ClassTransform +import java.lang.classfile.CodeModel +import java.lang.classfile.CodeTransform +import java.lang.classfile.FieldModel +import java.lang.classfile.FieldTransform +import java.lang.classfile.Interfaces +import java.lang.classfile.MethodModel +import java.lang.classfile.MethodSignature +import java.lang.classfile.MethodTransform +import java.lang.classfile.Signature +import java.lang.classfile.Superclass +import java.lang.classfile.TypeAnnotation +import java.lang.classfile.attribute.AnnotationDefaultAttribute +import java.lang.classfile.attribute.ConstantValueAttribute +import java.lang.classfile.attribute.EnclosingMethodAttribute +import java.lang.classfile.attribute.ExceptionsAttribute +import java.lang.classfile.attribute.InnerClassInfo +import java.lang.classfile.attribute.InnerClassesAttribute +import java.lang.classfile.attribute.ModuleAttribute +import java.lang.classfile.attribute.ModuleExportInfo +import java.lang.classfile.attribute.ModuleOpenInfo +import java.lang.classfile.attribute.ModuleProvideInfo +import java.lang.classfile.attribute.NestHostAttribute +import java.lang.classfile.attribute.NestMembersAttribute +import java.lang.classfile.attribute.PermittedSubclassesAttribute +import java.lang.classfile.attribute.RecordAttribute +import java.lang.classfile.attribute.RecordComponentInfo +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute +import java.lang.classfile.attribute.SignatureAttribute +import java.lang.classfile.constantpool.StringEntry +import java.lang.classfile.instruction.ConstantInstruction +import java.lang.classfile.instruction.ExceptionCatch +import java.lang.classfile.instruction.FieldInstruction +import java.lang.classfile.instruction.InvokeDynamicInstruction +import java.lang.classfile.instruction.InvokeInstruction +import java.lang.classfile.instruction.LocalVariable +import java.lang.classfile.instruction.LocalVariableType +import java.lang.classfile.instruction.NewMultiArrayInstruction +import java.lang.classfile.instruction.NewObjectInstruction +import java.lang.classfile.instruction.NewReferenceArrayInstruction +import java.lang.classfile.instruction.TypeCheckInstruction +import java.lang.constant.ClassDesc +import java.lang.constant.ConstantDesc +import java.lang.constant.DirectMethodHandleDesc +import java.lang.constant.DynamicCallSiteDesc +import java.lang.constant.DynamicConstantDesc +import java.lang.constant.MethodHandleDesc +import java.lang.constant.MethodTypeDesc +import java.lang.constant.PackageDesc +import java.util.regex.Pattern +import java.util.zip.ZipException +import kotlin.jvm.optionals.getOrNull +import org.apache.tools.zip.UnixStat +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.GradleException +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.logging.Logger + +/** + * Ported from ASM's Remapper and JDK's ClassRemapperImpl to use Java 24 Class-File API. + * + * @author John Engelman + */ +internal class RelocatorRemapper( + private val relocators: Set, + private val onModified: () -> Unit = {}, +) { + + fun asClassTransform(): ClassTransform = ClassTransform { clb, cle -> + when (cle) { + is FieldModel -> + clb.withField( + cle.fieldName().stringValue(), + mapClassDesc(ClassDesc.ofDescriptor(cle.fieldType().stringValue())), + ) { fb -> + fb.withFlags(cle.flags().flagsMask()).transform(cle, asFieldTransform()) + } + is MethodModel -> + clb.withMethod( + cle.methodName().stringValue(), + mapMethodDesc(cle.methodTypeSymbol()), + cle.flags().flagsMask(), + ) { mb -> + mb.transform(cle, asMethodTransform()) + } + is Superclass -> clb.withSuperclass(mapClassDesc(cle.superclassEntry().asSymbol())) + is Interfaces -> + clb.withInterfaceSymbols(cle.interfaces().map { mapClassDesc(it.asSymbol()) }) + is SignatureAttribute -> + clb.with(SignatureAttribute.of(mapClassSignature(cle.asClassSignature()))) + is InnerClassesAttribute -> + clb.with( + InnerClassesAttribute.of( + cle.classes().map { ici -> + InnerClassInfo.of( + mapClassDesc(ici.innerClass().asSymbol()), + ici.outerClass().map { mapClassDesc(it.asSymbol()) }, + ici.innerName().map { it.stringValue() }, + ici.flagsMask(), + ) + } + ) + ) + is EnclosingMethodAttribute -> + clb.with( + EnclosingMethodAttribute.of( + mapClassDesc(cle.enclosingClass().asSymbol()), + cle.enclosingMethodName().map { it.stringValue() }, + cle + .enclosingMethodType() + .map { MethodTypeDesc.ofDescriptor(it.stringValue()) } + .map(this::mapMethodDesc), + ) + ) + is RecordAttribute -> + clb.with(RecordAttribute.of(cle.components().map(this::mapRecordComponent))) + is ModuleAttribute -> + clb.with( + ModuleAttribute.of( + cle.moduleName(), + cle.moduleFlagsMask(), + cle.moduleVersion().getOrNull(), + cle.requires(), + cle.exports().map { mei -> + ModuleExportInfo.of( + clb + .constantPool() + .packageEntry( + PackageDesc.ofInternalName(map(mei.exportedPackage().asSymbol().internalName())) + ), + mei.exportsFlagsMask(), + mei.exportsTo(), + ) + }, + cle.opens().map { moi -> + ModuleOpenInfo.of( + clb + .constantPool() + .packageEntry( + PackageDesc.ofInternalName(map(moi.openedPackage().asSymbol().internalName())) + ), + moi.opensFlagsMask(), + moi.opensTo(), + ) + }, + cle.uses().map { clb.constantPool().classEntry(mapClassDesc(it.asSymbol())) }, + cle.provides().map { mp -> + ModuleProvideInfo.of( + mapClassDesc(mp.provides().asSymbol()), + mp.providesWith().map { mapClassDesc(it.asSymbol()) }, + ) + }, + ) + ) + is NestHostAttribute -> + clb.with(NestHostAttribute.of(mapClassDesc(cle.nestHost().asSymbol()))) + is NestMembersAttribute -> + clb.with( + NestMembersAttribute.ofSymbols(cle.nestMembers().map { mapClassDesc(it.asSymbol()) }) + ) + is PermittedSubclassesAttribute -> + clb.with( + PermittedSubclassesAttribute.ofSymbols( + cle.permittedSubclasses().map { mapClassDesc(it.asSymbol()) } + ) + ) + is RuntimeVisibleAnnotationsAttribute -> + clb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + clb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + is RuntimeVisibleTypeAnnotationsAttribute -> + clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + else -> clb.with(cle) + } + } + + fun mapClassDesc(desc: ClassDesc): ClassDesc { + when { + desc.isArray -> return mapClassDesc(desc.componentType()).arrayType() + desc.isPrimitive -> return desc + } + val internalName = desc.descriptorString().let { it.substring(1, it.length - 1) } + val mappedInternalName = map(internalName) + return if (internalName == mappedInternalName) desc + else ClassDesc.ofDescriptor("L$mappedInternalName;") + } + + private fun map(name: String, mapLiterals: Boolean = false): String { + // Maybe a list of types. + val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) } + + if (newName != name) { + onModified() + } + return newName + } + + private fun mapNameImpl(name: String, mapLiterals: Boolean): String { + var newName = name + var prefix = "" + var suffix = "" + + val matcher = classPattern.matcher(newName) + if (matcher.matches()) { + prefix = matcher.group(1) + "L" + suffix = "" + newName = matcher.group(2) + } + + for (relocator in relocators) { + if (mapLiterals && relocator.skipStringConstants) { + continue + } else if (relocator.canRelocateClass(newName)) { + return prefix + relocator.relocateClass(newName) + suffix + } else if (relocator.canRelocatePath(newName)) { + return prefix + relocator.relocatePath(newName) + suffix + } + } + + return name + } + + private fun asFieldTransform(): FieldTransform = FieldTransform { fb, fe -> + when (fe) { + is ConstantValueAttribute -> { + val constant = fe.constant() + if (constant is StringEntry) { + val remapped = map(constant.stringValue(), true) + fb.with(ConstantValueAttribute.of(fb.constantPool().stringEntry(remapped))) + } else { + fb.with(fe) + } + } + is SignatureAttribute -> fb.with(SignatureAttribute.of(mapSignature(fe.asTypeSignature()))) + is RuntimeVisibleAnnotationsAttribute -> + fb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + fb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + is RuntimeVisibleTypeAnnotationsAttribute -> + fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + else -> fb.with(fe) + } + } + + private fun asMethodTransform(): MethodTransform = MethodTransform { mb, me -> + when (me) { + is AnnotationDefaultAttribute -> + mb.with(AnnotationDefaultAttribute.of(mapAnnotationValue(me.defaultValue()))) + is CodeModel -> mb.transformCode(me, asCodeTransform()) + is ExceptionsAttribute -> + mb.with(ExceptionsAttribute.ofSymbols(me.exceptions().map { mapClassDesc(it.asSymbol()) })) + is SignatureAttribute -> + mb.with(SignatureAttribute.of(mapMethodSignature(me.asMethodSignature()))) + is RuntimeVisibleAnnotationsAttribute -> + mb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + is RuntimeInvisibleAnnotationsAttribute -> + mb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + is RuntimeVisibleParameterAnnotationsAttribute -> + mb.with( + RuntimeVisibleParameterAnnotationsAttribute.of( + me.parameterAnnotations().map(this::mapAnnotations) + ) + ) + is RuntimeInvisibleParameterAnnotationsAttribute -> + mb.with( + RuntimeInvisibleParameterAnnotationsAttribute.of( + me.parameterAnnotations().map(this::mapAnnotations) + ) + ) + is RuntimeVisibleTypeAnnotationsAttribute -> + mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + else -> mb.with(me) + } + } + + private fun asCodeTransform(): CodeTransform = CodeTransform { cob, coe -> + when (coe) { + is FieldInstruction -> + cob.fieldAccess( + coe.opcode(), + mapClassDesc(coe.owner().asSymbol()), + coe.name().stringValue(), + mapClassDesc(coe.typeSymbol()), + ) + is InvokeInstruction -> + cob.invoke( + coe.opcode(), + mapClassDesc(coe.owner().asSymbol()), + coe.name().stringValue(), + mapMethodDesc(coe.typeSymbol()), + coe.isInterface, + ) + is InvokeDynamicInstruction -> + cob.invokedynamic( + DynamicCallSiteDesc.of( + mapDirectMethodHandle(coe.bootstrapMethod()), + coe.name().stringValue(), + mapMethodDesc(coe.typeSymbol()), + *coe.bootstrapArgs().map(this::mapConstantValue).toTypedArray(), + ) + ) + is NewObjectInstruction -> cob.new_(mapClassDesc(coe.className().asSymbol())) + is NewReferenceArrayInstruction -> cob.anewarray(mapClassDesc(coe.componentType().asSymbol())) + is NewMultiArrayInstruction -> + cob.multianewarray(mapClassDesc(coe.arrayType().asSymbol()), coe.dimensions()) + is TypeCheckInstruction -> + cob.with(TypeCheckInstruction.of(coe.opcode(), mapClassDesc(coe.type().asSymbol()))) + is ExceptionCatch -> + cob.exceptionCatch( + coe.tryStart(), + coe.tryEnd(), + coe.handler(), + coe.catchType().map { cob.constantPool().classEntry(mapClassDesc(it.asSymbol())) }, + ) + is LocalVariable -> + cob.localVariable( + coe.slot(), + coe.name().stringValue(), + mapClassDesc(coe.typeSymbol()), + coe.startScope(), + coe.endScope(), + ) + is LocalVariableType -> + cob.localVariableType( + coe.slot(), + coe.name().stringValue(), + mapSignature(coe.signatureSymbol()), + coe.startScope(), + coe.endScope(), + ) + is ConstantInstruction.LoadConstantInstruction -> { + val value = coe.constantEntry().constantValue() + val name = value.javaClass.name + @Suppress("CAST_NEVER_SUCCEEDS") + when (name) { + "java.lang.String" -> { + val s = value.toString() + cob.ldc(cob.constantPool().stringEntry(map(s, mapLiterals = true))) + } + "java.lang.Integer" -> cob.ldc(cob.constantPool().intEntry(value as Int)) + "java.lang.Float" -> cob.ldc(cob.constantPool().floatEntry(value as Float)) + "java.lang.Long" -> cob.ldc(cob.constantPool().longEntry(value as Long)) + "java.lang.Double" -> cob.ldc(cob.constantPool().doubleEntry(value as Double)) + else -> cob.ldc(mapConstantValue(value as ConstantDesc)) + } + } + is RuntimeVisibleTypeAnnotationsAttribute -> + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + is RuntimeInvisibleTypeAnnotationsAttribute -> + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + else -> cob.with(coe) + } + } + + private fun mapMethodDesc(desc: MethodTypeDesc): MethodTypeDesc { + return MethodTypeDesc.of( + mapClassDesc(desc.returnType()), + *desc.parameterList().map { mapClassDesc(it) }.toTypedArray(), + ) + } + + private fun mapClassSignature(signature: ClassSignature): ClassSignature { + val superclassSignature = signature.superclassSignature()?.let { mapSignature(it) } + return ClassSignature.of( + mapTypeParams(signature.typeParameters()), + superclassSignature, + *signature.superinterfaceSignatures().map { mapSignature(it) }.toTypedArray(), + ) + } + + private fun mapMethodSignature(signature: MethodSignature): MethodSignature { + return MethodSignature.of( + mapTypeParams(signature.typeParameters()), + signature.throwableSignatures().map { mapSignature(it) }, + mapSignature(signature.result()), + *signature.arguments().map { mapSignature(it) }.toTypedArray(), + ) + } + + private fun mapRecordComponent(component: RecordComponentInfo): RecordComponentInfo { + return RecordComponentInfo.of( + component.name().stringValue(), + mapClassDesc(component.descriptorSymbol()), + component.attributes().map { atr -> + when (atr) { + is SignatureAttribute -> SignatureAttribute.of(mapSignature(atr.asTypeSignature())) + is RuntimeVisibleAnnotationsAttribute -> + RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + is RuntimeInvisibleAnnotationsAttribute -> + RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + is RuntimeVisibleTypeAnnotationsAttribute -> + RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + is RuntimeInvisibleTypeAnnotationsAttribute -> + RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + else -> atr + } + }, + ) + } + + private fun mapDirectMethodHandle(dmhd: DirectMethodHandleDesc): DirectMethodHandleDesc { + return when (dmhd.kind()) { + DirectMethodHandleDesc.Kind.GETTER, + DirectMethodHandleDesc.Kind.SETTER, + DirectMethodHandleDesc.Kind.STATIC_GETTER, + DirectMethodHandleDesc.Kind.STATIC_SETTER -> + MethodHandleDesc.ofField( + dmhd.kind(), + mapClassDesc(dmhd.owner()), + dmhd.methodName(), + mapClassDesc(ClassDesc.ofDescriptor(dmhd.lookupDescriptor())), + ) + else -> + MethodHandleDesc.ofMethod( + dmhd.kind(), + mapClassDesc(dmhd.owner()), + dmhd.methodName(), + mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor())), + ) + } + } + + private fun mapConstantValue(value: ConstantDesc): ConstantDesc { + return when (value) { + is ClassDesc -> mapClassDesc(value) + is DynamicConstantDesc<*> -> mapDynamicConstant(value) + is DirectMethodHandleDesc -> mapDirectMethodHandle(value) + is MethodTypeDesc -> mapMethodDesc(value) + else -> { + @Suppress("CAST_NEVER_SUCCEEDS") + if (value.javaClass.name == "java.lang.String") { + map(value.toString(), mapLiterals = true) as ConstantDesc + } else { + value + } + } + } + } + + private fun mapDynamicConstant(dcd: DynamicConstantDesc<*>): DynamicConstantDesc<*> { + return DynamicConstantDesc.ofNamed( + mapDirectMethodHandle(dcd.bootstrapMethod()), + dcd.constantName(), + mapClassDesc(dcd.constantType()), + *dcd.bootstrapArgsList().map(this::mapConstantValue).toTypedArray(), + ) + } + + @Suppress("UNCHECKED_CAST") + private fun mapSignature(signature: S): S { + return when (signature) { + is Signature.ArrayTypeSig -> + Signature.ArrayTypeSig.of(mapSignature(signature.componentSignature())) as S + is Signature.ClassTypeSig -> { + val mappedOuter = signature.outerType().getOrNull()?.let { mapSignature(it) } + val mappedClass = mapClassDesc(signature.classDesc()) + // Extract internal name simply bypassing util + val internalName = mappedClass.descriptorString().let { it.substring(1, it.length - 1) } + Signature.ClassTypeSig.of( + mappedOuter, + internalName, + *signature + .typeArgs() + .map { ta -> + when (ta) { + is Signature.TypeArg.Unbounded -> ta + is Signature.TypeArg.Bounded -> + Signature.TypeArg.bounded(ta.wildcardIndicator(), mapSignature(ta.boundType())) + } + } + .toTypedArray(), + ) as S + } + else -> signature + } + } + + private fun mapAnnotations(annotations: List) = annotations.map(this::mapAnnotation) + + private fun mapAnnotation(a: Annotation) = + Annotation.of( + mapClassDesc(a.classSymbol()), + a.elements().map { el -> AnnotationElement.of(el.name(), mapAnnotationValue(el.value())) }, + ) + + private fun mapAnnotationValue(valObj: AnnotationValue): AnnotationValue { + return when (valObj) { + is AnnotationValue.OfAnnotation -> + AnnotationValue.ofAnnotation(mapAnnotation(valObj.annotation())) + is AnnotationValue.OfArray -> + AnnotationValue.ofArray(valObj.values().map(this::mapAnnotationValue)) + is AnnotationValue.OfConstant -> { + if (valObj is AnnotationValue.OfString) { + val str = valObj.stringValue() + // mapLiterals=true enables the skipStringConstants check in each relocator. + val mapped = map(str, mapLiterals = true) + if (mapped != str) AnnotationValue.ofString(mapped) else valObj + } else { + valObj + } + } + is AnnotationValue.OfClass -> AnnotationValue.ofClass(mapClassDesc(valObj.classSymbol())) + is AnnotationValue.OfEnum -> + AnnotationValue.ofEnum( + mapClassDesc(valObj.classSymbol()), + valObj.constantName().stringValue(), + ) + } + } + + private fun mapTypeAnnotations(typeAnnotations: List) = + typeAnnotations.map { a -> + TypeAnnotation.of(a.targetInfo(), a.targetPath(), mapAnnotation(a.annotation())) + } + + private fun mapTypeParams(typeParams: List) = + typeParams.map { tp -> + Signature.TypeParam.of( + tp.identifier(), + tp.classBound().getOrNull()?.let { mapSignature(it) }, + *tp.interfaceBounds().map { mapSignature(it) }.toTypedArray(), + ) + } + + private companion object { + /** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */ + val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") + } +} + +@Suppress("unused", "DuplicatedCode") // Used by Multi-Release JARs for Java 24+. +internal fun FileCopyDetails.remapClass( + relocators: Set, + zipOutStr: ZipOutputStream, + preserveFileTimestamps: Boolean, + lastModified: Long, + logger: Logger, +) { + file.readBytes().let { bytes -> + var modified = false + val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() + val remapper = RelocatorRemapper(relocators) { modified = true } + + val newBytes = + try { + val classFile = ClassFile.of() + val classModel = classFile.parse(bytes) + val originalClassDesc = classModel.thisClass().asSymbol() + val newClassDesc = remapper.mapClassDesc(originalClassDesc) + classFile.transformClass(classModel, newClassDesc, remapper.asClassTransform()) + } catch (t: Throwable) { + throw GradleException("Error in Class-File API processing class $path", t) + } + + val finalBytes = + if (modified) { + newBytes + } else { + // If we didn't need to change anything, keep the original bytes as-is + bytes + } + + // Multi-release prefix was calculated earlier. + val newPath = path.replace(multiReleasePrefix, "") + val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) + try { + val entry = + zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { + unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() + } + // Now we put it back on so the class file is written out with the right extension. + zipOutStr.putNextEntry(entry) + zipOutStr.write(finalBytes) + zipOutStr.closeEntry() + } catch (_: ZipException) { + logger.warn("We have a duplicate $relocatedPath in source project") + } + } +} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index 3f22e3838..030b856fd 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -3,508 +3,43 @@ package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath -import java.lang.classfile.AnnotationElement -import java.lang.classfile.AnnotationValue -import java.lang.classfile.ClassSignature -import java.lang.classfile.ClassTransform -import java.lang.classfile.CodeModel -import java.lang.classfile.CodeTransform -import java.lang.classfile.FieldModel -import java.lang.classfile.FieldTransform -import java.lang.classfile.Interfaces -import java.lang.classfile.MethodModel -import java.lang.classfile.MethodSignature -import java.lang.classfile.MethodTransform -import java.lang.classfile.Signature -import java.lang.classfile.Superclass -import java.lang.classfile.TypeAnnotation -import java.lang.classfile.attribute.AnnotationDefaultAttribute -import java.lang.classfile.attribute.ConstantValueAttribute -import java.lang.classfile.attribute.EnclosingMethodAttribute -import java.lang.classfile.attribute.ExceptionsAttribute -import java.lang.classfile.attribute.InnerClassInfo -import java.lang.classfile.attribute.InnerClassesAttribute -import java.lang.classfile.attribute.ModuleAttribute -import java.lang.classfile.attribute.ModuleExportInfo -import java.lang.classfile.attribute.ModuleOpenInfo -import java.lang.classfile.attribute.ModuleProvideInfo -import java.lang.classfile.attribute.NestHostAttribute -import java.lang.classfile.attribute.NestMembersAttribute -import java.lang.classfile.attribute.PermittedSubclassesAttribute -import java.lang.classfile.attribute.RecordAttribute -import java.lang.classfile.attribute.RecordComponentInfo -import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute -import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute -import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute -import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute -import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute -import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute -import java.lang.classfile.attribute.SignatureAttribute -import java.lang.classfile.constantpool.StringEntry -import java.lang.classfile.instruction.ConstantInstruction -import java.lang.classfile.instruction.ExceptionCatch -import java.lang.classfile.instruction.FieldInstruction -import java.lang.classfile.instruction.InvokeDynamicInstruction -import java.lang.classfile.instruction.InvokeInstruction -import java.lang.classfile.instruction.LocalVariable -import java.lang.classfile.instruction.LocalVariableType -import java.lang.classfile.instruction.NewMultiArrayInstruction -import java.lang.classfile.instruction.NewObjectInstruction -import java.lang.classfile.instruction.NewReferenceArrayInstruction -import java.lang.classfile.instruction.TypeCheckInstruction -import java.lang.constant.ClassDesc -import java.lang.constant.ConstantDesc -import java.lang.constant.DirectMethodHandleDesc -import java.lang.constant.DynamicCallSiteDesc -import java.lang.constant.DynamicConstantDesc -import java.lang.constant.MethodHandleDesc -import java.lang.constant.MethodTypeDesc -import java.lang.constant.PackageDesc import java.util.regex.Pattern +import java.util.zip.ZipException +import org.apache.tools.zip.UnixStat +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.GradleException +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.logging.Logger +import org.vafer.jdeb.shaded.objectweb.asm.ClassReader +import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter +import org.vafer.jdeb.shaded.objectweb.asm.Opcodes +import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper +import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper /** - * Ported from ASM's Remapper and JDK's ClassRemapperImpl to use Java 24 Class-File API. + * Modified from + * [org.apache.maven.plugins.shade.DefaultShader.RelocatorRemapper](https://github.com/apache/maven-shade-plugin/blob/83c123d1f9c5f6927af2aca12ee322b5168a7c63/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L689-L772). + * Modified from + * [org.apache.maven.plugins.shade.DefaultShader.DefaultPackageMapper](https://github.com/apache/maven-shade-plugin/blob/199ffaecd26a912527173ed4edae366e48a00998/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L737-L774). * * @author John Engelman */ internal class RelocatorRemapper( private val relocators: Set, private val onModified: () -> Unit = {}, -) { +) : Remapper(Opcodes.ASM9) { - fun asClassTransform(): ClassTransform = ClassTransform { clb, cle -> - when (cle) { - is FieldModel -> - clb.withField( - cle.fieldName().stringValue(), - mapClassDesc(ClassDesc.ofDescriptor(cle.fieldType().stringValue())), - ) { fb -> - fb.withFlags(cle.flags().flagsMask()).transform(cle, asFieldTransform()) - } - is MethodModel -> - clb.withMethod( - cle.methodName().stringValue(), - mapMethodDesc(cle.methodTypeSymbol()), - cle.flags().flagsMask(), - ) { mb -> - mb.transform(cle, asMethodTransform()) - } - is Superclass -> clb.withSuperclass(mapClassDesc(cle.superclassEntry().asSymbol())) - is Interfaces -> - clb.withInterfaceSymbols(cle.interfaces().map { mapClassDesc(it.asSymbol())!! }) - is SignatureAttribute -> - clb.with(SignatureAttribute.of(mapClassSignature(cle.asClassSignature()))) - is InnerClassesAttribute -> - clb.with( - InnerClassesAttribute.of( - cle.classes().map { ici -> - InnerClassInfo.of( - mapClassDesc(ici.innerClass().asSymbol()), - ici.outerClass().map { mapClassDesc(it.asSymbol()) }, - ici.innerName().map { it.stringValue() }, - ici.flagsMask(), - ) - } - ) - ) - is EnclosingMethodAttribute -> - clb.with( - EnclosingMethodAttribute.of( - mapClassDesc(cle.enclosingClass().asSymbol()), - cle.enclosingMethodName().map { it.stringValue() }, - cle - .enclosingMethodType() - .map { MethodTypeDesc.ofDescriptor(it.stringValue()) } - .map(this::mapMethodDesc), - ) - ) - is RecordAttribute -> - clb.with(RecordAttribute.of(cle.components().map(this::mapRecordComponent))) - is ModuleAttribute -> - clb.with( - ModuleAttribute.of( - cle.moduleName(), - cle.moduleFlagsMask(), - cle.moduleVersion().orElse(null), - cle.requires(), - cle.exports().map { mei -> - ModuleExportInfo.of( - clb - .constantPool() - .packageEntry( - PackageDesc.ofInternalName(map(mei.exportedPackage().asSymbol().internalName())) - ), - mei.exportsFlagsMask(), - mei.exportsTo(), - ) - }, - cle.opens().map { moi -> - ModuleOpenInfo.of( - clb - .constantPool() - .packageEntry( - PackageDesc.ofInternalName(map(moi.openedPackage().asSymbol().internalName())) - ), - moi.opensFlagsMask(), - moi.opensTo(), - ) - }, - cle.uses().map { clb.constantPool().classEntry(mapClassDesc(it.asSymbol())!!) }, - cle.provides().map { mp -> - ModuleProvideInfo.of( - mapClassDesc(mp.provides().asSymbol()), - mp.providesWith().map { mapClassDesc(it.asSymbol())!! }, - ) - }, - ) - ) - is NestHostAttribute -> - clb.with(NestHostAttribute.of(mapClassDesc(cle.nestHost().asSymbol()))) - is NestMembersAttribute -> - clb.with( - NestMembersAttribute.ofSymbols(cle.nestMembers().map { mapClassDesc(it.asSymbol())!! }) - ) - is PermittedSubclassesAttribute -> - clb.with( - PermittedSubclassesAttribute.ofSymbols( - cle.permittedSubclasses().map { mapClassDesc(it.asSymbol())!! } - ) - ) - is RuntimeVisibleAnnotationsAttribute -> - clb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) - is RuntimeInvisibleAnnotationsAttribute -> - clb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) - is RuntimeVisibleTypeAnnotationsAttribute -> - clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) - is RuntimeInvisibleTypeAnnotationsAttribute -> - clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) - else -> clb.with(cle) - } - } - - private fun asFieldTransform(): FieldTransform = FieldTransform { fb, fe -> - when (fe) { - is ConstantValueAttribute -> { - val constant = fe.constant() - if (constant is StringEntry) { - val remapped = map(constant.stringValue(), true) - fb.with(ConstantValueAttribute.of(fb.constantPool().stringEntry(remapped))) - } else { - fb.with(fe) - } - } - is SignatureAttribute -> fb.with(SignatureAttribute.of(mapSignature(fe.asTypeSignature()))) - is RuntimeVisibleAnnotationsAttribute -> - fb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) - is RuntimeInvisibleAnnotationsAttribute -> - fb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) - is RuntimeVisibleTypeAnnotationsAttribute -> - fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) - is RuntimeInvisibleTypeAnnotationsAttribute -> - fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) - else -> fb.with(fe) - } - } - - private fun asMethodTransform(): MethodTransform = MethodTransform { mb, me -> - when (me) { - is AnnotationDefaultAttribute -> - mb.with(AnnotationDefaultAttribute.of(mapAnnotationValue(me.defaultValue()))) - is CodeModel -> mb.transformCode(me, asCodeTransform()) - is ExceptionsAttribute -> - mb.with( - ExceptionsAttribute.ofSymbols(me.exceptions().map { mapClassDesc(it.asSymbol())!! }) - ) - is SignatureAttribute -> - mb.with(SignatureAttribute.of(mapMethodSignature(me.asMethodSignature()))) - is RuntimeVisibleAnnotationsAttribute -> - mb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) - is RuntimeInvisibleAnnotationsAttribute -> - mb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) - is RuntimeVisibleParameterAnnotationsAttribute -> - mb.with( - RuntimeVisibleParameterAnnotationsAttribute.of( - me.parameterAnnotations().map(this::mapAnnotations) - ) - ) - is RuntimeInvisibleParameterAnnotationsAttribute -> - mb.with( - RuntimeInvisibleParameterAnnotationsAttribute.of( - me.parameterAnnotations().map(this::mapAnnotations) - ) - ) - is RuntimeVisibleTypeAnnotationsAttribute -> - mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) - is RuntimeInvisibleTypeAnnotationsAttribute -> - mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) - else -> mb.with(me) - } - } - - private fun asCodeTransform(): CodeTransform = CodeTransform { cob, coe -> - when (coe) { - is FieldInstruction -> - cob.fieldAccess( - coe.opcode(), - mapClassDesc(coe.owner().asSymbol())!!, - coe.name().stringValue(), - mapClassDesc(coe.typeSymbol())!!, - ) - is InvokeInstruction -> - cob.invoke( - coe.opcode(), - mapClassDesc(coe.owner().asSymbol())!!, - coe.name().stringValue(), - mapMethodDesc(coe.typeSymbol()), - coe.isInterface(), - ) - is InvokeDynamicInstruction -> - cob.invokedynamic( - DynamicCallSiteDesc.of( - mapDirectMethodHandle(coe.bootstrapMethod()), - coe.name().stringValue(), - mapMethodDesc(coe.typeSymbol()), - *coe.bootstrapArgs().map(this::mapConstantValue).toTypedArray(), - ) - ) - is NewObjectInstruction -> cob.new_(mapClassDesc(coe.className().asSymbol())!!) - is NewReferenceArrayInstruction -> - cob.anewarray(mapClassDesc(coe.componentType().asSymbol())!!) - is NewMultiArrayInstruction -> - cob.multianewarray(mapClassDesc(coe.arrayType().asSymbol())!!, coe.dimensions()) - is TypeCheckInstruction -> - cob.with(TypeCheckInstruction.of(coe.opcode(), mapClassDesc(coe.type().asSymbol())!!)) - is ExceptionCatch -> - cob.exceptionCatch( - coe.tryStart(), - coe.tryEnd(), - coe.handler(), - coe.catchType().map { cob.constantPool().classEntry(mapClassDesc(it.asSymbol())!!) }, - ) - is LocalVariable -> - cob.localVariable( - coe.slot(), - coe.name().stringValue(), - mapClassDesc(coe.typeSymbol())!!, - coe.startScope(), - coe.endScope(), - ) - is LocalVariableType -> - cob.localVariableType( - coe.slot(), - coe.name().stringValue(), - mapSignature(coe.signatureSymbol()), - coe.startScope(), - coe.endScope(), - ) - is ConstantInstruction.LoadConstantInstruction -> { - val value = coe.constantEntry().constantValue() - val name = value.javaClass.name - if (name == "java.lang.String") { - val s = value.toString() - cob.ldc(cob.constantPool().stringEntry(map(s, mapLiterals = true))) - } else if (name == "java.lang.Integer") { - cob.ldc(cob.constantPool().intEntry(value as Int)) - } else if (name == "java.lang.Float") { - cob.ldc(cob.constantPool().floatEntry(value as Float)) - } else if (name == "java.lang.Long") { - cob.ldc(cob.constantPool().longEntry(value as Long)) - } else if (name == "java.lang.Double") { - cob.ldc(cob.constantPool().doubleEntry(value as Double)) - } else { - cob.ldc(mapConstantValue(value as ConstantDesc)) - } - } - is RuntimeVisibleTypeAnnotationsAttribute -> - cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) - is RuntimeInvisibleTypeAnnotationsAttribute -> - cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) - else -> cob.with(coe) - } - } - - fun mapClassDesc(desc: ClassDesc?): ClassDesc? { - if (desc == null) return null - if (desc.isArray) return mapClassDesc(desc.componentType())!!.arrayType() - if (desc.isPrimitive) return desc - val internalName = desc.descriptorString().let { it.substring(1, it.length - 1) } - val mappedInternalName = map(internalName) - return if (internalName == mappedInternalName) desc - else ClassDesc.ofDescriptor("L$mappedInternalName;") - } - - private fun mapMethodDesc(desc: MethodTypeDesc): MethodTypeDesc { - return MethodTypeDesc.of( - mapClassDesc(desc.returnType()), - *desc.parameterList().map { mapClassDesc(it)!! }.toTypedArray(), - ) - } - - private fun mapClassSignature(signature: ClassSignature): ClassSignature { - val superclassSignature = signature.superclassSignature()?.let { mapSignature(it) } - return ClassSignature.of( - mapTypeParams(signature.typeParameters()), - superclassSignature, - *signature.superinterfaceSignatures().map { mapSignature(it) }.toTypedArray(), - ) - } - - private fun mapMethodSignature(signature: MethodSignature): MethodSignature { - return MethodSignature.of( - mapTypeParams(signature.typeParameters()), - signature.throwableSignatures().map { mapSignature(it) }, - mapSignature(signature.result()), - *signature.arguments().map { mapSignature(it) }.toTypedArray(), - ) - } - - private fun mapRecordComponent(component: RecordComponentInfo): RecordComponentInfo { - return RecordComponentInfo.of( - component.name().stringValue(), - mapClassDesc(component.descriptorSymbol())!!, - component.attributes().map { atr -> - when (atr) { - is SignatureAttribute -> SignatureAttribute.of(mapSignature(atr.asTypeSignature())) - is RuntimeVisibleAnnotationsAttribute -> - RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) - is RuntimeInvisibleAnnotationsAttribute -> - RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) - is RuntimeVisibleTypeAnnotationsAttribute -> - RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) - is RuntimeInvisibleTypeAnnotationsAttribute -> - RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) - else -> atr - } - }, - ) - } - - private fun mapDirectMethodHandle(dmhd: DirectMethodHandleDesc): DirectMethodHandleDesc { - return when (dmhd.kind()) { - DirectMethodHandleDesc.Kind.GETTER, - DirectMethodHandleDesc.Kind.SETTER, - DirectMethodHandleDesc.Kind.STATIC_GETTER, - DirectMethodHandleDesc.Kind.STATIC_SETTER -> - MethodHandleDesc.ofField( - dmhd.kind(), - mapClassDesc(dmhd.owner())!!, - dmhd.methodName(), - mapClassDesc(ClassDesc.ofDescriptor(dmhd.lookupDescriptor()))!!, - ) - else -> - MethodHandleDesc.ofMethod( - dmhd.kind(), - mapClassDesc(dmhd.owner())!!, - dmhd.methodName(), - mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor())), - ) - } - } - - private fun mapConstantValue(value: ConstantDesc): ConstantDesc { - return when (value) { - is ClassDesc -> mapClassDesc(value)!! - is DynamicConstantDesc<*> -> mapDynamicConstant(value) - is DirectMethodHandleDesc -> mapDirectMethodHandle(value) - is MethodTypeDesc -> mapMethodDesc(value) - else -> { - if (value.javaClass.name == "java.lang.String") - map(value.toString(), mapLiterals = true) as ConstantDesc - else value - } - } - } - - private fun mapDynamicConstant(dcd: DynamicConstantDesc<*>): DynamicConstantDesc<*> { - return DynamicConstantDesc.ofNamed( - mapDirectMethodHandle(dcd.bootstrapMethod()), - dcd.constantName(), - mapClassDesc(dcd.constantType())!!, - *dcd.bootstrapArgsList().map(this::mapConstantValue).toTypedArray(), - ) - } - - @Suppress("UNCHECKED_CAST") - private fun mapSignature(signature: S): S { - return when (signature) { - is Signature.ArrayTypeSig -> - Signature.ArrayTypeSig.of(mapSignature(signature.componentSignature())) as S - is Signature.ClassTypeSig -> { - val mappedOuter = signature.outerType().orElse(null)?.let { mapSignature(it) } - val mappedClass = mapClassDesc(signature.classDesc())!! - // Extract internal name simply bypassing util - val internalName = mappedClass.descriptorString().let { it.substring(1, it.length - 1) } - Signature.ClassTypeSig.of( - mappedOuter, - internalName, - *signature - .typeArgs() - .map { ta -> - when (ta) { - is Signature.TypeArg.Unbounded -> ta - is Signature.TypeArg.Bounded -> - Signature.TypeArg.bounded(ta.wildcardIndicator(), mapSignature(ta.boundType())) - } - } - .toTypedArray(), - ) as S - } - else -> signature - } - } - - private fun mapAnnotations( - annotations: List - ): List = annotations.map(this::mapAnnotation) - - private fun mapAnnotation(a: java.lang.classfile.Annotation): java.lang.classfile.Annotation = - java.lang.classfile.Annotation.of( - mapClassDesc(a.classSymbol())!!, - a.elements().map { el -> AnnotationElement.of(el.name(), mapAnnotationValue(el.value())) }, - ) - - private fun mapAnnotationValue(valObj: AnnotationValue): AnnotationValue { - return when (valObj) { - is AnnotationValue.OfAnnotation -> - AnnotationValue.ofAnnotation(mapAnnotation(valObj.annotation())) - is AnnotationValue.OfArray -> - AnnotationValue.ofArray(valObj.values().map(this::mapAnnotationValue)) - is AnnotationValue.OfConstant -> { - if (valObj is AnnotationValue.OfString) { - val str = valObj.stringValue() - // mapLiterals=true enables the skipStringConstants check in each relocator. - val mapped = map(str, mapLiterals = true) - if (mapped != str) AnnotationValue.ofString(mapped) else valObj - } else { - valObj - } - } - is AnnotationValue.OfClass -> AnnotationValue.ofClass(mapClassDesc(valObj.classSymbol())!!) - is AnnotationValue.OfEnum -> - AnnotationValue.ofEnum( - mapClassDesc(valObj.classSymbol())!!, - valObj.constantName().stringValue(), - ) + override fun mapValue(value: Any): Any { + return if (value is String) { + mapName(value, mapLiterals = true) + } else { + super.mapValue(value) } } - private fun mapTypeAnnotations(typeAnnotations: List): List = - typeAnnotations.map { a -> - TypeAnnotation.of(a.targetInfo(), a.targetPath(), mapAnnotation(a.annotation())) - } - - private fun mapTypeParams(typeParams: List): List = - typeParams.map { tp -> - Signature.TypeParam.of( - tp.identifier(), - tp.classBound().orElse(null)?.let { mapSignature(it) }, - *tp.interfaceBounds().map { mapSignature(it) }.toTypedArray(), - ) - } - - fun map(name: String): String = map(name, false) + override fun map(internalName: String): String = mapName(internalName) - fun map(name: String, mapLiterals: Boolean = false): String { + private fun mapName(name: String, mapLiterals: Boolean = false): String { // Maybe a list of types. val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) } @@ -528,7 +63,7 @@ internal class RelocatorRemapper( for (relocator in relocators) { if (mapLiterals && relocator.skipStringConstants) { - continue + return name } else if (relocator.canRelocateClass(newName)) { return prefix + relocator.relocateClass(newName) + suffix } else if (relocator.canRelocatePath(newName)) { @@ -544,3 +79,62 @@ internal class RelocatorRemapper( val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") } } + +/** + * Applies remapping to the given class with the specified relocation path. The remapped class is + * then written to the zip file. + */ +internal fun FileCopyDetails.remapClass( + relocators: Set, + zipOutStr: ZipOutputStream, + preserveFileTimestamps: Boolean, + lastModified: Long, + logger: Logger, +) = + file.readBytes().let { bytes -> + var modified = false + val remapper = RelocatorRemapper(relocators) { modified = true } + + // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant + // pool. + // Copying the original constant pool should be avoided because it would keep references + // to the original class names. This is not a problem at runtime (because these entries in + // the + // constant pool are never used), but confuses some tools such as Felix's + // maven-bundle-plugin + // that use the constant pool to determine the dependencies of a class. + val cw = ClassWriter(0) + val cr = ClassReader(bytes) + val cv = ClassRemapper(cw, remapper) + + try { + cr.accept(cv, ClassReader.EXPAND_FRAMES) + } catch (t: Throwable) { + throw GradleException("Error in ASM processing class $path", t) + } + + val newBytes = + if (modified) { + cw.toByteArray() + } else { + // If we didn't need to change anything, keep the original bytes as-is + bytes + } + + // Temporarily remove the multi-release prefix. + val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() + val newPath = path.replace(multiReleasePrefix, "") + val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) + try { + val entry = + zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { + unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() + } + // Now we put it back on so the class file is written out with the right extension. + zipOutStr.putNextEntry(entry) + zipOutStr.write(newBytes) + zipOutStr.closeEntry() + } catch (_: ZipException) { + logger.warn("We have a duplicate $relocatedPath in source project") + } + } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt index 4ca734184..0a9d865e7 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt @@ -4,8 +4,8 @@ package com.github.jengelman.gradle.plugins.shadow.tasks -import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper import com.github.jengelman.gradle.plugins.shadow.internal.cast +import com.github.jengelman.gradle.plugins.shadow.internal.remapClass import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass @@ -13,9 +13,7 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext import java.io.File -import java.lang.classfile.ClassFile import java.util.GregorianCalendar -import java.util.zip.ZipException import kotlin.metadata.jvm.KmModule import kotlin.metadata.jvm.KmPackageParts import kotlin.metadata.jvm.KotlinModuleMetadata @@ -176,7 +174,13 @@ constructor( if (relocators.isEmpty()) { fileDetails.writeToZip(path) } else { - fileDetails.remapClass() + fileDetails.remapClass( + relocators, + zipOutStr, + preserveFileTimestamps, + fileDetails.lastModified, + logger, + ) } } enableKotlinModuleRemapping && path.endsWith(".kotlin_module") -> { @@ -203,52 +207,6 @@ constructor( } } - /** - * Applies remapping to the given class with the specified relocation path. The remapped class - * is then written to the zip file. - */ - private fun FileCopyDetails.remapClass() = - file.readBytes().let { bytes -> - var modified = false - val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() - val remapper = RelocatorRemapper(relocators) { modified = true } - - val newBytes = - try { - val classFile = ClassFile.of() - val classModel = classFile.parse(bytes) - val originalClassDesc = classModel.thisClass().asSymbol() - val newClassDesc = remapper.mapClassDesc(originalClassDesc)!! - classFile.transformClass(classModel, newClassDesc, remapper.asClassTransform()) - } catch (t: Throwable) { - throw GradleException("Error in Class-File API processing class $path", t) - } - - val finalBytes = - if (modified) { - newBytes - } else { - // If we didn't need to change anything, keep the original bytes as-is - bytes - } - - // Multi-release prefix was calculated earlier. - val newPath = path.replace(multiReleasePrefix, "") - val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) - try { - val entry = - zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { - unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() - } - // Now we put it back on so the class file is written out with the right extension. - zipOutStr.putNextEntry(entry) - zipOutStr.write(finalBytes) - zipOutStr.closeEntry() - } catch (_: ZipException) { - logger.warn("We have a duplicate $relocatedPath in source project") - } - } - /** * Applies remapping to the given kotlin module with the specified relocation path. The remapped * module is then written to the zip file. From db85c86ba5435805a6dc3747aeb34e5b071ba52f Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 18:27:00 +0800 Subject: [PATCH 3/5] Fix merge --- .../shadow/internal/RelocatorRemapper.kt | 136 ++++-------------- 1 file changed, 27 insertions(+), 109 deletions(-) diff --git a/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index 40a5a117c..da3405533 100644 --- a/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -1,10 +1,6 @@ -@file:Suppress("DuplicatedCode") - package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator -import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass -import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import java.lang.classfile.Annotation import java.lang.classfile.AnnotationElement import java.lang.classfile.AnnotationValue @@ -64,25 +60,34 @@ import java.lang.constant.DynamicConstantDesc import java.lang.constant.MethodHandleDesc import java.lang.constant.MethodTypeDesc import java.lang.constant.PackageDesc -import java.util.regex.Pattern -import java.util.zip.ZipException import kotlin.jvm.optionals.getOrNull -import org.apache.tools.zip.UnixStat -import org.apache.tools.zip.ZipOutputStream import org.gradle.api.GradleException import org.gradle.api.file.FileCopyDetails -import org.gradle.api.logging.Logger -/** - * Ported from ASM's Remapper and JDK's ClassRemapperImpl to use Java 24 Class-File API. - * - * @author John Engelman - */ -internal class RelocatorRemapper( +@Suppress("unused") // Used by Multi-Release JARs for Java 24+. +internal fun FileCopyDetails.remapClass(relocators: Set): ByteArray = + file.readBytes().let { bytes -> + var modified = false + val remapper = RelocatorRemapper(relocators) { modified = true } + + val newBytes = + try { + val classFile = ClassFile.of() + val classModel = classFile.parse(bytes) + val newClassDesc = remapper.mapClassDesc(classModel.thisClass().asSymbol()) + classFile.transformClass(classModel, newClassDesc, remapper.asClassTransform()) + } catch (t: Throwable) { + throw GradleException("Error in Class-File API processing class $path", t) + } + + // If we didn't need to change anything, keep the original bytes as-is. + if (modified) newBytes else bytes + } + +private class RelocatorRemapper( private val relocators: Set, - private val onModified: () -> Unit = {}, + private val onModified: () -> Unit, ) { - fun asClassTransform(): ClassTransform = ClassTransform { clb, cle -> when (cle) { is FieldModel -> @@ -204,42 +209,7 @@ internal class RelocatorRemapper( else ClassDesc.ofDescriptor("L$mappedInternalName;") } - private fun map(name: String, mapLiterals: Boolean = false): String { - // Maybe a list of types. - val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) } - - if (newName != name) { - onModified() - } - return newName - } - - private fun mapNameImpl(name: String, mapLiterals: Boolean): String { - var newName = name - var prefix = "" - var suffix = "" - - val matcher = classPattern.matcher(newName) - if (matcher.matches()) { - prefix = matcher.group(1) + "L" - suffix = "" - newName = matcher.group(2) - } - - for (relocator in relocators) { - if (mapLiterals && relocator.skipStringConstants) { - continue - } else if (relocator.canRelocateClass(newName)) { - return prefix + relocator.relocateClass(newName) + suffix - } else if (relocator.canRelocatePath(newName)) { - return prefix + relocator.relocatePath(newName) + suffix - } - } - - return name - } - - private fun asFieldTransform(): FieldTransform = FieldTransform { fb, fe -> + private fun asFieldTransform() = FieldTransform { fb, fe -> when (fe) { is ConstantValueAttribute -> { val constant = fe.constant() @@ -263,7 +233,7 @@ internal class RelocatorRemapper( } } - private fun asMethodTransform(): MethodTransform = MethodTransform { mb, me -> + private fun asMethodTransform() = MethodTransform { mb, me -> when (me) { is AnnotationDefaultAttribute -> mb.with(AnnotationDefaultAttribute.of(mapAnnotationValue(me.defaultValue()))) @@ -296,7 +266,7 @@ internal class RelocatorRemapper( } } - private fun asCodeTransform(): CodeTransform = CodeTransform { cob, coe -> + private fun asCodeTransform() = CodeTransform { cob, coe -> when (coe) { is FieldInstruction -> cob.fieldAccess( @@ -545,58 +515,6 @@ internal class RelocatorRemapper( ) } - private companion object { - /** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */ - val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") - } -} - -@Suppress("unused", "DuplicatedCode") // Used by Multi-Release JARs for Java 24+. -internal fun FileCopyDetails.remapClass( - relocators: Set, - zipOutStr: ZipOutputStream, - preserveFileTimestamps: Boolean, - lastModified: Long, - logger: Logger, -) { - file.readBytes().let { bytes -> - var modified = false - val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() - val remapper = RelocatorRemapper(relocators) { modified = true } - - val newBytes = - try { - val classFile = ClassFile.of() - val classModel = classFile.parse(bytes) - val originalClassDesc = classModel.thisClass().asSymbol() - val newClassDesc = remapper.mapClassDesc(originalClassDesc) - classFile.transformClass(classModel, newClassDesc, remapper.asClassTransform()) - } catch (t: Throwable) { - throw GradleException("Error in Class-File API processing class $path", t) - } - - val finalBytes = - if (modified) { - newBytes - } else { - // If we didn't need to change anything, keep the original bytes as-is - bytes - } - - // Multi-release prefix was calculated earlier. - val newPath = path.replace(multiReleasePrefix, "") - val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) - try { - val entry = - zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { - unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() - } - // Now we put it back on so the class file is written out with the right extension. - zipOutStr.putNextEntry(entry) - zipOutStr.write(finalBytes) - zipOutStr.closeEntry() - } catch (_: ZipException) { - logger.warn("We have a duplicate $relocatedPath in source project") - } - } + private fun map(name: String, mapLiterals: Boolean = false): String = + relocators.mapName(name = name, mapLiterals = mapLiterals, onModified = onModified) } From bd620cdca33f4e68c2a24f50d7730969d9cad564 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 18:55:26 +0800 Subject: [PATCH 4/5] Call remap extensions --- .../shadow/internal/RelocatorRemapper.kt | 222 ++++++++---------- 1 file changed, 103 insertions(+), 119 deletions(-) diff --git a/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index da3405533..43d25ab2d 100644 --- a/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/java24/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -100,7 +100,7 @@ private class RelocatorRemapper( is MethodModel -> clb.withMethod( cle.methodName().stringValue(), - mapMethodDesc(cle.methodTypeSymbol()), + cle.methodTypeSymbol().remap(), cle.flags().flagsMask(), ) { mb -> mb.transform(cle, asMethodTransform()) @@ -108,8 +108,7 @@ private class RelocatorRemapper( is Superclass -> clb.withSuperclass(mapClassDesc(cle.superclassEntry().asSymbol())) is Interfaces -> clb.withInterfaceSymbols(cle.interfaces().map { mapClassDesc(it.asSymbol()) }) - is SignatureAttribute -> - clb.with(SignatureAttribute.of(mapClassSignature(cle.asClassSignature()))) + is SignatureAttribute -> clb.with(SignatureAttribute.of(cle.asClassSignature().remap())) is InnerClassesAttribute -> clb.with( InnerClassesAttribute.of( @@ -131,11 +130,10 @@ private class RelocatorRemapper( cle .enclosingMethodType() .map { MethodTypeDesc.ofDescriptor(it.stringValue()) } - .map(this::mapMethodDesc), + .map { it.remap() }, ) ) - is RecordAttribute -> - clb.with(RecordAttribute.of(cle.components().map(this::mapRecordComponent))) + is RecordAttribute -> clb.with(RecordAttribute.of(cle.components().map { it.remap() })) is ModuleAttribute -> clb.with( ModuleAttribute.of( @@ -187,13 +185,13 @@ private class RelocatorRemapper( ) ) is RuntimeVisibleAnnotationsAttribute -> - clb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + clb.with(RuntimeVisibleAnnotationsAttribute.of(cle.annotations().map { it.remap() })) is RuntimeInvisibleAnnotationsAttribute -> - clb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(cle.annotations()))) + clb.with(RuntimeInvisibleAnnotationsAttribute.of(cle.annotations().map { it.remap() })) is RuntimeVisibleTypeAnnotationsAttribute -> - clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(cle.annotations().map { it.remap() })) is RuntimeInvisibleTypeAnnotationsAttribute -> - clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(cle.annotations()))) + clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(cle.annotations().map { it.remap() })) else -> clb.with(cle) } } @@ -220,15 +218,15 @@ private class RelocatorRemapper( fb.with(fe) } } - is SignatureAttribute -> fb.with(SignatureAttribute.of(mapSignature(fe.asTypeSignature()))) + is SignatureAttribute -> fb.with(SignatureAttribute.of(fe.asTypeSignature().remap())) is RuntimeVisibleAnnotationsAttribute -> - fb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + fb.with(RuntimeVisibleAnnotationsAttribute.of(fe.annotations().map { it.remap() })) is RuntimeInvisibleAnnotationsAttribute -> - fb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(fe.annotations()))) + fb.with(RuntimeInvisibleAnnotationsAttribute.of(fe.annotations().map { it.remap() })) is RuntimeVisibleTypeAnnotationsAttribute -> - fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(fe.annotations().map { it.remap() })) is RuntimeInvisibleTypeAnnotationsAttribute -> - fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(fe.annotations()))) + fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(fe.annotations().map { it.remap() })) else -> fb.with(fe) } } @@ -236,32 +234,31 @@ private class RelocatorRemapper( private fun asMethodTransform() = MethodTransform { mb, me -> when (me) { is AnnotationDefaultAttribute -> - mb.with(AnnotationDefaultAttribute.of(mapAnnotationValue(me.defaultValue()))) + mb.with(AnnotationDefaultAttribute.of(me.defaultValue().remap())) is CodeModel -> mb.transformCode(me, asCodeTransform()) is ExceptionsAttribute -> mb.with(ExceptionsAttribute.ofSymbols(me.exceptions().map { mapClassDesc(it.asSymbol()) })) - is SignatureAttribute -> - mb.with(SignatureAttribute.of(mapMethodSignature(me.asMethodSignature()))) + is SignatureAttribute -> mb.with(SignatureAttribute.of(me.asMethodSignature().remap())) is RuntimeVisibleAnnotationsAttribute -> - mb.with(RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + mb.with(RuntimeVisibleAnnotationsAttribute.of(me.annotations().map { it.remap() })) is RuntimeInvisibleAnnotationsAttribute -> - mb.with(RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(me.annotations()))) + mb.with(RuntimeInvisibleAnnotationsAttribute.of(me.annotations().map { it.remap() })) is RuntimeVisibleParameterAnnotationsAttribute -> mb.with( RuntimeVisibleParameterAnnotationsAttribute.of( - me.parameterAnnotations().map(this::mapAnnotations) + me.parameterAnnotations().map { pas -> pas.map { it.remap() } } ) ) is RuntimeInvisibleParameterAnnotationsAttribute -> mb.with( RuntimeInvisibleParameterAnnotationsAttribute.of( - me.parameterAnnotations().map(this::mapAnnotations) + me.parameterAnnotations().map { pas -> pas.map { it.remap() } } ) ) is RuntimeVisibleTypeAnnotationsAttribute -> - mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(me.annotations().map { it.remap() })) is RuntimeInvisibleTypeAnnotationsAttribute -> - mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(me.annotations()))) + mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(me.annotations().map { it.remap() })) else -> mb.with(me) } } @@ -280,16 +277,16 @@ private class RelocatorRemapper( coe.opcode(), mapClassDesc(coe.owner().asSymbol()), coe.name().stringValue(), - mapMethodDesc(coe.typeSymbol()), + coe.typeSymbol().remap(), coe.isInterface, ) is InvokeDynamicInstruction -> cob.invokedynamic( DynamicCallSiteDesc.of( - mapDirectMethodHandle(coe.bootstrapMethod()), + coe.bootstrapMethod().remap(), coe.name().stringValue(), - mapMethodDesc(coe.typeSymbol()), - *coe.bootstrapArgs().map(this::mapConstantValue).toTypedArray(), + coe.typeSymbol().remap(), + *coe.bootstrapArgs().map { it.remap() }.toTypedArray(), ) ) is NewObjectInstruction -> cob.new_(mapClassDesc(coe.className().asSymbol())) @@ -317,7 +314,7 @@ private class RelocatorRemapper( cob.localVariableType( coe.slot(), coe.name().stringValue(), - mapSignature(coe.signatureSymbol()), + coe.signatureSymbol().remap(), coe.startScope(), coe.endScope(), ) @@ -334,186 +331,173 @@ private class RelocatorRemapper( "java.lang.Float" -> cob.ldc(cob.constantPool().floatEntry(value as Float)) "java.lang.Long" -> cob.ldc(cob.constantPool().longEntry(value as Long)) "java.lang.Double" -> cob.ldc(cob.constantPool().doubleEntry(value as Double)) - else -> cob.ldc(mapConstantValue(value as ConstantDesc)) + else -> cob.ldc((value as ConstantDesc).remap()) } } is RuntimeVisibleTypeAnnotationsAttribute -> - cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(coe.annotations().map { it.remap() })) is RuntimeInvisibleTypeAnnotationsAttribute -> - cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(coe.annotations()))) + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(coe.annotations().map { it.remap() })) else -> cob.with(coe) } } - private fun mapMethodDesc(desc: MethodTypeDesc): MethodTypeDesc { + private fun MethodTypeDesc.remap(): MethodTypeDesc { return MethodTypeDesc.of( - mapClassDesc(desc.returnType()), - *desc.parameterList().map { mapClassDesc(it) }.toTypedArray(), + mapClassDesc(returnType()), + *parameterList().map { mapClassDesc(it) }.toTypedArray(), ) } - private fun mapClassSignature(signature: ClassSignature): ClassSignature { - val superclassSignature = signature.superclassSignature()?.let { mapSignature(it) } + private fun ClassSignature.remap(): ClassSignature { + val superclassSignature = superclassSignature()?.remap() return ClassSignature.of( - mapTypeParams(signature.typeParameters()), + typeParameters().map { it.remap() }, superclassSignature, - *signature.superinterfaceSignatures().map { mapSignature(it) }.toTypedArray(), + *superinterfaceSignatures().map { it.remap() }.toTypedArray(), ) } - private fun mapMethodSignature(signature: MethodSignature): MethodSignature { + private fun MethodSignature.remap(): MethodSignature { return MethodSignature.of( - mapTypeParams(signature.typeParameters()), - signature.throwableSignatures().map { mapSignature(it) }, - mapSignature(signature.result()), - *signature.arguments().map { mapSignature(it) }.toTypedArray(), + typeParameters().map { it.remap() }, + throwableSignatures().map { it.remap() }, + result().remap(), + *arguments().map { it.remap() }.toTypedArray(), ) } - private fun mapRecordComponent(component: RecordComponentInfo): RecordComponentInfo { + private fun RecordComponentInfo.remap(): RecordComponentInfo { return RecordComponentInfo.of( - component.name().stringValue(), - mapClassDesc(component.descriptorSymbol()), - component.attributes().map { atr -> + name().stringValue(), + mapClassDesc(descriptorSymbol()), + attributes().map { atr -> when (atr) { - is SignatureAttribute -> SignatureAttribute.of(mapSignature(atr.asTypeSignature())) + is SignatureAttribute -> SignatureAttribute.of(atr.asTypeSignature().remap()) is RuntimeVisibleAnnotationsAttribute -> - RuntimeVisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + RuntimeVisibleAnnotationsAttribute.of(atr.annotations().map { it.remap() }) is RuntimeInvisibleAnnotationsAttribute -> - RuntimeInvisibleAnnotationsAttribute.of(mapAnnotations(atr.annotations())) + RuntimeInvisibleAnnotationsAttribute.of(atr.annotations().map { it.remap() }) is RuntimeVisibleTypeAnnotationsAttribute -> - RuntimeVisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + RuntimeVisibleTypeAnnotationsAttribute.of(atr.annotations().map { it.remap() }) is RuntimeInvisibleTypeAnnotationsAttribute -> - RuntimeInvisibleTypeAnnotationsAttribute.of(mapTypeAnnotations(atr.annotations())) + RuntimeInvisibleTypeAnnotationsAttribute.of(atr.annotations().map { it.remap() }) else -> atr } }, ) } - private fun mapDirectMethodHandle(dmhd: DirectMethodHandleDesc): DirectMethodHandleDesc { - return when (dmhd.kind()) { + private fun DirectMethodHandleDesc.remap(): DirectMethodHandleDesc { + return when (kind()) { DirectMethodHandleDesc.Kind.GETTER, DirectMethodHandleDesc.Kind.SETTER, DirectMethodHandleDesc.Kind.STATIC_GETTER, DirectMethodHandleDesc.Kind.STATIC_SETTER -> MethodHandleDesc.ofField( - dmhd.kind(), - mapClassDesc(dmhd.owner()), - dmhd.methodName(), - mapClassDesc(ClassDesc.ofDescriptor(dmhd.lookupDescriptor())), + kind(), + mapClassDesc(owner()), + methodName(), + mapClassDesc(ClassDesc.ofDescriptor(lookupDescriptor())), ) else -> MethodHandleDesc.ofMethod( - dmhd.kind(), - mapClassDesc(dmhd.owner()), - dmhd.methodName(), - mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor())), + kind(), + mapClassDesc(owner()), + methodName(), + MethodTypeDesc.ofDescriptor(lookupDescriptor()).remap(), ) } } - private fun mapConstantValue(value: ConstantDesc): ConstantDesc { - return when (value) { - is ClassDesc -> mapClassDesc(value) - is DynamicConstantDesc<*> -> mapDynamicConstant(value) - is DirectMethodHandleDesc -> mapDirectMethodHandle(value) - is MethodTypeDesc -> mapMethodDesc(value) + private fun ConstantDesc.remap(): ConstantDesc { + return when (this) { + is ClassDesc -> mapClassDesc(this) + is DynamicConstantDesc<*> -> remap() + is DirectMethodHandleDesc -> remap() + is MethodTypeDesc -> remap() else -> { @Suppress("CAST_NEVER_SUCCEEDS") - if (value.javaClass.name == "java.lang.String") { - map(value.toString(), mapLiterals = true) as ConstantDesc + if (javaClass.name == "java.lang.String") { + map(toString(), mapLiterals = true) as ConstantDesc } else { - value + this } } } } - private fun mapDynamicConstant(dcd: DynamicConstantDesc<*>): DynamicConstantDesc<*> { + private fun DynamicConstantDesc<*>.remap(): DynamicConstantDesc<*> { return DynamicConstantDesc.ofNamed( - mapDirectMethodHandle(dcd.bootstrapMethod()), - dcd.constantName(), - mapClassDesc(dcd.constantType()), - *dcd.bootstrapArgsList().map(this::mapConstantValue).toTypedArray(), + bootstrapMethod().remap(), + constantName(), + mapClassDesc(constantType()), + *bootstrapArgsList().map { it.remap() }.toTypedArray(), ) } @Suppress("UNCHECKED_CAST") - private fun mapSignature(signature: S): S { - return when (signature) { - is Signature.ArrayTypeSig -> - Signature.ArrayTypeSig.of(mapSignature(signature.componentSignature())) as S + private fun S.remap(): S { + return when (this) { + is Signature.ArrayTypeSig -> Signature.ArrayTypeSig.of(componentSignature().remap()) as S is Signature.ClassTypeSig -> { - val mappedOuter = signature.outerType().getOrNull()?.let { mapSignature(it) } - val mappedClass = mapClassDesc(signature.classDesc()) + val mappedOuter = outerType().getOrNull()?.remap() + val mappedClass = mapClassDesc(classDesc()) // Extract internal name simply bypassing util val internalName = mappedClass.descriptorString().let { it.substring(1, it.length - 1) } Signature.ClassTypeSig.of( mappedOuter, internalName, - *signature - .typeArgs() + *typeArgs() .map { ta -> when (ta) { is Signature.TypeArg.Unbounded -> ta is Signature.TypeArg.Bounded -> - Signature.TypeArg.bounded(ta.wildcardIndicator(), mapSignature(ta.boundType())) + Signature.TypeArg.bounded(ta.wildcardIndicator(), ta.boundType().remap()) } } .toTypedArray(), ) as S } - else -> signature + else -> this } } - private fun mapAnnotations(annotations: List) = annotations.map(this::mapAnnotation) - - private fun mapAnnotation(a: Annotation) = + private fun Annotation.remap() = Annotation.of( - mapClassDesc(a.classSymbol()), - a.elements().map { el -> AnnotationElement.of(el.name(), mapAnnotationValue(el.value())) }, + mapClassDesc(classSymbol()), + elements().map { el -> AnnotationElement.of(el.name(), el.value().remap()) }, ) - private fun mapAnnotationValue(valObj: AnnotationValue): AnnotationValue { - return when (valObj) { - is AnnotationValue.OfAnnotation -> - AnnotationValue.ofAnnotation(mapAnnotation(valObj.annotation())) - is AnnotationValue.OfArray -> - AnnotationValue.ofArray(valObj.values().map(this::mapAnnotationValue)) + private fun AnnotationValue.remap(): AnnotationValue { + return when (this) { + is AnnotationValue.OfAnnotation -> AnnotationValue.ofAnnotation(annotation().remap()) + is AnnotationValue.OfArray -> AnnotationValue.ofArray(values().map { it.remap() }) is AnnotationValue.OfConstant -> { - if (valObj is AnnotationValue.OfString) { - val str = valObj.stringValue() + if (this is AnnotationValue.OfString) { + val str = stringValue() // mapLiterals=true enables the skipStringConstants check in each relocator. val mapped = map(str, mapLiterals = true) - if (mapped != str) AnnotationValue.ofString(mapped) else valObj + if (mapped != str) AnnotationValue.ofString(mapped) else this } else { - valObj + this } } - is AnnotationValue.OfClass -> AnnotationValue.ofClass(mapClassDesc(valObj.classSymbol())) + is AnnotationValue.OfClass -> AnnotationValue.ofClass(mapClassDesc(classSymbol())) is AnnotationValue.OfEnum -> - AnnotationValue.ofEnum( - mapClassDesc(valObj.classSymbol()), - valObj.constantName().stringValue(), - ) + AnnotationValue.ofEnum(mapClassDesc(classSymbol()), constantName().stringValue()) } } - private fun mapTypeAnnotations(typeAnnotations: List) = - typeAnnotations.map { a -> - TypeAnnotation.of(a.targetInfo(), a.targetPath(), mapAnnotation(a.annotation())) - } + private fun TypeAnnotation.remap() = + TypeAnnotation.of(targetInfo(), targetPath(), annotation().remap()) - private fun mapTypeParams(typeParams: List) = - typeParams.map { tp -> - Signature.TypeParam.of( - tp.identifier(), - tp.classBound().getOrNull()?.let { mapSignature(it) }, - *tp.interfaceBounds().map { mapSignature(it) }.toTypedArray(), - ) - } + private fun Signature.TypeParam.remap() = + Signature.TypeParam.of( + identifier(), + classBound().getOrNull()?.remap(), + *interfaceBounds().map { it.remap() }.toTypedArray(), + ) private fun map(name: String, mapLiterals: Boolean = false): String = relocators.mapName(name = name, mapLiterals = mapLiterals, onModified = onModified) From c92385ce057c94d152c5412440181d0b96149688 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Thu, 12 Mar 2026 19:07:34 +0800 Subject: [PATCH 5/5] Update gradle.properties Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 68b93d05d..ecaad9c0a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.stdlib.default.dependency=false # Keep associated Kotlin compilations from depending on archive tasks (e.g., jar), which can create circular task graphs in multi-release setups. -# https://kotlinlang.org/docs/gradle-configure-project.html//disable-use-of-artifact-in-compilation-task +# https://kotlinlang.org/docs/gradle-configure-project.html#disable-use-of-artifact-in-compilation-task # https://kotlinlang.org/docs/whatsnew2020.html#added-task-dependency-for-rare-cases-when-the-compile-task-lacks-one-on-an-artifact kotlin.build.archivesTaskOutputAsFriendModule=false