@@ -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