From 86d3fb9a0ffa01f7e6484a9b6cdb148597ed05fb Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 22:12:53 +0800 Subject: [PATCH 01/15] Mixin method remap: fix desc being dropped & support wildcards --- .../CommonInjectionAnnotationVisitor.java | 173 ++++++++++++++---- .../integration/MixinIntegrationTest.java | 41 ++++- .../mixins/AmbiguousRemappedNameMixin.java | 4 + .../mixins/SeparateRemappedNameMixin.java | 41 +++++ .../mixins/WildcardTargetMixin.java | 4 + .../targets/SeparateRemappedNameTarget.java | 26 +++ .../integration/targets/WildcardTarget.java | 15 ++ 7 files changed, 270 insertions(+), 34 deletions(-) create mode 100644 src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java create mode 100644 src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.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 06cd1a05..cd003de2 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 @@ -18,10 +18,17 @@ package net.fabricmc.tinyremapper.extension.mixin.soft.annotation.injection; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; import org.objectweb.asm.AnnotationVisitor; @@ -85,11 +92,23 @@ public AnnotationVisitor visitArray(String name) { return new AnnotationVisitor(Constant.ASM_VERSION, av) { @Override public void visit(String name, Object value) { - Optional info = Optional.ofNullable(MemberInfo.parse(Objects.requireNonNull((String) value).replaceAll("\\s", ""))); + String string = Objects.requireNonNull((String) value); - value = info.map(i -> new InjectMethodMappable(data, i, targets).result().toString()).orElse((String) value); + MemberInfo info = MemberInfo.parse(string.replaceAll("\\s", "")); - super.visit(name, value); + if (info == null) { + super.visit(name, value); + return; + } + + MemberInfo[] resolved = new InjectMethodMappable(data, info, targets).result(); + if (resolved.length == 0) { + throw new RuntimeException("InjectMethodMappable should never resolve to zero entries"); + } + + for (MemberInfo memberInfos : resolved) { + super.visit(name, memberInfos.toString()); + } } }; } else if (name.equals(AnnotationElement.TARGET)) { // All @@ -133,7 +152,7 @@ public AnnotationVisitor visitAnnotation(String name, String descriptor) { return av; } - private static class InjectMethodMappable implements IMappable { + private static class InjectMethodMappable implements IMappable { private final CommonData data; private final MemberInfo info; private final List targets; @@ -161,52 +180,144 @@ private Optional resolvePartial(TrClass owner, String name, String des name = name.isEmpty() ? null : name; desc = desc.isEmpty() ? null : desc; - return data.resolver.resolveMethod(owner, name, desc, ResolveUtility.FLAG_FIRST | ResolveUtility.FLAG_NON_SYN).map(m -> m); + return data.resolver.resolveMethod(owner, name, desc, ResolveUtility.FLAG_FIRST).map(m -> m); } - @Override - public MemberInfo result() { + private Collection resolvePartials(TrClass owner, String name, String desc) { + Objects.requireNonNull(owner); + + name = name.isEmpty() ? null : name; + desc = desc.isEmpty() ? null : desc; + + return owner.resolveMethods(name, desc, false, null, null); + } + + private MemberInfo[] wildcardResult() { // 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 (info.getName().isEmpty() && !info.getDesc().isEmpty()) { + return new MemberInfo[] { + new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), "*", data.mapper.asTrRemapper().mapDesc(info.getDesc())) + }; } if (targets.isEmpty() || info.getName().isEmpty()) { - return info; + return new MemberInfo[] { info }; } List> collection = targets.stream() - .map(target -> resolvePartial(target, info.getName(), info.getDesc())) - .filter(Optional::isPresent) - .map(Optional::get) - .map(m -> { - String mappedName = data.mapper.mapName(m); - boolean shouldPassDesc = false; - - for (TrMethod other : m.getOwner().getMethods()) { // look for ambiguous targets - if (other == m) continue; - - if (data.mapper.mapName(other).equals(mappedName)) { - shouldPassDesc = true; + .flatMap(target -> resolvePartials(target, info.getName(), info.getDesc()).stream()) + .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) + .distinct() + .collect(Collectors.toList()); + + if (collection.isEmpty()) { + data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.getName(), targets); + return new MemberInfo[] { info }; + } + + Map> descriptorsForName = new TreeMap<>(); + for (Pair pair : collection) { + descriptorsForName.computeIfAbsent(pair.first(), k -> new TreeSet<>()).add(pair.second()); + } + + List finalMembers = new ArrayList<>(); + + if (info.getDesc().isEmpty()) { + // If the descriptor was omitted in the input, we want to omit the descriptor in the output as well + // However, we can only do this if all the methods in the source namespace + // are exactly matched in the target namespace + + for (Map.Entry> entry : descriptorsForName.entrySet()) { + String mappedName = entry.getKey(); + Set mappedDescriptors = entry.getValue(); + + Set allDescriptorsInTargets = new HashSet<>(); + + for (TrClass target : targets) { + for (TrMethod method : target.getMethods()) { + String otherName = data.mapper.mapName(method); + if (otherName.equals(mappedName)) { + allDescriptorsInTargets.add(data.mapper.mapDesc(method)); } } + } - return Pair.of(mappedName, shouldPassDesc ? data.mapper.mapDesc(m) : ""); - }) - .distinct().collect(Collectors.toList()); + if (allDescriptorsInTargets.equals(mappedDescriptors)) { + finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", "")); + } else { + for (String mappedDesc : mappedDescriptors) { + finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", mappedDesc)); + } + } + } + } else { + for (Map.Entry> entry : descriptorsForName.entrySet()) { + String mappedName = entry.getKey(); + Set mappedDescriptors = entry.getValue(); + + for (String mappedDesc : mappedDescriptors) { + finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", mappedDesc)); + } + } + } + + return finalMembers.toArray(new MemberInfo[0]); + } + + private MemberInfo singleResult() { + if (targets.isEmpty() || info.getName().isEmpty()) { + return info; + } + + List> collection = targets.stream() + .map(target -> resolvePartial(target, info.getName(), info.getDesc())) + .filter(Optional::isPresent) + .map(Optional::get) + .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) + .distinct() + .collect(Collectors.toList()); if (collection.size() > 1) { data.getLogger().error(Message.CONFLICT_MAPPING, info.getName(), collection); } else if (collection.isEmpty()) { data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.getName(), targets); + return info; + } + + Pair pair = collection.get(0); + String mappedName = pair.first(); + + boolean useDescriptor = !info.getDesc().isEmpty() || isNameAmbiguous(mappedName, pair.second()); + String desc = useDescriptor ? pair.second() : ""; + + return new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, info.getQuantifier(), desc); + } + + private boolean isNameAmbiguous(String mappedName, String mappedDesc) { + // Try to find a method with the same name, but a different descriptor + + for (TrClass target : targets) { + for (TrMethod method : target.getMethods()) { + String otherName = data.mapper.mapName(method); + if (otherName.equals(mappedName)) { // Same name + String otherDesc = data.mapper.mapDesc(method); + if (!otherDesc.equals(mappedDesc)) { // Different descriptor + return true; + } + } + } } - return collection.stream().findFirst() - .map(pair -> new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), pair.first(), info.getQuantifier(), info.getQuantifier().equals("*") ? "" : pair.second())) - .orElse(info); + return false; + } + + @Override + public MemberInfo[] result() { + if (info.getQuantifier().equals("*")) { + return this.wildcardResult(); + } else { + return new MemberInfo[] { singleResult() }; + } } } } 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 index 12ab1787..fb1bf78d 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java @@ -45,11 +45,13 @@ import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.DescAtMixin; import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.LvtRemapTargetMixin; import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.NonObfuscatedOverrideMixin; +import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.SeparateRemappedNameMixin; import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.WildcardTargetMixin; import net.fabricmc.tinyremapper.extension.mixin.integration.targets.AmbiguousRemappedNameTarget; import net.fabricmc.tinyremapper.extension.mixin.integration.targets.DescAtTarget; import net.fabricmc.tinyremapper.extension.mixin.integration.targets.LvtRemapTarget; import net.fabricmc.tinyremapper.extension.mixin.integration.targets.NonObfuscatedOverrideTarget; +import net.fabricmc.tinyremapper.extension.mixin.integration.targets.SeparateRemappedNameTarget; import net.fabricmc.tinyremapper.extension.mixin.integration.targets.WildcardTarget; public class MixinIntegrationTest { @@ -58,13 +60,23 @@ public class MixinIntegrationTest { @Test public void remapWildcardName() throws IOException { - String remapped = remap(WildcardTarget.class, WildcardTargetMixin.class, out -> - out.acceptClass("java/lang/String", "com/example/NotString")); + String remapped = remap(WildcardTarget.class, WildcardTargetMixin.class, out -> { + String fqn = "net/fabricmc/tinyremapper/extension/mixin/integration/targets/WildcardTarget"; + out.acceptClass("java/lang/String", "com/example/NotString"); + out.acceptMethod(new IMappingProvider.Member(fqn, "targetA", "(Ljava/lang/Object;)V"), "sameName"); + out.acceptMethod(new IMappingProvider.Member(fqn, "targetA", "()Ljava/lang/String;"), "sameName"); + out.acceptMethod(new IMappingProvider.Member(fqn, "targetB", "()Ljava/lang/Object;"), "sameName"); + }); // 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 + // *()Ljava/lang/String; -> *()Lcom/example/NotString; assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"*()Lcom/example/NotString;\"}")); + // Check that wildcards are expanded with descriptor to avoid incorrect targets (targetB) + // targetA* -> {"sameName*()Lcom/example/NotString;", "sameName*(Ljava/lang/Object;)V"} + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"sameName*()Lcom/example/NotString;\", \"sameName*(Ljava/lang/Object;)V\"}")); } @Test @@ -83,7 +95,7 @@ public void remapInvokeNonObfuscatedOverride() throws IOException { } @Test - public void remapAmbiuousRemappedName() throws IOException { + public void remapAmbiguousRemappedName() throws IOException { String remapped = remap(AmbiguousRemappedNameTarget.class, AmbiguousRemappedNameMixin.class, out -> { String fqn = "net/fabricmc/tinyremapper/extension/mixin/integration/targets/AmbiguousRemappedNameTarget"; out.acceptClass(fqn, "com/example/Remapped"); @@ -92,7 +104,30 @@ public void remapAmbiuousRemappedName() throws IOException { }); // full signature is used to disambiguate names + // addString -> add(Ljava/lang/String;)V assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add(Ljava/lang/String;)V\"")); + // ensure full signature is used for wildcard as well + // addString* -> add*(Ljava/lang/String;)V + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add*(Ljava/lang/String;)V\"}")); + } + + @Test + public void remapSeparateRemappedName() throws IOException { + String remapped = remap(SeparateRemappedNameTarget.class, SeparateRemappedNameMixin.class, out -> { + String fqn = "net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget"; + out.acceptMethod(new IMappingProvider.Member(fqn, "addString", "(Ljava/lang/String;)V"), "add1"); + out.acceptMethod(new IMappingProvider.Member(fqn, "addString", "(Ljava/lang/String;I)V"), "add2"); + }); + + // Ensure that descriptor isn't added and first method is targeted + // addString -> add1 + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add1\"}")); + // Ensure that descriptor is kept and second method is targeted + // addString(Ljava/lang/String;I)V -> add2(Ljava/lang/String;I)V + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add2(Ljava/lang/String;I)V\"}")); + // Ensure that both methods are targeted by wildcard + // addString* -> {"add1*", "add2*"} + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add1*\", \"add2*\"}")); } @Test diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java index d1482500..aef389b6 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java @@ -30,4 +30,8 @@ public class AmbiguousRemappedNameMixin { @Inject(method = "addString", at = @At("HEAD")) private void injectAddString(String string, CallbackInfo ci) { } + + @Inject(method = "addString*", at = @At("HEAD")) + private void injectAddStringWildcard(String string, CallbackInfo ci) { + } } diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java new file mode 100644 index 00000000..8b799317 --- /dev/null +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2026, 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 net.fabricmc.tinyremapper.extension.mixin.integration.targets.SeparateRemappedNameTarget; + +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; + +@Mixin(SeparateRemappedNameTarget.class) +public class SeparateRemappedNameMixin { + @Inject(method = "addString", at = @At("HEAD")) + private void injectAddStringFirst(String string, CallbackInfo ci) { + } + + @Inject(method = "addString(Ljava/lang/String;I)V", at = @At("HEAD")) + private void injectAddStringSecond(String string, int value, CallbackInfo ci) { + } + + @Inject(method = "addString*", at = @At("HEAD")) + private void injectAddStringBoth(CallbackInfo ci) { + } +} diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/WildcardTargetMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/WildcardTargetMixin.java index d0059ef3..af5f4d98 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/WildcardTargetMixin.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/WildcardTargetMixin.java @@ -35,4 +35,8 @@ private void constructorHook(final CallbackInfo ci) { @Inject(method = "*()Ljava/lang/String;", at = @At("HEAD"), cancellable = true) private void injectName(CallbackInfoReturnable ci) { } + + @Inject(method = "targetA*", at = @At("HEAD")) + private void injectTargetA(CallbackInfo ci) { + } } diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java new file mode 100644 index 00000000..febdbcd9 --- /dev/null +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2026, 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 SeparateRemappedNameTarget { + public void addString(String string) { + } + public void addString(String string, int value) { + } +} diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/WildcardTarget.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/WildcardTarget.java index bf88d47e..450733dd 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/WildcardTarget.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/WildcardTarget.java @@ -22,6 +22,9 @@ public class WildcardTarget { public WildcardTarget(String name) { } + public WildcardTarget(String name, int other) { + } + public String getName() { return ""; } @@ -29,4 +32,16 @@ public String getName() { public String getRealName() { return ""; } + + // Both targetA and targetB will be remapped to 'sameName' + public void targetA(Object o) { + } + + public String targetA() { + return null; + } + + public Object targetB() { + return null; + } } From 8e4e3b781b0f33b72f946264f80b7cdcb91d4814 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:09:07 +0800 Subject: [PATCH 02/15] Move result() above cases for clarity --- .../CommonInjectionAnnotationVisitor.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 cd003de2..896098eb 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 @@ -192,6 +192,15 @@ private Collection resolvePartials(TrClass owner, String name, String return owner.resolveMethods(name, desc, false, null, null); } + @Override + public MemberInfo[] result() { + if (info.getQuantifier().equals("*")) { + return this.wildcardResult(); + } else { + return new MemberInfo[] { singleResult() }; + } + } + private MemberInfo[] wildcardResult() { // Special case to remap the desc of wildcards without a name, such as `*()Lcom/example/ClassName;` if (info.getName().isEmpty() && !info.getDesc().isEmpty()) { @@ -310,14 +319,5 @@ private boolean isNameAmbiguous(String mappedName, String mappedDesc) { return false; } - - @Override - public MemberInfo[] result() { - if (info.getQuantifier().equals("*")) { - return this.wildcardResult(); - } else { - return new MemberInfo[] { singleResult() }; - } - } } } From 2e1a11beec3d6c58f80bd956ad0bba51f7b70276 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:11:03 +0800 Subject: [PATCH 03/15] Don't use wildcard if both name & desc are passed --- .../injection/CommonInjectionAnnotationVisitor.java | 4 ++-- .../extension/mixin/integration/MixinIntegrationTest.java | 7 ++----- .../integration/mixins/AmbiguousRemappedNameMixin.java | 4 ---- 3 files changed, 4 insertions(+), 11 deletions(-) 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 896098eb..4857eba5 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 @@ -255,7 +255,7 @@ private MemberInfo[] wildcardResult() { finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", "")); } else { for (String mappedDesc : mappedDescriptors) { - finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", mappedDesc)); + finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "", mappedDesc)); } } } @@ -265,7 +265,7 @@ private MemberInfo[] wildcardResult() { Set mappedDescriptors = entry.getValue(); for (String mappedDesc : mappedDescriptors) { - finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", mappedDesc)); + finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "", mappedDesc)); } } } 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 index fb1bf78d..915f4ec5 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/MixinIntegrationTest.java @@ -75,8 +75,8 @@ public void remapWildcardName() throws IOException { // *()Ljava/lang/String; -> *()Lcom/example/NotString; assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"*()Lcom/example/NotString;\"}")); // Check that wildcards are expanded with descriptor to avoid incorrect targets (targetB) - // targetA* -> {"sameName*()Lcom/example/NotString;", "sameName*(Ljava/lang/Object;)V"} - assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"sameName*()Lcom/example/NotString;\", \"sameName*(Ljava/lang/Object;)V\"}")); + // targetA* -> {"sameName()Lcom/example/NotString;", "sameName(Ljava/lang/Object;)V"} + assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"sameName()Lcom/example/NotString;\", \"sameName(Ljava/lang/Object;)V\"}")); } @Test @@ -106,9 +106,6 @@ public void remapAmbiguousRemappedName() throws IOException { // full signature is used to disambiguate names // addString -> add(Ljava/lang/String;)V assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add(Ljava/lang/String;)V\"")); - // ensure full signature is used for wildcard as well - // addString* -> add*(Ljava/lang/String;)V - assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add*(Ljava/lang/String;)V\"}")); } @Test diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java index aef389b6..d1482500 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/AmbiguousRemappedNameMixin.java @@ -30,8 +30,4 @@ public class AmbiguousRemappedNameMixin { @Inject(method = "addString", at = @At("HEAD")) private void injectAddString(String string, CallbackInfo ci) { } - - @Inject(method = "addString*", at = @At("HEAD")) - private void injectAddStringWildcard(String string, CallbackInfo ci) { - } } From 53ec0ec7c0f0e6521e5eed97b787f8edf8f0b58d Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:14:16 +0800 Subject: [PATCH 04/15] Use SortedMap/SortedSet types for clarity --- .../injection/CommonInjectionAnnotationVisitor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 4857eba5..0f92fd4b 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 @@ -27,6 +27,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; @@ -224,7 +226,7 @@ private MemberInfo[] wildcardResult() { return new MemberInfo[] { info }; } - Map> descriptorsForName = new TreeMap<>(); + SortedMap> descriptorsForName = new TreeMap<>(); for (Pair pair : collection) { descriptorsForName.computeIfAbsent(pair.first(), k -> new TreeSet<>()).add(pair.second()); } @@ -236,7 +238,7 @@ private MemberInfo[] wildcardResult() { // However, we can only do this if all the methods in the source namespace // are exactly matched in the target namespace - for (Map.Entry> entry : descriptorsForName.entrySet()) { + for (Map.Entry> entry : descriptorsForName.entrySet()) { String mappedName = entry.getKey(); Set mappedDescriptors = entry.getValue(); @@ -260,7 +262,7 @@ private MemberInfo[] wildcardResult() { } } } else { - for (Map.Entry> entry : descriptorsForName.entrySet()) { + for (Map.Entry> entry : descriptorsForName.entrySet()) { String mappedName = entry.getKey(); Set mappedDescriptors = entry.getValue(); From 648fcf53aa952830b14bd665643fd72417d30ccd Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:16:41 +0800 Subject: [PATCH 05/15] Use better name for singular remappedInfo --- .../injection/CommonInjectionAnnotationVisitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0f92fd4b..de036b50 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 @@ -108,8 +108,8 @@ public void visit(String name, Object value) { throw new RuntimeException("InjectMethodMappable should never resolve to zero entries"); } - for (MemberInfo memberInfos : resolved) { - super.visit(name, memberInfos.toString()); + for (MemberInfo remappedInfo : resolved) { + super.visit(name, remappedInfo.toString()); } } }; From 6677d5ad885d1388524653ca9880788ad298c3cd Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:26:08 +0800 Subject: [PATCH 06/15] Add simple case when we can't find a method by name --- .../CommonInjectionAnnotationVisitor.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) 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 de036b50..43124a15 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 @@ -196,6 +196,19 @@ private Collection resolvePartials(TrClass owner, String name, String @Override public MemberInfo[] result() { + if (targets.isEmpty() || info.getName().isEmpty()) { + // Simple case when we can't find the specific method by name + + String desc = info.getDesc(); + if (!desc.isEmpty()) { + desc = data.mapper.asTrRemapper().mapDesc(desc); + } + + return new MemberInfo[] { + new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), info.getQuantifier(), desc) + }; + } + if (info.getQuantifier().equals("*")) { return this.wildcardResult(); } else { @@ -204,17 +217,6 @@ public MemberInfo[] result() { } private MemberInfo[] wildcardResult() { - // Special case to remap the desc of wildcards without a name, such as `*()Lcom/example/ClassName;` - if (info.getName().isEmpty() && !info.getDesc().isEmpty()) { - return new MemberInfo[] { - new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), "*", data.mapper.asTrRemapper().mapDesc(info.getDesc())) - }; - } - - if (targets.isEmpty() || info.getName().isEmpty()) { - return new MemberInfo[] { info }; - } - List> collection = targets.stream() .flatMap(target -> resolvePartials(target, info.getName(), info.getDesc()).stream()) .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) @@ -276,10 +278,6 @@ private MemberInfo[] wildcardResult() { } private MemberInfo singleResult() { - if (targets.isEmpty() || info.getName().isEmpty()) { - return info; - } - List> collection = targets.stream() .map(target -> resolvePartial(target, info.getName(), info.getDesc())) .filter(Optional::isPresent) From e151d280c3c97602aae80a028b65efb80f9c42e3 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Sun, 26 Apr 2026 23:31:28 +0800 Subject: [PATCH 07/15] Use List instead of MemberInfo[] --- .../CommonInjectionAnnotationVisitor.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) 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 43124a15..21a0f259 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 @@ -103,8 +103,8 @@ public void visit(String name, Object value) { return; } - MemberInfo[] resolved = new InjectMethodMappable(data, info, targets).result(); - if (resolved.length == 0) { + List resolved = new InjectMethodMappable(data, info, targets).result(); + if (resolved.isEmpty()) { throw new RuntimeException("InjectMethodMappable should never resolve to zero entries"); } @@ -154,7 +154,7 @@ public AnnotationVisitor visitAnnotation(String name, String descriptor) { return av; } - private static class InjectMethodMappable implements IMappable { + private static class InjectMethodMappable implements IMappable> { private final CommonData data; private final MemberInfo info; private final List targets; @@ -195,7 +195,7 @@ private Collection resolvePartials(TrClass owner, String name, String } @Override - public MemberInfo[] result() { + public List result() { if (targets.isEmpty() || info.getName().isEmpty()) { // Simple case when we can't find the specific method by name @@ -204,19 +204,17 @@ public MemberInfo[] result() { desc = data.mapper.asTrRemapper().mapDesc(desc); } - return new MemberInfo[] { - new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), info.getQuantifier(), desc) - }; + return Collections.singletonList(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), info.getQuantifier(), desc)); } if (info.getQuantifier().equals("*")) { return this.wildcardResult(); } else { - return new MemberInfo[] { singleResult() }; + return Collections.singletonList(singleResult()); } } - private MemberInfo[] wildcardResult() { + private List wildcardResult() { List> collection = targets.stream() .flatMap(target -> resolvePartials(target, info.getName(), info.getDesc()).stream()) .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) @@ -225,7 +223,7 @@ private MemberInfo[] wildcardResult() { if (collection.isEmpty()) { data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.getName(), targets); - return new MemberInfo[] { info }; + return Collections.singletonList(info); } SortedMap> descriptorsForName = new TreeMap<>(); @@ -274,7 +272,7 @@ private MemberInfo[] wildcardResult() { } } - return finalMembers.toArray(new MemberInfo[0]); + return finalMembers; } private MemberInfo singleResult() { From 7610db056559c19fabd99077b1967fe2464835c7 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Mon, 27 Apr 2026 01:45:24 +0800 Subject: [PATCH 08/15] Rewrite --- .../fabricmc/tinyremapper/ClassInstance.java | 3 +- .../extension/mixin/common/data/Message.java | 1 + .../CommonInjectionAnnotationVisitor.java | 214 ++++++++++-------- 3 files changed, 124 insertions(+), 94 deletions(-) diff --git a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java index 24491a11..74cbcf92 100644 --- a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java +++ b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -907,7 +908,7 @@ public static String getMrjName(String clsName, int mrjVersion) { final Path srcPath; byte[] data; private ClassInstance mrjOrigin; - private final Map members = new HashMap<>(); // methods and fields are distinct due to their different desc separators + private final Map members = new LinkedHashMap<>(); // methods and fields are distinct due to their different desc separators private final ConcurrentMap resolvedMembers = new ConcurrentHashMap<>(); final Set parents = new HashSet<>(); final Set children = new HashSet<>(); diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java index a027564a..136a5b66 100644 --- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java +++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java @@ -25,4 +25,5 @@ public final class Message { public static final String NO_MAPPING_NON_RECURSIVE = "Cannot remap %s because it does not exist in any of the targets %s"; public static final String NO_MAPPING_RECURSIVE = "Cannot remap %s because it does not exist in any of the targets %s or their parents."; public static final String NOT_FULLY_QUALIFIED = "%s is not fully qualified."; + public static final String MISSING_INJECT = "Unable to fully remap %s, the method %s%s could not be targeted without conflicts"; } 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 21a0f259..ce932ecc 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 @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -40,7 +41,6 @@ import net.fabricmc.tinyremapper.api.TrMember.MemberType; import net.fabricmc.tinyremapper.api.TrMethod; import net.fabricmc.tinyremapper.extension.mixin.common.IMappable; -import net.fabricmc.tinyremapper.extension.mixin.common.ResolveUtility; import net.fabricmc.tinyremapper.extension.mixin.common.data.Annotation; import net.fabricmc.tinyremapper.extension.mixin.common.data.AnnotationElement; import net.fabricmc.tinyremapper.extension.mixin.common.data.CommonData; @@ -176,27 +176,30 @@ private static class InjectMethodMappable implements IMappable> } } - private Optional resolvePartial(TrClass owner, String name, String desc) { + private List resolvePartials(TrClass owner, String name, String desc) { Objects.requireNonNull(owner); name = name.isEmpty() ? null : name; desc = desc.isEmpty() ? null : desc; - return data.resolver.resolveMethod(owner, name, desc, ResolveUtility.FLAG_FIRST).map(m -> m); - } - - private Collection resolvePartials(TrClass owner, String name, String desc) { - Objects.requireNonNull(owner); - - name = name.isEmpty() ? null : name; - desc = desc.isEmpty() ? null : desc; - - return owner.resolveMethods(name, desc, false, null, null); + Collection col = owner.resolveMethods(name, desc, false, null, null); + if (col instanceof List) { + return (List) col; + } else { + return new ArrayList<>(col); + } } @Override public List result() { - if (targets.isEmpty() || info.getName().isEmpty()) { + String mappedOwner = info.getOwner(); + if (!mappedOwner.isEmpty()) { + mappedOwner = data.mapper.asTrRemapper().map(mappedOwner); + } + + int methodsPerTarget = quantifierStringToMax(info.getQuantifier()); + + if (targets.isEmpty() || info.getName().isEmpty() || methodsPerTarget <= 0) { // Simple case when we can't find the specific method by name String desc = info.getDesc(); @@ -204,118 +207,143 @@ public List result() { desc = data.mapper.asTrRemapper().mapDesc(desc); } - return Collections.singletonList(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), info.getName(), info.getQuantifier(), desc)); + return Collections.singletonList(new MemberInfo(mappedOwner, info.getName(), info.getQuantifier(), desc)); } - if (info.getQuantifier().equals("*")) { - return this.wildcardResult(); - } else { - return Collections.singletonList(singleResult()); - } - } + // Step 1. Collect all methods we want to target - private List wildcardResult() { - List> collection = targets.stream() - .flatMap(target -> resolvePartials(target, info.getName(), info.getDesc()).stream()) - .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) - .distinct() - .collect(Collectors.toList()); + Map, Set> fullMethodToTarget = new HashMap<>(); + SortedMap> namesToDesc = new TreeMap<>(); - if (collection.isEmpty()) { - data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.getName(), targets); - return Collections.singletonList(info); + for (TrClass target : targets) { + List methods = resolvePartials(target, info.getName(), info.getDesc()); + + int matchedCount = Math.min(methods.size(), methodsPerTarget); + for (int i = 0; i < matchedCount; i++) { + TrMember method = methods.get(i); + + String mappedName = data.mapper.mapName(method); + String mappedDesc = data.mapper.mapDesc(method); + + fullMethodToTarget.computeIfAbsent(Pair.of(mappedName, mappedDesc), k -> new HashSet<>()).add(target); + namesToDesc.computeIfAbsent(mappedName, k -> new TreeSet<>()).add(mappedDesc); + } } - SortedMap> descriptorsForName = new TreeMap<>(); - for (Pair pair : collection) { - descriptorsForName.computeIfAbsent(pair.first(), k -> new TreeSet<>()).add(pair.second()); + if (fullMethodToTarget.isEmpty()) { + data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.toString(), targets); + return Collections.singletonList(info); } - List finalMembers = new ArrayList<>(); + // Step 2. Try adding methods + // We need to avoid injecting into methods which weren't injected into in the source namespace + // The canInject() functions check to make sure we aren't targeting something unwanted - if (info.getDesc().isEmpty()) { - // If the descriptor was omitted in the input, we want to omit the descriptor in the output as well - // However, we can only do this if all the methods in the source namespace - // are exactly matched in the target namespace + List list = new ArrayList<>(); - for (Map.Entry> entry : descriptorsForName.entrySet()) { - String mappedName = entry.getKey(); - Set mappedDescriptors = entry.getValue(); + boolean wantDesc = !info.getDesc().isEmpty(); - Set allDescriptorsInTargets = new HashSet<>(); + for (Map.Entry> entry : namesToDesc.entrySet()) { + String mappedName = entry.getKey(); + SortedSet mappedDescriptors = entry.getValue(); - for (TrClass target : targets) { - for (TrMethod method : target.getMethods()) { - String otherName = data.mapper.mapName(method); - if (otherName.equals(mappedName)) { - allDescriptorsInTargets.add(data.mapper.mapDesc(method)); + if (!wantDesc && canInject(mappedName, fullMethodToTarget)) { // Try to apply method name without descriptor if possible + list.add(new MemberInfo(mappedOwner, mappedName, info.getQuantifier(), "")); + } else { + for (String mappedDesc : mappedDescriptors) { + if (canInject(mappedName, mappedDesc, fullMethodToTarget)) { + String quantifier = info.getQuantifier(); + if (quantifier.equals("*") || quantifier.startsWith("{0,")) { + quantifier = ""; } + list.add(new MemberInfo(mappedOwner, mappedName, quantifier, mappedDesc)); + } else { + data.getLogger().error(Message.MISSING_INJECT, info.toString(), mappedName, mappedDesc); } } + } + } - if (allDescriptorsInTargets.equals(mappedDescriptors)) { - finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "*", "")); - } else { - for (String mappedDesc : mappedDescriptors) { - finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "", mappedDesc)); - } + if (list.isEmpty()) { + return Collections.singletonList(info); + } + + return list; + } + + private boolean canInject(String mappedName, Map, Set> fullMethodToTarget) { + for (TrClass target : targets) { + for (TrMethod method : target.getMethods()) { + String otherName = data.mapper.mapName(method); + if (!otherName.equals(mappedName)) { + continue; } - } - } else { - for (Map.Entry> entry : descriptorsForName.entrySet()) { - String mappedName = entry.getKey(); - Set mappedDescriptors = entry.getValue(); - for (String mappedDesc : mappedDescriptors) { - finalMembers.add(new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, "", mappedDesc)); + String otherDesc = data.mapper.mapDesc(method); + Pair pair = Pair.of(otherName, otherDesc); + Set validClasses = fullMethodToTarget.get(pair); + if (validClasses == null || !validClasses.contains(target)) { + return false; } } } - return finalMembers; + return true; } - private MemberInfo singleResult() { - List> collection = targets.stream() - .map(target -> resolvePartial(target, info.getName(), info.getDesc())) - .filter(Optional::isPresent) - .map(Optional::get) - .map(m -> Pair.of(data.mapper.mapName(m), data.mapper.mapDesc(m))) - .distinct() - .collect(Collectors.toList()); - - if (collection.size() > 1) { - data.getLogger().error(Message.CONFLICT_MAPPING, info.getName(), collection); - } else if (collection.isEmpty()) { - data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, info.getName(), targets); - return info; + private boolean canInject(String mappedName, String mappedDesc, Map, Set> fullMethodToTarget) { + Pair pair = Pair.of(mappedName, mappedDesc); + Set validClasses = fullMethodToTarget.get(pair); + if (validClasses == null || validClasses.isEmpty()) { + return false; } - Pair pair = collection.get(0); - String mappedName = pair.first(); + for (TrClass target : targets) { + TrMethod method = target.getMethod(mappedName, mappedDesc); + if (method != null && !validClasses.contains(target)) { + return false; + } + } - boolean useDescriptor = !info.getDesc().isEmpty() || isNameAmbiguous(mappedName, pair.second()); - String desc = useDescriptor ? pair.second() : ""; + return true; + } + } - return new MemberInfo(data.mapper.asTrRemapper().map(info.getOwner()), mappedName, info.getQuantifier(), desc); + // Code based on Mixin Quantifier parsing code + // Copyright (c) SpongePowered + // Copyright (c) contributors + // https://github.com/FabricMC/Mixin/blob/e4edb3afad347f7561acf6a9dd4a64f2aa479658/src/main/java/org/spongepowered/asm/util/Quantifier.java + private static int quantifierStringToMax(String quantifier) { + if (quantifier == null || quantifier.isEmpty()) { + return 1; + } + if (quantifier.equals("*") || quantifier.equals("+")) { + return Integer.MAX_VALUE; + } + if (!quantifier.startsWith("{") || !quantifier.endsWith("}") || quantifier.length() < 3) { + return 0; } - private boolean isNameAmbiguous(String mappedName, String mappedDesc) { - // Try to find a method with the same name, but a different descriptor + String inner = quantifier.substring(1, quantifier.length() - 1).trim(); + if (inner.isEmpty()) { + return 0; + } - for (TrClass target : targets) { - for (TrMethod method : target.getMethods()) { - String otherName = data.mapper.mapName(method); - if (otherName.equals(mappedName)) { // Same name - String otherDesc = data.mapper.mapDesc(method); - if (!otherDesc.equals(mappedDesc)) { // Different descriptor - return true; - } - } - } - } + String strMax = inner; + + int comma = inner.indexOf(','); + if (comma > -1) { + strMax = inner.substring(comma + 1).trim(); + } - return false; + try { + return !strMax.isEmpty() ? Integer.parseInt(strMax) : Integer.MAX_VALUE; + } catch (NumberFormatException ex) { + return 0; } + } + + + } From da55bac49d283ec60b2b78e22f34c4727ac68ffb Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 12:03:58 +0800 Subject: [PATCH 09/15] Account for quantifier max when checking validity of no-descriptor inject --- .../CommonInjectionAnnotationVisitor.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) 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 ce932ecc..6c5e299d 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 @@ -241,19 +241,19 @@ public List result() { List list = new ArrayList<>(); - boolean wantDesc = !info.getDesc().isEmpty(); + boolean explicitDesc = !info.getDesc().isEmpty(); for (Map.Entry> entry : namesToDesc.entrySet()) { String mappedName = entry.getKey(); SortedSet mappedDescriptors = entry.getValue(); - if (!wantDesc && canInject(mappedName, fullMethodToTarget)) { // Try to apply method name without descriptor if possible + if (!explicitDesc && canInject(mappedName, methodsPerTarget, mappedDescriptors, fullMethodToTarget)) { // Try to apply method name without descriptor if possible list.add(new MemberInfo(mappedOwner, mappedName, info.getQuantifier(), "")); } else { for (String mappedDesc : mappedDescriptors) { if (canInject(mappedName, mappedDesc, fullMethodToTarget)) { String quantifier = info.getQuantifier(); - if (quantifier.equals("*") || quantifier.startsWith("{0,")) { + if (!explicitDesc) { quantifier = ""; } list.add(new MemberInfo(mappedOwner, mappedName, quantifier, mappedDesc)); @@ -271,21 +271,44 @@ public List result() { return list; } - private boolean canInject(String mappedName, Map, Set> fullMethodToTarget) { + private boolean canInject(String mappedName, int methodsPerTarget, Set neededDescriptors, Map, Set> fullMethodToTarget) { for (TrClass target : targets) { + int toCheck = methodsPerTarget; + + Set missingDescriptors = new HashSet<>(neededDescriptors); + for (TrMethod method : target.getMethods()) { String otherName = data.mapper.mapName(method); if (!otherName.equals(mappedName)) { continue; } + toCheck -= 1; + if (toCheck <= 0) { + break; + } + String otherDesc = data.mapper.mapDesc(method); + missingDescriptors.remove(otherDesc); + Pair pair = Pair.of(otherName, otherDesc); Set validClasses = fullMethodToTarget.get(pair); if (validClasses == null || !validClasses.contains(target)) { return false; } } + + // This check is needed because we might be mapping a -> b, + // but another method b exists in the target class at an earlier position + // In this case, we can't use b without a descriptor because it'll target + // the earlier method instead of the method we want + for (String missingDesc : missingDescriptors) { + Pair pair = Pair.of(mappedName, missingDesc); + Set validClasses = fullMethodToTarget.get(pair); + if (validClasses != null && validClasses.contains(target)) { + return false; + } + } } return true; From 09d97c330a955e3dd8f43643a933e7d19730d703 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 12:11:48 +0800 Subject: [PATCH 10/15] Print error messages when using unsupported min quantifier & when unable to parse quantifier --- .../extension/mixin/common/data/Message.java | 2 ++ .../CommonInjectionAnnotationVisitor.java | 33 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java index 136a5b66..729abd75 100644 --- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java +++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/Message.java @@ -26,4 +26,6 @@ public final class Message { public static final String NO_MAPPING_RECURSIVE = "Cannot remap %s because it does not exist in any of the targets %s or their parents."; public static final String NOT_FULLY_QUALIFIED = "%s is not fully qualified."; public static final String MISSING_INJECT = "Unable to fully remap %s, the method %s%s could not be targeted without conflicts"; + public static final String UNABLE_TO_PARSE_QUANTIFIER = "Unable to parse quantifier %s, remap behaviour may be incorrect"; + public static final String UNSUPPORTED_QUANTIFIER_MIN = "Quantifier min was provided, but due to conflicts %s after remap had to use descriptor"; } 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 6c5e299d..00dc498f 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 @@ -197,7 +197,9 @@ public List result() { mappedOwner = data.mapper.asTrRemapper().map(mappedOwner); } - int methodsPerTarget = quantifierStringToMax(info.getQuantifier()); + Pair parsedQuantifier = parseQuantifier(data, info.getQuantifier()); + int quantifierMin = parsedQuantifier.first(); + int methodsPerTarget = parsedQuantifier.second(); if (targets.isEmpty() || info.getName().isEmpty() || methodsPerTarget <= 0) { // Simple case when we can't find the specific method by name @@ -254,6 +256,9 @@ public List result() { if (canInject(mappedName, mappedDesc, fullMethodToTarget)) { String quantifier = info.getQuantifier(); if (!explicitDesc) { + if (quantifierMin > 0) { + data.getLogger().error(Message.UNSUPPORTED_QUANTIFIER_MIN, info.toString()); + } quantifier = ""; } list.add(new MemberInfo(mappedOwner, mappedName, quantifier, mappedDesc)); @@ -336,33 +341,43 @@ private boolean canInject(String mappedName, String mappedDesc, Map // Copyright (c) contributors // https://github.com/FabricMC/Mixin/blob/e4edb3afad347f7561acf6a9dd4a64f2aa479658/src/main/java/org/spongepowered/asm/util/Quantifier.java - private static int quantifierStringToMax(String quantifier) { + private static Pair parseQuantifier(CommonData data, String quantifier) { if (quantifier == null || quantifier.isEmpty()) { - return 1; + return Pair.of(0, 1); } - if (quantifier.equals("*") || quantifier.equals("+")) { - return Integer.MAX_VALUE; + if (quantifier.equals("*")) { + return Pair.of(0, Integer.MAX_VALUE); + } + if (quantifier.equals("+")) { + return Pair.of(1, Integer.MAX_VALUE); } if (!quantifier.startsWith("{") || !quantifier.endsWith("}") || quantifier.length() < 3) { - return 0; + data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); + return Pair.of(0, 0); } String inner = quantifier.substring(1, quantifier.length() - 1).trim(); if (inner.isEmpty()) { - return 0; + data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); + return Pair.of(0, 0); } + String strMin = inner; String strMax = inner; int comma = inner.indexOf(','); if (comma > -1) { + strMin = inner.substring(0, comma).trim(); strMax = inner.substring(comma + 1).trim(); } try { - return !strMax.isEmpty() ? Integer.parseInt(strMax) : Integer.MAX_VALUE; + int min = !strMin.isEmpty() ? Integer.parseInt(strMin) : 0; + int max = !strMax.isEmpty() ? Integer.parseInt(strMax) : Integer.MAX_VALUE; + return Pair.of(min, Math.max(min, max)); } catch (NumberFormatException ex) { - return 0; + data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); + return Pair.of(0, 0); } } From 76b65931b868a6b424b31ad7b6823eedf94d6a4e Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 12:16:06 +0800 Subject: [PATCH 11/15] Fix checking 1 too few methods --- .../annotation/injection/CommonInjectionAnnotationVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 00dc498f..6113db6a 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 @@ -288,10 +288,10 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n continue; } - toCheck -= 1; if (toCheck <= 0) { break; } + toCheck -= 1; String otherDesc = data.mapper.mapDesc(method); missingDescriptors.remove(otherDesc); From 9c1a7cf29e638ce124aaaf6a5b999fc3786acfc0 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 12:29:29 +0800 Subject: [PATCH 12/15] Disambiguate even when unnecessary due to implicit limit of 1 --- .../injection/CommonInjectionAnnotationVisitor.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 6113db6a..054f2dd0 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 @@ -288,19 +288,25 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n continue; } - if (toCheck <= 0) { + // We only break if methodsPerTarget > 1 in order to disambiguate even when unnecessary due to implicit limit of 1 + // e.g. If targeting method foo in [foo, bar -> baz, baz], we want to disambiguate the baz even though we would be + // targeting the correct method due to the max limit + if (toCheck <= 0 && methodsPerTarget > 1) { break; } - toCheck -= 1; String otherDesc = data.mapper.mapDesc(method); - missingDescriptors.remove(otherDesc); + if (toCheck > 0) { + missingDescriptors.remove(otherDesc); + } Pair pair = Pair.of(otherName, otherDesc); Set validClasses = fullMethodToTarget.get(pair); if (validClasses == null || !validClasses.contains(target)) { return false; } + + toCheck -= 1; } // This check is needed because we might be mapping a -> b, From d8561ee954b0e7d983ef77a21d3e9d1b259cdb8d Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 12:34:44 +0800 Subject: [PATCH 13/15] Throw illegal argument is methodsPerTarget <= 0, this is checked earlier by the caller --- .../CommonInjectionAnnotationVisitor.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 054f2dd0..fe032531 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 @@ -277,6 +277,10 @@ public List result() { } private boolean canInject(String mappedName, int methodsPerTarget, Set neededDescriptors, Map, Set> fullMethodToTarget) { + if (methodsPerTarget <= 0) { + throw new IllegalArgumentException(); + } + for (TrClass target : targets) { int toCheck = methodsPerTarget; @@ -288,13 +292,6 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n continue; } - // We only break if methodsPerTarget > 1 in order to disambiguate even when unnecessary due to implicit limit of 1 - // e.g. If targeting method foo in [foo, bar -> baz, baz], we want to disambiguate the baz even though we would be - // targeting the correct method due to the max limit - if (toCheck <= 0 && methodsPerTarget > 1) { - break; - } - String otherDesc = data.mapper.mapDesc(method); if (toCheck > 0) { missingDescriptors.remove(otherDesc); @@ -306,7 +303,13 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n return false; } + // We only break if methodsPerTarget > 1 in order to disambiguate even when unnecessary due to implicit limit of 1 + // e.g. If targeting method foo in [foo, bar -> baz, baz], we want to disambiguate the baz even though we would be + // targeting the correct method due to the max limit toCheck -= 1; + if (toCheck <= 0 && methodsPerTarget > 1) { + break; + } } // This check is needed because we might be mapping a -> b, From b5552b8676199cc37b158092c2111757b8dbb160 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 28 Apr 2026 13:29:07 +0800 Subject: [PATCH 14/15] Remove unnecessary missingDescriptors check which is covered by other checks --- .../CommonInjectionAnnotationVisitor.java | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) 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 fe032531..ce3f0fb1 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 @@ -249,7 +249,7 @@ public List result() { String mappedName = entry.getKey(); SortedSet mappedDescriptors = entry.getValue(); - if (!explicitDesc && canInject(mappedName, methodsPerTarget, mappedDescriptors, fullMethodToTarget)) { // Try to apply method name without descriptor if possible + if (!explicitDesc && canInject(mappedName, methodsPerTarget, fullMethodToTarget)) { // Try to apply method name without descriptor if possible list.add(new MemberInfo(mappedOwner, mappedName, info.getQuantifier(), "")); } else { for (String mappedDesc : mappedDescriptors) { @@ -276,7 +276,7 @@ public List result() { return list; } - private boolean canInject(String mappedName, int methodsPerTarget, Set neededDescriptors, Map, Set> fullMethodToTarget) { + private boolean canInject(String mappedName, int methodsPerTarget, Map, Set> fullMethodToTarget) { if (methodsPerTarget <= 0) { throw new IllegalArgumentException(); } @@ -284,8 +284,6 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n for (TrClass target : targets) { int toCheck = methodsPerTarget; - Set missingDescriptors = new HashSet<>(neededDescriptors); - for (TrMethod method : target.getMethods()) { String otherName = data.mapper.mapName(method); if (!otherName.equals(mappedName)) { @@ -293,9 +291,6 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n } String otherDesc = data.mapper.mapDesc(method); - if (toCheck > 0) { - missingDescriptors.remove(otherDesc); - } Pair pair = Pair.of(otherName, otherDesc); Set validClasses = fullMethodToTarget.get(pair); @@ -311,18 +306,6 @@ private boolean canInject(String mappedName, int methodsPerTarget, Set n break; } } - - // This check is needed because we might be mapping a -> b, - // but another method b exists in the target class at an earlier position - // In this case, we can't use b without a descriptor because it'll target - // the earlier method instead of the method we want - for (String missingDesc : missingDescriptors) { - Pair pair = Pair.of(mappedName, missingDesc); - Set validClasses = fullMethodToTarget.get(pair); - if (validClasses != null && validClasses.contains(target)) { - return false; - } - } } return true; From 28974b5c75c7a7f4db91f222bb247b675353e683 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Tue, 19 May 2026 18:30:23 +0200 Subject: [PATCH 15/15] Fix checkstyle issues --- .../CommonInjectionAnnotationVisitor.java | 22 +++++++++++++++---- .../mixins/SeparateRemappedNameMixin.java | 4 ++-- .../targets/SeparateRemappedNameTarget.java | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) 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 ce3f0fb1..3722a6ca 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 @@ -104,6 +104,7 @@ public void visit(String name, Object value) { } List resolved = new InjectMethodMappable(data, info, targets).result(); + if (resolved.isEmpty()) { throw new RuntimeException("InjectMethodMappable should never resolve to zero entries"); } @@ -183,6 +184,7 @@ private List resolvePartials(TrClass owner, String name, String desc) desc = desc.isEmpty() ? null : desc; Collection col = owner.resolveMethods(name, desc, false, null, null); + if (col instanceof List) { return (List) col; } else { @@ -193,6 +195,7 @@ private List resolvePartials(TrClass owner, String name, String desc) @Override public List result() { String mappedOwner = info.getOwner(); + if (!mappedOwner.isEmpty()) { mappedOwner = data.mapper.asTrRemapper().map(mappedOwner); } @@ -205,6 +208,7 @@ public List result() { // Simple case when we can't find the specific method by name String desc = info.getDesc(); + if (!desc.isEmpty()) { desc = data.mapper.asTrRemapper().mapDesc(desc); } @@ -221,6 +225,7 @@ public List result() { List methods = resolvePartials(target, info.getName(), info.getDesc()); int matchedCount = Math.min(methods.size(), methodsPerTarget); + for (int i = 0; i < matchedCount; i++) { TrMember method = methods.get(i); @@ -255,12 +260,15 @@ public List result() { for (String mappedDesc : mappedDescriptors) { if (canInject(mappedName, mappedDesc, fullMethodToTarget)) { String quantifier = info.getQuantifier(); + if (!explicitDesc) { if (quantifierMin > 0) { data.getLogger().error(Message.UNSUPPORTED_QUANTIFIER_MIN, info.toString()); } + quantifier = ""; } + list.add(new MemberInfo(mappedOwner, mappedName, quantifier, mappedDesc)); } else { data.getLogger().error(Message.MISSING_INJECT, info.toString(), mappedName, mappedDesc); @@ -286,6 +294,7 @@ private boolean canInject(String mappedName, int methodsPerTarget, Map pair = Pair.of(otherName, otherDesc); Set validClasses = fullMethodToTarget.get(pair); + if (validClasses == null || !validClasses.contains(target)) { return false; } @@ -302,6 +312,7 @@ private boolean canInject(String mappedName, int methodsPerTarget, Map baz, baz], we want to disambiguate the baz even though we would be // targeting the correct method due to the max limit toCheck -= 1; + if (toCheck <= 0 && methodsPerTarget > 1) { break; } @@ -314,12 +325,14 @@ private boolean canInject(String mappedName, int methodsPerTarget, Map, Set> fullMethodToTarget) { Pair pair = Pair.of(mappedName, mappedDesc); Set validClasses = fullMethodToTarget.get(pair); + if (validClasses == null || validClasses.isEmpty()) { return false; } for (TrClass target : targets) { TrMethod method = target.getMethod(mappedName, mappedDesc); + if (method != null && !validClasses.contains(target)) { return false; } @@ -337,18 +350,22 @@ private static Pair parseQuantifier(CommonData data, String qu if (quantifier == null || quantifier.isEmpty()) { return Pair.of(0, 1); } + if (quantifier.equals("*")) { return Pair.of(0, Integer.MAX_VALUE); } + if (quantifier.equals("+")) { return Pair.of(1, Integer.MAX_VALUE); } + if (!quantifier.startsWith("{") || !quantifier.endsWith("}") || quantifier.length() < 3) { data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); return Pair.of(0, 0); } String inner = quantifier.substring(1, quantifier.length() - 1).trim(); + if (inner.isEmpty()) { data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); return Pair.of(0, 0); @@ -358,6 +375,7 @@ private static Pair parseQuantifier(CommonData data, String qu String strMax = inner; int comma = inner.indexOf(','); + if (comma > -1) { strMin = inner.substring(0, comma).trim(); strMax = inner.substring(comma + 1).trim(); @@ -371,9 +389,5 @@ private static Pair parseQuantifier(CommonData data, String qu data.getLogger().error(Message.UNABLE_TO_PARSE_QUANTIFIER, quantifier); return Pair.of(0, 0); } - } - - - } diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java index 8b799317..819bf10f 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/SeparateRemappedNameMixin.java @@ -18,13 +18,13 @@ package net.fabricmc.tinyremapper.extension.mixin.integration.mixins; -import net.fabricmc.tinyremapper.extension.mixin.integration.targets.SeparateRemappedNameTarget; - 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 net.fabricmc.tinyremapper.extension.mixin.integration.targets.SeparateRemappedNameTarget; + @Mixin(SeparateRemappedNameTarget.class) public class SeparateRemappedNameMixin { @Inject(method = "addString", at = @At("HEAD")) diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java index febdbcd9..46c42d8c 100644 --- a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java +++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/SeparateRemappedNameTarget.java @@ -21,6 +21,7 @@ public class SeparateRemappedNameTarget { public void addString(String string) { } + public void addString(String string, int value) { } }