Skip to content

Commit 41a0ead

Browse files
Use actual parameter names instead of generic 'argN' names (#191)
* Fix: Use actual parameter names instead of generic 'argN' names * Address review #191 (review) * De-dupe code * Implement review * Address review * Address review --------- Co-authored-by: joe <burtonjae@hotmail.co.uk>
1 parent 10af730 commit 41a0ead

File tree

3 files changed

+149
-42
lines changed

3 files changed

+149
-42
lines changed

src/main/java/org/spongepowered/asm/mixin/FabricUtil.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,15 @@ public final class FabricUtil {
5454
*/
5555
public static final int COMPATIBILITY_0_16_5 = 16005; // 0.16.5+mixin.0.8.7
5656

57+
/**
58+
* Fabric compatibility version 0.17.0
59+
*/
60+
public static final int COMPATIBILITY_0_17_0 = 17000; // 0.17.0+mixin.0.8.7
61+
5762
/**
5863
* Latest compatibility version
5964
*/
60-
public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_16_5;
65+
public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_17_0;
6166

6267
public static String getModId(IMixinConfig config) {
6368
return getModId(config, "(unknown)");

src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -153,30 +153,30 @@ private Local[] initLocals(Target target, boolean argsOnly, AbstractInsnNode nod
153153
if (!argsOnly) {
154154
LocalVariableNode[] locals = Locals.getLocalsAt(target.classNode, target.method, node, org.spongepowered.asm.mixin.FabricUtil.getCompatibility(info));
155155
if (locals != null) {
156-
Local[] lvt = new Local[locals.length];
157-
for (int l = 0; l < locals.length; l++) {
158-
if (locals[l] != null) {
159-
lvt[l] = new Local(locals[l].name, Type.getType(locals[l].desc));
160-
}
161-
}
162-
return lvt;
156+
return getLocals(locals);
163157
}
164158
}
165-
166-
Local[] lvt = new Local[this.baseArgIndex + Bytecode.getArgsSize(target.arguments)];
167-
if (!this.isStatic) {
168-
lvt[0] = new Local("this", Type.getObjectType(target.classNode.name));
169-
}
170-
for (int local = this.baseArgIndex, arg = 0; local < lvt.length; local++) {
171-
Type argType = target.arguments[arg++];
172-
lvt[local] = new Local("arg" + local, argType);
173-
if (argType.getSize() == 2) {
174-
lvt[++local] = null;
159+
160+
int fabricCompatibility = org.spongepowered.asm.mixin.FabricUtil.getCompatibility(info);
161+
// Before 0.17.0, the names of arg variables were inconsistent. With argsOnly = true, they were "arg" + lvIndex
162+
// whereas for argsOnly = false, they were "arg" + paramIndex. We pick the latter always now which is the slightly
163+
// saner option, but we need to pick the former here when backwards compat is needed.
164+
boolean fallbackToLvIndex = fabricCompatibility < org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_17_0;
165+
LocalVariableNode[] initialLocals = Locals.getInitialMethodLocals(target.method, target.classNode, fabricCompatibility, fallbackToLvIndex);
166+
167+
return getLocals(initialLocals);
168+
}
169+
170+
private Local[] getLocals(LocalVariableNode[] initialLocals) {
171+
Local[] lvt = new Local[initialLocals.length];
172+
for (int l = 0; l < initialLocals.length; l++) {
173+
if (initialLocals[l] != null) {
174+
lvt[l] = new Local(initialLocals[l].name, Type.getType(initialLocals[l].desc));
175175
}
176176
}
177177
return lvt;
178178
}
179-
179+
180180
private void initOrdinals() {
181181
Map<Type, Integer> ordinalMap = new HashMap<Type, Integer>();
182182
for (int l = 0; l < this.locals.length; l++) {

src/main/java/org/spongepowered/asm/util/Locals.java

Lines changed: 125 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,117 @@ private Locals() {
270270
// utility class
271271
}
272272

273+
/**
274+
* Constructs the initial local variable frame for a method, containing only "this" (if non-static)
275+
* and the method parameters with their actual names extracted from the LVT when available.
276+
*
277+
* @param method the method to construct locals for
278+
* @param classNode the class containing the method
279+
* @param fabricCompatibility the compatibility level to use
280+
* @return an array of LocalVariableNodes representing the initial method frame
281+
*/
282+
public static LocalVariableNode[] getInitialMethodLocals(MethodNode method, ClassNode classNode, int fabricCompatibility) {
283+
return getInitialMethodLocals(method, classNode, fabricCompatibility, false);
284+
}
285+
286+
/**
287+
* Constructs the initial local variable frame for a method, containing only "this" (if non-static)
288+
* and the method parameters with their actual names extracted from the LVT when available.
289+
*
290+
* @param method the method to construct locals for
291+
* @param classNode the class containing the method
292+
* @param fabricCompatibility the compatibility level to use
293+
* @param fallbackToLvIndex whether the fallback names should use the LVT index of the variable (for backwards compatibility)
294+
* @return an array of LocalVariableNodes representing the initial method frame
295+
*/
296+
public static LocalVariableNode[] getInitialMethodLocals(MethodNode method, ClassNode classNode, int fabricCompatibility, boolean fallbackToLvIndex) {
297+
boolean isStatic = Bytecode.isStatic(method);
298+
Type[] argTypes = Type.getArgumentTypes(method.desc);
299+
int initialFrameSize = Bytecode.getFirstNonArgLocalIndex(method);
300+
LocalVariableNode[] frame = new LocalVariableNode[initialFrameSize];
301+
302+
int local = 0;
303+
304+
// Try to extract parameter names from LVT if compatibility level is high enough
305+
String[] paramNames = fabricCompatibility >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_17_0
306+
? getParameterNames(method, isStatic)
307+
: new String[argTypes.length];
308+
309+
// Initialise implicit "this" reference in non-static methods
310+
if (!isStatic) {
311+
frame[local++] = new LocalVariableNode("this", Type.getObjectType(classNode.name).getDescriptor(), null, null, null, 0);
312+
}
313+
314+
// Initialise method arguments
315+
for (int index = 0; index < argTypes.length; index++) {
316+
Type argType = argTypes[index];
317+
String paramName = paramNames[index];
318+
if (paramName == null) {
319+
if (fallbackToLvIndex) {
320+
paramName = "arg" + local;
321+
} else {
322+
paramName = "arg" + index;
323+
}
324+
}
325+
frame[local] = new LocalVariableNode(paramName, argType.getDescriptor(), null, null, null, local);
326+
local += argType.getSize();
327+
}
328+
329+
return frame;
330+
}
331+
332+
/**
333+
* Attempts to extract parameter names from the method's local variable table.
334+
* This is used as a fallback when detailed LVT information is not available at a specific
335+
* instruction, but we still want to use actual parameter names instead of generic "arg" names.
336+
*
337+
* @param method the method to extract parameter names from
338+
* @param isStatic whether the method is static (affects the starting local index)
339+
* @return an array of parameter names, with null entries where names are unavailable
340+
*/
341+
private static String[] getParameterNames(MethodNode method, boolean isStatic) {
342+
Type[] argTypes = Type.getArgumentTypes(method.desc);
343+
if (argTypes.length == 0) {
344+
return new String[0];
345+
}
346+
347+
// Always allocate array for all parameters
348+
String[] paramNames = new String[argTypes.length];
349+
350+
// Build map of local variable index to parameter index
351+
Map<Integer, Integer> indexToParam = new HashMap<>();
352+
int localIndex = isStatic ? 0 : 1; // Skip "this" for non-static methods
353+
for (int arg = 0; arg < argTypes.length; arg++) {
354+
indexToParam.put(localIndex, arg);
355+
localIndex += argTypes[arg].getSize();
356+
}
357+
358+
// Single pass through local variable table to extract names
359+
if (method.localVariables != null) {
360+
for (LocalVariableNode lvNode : method.localVariables) {
361+
Integer paramIndex = indexToParam.get(lvNode.index);
362+
if (paramIndex != null) {
363+
paramNames[paramIndex] = lvNode.name;
364+
}
365+
}
366+
}
367+
368+
// Merge in names from parameters field where LVT didn't provide them
369+
if (method.parameters != null) {
370+
for (int i = 0; i < Math.min(argTypes.length, method.parameters.size()); i++) {
371+
if (paramNames[i] == null) {
372+
paramNames[i] = method.parameters.get(i).name;
373+
}
374+
}
375+
}
376+
377+
return paramNames;
378+
}
379+
273380
/**
274381
* Injects appropriate LOAD opcodes into the supplied InsnList for each
275382
* entry in the supplied locals array starting at pos
276-
*
383+
*
277384
* @param locals Local types (can contain nulls for uninitialised, TOP, or
278385
* RETURN values in locals)
279386
* @param insns Instruction List to inject into
@@ -334,7 +441,7 @@ public static void loadLocals(Type[] locals, InsnList insns, int pos, int limit)
334441
*/
335442
public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, int fabricCompatibility) {
336443
if (fabricCompatibility >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_10_0) {
337-
return Locals.getLocalsAt(classNode, method, node, Settings.DEFAULT);
444+
return Locals.getLocalsAt(classNode, method, node, Settings.DEFAULT, fabricCompatibility);
338445
} else {
339446
return getLocalsAt092(classNode, method, node);
340447
}
@@ -343,16 +450,16 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
343450
/**
344451
* <p>Attempts to identify available locals at an arbitrary point in the
345452
* bytecode specified by node.</p>
346-
*
453+
*
347454
* <p>This method builds an approximate view of the locals available at an
348455
* arbitrary point in the bytecode by examining the following features in
349-
* the bytecode:</p>
456+
* the bytecode:</p>
350457
* <ul>
351458
* <li>Any available stack map frames</li>
352459
* <li>STORE opcodes</li>
353460
* <li>The local variable table</li>
354461
* </ul>
355-
*
462+
*
356463
* <p>Inference proceeds by walking the bytecode from the start of the
357464
* method looking for stack frames and STORE opcodes. When either of these
358465
* is encountered, an attempt is made to cross-reference the values in the
@@ -363,14 +470,14 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
363470
* simulated locals array are spaced according to their size (unlike the
364471
* representation in FrameNode) and this TOP, NULL and UNINTITIALIZED_THIS
365472
* opcodes will be represented as null values in the simulated frame.</p>
366-
*
473+
*
367474
* <p>This code does not currently simulate the prescribed JVM behaviour
368475
* where overwriting the second slot of a DOUBLE or LONG actually
369476
* invalidates the DOUBLE or LONG stored in the previous location, so we
370477
* have to hope (for now) that this behaviour isn't emitted by the compiler
371478
* or any upstream transformers. I may have to re-think this strategy if
372479
* this situation is encountered in the wild.</p>
373-
*
480+
*
374481
* @param classNode ClassNode containing the method, used to initialise the
375482
* implicit "this" reference in simple methods with no stack frames
376483
* @param method MethodNode to explore
@@ -384,6 +491,10 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
384491
* specified location
385492
*/
386493
public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, Settings settings) {
494+
return getLocalsAt(classNode, method, node, settings, org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_LATEST);
495+
}
496+
497+
private static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, Settings settings, int fabricCompatibility) {
387498
for (int i = 0; i < 3 && (node instanceof LabelNode || node instanceof LineNumberNode); i++) {
388499
AbstractInsnNode nextNode = Locals.nextNode(method.instructions, node);
389500
if (nextNode instanceof FrameNode) { // Do not ffwd over frames
@@ -402,25 +513,16 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
402513
}
403514
List<FrameData> frames = methodInfo.getFrames();
404515

516+
// Initialize the frame with "this" and method parameters
517+
LocalVariableNode[] initialLocals = getInitialMethodLocals(method, classNode, fabricCompatibility);
405518
LocalVariableNode[] frame = new LocalVariableNode[method.maxLocals];
406-
int local = 0, index = 0;
519+
System.arraycopy(initialLocals, 0, frame, 0, initialLocals.length);
407520

408-
// Initialise implicit "this" reference in non-static methods
409-
if ((method.access & Opcodes.ACC_STATIC) == 0) {
410-
frame[local++] = new LocalVariableNode("this", Type.getObjectType(classNode.name).toString(), null, null, null, 0);
411-
}
412-
413-
// Initialise method arguments
414-
for (Type argType : Type.getArgumentTypes(method.desc)) {
415-
frame[local] = new LocalVariableNode("arg" + index++, argType.toString(), null, null, null, local);
416-
local += argType.getSize();
417-
}
418-
419-
final int initialFrameSize = local;
420-
int frameSize = local;
521+
final int initialFrameSize = initialLocals.length;
522+
int frameSize = initialFrameSize;
421523
int frameIndex = -1;
422-
int lastFrameSize = local;
423-
int knownFrameSize = local;
524+
int lastFrameSize = initialFrameSize;
525+
int knownFrameSize = initialFrameSize;
424526
VarInsnNode storeInsn = null;
425527

426528
for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) {

0 commit comments

Comments
 (0)