Skip to content

Commit

Permalink
Add a reflective name fixer, used for various calls in Class and Meth…
Browse files Browse the repository at this point in the history
…odHandles.Lookup

This currently handles class names with mixed mappings, so doing:

Class.forName(ServerPlayerEntity.class.getName() + "$2");

now works correctly even if the inner class has been mapped differently.

This also supports field and method lookups using Class.get[Declared](Field/Method), as well as the MethodHandle.Lookup methods find[Static](Getter/Setter/VarHandle), and find(Static/Virtual/Special)

This only works if the mappings are present for it - using proper names will never work outside of a development environment.
In addition, we still encourage developers to use the MappingResolver to be sure the names will always be mapped correctly.

This commit also adds a somewhat unrelated change:

- Fixed InternalsHiderTransform always adding one to the maximum stack size of every single method.

(I hadn't checked the order that visitMaxs is called in - and since it must be called last, just before visitEnd, we can wait and see if we've needed to emit code that requires an additional stack element, whereas I previously thought it was called first)
  • Loading branch information
AlexIIL committed Dec 3, 2024
1 parent ccf0ff3 commit d9f99fc
Show file tree
Hide file tree
Showing 10 changed files with 1,037 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,98 @@
package net.fabricmc.minecraft.test;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.jetbrains.annotations.Nullable;
import org.quiltmc.loader.api.MappingResolver;
import org.quiltmc.loader.api.QuiltLoader;
import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.mappingio.tree.MappingTreeView;
import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView;

import net.minecraft.entity.Entity;
import net.minecraft.item.CompassItem;
import net.minecraft.item.DyeItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.world.World;

public class FabricEntrypointTest implements ModInitializer {
@Override
public void onInitialize() {
System.out.println("testmod initialized!");

System.out.println("Testing ReflectiveFixer");
try {
String clnom = ServerPlayerEntity.class.getName() + "$2"; // An anonymous inner class
Class<?> cls = Class.forName(clnom, true, FabricEntrypointTest.class.getClassLoader());
MappingResolver mappings = QuiltLoader.getMappingResolver();
MappingTreeView mappingsView = QuiltLauncherBase.getLauncher().getMappingConfiguration().getMappings();
String currentNamespace = mappings.getCurrentRuntimeNamespace();

for (String namespace : mappings.getNamespaces()) {
String name = mappings.unmapClassName(namespace, cls.getName());
System.out.println(
"[" + (currentNamespace.equals(namespace) ? "x" : " ") + "] " + namespace + " = " + name
);
}

System.out.println(cls.getName());
System.out.println(Arrays.toString(cls.getInterfaces()));
System.out.println(cls.getDeclaredField("field_29183"));
System.out.println(cls.getField("field_29183"));
MethodHandle getter = MethodHandles.privateLookupIn(cls, MethodHandles.lookup()).findGetter(
cls, "field_29183", ServerPlayerEntity.class
);
int h = 0;
for (int i = 0; i < 100_000; i++) {
MethodHandle handle = MethodHandles.privateLookupIn(cls, MethodHandles.lookup()).findGetter(
cls, "field_29183", ServerPlayerEntity.class
);
h = h + 1 * handle.hashCode();
}
System.out.println(h);
System.out.println("Obtained getter " + getter + " as " + getter.type().toMethodDescriptorString());

// inventoryTick (ItemStack stack, World world, Entity entity, int slot, boolean selected)V
MethodType type = MethodType.methodType(void.class, ItemStack.class, World.class, Entity.class, int.class, boolean.class);
String intermediaryName = null;
for (Method m : Item.class.getDeclaredMethods()) {
if (m.getReturnType() != type.returnType() || !Arrays.equals(type.parameterArray(), m.getParameterTypes())) {
continue;
}
System.out.println(m.getName());
// Should we publish this as API?
int i = mappingsView.getNamespaceId("named");
MethodMappingView method = mappingsView.getMethod(Item.class.getName().replace('.', '/'), "inventoryTick", type.descriptorString(), i);
if (method != null) {
System.out.println(method.getName("official"));
System.out.println(intermediaryName = method.getName("intermediary"));
System.out.println(method.getName("named"));
break;
}
}
System.out.println("Method Name = " + intermediaryName);
System.out.println("Method (Virtual) = " + MethodHandles.lookup().findVirtual(CompassItem.class, intermediaryName, type));
Lookup privateInItem = MethodHandles.privateLookupIn(Item.class, MethodHandles.lookup());
System.out.println("Method (Special) = " + privateInItem.findSpecial(Item.class, intermediaryName, type, Item.class));

System.out.println("Method (Declared) = " + Item.class.getDeclaredMethod(intermediaryName, type.parameterArray()));
System.out.println("Method (Any) = " + CompassItem.class.getMethod(intermediaryName, type.parameterArray()));

int namespace = mappingsView.getNamespaceId("named");
MethodMappingView method = mappingsView.getMethod(CompassItem.class.getName().replace('.', '/'), "getSpawnPosition", null, namespace);
System.out.println("getSpawnPosition = " + method);

} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/quiltmc/loader/api/MappingResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.Collection;

/**
* Helper class for performing mapping resolution.
* Helper class for performing mapping resolution. This can be obtained through {@link QuiltLoader#getMappingResolver()}.
*
* <p><strong>Note</strong>: The target namespace (the one being mapped to) for mapping (or the
* source one for unmapping) is always implied to be the one Loader is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public final class MappingConfiguration {
private String gameId;
private String gameVersion;
private String mappingsSource;
private final VisitableMappingTree mappings = new MemoryMappingTree();
private final VisitableMappingTree mappings = new MemoryMappingTree(true);
private List<String> namespaces;

public String getGameId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ public MethodVisitor visitMethod(int access, String mthName, String mthDescripto

return new MethodVisitor(api, sup) {

boolean hasIllegal = false;

@Override
public void visitCode() {
super.visitCode();
Expand All @@ -180,12 +182,6 @@ public void visitCode() {
}
}

@Override
public void visitMaxs(int maxStack, int maxLocals) {
// Sadly nothing we can do about this, since errors might occur anywhere :/
super.visitMaxs(maxStack + 1, maxLocals);
}

@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
if (owner.equals(className)) {
Expand All @@ -201,6 +197,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String descrip
}

if (set != null && !set.isPermitted(mod)) {
hasIllegal = true;
super.visitLdcInsn(set.generateError(mod, "the field " + owner + "." + name, className + "." + mthName + mthDescriptor));
super.visitMethodInsn(
Opcodes.INVOKESTATIC, METHOD_OWNER, set.getInvokeMethodName(), "(Ljava/lang/String;)V",
Expand Down Expand Up @@ -228,6 +225,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
}

if (set != null && !set.isPermitted(mod)) {
hasIllegal = true;
super.visitLdcInsn(
set.generateError(mod, "the method " + owner + "." + name + descriptor, className + "." + mthName + mthDescriptor)
);
Expand All @@ -239,6 +237,13 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri

super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}

@Override
public void visitMaxs(int maxStack, int maxLocals) {
// Ideally we'd track the actual stack value to figure out if we truly need to increase it
// But this is *much* easier
super.visitMaxs(maxStack + (hasIllegal ? 1 : 0), maxLocals);
}
};
}

Expand Down
Loading

0 comments on commit d9f99fc

Please sign in to comment.