Skip to content

Extend RLC to collections of resources#7166

Open
skehrli wants to merge 439 commits intotypetools:masterfrom
skehrli:rlc-collections-redesign
Open

Extend RLC to collections of resources#7166
skehrli wants to merge 439 commits intotypetools:masterfrom
skehrli:rlc-collections-redesign

Conversation

@skehrli
Copy link
Contributor

@skehrli skehrli commented Jul 23, 2025

Summary by CodeRabbit

  • New Features

    • Introduces collection-ownership checking integrated with the Resource Leak Checker, with new annotations (e.g., owning/non-owning collections and field destructors).
    • Defaults and ownership transfer for parameters, returns, fields, assignments, and new allocations.
    • Loop-aware analysis (enhanced-for and index loops) to verify element must-call obligations.
    • Improved diagnostics and dataflow visualization for iterated collection elements.
  • Bug Fixes

    • Fewer false positives: iterator and map access now reflect non-owning returns, reducing spurious errors.
  • Documentation

    • Manual updated with collection-ownership model, defaults, and usage guidance.
  • Tests

    • Extensive new test suites for collections, fields, loops, and defaults.

Sascha Kehrli and others added 30 commits June 22, 2025 17:00
Copy link
Contributor

@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: 2

🤖 Fix all issues with AI agents
In
@dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java:
- Line 914: The static mutable field `uid` in class CFGTranslationPhaseOne
causes a race when multiple instances run concurrently; change `protected static
long uid = 0;` to an instance field (e.g., `protected long uid = 0;`) and update
the uniqueName() method to use the instance `uid` (incrementing it in a
thread-safe manner for that instance) — if truly global, replace `long uid` with
an AtomicLong and use `uid.incrementAndGet()` in uniqueName(); adjust all
references to `uid` and ensure uniqueName() reflects the chosen fix.

In
@framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java:
- Around line 1196-1211: getRegularStore may NPE when flowResult is null or
input/regular store is null; change getRegularStore to be null-safe by (a)
updating its signature and Javadoc to return @Nullable Store, (b) guarding the
flowResult.getInput(block) call (if !analysis.isRunning() && flowResult == null
then return null), (c) also check that the obtained TransferInput<Value,Store>
input is non-null and return input.getRegularStore() only if input != null and
input.getRegularStore() != null, otherwise return null; reference symbols:
getRegularStore, flowResult, analysis.getInput, flowResult.getInput,
TransferInput, input.getRegularStore().
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c56f0c and 2c99d54.

📒 Files selected for processing (3)
  • dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java
  • framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java
  • framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: In MustCallConsistencyAnalyzer.addObligationsForOwningCollectionReturn, treat a null result from CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(...) as “no obligations” and skip creating CollectionObligation(s); do not throw. This can occur for OwningCollection over non-resource element types.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: When handling collection ownership in MustCallConsistencyAnalyzer, do not throw if CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(..) returns null; treat it as “no obligations” and skip creating CollectionObligation(s). Also, when constructing diagnostics that reference an element method name, fall back to "Unknown" if the list is null/empty.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.
📚 Learning: 2025-10-22T20:41:01.112Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.

Applied to files:

  • dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java
📚 Learning: 2025-11-02T02:18:00.536Z
Learnt from: mernst
Repo: typetools/checker-framework PR: 7354
File: annotation-file-utilities/tests/LocalMultipleManyMethodsShifted.java:14-14
Timestamp: 2025-11-02T02:18:00.536Z
Learning: In the Checker Framework repository, when doing refactoring PRs (such as preferring isEmpty() over size() comparisons), test files should not be changed. Tests should remain stable to preserve their exact patterns for annotation processing and bytecode verification purposes.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: typetools.checker-framework (typecheck_part1_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part2_jdk25)
  • GitHub Check: typetools.checker-framework (misc_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part2_jdk25)
  • GitHub Check: typetools.checker-framework (junit_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part1_jdk25)
  • GitHub Check: typetools.checker-framework (nonjunit_jdk25)
🔇 Additional comments (2)
framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java (1)

782-782: Suppression is appropriate for this debugging utility.

The @SuppressWarnings("collectionownership:argument") annotation is appropriately narrow and justified. The assertSameSize method only formats @MustCallUnknown collections for error messages via StringsPlume.join without transferring ownership, so suppressing the collection ownership argument warning is correct. This pattern is also used consistently in StubGenerator.formatList and ValueCheckerUtils.convertToStringVal.

framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java (1)

64-64: LGTM: Import added for new API.

The import is correctly placed and necessary for the new getRegularStore(Block) method.


/** The UID for the next unique name. */
protected long uid = 0;
protected static long uid = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Race condition: unsynchronized access to shared mutable static field.

Making uid static introduces a race condition when multiple CFGTranslationPhaseOne instances are created concurrently (e.g., during parallel compilation). The increment operation uid++ on line 923 is not atomic, which can lead to:

  • Lost updates when multiple threads read, increment, and write back the same value
  • Non-unique names being generated, violating the purpose of uniqueName()

Additionally, this contradicts the project pattern documented in the learnings: "prefer instance fields over static state for analysis caches."

🔒 Recommended fix: use instance field or AtomicLong

Option 1 (Preferred): Revert to instance field

-  protected static long uid = 0;
+  protected long uid = 0;

Option 2: If global uniqueness is required, use AtomicLong

-  protected static long uid = 0;
+  protected static final AtomicLong uid = new AtomicLong(0);

Then update uniqueName():

   protected String uniqueName(String prefix) {
-    return prefix + "#num" + uid++;
+    return prefix + "#num" + uid.getAndIncrement();
   }

Based on learnings, instance fields are preferred over static state in this codebase.

🤖 Prompt for AI Agents
In
@dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java
at line 914, The static mutable field `uid` in class CFGTranslationPhaseOne
causes a race when multiple instances run concurrently; change `protected static
long uid = 0;` to an instance field (e.g., `protected long uid = 0;`) and update
the uniqueName() method to use the instance `uid` (incrementing it in a
thread-safe manner for that instance) — if truly global, replace `long uid` with
an AtomicLong and use `uid.incrementAndGet()` in uniqueName(); adjust all
references to `uid` and ensure uniqueName() reflects the chosen fix.

