我正在尝试使用Findbugs编写一个bug检测器来查找方法调用“System.out.println”的实例。
我理解字节码中的“System.out.println”被编译为对GETSTATIC which pushes "System.out" onto the stack的调用。对INVOKEVIRTUAL的调用会从堆栈中弹出“System.out”并调用该方法。
我准备了一些代码(如下所示),它找到了正确的GETSTATIC和INVOKEVIRTUAL调用,但无法将两者联系在一起。我怀疑我可能需要以某种方式使用OpcodeStack,但我很难理解如何使用它。任何帮助,将不胜感激。
@Override
public void sawOpcode(int seen) {
// if opcode is getstatic
if (seen == GETSTATIC) {
String clsName = getClassConstantOperand();
if ("java/lang/System".equals(clsName)) {
String fldName = getNameConstantOperand();
if ("out".equals(fldName)) {
System.out.println("SYSTEM.OUT here");
}
}
}
// if opcode is invokevirtual
if (seen == INVOKEVIRTUAL) {
String cls = getDottedClassConstantOperand();
if ("java.io.PrintStream".equals(cls)) {
String methodName = getNameConstantOperand();
if ("println".equals(methodName)) {
bugReporter.reportBug(new BugInstance("SYSTEM_OUT_PRINTLN",
NORMAL_PRIORITY).addClassAndMethod(this)
.addSourceLine(this));
}
}
}
}
答案 0 :(得分:1)
你的任务比看起来要复杂一些。一个简单的案例:
System.out.println("abc");
也被翻译成一个简单的字节码:
getstatic #2; //java/lang/System.out
ldc #3; //String abc
invokevirtual #4; //Calling java/io/PrintStream.println(String)
但是,如果您尝试打印除简单常量/已知值以外的任何内容,则会变得更难:
int x = 42;
System.out.println(x + 17);
将被翻译为:
bipush 42
istore_1 //x = 42
getstatic #2; //java/lang/System.out
iload_1 //x
bipush 17
iadd //x + 17 on the stack
invokevirtual #5; //Calling java/io/PrintStream.println(int)
但等等,情况会变得更糟:
System.out.println("x times 27 is " + x * 27);
什么? StringBuilder
:?
new #6; //new java/lang/StringBuilder()
dup
invokespecial #7; //Calling java/lang/StringBuilder()
ldc #8; //String x times 2 is
invokevirtual #9; //Calling java/lang/StringBuilder.append(String)
iload_1 //x
bipush 27
imul //x * 27 on the stack
invokevirtual #10; //Calling java/lang/StringBuilder.append:(int) with 'x * 27' argument
invokevirtual #11; //Calling java/lang/StringBuilder.toString:()
invokevirtual #4; //Calling java/io/PrintStream.println(String)
有趣的是,原始代码被翻译为(这是一个已知的Java 5(?)优化):
System.out.println(
new StringBuilder().
append("x times 27 is ").
append(x * 27).
toString()
);
确实 - 你需要一个堆栈,你必须跟踪bytecode instruction中定义的每个推/弹操作。为这么简单的任务做了很多工作......
但是如果你走这条路,解决问题很简单:遇到INVOKEVIRTUAL时,堆栈顶部应包含一些值,顶部下方的值应为“java/lang/System.out
”。
话虽如此,我100%肯定Findbugs已经实现了这一点,可能你可以使用一些FindBugs API来让你的生活更轻松。
答案 1 :(得分:1)
我发现,对于我的用例,足以确定所有使用System.out或System.err - 在99%的情况下,这些将用于调用。稍后在块中打印或.println。我的检测器检测到加载System.err或System.out的GET_STATIC操作码。代码如下,显示了确定发生这种情况的3种选择。
package my.findbugs.detectors.forbiddencalls;
import org.apache.log4j.Logger; // it is not trivial to use a logger with FindBugs in Eclipse, leave it out if there are problems
import my.findbugs.detectors.util.DetectorUtil;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
public class CallToSystemOutPrintlnDetector2 extends OpcodeStackDetector {
private static final Logger LOGGER = Logger.getLogger(CallToSystemOutPrintlnDetector2.class);
private BugReporter bugReporter;
public CallToSystemOutPrintlnDetector2(BugReporter bugReporter) {
super();
this.bugReporter = bugReporter;
LOGGER.debug("Instantiated.");
}
public void sawOpcode(int seen) {
// find occurrences of:
//2: getstatic #54; //Field java/lang/System.out:Ljava/io/PrintStream;
//2: getstatic #54; //Field java/lang/System.out:Ljava/io/PrintStream;
if (seen == GETSTATIC){
try {
// LOGGER.debug(operand); // static java.lang.System.out Ljava/io/PrintStream;
// LOGGER.debug(operand.getClass()); // class edu.umd.cs.findbugs.classfile.analysis.FieldInfo
// LOGGER.debug(operand.getName()); // err
// LOGGER.debug(operand.getClassDescriptor()); // java/lang/System
// LOGGER.debug(operand.getSignature()); // Ljava/io/PrintStream;
FieldDescriptor operand = getFieldDescriptorOperand();
ClassDescriptor classDescriptor = operand.getClassDescriptor();
if ("java/lang/System".equals(classDescriptor.getClassName()) &&
("err".equals(operand.getName())||"out".equals(operand.getName()))) {
reportBug();
}
} catch (Exception e) {
//ignore
}
// could be used
// try {
// MethodDescriptor operand = getMethodDescriptorOperand();
// LOGGER.debug(operand); // java.lang.System.outLjava/io/PrintStream;
// LOGGER.debug(operand.getClass()); // class edu.umd.cs.findbugs.classfile.MethodDescriptor
// LOGGER.debug(operand.getName()); // err
// LOGGER.debug(operand.getClassDescriptor()); // java/lang/System
// LOGGER.debug(operand.getSignature()); // Ljava/io/PrintStream;
// } catch (Exception e) {
// //ignore
// }
// could be used
// try {
// String operand = getRefConstantOperand();
// LOGGER.debug(operand); // java.lang.System.out : Ljava.io.PrintStream;
// if (operand != null && (
// operand.startsWith("java.lang.System.out :") || operand.startsWith("java.lang.System.err :"))) {
// reportBug();
// }
// } catch (Exception e) {
// //ignore
// }
}
}
private void reportBug(){
this.bugReporter.reportBug(getBugInstance());
}
private BugInstance getBugInstance() {
return new BugInstance(this, "MY_CALL_TO_SYSTEM_OUT_BUG", DetectorUtil.MY_PRIORITY)
.addClassAndMethod(this)
.addSourceLine(this);
}
}
答案 2 :(得分:0)
使用OpcodeStack类。
当你看到GETSTATIC,并且你意识到你已经'out'时,将生成的OpcodeStack.Item上的用户值设置为Boolean.TRUE。要做到这一点
try {
//process opcodes
} finally {
super.sawOpcode(seen);
if (pseudocode-saw System.out.println) {
OpcodeStack.Item item = stack.getStackItem(0);
item.setUserValue(Boolean.TRUE);
}
然后在处理println调用时,查看tos,如果用户值设置为Boolean.TRUE,则表示您处于报告错误的状态。
答案 3 :(得分:0)
我在过去提出的一个解决方案是System.out.println
调用“括号内没有太多计算”,即最多有MAX_WILDCARDS指令。
我扩展了ByteCodePatternDetector,我的模式如下:
ByteCodePattern pattern = new ByteCodePattern();
// as this is a pattern, I cannot specify here that this is supposed to be System.out
pattern.add(new Load(SYSO_FIELD, "sysoResult"));
pattern.add(new Wild(MAX_WILDCARDS));
pattern.add(new Invoke("java.io.PrintStream", "println", "/.*", Invoke.INSTANCE, null).label(LABEL_PRINT));
我稍后确保加载和调用操作的字节代码是正确的,并比较我从match.getBindingSet().lookup(...)
检索的类和字段名称,以确保它被System.out
调用。
我已经看到了现有的答案,我考虑改变我的解决方案。我只是为了完整起见而添加了这个。