Findbugs:自定义检测器
Findbugs: custom detector
我正在 Findbugs 中编写自定义检测器。我想知道是否有任何方法可以跟踪 ASTORE 和相应的 ALOAD 指令?也就是说,如果 ASTORE 3 出现在我的字节码中,我想首先确定它是一个 ASTORE 指令然后它的 index(在这种情况下:3)并寻找具有相同索引的 ALOAD 指令(在这种情况下 ALOAD 3 指令)。
例如下面的字节码,我想读取ASTORE 8指令(第29行),看看有没有 ALOAD 指令与 index 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
}
}
不知道上面的做法对不对
对于简单的情况,最好扩展 OpcodeStackDetector
。这个抽象 class 支持堆栈值的跟踪并存储有关它们的信息。您根本不应该关心 ASTORE、ALOAD 等。只需检查 INVOKEINTERFACE。例如,如果你想找到最后一个方法参数是另一个方法的return值的地方,你可以这样做:
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
方法的 return,但不知道该方法中使用了哪些参数。
如果您需要跟踪更复杂的情况,那么最好使用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"))
),它也会起作用。
我正在 Findbugs 中编写自定义检测器。我想知道是否有任何方法可以跟踪 ASTORE 和相应的 ALOAD 指令?也就是说,如果 ASTORE 3 出现在我的字节码中,我想首先确定它是一个 ASTORE 指令然后它的 index(在这种情况下:3)并寻找具有相同索引的 ALOAD 指令(在这种情况下 ALOAD 3 指令)。
例如下面的字节码,我想读取ASTORE 8指令(第29行),看看有没有 ALOAD 指令与 index 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
}
}
不知道上面的做法对不对
对于简单的情况,最好扩展 OpcodeStackDetector
。这个抽象 class 支持堆栈值的跟踪并存储有关它们的信息。您根本不应该关心 ASTORE、ALOAD 等。只需检查 INVOKEINTERFACE。例如,如果你想找到最后一个方法参数是另一个方法的return值的地方,你可以这样做:
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
方法的 return,但不知道该方法中使用了哪些参数。
如果您需要跟踪更复杂的情况,那么最好使用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"))
),它也会起作用。