Comment on lines +1196 to +1211
/**
* Returns the regular store for a given block.
*
* @param block a block whose regular store to return
* @return the regular store of {@code block}
*/
public Store getRegularStore(Block block) {
TransferInput<Value, Store> input;
if (!analysis.isRunning()) {
input = flowResult.getInput(block);
} else {
input = analysis.getInput(block);
}

return input.getRegularStore();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null safety guards to prevent NPE.

The method can throw a NullPointerException in two scenarios:

  1. When flowResult is null: Line 1205 calls flowResult.getInput(block) without checking if flowResult is null. Evidence that this can occur: getInferredValueFor (lines 2068-2073) explicitly guards against this case with the comment "When parsing stub or ajava files, the analysis is not running (it has not yet started), and flowResult is null".

  2. Inconsistent nullability: Similar methods like getStoreBefore(Tree) (line 1189) and getStoreBefore(Node) (line 1219) return @Nullable Store, but this returns non-nullable Store. If input or input.getRegularStore() can be null, callers have no indication they need to handle it.

🛡️ Suggested fix to add null safety
   /**
    * Returns the regular store for a given block.
    *
    * @param block a block whose regular store to return
    * @return the regular store of {@code block}
    */
-  public Store getRegularStore(Block block) {
+  public @Nullable Store getRegularStore(Block block) {
     TransferInput<Value, Store> input;
-    if (!analysis.isRunning()) {
+    if (!analysis.isRunning() && flowResult == null) {
+      // When parsing stub or ajava files, the analysis is not running and flowResult is null.
+      return null;
+    } else if (!analysis.isRunning()) {
       input = flowResult.getInput(block);
     } else {
       input = analysis.getInput(block);
     }
 
+    if (input == null) {
+      return null;
+    }
     return input.getRegularStore();
   }

Alternatively, if callers are expected to only invoke this method when flowResult is guaranteed to be non-null, document this precondition clearly in the Javadoc.

🤖 Prompt for AI Agents
In
@framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java
around lines 1196 - 1211, getRegularStore may NPE when flowResult is null or
input/regular store is null; change getRegularStore to be null-safe by (a)
updating its signature and Javadoc to return @Nullable Store, (b) guarding the
flowResult.getInput(block) call (if !analysis.isRunning() && flowResult == null
then return null), (c) also check that the obtained TransferInput<Value,Store>
input is non-null and return input.getRegularStore() only if input != null and
input.getRegularStore() != null, otherwise return null; reference symbols:
getRegularStore, flowResult, analysis.getInput, flowResult.getInput,
TransferInput, input.getRegularStore().

Copy link
Contributor

@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: 10

🤖 Fix all issues with AI agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:
- Around line 3340-3437: In
MustCallConsistencyAnalyzer.patternMatchEnhancedCollectionForLoop replace the
identity comparison of Name objects with an equality check: change the line that
sets isAssignmentOfIterVar (currently using iterVarDecl.getName() ==
loopVariable.getName()) to use Name.equals(...) or
Objects.equals(iterVarDecl.getName(), loopVariable.getName()) so the iterator
variable name is compared by value rather than by reference; ensure you import
java.util.Objects if you use Objects.equals and update only that comparison in
the method.
- Around line 767-798: The method addObligationsForOwningCollectionReturn
currently throws BugInCF when
coAtf.getMustCallValuesOfResourceCollectionComponent(node.getTree()) returns
null; change this to treat a null mustCallValues as "no obligations" by simply
skipping obligation creation: after retrieving mustCallValues, check if it is
null and return (or continue) without throwing, so only non-null lists are
iterated to create CollectionObligation instances (preserve existing checks like
ResourceLeakUtils.isIterator and use tmpVarAsResourceAlias and
MethodExitKind.ALL as before).
- Around line 1933-1940: In the OwningCollection case in
MustCallConsistencyAnalyzer, avoid calling .get(0) directly on
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()); instead,
store that result in a local variable (e.g., mustCallValues), check it for null
and !mustCallValues.isEmpty() before using mustCallValues.get(0), and only pass
the first element to checker.reportError when present; if null/empty, handle the
missing value path consistently with the earlier fix (skip using get(0) or use a
safe fallback) so no NPE can occur.
- Around line 1867-1876: The call to
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0) in
MustCallConsistencyAnalyzer may NPE or IOBE; before passing that value into
checker.reportError, retrieve the list into a local variable, check for null or
isEmpty(), and if empty use the string "Unknown" (or a safe default) as the
element/method name argument; then call checker.reportError with either the
first element or the "Unknown" fallback to avoid exceptions.
- Around line 808-851: The code in
addObligationsForCreatesCollectionObligationAnno calls
coAtf.getMustCallValuesOfResourceCollectionComponent(receiverNode.getTree()) and
iterates mustCallValues without a null check, risking an NPE; guard the loop by
checking if mustCallValues is non-null before iterating (i.e., if
(mustCallValues != null) { for (...)
obligations.add(CollectionObligation.fromTree(...)); }), keeping the existing
behavior when the method returns null for non-resource elements and leaving the
subsequent checkEnclosingMethodIsCreatesMustCallFor logic unchanged.
- Around line 511-583: CollectionObligation overrides equals(...) to include
mustCallMethod but doesn't override hashCode(), breaking the equals/hashCode
contract; add a matching hashCode() method in class CollectionObligation that
combines super.hashCode() with mustCallMethod.hashCode() (handle mustCallMethod
possibly null consistently) so instances used in hash-based collections behave
correctly. Ensure the new hashCode() is consistent with equals() and lives
alongside the existing equals(...) override.

In @docs/manual/resource-leak-checker.tex:
- Around line 957-958: The docs contain a leftover TODO in the JDK Method
Signatures subsection (label resource-leak-collections-jdk-methods) that must be
resolved before release; replace the placeholder with a concrete table of common
java.util collection/iterable method signatures annotated with the collection
ownership annotations used in this PR (e.g., methods like Collection.add,
Collection.remove, Iterable.iterator, List.get, Map.put etc., showing whether
they transfer/retain/borrow ownership), or if the table is not ready, remove the
TODO and add a short “To be completed” note with a linked issue number; update
the subsection text to reference the annotation names exactly as used in the PR
and ensure the LaTeX label/resource-leak-collections-jdk-methods remains
correct.
- Around line 959-1001: Remove or replace the large commented-out legacy
documentation block about the Resource Leak Checker (the section discussing
@Owning, @NotOwning, @MustCall defaults and examples like List<Socket>) — either
delete the entire commented block to clean up the docs or convert it into a
concise "historical note" paragraph summarizing the prior limitations and that
they are now resolved; update the surrounding resource-leak-checker section to
reference the short note if kept.
- Around line 731-737: The example's catch block incorrectly calls
System.out.println(e.printStackTrace()) which is invalid because
printStackTrace() returns void; update the catch to either call
e.printStackTrace() directly or print the exception via System.out.println(e)
(or use a logger), leaving the loop and the Socket s / sockets / s.close() logic
unchanged so compilation succeeds.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c99d54 and 036ebd8.

📒 Files selected for processing (2)
  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
  • docs/manual/resource-leak-checker.tex
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: In MustCallConsistencyAnalyzer.addObligationsForOwningCollectionReturn, treat a null result from CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(...) as “no obligations” and skip creating CollectionObligation(s); do not throw. This can occur for OwningCollection over non-resource element types.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: When handling collection ownership in MustCallConsistencyAnalyzer, do not throw if CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(..) returns null; treat it as “no obligations” and skip creating CollectionObligation(s). Also, when constructing diagnostics that reference an element method name, fall back to "Unknown" if the list is null/empty.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.
📚 Learning: 2025-10-22T20:43:32.957Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: In MustCallConsistencyAnalyzer.addObligationsForOwningCollectionReturn, treat a null result from CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(...) as “no obligations” and skip creating CollectionObligation(s); do not throw. This can occur for OwningCollection over non-resource element types.

Applied to files:

  • docs/manual/resource-leak-checker.tex
  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
📚 Learning: 2025-10-22T20:43:32.957Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: When handling collection ownership in MustCallConsistencyAnalyzer, do not throw if CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(..) returns null; treat it as “no obligations” and skip creating CollectionObligation(s). Also, when constructing diagnostics that reference an element method name, fall back to "Unknown" if the list is null/empty.

Applied to files:

  • docs/manual/resource-leak-checker.tex
  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
📚 Learning: 2025-10-22T20:41:01.112Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.

Applied to files:

  • docs/manual/resource-leak-checker.tex
  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
📚 Learning: 2025-11-02T02:18:00.536Z
Learnt from: mernst
Repo: typetools/checker-framework PR: 7354
File: annotation-file-utilities/tests/LocalMultipleManyMethodsShifted.java:14-14
Timestamp: 2025-11-02T02:18:00.536Z
Learning: In the Checker Framework repository, when doing refactoring PRs (such as preferring isEmpty() over size() comparisons), test files should not be changed. Tests should remain stable to preserve their exact patterns for annotation processing and bytecode verification purposes.

Applied to files:

  • checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: typetools.checker-framework (typecheck_part2_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part1_jdk25)
  • GitHub Check: typetools.checker-framework (misc_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part2_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part1_jdk25)
  • GitHub Check: typetools.checker-framework (nonjunit_jdk25)
  • GitHub Check: typetools.checker-framework (junit_jdk25)
🔇 Additional comments (14)
docs/manual/resource-leak-checker.tex (6)

138-158: LGTM!

The new subsection for collection annotations is well-structured, correctly introduces the three new annotations, and appropriately cross-references the detailed sections later in the document.


643-667: LGTM!

The introduction clearly defines what constitutes a "resource collection" and explains the dual tracking mechanism (ownership type system + dataflow analysis). The examples effectively illustrate the distinction between resource and non-resource collections.


668-688: LGTM!

The type hierarchy is clearly presented with the ASCII diagram, and each qualifier's meaning is well-explained. The note about user-writable annotations being limited to @OwningCollection and @NotOwningCollection is helpful.


767-816: LGTM!

The section clearly explains how to fulfill collection obligations using loops. The requirements for indexed for loops (bounds checking, no reassignment, etc.) are well-specified and the examples are helpful.


818-882: LGTM!

The documentation for resource collection fields is thorough. The Aggregator example effectively demonstrates:

  • Default field ownership
  • Constructor with @OwningCollection parameter
  • @CreatesMustCallFor for mutating methods
  • @CollectionFieldDestructor for the disposal method

The enumerated verification rules and ownership transfer restrictions are clearly explained.


883-887: LGTM!

Clear explanation of the alternative pattern using @NotOwningCollection for fields where the enclosing class should not take responsibility.

checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java (8)

177-184: LGTM! Field additions and constructor changes are well-structured.

The new fields coAtf and isLoopBodyAnalysis are appropriately added as instance fields, and the constructor properly initializes them. This design aligns with the preference for instance state over static state for analysis-related data.

Also applies to: 715-722


280-320: LGTM! Well-designed extension methods for Obligation.

The getReplacement method provides a clean extension point for subclasses via dynamic dispatch. The fromTree factory method appropriately handles both ExpressionTree and VariableTree, with clear error messaging for unsupported types. The canBeSatisfiedThrough(Tree) method correctly checks both direct tree equality and JavaExpression equivalence.

Also applies to: 369-387


2151-2217: LGTM! Well-refactored method with clear return semantics.

The method has been updated to return a boolean indicating whether the check succeeded, which provides better API semantics than the previous void return. The early type check for FieldAccessNode is appropriate, and all code paths properly return a boolean value.


2239-2243: LGTM! Properly handles nested field access.

The addition of the FieldAccessNode case with recursive handling correctly supports nested field access patterns like obj.field.subfield, which was previously not handled.


3180-3193: LGTM! Clean abstraction for formatting method names.

The formatMustCallMethod helper properly handles the special case of UNKNOWN_METHOD_NAME by returning a user-friendly "Unknown" string, which aligns with the pattern for handling missing method name information in diagnostics.


2964-2989: LGTM! Proper handling of CollectionObligation in error reporting.

The special case handling for CollectionObligation correctly reports collection-specific errors and uses the "Unknown" fallback for missing method names, consistent with the pattern established in formatMustCallMethod.


3439-3557: LGTM! Well-structured loop body analysis.

The method properly validates preconditions with Objects.requireNonNull, handles the empty loop body case early, and correctly catches InvalidLoopBodyAnalysisException to abort unreachable loop analysis. The use of == for block comparison at line 3520 is appropriate since block identity is the correct check here.


2537-2558: LGTM! Well-designed exception for aborting unreachable loop analysis.

The InvalidLoopBodyAnalysisException is appropriately used to signal when loop body analysis encounters unreachable blocks. The exception is properly caught at call sites (lines 2490, 3544), and the detailed comment at lines 2540-2553 clearly explains why this approach is sound.

Also applies to: 3661-3681

Comment on lines +511 to +583
/** Obligation to call a certain method on all elements of a collection. */
static class CollectionObligation extends Obligation {

/** The method that must be called on all elements of the collection. */
public String mustCallMethod;

/**
* Create a CollectionObligation from a set of resource aliases.
*
* @param mustCallMethod the method to be called on all elements of the collection
* @param resourceAliases a set of resource aliases
* @param whenToEnforce when this Obligation should be enforced
*/
public CollectionObligation(
String mustCallMethod,
Set<ResourceAlias> resourceAliases,
Set<MethodExitKind> whenToEnforce) {
super(resourceAliases, whenToEnforce);
this.mustCallMethod = mustCallMethod;
}

/**
* Create a CollectionObligation from an Obligation.
*
* @param obligation the obligation to create a CollectionObligation from
* @param mustCallMethod the method that must be called on the elements of the collection
*/
private CollectionObligation(Obligation obligation, String mustCallMethod) {
super(obligation.resourceAliases, obligation.whenToEnforce);
this.mustCallMethod = mustCallMethod;
}

/**
* Creates and returns a CollectionObligation derived from the given expression.
*
* @param tree the tree from which to create the CollectionObligation. Must be {@code
* ExpressionTree} or {@code VariableTree}.
* @param mustCallMethod the method that must be called on the elements of the collection
* @return a CollectionObligation derived from the given tree
*/
public static CollectionObligation fromTree(Tree tree, String mustCallMethod) {
return new CollectionObligation(Obligation.fromTree(tree), mustCallMethod);
}

/**
* Returns a new CollectionObligation.
*
* <p>We need this method since we frequently need to replace obligations. If the old obligation
* was a CollectionObligation, we want the replacement to be as well. Dynamic dispatch then
* allows us to simply call getReplacement() on an obligation and get the replacement of the
* right class.
*
* @param resourceAliases set of resource aliases for the new obligation
* @return a new CollectionObligation with the passed traits
*/
@Override
public CollectionObligation getReplacement(
Set<ResourceAlias> resourceAliases, Set<MethodExitKind> whenToEnforce) {
return new CollectionObligation(this.mustCallMethod, resourceAliases, whenToEnforce);
}

@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CollectionObligation)) {
return false;
}
return super.equals(obj)
&& ((CollectionObligation) obj).mustCallMethod.equals(this.mustCallMethod);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Override hashCode() when overriding equals().

The CollectionObligation class overrides equals() to include the mustCallMethod field but does not override hashCode(). This violates the general contract for hashCode() and can lead to incorrect behavior when CollectionObligation instances are used in hash-based collections (such as the obligations sets throughout this file).

🔧 Proposed fix
     @Override
     public boolean equals(@Nullable Object obj) {
       if (obj == this) {
         return true;
       }
       if (!(obj instanceof CollectionObligation)) {
         return false;
       }
       return super.equals(obj)
           && ((CollectionObligation) obj).mustCallMethod.equals(this.mustCallMethod);
     }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(super.hashCode(), mustCallMethod);
+    }
   }
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 511 - 583, CollectionObligation overrides equals(...) to include
mustCallMethod but doesn't override hashCode(), breaking the equals/hashCode
contract; add a matching hashCode() method in class CollectionObligation that
combines super.hashCode() with mustCallMethod.hashCode() (handle mustCallMethod
possibly null consistently) so instances used in hash-based collections behave
correctly. Ensure the new hashCode() is consistent with equals() and lives
alongside the existing equals(...) override.

Comment on lines +767 to +798
/**
* Adds {@code CollectionObligation}s if the type of {@code node} is {@code @OwningCollection}.
*
* @param obligations the set of tracked obligations
* @param node the node of a return expression
*/
private void addObligationsForOwningCollectionReturn(Set<Obligation> obligations, Node node) {
LocalVariableNode tmpVar = cmAtf.getTempVarForNode(node);
if (tmpVar != null) {
CollectionOwnershipStore coStore = coAtf.getStoreAfter(node);
CollectionOwnershipType cotype = coAtf.getCoType(tmpVar, coStore);
if (cotype == CollectionOwnershipType.OwningCollection) {
ResourceAlias tmpVarAsResourceAlias =
new ResourceAlias(new LocalVariable(tmpVar), node.getTree());
List<String> mustCallValues =
coAtf.getMustCallValuesOfResourceCollectionComponent(node.getTree());
if (mustCallValues == null) {
throw new BugInCF(
"List of MustCall values of component type is null for OwningCollection return value:"
+ " "
+ node);
}
if (!ResourceLeakUtils.isIterator(node.getType())) {
for (String mustCallMethod : mustCallValues) {
obligations.add(
new CollectionObligation(
mustCallMethod, ImmutableSet.of(tmpVarAsResourceAlias), MethodExitKind.ALL));
}
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Handle null mustCallValues gracefully instead of throwing.

Lines 783-788 throw a BugInCF when getMustCallValuesOfResourceCollectionComponent returns null. According to the established pattern for this codebase, a null result should be treated as "no obligations" and the method should skip creating CollectionObligations rather than throwing an exception. This can occur for @OwningCollection over non-resource element types.

Based on learnings, this needs to be changed to treat null as a no-op case.

🔧 Proposed fix
     List<String> mustCallValues =
         coAtf.getMustCallValuesOfResourceCollectionComponent(node.getTree());
     if (mustCallValues == null) {
-      throw new BugInCF(
-          "List of MustCall values of component type is null for OwningCollection return value:"
-              + " "
-              + node);
+      // No obligations for non-resource element types
+      return;
     }
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 767 - 798, The method addObligationsForOwningCollectionReturn
currently throws BugInCF when
coAtf.getMustCallValuesOfResourceCollectionComponent(node.getTree()) returns
null; change this to treat a null mustCallValues as "no obligations" by simply
skipping obligation creation: after retrieving mustCallValues, check if it is
null and return (or continue) without throwing, so only non-null lists are
iterated to create CollectionObligation instances (preserve existing checks like
ResourceLeakUtils.isIterator and use tmpVarAsResourceAlias and
MethodExitKind.ALL as before).

Comment on lines +808 to +851
private void addObligationsForCreatesCollectionObligationAnno(
Set<Obligation> obligations, MethodInvocationNode node) {
ExecutableElement methodElement = TreeUtils.elementFromUse(node.getTree());
boolean hasCreatesCollectionObligation =
coAtf.getDeclAnnotation(methodElement, CreatesCollectionObligation.class) != null;
if (hasCreatesCollectionObligation) {
Node receiverNode = node.getTarget().getReceiver();
receiverNode = removeCastsAndGetTmpVarIfPresent(receiverNode);
boolean receiverIsResourceCollection = coAtf.isResourceCollection(receiverNode.getTree());
if (!receiverIsResourceCollection) {
return;
}
boolean receiverIsOwningField =
coAtf.isOwningCollectionField(TreeUtils.elementFromTree(receiverNode.getTree()));

CollectionOwnershipStore coStore = coAtf.getStoreBefore(node);
CollectionOwnershipType receiverType = coAtf.getCoType(receiverNode, coStore);
if (receiverType == null) {
throw new BugInCF("Method receiver not in collection ownership store: " + receiverNode);
}
switch (receiverType) {
case OwningCollectionWithoutObligation:
// fall through
case OwningCollection:
if (!receiverIsOwningField) {
List<String> mustCallValues =
coAtf.getMustCallValuesOfResourceCollectionComponent(receiverNode.getTree());
for (String mustCallMethod : mustCallValues) {
obligations.add(
CollectionObligation.fromTree(receiverNode.getTree(), mustCallMethod));
}
}
if (receiverIsOwningField) {
TreePath currentPath = cmAtf.getPath(node.getTree());
MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath);
if (enclosingMethodTree != null) {
checkEnclosingMethodIsCreatesMustCallFor(receiverNode, enclosingMethodTree);
}
}
break;
default:
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null check for mustCallValues to prevent potential NPE.

At line 834, getMustCallValuesOfResourceCollectionComponent is called but there's no null check before iterating over the result at lines 835-838. According to the established pattern, this method can return null for non-resource element types, which would cause a NullPointerException.

🔧 Proposed fix
     switch (receiverType) {
       case OwningCollectionWithoutObligation:
       // fall through
       case OwningCollection:
         if (!receiverIsOwningField) {
           List<String> mustCallValues =
               coAtf.getMustCallValuesOfResourceCollectionComponent(receiverNode.getTree());
+          if (mustCallValues == null) {
+            // No obligations for non-resource element types
+            break;
+          }
           for (String mustCallMethod : mustCallValues) {
             obligations.add(
                 CollectionObligation.fromTree(receiverNode.getTree(), mustCallMethod));
           }
         }
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 808 - 851, The code in
addObligationsForCreatesCollectionObligationAnno calls
coAtf.getMustCallValuesOfResourceCollectionComponent(receiverNode.getTree()) and
iterates mustCallValues without a null check, risking an NPE; guard the loop by
checking if mustCallValues is non-null before iterating (i.e., if
(mustCallValues != null) { for (...)
obligations.add(CollectionObligation.fromTree(...)); }), keeping the existing
behavior when the method returns null for non-resource elements and leaving the
subsequent checkEnclosingMethodIsCreatesMustCallFor logic unchanged.

Comment on lines +1523 to 1565
private void handleAssignmentToCollectionElementVariable(
Set<Obligation> obligations, AssignmentNode node, LocalVariableNode lhsVar, Node rhsExpr) {
if (!isLoopBodyAnalysis) {
return;
}
Obligation oldObligation = null, newObligation = null;
for (Obligation o : obligations) {
if (oldObligation != null && newObligation != null) {
break;
}
for (ResourceAlias alias : o.resourceAliases) {
if ((alias.tree instanceof ExpressionTree)
&& (rhsExpr.getTree() instanceof ExpressionTree)
&& TreeUtils.sameTree(
(ExpressionTree) alias.tree, (ExpressionTree) rhsExpr.getTree())) {
Set<ResourceAlias> newResourceAliasesForObligation =
new LinkedHashSet<>(o.resourceAliases);
// It is possible to observe assignments to temporary variables, e.g.,
// synthetic assignments to ternary expression variables in the CFG. For such
// cases, use the tree associated with the temp var for the resource alias,
// as that is the tree where errors should be reported.
Tree treeForAlias =
// I don't think we need a tempVar here, since the only case where we're interested
// in such an assignment in the loopBodyAnalysis is if the lhs is an actual variable
// to be used as an alias, so we don't care about the case where lhs is a temp-var.
// typeFactory.isTempVar(lhsVar)
// ? typeFactory.getTreeForTempVar(lhsVar)
// : node.getTree();
node.getTree();
ResourceAlias aliasForAssignment =
new ResourceAlias(new LocalVariable(lhsVar), treeForAlias);
newResourceAliasesForObligation.add(aliasForAssignment);
oldObligation = o;
newObligation = new Obligation(newResourceAliasesForObligation, o.whenToEnforce);
break;
}
}
}
if (oldObligation != null && newObligation != null) {
obligations.remove(oldObligation);
obligations.add(newObligation);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider removing or addressing commented code and uncertain comment.

Lines 1543-1550 contain a large commented-out code block with a comment expressing uncertainty ("I don't think we need a tempVar here"). If the commented code is not needed, it should be removed to improve code clarity. If there's uncertainty about whether temp var handling is needed, this should be verified and documented more clearly.

Comment on lines +1867 to +1876
}
} else {
// is an initialization block. Not supported.
checker.reportError(
node,
"unfulfilled.collection.obligations",
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0),
lhs.getTree(),
"Field assignment outside method or declaration might overwrite field's current value");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add null and empty checks before accessing mustCallValues.

At line 1873, the code calls .get(0) on the result of getMustCallValuesOfResourceCollectionComponent() without checking if the result is null or empty. This could cause a NullPointerException or IndexOutOfBoundsException. Based on learnings, when constructing diagnostics that reference an element method name, you should fall back to "Unknown" if the list is null/empty.

🔧 Proposed fix
+        List<String> mustCallValues = 
+            coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree());
+        String methodName = (mustCallValues == null || mustCallValues.isEmpty()) 
+            ? "Unknown" 
+            : mustCallValues.get(0);
         checker.reportError(
             node,
             "unfulfilled.collection.obligations",
-            coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0),
+            methodName,
             lhs.getTree(),
             "Field assignment outside method or declaration might overwrite field's current value");
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 1867 - 1876, The call to
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0) in
MustCallConsistencyAnalyzer may NPE or IOBE; before passing that value into
checker.reportError, retrieve the list into a local variable, check for null or
isEmpty(), and if empty use the string "Unknown" (or a safe default) as the
element/method name argument; then call checker.reportError with either the
first element or the "Unknown" fallback to avoid exceptions.

Comment on lines +1933 to +1940
case OwningCollection:
// assignment not allowed
checker.reportError(
node.getTree(),
"unfulfilled.collection.obligations",
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0),
lhs.getTree(),
"Field assignment might overwrite field's current value");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add null and empty checks before accessing mustCallValues (second occurrence).

Similar to the previous issue, line 1938 calls .get(0) on the result of getMustCallValuesOfResourceCollectionComponent() without null or empty checks. This is a duplicate of the pattern identified earlier and needs the same fix.

🔧 Proposed fix
+        List<String> mustCallValues = 
+            coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree());
+        String methodName = (mustCallValues == null || mustCallValues.isEmpty()) 
+            ? "Unknown" 
+            : mustCallValues.get(0);
         checker.reportError(
             node.getTree(),
             "unfulfilled.collection.obligations",
-            coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()).get(0),
+            methodName,
             lhs.getTree(),
             "Field assignment might overwrite field's current value");
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 1933 - 1940, In the OwningCollection case in
MustCallConsistencyAnalyzer, avoid calling .get(0) directly on
coAtf.getMustCallValuesOfResourceCollectionComponent(lhs.getTree()); instead,
store that result in a local variable (e.g., mustCallValues), check it for null
and !mustCallValues.isEmpty() before using mustCallValues.get(0), and only pass
the first element to checker.reportError when present; if null/empty, handle the
missing value path consistently with the earlier fix (skip using get(0) or use a
safe fallback) so no NPE can occur.

Comment on lines +3340 to +3437
private void patternMatchEnhancedCollectionForLoop(
MethodInvocationNode methodInvocationNode, ControlFlowGraph cfg) {
boolean nodeIsDesugaredFromEnhancedForLoop =
methodInvocationNode.getIterableExpression() != null;
if (nodeIsDesugaredFromEnhancedForLoop) {
// this is the Iterator.next() call desugared from an enhanced-for-loop
EnhancedForLoopTree loop = methodInvocationNode.getEnhancedForLoop();
if (loop == null) {
throw new BugInCF(
"MethodInvocationNode.iterableExpression should be non-null iff"
+ " MethodInvocationNode.enhancedForLoop is non-null");
}

// Find the first block of the loop body.
// Start from the synthetic (desugared) iterator.next() node and traverse the cfg
// until the assignment of the loop iterator variable is found, which is the last
// desugared instruction. The next block is then the start of the loop body.
VariableTree loopVariable = loop.getVariable();
SingleSuccessorBlock ssblock = (SingleSuccessorBlock) methodInvocationNode.getBlock();
Iterator<Node> nodeIterator = ssblock.getNodes().iterator();
Node loopVarNode = null;
Node node;
boolean isAssignmentOfIterVar;
do {
while (!nodeIterator.hasNext()) {
ssblock = (SingleSuccessorBlock) ssblock.getSuccessor();
nodeIterator = ssblock.getNodes().iterator();
}
node = nodeIterator.next();
isAssignmentOfIterVar = false;
if ((node instanceof AssignmentNode) && (node.getTree() instanceof VariableTree)) {
loopVarNode = ((AssignmentNode) node).getTarget();
VariableTree iterVarDecl = (VariableTree) node.getTree();
isAssignmentOfIterVar = iterVarDecl.getName() == loopVariable.getName();
}
} while (!isAssignmentOfIterVar);
Block loopBodyEntryBlock = ssblock.getSuccessor();

// Find the loop-body-condition
// Start from the synthetic (desugared) iterator.next() node and traverse the cfg
// backwards until the conditional block is found. The previous block is then the block
// containing the desugared loop condition iterator.hasNext().
Block block = methodInvocationNode.getBlock();
nodeIterator = block.getNodes().iterator();
boolean isLoopCondition;
do {
while (!nodeIterator.hasNext()) {
Set<Block> predBlocks = block.getPredecessors();
if (predBlocks.size() == 1) {
block = predBlocks.iterator().next();
nodeIterator = block.getNodes().iterator();
} else {
// there is no trivial resolution here. Best we can do is just skip this loop,
// which is of course sound.
return;
// throw new BugInCF(
// "Encountered more than one CFG Block predecessor trying to find the"
// + " enhanced-for-loop update block. Block: ");
}
}
node = nodeIterator.next();
isLoopCondition = false;
if (node instanceof MethodInvocationNode) {
MethodInvocationTree mit = ((MethodInvocationNode) node).getTree();
isLoopCondition = TreeUtils.isHasNextCall(mit);
}
} while (!isLoopCondition);

Block blockContainingLoopCondition = node.getBlock();
if (blockContainingLoopCondition.getSuccessors().size() != 1) {
throw new BugInCF(
"loop condition has: "
+ blockContainingLoopCondition.getSuccessors().size()
+ " successors instead of 1.");
}
Block conditionalBlock = blockContainingLoopCondition.getSuccessors().iterator().next();
if (!(conditionalBlock instanceof ConditionalBlock)) {
throw new BugInCF(
"loop condition successor is not ConditionalBlock, but: "
+ conditionalBlock.getClass());
}

// add the blocks into a static datastructure in the calledmethodsatf, such that it can
// analyze
// them (call MustCallConsistencyAnalyzer.analyzeFulfillingLoops, which in turn adds the trees
// to the static datastructure in McoeAtf)
PotentiallyFulfillingLoop pfLoop =
new PotentiallyFulfillingLoop(
loop.getExpression(),
loopVarNode.getTree(),
node.getTree(),
loopBodyEntryBlock,
block,
(ConditionalBlock) conditionalBlock,
loopVarNode);
this.analyzeObligationFulfillingLoop(cfg, pfLoop);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use equals() instead of == for Name comparison.

At line 3373, the code uses == to compare Name objects:

isAssignmentOfIterVar = iterVarDecl.getName() == loopVariable.getName();

According to the established pattern for this codebase, Name comparisons should use Name.equals() or Objects.equals() instead of identity comparison (==), unless the Interning Checker is explicitly used to guarantee reference equality.

🔧 Proposed fix
-          isAssignmentOfIterVar = iterVarDecl.getName() == loopVariable.getName();
+          isAssignmentOfIterVar = Objects.equals(iterVarDecl.getName(), loopVariable.getName());
🤖 Prompt for AI Agents
In
@checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java
around lines 3340 - 3437, In
MustCallConsistencyAnalyzer.patternMatchEnhancedCollectionForLoop replace the
identity comparison of Name objects with an equality check: change the line that
sets isAssignmentOfIterVar (currently using iterVarDecl.getName() ==
loopVariable.getName()) to use Name.equals(...) or
Objects.equals(iterVarDecl.getName(), loopVariable.getName()) so the iterator
variable name is compared by value rather than by reference; ensure you import
java.util.Objects if you use Objects.equals and update only that comparison in
the method.

Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In @docs/manual/resource-leak-checker.tex:
- Around line 645-651: Replace the vague phrase "properly disposed of" in the
sentence starting "What the checker effectively verifies is that any resource
collection is properly disposed of." with the more precise wording "required
methods are definitely called on all elements of any resource collection" so the
description aligns with must-call terminology used elsewhere in the document.
- Around line 867-871: Summary: The phrase "Assignments in initializer blocks
are not permitted" is ambiguous about whether it means instance initializers,
static initializers, or both; replace it with an explicit statement such as
"Assignments in instance or static initializer blocks are not permitted" (or
specify only instance or only static if that was the intent) and update the
surrounding text to use that clarified terminology; specifically modify the
sentence beginning "Assignments in initializer blocks are not permitted" so it
names "instance initializer blocks" and/or "static initializer blocks" and
ensure consistency with the rest of the paragraph where constructor and method
entry semantics are discussed.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 036ebd8 and 5d9dc72.

📒 Files selected for processing (1)
  • docs/manual/resource-leak-checker.tex
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: In MustCallConsistencyAnalyzer.addObligationsForOwningCollectionReturn, treat a null result from CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(...) as “no obligations” and skip creating CollectionObligation(s); do not throw. This can occur for OwningCollection over non-resource element types.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: When handling collection ownership in MustCallConsistencyAnalyzer, do not throw if CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(..) returns null; treat it as “no obligations” and skip creating CollectionObligation(s). Also, when constructing diagnostics that reference an element method name, fall back to "Unknown" if the list is null/empty.
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.
📚 Learning: 2025-10-22T20:43:32.957Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: In MustCallConsistencyAnalyzer.addObligationsForOwningCollectionReturn, treat a null result from CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(...) as “no obligations” and skip creating CollectionObligation(s); do not throw. This can occur for OwningCollection over non-resource element types.

Applied to files:

  • docs/manual/resource-leak-checker.tex
📚 Learning: 2025-10-22T20:43:32.957Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java:767-798
Timestamp: 2025-10-22T20:43:32.957Z
Learning: When handling collection ownership in MustCallConsistencyAnalyzer, do not throw if CollectionOwnershipAnnotatedTypeFactory.getMustCallValuesOfResourceCollectionComponent(..) returns null; treat it as “no obligations” and skip creating CollectionObligation(s). Also, when constructing diagnostics that reference an element method name, fall back to "Unknown" if the list is null/empty.

Applied to files:

  • docs/manual/resource-leak-checker.tex
📚 Learning: 2025-10-22T20:41:01.112Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java:128-136
Timestamp: 2025-10-22T20:41:01.112Z
Learning: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.

Applied to files:

  • docs/manual/resource-leak-checker.tex
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: typetools.checker-framework (inference_part1_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part2_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part1_jdk25)
  • GitHub Check: typetools.checker-framework (misc_jdk25)
  • GitHub Check: typetools.checker-framework (nonjunit_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part2_jdk25)
  • GitHub Check: typetools.checker-framework (junit_jdk25)
🔇 Additional comments (4)
docs/manual/resource-leak-checker.tex (4)

138-158: LGTM! Clear introduction to collection ownership annotations.

The subsection effectively introduces the new collection ownership annotations with appropriate cross-references to detailed sections. The organization and writing are clear and professional.


668-766: LGTM! Comprehensive documentation of the ownership type system.

The type hierarchy, defaults, and ownership transfer rules are clearly explained with helpful code examples. The documentation effectively covers parameter defaults, return type defaults, and assignment behavior.


889-956: Clarify status of commented-out iterator section.

This large commented-out section (~67 lines) documents iterator behavior over resource collections. If this feature is implemented and ready, the section should be uncommented. If it's not yet ready or has been deferred, the commented section should be removed to avoid confusion.

Consider either:

  1. Uncomment if the iterator feature is fully implemented and tested
  2. Remove if this is deferred work
  3. Add a brief note explaining why it's commented (if it's intentionally kept for near-term work)

Please clarify the status of iterator support and whether this documentation should be included in this PR.


873-880: Excellent documentation of field ownership restrictions.

The restrictions on owned resource collection fields are clearly enumerated and well-justified by the explanation that "the checker doesn't allow transferring the ownership away from such a field." This helps users understand both what is prohibited and why.

The four bullet points clearly cover the main restriction scenarios.

Comment on lines +645 to +651
The Resource Leak Checker handles homogeneous collections of resources. In a homogeneous collection, every element
has exactly the same must-call and called-methods properties. Instances of \texttt{java.util.Iterable} are supported;
this section calls those ``collections''.
% Usage of \texttt{java.util.Iterator}s over \texttt{java.util.Iterable}s are also supported, but they are not considered collections.

What the checker effectively verifies is that any resource collection is properly disposed of. That is, there is some loop that definitely calls the required methods on all elements of the collection.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: Consider clarifying "properly disposed of" for precision.

Line 650 states "What the checker effectively verifies is that any resource collection is properly disposed of." Consider being more precise, such as: "What the checker effectively verifies is that required methods are definitely called on all elements of any resource collection."

This aligns better with the must-call terminology used throughout the rest of the documentation.

🤖 Prompt for AI Agents
In @docs/manual/resource-leak-checker.tex around lines 645 - 651, Replace the
vague phrase "properly disposed of" in the sentence starting "What the checker
effectively verifies is that any resource collection is properly disposed of."
with the more precise wording "required methods are definitely called on all
elements of any resource collection" so the description aligns with must-call
terminology used elsewhere in the document.

Comment on lines 867 to 871
\begin{itemize}
\item Write \code{List<? extends Socket>}. This rejects calls to \<add()>
or other methods that require an instance of the type variable, but it
preserves some of the behavior (e.g., calls to \<remove()> are permitted).
This is the best choice most of the time if the \<List> is not intended to
be owning.
\item Write \code{List<@MustCall Socket>}. This makes it an error to
add a Socket to the list, since the type of the Socket is
\<@MustCall("close")> but the list requires \<@MustCall()>.
\item Suppress one or more warnings.
\item Assignments in initializer blocks are not permitted.
\item Assignments in a declaration initializer are permitted if the right-hand side is at most \texttt{@OwningCollectionWithoutObligation}.
\item Thanks to the above two restrictions, it can be assumed that an owned resource collection field is \texttt{@OwningCollectionWithoutObligation} at the beginning of a constructor. An assignment to the field is now allowed if the field is \texttt{@OwningCollectionWithoutObligation} just before the assignment. This condition holds in any other (non-constructor) method too, but the field is \texttt{@OwningCollection} at the method entrance, unlike for a constructor. A legal assignment to an owning resource collection field resolves the obligation of the right-hand side.
\end{itemize}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Clarify "initializer blocks" terminology.

Line 868 states "Assignments in initializer blocks are not permitted" but it's not immediately clear whether this refers to instance initializers, static initializers, or both. Consider being more specific, e.g., "Assignments in instance or static initializer blocks are not permitted."

🤖 Prompt for AI Agents
In @docs/manual/resource-leak-checker.tex around lines 867 - 871, Summary: The
phrase "Assignments in initializer blocks are not permitted" is ambiguous about
whether it means instance initializers, static initializers, or both; replace it
with an explicit statement such as "Assignments in instance or static
initializer blocks are not permitted" (or specify only instance or only static
if that was the intent) and update the surrounding text to use that clarified
terminology; specifically modify the sentence beginning "Assignments in
initializer blocks are not permitted" so it names "instance initializer blocks"
and/or "static initializer blocks" and ensure consistency with the rest of the
paragraph where constructor and method entry semantics are discussed.

Comment on lines +957 to +958
\subsectionAndLabel{JDK Method Signatures}{resource-leak-collections-jdk-methods}
[[TODO: Add a table with a number of collection/iterable methods and their collection ownership annotations]]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address TODO before merging.

The placeholder "[[TODO: Add a table with a number of collection/iterable methods and their collection ownership annotations]]" indicates incomplete documentation. Documentation for JDK method signatures would help users understand how standard library methods interact with the collection ownership system.

This should be completed before merging, or if the documentation is being deferred, add a note indicating that users should refer to the annotated JDK sources or provide a link to where this information can be found.

Would you like me to help generate a starter table of common JDK method signatures based on the implementation in the codebase?

Copy link
Contributor

@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.

♻️ Duplicate comments (6)
checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java (5)

763-764: 🧹 Nitpick | 🔵 Trivial

Do not expose mutable called-methods backing set.

Return an unmodifiable view (or immutable copy) from getCalledMethods().

Suggested change
     public Set<String> getCalledMethods() {
-      return calledMethods;
+      return Collections.unmodifiableSet(calledMethods);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`
around lines 763 - 764, The getter
RLCCalledMethodsAnnotatedTypeFactory.getCalledMethods currently returns the
mutable backing field calledMethods; change it to return an unmodifiable view or
immutable copy (for example Collections.unmodifiableSet(calledMethods) or new
HashSet<>(calledMethods)) so callers cannot mutate the internal set while
preserving the existing field name and behavior.

780-781: 🧹 Nitpick | 🔵 Trivial

Reuse cached rlc instead of re-fetching checker.

Use the existing field for consistency and less indirection.

Suggested change
-    MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer =
-        new MustCallConsistencyAnalyzer(ResourceLeakUtils.getResourceLeakChecker(this), true);
+    MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer =
+        new MustCallConsistencyAnalyzer(rlc, true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`
around lines 780 - 781, Replace the call to
ResourceLeakUtils.getResourceLeakChecker(this) when creating the
MustCallConsistencyAnalyzer with the already-cached field rlc to avoid
re-fetching the checker; update the MustCallConsistencyAnalyzer instantiation
(the mustCallConsistencyAnalyzer variable creation) to pass rlc instead of
ResourceLeakUtils.getResourceLeakChecker(this) so the analyzer uses the existing
cached checker.

795-797: ⚠️ Potential issue | 🟠 Major

Containment check should require non-empty node mapping.

Line 796 checks only != null; if the API returns an empty collection, this may trigger loop analysis for non-contained trees.

Suggested change
-        boolean loopContainedInThisMethod =
-            cfg.getNodesCorrespondingToTree(collectionElementTree) != null;
+        List<?> nodes = cfg.getNodesCorrespondingToTree(collectionElementTree);
+        boolean loopContainedInThisMethod = nodes != null && !nodes.isEmpty();
#!/bin/bash
set -euo pipefail

# Verify API semantics and current call-site usage.
CFG_FILE="$(fd 'ControlFlowGraph.java$' | head -n1)"
echo "Inspecting: ${CFG_FILE}"

rg -nP 'getNodesCorrespondingToTree\s*\(' "$CFG_FILE" -C3
rg -nP 'return\s+Collections\.emptyList\(|return\s+null' "$CFG_FILE" -C3 || true

echo
echo "Null-only containment checks in Java sources:"
rg -nP 'getNodesCorrespondingToTree\([^)]*\)\s*!=\s*null' --type java -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`
around lines 795 - 797, The containment check currently uses
cfg.getNodesCorrespondingToTree(collectionElementTree) != null which can be true
for an empty collection; change it to first capture the result into a local
(e.g., List/Collection nodes =
cfg.getNodesCorrespondingToTree(collectionElementTree)) and then set
loopContainedInThisMethod = (nodes != null && !nodes.isEmpty()) so the check
requires a non-empty mapping; update the logic in
RLCCalledMethodsAnnotatedTypeFactory where loopContainedInThisMethod is computed
to use this non-empty check.

487-506: ⚠️ Potential issue | 🟡 Minor

Fix Javadoc type wording and typos.

Javadocs still mention CFStore (Line 492) and contain “wether”/“bodys” typos (Lines 504, 770).

Also applies to: 768-771

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`
around lines 487 - 506, The Javadoc for methods in
RLCCalledMethodsAnnotatedTypeFactory uses the wrong type name and contains
typos: replace occurrences of "CFStore" with "AccumulationStore" in the Javadoc
for getStoreAfterBlock and the conditional-store method (the method returning
then/else stores), and fix misspellings "wether" → "whether" and "bodys" →
"bodies" (also update any duplicated occurrences around the conditional-store
Javadoc). Ensure parameter descriptions and return tags reference
AccumulationStore and correct spelling for clarity.

122-166: ⚠️ Potential issue | 🟠 Major

Avoid static mutable loop-tracking state in the type factory.

potentiallyFulfillingLoops being static and directly exposed creates cross-analysis contamination/leak risks. Make it an instance field and return an unmodifiable view.

Refactor sketch
- private static final Set<PotentiallyFulfillingLoop> potentiallyFulfillingLoops = new HashSet<>();
+ private final Set<PotentiallyFulfillingLoop> potentiallyFulfillingLoops = new HashSet<>();

- public static void addPotentiallyFulfillingLoop(...)
+ public void addPotentiallyFulfillingLoop(...)

- public static Set<PotentiallyFulfillingLoop> getPotentiallyFulfillingLoops() {
-   return potentiallyFulfillingLoops;
+ public Set<PotentiallyFulfillingLoop> getPotentiallyFulfillingLoops() {
+   return Collections.unmodifiableSet(potentiallyFulfillingLoops);
  }
Based on learnings: In checker/src/main/java/org/checkerframework/checker/collectionownership/CollectionOwnershipAnnotatedTypeFactory.java, prefer instance fields over static state for analysis caches (e.g., maps keyed by Tree/Block). There is at most one ATF per running checker, so static mutable fields are discouraged.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`
around lines 122 - 166, Change the static mutable set potentiallyFulfillingLoops
into an instance field and expose it only via an unmodifiable view: remove the
static modifier from the field and initialize it (new HashSet<>()) in the
RLCCalledMethodsAnnotatedTypeFactory constructor; make
addPotentiallyFulfillingLoop and getPotentiallyFulfillingLoops instance methods
(non-static) that operate on this instance field; have
getPotentiallyFulfillingLoops return
Collections.unmodifiableSet(potentiallyFulfillingLoops); and update any callers
to invoke the instance methods on the RLCCalledMethodsAnnotatedTypeFactory
object instead of statically.
checker/src/main/java/org/checkerframework/checker/rlccalledmethods/messages.properties (1)

1-2: ⚠️ Potential issue | 🟡 Minor

Use consistent “of …” phrasing in override diagnostics.

These two messages still say “Incompatible ownership for …”; prior review requested “of return type” / “of parameter …” for precision and consistency.

Suggested wording update
-owning.override.return=Incompatible ownership for return.%nfound   : no ownership annotation or `@Owning`%nrequired: `@NotOwning`%nConsequence: method %s in %s cannot override method %s in %s
-owning.override.param=Incompatible ownership for parameter %s.%nfound   : no ownership annotation or `@NotOwning`%nrequired: `@Owning`%nConsequence: method %s in %s cannot override method %s in %s
+owning.override.return=Incompatible ownership of return type.%nfound   : no ownership annotation or `@Owning`%nrequired: `@NotOwning`%nConsequence: method %s in %s cannot override method %s in %s
+owning.override.param=Incompatible ownership of parameter %s.%nfound   : no ownership annotation or `@NotOwning`%nrequired: `@Owning`%nConsequence: method %s in %s cannot override method %s in %s
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/messages.properties`
around lines 1 - 2, Update the two diagnostic messages to use consistent "of
..." phrasing: change the key owning.override.return's message to refer to "of
return type" (e.g., "Incompatible ownership of return type.%nfound ... required
...") and change owning.override.param's message to refer to "of parameter %s"
(e.g., "Incompatible ownership of parameter %s.%nfound ... required ..."),
keeping the rest of the wording and format intact so the %s placeholders and
consequences remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/messages.properties`:
- Around line 1-2: Update the two diagnostic messages to use consistent "of ..."
phrasing: change the key owning.override.return's message to refer to "of return
type" (e.g., "Incompatible ownership of return type.%nfound ... required ...")
and change owning.override.param's message to refer to "of parameter %s" (e.g.,
"Incompatible ownership of parameter %s.%nfound ... required ..."), keeping the
rest of the wording and format intact so the %s placeholders and consequences
remain unchanged.

In
`@checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java`:
- Around line 763-764: The getter
RLCCalledMethodsAnnotatedTypeFactory.getCalledMethods currently returns the
mutable backing field calledMethods; change it to return an unmodifiable view or
immutable copy (for example Collections.unmodifiableSet(calledMethods) or new
HashSet<>(calledMethods)) so callers cannot mutate the internal set while
preserving the existing field name and behavior.
- Around line 780-781: Replace the call to
ResourceLeakUtils.getResourceLeakChecker(this) when creating the
MustCallConsistencyAnalyzer with the already-cached field rlc to avoid
re-fetching the checker; update the MustCallConsistencyAnalyzer instantiation
(the mustCallConsistencyAnalyzer variable creation) to pass rlc instead of
ResourceLeakUtils.getResourceLeakChecker(this) so the analyzer uses the existing
cached checker.
- Around line 795-797: The containment check currently uses
cfg.getNodesCorrespondingToTree(collectionElementTree) != null which can be true
for an empty collection; change it to first capture the result into a local
(e.g., List/Collection nodes =
cfg.getNodesCorrespondingToTree(collectionElementTree)) and then set
loopContainedInThisMethod = (nodes != null && !nodes.isEmpty()) so the check
requires a non-empty mapping; update the logic in
RLCCalledMethodsAnnotatedTypeFactory where loopContainedInThisMethod is computed
to use this non-empty check.
- Around line 487-506: The Javadoc for methods in
RLCCalledMethodsAnnotatedTypeFactory uses the wrong type name and contains
typos: replace occurrences of "CFStore" with "AccumulationStore" in the Javadoc
for getStoreAfterBlock and the conditional-store method (the method returning
then/else stores), and fix misspellings "wether" → "whether" and "bodys" →
"bodies" (also update any duplicated occurrences around the conditional-store
Javadoc). Ensure parameter descriptions and return tags reference
AccumulationStore and correct spelling for clarity.
- Around line 122-166: Change the static mutable set potentiallyFulfillingLoops
into an instance field and expose it only via an unmodifiable view: remove the
static modifier from the field and initialize it (new HashSet<>()) in the
RLCCalledMethodsAnnotatedTypeFactory constructor; make
addPotentiallyFulfillingLoop and getPotentiallyFulfillingLoops instance methods
(non-static) that operate on this instance field; have
getPotentiallyFulfillingLoops return
Collections.unmodifiableSet(potentiallyFulfillingLoops); and update any callers
to invoke the instance methods on the RLCCalledMethodsAnnotatedTypeFactory
object instead of statically.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63f0c0f and ea3601a.

📒 Files selected for processing (2)
  • checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsAnnotatedTypeFactory.java
  • checker/src/main/java/org/checkerframework/checker/rlccalledmethods/messages.properties

Copy link
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java`:
- Line 28: The regex in DependentTypesError (Pattern.compile("\\[error for
expression: (.*); error: [.*]\\]")) incorrectly uses a character class [.*]
instead of a second capturing group, causing groupCount() == 2 and group(2) to
fail; change the pattern so it defines two capturing groups to match the
FORMAT_STRING's two placeholders (e.g. replace [.*] with (.*) or an appropriate
capturing pattern), update the Pattern.compile call accordingly, and run tests
to ensure groupCount() and subsequent group(1)/group(2) usages succeed.

In `@framework/tests/h1h2checker/TypeRefinement.java`:
- Around line 14-15: The test has a split/malformed directive across two lines
(line 14 contains "// :: error: [h1h2checker.h1invalid.forbidden] :: warning:"
and line 15 contains a plain comment with the warning key), so merge them into a
single directive comment and ensure both the error and warning keys are on the
same directive line (e.g., a single "// :: error:
[h1h2checker.h1invalid.forbidden] :: warning:
(cast.unsafe.constructor.invocation)" comment) so the test harness can parse the
warning key; update the directive near the TypeRefinement test declaration to
use that single-line format.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea3601a and 88ca42c.

📒 Files selected for processing (12)
  • checker/tests/README.md
  • checker/tests/i18n-formatter/ConversionCategoryTest.java
  • checker/tests/i18n-formatter/HasFormat.java
  • checker/tests/i18n-formatter/I18nFormat.java
  • checker/tests/i18n-formatter/I18nFormatForTest.java
  • checker/tests/i18n-formatter/IsFormat.java
  • checker/tests/i18n-formatter/ManualExampleI18nFormatter.java
  • checker/tests/i18n-formatter/Syntax.java
  • framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java
  • framework/tests/h1h2checker/TypeRefinement.java
  • framework/tests/reportmodifiers/TestModifiers.java
  • framework/tests/reporttreekinds/TestTreeKinds.java

Copy link
Contributor

@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.

♻️ Duplicate comments (1)
framework/tests/h1h2checker/TypeRefinement.java (1)

14-15: ⚠️ Potential issue | 🔴 Critical

Consolidate split warning directive to single line.

The warning directive is split across two lines: line 14 ends with :: warning: without a key, and line 15 contains the key in a plain comment rather than a directive comment. This malformed pattern prevents the test harness from parsing the warning key.

🔧 Suggested fix (as previously recommended)
-    // :: error: [h1h2checker.h1invalid.forbidden] :: warning:
-    // [cast.unsafe.constructor.invocation]
+    // :: error: [h1h2checker.h1invalid.forbidden] :: warning: [cast.unsafe.constructor.invocation]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/tests/h1h2checker/TypeRefinement.java` around lines 14 - 15,
Consolidate the split test-harness directive into a single well-formed comment:
replace the two-line malformed sequence (the line containing ":: error:
[h1h2checker.h1invalid.forbidden] :: warning:" and the following line containing
just "[cast.unsafe.constructor.invocation]") with one complete directive comment
so each warning key is part of the same directive (e.g., a single comment that
includes both [h1h2checker.h1invalid.forbidden] and
[cast.unsafe.constructor.invocation] as part of the :: error/:: warning
directive). Locate the directive near TypeRefinement.java's test assertion and
update the comment so the harness can parse the warning keys correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/tests/h1h2checker/TypeRefinement.java`:
- Around line 14-15: Consolidate the split test-harness directive into a single
well-formed comment: replace the two-line malformed sequence (the line
containing ":: error: [h1h2checker.h1invalid.forbidden] :: warning:" and the
following line containing just "[cast.unsafe.constructor.invocation]") with one
complete directive comment so each warning key is part of the same directive
(e.g., a single comment that includes both [h1h2checker.h1invalid.forbidden] and
[cast.unsafe.constructor.invocation] as part of the :: error/:: warning
directive). Locate the directive near TypeRefinement.java's test assertion and
update the comment so the harness can parse the warning keys correctly.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88ca42c and 9cae831.

📒 Files selected for processing (1)
  • framework/tests/h1h2checker/TypeRefinement.java

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.

3 participants