Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/main/java/net/fabricmc/tinyremapper/LocalInstance.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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;
}
11 changes: 11 additions & 0 deletions src/main/java/net/fabricmc/tinyremapper/MemberInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -68,6 +69,11 @@ public int getIndex() {
return index;
}

@Override
public TrLocal[] getLocals() {
return this.locals.clone();
}

public MrjState getContext() {
return cls.getContext();
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 21 additions & 3 deletions src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -643,10 +646,25 @@ 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);
return new MethodVisitor(Constant.ASM_VERSION, super.visitMethod(access, name, desc, signature, exceptions)) {
final List<TrLocal> 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
Expand All @@ -662,7 +680,7 @@ 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);
reader.accept(cv, ClassReader.SKIP_FRAMES);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what the performance implications of this will be 🤔 If it is going to be an issue an option would be to make the locals opt-in for when they are needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking again, this will also increase the memory usage. I think having an option to either enable or disable tracking locals would be nice to have.


return ret;
}
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/net/fabricmc/tinyremapper/api/TrLocal.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

package net.fabricmc.tinyremapper.api;

public interface TrLocal {
String getName();
String getDesc();
int getIndex();
TrMethod getOwner();
}
2 changes: 2 additions & 0 deletions src/main/java/net/fabricmc/tinyremapper/api/TrMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> targets;
protected final CommonData data;
protected final List<String> targets;

CommonInjectionAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List<String> targets) {
super(Constant.ASM_VERSION, Objects.requireNonNull(delegate));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> targets;

private final List<MemberInfo> methods = new ArrayList<>();

public class ModifyVariableAnnotationVisitor extends CommonInjectionAnnotationVisitor {
public ModifyVariableAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List<String> 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<MemberInfo> methods;
private final List<TrClass> targets;

ModifyVariableSecondPassAnnotationVisitor(CommonData data, AnnotationVisitor delegate, List<String> targets, List<MemberInfo> 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<String> 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<String, Integer> 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<TrMethod> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Loading