Skip to content

Add @DoesNotUnrefineReceiver("modifiability") annotations#276

Merged
mernst merged 17 commits into
typetools:masterfrom
mernst:dnur
May 21, 2026
Merged

Add @DoesNotUnrefineReceiver("modifiability") annotations#276
mernst merged 17 commits into
typetools:masterfrom
mernst:dnur

Conversation

@mernst
Copy link
Copy Markdown
Member

@mernst mernst commented May 20, 2026

Merge with typetools/jdk21u#8

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR updates Checker Framework annotations across core JDK classes, interfaces, collection implementations, concurrent collections, iterators, views, and helper utilities. The changes add @DoesNotUnrefineReceiver("modifiability"), add or move @Pure and @SideEffectFree, and reorder existing nullness-related annotations. Several generic toArray(...) and stream toArray(...) declarations lose @SideEffectFree. No runtime logic, control flow, data structure algorithms, or method parameter and return types are changed.

Possibly related PRs

  • typetools/jdk#275: Both PRs adjust Checker Framework iterator annotations in overlapping provider and iterator implementations such as URL, Charset, and Files.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java (1)

1342-1352: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

@Pure is unsound on methods reading concurrently mutable state; use @SideEffectFree instead.

Methods at lines 1342, 4801, 4990, 5386, 5431, 5476, and 5521 are annotated @Pure but read from volatile/concurrent fields (table, result.get() from AtomicReference). Since @Pure requires both side-effect-freedom and determinism (same return value for same receiver/args/environment), and determinism cannot be guaranteed under concurrent mutation, these methods violate the @Pure contract. Replace with @SideEffectFree, which is already imported and used elsewhere in the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java`
around lines 1342 - 1352, The methods annotated `@Pure` (notably hashCode() and
the other methods that read concurrent/volatile state or call
AtomicReference.get()) are not deterministic under concurrent mutation; replace
the `@Pure` annotation with `@SideEffectFree` for each such method (e.g., hashCode
and the methods referenced at lines 4801, 4990, 5386, 5431, 5476, 5521) so the
annotation accurately reflects side-effect freedom without implying
determinism—ensure you import/use the existing SideEffectFree annotation already
present in the file.
src/java.base/share/classes/java/util/IdentityHashMap.java (1)

795-806: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove @Pure annotation—nextIndex mutates receiver state.

The nextIndex method modifies three receiver fields (indexValid on line 802, lastReturnedIndex on line 803, and index on line 804). The @Pure annotation contradicts this behavior. Use @SideEffectsOnly("this") instead, as indicated by the commented-out annotation on line 794.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/util/IdentityHashMap.java` around lines 795
- 806, Replace the incorrect `@Pure` annotation on method nextIndex of
IdentityHashMapIterator<T> with `@SideEffectsOnly`("this") because nextIndex
mutates receiver state (it updates indexValid, lastReturnedIndex, and index) and
therefore is not pure; locate the nextIndex method and the commented-out
`@SideEffectsOnly`("this") and re-enable that annotation in place of `@Pure` so the
annotation accurately reflects the method’s side effects.
src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java (1)

1645-1650: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove @SideEffectFree from memoizing methods that mutate receiver fields.

Methods keySet() and values() perform lazy initialization by assigning to receiver fields (keySet, values, keySetView, valuesView). The @SideEffectFree annotation is incorrect—these methods mutate receiver state and violate the annotation's contract, causing unsound checker assumptions.

Affected methods:

  • keySet() at 1645–1650 and 3027–3032
  • values() at 1678–1683 and 3041–3046
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java`
around lines 1645 - 1650, Remove the incorrect `@SideEffectFree` annotation from
the lazy-memoizing accessor methods that mutate receiver fields—specifically the
keySet() and values() methods (both overloads/occurrences) which assign to the
cached fields keySet, values, keySetView and valuesView; update the method
declarations to drop `@SideEffectFree` so the checker no longer assumes they are
side-effect-free and no other behavioral changes are made.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/java.base/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java`:
- Around line 932-943: The annotated purity is incorrect because getFirst(),
getLast(), toString(), Itr.startNode(), and DescendingItr.startNode() call
first()/last() which can mutate head/tail via
HEAD.compareAndSet()/TAIL.compareAndSet(); remove the `@Pure` and `@SideEffectFree`
annotations from these methods (getFirst, getLast, toString, Itr.startNode,
DescendingItr.startNode) so they no longer claim to be side-effect-free and
update any method-level annotations/comments accordingly to reflect that these
calls may modify head/tail through CAS.

In `@src/java.base/share/classes/java/util/ImmutableCollections.java`:
- Around line 1360-1363: The method nextIndex() mutates the receiver (it assigns
to this.idx) so the `@Pure` annotation is incorrect; remove the `@Pure` annotation
from the nextIndex() method declaration (leave other annotations like
`@DoesNotUnrefineReceiver`("modifiability") intact) so the method's metadata
correctly reflects that it has visible side effects.

