Bug 237236 - [compiler][null] extending nonnull analysis to final fields
Summary: [compiler][null] extending nonnull analysis to final fields
Status: VERIFIED WONTFIX
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.4   Edit
Hardware: PC Windows XP
: P5 enhancement with 1 vote (vote)
Target Milestone: 4.4 M5   Edit
Assignee: Stephan Herrmann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 388184 405036 428228 (view as bug list)
Depends on:
Blocks:
 
Reported: 2008-06-16 04:04 EDT by utilisateur_768 CLA
Modified: 2020-02-19 09:57 EST (History)
16 users (show)

See Also:
stephan.herrmann: review-


Attachments
proposed fix v1.0 + regression tests (25.79 KB, patch)
2012-01-23 06:28 EST, Ayushman Jain CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description utilisateur_768 CLA 2008-06-16 04:04:41 EDT
Build ID: I20080523-0100

An initialized final field cannot be assigned null somewhere, so the null analysis should consider it as NON_NULL. 


class A {
  final ArrayList l = new ArrayList();

  void dump(A a) {
    // non-null comparison is always true
    if (a.l != null) {
      for (Iterator it = a.l.iterator(); it.hasNext(); )
        System.out.println("hey " + it.next());
    }
  }
}
Comment 1 NoName CLA 2008-06-17 08:51:15 EDT
Why isn't it a good feature ?
Comment 2 Maxime Daniel CLA 2008-06-18 02:38:14 EDT
No one says this feature would not be desirable, and I apologize if I gave that impression. If you look at the NullReferenceTest class, you will see that this was considered at the beginning of the implementation, but other features got a higher priority and fields were dropped from the scope of our first implementation. As always, we only implement part of the desirable features, hopefully focusing on the most desirable ones.
Furthermore, we currently have no concrete plans to push the envelope of null-analysis features in JDT, hence I downgraded the priority to P5, as I did for other null-related bugs.
We may or may not open a 'null-analysis effort' at some future point, but the decision is not with me. If an effort is launched, the null-analysis related bugs will be considered and their relative priorities will be reviewed.
Comment 3 utilisateur_768 CLA 2008-06-18 02:53:18 EDT
Thanks a lot for the explanation. I'll wait for the 'null-analysis effort' :)
Comment 4 Maxime Daniel CLA 2008-06-18 03:47:34 EDT
You're welcome.
Comment 5 Stephan Herrmann CLA 2011-02-28 07:28:18 EST
This was considered in bug 247564 but dropped as of bug 247564 comment 16.

Ayush, do we want to
a) close this as WONTFIX, or
b) extend bug 247564 to final fields with an initializer?

Also relevant: bug 331649.
Comment 6 Ayushman Jain CLA 2011-03-01 07:29:32 EST
(In reply to comment #5)
> Ayush, do we want to
> a) close this as WONTFIX, or
> b) extend bug 247564 to final fields with an initializer?

Lets keep this open for now. Once the issues in bug 247564 are tackled, we can look into this.
Comment 7 Srikanth Sankaran CLA 2012-01-21 20:29:32 EST
Ayush & Stephan,

