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

Map.merge nullness annotations are unsound #3046

Closed
cpovirk opened this issue Jan 13, 2020 · 2 comments
Closed

Map.merge nullness annotations are unsound #3046

cpovirk opened this issue Jan 13, 2020 · 2 comments

Comments

@cpovirk
Copy link
Contributor

cpovirk commented Jan 13, 2020

The problem is that the stubs permit the merge function to return null but don't declare that merge itself can then return that to the user:

default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {

$ cat Merge.java 
import java.util.HashMap;
import java.util.Map;

class Merge {
  public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    map.put("k", "v");
    map.merge("k", "v", (a, b) -> null).toString();
  }
}
$ checker/bin/javac -processor org.checkerframework.checker.nullness.NullnessChecker Merge.java && java Merge
Exception in thread "main" java.lang.NullPointerException
  at Merge.main(Merge.java:8)

The fix would be either:

  • Require remappingFunction to return a V (not @Nullable V). This reduces expressiveness, but my guess is that users rarely use the ability to return null. (For what it's worth, I didn't notice this bug from actually returning null from a merge function. I just happened to notice it when looking at the stub for Map.)
  • Change the return type of merge to @Nullable V. This preserves expressiveness but may inconvenience the average caller of merge. (This is of course somewhat like Map.get. However, @KeyFor can't help much here. And it would be mildly unfortunate for users to pass a non-null value and a function that never returns null and yet have to handle null.)

I don't believe @PolyNull is a solution here, at least not by itself: If the user has an empty Map<String, @Nullable String>, then map.merge("k", null, someFunction) will return null, even if someFunction never returns null. To use @PolyNull, we'd need to also change the value parameter's type to @NonNull V.

Source file and -version -verbose -AprintAllQualifiers output attached.

@cpovirk
Copy link
Contributor Author

cpovirk commented Jan 13, 2020

It looks like the intention was to avoid having to make the return type @Nullable. But as noted, I don't think it's panned out :(

@mernst
Copy link
Member

mernst commented Mar 11, 2020

Thanks very much for the report, Chris. Sorry for taking so long to address it.

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

No branches or pull requests

2 participants