In `@src/java.base/share/classes/java/util/LinkedHashMap.java`:
- Around line 1129-1132: The `@Pure` annotation on
LinkedHashMap.LinkedKeyIterator.next() is incorrect because nextNode() mutates
iterator state; remove the `@Pure` annotation from the LinkedKeyIterator.next(...)
declaration (leaving `@SideEffectsOnly`("this") and
`@DoesNotUnrefineReceiver`("modifiability") intact) so its contract matches
LinkedValueIterator.next() and LinkedEntryIterator.next() which are not marked
`@Pure`.
- Around line 1230-1233: Remove the `@Pure` annotation from the
ReversedLinkedHashMapView methods public V get(Object key) and public V
getOrDefault(Object key, V defaultValue) because they call base.get(key) /
base.getOrDefault(key, defaultValue) which may invoke afterNodeAccess(e) when
the underlying LinkedHashMap has accessOrder=true and thus mutate internal state
and modCount; locate the methods by their signatures (get(Object) and
getOrDefault(Object, V)) and simply delete the `@Pure` annotations so the
annotations no longer claim immutability.

In `@src/java.base/share/classes/java/util/TreeSet.java`:
- Around line 638-641: Remove the redundant
`@DoesNotUnrefineReceiver`("modifiability") annotation from the spliterator()
method (and the other 8 similar occurrences); keep the `@SideEffectFree`
annotation only, since it already guarantees no visible side effects and thus
preserves receiver modifiability—update the annotations on public Spliterator<E>
spliterator() (and the matching method declarations) to drop
`@DoesNotUnrefineReceiver` while leaving `@SideEffectFree` intact.

---

Outside diff comments:
In `@src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java`:
- Around line 1342-1352: The methods annotated `@Pure` (notably hashCode() and the
other methods that read concurrent/volatile state or call AtomicReference.get())
are not deterministic under concurrent mutation; replace the `@Pure` annotation
with `@SideEffectFree` for each such method (e.g., hashCode and the methods
referenced at lines 4801, 4990, 5386, 5431, 5476, 5521) so the annotation
accurately reflects side-effect freedom without implying determinism—ensure you
import/use the existing SideEffectFree annotation already present in the file.

In `@src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java`:
- Around line 1645-1650: Remove the incorrect `@SideEffectFree` annotation from
the lazy-memoizing accessor methods that mutate receiver fields—specifically the
keySet() and values() methods (both overloads/occurrences) which assign to the
cached fields keySet, values, keySetView and valuesView; update the method
declarations to drop `@SideEffectFree` so the checker no longer assumes they are
side-effect-free and no other behavioral changes are made.

In `@src/java.base/share/classes/java/util/IdentityHashMap.java`:
- Around line 795-806: Replace the incorrect `@Pure` annotation on method
nextIndex of IdentityHashMapIterator<T> with `@SideEffectsOnly`("this") because
nextIndex mutates receiver state (it updates indexValid, lastReturnedIndex, and
index) and therefore is not pure; locate the nextIndex method and the
commented-out `@SideEffectsOnly`("this") and re-enable that annotation in place of
`@Pure` so the annotation accurately reflects the method’s side effects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f532fdfd-0288-4225-91b4-af69dfbd20d8

📥 Commits

Reviewing files that changed from the base of the PR and between f7ef3b0 and 27f4b4a.

