77import java .util .stream .Collectors ;
88import java .util .stream .Stream ;
99
10+ import javassist .bytecode .AttributeInfo ;
1011import org .tinylog .TaggedLogger ;
1112
1213import com .appland .appmap .config .AppMapConfig ;
13- import com .appland .appmap .config .Properties ;
14- import com .appland .appmap .util .Logger ;
1514
1615import javassist .CtBehavior ;
1716import javassist .CtClass ;
3029public class Parameters implements Iterable <Value > {
3130 private static final TaggedLogger logger = AppMapConfig .getLogger (null );
3231
33- private final ArrayList <Value > values = new ArrayList <Value >();
32+ private final ArrayList <Value > values = new ArrayList <>();
3433
3534 public Parameters () { }
3635
@@ -48,7 +47,7 @@ public Parameters(CtBehavior behavior) {
4847 "." + behavior .getName () +
4948 methodInfo .getDescriptor ();
5049
51- CtClass [] paramTypes = null ;
50+ CtClass [] paramTypes ;
5251 try {
5352 paramTypes = behavior .getParameterTypes ();
5453 } catch (NotFoundException e ) {
@@ -71,51 +70,11 @@ public Parameters(CtBehavior behavior) {
7170 return ;
7271 }
7372
74- CodeAttribute codeAttribute = methodInfo .getCodeAttribute ();
75- LocalVariableAttribute locals = null ;
76- if (codeAttribute != null ) {
77- locals = (LocalVariableAttribute ) codeAttribute .getAttribute (javassist .bytecode .LocalVariableAttribute .tag );
78- } else {
79- logger .debug ("No code attribute for {}" , fqn );
80- }
81-
73+ String [] paramNames = getParameterNames (methodInfo , paramTypes );
8274 int numParams = paramTypes .length ;
83- String [] paramNames = new String [numParams ];
84- if (locals != null && numParams > 0 ) {
85- int numLocals = locals .tableLength ();
86-
87- // This is handy when debugging this code, but produces too much
88- // noise for general use.
89- if (Properties .DebugLocals ) {
90- logger .debug ("local variables for {}" , fqn );
91- for (int idx = 0 ; idx < numLocals ; idx ++) {
92- logger .debug (" {} {} {}" , idx , locals .variableName (idx ), locals .index (idx ));
93- }
94- }
95-
96- // Iterate through the local variables to find the ones that match the argument slots.
97- // Arguments are pushed into consecutive slots, starting at 0 (for this or the first argument),
98- // and then incrementing by 1 for each argument, unless the argument is an unboxed long or double,
99- // in which case it takes up two slots.
100- int slot = Modifier .isStatic (behavior .getModifiers ()) ? 0 : 1 ; // ignore `this`
101- for (int i = 0 ; i < numParams ; i ++) {
102- try {
103- // note that the slot index is not the same as the
104- // parameter index or the local variable index
105- paramNames [i ] = locals .variableNameByIndex (slot );
106- } catch (Exception e ) {
107- // the debug info might be corrupted or partial, let's not crash in this case
108- logger .debug (e , "Failed to get local variable name for slot {} in {}" , slot , fqn );
109- } finally {
110- // note these only correspond to unboxed types — boxed double and long will still have width 1
111- int width = paramTypes [i ] == CtClass .doubleType || paramTypes [i ] == CtClass .longType ? 2 : 1 ;
112- slot += width ;
113- }
114- }
115- }
11675
11776 Value [] paramValues = new Value [numParams ];
118- for (int i = 0 ; i < paramTypes . length ; ++i ) {
77+ for (int i = 0 ; i < numParams ; ++i ) {
11978 // Use a real parameter name if we have it, a fake one if we
12079 // don't.
12180 String paramName = paramNames [i ];
@@ -130,11 +89,61 @@ public Parameters(CtBehavior behavior) {
13089 paramValues [i ] = param ;
13190 }
13291
133- for (int i = 0 ; i < paramValues . length ; ++ i ) {
134- this .add (paramValues [ i ] );
92+ for (Value paramValue : paramValues ) {
93+ this .add (paramValue );
13594 }
13695 }
13796
97+ /**
98+ * Iterate through the LocalVariableTables to get parameter names.
99+ * Local variable tables are debugging metadata containing information about local variables.
100+ * Variables are organized into slots; first slots are used for parameters, then for local variables.
101+ *
102+ * @param methodInfo for the method
103+ * @param paramTypes types of the parameters (used to calculate slot positions)
104+ * @return Array of parameter names (ignoring this), with null for any names that could not be determined.
105+ * Length of the array matches length of paramTypes.
106+ * @see <a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.13">The Java Virtual Machine Specification: The LocalVariableTable Attribute</a>
107+ */
108+ private static String [] getParameterNames (MethodInfo methodInfo , CtClass [] paramTypes ) {
109+ String [] paramNames = new String [paramTypes .length ];
110+
111+ CodeAttribute codeAttribute = methodInfo .getCodeAttribute ();
112+ if (codeAttribute != null ) {
113+ boolean isStatic = Modifier .isStatic (methodInfo .getAccessFlags ());
114+
115+ // count number of slots taken by all the parameters
116+ int slotCount = isStatic ? 0 : 1 ; // account for `this`
117+ for (CtClass paramType : paramTypes ) {
118+ slotCount += (paramType == CtClass .doubleType || paramType == CtClass .longType ) ? 2 : 1 ;
119+ }
120+
121+ String [] namesBySlot = new String [slotCount ];
122+
123+ for (AttributeInfo attr : codeAttribute .getAttributes ()) {
124+ if (attr instanceof LocalVariableAttribute ) {
125+ LocalVariableAttribute localVarAttr = (LocalVariableAttribute ) attr ;
126+
127+ for (int i = 0 ; i < localVarAttr .tableLength (); i ++) {
128+ int index = localVarAttr .index (i );
129+ if (index < slotCount ) {
130+ namesBySlot [index ] = localVarAttr .variableName (i );
131+ }
132+ }
133+ }
134+ }
135+
136+ int slot = isStatic ? 0 : 1 ; // ignore `this`
137+ for (int i = 0 ; i < paramTypes .length ; i ++) {
138+ paramNames [i ] = namesBySlot [slot ];
139+ int width = paramTypes [i ] == CtClass .doubleType || paramTypes [i ] == CtClass .longType ? 2 : 1 ;
140+ slot += width ;
141+ }
142+ }
143+
144+ return paramNames ;
145+ }
146+
138147 /**
139148 * Get an iterator for each {@link Value}.
140149 * @return A {@link Value} iterator
@@ -172,43 +181,29 @@ public int size() {
172181 return this .values .size ();
173182 }
174183
175-
176184 /**
177- * Clears the internal value array.
178- */
179- public void clear () {
180- this .values .clear ();
181- }
182-
183- /**
184- * Gets a {@Value} object stored by this Parameters object by name/identifier.
185+ * Gets a {@link Value} object stored by this Parameters object by name/identifier.
185186 * @param name The name or identifier of the @{link Value} to be returned
186187 * @return The {@link Value} object found
187188 * @throws NoSuchElementException If no @{link Value} object is found
188189 */
189190 public Value get (String name ) throws NoSuchElementException {
190- if (this .values != null ) {
191- for (Value param : this .values ) {
192- if (param .name .equals (name )) {
193- return param ;
194- }
191+ for (Value param : this .values ) {
192+ if (param .name .equals (name )) {
193+ return param ;
195194 }
196195 }
197196
198197 throw new NoSuchElementException ();
199198 }
200199
201200 /**
202- * Gets a {@Value} object stored by this Parameters object by index.
201+ * Gets a {@link Value} object stored by this Parameters object by index.
203202 * @param index The index of the @{link Value} to be returned
204203 * @return The {@link Value} object at the given index
205204 * @throws NoSuchElementException if no @{link Value} object is found at the given index
206205 */
207206 public Value get (Integer index ) throws NoSuchElementException {
208- if (this .values == null ) {
209- throw new NoSuchElementException ();
210- }
211-
212207 try {
213208 return this .values .get (index );
214209 } catch (NullPointerException | IndexOutOfBoundsException e ) {
@@ -233,10 +228,10 @@ public Boolean validate(Integer index, String type) {
233228 }
234229
235230 /**
236- * Performs a deep copy of the Parameters object and all of its values .
231+ * Creates a copy of the parameters object with the value types, kinds and names preserved .
237232 * @return A new Parameters object
238233 */
239- public Parameters clone () {
234+ public Parameters freshCopy () {
240235 Parameters clonedParams = new Parameters ();
241236 for (Value param : this .values ) {
242237 clonedParams .add (new Value (param ));
0 commit comments