diff --git a/src/main/java/net/fabricmc/tinyremapper/LocalInstance.java b/src/main/java/net/fabricmc/tinyremapper/LocalInstance.java
new file mode 100644
index 00000000..c36037e9
--- /dev/null
+++ b/src/main/java/net/fabricmc/tinyremapper/LocalInstance.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import net.fabricmc.tinyremapper.api.TrLocal;
+import net.fabricmc.tinyremapper.api.TrMethod;
+
+public class LocalInstance implements TrLocal {
+ public LocalInstance(TrMethod owner, String name, String desc, int index) {
+ this.owner = owner;
+ this.name = name;
+ this.desc = desc;
+ this.index = index;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public String getDesc() {
+ return this.desc;
+ }
+
+ @Override
+ public int getIndex() {
+ return this.index;
+ }
+
+ @Override
+ public TrMethod getOwner() {
+ return this.owner;
+ }
+
+ final TrMethod owner;
+ final String name;
+ final String desc;
+ final int index;
+}
diff --git a/src/main/java/net/fabricmc/tinyremapper/Main.java b/src/main/java/net/fabricmc/tinyremapper/Main.java
index 727f8f38..da218771 100644
--- a/src/main/java/net/fabricmc/tinyremapper/Main.java
+++ b/src/main/java/net/fabricmc/tinyremapper/Main.java
@@ -61,6 +61,7 @@ public static void main(String[] rawArgs) {
boolean renameInvalidLocals = false;
Pattern invalidLvNamePattern = null;
boolean inferNameFromSameLvIndex = false;
+ boolean disableLocalVariableTracking = false;
NonClassCopyMode ncCopyMode = NonClassCopyMode.FIX_META_INF;
int threads = -1;
@@ -132,6 +133,9 @@ public static void main(String[] rawArgs) {
case "infernamefromsamelvindex":
inferNameFromSameLvIndex = true;
break;
+ case "disablelocalvariabletracking":
+ disableLocalVariableTracking = true;
+ break;
case "nonclasscopymode":
switch (arg.substring(valueSepPos + 1).toLowerCase(Locale.ENGLISH)) {
case "unchanged": ncCopyMode = NonClassCopyMode.UNCHANGED; break;
@@ -270,6 +274,7 @@ public static void main(String[] rawArgs) {
.renameInvalidLocals(renameInvalidLocals)
.invalidLvNamePattern(invalidLvNamePattern)
.inferNameFromSameLvIndex(inferNameFromSameLvIndex)
+ .disableLocalVariableTracking(disableLocalVariableTracking)
.threads(threads);
for (TinyRemapper.Extension ext : providedExtensions) {
diff --git a/src/main/java/net/fabricmc/tinyremapper/MemberInstance.java b/src/main/java/net/fabricmc/tinyremapper/MemberInstance.java
index dd312d86..6c3a551d 100644
--- a/src/main/java/net/fabricmc/tinyremapper/MemberInstance.java
+++ b/src/main/java/net/fabricmc/tinyremapper/MemberInstance.java
@@ -25,6 +25,7 @@
import net.fabricmc.tinyremapper.TinyRemapper.MrjState;
import net.fabricmc.tinyremapper.api.TrClass;
import net.fabricmc.tinyremapper.api.TrField;
+import net.fabricmc.tinyremapper.api.TrLocal;
import net.fabricmc.tinyremapper.api.TrMember;
import net.fabricmc.tinyremapper.api.TrMethod;
@@ -68,6 +69,11 @@ public int getIndex() {
return index;
}
+ @Override
+ public TrLocal[] getLocals() {
+ return this.locals.clone();
+ }
+
public MrjState getContext() {
return cls.getContext();
}
@@ -113,6 +119,10 @@ public void forceSetNewName(String name) {
newName = name;
}
+ public void setLocals(TrLocal[] locals) {
+ this.locals = locals.clone();
+ }
+
@Override
public String toString() {
return String.format("%s/%s%s", cls.getName(), name, desc);
@@ -151,6 +161,7 @@ public static String getNameFromId(TrMember.MemberType type, String id, boolean
final String desc;
final int access;
final int index;
+ TrLocal[] locals;
private volatile String newName;
private volatile String newBridgedName;
String newNameOriginatingCls;
diff --git a/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java b/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java
index 998e4936..89b3dd80 100644
--- a/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java
+++ b/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java
@@ -57,6 +57,7 @@
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.RecordComponentVisitor;
@@ -67,9 +68,11 @@
import net.fabricmc.tinyremapper.IMappingProvider.Member;
import net.fabricmc.tinyremapper.api.TrClass;
import net.fabricmc.tinyremapper.api.TrEnvironment;
+import net.fabricmc.tinyremapper.api.TrLocal;
import net.fabricmc.tinyremapper.api.TrLogger;
import net.fabricmc.tinyremapper.api.TrMember;
import net.fabricmc.tinyremapper.api.TrMember.MemberType;
+import net.fabricmc.tinyremapper.extension.mixin.common.data.Constant;
public class TinyRemapper {
public static class Builder {
@@ -183,6 +186,14 @@ public Builder inferNameFromSameLvIndex(boolean value) {
return this;
}
+ /**
+ * Whether to disable the tracking of local variable names, used in ModifyVariable local name remapping.
+ */
+ public Builder disableLocalVariableTracking(boolean value) {
+ this.disableLocalVariableTracking = value;
+ return this;
+ }
+
@Deprecated
public Builder extraAnalyzeVisitor(ClassVisitor visitor) {
return extraAnalyzeVisitor((mrjVersion, className, next) -> {
@@ -233,7 +244,7 @@ public TinyRemapper build() {
propagateBridges, propagateRecordComponents,
removeFrames, ignoreConflicts, resolveMissing, checkPackageAccess || fixPackageAccess, fixPackageAccess,
rebuildSourceFilenames, skipLocalMapping, renameInvalidLocals, invalidLvNamePattern, inferNameFromSameLvIndex,
- analyzeVisitors, stateProcessors, preApplyVisitors, postApplyVisitors,
+ disableLocalVariableTracking || skipLocalMapping, analyzeVisitors, stateProcessors, preApplyVisitors, postApplyVisitors,
extraRemapper, logger);
return remapper;
@@ -258,6 +269,7 @@ public TinyRemapper build() {
private boolean renameInvalidLocals = false;
private Pattern invalidLvNamePattern;
private boolean inferNameFromSameLvIndex;
+ private boolean disableLocalVariableTracking = false;
private final List analyzeVisitors = new ArrayList<>();
private final List stateProcessors = new ArrayList<>();
private final List preApplyVisitors = new ArrayList<>();
@@ -321,6 +333,7 @@ private TinyRemapper(Collection mappingProviders, boolean igno
boolean rebuildSourceFilenames,
boolean skipLocalMapping,
boolean renameInvalidLocals, Pattern invalidLvNamePattern, boolean inferNameFromSameLvIndex,
+ boolean disableLocalVariableTracking,
List analyzeVisitors, List stateProcessors,
List preApplyVisitors, List postApplyVisitors,
Remapper extraRemapper, TrLogger logger) {
@@ -345,6 +358,7 @@ private TinyRemapper(Collection mappingProviders, boolean igno
this.renameInvalidLocals = renameInvalidLocals;
this.invalidLvNamePattern = invalidLvNamePattern;
this.inferNameFromSameLvIndex = inferNameFromSameLvIndex;
+ this.disableLocalVariableTracking = disableLocalVariableTracking;
this.analyzeVisitors = analyzeVisitors;
this.stateProcessors = stateProcessors;
this.preApplyVisitors = preApplyVisitors;
@@ -643,10 +657,29 @@ public void visit(int version, int access, String name, String signature, String
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
- MemberInstance prev = ret.addMember(new MemberInstance(TrMember.MemberType.METHOD, ret, name, desc, access, ret.getMembers().size()));
+ MemberInstance member = new MemberInstance(MemberType.METHOD, ret, name, desc, access, ret.getMembers().size());
+ MemberInstance prev = ret.addMember(member);
if (prev != null) throw new RuntimeException(String.format("duplicate method %s/%s%s in inputs", ret.getName(), name, desc));
- return super.visitMethod(access, name, desc, signature, exceptions);
+ if (TinyRemapper.this.disableLocalVariableTracking) {
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ } else {
+ return new MethodVisitor(Constant.ASM_VERSION, super.visitMethod(access, name, desc, signature, exceptions)) {
+ final List locals = new ArrayList<>();
+
+ @Override
+ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
+ this.locals.add(new LocalInstance(member, name, descriptor, index));
+ super.visitLocalVariable(name, descriptor, signature, start, end, index);
+ }
+
+ @Override
+ public void visitEnd() {
+ member.setLocals(locals.toArray(new TrLocal[0]));
+ super.visitEnd();
+ }
+ };
+ }
}
@Override
@@ -662,7 +695,11 @@ public FieldVisitor visitField(int access, String name, String desc, String sign
cv = analyzeVisitors.get(i).insertAnalyzeVisitor(isInput, mrjVersion, name, cv, tags);
}
- reader.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE);
+ if (this.disableLocalVariableTracking) {
+ reader.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE);
+ } else {
+ reader.accept(cv, ClassReader.SKIP_FRAMES);
+ }
return ret;
}
@@ -1454,6 +1491,7 @@ public void propagate(TrMember m, String newName) {
private final boolean renameInvalidLocals;
private final Pattern invalidLvNamePattern;
private final boolean inferNameFromSameLvIndex;
+ private final boolean disableLocalVariableTracking;
private final List analyzeVisitors;
private final List stateProcessors;
private final List preApplyVisitors;
diff --git a/src/main/java/net/fabricmc/tinyremapper/api/TrLocal.java b/src/main/java/net/fabricmc/tinyremapper/api/TrLocal.java
new file mode 100644
index 00000000..eb74f0c9
--- /dev/null
+++ b/src/main/java/net/fabricmc/tinyremapper/api/TrLocal.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.api;
+
+public interface TrLocal {
+ String getName();
+ String getDesc();
+ int getIndex();
+ TrMethod getOwner();
+}
diff --git a/src/main/java/net/fabricmc/tinyremapper/api/TrMethod.java b/src/main/java/net/fabricmc/tinyremapper/api/TrMethod.java
index 13ee30f0..03c50b2f 100644
--- a/src/main/java/net/fabricmc/tinyremapper/api/TrMethod.java
+++ b/src/main/java/net/fabricmc/tinyremapper/api/TrMethod.java
@@ -48,4 +48,6 @@ default boolean isAbstract() {
default boolean isVirtual() {
return getType().equals(MemberType.METHOD) && (getAccess() & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) == 0;
}
+
+ TrLocal[] getLocals();
}
diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/AnnotationElement.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/AnnotationElement.java
index e2c60408..9d4e38f0 100644
--- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/AnnotationElement.java
+++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/common/data/AnnotationElement.java
@@ -34,6 +34,7 @@ public final class AnnotationElement {
public static final String TO = "to";
public static final String SLICE = "slice";
public static final String METHOD = "method";
+ public static final String NAME = "name";
public static final String DEFINITION_METHOD = "method";
public static final String DEFINITION_FIELD = "field";
}
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 47a531a3..5ac80e2a 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
@@ -46,8 +46,8 @@
* method with the first occurrence in ASM will be remapped.
*/
class CommonInjectionAnnotationVisitor extends AnnotationVisitor {
- private final CommonData data;
- private final List targets;
+ protected final CommonData data;
+ protected final List targets;
CommonInjectionAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List targets) {
super(Constant.ASM_VERSION, Objects.requireNonNull(delegate));
diff --git a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/ModifyVariableAnnotationVisitor.java b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/ModifyVariableAnnotationVisitor.java
index 8dc6714b..34286209 100644
--- a/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/ModifyVariableAnnotationVisitor.java
+++ b/src/main/java/net/fabricmc/tinyremapper/extension/mixin/soft/annotation/injection/ModifyVariableAnnotationVisitor.java
@@ -18,14 +18,151 @@
package net.fabricmc.tinyremapper.extension.mixin.soft.annotation.injection;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.tree.AnnotationNode;
+import net.fabricmc.tinyremapper.api.TrClass;
+import net.fabricmc.tinyremapper.api.TrLocal;
+import net.fabricmc.tinyremapper.api.TrMethod;
+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;
+import net.fabricmc.tinyremapper.extension.mixin.common.data.Constant;
+import net.fabricmc.tinyremapper.extension.mixin.common.data.Message;
+import net.fabricmc.tinyremapper.extension.mixin.soft.data.MemberInfo;
+
+public class ModifyVariableAnnotationVisitor extends AnnotationNode {
+ private final CommonData data;
+ private final AnnotationVisitor delegate;
+ private final List targets;
+
+ private final List methods = new ArrayList<>();
-public class ModifyVariableAnnotationVisitor extends CommonInjectionAnnotationVisitor {
public ModifyVariableAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List targets) {
- super(data, delegate, targets);
+ super(Constant.ASM_VERSION, Annotation.MODIFY_VARIABLE);
+ this.data = Objects.requireNonNull(data);
+ this.delegate = Objects.requireNonNull(delegate);
+ this.targets = Objects.requireNonNull(targets);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ AnnotationVisitor av = super.visitArray(name);
+
+ if (name.equals(AnnotationElement.METHOD)) {
+ return new AnnotationVisitor(Constant.ASM_VERSION, av) {
+ @Override
+ public void visit(String name, Object value) {
+ MemberInfo info = MemberInfo.parse(Objects.requireNonNull((String) value).replaceAll("\\s", ""));
+
+ if (info != null && (info.getOwner().isEmpty() || ModifyVariableAnnotationVisitor.this.targets.contains(info.getOwner()))) {
+ ModifyVariableAnnotationVisitor.this.methods.add(info);
+ }
+
+ super.visit(name, value);
+ }
+ };
+ }
+
+ return av;
+ }
+
+ @Override
+ public void visitEnd() {
+ this.accept(new ModifyVariableSecondPassAnnotationVisitor(this.data, this.delegate, this.targets, this.methods));
+
+ super.visitEnd();
+ }
+
+ private static class ModifyVariableSecondPassAnnotationVisitor extends CommonInjectionAnnotationVisitor {
+ private final List methods;
+ private final List targets;
+
+ ModifyVariableSecondPassAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List targets, List methods) {
+ super(data, delegate, targets);
+ this.methods = methods;
+ this.targets = Objects.requireNonNull(targets).stream()
+ .map(data.resolver::resolveClass)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ AnnotationVisitor av = super.visitArray(name);
+
+ if (name.equals(AnnotationElement.NAME)) {
+ return new AnnotationVisitor(Constant.ASM_VERSION, av) {
+ @Override
+ public void visit(String name, Object value) {
+ String localName = Objects.requireNonNull((String) value).replaceAll("\\s", "");
+
+ List collection = targets.stream()
+ .flatMap(target -> methods.stream().map(info -> resolvePartial(target, info.getName(), info.getDesc())))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(m -> {
+ TrLocal[] localVariables = m.getLocals();
+
+ if (localVariables == null || localVariables.length == 0) {
+ return localName;
+ }
+
+ Map lvtName2Index = new HashMap<>();
+
+ for (TrLocal variable : localVariables) {
+ if (!lvtName2Index.containsKey(variable.getName())) {
+ lvtName2Index.put(variable.getName(), variable.getIndex());
+ } else {
+ lvtName2Index.put(variable.getName(), -1); // TODO actually generate lvt for injection points, currently only handles unique names
+ }
+ }
+
+ if (!lvtName2Index.containsKey(localName)) {
+ return localName;
+ }
+
+ int lvIndex = lvtName2Index.get(localName);
+
+ if (lvIndex < 0) {
+ return localName;
+ }
+
+ return data.mapper.asTrRemapper().mapMethodArg(m.getOwner().getName(), m.getName(), m.getDesc(), lvIndex, localName);
+ })
+ .distinct().collect(Collectors.toList());
+
+ if (collection.size() > 1) {
+ data.getLogger().error(Message.CONFLICT_MAPPING, localName, collection);
+ } else if (collection.isEmpty()) {
+ data.getLogger().warn(Message.NO_MAPPING_NON_RECURSIVE, localName, targets);
+ }
+
+ super.visit(name, collection.stream().findFirst().orElse(localName));
+ }
+ };
+ }
+
+ return av;
+ }
+
+ private Optional resolvePartial(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 | ResolveUtility.FLAG_NON_SYN).map(m -> m);
+ }
}
}
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 576f768c..62a27206 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
@@ -42,9 +42,11 @@
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
import net.fabricmc.tinyremapper.extension.mixin.integration.mixins.AmbiguousRemappedNameMixin;
+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.WildcardTargetMixin;
import net.fabricmc.tinyremapper.extension.mixin.integration.targets.AmbiguousRemappedNameTarget;
+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.WildcardTarget;
@@ -91,6 +93,22 @@ public void remapAmbiuousRemappedName() throws IOException {
assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/Inject;(method={\"add(Ljava/lang/String;)V\""));
}
+ @Test
+ public void remapLvtName() throws IOException {
+ String remapped = remap(LvtRemapTarget.class, LvtRemapTargetMixin.class, out -> {
+ String fqn = "net/fabricmc/tinyremapper/extension/mixin/integration/targets/LvtRemapTarget";
+ out.acceptClass(fqn, "com/example/Remapped");
+ IMappingProvider.Member member = new IMappingProvider.Member(fqn, "target", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ out.acceptMethod(member, "targetRemapped");
+ out.acceptMethodArg(member, 1, "remappedStr1");
+ out.acceptMethodArg(member, 2, "remappedStr2");
+ out.acceptMethodArg(member, 3, "remappedStr3");
+ out.acceptMethodArg(member, 4, "remappedStr4");
+ });
+
+ assertTrue(remapped.contains("@Lorg/spongepowered/asm/mixin/injection/ModifyVariable;(method={\"targetRemapped\"}, at=@Lorg/spongepowered/asm/mixin/injection/At;(value=\"HEAD\"), name={\"remappedStr3\"})"));
+ }
+
private String remap(Class> target, Class> mixin, IMappingProvider mappings) throws IOException {
Path classpath = createJar(target);
Path input = createJar(mixin);
diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/LvtRemapTargetMixin.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/LvtRemapTargetMixin.java
new file mode 100644
index 00000000..5e94b6af
--- /dev/null
+++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/mixins/LvtRemapTargetMixin.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+import net.fabricmc.tinyremapper.extension.mixin.integration.targets.LvtRemapTarget;
+
+@Mixin(LvtRemapTarget.class)
+public class LvtRemapTargetMixin {
+ @ModifyVariable(method = "target", at = @At("HEAD"), name = "str3")
+ private String modifyStr3(String str3) {
+ return "noice";
+ }
+}
diff --git a/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/LvtRemapTarget.java b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/LvtRemapTarget.java
new file mode 100644
index 00000000..f1d461d8
--- /dev/null
+++ b/src/test/java/net/fabricmc/tinyremapper/extension/mixin/integration/targets/LvtRemapTarget.java
@@ -0,0 +1,24 @@
+/*
+ * 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 LvtRemapTarget {
+ public void target(String str1, String str2, String str3, String str4) {
+ }
+}