📒 Files selected for processing (94)
  • src/java.base/share/classes/java/io/BufferedReader.java
  • src/java.base/share/classes/java/lang/CharSequence.java
  • src/java.base/share/classes/java/lang/Class.java
  • src/java.base/share/classes/java/lang/String.java
  • src/java.base/share/classes/java/lang/invoke/AbstractConstantGroup.java
  • src/java.base/share/classes/java/net/URL.java
  • src/java.base/share/classes/java/nio/charset/Charset.java
  • src/java.base/share/classes/java/nio/file/FileTreeIterator.java
  • src/java.base/share/classes/java/nio/file/Files.java
  • src/java.base/share/classes/java/nio/file/Path.java
  • src/java.base/share/classes/java/security/Provider.java
  • src/java.base/share/classes/java/util/AbstractCollection.java
  • src/java.base/share/classes/java/util/AbstractList.java
  • src/java.base/share/classes/java/util/AbstractMap.java
  • src/java.base/share/classes/java/util/AbstractQueue.java
  • src/java.base/share/classes/java/util/AbstractSequentialList.java
  • src/java.base/share/classes/java/util/AbstractSet.java
  • src/java.base/share/classes/java/util/ArrayDeque.java
  • src/java.base/share/classes/java/util/ArrayList.java
  • src/java.base/share/classes/java/util/Arrays.java
  • src/java.base/share/classes/java/util/Collection.java
  • src/java.base/share/classes/java/util/Collections.java
  • src/java.base/share/classes/java/util/Deque.java
  • src/java.base/share/classes/java/util/EnumMap.java
  • src/java.base/share/classes/java/util/EnumSet.java
  • src/java.base/share/classes/java/util/Enumeration.java
  • src/java.base/share/classes/java/util/HashMap.java
  • src/java.base/share/classes/java/util/HashSet.java
  • src/java.base/share/classes/java/util/Hashtable.java
  • src/java.base/share/classes/java/util/IdentityHashMap.java
  • src/java.base/share/classes/java/util/ImmutableCollections.java
  • src/java.base/share/classes/java/util/Iterator.java
  • src/java.base/share/classes/java/util/JumboEnumSet.java
  • src/java.base/share/classes/java/util/LinkedHashMap.java
  • src/java.base/share/classes/java/util/LinkedHashSet.java
  • src/java.base/share/classes/java/util/LinkedList.java
  • src/java.base/share/classes/java/util/List.java
  • src/java.base/share/classes/java/util/ListIterator.java
  • src/java.base/share/classes/java/util/Map.java
  • src/java.base/share/classes/java/util/NavigableMap.java
  • src/java.base/share/classes/java/util/NavigableSet.java
  • src/java.base/share/classes/java/util/Objects.java
  • src/java.base/share/classes/java/util/PrimitiveIterator.java
  • src/java.base/share/classes/java/util/PriorityQueue.java
  • src/java.base/share/classes/java/util/Properties.java
  • src/java.base/share/classes/java/util/Queue.java
  • src/java.base/share/classes/java/util/RegularEnumSet.java
  • src/java.base/share/classes/java/util/ReverseOrderDequeView.java
  • src/java.base/share/classes/java/util/ReverseOrderListView.java
  • src/java.base/share/classes/java/util/ReverseOrderSortedMapView.java
  • src/java.base/share/classes/java/util/ReverseOrderSortedSetView.java
  • src/java.base/share/classes/java/util/Scanner.java
  • src/java.base/share/classes/java/util/SequencedCollection.java
  • src/java.base/share/classes/java/util/SequencedMap.java
  • src/java.base/share/classes/java/util/SequencedSet.java
  • src/java.base/share/classes/java/util/ServiceLoader.java
  • src/java.base/share/classes/java/util/Set.java
  • src/java.base/share/classes/java/util/SortedMap.java
  • src/java.base/share/classes/java/util/SortedSet.java
  • src/java.base/share/classes/java/util/Spliterators.java
  • src/java.base/share/classes/java/util/Stack.java
  • src/java.base/share/classes/java/util/TreeMap.java
  • src/java.base/share/classes/java/util/TreeSet.java
  • src/java.base/share/classes/java/util/Vector.java
  • src/java.base/share/classes/java/util/WeakHashMap.java
  • src/java.base/share/classes/java/util/concurrent/ArrayBlockingQueue.java
  • src/java.base/share/classes/java/util/concurrent/BlockingDeque.java
  • src/java.base/share/classes/java/util/concurrent/BlockingQueue.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentLinkedQueue.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentNavigableMap.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java
  • src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java
  • src/java.base/share/classes/java/util/concurrent/CopyOnWriteArraySet.java
  • src/java.base/share/classes/java/util/concurrent/DelayQueue.java
  • src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java
  • src/java.base/share/classes/java/util/concurrent/LinkedBlockingQueue.java
  • src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java
  • src/java.base/share/classes/java/util/concurrent/PriorityBlockingQueue.java
  • src/java.base/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java
  • src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java
  • src/java.base/share/classes/java/util/jar/Attributes.java
  • src/java.base/share/classes/java/util/regex/Matcher.java
  • src/java.base/share/classes/java/util/regex/Pattern.java
  • src/java.base/share/classes/java/util/stream/SpinedBuffer.java
  • src/java.base/share/classes/java/util/stream/Stream.java
  • src/java.base/share/classes/java/util/zip/ZipFile.java
  • src/java.base/share/classes/jdk/internal/org/objectweb/asm/tree/InsnList.java
  • src/java.base/share/classes/sun/net/www/HeaderParser.java
  • src/java.base/share/classes/sun/net/www/MessageHeader.java
  • src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java
💤 Files with no reviewable changes (1)
  • src/java.base/share/classes/java/util/stream/Stream.java

Comment thread src/java.base/share/classes/java/util/ImmutableCollections.java
Comment thread src/java.base/share/classes/java/util/LinkedHashMap.java
Comment thread src/java.base/share/classes/java/util/LinkedHashMap.java
Comment thread src/java.base/share/classes/java/util/TreeSet.java Outdated
@mernst
Copy link
Copy Markdown
Member Author

