Reputation: 643
I am writing a custom detector in Findbugs. I want to know if there is any way by which I can keep track of ASTORE and corresponding ALOAD instruction? That is if ASTORE 3 occurs in my bytecode, I want to first identify it is an ASTORE instruction then its index (in this case: 3) and look for ALOAD instruction with same index (in this case ALOAD 3 instruction).
For example in the bytecode shown below, I want to read the ASTORE 8 instruction (appearing on line #29), and see if there is any ALOAD instruction with index 8. I.e, ALOAD 8 (which can be seen on line #73).
29: astore 8
31: aload_1
32: iconst_0
.
.
.
.
.
.
60: ldc #54 // String number
62: aload 11
64: invokeinterface #56, 3 // InterfaceMethod javax/servlet/http/HttpSession.setAttribute:(Ljava/lang/String;Ljava/lang/Object;)V
69: aload 12
71: aload 7
73: aload 8
75: invokeinterface #62, 3 // InterfaceMethod com/ibm/itim/ws/services/WSSessionService.getNumber:(Ljava/lang/String;Ljava/lang/String;)Lcom/ibm/itim/ws/model/WSSession;
80: astore 14
Further, if I find corresponding ALOAD instruction then I want to check which method is invoked. Which I know can be checked using the sawOpcode() method as shown:
if (seen == INVOKEINTERFACE){...}
In short, I want to do something like this :
pseudo-code
public void sawOpcode(int seen) {
if (seen == ASTORE){
//code to identify its index i; i.e, ASTORE i
if(seen == ALOAD_i){
//if the corresponding ALOAD instruction is found...
if(seen == INVOKEINTERFACE){
// Identify the method invoked
}
}
Don't know if the above approach is right.
Upvotes: 0
Views: 695
Reputation: 100249
For simple cases it's better to extend the OpcodeStackDetector
. This abstract class supports the tracking of the stack values and stores the information about them. You should not care at all for ASTORE, ALOAD, etc. Just check for INVOKEINTERFACE. For example, if you want to find the places where the last method parameter is the return value of another method, you can do the following:
public void sawOpcode(int seen) {
if(seen == INVOKEINTERFACE && getMethodDescriptorOperand().getSlashedClassName()
.equals("com/ibm/itim/ws/services/WSSessionService") &&
getMethodDescriptorOperand().getName().equals("getNumber'))
Item topStackItem = getStack().getStackItem(0);
XMethod returnOf = topStackItem.getReturnValueOf();
if(returnOf != null && returnOf.getName().equals("getParameter"))
// here we go
}
}
You can change 0
to other number in getStackItem
call to get other operands as well. Unfortunately this way you can know that the value is the return of getParameter
method, but don't know which arguments were used in that method.
If you need to track more complex situations, then it's better to use ValueNumberAnalysis
. It's simple yet powerful concept: it just assigns the same number for values which are statically proved to be the same. Suppose you want to track all the request parameters. Let's do some preparations on method entry (for example, in visitCode
):
private ValueNumberDataflow vna;
private Map<ValueNumber, String> vnToParameterName;
@Override
public void visit(Code code) {
try {
this.vna = getClassContext().getValueNumberDataflow(getMethod());
} catch (DataflowAnalysisException | CFGBuilderException e) {
bugReporter.logError("Unable to get VNA for "+getMethodDescriptor(), e);
return;
}
this.vnToParameterName = new HashMap<>();
super.visit(code);
}
The Map
will be used to store values and corresponding parameter names. This can be done in sawOpcode
:
@Override
public void sawOpcode(int seen) {
if(seen == INVOKEINTERFACE) {
if(getNameConstantOperand().equals("getParameter") &&
getSigConstantOperand().equals("(Ljava/lang/String;)Ljava/lang/String;")
/* && check the class if necessary */) {
Object topValue = getStack().getStackItem(0).getConstant();
if(topValue instanceof String) { // known parameter name like "name"
// Iterate over locations corresponding to current PC
// (usually only one such location exists)
for(Location location : vna.getCFG()
.getLocationsContainingInstructionWithOffset(getPC())) {
try {
// This frame contains value numbers
// right after the INVOKEINTERFACE execution
ValueNumberFrame frame = vna.getFactAfterLocation(location);
// ValueNumber corresponding to the top stack value:
// the return value of getParameters() method
ValueNumber vn = frame.getTopValue();
vnToParameterName.put(vn, (String) topValue);
} catch (DataflowAnalysisException e) {
return;
}
}
}
}
}
}
So now you can use this map. Add some more code to the sawOpcode
:
if(seen == INVOKEINTERFACE && getMethodDescriptorOperand().getSlashedClassName()
.equals("com/ibm/itim/ws/services/WSSessionService") &&
getMethodDescriptorOperand().getName().equals("getNumber"))
for(Location location : vna.getCFG()
.getLocationsContainingInstructionWithOffset(getPC())) {
try {
// This frame contains value numbers
// right before the INVOKEINTERFACE execution
ValueNumberFrame frame = vna.getFactAtLocation(location);
// ValueNumber corresponding to the top stack value:
// the last parameter for getNumber method
ValueNumber vn = frame.getStackValue(0);
String parameterName = vnToParameterName.get(vn);
if(parameterName != null) {
// hurrah: this parameter is in fact
// the return value of getParameter(parameterName)
}
} catch (DataflowAnalysisException e) {
return;
}
}
I did not test this code, so some minor problems are possible. Note that ValueNumberAnalysis
is quite powerful thing. It's not only capable to track ASTORE/ALOAD, but any number of resaving this value into another variable and even (with some limitations) store to the field with subsequent load. Of course it will work also if you don't use local variables at all (like getNumber(request.getParameter("name"))
).
Upvotes: 2