Skip to content

Commit 886332e

Browse files
committed
refactor(parameters): Extract parameter name resolution to a new method
The logic for extracting parameter names from `LocalVariableAttribute` was quite complex and interleaved with the parameter value construction. This commit extracts that logic into a new private static method `getParameterNames` to improve readability and maintainability of the `Parameters` constructor. As opposed to the previous implementation, the new method traverses all local variable tables (as the spec suggests there can be several) and doesn't spam the logs if debug info is missing. Additionally: - Updated `Parameters` constructor to use the new `getParameterNames` method. - Replaced `this.staticParameters.clone()` with `this.staticParameters.freshCopy()` for clarity, as it's not a deep clone but a copy of value types, kinds and names. - Cleaned up unused imports and removed `clear()` method as it's not used and modifies the object unexpectedly. - Made minor improvements to null checks and error handling within `Parameters` methods for robustness.
1 parent 50f7f21 commit 886332e

File tree

2 files changed

+65
-70
lines changed

2 files changed

+65
-70
lines changed

agent/src/main/java/com/appland/appmap/output/v1/Parameters.java

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77
import java.util.stream.Collectors;
88
import java.util.stream.Stream;
99

10+
import javassist.bytecode.AttributeInfo;
1011
import org.tinylog.TaggedLogger;
1112

1213
import com.appland.appmap.config.AppMapConfig;
13-
import com.appland.appmap.config.Properties;
14-
import com.appland.appmap.util.Logger;
1514

1615
import javassist.CtBehavior;
1716
import javassist.CtClass;
@@ -30,7 +29,7 @@
3029
public 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));

agent/src/main/java/com/appland/appmap/transform/annotations/Hook.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void buildParameters() {
7373
}
7474

7575
public Parameters getRuntimeParameters(HookBinding binding) {
76-
Parameters runtimeParameters = this.staticParameters.clone();
76+
Parameters runtimeParameters = this.staticParameters.freshCopy();
7777
Stream.concat(Stream.of(this.sourceSystem), this.optionalSystems.stream())
7878
.sorted(Comparator.comparingInt(ISystem::getParameterPriority))
7979
.forEach(system -> {

0 commit comments

Comments
 (0)