mernst commented May 20, 2026

@CodeRabbit The Java method annotation @SideEffectFree means that the method has no visible side-effects. A @SideEffectFree method is allowed to set private caches.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Tip

For best results, initiate chat on the files or code changes.

@mernst, thank you for the clarification! I've noted that in the Checker Framework, @SideEffectFree means no visible side-effects — a method annotated with @SideEffectFree is permitted to update private caches or perform other internal state changes that are not observable by callers.


✏️ Learnings added
Learnt from: mernst
URL: https://github.com/typetools/jdk/pull/276

Timestamp: 2026-05-20T15:17:14.803Z
Learning: In the Checker Framework (used in the typetools/jdk repository), the `SideEffectFree` annotation on a Java method means the method has no *visible* side-effects. A `SideEffectFree` method is allowed to set private caches or perform other internal state changes that are not observable by callers. It does NOT mean the method is strictly free of all side-effects.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/java.base/share/classes/java/util/LinkedHashMap.java (1)

556-572: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove @Pure annotations from access-order-sensitive methods

Lines 557 and 571 mark get() and getOrDefault() as @Pure, but both methods mutate receiver state via afterNodeAccess(...) when accessOrder==true (lines 562–563 and 576–577). This violates the @Pure contract, which requires methods to be side-effect-free. The existing CFComment already documents that these methods are "not strictly pure" when access order is enabled.

Suggested fix
 `@CFComment`("`get()` is not strictly pure: if `accessOrder==true`, it changes the access order")
-@Pure
 public `@Nullable` V get(`@GuardSatisfied` LinkedHashMap<K, V> this, `@UnknownSignedness` `@GuardSatisfied` `@Nullable` Object key) {
     Node<K,V> e;
     if ((e = getNode(key)) == null)
         return null;
     if (accessOrder)
         afterNodeAccess(e);
     return e.value;
 }

 /**
  * {`@inheritDoc`}
  */
 `@CFComment`("`getOrDefault()` is not strictly pure: if `accessOrder==true`, it changes the access order")
-@Pure
 public V getOrDefault(`@Nullable` Object key, V defaultValue) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/java.base/share/classes/java/util/LinkedHashMap.java` around lines 556 -
572, Remove the incorrect `@Pure` annotation from the access-order-sensitive
methods: delete the `@Pure` annotation on the get(`@GuardSatisfied`
LinkedHashMap<K,V> this, `@Nullable` Object key) method and the `@Pure` annotation
on getOrDefault(`@Nullable` Object key, V defaultValue), since both call
afterNodeAccess(e) when accessOrder is true and therefore can mutate receiver
state; leave the existing CFComment lines intact to document the conditional
impurity and ensure no other annotations or method signatures are changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/java.base/share/classes/java/util/LinkedHashMap.java`:
- Around line 556-572: Remove the incorrect `@Pure` annotation from the
access-order-sensitive methods: delete the `@Pure` annotation on the
get(`@GuardSatisfied` LinkedHashMap<K,V> this, `@Nullable` Object key) method and
the `@Pure` annotation on getOrDefault(`@Nullable` Object key, V defaultValue),
since both call afterNodeAccess(e) when accessOrder is true and therefore can
mutate receiver state; leave the existing CFComment lines intact to document the
conditional impurity and ensure no other annotations or method signatures are
changed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 93bae187-324c-4934-a24a-e03c112a979b

📥 Commits

Reviewing files that changed from the base of the PR and between 243e3f6 and 72f2938.

📒 Files selected for processing (11)
  • src/java.base/share/classes/java/util/Collections.java
  • src/java.base/share/classes/java/util/HashSet.java
  • src/java.base/share/classes/java/util/IdentityHashMap.java
  • src/java.base/share/classes/java/util/ImmutableCollections.java
  • src/java.base/share/classes/java/util/LinkedHashMap.java
  • src/java.base/share/classes/java/util/LinkedHashSet.java
  • src/java.base/share/classes/java/util/RegularEnumSet.java
  • src/java.base/share/classes/java/util/ReverseOrderListView.java
  • src/java.base/share/classes/java/util/SortedSet.java
  • src/java.base/share/classes/java/util/TreeSet.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
💤 Files with no reviewable changes (7)
  • src/java.base/share/classes/java/util/HashSet.java
  • src/java.base/share/classes/java/util/LinkedHashSet.java
  • src/java.base/share/classes/java/util/RegularEnumSet.java
  • src/java.base/share/classes/java/util/SortedSet.java
  • src/java.base/share/classes/java/util/ImmutableCollections.java
  • src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
  • src/java.base/share/classes/java/util/IdentityHashMap.java

@mernst mernst merged commit 805ed88 into typetools:master May 21, 2026
49 checks passed
@mernst mernst deleted the dnur branch May 21, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant