I am trying to write a bug detector to find instances of the method call "System.out.println" using Findbugs.
I understand that "System.out.println" in bytecode is compiled to a call to GETSTATIC, which pushes "System.out" onto the stack. A call to INVOKEVIRTUAL pops "System.out" off the stack and calls the method.
I have prepared some code (found below) which finds the correct GETSTATIC and INVOKEVIRTUAL calls, but have been unable to link the two together. I suspect I may need to use OpcodeStack in some way, but am having trouble in understanding how I can use it. Any help would be appreciated.
@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));
}
}
}
}
I found that, for my use case, it is enough to determine that System.out or System.err are used at all - in 99% of cases, these will be used for calling .print or .println later in the block. My detector detects GET_STATIC opcodes that load System.err or System.out. The code is below, showing 3 alternatives of determining that this occurs.
Use the class OpcodeStack.
When you see a GETSTATIC, and you realize you have 'out', set the user value on the generated OpcodeStack.Item to Boolean.TRUE. to do this do
then when you process the println call, look at the tos, and if the user value is set to Boolean.TRUE, you know you are in the state to report the bug.
One solution I came up with in the past finds
System.out.println
calls where there is "not too much calculation inside the parentheses", i.e. where a maximum of MAX_WILDCARDS instructions lie between them.I extended ByteCodePatternDetector, my pattern being the following:
I later make sure the byte codes for the Load and Invoke operation are the correct ones, and compare the class and field name I retrieve from
match.getBindingSet().lookup(...)
to make sure it isSystem.out
being called.I have seen the existing answers, and I consider changing my solution. I just added this for the sake of completeness.
Your task is a bit more complicated than it seems. A simple case:
Is translated into a simple bytecode as well:
However if you are trying to print anything except simple constant/known value it gets harder:
Will be translated to:
But wait, it can get worse:
What?
StringBuilder
: ?Interestingly, the original code was translated to (which is a known Java 5 (?) optimization):
Solution
So indeed - you will need a stack and you'll have to keep track of every push/pop operation as defined in bytecode instruction. A lot of work for such a simple task...
But if you go this path, solving the problem is quite simple: when you encounter INVOKEVIRTUAL the top of the stack should contain some value and the value below the top should be a "
java/lang/System.out
".That being said I'm 100% sure Findbugs already implemented this and probably you can use some FindBugs API to make your life easier.