Skip to content
Open
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
158e737
Suppresses RLC non-final field overwrite warning for safe constructor…
iamsanjaymalakar Apr 18, 2025
325e983
Add `@FindDistinct` annotation
mernst Apr 18, 2025
4cb90d7
Resolves PR review comments.
iamsanjaymalakar Apr 22, 2025
9d4d403
Merge branch 'master' into 7049-dev
iamsanjaymalakar Jun 13, 2025
c524542
Addresses PR comments.
iamsanjaymalakar Jun 13, 2025
a9a1e84
Merge ../checker-framework-branch-master into 7049-dev
mernst Jul 10, 2025
dbd6447
Merge branch 'master' into 7049-dev
iamsanjaymalakar Jul 14, 2025
29071ca
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Jul 14, 2025
14aebc4
Merge ../checker-framework-branch-master into 7049-dev
mernst Jul 14, 2025
87b838c
Punctuation
mernst Jul 14, 2025
8ad095b
Improve comments
mernst Jul 14, 2025
fa2aef4
Tweak coment and code
mernst Jul 14, 2025
96c47f8
Better comments.
iamsanjaymalakar Jul 15, 2025
9a70147
Merge ../checker-framework-branch-master into 7049-dev
mernst Jul 20, 2025
4010022
Add test showing error message at wrong location
mernst Jul 20, 2025
4765ecf
Comments and variable name
mernst Jul 20, 2025
143f3f4
Enhance test
mernst Jul 20, 2025
b11cd19
Merge ../checker-framework-branch-master into 7049-dev
mernst Jul 21, 2025
dda12eb
Merge branch 'master' into 7049-dev
iamsanjaymalakar Aug 30, 2025
d78354e
Merge branch 'master' into 7049-dev
iamsanjaymalakar Oct 4, 2025
81c8cab
Resolves PR comments. Mike's new test is passing now.
iamsanjaymalakar Oct 7, 2025
f3b9022
Merge ../checker-framework-branch-master into 7049-dev
mernst Oct 9, 2025
0d53a06
Merge ../checker-framework-branch-master into 7049-dev
mernst Oct 10, 2025
07617a7
Improve documentation and naming
mernst Oct 10, 2025
c188d0f
Naming
mernst Oct 10, 2025
7f4360a
Add tests
mernst Oct 10, 2025
7eb84c4
Addresses Mike's review comments. Adds test.
iamsanjaymalakar Oct 11, 2025
827e573
Merge branch 'master' into 7049-dev
iamsanjaymalakar Oct 11, 2025
80d52c2
Replaces == with equals.
iamsanjaymalakar Oct 11, 2025
a5d1f60
Method call in instance initializer block.
iamsanjaymalakar Oct 11, 2025
786d477
Update checker/tests/resourceleak-firstinitconstructor/InstanceInitia…
mernst Oct 12, 2025
5d9d51a
Merge ../checker-framework-branch-master into 7049-dev
mernst Oct 12, 2025
46687a1
Expand tests
mernst Oct 12, 2025
679cbdf
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Oct 12, 2025
000a833
Fix spelling
mernst Oct 12, 2025
2d9e299
More tests
mernst Oct 12, 2025
d55afd1
Side effecting constructor calls that may rewrite field. Updates tests.
iamsanjaymalakar Oct 17, 2025
93210dd
Reverts == with .equals due to interning:not.interned error.
iamsanjaymalakar Oct 17, 2025
04d72ba
Merge branch 'master' into 7049-dev
iamsanjaymalakar Oct 17, 2025
2d1691a
Merge branch 'master' into 7049-dev
msridhar Jan 5, 2026
43490e6
Nullness checker errors from azure build pipeline.
iamsanjaymalakar Jan 6, 2026
2429215
Nullness checker errors from azure build pipeline.
iamsanjaymalakar Jan 6, 2026
33f4adf
Nullness checker errors from azure build pipeline.
iamsanjaymalakar Jan 6, 2026
caa85b1
Merge ../checker-framework-branch-master into 7049-dev
mernst Jan 21, 2026
a7eb9a8
Documentation edits
mernst Jan 22, 2026
df08e91
Complete the method renaming
mernst Jan 22, 2026
a891075
Removes a redundant null check.
iamsanjaymalakar Jan 22, 2026
f74552f
Merge branch 'master' into 7049-dev
iamsanjaymalakar Jan 24, 2026
17468f6
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 2, 2026
9bd6d38
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 4, 2026
aa0e9e9
Makes the scanner more conservative.
iamsanjaymalakar Feb 5, 2026
4d93d90
Suppresses interning checker warning.
iamsanjaymalakar Feb 5, 2026
b6a6daf
Fixes typo.
iamsanjaymalakar Feb 5, 2026
48ea240
Merge branch 'master' into 7049-dev
iamsanjaymalakar Feb 5, 2026
219c5b5
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 6, 2026
7b7f3f9
Adds object allocation side effect check for inline initializer block.
iamsanjaymalakar Feb 6, 2026
1d0fdae
Coderabbit suggestions.
iamsanjaymalakar Feb 6, 2026
11ebf6c
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 9, 2026
705019a
Tweaks
mernst Feb 9, 2026
80f0d1f
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Feb 9, 2026
6aa49ee
Simplify wording
mernst Feb 9, 2026
efc7b7c
Remove warning suppressions
mernst Feb 9, 2026
af3ca85
Brevity
mernst Feb 9, 2026
80e4041
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 9, 2026
ece1b44
Merge branch 'master' into 7049-dev
iamsanjaymalakar Feb 10, 2026
6c806fb
Merge branch 'master' into 7049-dev
iamsanjaymalakar Feb 11, 2026
b9af79d
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 20, 2026
6a3e7d0
Merge ../checker-framework-branch-master into 7049-dev
mernst Feb 27, 2026
52f6d69
Merge ../checker-framework-branch-master into 7049-dev
mernst Mar 2, 2026
869bb89
Merge ../checker-framework-branch-master into 7049-dev
mernst Mar 2, 2026
57985ba
Put error key in brackets
mernst Mar 2, 2026
481e926
Merge ../checker-framework-fork-mernst-branch-more-square-brackets in…
mernst Mar 2, 2026
4babfbd
Put error key in brackets
mernst Mar 2, 2026
7668ccc
Undo a change
mernst Mar 2, 2026
cb5f029
Another pair of brackets
mernst Mar 2, 2026
7305395
Merge ../checker-framework-fork-mernst-branch-more-square-brackets in…
mernst Mar 2, 2026
224f485
Merge ../checker-framework-branch-master into more-square-brackets
mernst Mar 3, 2026
775a2db
Add space around heading
mernst Mar 3, 2026
f26dca7
Merge ../checker-framework-branch-master into more-square-brackets
mernst Mar 3, 2026
8e127b5
Merge ../checker-framework-fork-mernst-branch-more-square-brackets in…
mernst Mar 3, 2026
29b6694
Merge ../checker-framework-branch-master into 7049-dev
mernst Mar 3, 2026
e3f1c5d
Merge ../checker-framework-branch-master into 7049-dev
mernst Mar 4, 2026
b278f86
Addresses Mike's PR review comments.
iamsanjaymalakar Apr 3, 2026
d6b1e99
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 3, 2026
9b70eec
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 3, 2026
4d88f93
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 4, 2026
3e5fcd2
Merge ../checker-framework-branch-master into 7049-dev
mernst Apr 7, 2026
f9b8a87
Merge ../checker-framework-branch-master into 7049-dev
mernst Apr 9, 2026
db0b98b
Add comment
mernst Apr 9, 2026
4fccac1
Use named `instanceof`
mernst Apr 9, 2026
96dee63
Eliminate need for warning suppression
mernst Apr 9, 2026
f5420f7
Tweaks
mernst Apr 9, 2026
1bc4989
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Apr 9, 2026
27703ee
Addresses PR comments.
iamsanjaymalakar Apr 11, 2026
f55e800
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 12, 2026
a4b23a5
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Apr 13, 2026
e9fcc45
Merge ../checker-framework-branch-master into 7049-dev
mernst Apr 14, 2026
1f04475
Merge ../checker-framework-branch-master into 7049-dev
mernst Apr 14, 2026
544c7f8
Code review changes
mernst Apr 14, 2026
05247d8
Update contract: return node must have an expression
mernst Apr 14, 2026
013cc24
Merge branch '7049-dev' of github.com:iamsanjaymalakar/checker-framew…
mernst Apr 14, 2026
9764391
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 17, 2026
b5cd39c
Add import statement
mernst Apr 19, 2026
38ef995
Addresses PR comments.
iamsanjaymalakar Apr 20, 2026
038ae36
improve documentation.
iamsanjaymalakar Apr 20, 2026
6bdd2e9
Merge branch 'master' into 7049-dev
iamsanjaymalakar Apr 20, 2026
8f1a689
Adds @FindDistinct.
iamsanjaymalakar Apr 20, 2026
3476b4d
Adds @InternedDistinct.
iamsanjaymalakar Apr 20, 2026
6a62d27
Code review changes
mernst Apr 22, 2026
54448fd
Merge ../checker-framework-branch-master into 7049-dev
mernst Apr 22, 2026
e2b0f40
Merge ../checker-framework-fork-iamsanjaymalakar-branch-7049-dev into…
mernst Apr 22, 2026
131101b
Merge pull request #1 from iamsanjaymalakar/7049-dev-2
iamsanjaymalakar Apr 22, 2026
0868839
Merge branch 'master' into 7049-dev
mernst Apr 22, 2026
6b01f8b
Handles nested assignments.
iamsanjaymalakar 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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.checkerframework.checker.test.junit;

