Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9e5736f
fix: clear safeSymbols to prevent memory leak across files
romainbrenguier Sep 14, 2026
1131a51
fix: clear static caches after scan to prevent AST retention in long-…
romainbrenguier Sep 15, 2026
7c6dcd5
Add check state cleanup architecture test
romainbrenguier Sep 15, 2026
efc9236
Clear check state after file analysis
romainbrenguier Sep 15, 2026
1bbd24d
Reset check state before file analysis
romainbrenguier Sep 15, 2026
906e186
Fix check state cleanup regressions
romainbrenguier Sep 15, 2026
9be2055
Update ruling results for PR #6144 (#6145)
github-actions[bot] Sep 15, 2026
13e444f
Fix check state cleanup regressions and clean up no-op clears
romainbrenguier Sep 15, 2026
145e8dd
Fix RedundantTypeCastCheck UOE, simplify ClassImportCouplingCheck, ti…
romainbrenguier Sep 15, 2026
8eb787e
Extract clearState() hook in IssuableSubscriptionVisitor, revert S116…
romainbrenguier Sep 15, 2026
006ad85
Call clearState() from leaveFile(), fix unused import, clean up redun…
romainbrenguier Sep 15, 2026
7a9ed3c
Refactor checks to use clearState() instead of manual cleanup in setC…
romainbrenguier Sep 16, 2026
6a3b8a8
Document per-file state cleanup guidelines, make excludedCatchTrees f…
romainbrenguier Sep 17, 2026
01211ca
Guarantee subscription visitor state cleanup after file analysis
romainbrenguier Sep 28, 2026
1f9df19
Assert package-info check state is cleared after analysis
romainbrenguier Sep 28, 2026
c60d974
Extract visitors bridge creation out of assertThatThrownBy lambda
romainbrenguier Sep 29, 2026
d1d5231
Continue visitor state cleanup after individual failures
romainbrenguier Oct 12, 2026
bfd54f7
Do not let cleanup failures mask the original check failure
romainbrenguier Oct 12, 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
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,41 @@ JavaQuickFix quickFix = JavaQuickFix.newQuickFix("Remove redundant check")
reportIssue(tree, "Message").withQuickFix(() -> quickFix);
```

### Per-File State Cleanup

Rules that accumulate state in collections or mutable fields during file analysis **must clear that state between files**. A single rule instance is reused across all files in a module.

For `IssuableSubscriptionVisitor` subclasses, override `clearState()` — it is called automatically before and after each file:

```java
@Rule(key = "S1234")
public class MyCheck extends IssuableSubscriptionVisitor {

private final Set<Symbol> seen = new HashSet<>();

@Override
protected void clearState() {
seen.clear();
}
}
```

For `BaseTreeVisitor` / `JavaFileScanner` implementations, clear state in `scanFile()` (typically in a `finally` block):

```java
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
try {
scan(context.getTree());
} finally {
myCollection.clear();
}
}
```

**Do not** clear lazy-initialized caches derived from immutable configuration (e.g. parsed `@RuleProperty` values) — those are intentionally computed once per rule instance.

### Dependency-Aware Rules

When a rule should only apply when a specific library is present, or when its behaviour depends on a dependency version, implement `DependencyVersionAware` alongside the visitor base class:
Expand Down
6 changes: 6 additions & 0 deletions java-checks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public void setContext(JavaFileScannerContext context) {
@Override
public void leaveFile(JavaFileScannerContext context) {
withinSynchronizedBlock.clear();
super.leaveFile(context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,10 @@ private static Optional<Symbol> getIdentifierSymbol(MethodInvocationTree mit) {
IdentifierTree identifier = (IdentifierTree) callee;
return Optional.of(identifier.symbol());
}
@Override
protected void clearState() {
primaryTargets.clear();
secondaryTargets.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public class BoxedBooleanExpressionsCheck extends BaseTreeVisitor implements Jav

@Override
public void scanFile(JavaFileScannerContext context) {
ifStatementCache.clear();
firstNullCheckCache.clear();
safeSymbols.clear();
this.context = context;
try {
if (context.getSemanticModel() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,19 @@ public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implemen
private Deque<UsageStatus> usageStatusStack;
private Set<String> exceptions;
private Set<String> exceptionIdentifiers;
private Set<CatchTree> excludedCatchTrees = new HashSet<>();
private final Set<CatchTree> excludedCatchTrees = new HashSet<>();

@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
usageStatusStack = new ArrayDeque<>();
if (context.getSemanticModel() != null) {
scan(context.getTree());
try {
if (context.getSemanticModel() != null) {
scan(context.getTree());
}
} finally {
excludedCatchTrees.clear();
}
Comment thread
gitar-bot[bot] marked this conversation as resolved.
excludedCatchTrees.clear();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ public class ClassVariableVisibilityCheck extends BaseTreeVisitor implements Jav
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
isClassStack.clear();
try {
scan(context.getTree());
} finally {
isClassStack.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ public class DateEnumsCheck extends AbstractMethodDetection implements JavaVersi
private final Map<InputFile, List<CachedIssue>> issuesByFile = new HashMap<>();

@Override
public void setContext(JavaFileScannerContext context) {
super.setContext(context);
protected void clearState() {
importSupplier = null;
currentFileTotalUsageCount = 0;
currentFileNoEnumUsageCount = 0;
Expand All @@ -175,7 +174,6 @@ public void setContext(JavaFileScannerContext context) {

@Override
public void leaveFile(JavaFileScannerContext context) {
importSupplier = null;
CachedFileData restored = restoredFiles.remove(context.getInputFile());
if (restored != null) {
projectTotalUsageCount -= restored.totalCount();
Expand All @@ -200,9 +198,7 @@ public void leaveFile(JavaFileScannerContext context) {
}
}

currentFileTotalUsageCount = 0;
currentFileNoEnumUsageCount = 0;
currentFileIssues.clear();
super.leaveFile(context);
}

@Override
Expand Down Expand Up @@ -354,6 +350,7 @@ public void endOfAnalysis(ModuleScannerContext context) {
}
projectTotalUsageCount = 0;
projectTotalNoEnumUsageCount = 0;
currentFileIssues.clear();
issuesByFile.clear();
Comment thread
aurelien-coet-sonarsource marked this conversation as resolved.
restoredFiles.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ private record VariableData(Symbol symbol, String identifier, ExpressionTree ini
private final List<VariableData> hardCodedUri = new ArrayList<>();

@Override
public void setContext(JavaFileScannerContext context) {
super.setContext(context);
protected void clearState() {
annotationsStack.clear();
identifiersUsedInAnnotations.clear();
hardCodedUri.clear();
Expand All @@ -116,7 +115,8 @@ public void leaveFile(JavaFileScannerContext context) {
}
}

for (VariableData v : hardCodedUri) {
try {
for (VariableData v : hardCodedUri) {
// equals to an identifier with unknown semantic, we cannot compare their symbols
if (idNamesWithoutSemantic.contains(v.identifier())) {
continue;
Expand All @@ -128,6 +128,9 @@ public void leaveFile(JavaFileScannerContext context) {
continue;
}
reportHardcodedURI(v.initializer());
}
} finally {
super.leaveFile(context);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ public void scanFile(JavaFileScannerContext context) {
excludeIssueAtLine = 0;
this.context = context;
fileLines = context.getFileLines();
scan(context.getTree());
try {
scan(context.getTree());
} finally {
fileLines = null;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ public class InnerStaticClassesCheck extends BaseTreeVisitor implements JavaFile
@Override
public void scanFile(final JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
outerClasses.clear();
atLeastOneReference.clear();
try {
scan(context.getTree());
} finally {
outerClasses.clear();
atLeastOneReference.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,15 @@ private static String[] testMethodNames(String[] lowerCaseNames) {
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
if (context.getSemanticModel() == null) {
return;
treeStack.clear();
try {
if (context.getSemanticModel() == null) {
return;
}
scan(context.getTree());
} finally {
treeStack.clear();
}
scan(context.getTree());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@ public void endOfAnalysis(ModuleScannerContext context) {
for (String missingPackageInfo : missingPackageWithoutPackageFile) {
context.addIssueOnProject(this, "Add a 'package-info.java' file to document the '" + missingPackageInfo + "' package");
}
missingPackageWithoutPackageFile.clear();
knownPackageWithPackageFile.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,17 @@ private void addMethodReturningPrivateMutable(String methodSignature, Identifier

@Override
public void scanFile(final JavaFileScannerContext context) {
scan(context.getTree());
dataPropagationGraph.reportMutableStoreReachableByOutsideCall(context, this);
dataPropagationGraph.reportMutableFieldReachingToOutside(context, this);
dataPropagationGraph.clear();
parametersStack.clear();
methodSignatureStack.clear();
try {
scan(context.getTree());
dataPropagationGraph.reportMutableStoreReachableByOutsideCall(context, this);
dataPropagationGraph.reportMutableFieldReachingToOutside(context, this);
} finally {
parametersStack.clear();
methodSignatureStack.clear();
dataPropagationGraph.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ public class NestedIfStatementsCheck extends BaseTreeVisitor implements JavaFile
public void scanFile(final JavaFileScannerContext context) {
this.context = context;
this.nestingLevel = new ArrayDeque<>();
scan(context.getTree());
try {
scan(context.getTree());
} finally {
nestingLevel.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,13 @@ private static void put(Iterable<Tree.Kind> firstSet, Iterable<Tree.Kind> second
public void scanFile(JavaFileScannerContext context) {
this.context = context;
reportedLines.clear();
scan(context.getTree());
reportedLines.clear();
stack.clear();
try {
scan(context.getTree());
} finally {
stack.clear();
reportedLines.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ public class RawExceptionCheck extends BaseTreeVisitor implements JavaFileScanne
public void scanFile(JavaFileScannerContext context) {
this.context = (FluentReporting) context;
this.javaVersion = context.getJavaVersion();
scan(context.getTree());
exceptionsThrownByMethodInvocations.clear();
try {
scan(context.getTree());
} finally {
exceptionsThrownByMethodInvocations.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.Arrays;
import java.util.List;
import org.sonar.check.Rule;
Expand Down Expand Up @@ -111,4 +110,9 @@ private static boolean matchesWarning(JWarning warning, TypeCastTree tree) {
return tree.equals(warningTree);
}

@Override
protected void clearState() {
warnings = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -139,4 +138,9 @@ private void updateWarnings(AssignmentExpressionTree tree) {
warnings.removeIf(warning -> tree.equals(warning.syntaxTree()));
}

@Override
protected void clearState() {
warnings.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -144,4 +143,10 @@ private boolean isNotCaught(Type type) {
.noneMatch(type::isSubtypeOf);
}

@Override
protected void clearState() {
shouldCheck.clear();
tryCatches.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ public class SeveralBreakOrContinuePerLoopCheck extends BaseTreeVisitor implemen
public void scanFile(JavaFileScannerContext context) {
this.context = context;
loopCount = 0;
scan(context.getTree());
breakAndContinueCounter.clear();
currentScopeIsSwitch.clear();
try {
scan(context.getTree());
} finally {
breakAndContinueCounter.clear();
currentScopeIsSwitch.clear();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,12 @@ public void setContext(JavaFileScannerContext context) {

@Override
public void leaveFile(JavaFileScannerContext context) {
for (int i = 0; i < shiftTrees.size(); i++) {
checkShiftTree(shiftTrees.get(i), i);
try {
for (int i = 0; i < shiftTrees.size(); i++) {
checkShiftTree(shiftTrees.get(i), i);
}
} finally {
shiftTrees.clear();
}
}

Expand Down
Loading
Loading