typetools / checker-framework

Pluggable type-checking for Java
http://checkerframework.org/
Other
1.01k stars 350 forks source link

Version of @Pure that persists through side effects #1740

Open metalhead8816 opened 6 years ago

metalhead8816 commented 6 years ago

Checker output and YourClassName.java reproduction case attached.

This is fairly common for use of nano protos where initialization might occur in between copying of data based on nullness of a field.

A workaround for now is to use a local variable before the initialization of mutableData and then assign after initialization based on the local variable.

nullness-issue-pure.zip

wmdietl commented 6 years ago

Thanks for the report!

At first I thought that making the constructor for MutableData @Pure would solve the issue. However, the problem is something completely different: the field update between the check and the use of the pure method.

Take this example:

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.Pure;

class YourClassNameHere {
  int i;

  void foo(PureData pureData) {
    if (pureData.value() != null) {
      this.i = 4;

      @NonNull Object o = pureData.value();
    }
  }

  interface PureData {
      @Pure
      @Nullable Object value();
  }
}

This results in the same message:

YourClassNameHere.java:13: error: [assignment.type.incompatible] incompatible types in assignment.
      @NonNull Object o = pureData.value();
                                        ^
  found   : @Initialized @Nullable Object
  required: @UnknownInitialization @NonNull Object
1 error

A pure method is only guaranteed to return the same value if the heap didn't change. The update to field i could influence the result of PureData.value(), for example, an implementation of that method could depend on the value of i to decide whether to return null or not.

By storing the result of pureData.value() in a local variable you ensure that the method is only invoked once and the result can't change.

Please let us know whether this explains the behavior.

metalhead8816 commented 6 years ago

Hi Werner, That definitely explains the behavior, although I would think this comes up quite a bit especially when combined with @AutoValue (which my actual code hitting this is). Is there a different annotation to state that this value will not change if called multiple times since it is a simple getter?

mernst commented 6 years ago

Is there a different annotation to state that this value will not change if called multiple times since it is a simple getter?

Side effects can change the result of a getter, by changing the private variable that it returns. So you would need a much stronger requirement than "is a simple getter".

Are you looking for an annotation that is like @Pure, but indicates that the method returns the same value throughout the program and no side effect can affect its value?

metalhead8816 commented 6 years ago

Yes, an annotation for denoting this method returns an immutable piece of data. AutoValue already has strong guarantees here, but if each of the value methods could be annotated with @Immutable or something like that I think it could solve this.

mernst commented 6 years ago

@Immutable would not be the right annotation. (The fact that a routine returns an immutable datum is orthogonal to whether the routine returns the same value every time the routine is called.)

metalhead8816 commented 6 years ago

OK, happy for a different annotation, but I hope my point has come across. Maybe if the class is considered an immutable POJO than each of its methods should be considered to never return different data based on any heap updates.

So maybe the annotations should be placed at the class level to signify this for each method. As you mentioned a method level annotation makes less sense unless it also signifies the underlying data backing the return value in the class isn't changing.

Some ideas for method annotations: @NoVariation @NoCalculation

Maybe if class is @Pojo and @Immutable this applies to all methods.

mernst commented 6 years ago

I agree this would be valuable. My main point is that we need to be careful about the semantics because it would be easy to propose an unsound rule, and about the naming so that programmers can understand it. Thanks for the suggestions!

mernst commented 6 years ago

As a summary, there is no bug. The Checker Framework is working as intended. However, there is a feature request to make the Checker Framework more expressive and issue fewer false positive warnings.

I see three ways we could deal with this problem:

I'm leaving this issue open because we would like to solve it. Anyone who wants to can contribute a pull request that implements one of the possible fixes. In the meanwhile, the existence of a workaround means this is not a blocking issue.