【问题标题】:Findbugs: custom detectorFindbugs:自定义检测器
【发布时间】:2015-08-15 16:52:06
【问题描述】:

我正在 Findbugs 中编写自定义检测器。我想知道是否有任何方法可以跟踪 ASTORE 和相应的 ALOAD 指令?也就是说,如果 ASTORE 3 出现在我的字节码中,我想首先确定它是一个 ASTORE 指令,然后是它的 index (在这种情况下:3 ) 并寻找具有相同索引的 ALOAD 指令(在本例中为 ALOAD 3 指令)。

例如在下面显示的字节码中,我想读取 ASTORE 8 指令(出现在第 29 行),看看是否有任何 ALOAD 指令与索引 8。即,ALOAD 8(可以在第 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

此外,如果我找到相应的 ALOAD 指令,那么我想检查调用了哪个方法。我知道可以使用如下所示的 sawOpcode() 方法进行检查:

    if (seen == INVOKEINTERFACE){...}

简而言之,我想做这样的事情:

伪代码

    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

            }
        }

不知道上面的做法对不对。

【问题讨论】:

  • 作为 FindBugs 开发人员,我可能会帮助您,但目前您的问题有点模棱两可。我猜你不是对每一个 ASTORE 感兴趣,而是在寻找一些特定的情况(例如,特定字符串的 ASTORE,或 null 的 ASTORE,或先前 GETFIELD 或先前方法调用的结果的 ASTORE)。您能否更准确地描述这部分:您正在寻找哪些价值观?请注意,即使您已配对 ALOAD,这并不意味着您加载的值与可能存在分支目标(循环开始等)相同。通常应该跟踪值,而不是寄存器。
  • 我正在寻找特定字符串的 ASTORE。例如。字符串名称 = request.getParameter("name")。该语句在字节码中被翻译为ALOAD 1. LDC "name". INVOKEINTERFACE. ASTORE 7。现在我想跟踪这个 ASORE 7 以找到 ALOAD 7。@Tagir
  • 我编辑了我的答案并提供了更详细的示例。如果您还有问题,请随时询问。

标签: java bytecode findbugs


【解决方案1】:

对于简单的情况,最好扩展OpcodeStackDetector。这个抽象类支持堆栈值的跟踪并存储有关它们的信息。您根本不应该关心 ASTORE、ALOAD 等。只需检查 INVOKEINTERFACE。例如,如果要查找最后一个方法参数是另一个方法的返回值的地方,可以这样做:

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

您也可以在getStackItem 调用中将0 更改为其他数字以获取其他操作数。不幸的是,这样您可以知道该值是getParameter 方法的返回值,但不知道该方法中使用了哪些参数。

如果您需要跟踪更复杂的情况,最好使用ValueNumberAnalysis。这是一个简单而强大的概念:它只是为静态证明相同的值分配相同的数字。假设您要跟踪所有请求参数。让我们在方法入口做一些准备(例如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);
}

Map 将用于存储值和相应的参数名称。这可以在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;
                    }
                }
            }
        }
    }
}

所以现在你可以使用这张地图了。在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;
        }
    }

我没有测试这段代码,所以可能会出现一些小问题。请注意,ValueNumberAnalysis 是非常强大的东西。它不仅能够跟踪 ASTORE/ALOAD,而且可以将任意数量的值重新保存到另一个变量中,甚至(有一些限制)存储到随后加载的字段中。当然,如果您根本不使用局部变量(如getNumber(request.getParameter("name"))),它也可以工作。

【讨论】:

    猜你喜欢
    • 2015-03-15
    • 1970-01-01
    • 2015-10-02
    • 2015-11-11
    • 2017-01-08
    • 2018-12-21
    • 2014-05-11
    • 2023-03-21
    • 2022-08-16
    相关资源
    最近更新 更多