Where has the resolution of bug 247564 left us with vis-à-vis
the current one ?
Comment 8 Ayushman Jain CLA 2012-01-22 23:49:29 EST
(In reply to comment #7)
> Ayush & Stephan,
> 
> Where has the resolution of bug 247564 left us with vis-à-vis
> the current one ?

This was not considered because of  bug 247564 comment 16, as pointed out above. We can perhaps make a special case for final fields initialized as they are declared and treat them like static finals thereafter. Will consider for M6.

Stephan, any thoughts? This shouldn't be difficult if we add this field's position also to the mask that we create for constant fields.
Comment 9 Ayushman Jain CLA 2012-01-23 05:23:57 EST
This may be ok for unqualified access and 'this' reference, but we should not support final fields accessed from an object. Here's one example to show why
public class X {
  final Object o2 = null;
  public void someMethod(){}
  public void foo(X x) {
	 if (x.o2 == null) {
	 }
	 x.o2.toString();
	 if (this.o2 == null) {
	 }
	 this.o2.toString();
  }
}

Here x.o2.toString() will mark o2 as cannot be null further in the flow. This will pollute the null status of this.o2 as well, since the flowInfo knows only a single o2.
Comment 10 Ayushman Jain CLA 2012-01-23 06:28:36 EST
Created attachment 209904 [details]
proposed fix v1.0 + regression tests

This fix piggybacks on the treatment of static final fields. We record final fields initialized at the time of declaration in the constant fields mask. And then during marking of the null status, we make sure we handle both initialized finals and static finals at par. The only other challenge was to make sure access of final field using 'this' reference also works, and works only for a final field that was initialized when declared. For this, we check if the field was set in the constantFieldsMask earlier, and then return its binding.
Comment 11 Srikanth Sankaran CLA 2012-01-28 20:44:54 EST
(In reply to comment #10)
> Created attachment 209904 [details]
> proposed fix v1.0 + regression tests

Let us defer this until we have had some more discussions.
Comment 12 Srikanth Sankaran CLA 2012-01-31 03:56:21 EST
(In reply to comment #11)
> (In reply to comment #10)
> > Created attachment 209904 [details]
> > proposed fix v1.0 + regression tests
> 
> Let us defer this until we have had some more discussions.

Now that we have moved out some follow up items on null field
analysis to future release, we can go ahead with a solution
that fits well in the current scheme of things.

Stephan, can you please review this for inclusion in M6. TIA.
Comment 13 Srikanth Sankaran CLA 2012-02-07 02:23:21 EST
(In reply to comment #12)
> (In reply to comment #11)
> > (In reply to comment #10)
> > > Created attachment 209904 [details]
> > > proposed fix v1.0 + regression tests
> > 
> > Let us defer this until we have had some more discussions.
> 
> Now that we have moved out some follow up items on null field
> analysis to future release, we can go ahead with a solution
> that fits well in the current scheme of things.
> 
> Stephan, can you please review this for inclusion in M6. TIA.

See https://bugs.eclipse.org/bugs/show_bug.cgi?id=370787 for a
problem with static final fields. I didn't check whether the
current patch would have the equivalent problem for instance
final fields. If it does, we should hold on it till we have
a good story on bug 370787
Comment 14 Stephan Herrmann CLA 2012-02-12 14:07:10 EST
(In reply to comment #13)
> (In reply to comment #12)
> > Stephan, can you please review this for inclusion in M6. TIA.
> 
> See https://bugs.eclipse.org/bugs/show_bug.cgi?id=370787 for a
> problem with static final fields. I didn't check whether the
> current patch would have the equivalent problem for instance
> final fields. If it does, we should hold on it till we have
> a good story on bug 370787

Ayush, have you checked whether this bug must be updated after bug 370787, or is the last patch still valid under those considerations?
Comment 15 Stephan Herrmann CLA 2012-09-04 17:49:58 EDT
*** Bug 388184 has been marked as a duplicate of this bug. ***
Comment 16 Stephan Herrmann CLA 2013-04-06 08:24:18 EDT
*** Bug 405036 has been marked as a duplicate of this bug. ***
Comment 17 Osvaldo Pinali Doederlein CLA 2013-09-03 18:57:43 EDT
The current analysis fails pretty bad, example:

import javax.annotation.Nullable;
class BadNull {
  private final @Nullable String x = "k";
  public String theBad1() {
    return x == null ? "" : x.toString();
  }
  public String theBad2() {
    if (x == null) {
      return "";
    } else {
      return x.toString();
    }
  }
  public String theUgly() {
    if (x != null) {
      return x.toString();
    }
    return "";
  }
}

(With Eclipse configured to use the javax annotations). Will report potential null access in all three method above. This happens even when the field is final and initialized to a non-null value.
Comment 18 Osvaldo Pinali Doederlein CLA 2013-09-04 18:17:26 EDT
Another analysis failure:

@ParametersAreNonnullByDefault
...
  public void x(Long l) {}
  public long z() { return 0L; }
  public void y() { x(z()); }

Null type safety: The expression of type long needs unchecked conversion to conform to '@Nonnull Long' 

which is bad, as autoboxing will never produce a null Long object. Likely the problem is that autoboxing is desugared to an API call which is not annotated as @Nonnull.
Comment 19 Stephan Herrmann CLA 2013-09-05 06:16:51 EDT
(In reply to Osvaldo Pinali Doederlein from comment #18)
> Another analysis failure:
> 
> @ParametersAreNonnullByDefault
> ...
>   public void x(Long l) {}
>   public long z() { return 0L; }
>   public void y() { x(z()); }
> 
> Null type safety: The expression of type long needs unchecked conversion to
> conform to '@Nonnull Long' 
> 
> which is bad, as autoboxing will never produce a null Long object. Likely
> the problem is that autoboxing is desugared to an API call which is not
> annotated as @Nonnull.

This looks like a duplicate of bug 407414, I'll make a note in that bug.
Comment 20 Stephan Herrmann CLA 2013-09-05 07:37:34 EDT
I guess this bug deserves a fresh assessment:

(A) All final fields with an initializer can be analysed and if found to be non-null
we can store this information for further analysis.

Potential risks: dependencies between different fields/initializers should already be
handled correctly by prohibition of forward references in initializers, but if such
dependency is hidden inside (getter) methods, analysis results may be incorrect:
  public class T {
    final Object f1 = getF2();
    final Object f2 = getF1();
    @NonNull Object getF1() { return this.f1; }
    @NonNull Object getF2() { return this.f2; }
    public static void main(String[] args) {
      System.out.println(new T().getF1().toString());
    }
  }
This would be accepted by the compiler but throw NPE.
This is the same risk as assuming that each access to a final field finds a validly
initialized value, which Java cannot fully guarantee.

Hints at this danger could be detected by checking whether any field initializers
leak "this" (as a receiver or parameter in any method call).

(B) Final instance fields initialized in constructors are basically equivalent to (A)
given that one is translated into the other anyway.

(C) Final static fields without initializer add the option that initialization is
spread over multiple static initializer blocks, but I don't see a semantic difference
between multiple initializers and one big merged initializer.

It seems in bug 370787 we concluded that static final fields not initialized in place
are specifically dangerous. From today's perspective I'd say this particular danger
- arises in *any* situation where the required initialization phase calls methods
- threatens not only null analysis but also the assumed guarantees of final.

In one regard the mentioned leaking is more difficult to detect for static fields:
*Any* method call could transitively lead to reading the still uninitialized static
field, since no 'this' reference needs to be passed.


Several approaches exist in research for making object initialization safe.
If I understand those approaches correctly, their common key point is that any method
transitively reachable from any initialization code has to obey specific constraints,
viz. don't assume the object to be fully initialized.
I'm mentioning this, because this would put the blame on those methods like getF1()
above, *not* on the field.


With these considerations, I suggest we:
- apply the same treatment to all fields in (A) to (C) above
- use the nullness information from field initialization if null annotations are
  enabled, and henceforth treat fields with a non-null initialization as @NonNull.


Looking beyond, we could in some future add a warning regarding the mentioned leaks.
Method calls inside instance initialization code which leak "this" are well detectable.
This warning can only give an over estimation of potentially dangerous situations.
If that warning is seen frequently, using one or a few more annotations for specifying
object initialization will be recommended.
A similar warning for static initializers would be overkill (unless we had a "@Pure"
annotation...), i.e., reporting more false positives than relevant warnings.


I'm tentatively marking for 4.4 M3.


I think fixing this bug would make the analysis more consistent.
Let me know if anyone has concerns with my proposal.
Comment 21 Stephan Herrmann CLA 2013-10-24 06:59:43 EDT
I have more urgent tasks on my plate right till the end of this year.
Hopefully I'll find some time early next year.
Comment 22 Stephan Herrmann CLA 2014-01-18 13:34:02 EST
Re-reading this bug for the umptieth time, I seem to see a roadblock: without bytecode analysis, we'll never get the same results for a class read from binary as when the source is parsed. This would imply that incremental builds get worse results than full builds, which is unacceptable.

Actually, that's one of the reasons why we started work on null annotations in the first place: to make null assumptions explicit, without the need to look inside.

Users wanting the effect requested here are encouraged to mark those fields as @NonNull.
Comment 23 Manoj N Palat CLA 2014-01-21 04:02:00 EST
Verfied (that null analysis does not address this unless @NonNull specified as per comment 22) for Eclipse 4.4M5 Version: Luna Build id: I20140120-2000
Comment 24 Stephan Herrmann CLA 2014-02-16 12:34:43 EST
*** Bug 428228 has been marked as a duplicate of this bug. ***
Comment 25 Aaron Digulla CLA 2014-03-28 06:18:31 EDT
Not sure if this is related to this bug or not: object.getClass() never returns null.

Is it possible to add a list of "known safe methods" to the null analysis?
Comment 26 Volker Berlin CLA 2014-03-28 18:11:12 EDT
(In reply to Aaron Digulla from comment #25)
> Not sure if this is related to this bug or not: object.getClass() never
> returns null.
> 
> Is it possible to add a list of "known safe methods" to the null analysis?

This is more related to bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=331651
Comment 27 Ludovic Pollet CLA 2014-03-31 10:53:58 EDT
(In reply to Stephan Herrmann from comment #22)
> Re-reading this bug for the umptieth time, I seem to see a roadblock:
> without bytecode analysis, we'll never get the same results for a class read
> from binary as when the source is parsed. This would imply that incremental
> builds get worse results than full builds, which is unacceptable.
> 
> Actually, that's one of the reasons why we started work on null annotations
> in the first place: to make null assumptions explicit, without the need to
> look inside.
> 
> Users wanting the effect requested here are encouraged to mark those fields
> as @NonNull.

What about adding the @NonNull annotations that are infered for final fields into the generated bytecode ?
Comment 28 Stephan Herrmann CLA 2014-03-31 13:23:00 EDT
(In reply to Ludovic Pollet from comment #27)
> What about adding the @NonNull annotations that are infered for final fields
> into the generated bytecode ?

That breaks the contract that our compiler generates exactly those bytecodes as are specified by the specification. We had this discussion regarding NonNullByDefault which initially I materialized into the bytecode, but this was voted down by the project leads. One argument was that when consuming an existing class file it should not matter, whether it was created by our compiler or by javac.
Comment 29 Stephan Herrmann CLA 2016-12-11 13:15:35 EST
*** Bug 509039 has been marked as a duplicate of this bug. ***