使用 Findbugs 编写检测器以搜索“System.out.println”的使用
Posted
技术标签:
【中文标题】使用 Findbugs 编写检测器以搜索“System.out.println”的使用【英文标题】:Writing a detector to search for uses of "System.out.println" using Findbugs 【发布时间】:2011-12-03 23:48:13 【问题描述】:我正在尝试编写一个错误检测器来使用 Findbugs 查找方法调用“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));
【问题讨论】:
必须在字节码上运行,还是可以在源文件上运行? 抱歉,编辑了帖子,我忘了指明我使用的是 Findbugs(因此它必须在字节码上运行)。谢谢。 好的。因为 PMD 对 pmd.sourceforge.net/rules/logging-java.html 有完全正确的规则 谢谢,但是我很想知道探测器在 Findbugs 中的外观,因为有了这个“简单”示例,我将能够处理更复杂的示例。 【参考方案1】:我发现,对于我的用例,确定使用 System.out 或 System.err 就足够了 - 在 99% 的情况下,它们将用于调用 . print 或 .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】:您的任务比看起来要复杂一些。一个简单的案例:
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 让您的生活更轻松。
【讨论】:
我想确认我之前在这里检查过这样的检测器:findbugs.sourceforge.net/bugDescriptions.html 并且还编写了一个包含“System.out.println”的测试用例,它没有被标记为漏洞。我知道问题会根据正在打印的字符串而增长。我可以使用以下方法弹出堆栈顶部: OpcodeStack.Item item = stack.getStackItem(0);但是,将其与使用 GETSTATIC 推入堆栈的内容进行比较时,我遇到了一些困难。感谢您的详细回复。【参考方案3】:使用类 OpcodeStack。
当您看到 GETSTATIC 并意识到您已经“出局”时,将生成的 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,你就知道你处于报告错误的状态。
【讨论】:
【参考方案4】:我过去提出的一个解决方案发现 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
被调用。
我已经看到了现有的答案,我正在考虑改变我的解决方案。为了完整起见,我只是添加了这个。
【讨论】:
以上是关于使用 Findbugs 编写检测器以搜索“System.out.println”的使用的主要内容,如果未能解决你的问题,请参考以下文章
如何编写自定义的 gradle 任务以不忽略 Findbugs 违规但在分析完成后失败
IDEAL葵花宝典:java代码开发规范插件 FindBugs-IDEA