编写检测器来搜索“System.out.println”的使用使用 Findbugs

发布于 2024-12-08 19:08:48 字数 1639 浏览 3 评论 0原文

我正在尝试编写一个错误检测器来使用 Findbugs 查找方法调用“System.out.println”的实例。

我知道字节码中的“System.out.println”被编译为对 GETSTATIC 的调用, 将“System.out”压入堆栈。对 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)); 
                            } 
                    } 
            } 

    }

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

    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

风筝在阴天搁浅。 2024-12-15 19:08:48

我发现,对于我的用例,足以确定是否使用了 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);
    }

}

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.

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

}
明明#如月 2024-12-15 19:08:48

您的任务比看起来要复杂一些。一个简单的情况:

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()
  );

解决方案

所以确实 - 您将需要一个堆栈,并且您必须跟踪每次推送/弹出字节码指令中定义的操作。对于这样一个简单的任务需要做很多工作......

但是如果你走这条路,解决问题非常简单:当你遇到 INVOKEVIRTUAL 时,堆栈顶部应该包含一些值,顶部下面的值应该是一个“<代码>java/lang/System.out”。

话虽这么说,我 100% 确定 Findbugs 已经实现了这一点,也许您可​​以使用一些 FindBugs API 来让您的生活更轻松。

Your task is a bit more complicated than it seems. A simple case:

System.out.println("abc");

Is translated into a simple bytecode as well:

getstatic   #2; //java/lang/System.out
ldc #3; //String abc
invokevirtual   #4; //Calling java/io/PrintStream.println(String)

However if you are trying to print anything except simple constant/known value it gets harder:

int x = 42;
System.out.println(x + 17);

Will be translated to:

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)

But wait, it can get worse:

System.out.println("x times 27 is " + x * 27);

What? 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)

Interestingly, the original code was translated to (which is a known Java 5 (?) optimization):

System.out.println(
  new StringBuilder().
    append("x times 27 is ").
    append(x * 27).
    toString()
  );

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.

忘年祭陌 2024-12-15 19:08:48

使用 OpcodeStack 类。

当您看到 GETSTATIC 并意识到已“out”时,请将生成的 OpcodeStack.Item 上的用户值设置为 Boolean.TRUE。为此,请在处理 println 调用时执行此操作

try {
     //process opcodes
} finally {
    super.sawOpcode(seen);
    if (pseudocode-saw System.out.println) {
        OpcodeStack.Item item = stack.getStackItem(0);
        item.setUserValue(Boolean.TRUE);
}

,查看 tos,如果用户值设置为 Boolean.TRUE,则您知道您处于报告错误的状态。

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

try {
     //process opcodes
} finally {
    super.sawOpcode(seen);
    if (pseudocode-saw System.out.println) {
        OpcodeStack.Item item = stack.getStackItem(0);
        item.setUserValue(Boolean.TRUE);
}

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.

琉璃繁缕 2024-12-15 19:08:48

我过去提出的一种解决方案发现 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));

稍后我确保 Load 和 Invoke 操作的字节码是正确的,并比较从 match.getBindingSet().lookup() 检索的类和字段名称。 ..) 以确保正在调用 System.out

我已经看到了现有的答案,并且我考虑更改我的解决方案。为了完整起见,我添加了这个。

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:

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));

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 is System.out being called.

I have seen the existing answers, and I consider changing my solution. I just added this for the sake of completeness.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文