diff --git a/build.gradle b/build.gradle index f9a75eb9..42e9d89c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,9 @@ dependencies { implementation "net.fabricmc:mapping-io:0.7.1" testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' + testCompileOnly ('net.fabricmc:sponge-mixin:0.16.2+mixin.0.8.7') { + transitive = false + } } java { diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/CommonInjectionAnnotationVisitor.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/CommonInjectionAnnotationVisitor.java index 40d5ddad..d6d6e0f1 100644 --- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/CommonInjectionAnnotationVisitor.java +++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/CommonInjectionAnnotationVisitor.java @@ -165,6 +165,14 @@ private Optional resolvePartial(TrClass owner, String name, String des @Override public MemberInfo result() { + // Special case to remap the desc of wildcards without a name, such as `*()Lcom/example/ClassName;` + if (info.getOwner().isEmpty() + && info.getName().isEmpty() + && info.getQuantifier().equals("*") + && !info.getDesc().isEmpty()) { + return new MemberInfo(info.getOwner(), info.getName(), info.getQuantifier(), data.mapper.asTrRemapper().mapDesc(info.getDesc())); + } + if (targets.isEmpty() || info.getName().isEmpty()) { return info; } diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfo.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfo.java index 992e1f25..7f7f2058 100644 --- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfo.java +++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfo.java @@ -144,11 +144,6 @@ private String formattedDesc() { return ":" + desc; } - // Wildcards match regardless of descriptor - if (getQuantifier().equals("*")) { - return ""; - } - return desc; } } diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java new file mode 100644 index 00000000..fa2f8565 --- /dev/null +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2025, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.extension.mixin.integration; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceClassVisitor; + +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.extension.mixin.MixinExtension; +import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.TargetMixin; +import net.fabricmc.tinyremapper.extension.mixin.integration.targets.Target; + +public class MixinIntegrationTest { + @TempDir + static Path folder; + + @Test + public void remapWildcardName() throws IOException { + Path classpath = createJar(Target.class); + Path input = createJar(TargetMixin.class); + Path output = folder.resolve("output.jar"); + + TinyRemapper tinyRemapper = TinyRemapper.newRemapper() + .extension(new MixinExtension()) + .withMappings(out -> out.acceptClass("java/lang/String", "com/example/NotString")) + .build(); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { + tinyRemapper.readClassPath(classpath); + tinyRemapper.readInputs(input); + + tinyRemapper.apply(outputConsumer); + } + + String remapped = textify(output, TargetMixin.class); + // Check constructor inject did not gain a desc + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"*\"}")); + // Check that wildcard desc is remapped without a name + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"*()Lcom/example/NotString;\"}")); + } + + // Create a zip file in the temp dir containing only the passed class file. + private Path createJar(Class clazz) throws IOException { + String classFileName = clazz.getName().replace('.', '/') + ".class"; + Path jarFile = folder.resolve(clazz.getSimpleName() + ".jar"); + + try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarFile))) { + jarOut.putNextEntry(new JarEntry(classFileName)); + + try (InputStream classIn = clazz.getResourceAsStream('/' + classFileName)) { + byte[] buffer = new byte[8192]; + int bytesRead; + + while ((bytesRead = classIn.read(buffer)) != -1) { + jarOut.write(buffer, 0, bytesRead); + } + } + + jarOut.closeEntry(); + } + + return jarFile; + } + + public static String textify(Path zipPath, Class clazz) throws IOException { + String classFileName = clazz.getName().replace('.', '/') + ".class"; + + try (ZipFile zipFile = new ZipFile(zipPath.toFile())) { + ZipEntry entry = zipFile.getEntry(classFileName); + + try (InputStream inputStream = zipFile.getInputStream(entry)) { + ClassReader classReader = new ClassReader(inputStream); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, new Textifier(), printWriter); + + classReader.accept(traceClassVisitor, 0); + return stringWriter.toString(); + } + } + } +} diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/TargetMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/TargetMixin.java new file mode 100644 index 00000000..e39a0757 --- /dev/null +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/TargetMixin.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2025, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.extension.mixin.integration.mixins; + +import java.lang.annotation.Target; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Target.class) +public abstract class TargetMixin { + @Inject(method = "*", at = @At(value = "RETURN")) + private void constructorHook(final CallbackInfo ci) { + } + + @Inject(method = "*()Ljava/lang/String;", at = @At("HEAD"), cancellable = true) + private void injectName(CallbackInfoReturnable ci) { + } +} diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/Target.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/Target.java new file mode 100644 index 00000000..6bf6ecef --- /dev/null +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/Target.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2025, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.extension.mixin.integration.targets; + +public class Target { + public Target(String name) { + } + + public String getName() { + return ""; + } + + public String getRealName() { + return ""; + } +} diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfoTest.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfoTest.java index 18f97a56..653cc2a8 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfoTest.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/soft/data/MemberInfoTest.java @@ -115,6 +115,25 @@ void parse() { assertEquals(info.getName(), ""); assertEquals(info.getQuantifier(), "*"); assertEquals(info.getDesc(), "()V"); + assertEquals(info.toString(), "*()V"); + + // https://github.com/FabricMC/tiny-remapper/issues/137 + info = MemberInfo.parse("*"); + assertNotNull(info); + assertNull(info.getType()); + assertEquals(info.getOwner(), ""); + assertEquals(info.getName(), ""); + assertEquals(info.getQuantifier(), "*"); + assertEquals(info.getDesc(), ""); assertEquals(info.toString(), "*"); + + info = MemberInfo.parse("*()Lcom/example/ExampleClass;"); + assertNotNull(info); + assertEquals(info.getType(), MemberType.METHOD); + assertEquals(info.getOwner(), ""); + assertEquals(info.getName(), ""); + assertEquals(info.getQuantifier(), "*"); + assertEquals(info.getDesc(), "()Lcom/example/ExampleClass;"); + assertEquals(info.toString(), "*()Lcom/example/ExampleClass;"); } }