Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ebb561b
Handle WPI loop on Unknowninitialization annotations
erfan-arvan Jun 5, 2024
30d842f
Resolved merge conflicts
erfan-arvan Jun 19, 2024
05ef6fb
Rename FlowExpression to JavaExpression in tests
smillst Jun 5, 2024
ebf782c
Update dependency io.github.classgraph:classgraph to v4.8.173 (#6653)
renovate[bot] Jun 5, 2024
5167ed1
Implement `JavaExpression.SuperReference`
jyoo980 Jun 5, 2024
ee517ea
Added a Tree argument to AnnotatedTypes.adaptParameters()
smillst Jun 6, 2024
f3a08a7
Update contributors.tex
erfan-arvan Jun 6, 2024
5df02b7
Update contributors.tex to include the latest contributors
erfan-arvan Jun 6, 2024
42d7812
Update contributors.tex to include my name
erfan-arvan Jun 6, 2024
711ae4d
How to indicate that side effects can change a value's type qualifier…
mernst Jun 6, 2024
d9a6504
Define `TreeUtils.fieldsFromClassTree()` (#6659)
mernst Jun 6, 2024
342bc94
Update contributors.tex after make it again
erfan-arvan Jun 7, 2024
b5a62d8
Try to pass CI test -> Contrubutors names
erfan-arvan Jun 7, 2024
5f3f89d
Update contributors.tex to reflect latest changes
erfan-arvan Jun 7, 2024
81f1f2b
Improve Javadoc for `getAnnotationFromJavaExpression`
smillst Jun 8, 2024
8c345fd
Add `graphviz` to list of dev dependencies for macOS
jyoo980 Jun 11, 2024
118b771
Exclude my username from contributors.tex
erfan-arvan Jun 18, 2024
4818ac2
Transferred the proposed change for handling WPI loop for unknowninit…
erfan-arvan Jun 18, 2024
134e76f
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
kelloggm Jun 19, 2024
1c31423
Rename method to adjust annotations before updating annotation set, u…
erfan-arvan Jun 18, 2024
131e864
Merge remote-tracking branch 'origin/bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jun 20, 2024
4691e84
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jun 22, 2024
872d7e1
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jun 24, 2024
33c82a6
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
kelloggm Jun 27, 2024
5b642c3
Merge branch 'typetools:master' into bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jul 1, 2024
d7acb57
Changed the name of the method, enhanced the body, and transferred th…
erfan-arvan Jul 6, 2024
1b1d56f
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jul 6, 2024
8826275
Removed unnecessary comments
erfan-arvan Jul 7, 2024
388e584
Merge branch 'typetools:master' into bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jul 7, 2024
fd16cc8
Merge branch 'bugfix/wpi-loop-unknowninitialization' of https://githu…
erfan-arvan Jul 7, 2024
eb79855
Removed the imports from the test case
erfan-arvan Jul 7, 2024
6ae7fba
Changed the name of the test case
erfan-arvan Jul 7, 2024
f057894
Update checker build.gradle to fix ainferNullnessGenerateJaifs task i…
erfan-arvan Jul 10, 2024
1a32487
Rename an instance of `VarArgsTest`
mernst Jul 7, 2024
90129a3
Give `DefaultQualifier` runtime retention
mernst Jul 7, 2024
cf451ba
Minor changes related to Non-Empty Checker
mernst Jul 8, 2024
8eafe7d
Use `filePermissions` instead of deprecated `fileMode` (#6693)
mernst Jul 8, 2024
1d5fb60
Disambiguate owners of fields in output (#6699)
mernst Jul 8, 2024
217ba50
Comment changes (#6700)
mernst Jul 8, 2024
84a1b0e
Merge ../checker-framework-branch-master into bugfix/wpi-loop-unknown…
mernst Mar 3, 2026
52b0e03
Merge ../checker-framework-branch-master into bugfix/wpi-loop-unknown…
mernst Mar 3, 2026
8aafb3b
OptionalAnnotatedTypeFactory => OptionalImplAnnotatedTypeFactory
mernst Mar 3, 2026
7b2c05f
Use
mernst Mar 3, 2026
20a8d0d
Merge ../checker-framework-branch-master into bugfix/wpi-loop-unknown…
mernst Mar 4, 2026
a5eb537
Put error message key in brackets
mernst Mar 4, 2026
49c1716
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
mernst Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion checker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -1065,4 +1065,3 @@ publishing {
signing {
sign(publishing.publications.checker)
}

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
Expand Down Expand Up @@ -953,6 +954,8 @@ public boolean isImmutable(TypeMirror type) {
@Override
public void wpiAdjustForUpdateField(
Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {
// Adjust initialization annotations first
wpiAdjustInitializationAnnotations(rhsATM, TypeUseLocation.FIELD);
Comment on lines +957 to +958
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Limit the WPI initialization rewrite to parameter updates.

These call sites rewrite @UnknownInitialization for field updates and all non-field updates. That is broader than the stated WPI fix: wpiAdjustForUpdateNonField also covers non-parameter locations such as receivers, so this can unnecessarily weaken inferred initialization annotations outside the looping case.

Please move this adjustment to the point that has the actual TypeUseLocation.PARAMETER, or otherwise pass through the real location and guard on it.

Also applies to: 977-978

🤖 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/nullness/NullnessAnnotatedTypeFactory.java`
around lines 957 - 958, The call to wpiAdjustInitializationAnnotations(rhsATM,
TypeUseLocation.FIELD) is too broad and rewrites `@UnknownInitialization` for
non-parameter updates; move this WPI initialization rewrite so it only runs for
parameter updates (i.e., at the site that has TypeUseLocation.PARAMETER) or
change the call sites (including the analogous spots around
wpiAdjustForUpdateNonField at the other location) to pass the actual
TypeUseLocation and guard inside wpiAdjustInitializationAnnotations so it only
modifies rhsATM when the location is PARAMETER, leaving field/receiver updates
unchanged.

// Synthetic variable names contain "#". Ignore them.
if (!rhsATM.hasPrimaryAnnotation(Nullable.class) || fieldName.contains("#")) {
return;
Expand All @@ -971,11 +974,42 @@ public void wpiAdjustForUpdateField(
// then change rhs to @Nullable
@Override
public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {
// Adjust initialization annotations first
wpiAdjustInitializationAnnotations(rhsATM, TypeUseLocation.OTHERWISE);
if (rhsATM.hasPrimaryAnnotation(MonotonicNonNull.class)) {
rhsATM.replaceAnnotation(NULLABLE);
}
}

/**
* Changes the type of {@code rhsATM} when assigned to any pseudo-assignment, for use by
* whole-program inference.
*
* <p>If {@code rhsATM} is Nullable or MonotonicNonNull and has the UnknownInitialization
* annotation, replace it with {@code UnknownInitialization(java.lang.Object.class)}. This ensures
* that if there is a constructor where the type hasn't been initialized, its effect is
* considered. Otherwise, whole-program inference might get stuck in a loop.
*
* @param rhsATM the type of the right-hand side of the pseudo-assignment, which is side-effected
* by this method
* @param defLoc the location where the annotation will be added. It is either a FIELD or a
* non-field (OTHERWISE).
*/
public void wpiAdjustInitializationAnnotations(
AnnotatedTypeMirror rhsATM, TypeUseLocation defLoc) {
// defLoc is defined to be used in the future if needed to differentiate between field and
// non-field locations
if ((rhsATM.hasPrimaryAnnotation(Nullable.class)
|| rhsATM.hasPrimaryAnnotation(MonotonicNonNull.class))) {
for (AnnotationMirror anno : rhsATM.getPrimaryAnnotations()) {
if (AnnotationUtils.areSameByName(
anno, "org.checkerframework.checker.initialization.qual" + ".UnknownInitialization")) {
rhsATM.replaceAnnotation(UNKNOWN_INITIALIZATION);
}
}
}
}
Comment on lines +998 to +1011
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the exact content of the method and check if defLoc is used
cat -n checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java | sed -n '994,1007p'

Repository: typetools/checker-framework

Length of output: 820


🏁 Script executed:

# Check the AnnotatedTypeMirror class to understand getPrimaryAnnotations() return type
fd -e java -x grep -l "class AnnotatedTypeMirror" {}

Repository: typetools/checker-framework

Length of output: 157


🏁 Script executed:

# Look for similar patterns where getPrimaryAnnotations is used in a loop with replaceAnnotation
rg -n "for.*getPrimaryAnnotations\(\)" --type java -A 5 -B 1

Repository: typetools/checker-framework

Length of output: 31135


🏁 Script executed:

# Check if there are other methods that iterate over getPrimaryAnnotations and modify annotations
rg -n "getPrimaryAnnotations\(\)" --type java -A 2 -B 1 | grep -E "(for|while|replaceAnnotation)" -A 2 -B 2 | head -60

Repository: typetools/checker-framework

Length of output: 6768


🏁 Script executed:

# Check getPrimaryAnnotations() return type in AnnotatedTypeMirror
rg -n "getPrimaryAnnotations\(\)" framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java -B 2 -A 2

Repository: typetools/checker-framework

Length of output: 1802


🏁 Script executed:

# Check the actual implementation of getPrimaryAnnotations() method
sed -n '1,3000p' framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java | grep -n "getPrimaryAnnotations" -B 1 -A 5 | head -40

Repository: typetools/checker-framework

Length of output: 1711


🏁 Script executed:

# Look at the replaceAnnotation implementation
rg -n "public.*replaceAnnotation" framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java -A 10

Repository: typetools/checker-framework

Length of output: 1149


🏁 Script executed:

# Check if WholeProgramInferenceJavaParserStorage pattern at 574-576 has issues reported
sed -n '570,585p' framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java

Repository: typetools/checker-framework

Length of output: 943


Unused parameter defLoc.

The defLoc parameter is never used in this method. The comment at lines 996-997 indicates it's "defined to be used in the future," but if it's truly needed later, consider using @SuppressWarnings("unused") to document intent explicitly.

Additionally, the loop pattern at lines 1000-1005 may cause ConcurrentModificationException. Iterating over getPrimaryAnnotations() while calling replaceAnnotation() modifies the underlying annotation set during iteration. See AnnotatedTypeReplacer.java (lines 112-117) for an explicit warning about this pattern. Collect items to process first, then modify after the loop:

Suggested fix
List<AnnotationMirror> annotationsToReplace = new ArrayList<>();
for (AnnotationMirror anno : rhsATM.getPrimaryAnnotations()) {
  if (AnnotationUtils.areSameByName(
      anno, "org.checkerframework.checker.initialization.qual" + ".UnknownInitialization")) {
    annotationsToReplace.add(anno);
  }
}
for (AnnotationMirror anno : annotationsToReplace) {
  rhsATM.replaceAnnotation(UNKNOWN_INITIALIZATION);
}
🤖 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/nullness/NullnessAnnotatedTypeFactory.java`
around lines 994 - 1007, The parameter defLoc in
wpiAdjustInitializationAnnotations is unused—either annotate it with
`@SuppressWarnings`("unused") or remove it—and avoid mutating rhsATM while
iterating its primary annotations: instead, iterate
rhsATM.getPrimaryAnnotations() and collect any AnnotationMirror matching
AnnotationUtils.areSameByName(... "UnknownInitialization") into a temporary
list, then after the loop call rhsATM.replaceAnnotation(UNKNOWN_INITIALIZATION)
for each collected annotation; this prevents ConcurrentModificationException
when modifying the annotation set during iteration.


@Override
public boolean wpiShouldInferTypesForReceivers() {
// All receivers must be non-null, or the dereference involved in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
* @checker_framework.manual #optional-checker Optional Checker
*/
public class OptionalImplVisitor
extends BaseTypeVisitor</* OptionalAnnotatedTypeFactory*/ BaseAnnotatedTypeFactory> {
extends BaseTypeVisitor</* OptionalImplAnnotatedTypeFactory*/ BaseAnnotatedTypeFactory> {
Comment on lines 67 to +68
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if OptionalImplAnnotatedTypeFactory exists
fd -t f "OptionalImplAnnotatedTypeFactory.java"

Repository: typetools/checker-framework

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Find and examine OptionalTransfer.java around line 50
fd -t f "OptionalTransfer.java" --exec head -100 {} \;

Repository: typetools/checker-framework

Length of output: 4747


🏁 Script executed:

#!/bin/bash
# Check the OptionalImplVisitor.java file to see the full context
fd -t f "OptionalImplVisitor.java" --exec cat -n {} \;

Repository: typetools/checker-framework

Length of output: 46549


Consider using the specific type parameter for consistency with OptionalTransfer.

The type parameter is widened to BaseAnnotatedTypeFactory while the specific OptionalImplAnnotatedTypeFactory is commented out. This creates an inconsistency with OptionalTransfer, which explicitly casts analysis.getTypeFactory() to OptionalImplAnnotatedTypeFactory in its constructor. Since OptionalImplAnnotatedTypeFactory exists and is the actual runtime type, reverting to the specific type parameter would improve type safety and code clarity without affecting functionality.

🤖 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/optional/OptionalImplVisitor.java`
around lines 67 - 68, The class OptionalImplVisitor widens its BaseTypeVisitor
type parameter to BaseAnnotatedTypeFactory; change that generic to the concrete
OptionalImplAnnotatedTypeFactory to match OptionalTransfer and improve
type-safety. Update the class declaration (OptionalImplVisitor extends
BaseTypeVisitor<OptionalImplAnnotatedTypeFactory>), then remove any unnecessary
casts and adjust usages of getTypeFactory() and any fields/constructors that
assumed BaseAnnotatedTypeFactory so they use OptionalImplAnnotatedTypeFactory
directly (this will align with OptionalTransfer's use of
analysis.getTypeFactory()).


/** The Collection type. */
private final TypeMirror collectionType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.checkerframework.checker.optional;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.optional.qual.Present;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.TreeUtils;

/** The transfer function for the Optional Checker. */
public class OptionalTransfer extends CFTransfer {

/** The {@link OptionalImplAnnotatedTypeFactory} instance for this transfer class. */
private final OptionalImplAnnotatedTypeFactory optionalTypeFactory;

/** The @{@link Present} annotation. */
private final AnnotationMirror PRESENT;

/** The element for java.util.Optional.ifPresent(). */
private final ExecutableElement optionalIfPresent;

/** The element for java.util.Optional.ifPresentOrElse(), or null. */
private final @Nullable ExecutableElement optionalIfPresentOrElse;

/**
* Create an OptionalTransfer.
*
* @param analysis the Optional Checker instance
*/
public OptionalTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
super(analysis);
optionalTypeFactory = (OptionalImplAnnotatedTypeFactory) analysis.getTypeFactory();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's locate and examine the OptionalTransfer file
find . -name "OptionalTransfer.java" -type f

Repository: typetools/checker-framework

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Execute the verification commands from the review comment
rg -nP --type=java -C3 '\bnew\s+OptionalTransfer\s*\(|\bnew\s+OptionalImplTransfer\s*\('

Repository: typetools/checker-framework

Length of output: 862


🏁 Script executed:

#!/bin/bash
# Search for class definitions
rg -nP --type=java -C3 'class\s+OptionalTransfer\b|class\s+OptionalImplTransfer\b'

Repository: typetools/checker-framework

Length of output: 1925


🏁 Script executed:

#!/bin/bash
# Search for the cast in question
rg -nP --type=java -C3 '\(OptionalImplAnnotatedTypeFactory\)\s*analysis\.getTypeFactory\(\)'

Repository: typetools/checker-framework

Length of output: 2077


🏁 Script executed:

#!/bin/bash
# Read OptionalTransfer.java to understand the full context
cat -n checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | head -80

Repository: typetools/checker-framework

Length of output: 4603


🏁 Script executed:

#!/bin/bash
# Search for where OptionalTransfer is instantiated
rg -nP --type=java 'createFlowTransferFunction|new OptionalTransfer' checker/src/main/java/org/checkerframework/checker/optional/

Repository: typetools/checker-framework

Length of output: 220


🏁 Script executed:

#!/bin/bash
# Check if OptionalTransfer or OptionalImplTransfer are subclassed
rg -nP --type=java 'extends OptionalTransfer|extends OptionalImplTransfer'

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Get the full OptionalTransfer.java file to analyze its methods
wc -l checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java

Repository: typetools/checker-framework

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Search more broadly for OptionalTransfer usage and instantiation
rg -nP --type=java 'OptionalTransfer' checker/src/main/java/org/checkerframework/checker/optional/

Repository: typetools/checker-framework

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Check if there's a base OptionalAnnotatedTypeFactory that might wire OptionalTransfer
rg -nP --type=java 'class.*OptionalAnnotatedTypeFactory|createFlowTransferFunction' checker/src/main/java/org/checkerframework/checker/optional/

Repository: typetools/checker-framework

Length of output: 220


🏁 Script executed:

#!/bin/bash
# Look for all TypeFactory classes in the optional checker
fd -e java checker/src/main/java/org/checkerframework/checker/optional/ | xargs grep -l 'class.*TypeFactory'

Repository: typetools/checker-framework

Length of output: 618


🏁 Script executed:

#!/bin/bash
# Look for all TypeFactory classes in the optional checker
fd . 'checker/src/main/java/org/checkerframework/checker/optional/' -e java | xargs grep -l 'class.*TypeFactory'

Repository: typetools/checker-framework

Length of output: 168


🏁 Script executed:

#!/bin/bash
# List all Java files in the optional checker directory
fd . 'checker/src/main/java/org/checkerframework/checker/optional/' -e java

Repository: typetools/checker-framework

Length of output: 587


🏁 Script executed:

#!/bin/bash
# Check OptionalImplAnnotatedTypeFactory to see if it wires OptionalTransfer or OptionalImplTransfer
cat -n checker/src/main/java/org/checkerframework/checker/optional/OptionalImplAnnotatedTypeFactory.java | sed -n '125,145p'

Repository: typetools/checker-framework

Length of output: 392


🏁 Script executed:

#!/bin/bash
# Search for where OptionalTransfer or OptionalImplTransfer constructors are called
rg -nP --type=java '\(new\s+OptionalTransfer|new\s+OptionalImplTransfer|OptionalTransfer\(|OptionalImplTransfer\('

Repository: typetools/checker-framework

Length of output: 571


🏁 Script executed:

#!/bin/bash
# Check OptionalChecker to see its structure and wiring
cat -n checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java

Repository: typetools/checker-framework

Length of output: 1957


🏁 Script executed:

#!/bin/bash
# Look for OptionalAnnotatedTypeFactory (without "Impl")
fd . 'checker/src/main/java/org/checkerframework/checker/optional/' -e java | xargs grep -l 'OptionalAnnotatedTypeFactory' | grep -v Impl

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Search for who creates OptionalTransfer - maybe via inheritance or factory method
rg -nP --type=java 'class OptionalChecker|createFlowTransferFunction' checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java

Repository: typetools/checker-framework

Length of output: 129


🏁 Script executed:

#!/bin/bash
# Look at the full OptionalChecker file
wc -l checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java

Repository: typetools/checker-framework

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Search across the entire codebase for any usage of OptionalTransfer
rg -nP --type=java 'OptionalTransfer' --max-count=20

Repository: typetools/checker-framework

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Check if OptionalTransfer is referenced in any imports or instantiations
rg -nP 'import.*OptionalTransfer|new OptionalTransfer'

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Look at OptionalImplChecker to understand the full architecture
cat -n checker/src/main/java/org/checkerframework/checker/optional/OptionalImplChecker.java | head -50

Repository: typetools/checker-framework

Length of output: 1965


🏁 Script executed:

#!/bin/bash
# Check if there's a separate OptionalAnnotatedTypeFactory (without Impl)
rg -nP --type=java 'class OptionalAnnotatedTypeFactory'

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Search for all TypeFactory classes and their flow transfer functions
rg -nP --type=java 'class.*TypeFactory.*extends|createFlowTransferFunction' checker/src/main/java/org/checkerframework/checker/optional/

Repository: typetools/checker-framework

Length of output: 402


🏁 Script executed:

#!/bin/bash
# Search in the NonEmptyChecker directory for any references to OptionalTransfer
rg -nP --type=java 'OptionalTransfer' checker/src/main/java/org/checkerframework/checker/nonempty/

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Get full context of OptionalImplAnnotatedTypeFactory file to understand inheritance
head -50 checker/src/main/java/org/checkerframework/checker/optional/OptionalImplAnnotatedTypeFactory.java

Repository: typetools/checker-framework

Length of output: 2113


🏁 Script executed:

#!/antml:bash
# Compare the two transfer classes more directly - get their full method lists
rg -nP --type=java '^\s+(public|private|protected).*\(' checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | head -20

Repository: typetools/checker-framework

Length of output: 260


🏁 Script executed:

#!/antml:bash
# Search entire codebase for any reference to OptionalTransfer outside the optional package
rg -nP 'OptionalTransfer' --type=java | grep -v 'OptionalImplTransfer'

Repository: typetools/checker-framework

Length of output: 496


🏁 Script executed:

#!/antml:bash
# Check if OptionalTransfer is used as a parent class or referenced in tests
rg -nP 'OptionalTransfer' --type=java

Repository: typetools/checker-framework

Length of output: 496


🏁 Script executed:

#!/antml:bash
# Look at OptionalImplTransfer more carefully to see if it extends or duplicates OptionalTransfer
head -100 checker/src/main/java/org/checkerframework/checker/optional/OptionalImplTransfer.java

Repository: typetools/checker-framework

Length of output: 4629


🏁 Script executed:

#!/antml:bash
# Check the entire OptionalTransfer file to understand all methods
tail -n +60 checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java

Repository: typetools/checker-framework

Length of output: 2152


🏁 Script executed:

#!/antml:bash
# Compare OptionalTransfer and OptionalImplTransfer to see the full extent of duplication
diff <(sed -n '48,101p' checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java) <(sed -n '91,155p' checker/src/main/java/org/checkerframework/checker/optional/OptionalImplTransfer.java)

Repository: typetools/checker-framework

Length of output: 158


🏁 Script executed:

#!/antml:bash
# Get the full length of OptionalImplTransfer to compare
wc -l checker/src/main/java/org/checkerframework/checker/optional/OptionalImplTransfer.java

Repository: typetools/checker-framework

Length of output: 160


🏁 Script executed:

#!/antml:bash
# Verify that OptionalTransfer is truly never referenced anywhere
rg -c 'OptionalTransfer' --type=java | grep -v ':0$'

Repository: typetools/checker-framework

Length of output: 154


🏁 Script executed:

#!/antml:bash
# Do a comprehensive search for OptionalTransfer in the entire codebase including test files
find . -name "*.java" -type f | xargs grep -l 'OptionalTransfer' 2>/dev/null | head -20

Repository: typetools/checker-framework

Length of output: 154


🏁 Script executed:

#!/antml:bash
# Check if OptionalTransfer appears in XML configuration files or annotation processors
find . -type f \( -name "*.xml" -o -name "*.properties" -o -name "*.txt" \) -exec grep -l 'OptionalTransfer' {} \; 2>/dev/null

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/antml:bash
# Verify the complete initialization logic of both transfer classes side by side
echo "=== OptionalTransfer Constructor ===" && sed -n '48,57p' checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java
echo -e "\n=== OptionalImplTransfer Constructor ===" && sed -n '91,107p' checker/src/main/java/org/checkerframework/checker/optional/OptionalImplTransfer.java

Repository: typetools/checker-framework

Length of output: 1877


🏁 Script executed:

#!/antml:bash
# Search for test files related to Optional
find . -path "*/test*" -name "*Optional*.java" -type f | head -10

Repository: typetools/checker-framework

Length of output: 703


🏁 Script executed:

#!/antml:bash
# Search for any dynamic instantiation or reflection-based creation of OptionalTransfer
rg -nP --type=java 'forName.*OptionalTransfer|Class\.forName|newInstance|invoke.*OptionalTransfer'

Repository: typetools/checker-framework

Length of output: 16114


🏁 Script executed:

#!/antml:bash
# Check git history to see when OptionalTransfer was created and if it was ever used
git log --oneline --all -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | head -5

Repository: typetools/checker-framework

Length of output: 319


🏁 Script executed:

#!/antml:bash
# Check if there's any recent change or if OptionalTransfer is a new class
git log -1 --format="%H %ai %s" -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java

Repository: typetools/checker-framework

Length of output: 171


🏁 Script executed:

#!/antml:bash
# Look for commits that mention OptionalTransfer
git log --oneline --all --grep='OptionalTransfer' | head -5

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/antml:bash
# Verify that OptionalTransfer is truly dead code - check if there are any extension points or registrations
rg -nP --type=java 'extends OptionalTransfer|implements.*OptionalTransfer'

Repository: typetools/checker-framework

Length of output: 53


🏁 Script executed:

#!/antml:bash
# Check the recent commit to see what OptionalTransfer was added for
git show 9c61e80c --stat | head -30

Repository: typetools/checker-framework

Length of output: 1727


🏁 Script executed:

#!/antml:bash
# Get more detail on the commit that introduced OptionalTransfer
git show 9c61e80c --name-status | grep -i optional

Repository: typetools/checker-framework

Length of output: 1461


🏁 Script executed:

#!/antml:bash
# Check if OptionalTransfer was re-added after the deletion
git log --oneline -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | head -10

Repository: typetools/checker-framework

Length of output: 113


🏁 Script executed:

#!/antml:bash
# Get the full history including when it was re-added
git log --all --oneline --follow -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java

Repository: typetools/checker-framework

Length of output: 477


🏁 Script executed:

#!/antml:bash
# Check the recent commits to see when OptionalTransfer was added back
git log --oneline -20 -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java

Repository: typetools/checker-framework

Length of output: 113


🏁 Script executed:

#!/antml:bash
# Check what commit a5eb5378 did - specifically regarding OptionalTransfer
git show a5eb5378 -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | head -100

Repository: typetools/checker-framework

Length of output: 4917


🏁 Script executed:

#!/antml:bash
# Get the full diff of that commit to understand the context
git show a5eb5378 --stat | grep -i optional

Repository: typetools/checker-framework

Length of output: 3917


🏁 Script executed:

#!/antml:bash
# Check if OptionalTransfer was added in a5eb5378
git show a5eb5378 --name-status | grep OptionalTransfer

Repository: typetools/checker-framework

Length of output: 154


🏁 Script executed:

#!/antml:bash
# Check if there's any plan to use OptionalTransfer or if it's truly abandoned
git log --all -p a5eb5378^..a5eb5378 -- checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java | tail -50

Repository: typetools/checker-framework

Length of output: 112


OptionalTransfer is dead code that duplicates OptionalImplTransfer and should be removed.

OptionalTransfer is never instantiated anywhere in the codebase. The wiring chain OptionalImplAnnotatedTypeFactory.createFlowTransferFunction() returns only new OptionalImplTransfer(analysis), never OptionalTransfer. The cast at line 50 and the initialStore() method (lines 60–100) are identical to OptionalImplTransfer (lines 91–155), creating dead code duplication and maintenance burden. This class appears to be orphaned from a refactoring; either remove it or document its intended purpose.

🤖 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/optional/OptionalTransfer.java`
at line 50, OptionalTransfer is dead duplicated code; remove it to eliminate
redundancy. Delete the OptionalTransfer class entirely (including the cast
optionalTypeFactory = (OptionalImplAnnotatedTypeFactory)
analysis.getTypeFactory(); and its initialStore() implementation) and ensure no
callers reference it — the factory
OptionalImplAnnotatedTypeFactory.createFlowTransferFunction() already constructs
OptionalImplTransfer, so rely on OptionalImplTransfer (and keep its
initialStore() implementation at lines referenced in that class) as the single
transfer implementation; if there was intended special behavior, consolidate
that behavior into OptionalImplTransfer instead of keeping OptionalTransfer.

Elements elements = optionalTypeFactory.getElementUtils();
PRESENT = AnnotationBuilder.fromClass(elements, Present.class);
ProcessingEnvironment env = optionalTypeFactory.getProcessingEnv();
optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env);
optionalIfPresentOrElse =
TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env);
}

@Override
public CFStore initialStore(UnderlyingAST underlyingAST, List<LocalVariableNode> parameters) {

CFStore result = super.initialStore(underlyingAST, parameters);

if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) {
// Check whether this lambda is an argument to `Optional.ifPresent()` or
// `Optional.ifPresentOrElse()`. If so, then within the lambda, the receiver of the
// `ifPresent*` method is @Present.
CFGLambda cfgLambda = (CFGLambda) underlyingAST;
LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree();
List<? extends VariableTree> lambdaParams = lambdaTree.getParameters();
if (lambdaParams.size() == 1) {
TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
Tree lambdaParent = lambdaPath.getParentPath().getLeaf();
Comment on lines +72 to +73
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential NPE if getParentPath() returns null.

While unlikely in practice (a lambda should always have a parent), getParentPath() can return null for root elements. Adding a null check would make the code more defensive.

🛡️ Proposed fix
         TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
-        Tree lambdaParent = lambdaPath.getParentPath().getLeaf();
+        TreePath parentPath = lambdaPath.getParentPath();
+        if (parentPath == null) {
+          return result;
+        }
+        Tree lambdaParent = parentPath.getLeaf();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
Tree lambdaParent = lambdaPath.getParentPath().getLeaf();
TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
TreePath parentPath = lambdaPath.getParentPath();
if (parentPath == null) {
return result;
}
Tree lambdaParent = parentPath.getLeaf();
🤖 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/optional/OptionalTransfer.java`
around lines 72 - 73, The code assumes getParentPath() on the TreePath returned
by optionalTypeFactory.getPath(lambdaTree) is non-null; add a defensive
null-check after obtaining TreePath lambdaPath (from
optionalTypeFactory.getPath(lambdaTree)) and handle a null parentPath before
calling getLeaf(): if lambdaPath.getParentPath() is null, either return early
(e.g., skip processing this lambda) or log/throw a clear error, otherwise
proceed to assign lambdaParent = lambdaPath.getParentPath().getLeaf(); this
prevents a potential NPE when computing lambdaParent.

if (lambdaParent instanceof MethodInvocationTree) {
MethodInvocationTree invok = (MethodInvocationTree) lambdaParent;
ExecutableElement methodElt = TreeUtils.elementFromUse(invok);
if (methodElt.equals(optionalIfPresent) || methodElt.equals(optionalIfPresentOrElse)) {
// `underlyingAST` is an invocation of `Optional.ifPresent()` or
// `Optional.ifPresentOrElse()`. In the lambda, the receiver is @Present.
ExpressionTree methodSelectTree = TreeUtils.withoutParens(invok.getMethodSelect());
ExpressionTree receiverTree = ((MemberSelectTree) methodSelectTree).getExpression();
JavaExpression receiverJe = JavaExpression.fromTree(receiverTree);
result.insertValue(receiverJe, PRESENT);
}
}
}
}

// TODO: Similar logic to the above can be applied in the Nullness Checker.
// Some methods take a function as an argument, guaranteeing that, if the function is
// called:
// * the value passed to the function is non-null
// * some other argument to the method is non-null
// Examples:
// * Jodd's `StringUtil.ifNotNull()`
// * `Opt.ifPresent()`
// * `Opt.map()`

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
interface Game {

@org.checkerframework.dataflow.qual.SideEffectFree
void newGame();
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
GameImpl implements // package-private
Game {

private MoveValidator moveValidator;

@org.checkerframework.dataflow.qual.Impure
public GameImpl(MoveValidator mValidator) {
mValidator.setGame(this);
moveValidator = mValidator;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public GameImpl() {
}

@org.checkerframework.dataflow.qual.SideEffectFree
public void newGame( // package-private
GameImpl this) {
// Implementation of starting a new game
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
interface MoveValidator {

@org.checkerframework.dataflow.qual.Impure
void setGame( MoveValidator this, Game game);
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
PlayerDependentMoveValidator implements // package-private
MoveValidator {

private Game game;

private MoveValidator blackMoveValidator = new SimpleMoveValidator();

@org.checkerframework.dataflow.qual.Impure
public void setGame( // package-private
PlayerDependentMoveValidator this, Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public PlayerDependentMoveValidator() {
}

@org.checkerframework.dataflow.qual.Impure
public PlayerDependentMoveValidator( Game game) {
this.setGame(game);
blackMoveValidator.setGame(game);
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
SimpleMoveValidator implements // package-private
MoveValidator {

private Game game;

@org.checkerframework.dataflow.qual.Impure
public void setGame( // package-private
SimpleMoveValidator this, Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public SimpleMoveValidator() {
}

@org.checkerframework.dataflow.qual.Pure
public MoveValidator createMoveValidator() {
return new PlayerDependentMoveValidator(game);
}
public void test(){
PlayerDependentMoveValidator g1 =
new PlayerDependentMoveValidator(game);
this.game = g1.game;
}
Comment on lines +86 to +90
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same visibility issue as the NullnessChecker variant.

The test() method at line 89 accesses g1.game, but game is declared as private at line 44 in PlayerDependentMoveValidator. This should match the non-annotated version where game is public.

Additionally, the test() method lacks the @org.checkerframework.dataflow.qual.* annotation present on other methods.

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

In
`@checker/tests/ainfer-nullness/input-annotation-files/WPIUnknownInitializationLoopTest-org.checkerframework.checker.nullness.KeyForSubchecker.ajava`
around lines 86 - 90, The test() method accesses g1.game but
PlayerDependentMoveValidator declares game as private; change the visibility of
the game field in PlayerDependentMoveValidator to public to match the
non-annotated variant so test() can access g1.game, and add the same
`@org.checkerframework.dataflow.qual`.* annotation used on the other methods in
this file to the test() method (i.e., mirror the annotation applied to the other
methods) so its nullness/dataflow contract matches the rest of the class.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
interface Game {

@org.checkerframework.dataflow.qual.SideEffectFree
void newGame();
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
GameImpl implements // package-private
Game {

private @org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.MonotonicNonNull MoveValidator moveValidator;

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.moveValidator" })
@org.checkerframework.dataflow.qual.Impure
public GameImpl(MoveValidator mValidator) {
mValidator.setGame(this);
moveValidator = mValidator;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public GameImpl() {
}

@org.checkerframework.framework.qual.EnsuresQualifier(expression = { "this.moveValidator" }, qualifier = org.checkerframework.checker.nullness.qual.Nullable.class)
@org.checkerframework.dataflow.qual.SideEffectFree
public void newGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
GameImpl this) {
// Implementation of starting a new game
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
interface MoveValidator {

@org.checkerframework.dataflow.qual.Impure
void setGame(@org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game);
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
PlayerDependentMoveValidator implements // package-private
MoveValidator {

private @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game;

private @org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull MoveValidator blackMoveValidator = new SimpleMoveValidator();

@org.checkerframework.framework.qual.EnsuresQualifier(expression = { "this.game" }, qualifier = org.checkerframework.checker.nullness.qual.Nullable.class)
@org.checkerframework.dataflow.qual.Impure
public void setGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
PlayerDependentMoveValidator this, @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public PlayerDependentMoveValidator() {
}

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "#1" })
@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Impure
public PlayerDependentMoveValidator(@org.checkerframework.checker.initialization.qual.UnknownInitialization(java.lang.Object.class) @org.checkerframework.checker.nullness.qual.Nullable Game game) {
this.setGame(game);
blackMoveValidator.setGame(game);
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
SimpleMoveValidator implements // package-private
MoveValidator {

private @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.MonotonicNonNull Game game;

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Impure
public void setGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
SimpleMoveValidator this, @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.NonNull Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public SimpleMoveValidator() {
}

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Pure
public @org.checkerframework.checker.initialization.qual.UnderInitialization(PlayerDependentMoveValidator.class) @org.checkerframework.checker.nullness.qual.NonNull MoveValidator createMoveValidator() {
return new PlayerDependentMoveValidator(game);
}
public void test(){
PlayerDependentMoveValidator g1 =
new PlayerDependentMoveValidator(game);
this.game = g1.game;
}
}
Loading
Loading