Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullnessAnalysis treats the receiver object of a method invocation as non-null #1929

Closed
thebesttv opened this issue Nov 20, 2022 · 4 comments

Comments

@thebesttv
Copy link
Contributor

Describe the bug

NullnessAnalysis treats the receiver object of a method invocation as non-null even if the object may be null, causing false negatives. For example, the method:

    void possibleNullOnSplit(int n) {
        Object a = null;
        if (n > 0) {
            a = "notNull";
        }
        Class c = a.getClass();
    }

has IR like this:

    void possibleNullOnSplit(int)
    {
        NullDeref2 this;
        int n;
        java.lang.Object a, temp$0;
        java.lang.Class c, temp$1;
        this := @this: NullDeref2;
        n := @parameter0: int;
        a = null;
        if n > 0 goto label1;
        goto label2;
     label1:
        nop;
        temp$0 = "notNull";
        a = temp$0;
     label2:
        nop;
        temp$1 = virtualinvoke a.<java.lang.Object: java.lang.Class getClass()>();
        c = temp$1;
        return;
    }

The local variable a is neither always-null nor always-nonnull before statement temp$1 = virtualinvoke a.<java.lang.Object: java.lang.Class getClass()>();. But after that statement, a becomes always-nonnull.

This is caused by flowThrough function in NullnessAnalysis simply setting the base to NON_NULL:

  protected void flowThrough(AnalysisInfo in, Unit u, List<AnalysisInfo> fallOut, List<AnalysisInfo> branchOuts) {
    // ...
    // for invoke expr, set the receiver object to non-null, if there is one
    if (s.containsInvokeExpr()) {
      handleInvokeExpr(s.getInvokeExpr(), out);
    } // ...
  }

  private void handleInvokeExpr(InvokeExpr invokeExpr, AnalysisInfo out) {
    if (invokeExpr instanceof InstanceInvokeExpr) {
      InstanceInvokeExpr instanceInvokeExpr = (InstanceInvokeExpr) invokeExpr;
      // here we know that the receiver must point to an object
      out.put(instanceInvokeExpr.getBase(), NON_NULL);
    }
  }

Input file

The input file is soot-null-dereference.zip, a Gradle project mainly composed of src/main/java/thebesttv/Main.java and src/main/resources/test/NullDeref2.java.

To reproduce

Unzip the input file and run ./gradlew run. At the end of the output is:

  nop
    Null:     []
    Non-null: [this, temp$0]
  temp$1 = virtualinvoke a.<java.lang.Object: java.lang.Class getClass()>()
    Null:     []
    Non-null: [this, a, temp$0]
  c = temp$1
    Null:     []
    Non-null: [this, a, temp$0]
  return

You can see the local variable a becomes always-nonnull after the statement temp$1 = virtualinvoke a.<java.lang.Object: java.lang.Class getClass()>();.

Expected behavior

The local variable a is neither always-null nor always-nonnull after the statement.

@patricklam
Copy link
Contributor

patricklam commented Nov 20, 2022 via email

@thebesttv
Copy link
Contributor Author

Thanks a lot for you reply, that's very helpful!

I have just another question: for some code

a = mayReturnNull();
a.f1();
a.f2();

At statement a = mayReturnNull();, if a is neither always-null nor always-nonnull, then I will report a.f1() as a possible NPE. But is it possible to also report a.f2() as NPE?

I assume, from your reply, only a.f1() can be reported, and a.f2() can not be reported since a is always-nonnull.

@patricklam
Copy link
Contributor

patricklam commented Nov 20, 2022 via email

@thebesttv
Copy link
Contributor Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants