JSpecify: Handle @Nonnull elements in @Nullable content arrays#963
JSpecify: Handle @Nonnull elements in @Nullable content arrays#963msridhar merged 17 commits intouber:masterfrom
Conversation
|
|
||
| public class ArrayIndexElement implements AccessPathElement { | ||
| private final Element javaElement; | ||
| @Nullable final String index; |
There was a problem hiding this comment.
Why is this @Nullable? Also, we shouldn't always use a String here. We want to use something like an Element for a local variable I think.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #963 +/- ##
============================================
+ Coverage 85.88% 85.90% +0.02%
- Complexity 2051 2067 +16
============================================
Files 82 83 +1
Lines 6806 6859 +53
Branches 1312 1323 +11
============================================
+ Hits 5845 5892 +47
- Misses 547 550 +3
- Partials 414 417 +3 ☔ View full report in Codecov by Sentry. |
msridhar
left a comment
There was a problem hiding this comment.
Looking great! I have some comments
| public Element getJavaElement() { | ||
| return this.javaElement; | ||
| } | ||
| public interface AccessPathElement { |
| return false; | ||
| } | ||
| } | ||
| String toString(); |
There was a problem hiding this comment.
I think toString(), equals(), and hashCode() do not need to be declared here
| return this.javaElement; | ||
| } | ||
| public interface AccessPathElement { | ||
| Element getJavaElement(); |
| import javax.annotation.Nullable; | ||
| import javax.lang.model.element.Element; | ||
|
|
||
| public class FieldOrMethodCallElement implements AccessPathElement { |
There was a problem hiding this comment.
Needs Javadoc (you can take it from the previous docs for AccessPathElement)
| @Nullable private final Integer constantIndex; | ||
| @Nullable private final Element variableElement; |
There was a problem hiding this comment.
Rather than having two @Nullable fields here to represent the cases, I'd prefer a single field of type Object named index for simplicity and efficiency. The API can still be type safe (i.e., you can only pass in an Integer or an Element); the fact that the same field is used internally would be a private implementation detail. You can document the expectations in a comment.
| } else if (arrayNode instanceof MethodInvocationNode) { | ||
| return ASTHelpers.getSymbol(((MethodInvocationNode) arrayNode).getTree()); |
There was a problem hiding this comment.
Allowing the array itself to be the result of a method invocation is a bit more risky. Let's remove this case, and handle it later if we decide we need it
| return null; | ||
| } | ||
| } | ||
| result = buildAccessPathRecursive(stripCasts(arrayNode), elements, apContext, mapKey); |
There was a problem hiding this comment.
Why not strip the casts right at line 415?
| ReadableUpdates updates = new ReadableUpdates(); | ||
| setNonnullIfAnalyzeable(updates, node.getArray()); | ||
| Nullness resultNullness; | ||
| Nullness resultNullness = defaultAssumption; |
There was a problem hiding this comment.
Let's not use defaultAssumption here; it's a bit confusing. Instead, add an explicit else at line 814 and set resultNullness to Nullness.NONNULL when we are not in JSpecify mode
There was a problem hiding this comment.
Just get rid of the initializer expression here:
| Nullness resultNullness = defaultAssumption; | |
| Nullness resultNullness; |
| if (isElementNullable) { | ||
| AccessPath arrayAccessPath = AccessPath.getAccessPathForNode(node, state, apContext); | ||
| if (arrayAccessPath != null) { | ||
| @Nullable | ||
| Nullness accessPathNullness = | ||
| input.getRegularStore().getNullnessOfAccessPath(arrayAccessPath); | ||
| if (accessPathNullness == Nullness.NULLABLE) { | ||
| resultNullness = Nullness.NULLABLE; | ||
| } | ||
| } |
|
We should add tests for the following cases:
|
| ReadableUpdates updates = new ReadableUpdates(); | ||
| setNonnullIfAnalyzeable(updates, node.getArray()); | ||
| Nullness resultNullness; | ||
| Nullness resultNullness = defaultAssumption; |
There was a problem hiding this comment.
Just get rid of the initializer expression here:
| Nullness resultNullness = defaultAssumption; | |
| Nullness resultNullness; |
| if (isElementNullable) { | ||
| AccessPath arrayAccessPath = AccessPath.getAccessPathForNode(node, state, apContext); | ||
| if (arrayAccessPath != null) { | ||
| @Nullable |
There was a problem hiding this comment.
This annotation should not be needed
| * @param index The index used to access the array. Must be either an Integer (for constant | ||
| * indices) or an Element (for variable indices). | ||
| */ | ||
| public ArrayIndexElement(Element javaElement, Object index) { |
There was a problem hiding this comment.
We can have the field be of Object object, but we should have two separate constructors, one for integer literals and one for variables / fields, each taking the appropriate type. We shouldn't allow for passing in an arbitrary Object as the index. Alternately (and perhaps better), make the constructor private, and then write two static methods, one for each case, with appropriate names (like makeWithIntegerIndex)
|
您好,邮件已经收到,查阅后会尽快给您回复!
|
| " if (fizz[i]!=null) { ", | ||
| " // field access indexes aren't handled", | ||
| " // BUG: Diagnostic contains: dereferenced expression fizz[i] is @Nullable", | ||
| " fizz[i].toString();", |
There was a problem hiding this comment.
Hrm, I thought this case would be handled? What exact cases do we allow for index expressions again?
There was a problem hiding this comment.
Ah my bad, this should work fine for primitives. Updated this and method invocation case accordingly.
|
I realized this change might have a deeper impact than we were expecting. Right now, the creation of access path elements for array accesses is not gated on the flag for JSpecify mode, and passing the NullAway config into all relevant methods to do that would be kind of painful. But, I believe this means that code like this will pass NullAway now, when it didn't before this change: if (arr[i].f != null) {
arr[i].f.toString();
}@armughan11 can you write a test to confirm this? @lazaroclapp do you see any high-level issues with not reporting warnings for code like the above? It seems to be keeping in the spirit of NullAway, but it does potentially introduce new vectors of unsoundness, e.g.: if (arr[i].f != null) {
arr[j] = null; // overwrites a[i] if i == j
arr[i].f.toString(); // still no warning from NullAway
}Most programs don't code too much directly with arrays, but I wonder if we should do some more performance and sanity testing before landing this. |
|
Just added the test and you're correct @msridhar. The test you mentioned works fine right now, but throws an error without our changes. |
msridhar
left a comment
There was a problem hiding this comment.
Benchmarking didn't detect any perf regressions, and I rebuilt an internal code base with this change and didn't see any new errors or crashes.
I'm still hoping @lazaroclapp can give his feedback before we land this one.
|
Gonna go ahead and merge this; I think we can address any concerns in follow-up PRs |
This MR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [org.tukaani:xz](https://tukaani.org/xz/java.html) ([source](https://github.com/tukaani-project/xz-java)) | compile | minor | `1.9` -> `1.10` | | [org.eclipse:yasson](https://projects.eclipse.org/projects/ee4j.yasson) ([source](https://github.com/eclipse-ee4j/yasson)) | compile | patch | `3.0.3` -> `3.0.4` | | [org.eclipse.parsson:parsson](https://github.com/eclipse-ee4j/parsson) | compile | patch | `1.1.6` -> `1.1.7` | | [com.uber.nullaway:nullaway](https://github.com/uber/NullAway) | | patch | `0.11.0` -> `0.11.1` | | [io.hosuaby:inject-resources-junit-jupiter](https://github.com/hosuaby/inject-resources) | test | patch | `0.3.3` -> `0.3.5` | | [org.codehaus.mojo:exec-maven-plugin](https://www.mojohaus.org/exec-maven-plugin) ([source](https://github.com/mojohaus/exec-maven-plugin)) | build | minor | `3.3.0` -> `3.4.0` | --- ### Release Notes <details> <summary>tukaani-project/xz-java</summary> ### [`v1.10`](https://github.com/tukaani-project/xz-java/releases/tag/v1.10): XZ for Java 1.10 [Compare Source](tukaani-project/xz-java@v1.9...v1.10) </details> <details> <summary>eclipse-ee4j/yasson</summary> ### [`v3.0.4`](eclipse-ee4j/yasson@3.0.3...3.0.4) [Compare Source](eclipse-ee4j/yasson@3.0.3...3.0.4) </details> <details> <summary>eclipse-ee4j/parsson</summary> ### [`v1.1.7`](eclipse-ee4j/parsson@1.1.6...1.1.7) [Compare Source](eclipse-ee4j/parsson@1.1.6...1.1.7) </details> <details> <summary>uber/NullAway</summary> ### [`v0.11.1`](https://github.com/uber/NullAway/blob/HEAD/CHANGELOG.md#Version-0111) [Compare Source](uber/NullAway@v0.11.0...v0.11.1) - Fix issue 1008 ([#​1009](uber/NullAway#1009)) - JSpecify: read upper bound annotations from bytecode and add tests ([#​1004](uber/NullAway#1004)) - Fix crash with suggested suppressions in JSpecify mode ([#​1001](uber/NullAway#1001)) - Update to JSpecify 1.0 and use JSpecify annotations in NullAway code ([#​1000](uber/NullAway#1000)) - Expose [@​EnsuresNonNull](https://github.com/EnsuresNonNull) and [@​RequiresNonNull](https://github.com/RequiresNonNull) in annotations package ([#​999](uber/NullAway#999)) - Don't report initializer warnings on [@​NullUnmarked](https://github.com/NullUnmarked) constructors / methods ([#​997](uber/NullAway#997)) - Strip annotations from MethodSymbol strings ([#​993](uber/NullAway#993)) - JSpecify: fix crashes where declared parameter / return types were raw ([#​989](uber/NullAway#989)) - JSpecify: Handle [@​nullable](https://github.com/nullable) elements for enhanced-for-loops on arrays ([#​986](uber/NullAway#986)) - Features/944 tidy stream nullability propagator ([#​985](uber/NullAway#985)) - Tests for loops over arrays ([#​982](uber/NullAway#982)) - Bug fixes for array subtyping at returns / parameter passing ([#​980](uber/NullAway#980)) - JSpecify: Handle [@​nonnull](https://github.com/nonnull) elements in [@​nullable](https://github.com/nullable) content arrays ([#​963](uber/NullAway#963)) - Don't report [@​nullable](https://github.com/nullable) type argument errors for unmarked classes ([#​958](uber/NullAway#958)) - External Library Models: Adding support for Nullable upper bounds of Generic Type parameters ([#​949](uber/NullAway#949)) - Refactoring / code cleanups: - Test on JDK 22 ([#​992](uber/NullAway#992)) - Add test case for [@​nullable](https://github.com/nullable) Void with override in JSpecify mode ([#​990](uber/NullAway#990)) - Enable UnnecessaryFinal and PreferredInterfaceType EP checks ([#​991](uber/NullAway#991)) - Add missing [@​test](https://github.com/test) annotation ([#​988](uber/NullAway#988)) - Fix typo in variable name ([#​987](uber/NullAway#987)) - Remove AbstractConfig class ([#​974](uber/NullAway#974)) - Fix Javadoc for MethodRef ([#​973](uber/NullAway#973)) - Refactored data clumps with the help of LLMs (research project) ([#​960](uber/NullAway#960)) - Build / CI tooling maintenance: - Various cleanups enabled by bumping minimum Java and Error Prone versions ([#​962](uber/NullAway#962)) - Disable publishing of snapshot builds from CI ([#​967](uber/NullAway#967)) - Update Gradle action usage in CI workflow ([#​969](uber/NullAway#969)) - Update Gradle config to always compile Java code using JDK 17 ([#​971](uber/NullAway#971)) - Update JavaParser to 3.26.0 ([#​970](uber/NullAway#970)) - Reenable JMH benchmarking in a safer manner ([#​975](uber/NullAway#975)) - Updated JMH Benchmark Comment Action ([#​976](uber/NullAway#976)) - Update to Gradle 8.8 ([#​981](uber/NullAway#981)) - Update to Error Prone 2.28.0 ([#​984](uber/NullAway#984)) - Update to Gradle 8.9 ([#​998](uber/NullAway#998)) - Update to WALA 1.6.6 ([#​1003](uber/NullAway#1003)) </details> <details> <summary>hosuaby/inject-resources</summary> ### [`v0.3.5`](hosuaby/inject-resources@v0.3.4...v0.3.5) [Compare Source](hosuaby/inject-resources@v0.3.4...v0.3.5) ### [`v0.3.4`](hosuaby/inject-resources@v0.3.3...v0.3.4) [Compare Source](hosuaby/inject-resources@v0.3.3...v0.3.4) </details> <details> <summary>mojohaus/exec-maven-plugin</summary> ### [`v3.4.0`](https://github.com/mojohaus/exec-maven-plugin/releases/tag/3.4.0) [Compare Source](mojohaus/exec-maven-plugin@3.3.0...3.4.0) <!-- Optional: add a release summary here --> #### 🚀 New features and improvements - Allow `<includePluginDependencies>` to be specified for the exec:exec goal ([#​432](mojohaus/exec-maven-plugin#432)) [@​sebthom](https://github.com/sebthom) #### 🐛 Bug Fixes - Do not get UPPERCASE env vars ([#​427](mojohaus/exec-maven-plugin#427)) [@​wheezil](https://github.com/wheezil) #### 📦 Dependency updates - Bump org.codehaus.mojo:mojo-parent from 82 to 84 ([#​434](mojohaus/exec-maven-plugin#434)) [@​dependabot](https://github.com/dependabot) - Bump org.codehaus.plexus:plexus-xml from 3.0.0 to 3.0.1 ([#​431](mojohaus/exec-maven-plugin#431)) [@​dependabot](https://github.com/dependabot) #### 👻 Maintenance - Remove Log4j 1.2.x from ITs ([#​437](mojohaus/exec-maven-plugin#437)) [@​slawekjaranowski](https://github.com/slawekjaranowski) #### 🔧 Build - Use Maven 3.9.7 and 4.0.0-beta-3 ([#​433](mojohaus/exec-maven-plugin#433)) [@​slawekjaranowski](https://github.com/slawekjaranowski) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This MR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC4yNC4wIiwidXBkYXRlZEluVmVyIjoiMzQuMjQuMCJ9-->
Handles unexpected error cases in JSpecify mode where an index is asserted
@Nonnull(fizz[x]!=null) but the array elements itself could be null (@Nullable String [] fizz).Example
Current Behavior
This throws an error due to dereference of
fizz[i]New Behavior
There should be no error here unless it's outside the block. So only the latter dereference throws up an error in the below case.