[ 
https://issues.apache.org/jira/browse/GROOVY-12251?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12251:
-------------------------------
    Description: 
The {{NullChecker}} type-checking extension catches _assigning_ null to a 
{{@NonNull}} field, but a {{@NonNull}} field that is simply *never initialized* 
passes silently — and is then trusted as non-null at every dereference, so the 
one field guaranteed to hold null is the one the checker never warns about. 
Definite-initialization checking of non-null fields is a core check in 
comparable tools (NullAway, the Checker Framework's nullness checker).

This issue adds, in flow-sensitive mode ({{NullChecker(strict: true)}}), a 
check that each explicitly-annotated {{@NonNull}} instance field is definitely 
initialized in one of the accepted ways:

* at its declaration ({{@NonNull String name = 'unknown'}})
* in an instance initializer block
* by *every* declared constructor — a constructor delegating via {{this(...)}} 
relies on its delegate

A class with no declared constructor reports {{@NonNull field 'name' is not 
initialized}}; a class where some but not all constructors assign the field 
reports {{@NonNull field 'name' is not initialized by all constructors}}.

Example:

{code:groovy}
import groovy.transform.TypeChecked

@TypeChecked(extensions = 'groovy.typecheckers.NullChecker(strict: true)')
class Library {
    @NonNull String catalog                  // never assigned
    Library(String catalog) { }              // oops — forgot this.catalog = 
catalog
}
{code}

{noformat}
[Static type checking] - @NonNull field 'catalog' is not initialized by all 
constructors
{noformat}

Deliberate scoping, to keep the check noise-free for idiomatic Groovy:

* *Strict mode only.* The lenient checker's contract is incremental adoption; 
enabling this there would immediately flag existing {{@NonNull}} property 
classes constructed via named arguments ({{new Foo(name: 'x')}} initializes the 
property _after_ default construction). Strict mode is documented as the "full 
coverage" tier, which is where a definite-initialization guarantee belongs. 
Promoting it to lenient mode later remains open for discussion.
* *Explicitly-annotated fields only.* Fields that are merely non-null by 
default (under {{@NullMarked}} / {{@NonNullByDefault}} without their own 
annotation) are not checked, for the same named-argument-bean reason.
* *Exclusions:* {{@MonotonicNonNull}} and {{@Lazy}} fields (deliberately 
initialized late), primitives, and interfaces. Static fields are deferred for 
now.
* *Not path-sensitive:* an assignment anywhere in a constructor counts; 
per-path definite-assignment analysis could tighten this later.


> NullChecker: check @NonNull fields are definitely initialized (strict mode)
> ---------------------------------------------------------------------------
>
>                 Key: GROOVY-12251
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12251
>             Project: Groovy
>          Issue Type: Improvement
>          Components: groovy-typecheckers
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> The {{NullChecker}} type-checking extension catches _assigning_ null to a 
> {{@NonNull}} field, but a {{@NonNull}} field that is simply *never 
> initialized* passes silently — and is then trusted as non-null at every 
> dereference, so the one field guaranteed to hold null is the one the checker 
> never warns about. Definite-initialization checking of non-null fields is a 
> core check in comparable tools (NullAway, the Checker Framework's nullness 
> checker).
> This issue adds, in flow-sensitive mode ({{NullChecker(strict: true)}}), a 
> check that each explicitly-annotated {{@NonNull}} instance field is 
> definitely initialized in one of the accepted ways:
> * at its declaration ({{@NonNull String name = 'unknown'}})
> * in an instance initializer block
> * by *every* declared constructor — a constructor delegating via 
> {{this(...)}} relies on its delegate
> A class with no declared constructor reports {{@NonNull field 'name' is not 
> initialized}}; a class where some but not all constructors assign the field 
> reports {{@NonNull field 'name' is not initialized by all constructors}}.
> Example:
> {code:groovy}
> import groovy.transform.TypeChecked
> @TypeChecked(extensions = 'groovy.typecheckers.NullChecker(strict: true)')
> class Library {
>     @NonNull String catalog                  // never assigned
>     Library(String catalog) { }              // oops — forgot this.catalog = 
> catalog
> }
> {code}
> {noformat}
> [Static type checking] - @NonNull field 'catalog' is not initialized by all 
> constructors
> {noformat}
> Deliberate scoping, to keep the check noise-free for idiomatic Groovy:
> * *Strict mode only.* The lenient checker's contract is incremental adoption; 
> enabling this there would immediately flag existing {{@NonNull}} property 
> classes constructed via named arguments ({{new Foo(name: 'x')}} initializes 
> the property _after_ default construction). Strict mode is documented as the 
> "full coverage" tier, which is where a definite-initialization guarantee 
> belongs. Promoting it to lenient mode later remains open for discussion.
> * *Explicitly-annotated fields only.* Fields that are merely non-null by 
> default (under {{@NullMarked}} / {{@NonNullByDefault}} without their own 
> annotation) are not checked, for the same named-argument-bean reason.
> * *Exclusions:* {{@MonotonicNonNull}} and {{@Lazy}} fields (deliberately 
> initialized late), primitives, and interfaces. Static fields are deferred for 
> now.
> * *Not path-sensitive:* an assignment anywhere in a constructor counts; 
> per-path definite-assignment analysis could tighten this later.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to