Manoj
Manoj

Reputation: 643

Findbugs: custom detector

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

Answers (1)

Tagir Valeev
Tagir Valeev

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

Related Questions