Hi,

I'll give my two cents here.

I'm not 100% sure that the 1-5% of bugs are as severe as other types of bugs. Yes, throwing NPEs at user is not very polite. On the other hand, many of these actually boil down to user errors. Then we might ask what a correct solution would be. If we manage to figure out what the actual problem is and tell user what specifically is missing or going wrong, that would be just awesome. On the other hand, if a tool used for avoiding "unexpected" NPEs forces us to code

   Object value = Objects.requireNonNull(myNullableObject); // or similar using Preconditions
   value.doStuff();

instead of just

  myNullableObject.doStuff()

what we actually did, is a) made a framework happy, and b) changed a line at which NPE is thrown by 1 (and yes, internally prevented JVM from thrown SIGSEGV at itself, but that is deeply technical thing). Nothing changed semantically, from user perspective.

Now, given that the framework significantly rises compile time (due to all the checks), causes many "bugs" being reported by static code analysis tools (because the framework adds @Nonnull default annotations everywhere, even when Beam's code actually counts with nullability of a field), and given how much we currently suppress these checks ($ git grep BEAM-10402 | wc -l -> 1981), I'd say this deserves a deeper discussion.

 Jan


On 1/20/21 10:48 PM, Kenneth Knowles wrote:
Yes, completely sound nullability checking has been added to the project via checkerframework, based on a large number of NPE bugs (1-5% depending on how you search, but many other bugs likely attributable to nullness-based design errors) which are extra embarrassing because NPEs have were essentially solved, even in practice for Java, well before Beam existed.

Checker framework is a pluggable type system analysis with some amount of control flow sensitivity. Every value has a type that is either nullable or not, and certain control structures (like checking for null) can alter the type inside a scope. The best way to think about it is to consider every value in the program as either nullable or not, much like you think of every value as either a string or not, and to view method calls as inherently stateful/nondetermistic. This can be challenging in esoteric cases, but usually makes the overall code health better anyhow.

Your example illustrates how problematic the design of the Java language is: the analysis cannot assume that `getDescription` is a pure function, and neither should you. Even if it is aware of boolean-short-circuit it would not be sound to accept this code. There is an annotation for this in the cases where it is true (like proto-generate getters): https://checkerframework.org/api/org/checkerframework/dataflow/qual/Pure.html

The refactor for cases like this is trivial so there isn't a lot of value to thinking too hard about it.

if (statusCode.equals(Code.INVALID_ARGUMENT)
  @Nullable String desc = statusCode.toStatus().getDescription();
  if (desc != null && desc.contains("finalized")) {
    return false;
  }
}

To a casual eye, this may look like a noop change. To the analysis it makes all the difference. And IMO this difference is real. Humans may assume it is a noop and humans would be wrong. So many times when you think/expect/hope that `getXYZ()` is a trivial getter method you later learn that it was tweaked for some odd reason. I believe this code change makes the code better. Suppressing these errors should be exceptionally rare, and never in normal code. It is far better to improve your code than to suppress the issue.

It would be very cool for the proto compiler to annotate enough that new-and-improved type checkers could make things more convenient.

Kenn

On Mon, Jan 11, 2021 at 8:53 PM Reuven Lax <re...@google.com <mailto:re...@google.com>> wrote:

    I can use that trick. However I'm surprised that the check appears
    to be so simplistic.

    For example, the following code triggers the check on
    getDescription().contains(), since getDescription returns a
    Nullable string. However even a simplistic analysis should realize
    that getDescription() was checked for null first! I have a dozen
    or so cases like this, and I question how useful such a simplistic
    check it will be.

    if (statusCode.equals(Code.INVALID_ARGUMENT)
    &&statusCode.toStatus().getDescription() !=null 
&&statusCode.toStatus().getDescription().contains("finalized")) {return false;
    }


    On Mon, Jan 11, 2021 at 8:32 PM Boyuan Zhang <boyu...@google.com
    <mailto:boyu...@google.com>> wrote:

        Yeah it seems like the checker is enabled:
        https://issues.apache.org/jira/browse/BEAM-10402. I used
        @SuppressWarnings({"nullness" )}) to suppress the error when I
        think it's not really a concern.

        On Mon, Jan 11, 2021 at 8:28 PM Reuven Lax <re...@google.com
        <mailto:re...@google.com>> wrote:

            Has extra Nullable checking been enabled in the Beam
            project? I have a PR that was on hold for several months,
            and I'm struggling now with compile failing to complaints
            about assigning something that is nullable to something
            that is not nullable. Even when the immediate control flow
            makes it absolutely impossible for the variable to be null.

            Has something changed here?

            Reuven

Reply via email to