import java.io.File;
import java.util.List;
import org.checkerframework.checker.resourceleak.ResourceLeakChecker;
import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
import org.junit.runners.Parameterized.Parameters;

/**
* Tests for validating safe suppression of resource leak warnings when a private field is
* initialized for the first time inside a constructor.
*
* <p>These tests check that the checker allows first-time constructor-based assignments (when safe)
* and continues to report reassignments or leaks in all other cases (e.g., after method calls,
* initializer blocks, etc.).
*/
public class ResourceLeakFirstInitConstructorTest extends CheckerFrameworkPerDirectoryTest {
public ResourceLeakFirstInitConstructorTest(List<File> testFiles) {
super(
testFiles,
ResourceLeakChecker.class,
"resourceleak-firstinitconstructor",
"-AwarnUnneededSuppressions",
"-encoding",
"UTF-8");
}

@Parameters
public static String[] getTestDirs() {
return new String[] {"resourceleak-firstinitconstructor"};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Test: Field is initialized in one constructor and reassigned in another via this() chaining.
// Expected: Warning in constructor and open() due to reassignments.

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class ConstructorChainingLeak {
private @Owning FileInputStream s;

public ConstructorChainingLeak() throws Exception {
this(42);
Comment thread
iamsanjaymalakar marked this conversation as resolved.
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
}

private ConstructorChainingLeak(int x) throws Exception {
s = new FileInputStream("test.txt");
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Test: Field is explicitly initialized to null and assigned in constructor.
// Expected: No warning in constructor, warning in open().

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class ExplicitNullInitializer {
private @Owning FileInputStream s = null;

public ExplicitNullInitializer() {
try {
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
27 changes: 27 additions & 0 deletions checker/tests/resourceleak-firstinitconstructor/FinalField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class FinalField {
private int i;

private final @Owning FileInputStream s;

public FinalField() throws Exception {
havoc();
s = new FileInputStream("test.txt");
}

void havoc() {
i++;
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Demonstrates conservative behavior for assignments inside conditionals.
//
// Because the current analysis does not reason about control-flow branches,
// each assignment inside an if/else is treated as a potential re-assignment
// rather than the first write. As a result, every write below is reported,
// even though each branch actually assigns the field only once.
//
// A CFG-aware implementation would recognize that only one branch executes
// and would not warn on any of these assignments.

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class FirstAssignmentInConditional {
private @Owning FileInputStream s;

public FirstAssignmentInConditional(boolean b) {
try {
if (b) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be straightforward to handle this case. Override visitIf to compute a value for both sides. If either one is true, return true. Otherwise, if either one is false, return false`. Otherwise, return null. (And first process the condition, of course.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I could special-case this if/else, but I chose not to: any branching is now a barrier and we bail out, because partial branch handling might quickly turns into CFG dependency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see how it would turn into a CFG dependency.

// ::error: [required.method.not.called]
s = new FileInputStream("test1.txt"); // false positive: first write in this branch
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is the first assignment, I would expect that it is permitted even though there would be an error reported at the other assignment. This is based on the comment above "checks if {@code assignment} is the first write to {@code field}", which is true for this assignment. Maybe that comment needs to be clarified.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks, agreed. The earlier wording was misleading because it suggested “first write to the field” in a semantic/control-flow sense.

I updated the implementation and comments to make the policy explicit: this is an AST-only, conservative check that only recognizes a “first write” when the assignment occurs in the constructor’s straight-line initialization prefix (statements that execute in order without branching/looping). If an if (or other branching/looping construct) occurs before the target assignment, the analysis bails out and returns false.

Under that policy, an assignment inside an if branch is intentionally not treated as “first,” even if it is the first write along that branch, because proving that requires control-flow reasoning.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see that handling branches requires control-flow reasoning or a control-flow graph. It can be done purely (and simply) in the AST. If you don't want to do it, that is one thing, but please provide the correct rationale.

I think the wording you are looking for is "lexically first"; "AST-only" does not convey anything to me.

} else {
// ::error: [required.method.not.called]
s = new FileInputStream("test2.txt"); // false positive: first write in this branch
}
} catch (Exception e) {
}
}

public FirstAssignmentInConditional(boolean b1, boolean b2) {
try {
if (b1) {
if (b2) {
// ::error: [required.method.not.called]
s = new FileInputStream("test1.txt"); // false positive
} else {
// ::error: [required.method.not.called]
s = new FileInputStream("test2.txt"); // false positive
}
} else {
if (b2) {
// ::error: [required.method.not.called]
s = new FileInputStream("test1.txt"); // false positive
} else {
// ::error: [required.method.not.called]
s = new FileInputStream("test2.txt"); // false positive
}
}
} catch (Exception e) {
}
}
Comment thread
mernst marked this conversation as resolved.

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
// ignore
}
}
Comment thread
mernst marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Test: Field has no initializer and is first assigned in constructor in a try-catch block.
// Expected: No warning in constructor, warning in open().

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class FirstAssignmentInConstructor {
private @Owning FileInputStream s;

public FirstAssignmentInConstructor() {
try {
s = new FileInputStream("test.txt"); // no warning
} catch (Exception e) {
}
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Test: Field has no initializer and is first assigned in constructor, but in a constructor block.
// Expected: No warning in the first assignment in the constructor block, warning in any later
// assignments in the constructor and in open().

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class FirstAssignmentInConstructorBlock {
private @Owning FileInputStream s;

static FileInputStream s2;
static FileInputStream s3;

public FirstAssignmentInConstructorBlock() {
s = s2;
}

public FirstAssignmentInConstructorBlock(boolean b) {
{
s = s2;
}
}

public FirstAssignmentInConstructorBlock(int i) {
{
s = s2;
}
// :: error: [required.method.not.called]
s = s2;
}

public FirstAssignmentInConstructorBlock(float f) {
s = s2;
{
// :: error: [required.method.not.called]
s = s2;
}
}

public FirstAssignmentInConstructorBlock(byte b) {
{
s = s3;
}
// :: error: [required.method.not.called]
s = s2;
}

public FirstAssignmentInConstructorBlock(char c) {
s = s2;
{
// :: error: [required.method.not.called]
s = s3;
}
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Test: Field has a non-null inline initializer and is reassigned in constructor and open().
// Expected: Warning in constructor and in open().

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class InlineInitializerLeak {
private @Owning FileInputStream s = new FileInputStream("test.txt");

public InlineInitializerLeak() throws Exception {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
s = new FileInputStream("test.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.s", methods = "close")
public void close() {
try {
s.close();
} catch (Exception e) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Test: Field is assigned in an instance initializer block and reassigned later.
// Expected: Warning in initializer block, constructor and open().

import java.io.FileInputStream;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;

@InheritableMustCall({"close"})
class InstanceInitializerBlockLeak {
private @Owning FileInputStream f;

{
try {
// :: error: [required.method.not.called]
f = new FileInputStream("file.txt");
} catch (Exception e) {
}
}

public InstanceInitializerBlockLeak() {
try {
// :: error: [required.method.not.called]
f = new FileInputStream("file.txt");
} catch (Exception e) {
}
}

// :: error: [missing.creates.mustcall.for]
public void open() {
try {
// :: error: [required.method.not.called]
f = new FileInputStream("file.txt");
} catch (Exception e) {
}
}

@EnsuresCalledMethods(value = "this.f", methods = "close")
public void close() {
try {
f.close();
} catch (Exception e) {
}
}
}
Loading