From 9e5736ffc03f57e3b0e4b969fc153bb9d91556ed Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 14 Sep 2026 17:20:18 +0200 Subject: [PATCH 01/18] fix: clear safeSymbols to prevent memory leak across files --- .../java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java index c29ee75521e..23c8db532e1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java @@ -88,6 +88,7 @@ public class BoxedBooleanExpressionsCheck extends BaseTreeVisitor implements Jav @Override public void scanFile(JavaFileScannerContext context) { + safeSymbols.clear(); this.context = context; try { if (context.getSemanticModel() != null) { From 1131a510f65410cd5670728c9ed3241f37ee380a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 09:45:23 +0200 Subject: [PATCH 02/18] fix: clear static caches after scan to prevent AST retention in long-lived processes Move cache clearing (ifStatementCache, firstNullCheckCache, safeSymbols) from the start of scanFile to a finally block, so the last scanned file's AST is not retained for the lifetime of the plugin classloader in IDE sessions (SonarLint). Co-Authored-By: Claude Opus 4.6 --- .../org/sonar/java/checks/BoxedBooleanExpressionsCheck.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java index 23c8db532e1..39637682d0f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/BoxedBooleanExpressionsCheck.java @@ -88,6 +88,8 @@ public class BoxedBooleanExpressionsCheck extends BaseTreeVisitor implements Jav @Override public void scanFile(JavaFileScannerContext context) { + ifStatementCache.clear(); + firstNullCheckCache.clear(); safeSymbols.clear(); this.context = context; try { From 7c6dcd559ab13741a950a4680db886385a146ae4 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 10:04:39 +0200 Subject: [PATCH 03/18] Add check state cleanup architecture test --- java-checks/pom.xml | 6 + .../java/checks/CheckStateCleanupTest.java | 121 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java diff --git a/java-checks/pom.xml b/java-checks/pom.xml index 4d6679c1b10..c156f27fdd7 100644 --- a/java-checks/pom.xml +++ b/java-checks/pom.xml @@ -83,6 +83,12 @@ junit-jupiter test + + com.tngtech.archunit + archunit-junit5 + 1.5.0 + test + org.mockito mockito-core diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java new file mode 100644 index 00000000000..3267ed89c86 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -0,0 +1,121 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaCodeUnit; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaMethodCall; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.JavaFileScannerContext; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +class CheckStateCleanupTest { + + private static final DescribedPredicate CHECK_CLASSES = new DescribedPredicate<>("check classes") { + @Override + public boolean test(JavaClass input) { + return input.isAnnotatedWith(Rule.class); + } + }; + + private static final ArchCondition CLEAR_COLLECTION_FIELDS = new ArchCondition<>("clear collection fields at file boundaries") { + @Override + public void check(JavaClass checkClass, ConditionEvents events) { + Set collectionFields = checkClass.getFields().stream() + .filter(field -> !Modifier.isStatic(field.reflect().getModifiers())) + .filter(CheckStateCleanupTest::isCollectionField) + .collect(java.util.stream.Collectors.toSet()); + for (JavaField field : collectionFields) { + for (JavaMethod lifecycleMethod : lifecycleMethods(checkClass)) { + if (!clearsField(lifecycleMethod, field, checkClass, new HashSet<>())) { + events.add(SimpleConditionEvent.violated(checkClass, + checkClass.getFullName() + " does not clear " + field.getName() + " in " + lifecycleMethod.getName())); + } + } + } + } + }; + + @Test + void collection_fields_are_cleared_at_file_boundaries() { + classes().that(CHECK_CLASSES).should(CLEAR_COLLECTION_FIELDS).check(new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages("org.sonar.java.checks")); + } + + private static boolean isCollectionField(JavaField field) { + return field.getRawType().isAssignableTo(Collection.class) || field.getRawType().isAssignableTo(Map.class); + } + + private static Set lifecycleMethods(JavaClass checkClass) { + if (checkClass.isAssignableTo(IssuableSubscriptionVisitor.class)) { + Set methods = methodsNamed(checkClass, "setContext", JavaFileScannerContext.class); + methods.addAll(methodsNamed(checkClass, "leaveFile", JavaFileScannerContext.class)); + return methods; + } + return methodsNamed(checkClass, "scanFile", JavaFileScannerContext.class); + } + + private static Set methodsNamed(JavaClass checkClass, String name, Class parameterType) { + Set methods = new HashSet<>(); + for (JavaMethod method : checkClass.getMethods()) { + if (method.getName().equals(name) + && method.getRawParameterTypes().size() == 1 + && method.getRawParameterTypes().get(0).isEquivalentTo(parameterType)) { + methods.add(method); + } + } + return methods; + } + + private static boolean clearsField(JavaCodeUnit method, JavaField field, JavaClass checkClass, Set visited) { + if (!visited.add(method)) { + return false; + } + boolean accessesField = field.getAccessesToSelf().stream().anyMatch(access -> access.getOrigin().equals(method)); + boolean clearsCollection = method.getMethodCallsFromSelf().stream().anyMatch(CheckStateCleanupTest::isClearCall); + if (accessesField && clearsCollection) { + return true; + } + return method.getMethodCallsFromSelf().stream() + .map(JavaMethodCall::getTarget) + .map(target -> target.resolveMember().orElse(null)) + .filter(javaMethod -> javaMethod != null && javaMethod.getOwner().equals(checkClass)) + .anyMatch(calledMethod -> clearsField(calledMethod, field, checkClass, visited)); + } + + private static boolean isClearCall(JavaMethodCall call) { + return call.getName().equals("clear") + && (call.getTargetOwner().isAssignableTo(Collection.class) || call.getTargetOwner().isAssignableTo(Map.class)); + } +} From efc92369903b3e42e179f2ca2aaf987c4d3ef75b Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 10:14:26 +0200 Subject: [PATCH 04/18] Clear check state after file analysis --- .../CatchUsesExceptionWithContextCheck.java | 15 +++++++++--- .../checks/ClassVariableVisibilityCheck.java | 7 +++++- .../org/sonar/java/checks/DateEnumsCheck.java | 1 + .../checks/DepthOfInheritanceTreeCheck.java | 17 +++++++++++--- .../sonar/java/checks/HardcodedURICheck.java | 8 ++++++- .../sonar/java/checks/IndentationCheck.java | 10 ++++++-- .../java/checks/InnerStaticClassesCheck.java | 9 +++++++- .../java/checks/LazyArgEvaluationCheck.java | 11 ++++++--- .../sonar/java/checks/MagicNumberCheck.java | 10 +++++--- .../java/checks/MissingPackageInfoCheck.java | 2 ++ .../java/checks/MutableMembersUsageCheck.java | 15 ++++++++---- .../java/checks/NestedIfStatementsCheck.java | 6 ++++- .../java/checks/OperatorPrecedenceCheck.java | 9 ++++++-- .../sonar/java/checks/RawExceptionCheck.java | 7 +++++- .../SeveralBreakOrContinuePerLoopCheck.java | 9 +++++++- .../java/checks/ShiftOnIntOrLongCheck.java | 8 +++++-- .../StaticFieldInitializationCheck.java | 3 +++ .../sonar/java/checks/StaticMethodCheck.java | 9 ++++++-- .../java/checks/TrailingCommentCheck.java | 3 +++ .../checks/TypeParametersShadowingCheck.java | 6 ++++- .../java/checks/WaitInWhileLoopCheck.java | 1 + .../java/checks/design/BrainMethodCheck.java | 1 + .../design/ClassImportCouplingCheck.java | 19 ++++++++++++--- .../checks/naming/BadPackageNameCheck.java | 1 + .../checks/security/CookieHttpOnlyCheck.java | 23 ++++++++++++------- .../checks/security/SecureCookieCheck.java | 21 ++++++++++++----- .../SpringBeansShouldBeAccessibleCheck.java | 3 +++ .../unused/UnusedPrivateFieldCheck.java | 13 ++++++++--- .../java/checks/CheckStateCleanupTest.java | 5 ++++ 29 files changed, 201 insertions(+), 51 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java index 0c9852fa668..37a50d9170c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java @@ -112,10 +112,19 @@ public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implemen 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 { + if (exceptions != null) { + exceptions.clear(); + } + if (exceptionIdentifiers != null) { + exceptionIdentifiers.clear(); + } + excludedCatchTrees.clear(); } - excludedCatchTrees.clear(); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/ClassVariableVisibilityCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ClassVariableVisibilityCheck.java index 8cd68cb9308..e9cde2e8f94 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ClassVariableVisibilityCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ClassVariableVisibilityCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java index 85661fe36bf..02576d074a9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java @@ -354,6 +354,7 @@ public void endOfAnalysis(ModuleScannerContext context) { } projectTotalUsageCount = 0; projectTotalNoEnumUsageCount = 0; + currentFileIssues.clear(); issuesByFile.clear(); restoredFiles.clear(); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java index 0ef14bdba85..81019ce0f46 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java @@ -66,8 +66,19 @@ public class DepthOfInheritanceTreeCheck extends BaseTreeVisitor implements Java @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (context.getSemanticModel() != null) { - scan(context.getTree()); + if (filteredPatterns != null) { + filteredPatterns.clear(); + } + filteredPatterns = null; + try { + if (context.getSemanticModel() != null) { + scan(context.getTree()); + } + } finally { + if (filteredPatterns != null) { + filteredPatterns.clear(); + } + filteredPatterns = null; } } @@ -112,7 +123,7 @@ private List getPatterns() { String.join(",", FRAMEWORK_EXCLUSION_PATTERNS), filteredClasses ); - filteredPatterns = Arrays.asList(PatternUtils.createPatterns(permittedPatterns)); + filteredPatterns = new ArrayList<>(Arrays.asList(PatternUtils.createPatterns(permittedPatterns))); } return filteredPatterns; } diff --git a/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java b/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java index ddd434ceebb..d10a77a4ffe 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java @@ -116,7 +116,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; @@ -128,6 +129,11 @@ public void leaveFile(JavaFileScannerContext context) { continue; } reportHardcodedURI(v.initializer()); + } + } finally { + annotationsStack.clear(); + identifiersUsedInAnnotations.clear(); + hardCodedUri.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java index c75c8d95503..24eaa44f72d 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java @@ -16,6 +16,7 @@ */ package org.sonar.java.checks; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -66,8 +67,13 @@ public void scanFile(JavaFileScannerContext context) { isBlockAlreadyReported = false; excludeIssueAtLine = 0; this.context = context; - fileLines = context.getFileLines(); - scan(context.getTree()); + fileLines = new ArrayList<>(context.getFileLines()); + try { + scan(context.getTree()); + } finally { + fileLines.clear(); + fileLines = null; + } } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/InnerStaticClassesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/InnerStaticClassesCheck.java index a81094d04dc..f383958a40a 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/InnerStaticClassesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/InnerStaticClassesCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/LazyArgEvaluationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/LazyArgEvaluationCheck.java index 19cd9f66300..29bae332432 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/LazyArgEvaluationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/LazyArgEvaluationCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java index bbedb771db1..27b1c22e9f2 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java @@ -57,10 +57,14 @@ public class MagicNumberCheck extends BaseTreeVisitor implements JavaFileScanner public void scanFile(JavaFileScannerContext context) { this.context = context; this.authorizedNumbersList = new ArrayList<>(); - for (String s : authorizedNumbers.split(",")) { - authorizedNumbersList.add(new BigDecimal(s.trim())); + try { + for (String s : authorizedNumbers.split(",")) { + authorizedNumbersList.add(new BigDecimal(s.trim())); + } + scan(context.getTree()); + } finally { + authorizedNumbersList.clear(); } - scan(context.getTree()); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/MissingPackageInfoCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MissingPackageInfoCheck.java index fdb811311d1..0744db6cc96 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MissingPackageInfoCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MissingPackageInfoCheck.java @@ -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(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/MutableMembersUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MutableMembersUsageCheck.java index 9bcfbce860a..cbe6b54eca1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MutableMembersUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MutableMembersUsageCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java index 2578268d7c0..bdc52bbef55 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java index f2a68104a4f..ae59b2313ce 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java @@ -105,8 +105,13 @@ private static void put(Iterable firstSet, Iterable 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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java index 55967f1a051..a72e3338844 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/SeveralBreakOrContinuePerLoopCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SeveralBreakOrContinuePerLoopCheck.java index ed338e48495..8fabfc7d476 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SeveralBreakOrContinuePerLoopCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SeveralBreakOrContinuePerLoopCheck.java @@ -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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/ShiftOnIntOrLongCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ShiftOnIntOrLongCheck.java index 79e05b43585..e06f7f14ba0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ShiftOnIntOrLongCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ShiftOnIntOrLongCheck.java @@ -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(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java index c6d0fee683f..121a071cae1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java @@ -56,6 +56,9 @@ public List nodesToVisit() { @Override public void setContext(JavaFileScannerContext context) { + classWithSynchronizedMethod.clear(); + withinStaticInitializer.clear(); + methodUsesLocks.clear(); classWithSynchronizedMethod.push(false); withinStaticInitializer.push(false); methodUsesLocks.push(false); diff --git a/java-checks/src/main/java/org/sonar/java/checks/StaticMethodCheck.java b/java-checks/src/main/java/org/sonar/java/checks/StaticMethodCheck.java index 0d36d886a78..58e23ee3c4c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/StaticMethodCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/StaticMethodCheck.java @@ -92,8 +92,13 @@ public class StaticMethodCheck extends BaseTreeVisitor implements JavaFileScanne @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (context.getSemanticModel() != null) { - scan(context.getTree()); + methodReferences.clear(); + try { + if (context.getSemanticModel() != null) { + scan(context.getTree()); + } + } finally { + methodReferences.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java index 4fe1ee6ec07..44827eac514 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java @@ -63,6 +63,9 @@ public void setContext(JavaFileScannerContext context) { if (pattern == null) { pattern = Pattern.compile(legalCommentPattern); } + if (visitedTokens != null) { + visitedTokens.clear(); + } visitedTokens = new HashSet<>(); super.setContext(context); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java index e4063da1465..a07c8e21829 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java @@ -42,7 +42,11 @@ public class TypeParametersShadowingCheck extends BaseTreeVisitor implements Jav public void scanFile(JavaFileScannerContext context) { this.context = context; currentTypeParametersInScope = new HashMap<>(); - scan(context.getTree()); + try { + scan(context.getTree()); + } finally { + currentTypeParametersInScope.clear(); + } } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java index 74d5362ddb0..6f6bea6d50b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java @@ -37,6 +37,7 @@ public class WaitInWhileLoopCheck extends AbstractMethodDetection { @Override public void setContext(JavaFileScannerContext context) { + inWhileLoop.clear(); inWhileLoop.push(false); super.setContext(context); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/design/BrainMethodCheck.java b/java-checks/src/main/java/org/sonar/java/checks/design/BrainMethodCheck.java index 8efe36b6f5f..ec651236805 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/design/BrainMethodCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/design/BrainMethodCheck.java @@ -128,6 +128,7 @@ public void endOfAnalysis(ModuleScannerContext context) { IssueFound issueFound = issuesFound.get(i); defaultContext.reportIssue(issueFound.analyzerMessage); } + issuesFound.clear(); } private static class IssueFound { diff --git a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java index bc1c5658435..59e3a0ab211 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java @@ -51,9 +51,22 @@ public class ClassImportCouplingCheck extends AbstractCouplingChecker { @Override public void scanFile(JavaFileScannerContext context) { - super.scanFile(context); - imports = null; - secondaryLocations = null; + if (imports != null) { + imports.clear(); + } + if (secondaryLocations != null) { + secondaryLocations.clear(); + } + try { + super.scanFile(context); + } finally { + if (imports != null) { + imports.clear(); + } + if (secondaryLocations != null) { + secondaryLocations.clear(); + } + } } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java b/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java index 06fc65579ef..b454b0001e0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java @@ -66,6 +66,7 @@ public boolean scanWithoutParsing(InputFileScannerContext context) { @Override public void scanFile(JavaFileScannerContext context) { + badPackageNames.clear(); var packageDeclaration = context.getTree().packageDeclaration(); String name = packageDeclaration != null ? PackageUtils.packageName(packageDeclaration, ".") : ""; if (context.getCacheContext().isCacheEnabled()) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java index 5c3fdb06483..6b75be22147 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java @@ -156,14 +156,21 @@ public void setContext(JavaFileScannerContext context) { @Override public void leaveFile(JavaFileScannerContext context) { - for (TypeTree typeTree : symbolConstructorMapToReport.values()) { - reportIssue(typeTree, MESSAGE); - } - for (MethodInvocationTree mit : settersToReport) { - reportIssue(mit.arguments(), MESSAGE); - } - for (TypeTree typeTree : newClassToReport) { - reportIssue(typeTree, MESSAGE); + try { + for (TypeTree typeTree : symbolConstructorMapToReport.values()) { + reportIssue(typeTree, MESSAGE); + } + for (MethodInvocationTree mit : settersToReport) { + reportIssue(mit.arguments(), MESSAGE); + } + for (TypeTree typeTree : newClassToReport) { + reportIssue(typeTree, MESSAGE); + } + } finally { + ignoredVariables.clear(); + symbolConstructorMapToReport.clear(); + settersToReport.clear(); + newClassToReport.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java index abbdea47430..6c3e2da8e28 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java @@ -160,12 +160,21 @@ public void setContext(JavaFileScannerContext context) { @Override public void leaveFile(JavaFileScannerContext context) { - cookieConstructors.forEach(r -> reportIssue(r.identifier(), MESSAGE)); - deletionCandidateSecureCalls.forEach((symbol, mit) -> { - if (!maxAgeZeroSeen.contains(symbol)) { - reportIssue(mit.arguments(), MESSAGE); - } - }); + try { + cookieConstructors.forEach(r -> reportIssue(r.identifier(), MESSAGE)); + deletionCandidateSecureCalls.forEach((symbol, mit) -> { + if (!maxAgeZeroSeen.contains(symbol)) { + reportIssue(mit.arguments(), MESSAGE); + } + }); + } finally { + unsecuredCookies.clear(); + deletionCandidateCookies.clear(); + cookieConstructors.clear(); + maxAgeZeroSeen.clear(); + deletionCandidateSecureCalls.clear(); + enclosingClass.clear(); + } } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheck.java index fdaf4d7a914..c2b853304a3 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheck.java @@ -106,6 +106,9 @@ public void endOfAnalysis(ModuleScannerContext context) { // support sub-packages .filter(entry -> packagesScannedBySpringAtProjectLevel.stream().noneMatch(entry.getKey()::contains)) .forEach(entry -> entry.getValue().forEach(defaultContext::reportIssue)); + messagesPerPackage.clear(); + packagesScannedBySpringAtProjectLevel.clear(); + packagesScannedBySpringAtFileLevel.clear(); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index 206372ce7d4..505d1074385 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -97,13 +97,20 @@ public void setContext(JavaFileScannerContext context) { @Override public void leaveFile(JavaFileScannerContext context) { - if (!hasNativeMethod) { - classes.forEach(this::checkClassFields); + try { + if (!hasNativeMethod) { + classes.forEach(this::checkClassFields); + } + } finally { + clearState(); } - clearState(); } private void clearState() { + if (ignoredAnnotations != null) { + ignoredAnnotations.clear(); + ignoredAnnotations = null; + } classes.clear(); assignments.clear(); unknownIdentifiers.clear(); diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java index 3267ed89c86..20fe1ca394e 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -36,6 +36,8 @@ import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.ModuleScannerContext; +import org.sonar.plugins.java.api.internal.EndOfAnalysis; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -78,6 +80,9 @@ private static boolean isCollectionField(JavaField field) { } private static Set lifecycleMethods(JavaClass checkClass) { + if (checkClass.isAssignableTo(EndOfAnalysis.class)) { + return methodsNamed(checkClass, "endOfAnalysis", ModuleScannerContext.class); + } if (checkClass.isAssignableTo(IssuableSubscriptionVisitor.class)) { Set methods = methodsNamed(checkClass, "setContext", JavaFileScannerContext.class); methods.addAll(methodsNamed(checkClass, "leaveFile", JavaFileScannerContext.class)); From 1bbd24dce873ae865e57b91854954c793f06fd4a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 10:22:45 +0200 Subject: [PATCH 05/18] Reset check state before file analysis --- .../java/checks/CatchUsesExceptionWithContextCheck.java | 6 ++++++ .../main/java/org/sonar/java/checks/IndentationCheck.java | 3 +++ .../main/java/org/sonar/java/checks/MagicNumberCheck.java | 3 +++ .../java/org/sonar/java/checks/NestedIfStatementsCheck.java | 3 +++ .../org/sonar/java/checks/TypeParametersShadowingCheck.java | 3 +++ 5 files changed, 18 insertions(+) diff --git a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java index 37a50d9170c..fd0a61a053c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java @@ -111,6 +111,12 @@ public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implemen @Override public void scanFile(JavaFileScannerContext context) { this.context = context; + if (exceptions != null) { + exceptions.clear(); + } + if (exceptionIdentifiers != null) { + exceptionIdentifiers.clear(); + } usageStatusStack = new ArrayDeque<>(); try { if (context.getSemanticModel() != null) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java index 24eaa44f72d..7ba543076c9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java @@ -67,6 +67,9 @@ public void scanFile(JavaFileScannerContext context) { isBlockAlreadyReported = false; excludeIssueAtLine = 0; this.context = context; + if (fileLines != null) { + fileLines.clear(); + } fileLines = new ArrayList<>(context.getFileLines()); try { scan(context.getTree()); diff --git a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java index 27b1c22e9f2..e41651ade82 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java @@ -56,6 +56,9 @@ public class MagicNumberCheck extends BaseTreeVisitor implements JavaFileScanner @Override public void scanFile(JavaFileScannerContext context) { this.context = context; + if (authorizedNumbersList != null) { + authorizedNumbersList.clear(); + } this.authorizedNumbersList = new ArrayList<>(); try { for (String s : authorizedNumbers.split(",")) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java index bdc52bbef55..62f8a972d70 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java @@ -52,6 +52,9 @@ public class NestedIfStatementsCheck extends BaseTreeVisitor implements JavaFile @Override public void scanFile(final JavaFileScannerContext context) { this.context = context; + if (nestingLevel != null) { + nestingLevel.clear(); + } this.nestingLevel = new ArrayDeque<>(); try { scan(context.getTree()); diff --git a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java index a07c8e21829..1c2d06dbe20 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java @@ -41,6 +41,9 @@ public class TypeParametersShadowingCheck extends BaseTreeVisitor implements Jav @Override public void scanFile(JavaFileScannerContext context) { this.context = context; + if (currentTypeParametersInScope != null) { + currentTypeParametersInScope.clear(); + } currentTypeParametersInScope = new HashMap<>(); try { scan(context.getTree()); From 906e186626c22908b981e2eeed626b2e691c7bc2 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 11:06:31 +0200 Subject: [PATCH 06/18] Fix check state cleanup regressions --- .../AccessibilityChangeOnRecordsCheck.java | 20 +++++++++ .../CatchUsesExceptionWithContextCheck.java | 12 ------ .../checks/DepthOfInheritanceTreeCheck.java | 15 +------ .../sonar/java/checks/IndentationCheck.java | 7 +-- .../sonar/java/checks/MagicNumberCheck.java | 13 ++---- .../java/checks/RedundantTypeCastCheck.java | 23 ++++++++++ .../java/checks/SelfAssignmentCheck.java | 23 ++++++++++ .../ServletMethodsExceptionsThrownCheck.java | 22 ++++++++++ .../checks/SynchronizedClassUsageCheck.java | 22 ++++++++++ .../checks/ThrowCheckedExceptionCheck.java | 21 +++++++++ .../java/checks/TrailingCommentCheck.java | 3 -- .../java/checks/UnreachableCatchCheck.java | 21 +++++++++ .../sonar/java/checks/UselessImportCheck.java | 24 +++++++++++ .../VolatileVariablesOperationsCheck.java | 21 +++++++++ .../design/ClassImportCouplingCheck.java | 4 ++ .../checks/naming/BadPackageNameCheck.java | 1 - .../security/DebugFeatureEnabledCheck.java | 21 +++++++++ .../java/checks/security/ZipEntryCheck.java | 21 +++++++++ .../DoubleCheckedLockingCheck.java | 20 +++++++++ .../synchronization/TwoLocksWaitCheck.java | 19 ++++++++ .../checks/tests/MockingAllMethodsCheck.java | 20 +++++++++ .../unused/UnusedPrivateFieldCheck.java | 4 -- .../java/checks/CheckStateCleanupTest.java | 43 ++++++++++++++++--- 23 files changed, 344 insertions(+), 56 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java index 13890f19f03..aa0273a93c2 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java @@ -108,4 +108,24 @@ private static Optional getIdentifierSymbol(MethodInvocationTree mit) { IdentifierTree identifier = (IdentifierTree) callee; return Optional.of(identifier.symbol()); } + private void clearState() { + primaryTargets.clear(); + secondaryTargets.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java index fd0a61a053c..17227149ee6 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java @@ -111,24 +111,12 @@ public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implemen @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (exceptions != null) { - exceptions.clear(); - } - if (exceptionIdentifiers != null) { - exceptionIdentifiers.clear(); - } usageStatusStack = new ArrayDeque<>(); try { if (context.getSemanticModel() != null) { scan(context.getTree()); } } finally { - if (exceptions != null) { - exceptions.clear(); - } - if (exceptionIdentifiers != null) { - exceptionIdentifiers.clear(); - } excludedCatchTrees.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java index 81019ce0f46..d2b4e2eb11f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java @@ -66,19 +66,8 @@ public class DepthOfInheritanceTreeCheck extends BaseTreeVisitor implements Java @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (filteredPatterns != null) { - filteredPatterns.clear(); - } - filteredPatterns = null; - try { - if (context.getSemanticModel() != null) { - scan(context.getTree()); - } - } finally { - if (filteredPatterns != null) { - filteredPatterns.clear(); - } - filteredPatterns = null; + if (context.getSemanticModel() != null) { + scan(context.getTree()); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java index 7ba543076c9..f09528d2045 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/IndentationCheck.java @@ -16,7 +16,6 @@ */ package org.sonar.java.checks; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -67,14 +66,10 @@ public void scanFile(JavaFileScannerContext context) { isBlockAlreadyReported = false; excludeIssueAtLine = 0; this.context = context; - if (fileLines != null) { - fileLines.clear(); - } - fileLines = new ArrayList<>(context.getFileLines()); + fileLines = context.getFileLines(); try { scan(context.getTree()); } finally { - fileLines.clear(); fileLines = null; } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java index e41651ade82..bbedb771db1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MagicNumberCheck.java @@ -56,18 +56,11 @@ public class MagicNumberCheck extends BaseTreeVisitor implements JavaFileScanner @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (authorizedNumbersList != null) { - authorizedNumbersList.clear(); - } this.authorizedNumbersList = new ArrayList<>(); - try { - for (String s : authorizedNumbers.split(",")) { - authorizedNumbersList.add(new BigDecimal(s.trim())); - } - scan(context.getTree()); - } finally { - authorizedNumbersList.clear(); + for (String s : authorizedNumbers.split(",")) { + authorizedNumbersList.add(new BigDecimal(s.trim())); } + scan(context.getTree()); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java index 03176e0d98f..0b36d4a5dbe 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.Arrays; import java.util.List; import org.sonar.check.Rule; @@ -111,4 +113,25 @@ private static boolean matchesWarning(JWarning warning, TypeCastTree tree) { return tree.equals(warningTree); } + private void clearState() { + if (warnings != null) { + warnings.clear(); + } + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java index 8a33ed45f8d..f3c55745184 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -139,4 +141,25 @@ private void updateWarnings(AssignmentExpressionTree tree) { warnings.removeIf(warning -> tree.equals(warning.syntaxTree())); } + private void clearState() { + if (warnings != null) { + warnings.clear(); + } + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java index 9ee3742cfe7..e08e556f4e4 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; @@ -144,4 +146,24 @@ private boolean isNotCaught(Type type) { .noneMatch(type::isSubtypeOf); } + private void clearState() { + shouldCheck.clear(); + tryCatches.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java index 7e88b5b51f9..1c37b92740c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -199,4 +201,24 @@ private boolean isDeprecatedType(Type symbolType) { } } + private void clearState() { + exclusions.clear(); + visited.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java index cf59da7acd7..3f931267ffa 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; @@ -80,4 +82,23 @@ private static boolean isCompatibleWithThrows(Type exceptionType, List private static boolean isOverriding(MethodTree methodTree) { return Boolean.TRUE.equals(methodTree.isOverriding()); } + private void clearState() { + methods.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java index 44827eac514..4fe1ee6ec07 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java @@ -63,9 +63,6 @@ public void setContext(JavaFileScannerContext context) { if (pattern == null) { pattern = Pattern.compile(legalCommentPattern); } - if (visitedTokens != null) { - visitedTokens.clear(); - } visitedTokens = new HashSet<>(); super.setContext(context); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java index 6961cfb645f..ef803682360 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -149,4 +151,23 @@ private static List childrenExceptionTypes(List targets, Map imp } return quickFix.build(); } + private void clearState() { + imports.clear(); + importsNames.clear(); + duplicatedImports.clear(); + usedInJavaDoc.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java index 2693852b653..145350869c0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -180,4 +182,23 @@ public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { } + private void clearState() { + visitedUnaryExpressions.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java index 59e3a0ab211..f826aa0d06c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java @@ -53,18 +53,22 @@ public class ClassImportCouplingCheck extends AbstractCouplingChecker { public void scanFile(JavaFileScannerContext context) { if (imports != null) { imports.clear(); + imports = null; } if (secondaryLocations != null) { secondaryLocations.clear(); + secondaryLocations = null; } try { super.scanFile(context); } finally { if (imports != null) { imports.clear(); + imports = null; } if (secondaryLocations != null) { secondaryLocations.clear(); + secondaryLocations = null; } } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java b/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java index b454b0001e0..06fc65579ef 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/naming/BadPackageNameCheck.java @@ -66,7 +66,6 @@ public boolean scanWithoutParsing(InputFileScannerContext context) { @Override public void scanFile(JavaFileScannerContext context) { - badPackageNames.clear(); var packageDeclaration = context.getTree().packageDeclaration(); String name = packageDeclaration != null ? PackageUtils.packageName(packageDeclaration, ".") : ""; if (context.getCacheContext().isCacheEnabled()) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java index eed4c082639..bb65e4076e9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks.security; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; @@ -132,4 +134,23 @@ private boolean enclosingClassExtendsThrowable() { return enclosingClass.peek() != null && enclosingClass.peek().type().isSubtypeOf("java.lang.Throwable"); } + private void clearState() { + enclosingClass.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java index 1b79764b7b9..cf186f48c96 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java @@ -16,6 +16,8 @@ */ package org.sonar.java.checks.security; +import org.sonar.plugins.java.api.JavaFileScannerContext; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -108,4 +110,23 @@ public void leaveNode(Tree tree) { private void report(MethodInvocationTree mit) { reportIssue(ExpressionUtils.methodName(mit), ISSUE_MESSAGE); } + private void clearState() { + calls.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java index 871add77f45..ab896b931a4 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java @@ -200,4 +200,24 @@ public CriticalSection(SynchronizedStatementTree synchronizedTree, int ifStackDe } } + private void clearState() { + ifFieldStack.clear(); + synchronizedStmtStack.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java b/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java index af57ee70405..9daae50fbf8 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java @@ -159,4 +159,23 @@ void decrement() { } } + private void clearState() { + synchronizedStack.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java index 5dc6cfab069..c6052b26cb2 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java @@ -106,4 +106,24 @@ private static boolean isNonPrivateMethod(Symbol symbol) { return symbol.isMethodSymbol() && !symbol.isPrivate() && declaration != null && !declaration.is(Tree.Kind.CONSTRUCTOR); } + private void clearState() { + mockedMethodsPerObject.clear(); + whenCalls.clear(); + } + + @Override + public void setContext(JavaFileScannerContext context) { + clearState(); + super.setContext(context); + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + try { + super.leaveFile(context); + } finally { + clearState(); + } + } + } diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index 505d1074385..a5572dc91fa 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -107,10 +107,6 @@ public void leaveFile(JavaFileScannerContext context) { } private void clearState() { - if (ignoredAnnotations != null) { - ignoredAnnotations.clear(); - ignoredAnnotations = null; - } classes.clear(); assignments.clear(); unknownIdentifiers.clear(); diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java index 20fe1ca394e..70c3e0d1f49 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -20,6 +20,7 @@ import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.importer.ClassFileImporter; @@ -56,12 +57,19 @@ public void check(JavaClass checkClass, ConditionEvents events) { Set collectionFields = checkClass.getFields().stream() .filter(field -> !Modifier.isStatic(field.reflect().getModifiers())) .filter(CheckStateCleanupTest::isCollectionField) + .filter(field -> !isPropertyCache(field)) .collect(java.util.stream.Collectors.toSet()); + Set lifecycleMethods = lifecycleMethods(checkClass); for (JavaField field : collectionFields) { - for (JavaMethod lifecycleMethod : lifecycleMethods(checkClass)) { - if (!clearsField(lifecycleMethod, field, checkClass, new HashSet<>())) { - events.add(SimpleConditionEvent.violated(checkClass, - checkClass.getFullName() + " does not clear " + field.getName() + " in " + lifecycleMethod.getName())); + if (lifecycleMethods.isEmpty()) { + events.add(SimpleConditionEvent.violated(checkClass, + checkClass.getFullName() + " has collection state but no lifecycle method")); + } else { + for (JavaMethod lifecycleMethod : lifecycleMethods) { + if (!clearsField(lifecycleMethod, field, checkClass, new HashSet<>())) { + events.add(SimpleConditionEvent.violated(checkClass, + checkClass.getFullName() + " does not clear " + field.getName() + " in " + lifecycleMethod.getName())); + } } } } @@ -79,6 +87,23 @@ private static boolean isCollectionField(JavaField field) { return field.getRawType().isAssignableTo(Collection.class) || field.getRawType().isAssignableTo(Map.class); } + private static boolean isPropertyCache(JavaField field) { + return field.getAllInvolvedRawTypes().stream() + .filter(type -> !type.isAssignableTo(Collection.class) && !type.isAssignableTo(Map.class)) + .allMatch(CheckStateCleanupTest::isPropertyType); + } + + private static boolean isPropertyType(JavaClass type) { + String name = type.getName(); + return name.equals(String.class.getName()) + || name.equals("java.math.BigDecimal") + || name.equals("java.util.regex.Pattern") + || name.equals("org.sonar.api.utils.WildcardPattern") + || name.equals("java.lang.Boolean") + || name.endsWith("PrimitiveCheck") + || type.isAssignableTo(Number.class); + } + private static Set lifecycleMethods(JavaClass checkClass) { if (checkClass.isAssignableTo(EndOfAnalysis.class)) { return methodsNamed(checkClass, "endOfAnalysis", ModuleScannerContext.class); @@ -107,9 +132,13 @@ private static boolean clearsField(JavaCodeUnit method, JavaField field, JavaCla if (!visited.add(method)) { return false; } - boolean accessesField = field.getAccessesToSelf().stream().anyMatch(access -> access.getOrigin().equals(method)); - boolean clearsCollection = method.getMethodCallsFromSelf().stream().anyMatch(CheckStateCleanupTest::isClearCall); - if (accessesField && clearsCollection) { + boolean clearsField = method.getMethodCallsFromSelf().stream() + .filter(CheckStateCleanupTest::isClearCall) + .anyMatch(call -> field.getAccessesToSelf().stream() + .anyMatch(access -> access.getOrigin().equals(method) && access.getLineNumber() == call.getLineNumber())); + boolean resetsField = field.getAccessesToSelf().stream() + .anyMatch(access -> access.getOrigin().equals(method) && access.getAccessType() == JavaFieldAccess.AccessType.SET); + if (clearsField || resetsField) { return true; } return method.getMethodCallsFromSelf().stream() From 9be2055e6017ac89cd55705aeda3be5ac0b86ac0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:17:04 +0200 Subject: [PATCH 07/18] Update ruling results for PR #6144 (#6145) Co-authored-by: github-actions[bot] --- .../commons-beanutils/java-S1166.json | 35 ++++++++++++- .../expected/eclipse-jetty/java-S1166.json | 21 ++++++++ .../resources/expected/guava/java-S1166.json | 50 +++++++++++++++++-- .../jboss-ejb3-tutorial/java-S1166.json | 3 ++ .../expected/sonar-server/java-S1166.json | 42 ++++++++++++++++ 5 files changed, 146 insertions(+), 5 deletions(-) diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json index 45d65366cbd..7fa60e1b10d 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json @@ -7,16 +7,28 @@ ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanComparator.java": [ 168, -171 +171, +174 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java": [ 121 ], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ +826 +], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java": [ -1057 +284, +363, +394, +901, +939, +1057, +1110 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java": [ +307, 391, +396, 424 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java": [ @@ -36,10 +48,15 @@ "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java": [ 105, 111, +488, 515, 524 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [ +738, +843, +887, +967, 1075 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ @@ -49,12 +66,16 @@ 960, 1342, 1344, +1346, 1388, 1390, +1392, 1431, 1433, +1435, 1477, 1479, +1481, 1530, 1707 ], @@ -76,5 +97,15 @@ ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/expression/DefaultResolver.java": [ 99 +], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/LocaleBeanUtilsBean.java": [ +675, +731 +], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/BigDecimalLocaleConverter.java": [ +227 +], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java": [ +232 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json index 50eaf837e3e..0e49ee16059 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json @@ -20,6 +20,9 @@ "org.eclipse.jetty:jetty-project:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ 103 ], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +726 +], "org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ 219 ], @@ -72,6 +75,9 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java": [ 49 ], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java": [ +153 +], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ManifestUtils.java": [ 54, 82 @@ -90,10 +96,16 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java": [ 190 ], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ProcessorUtils.java": [ +41 +], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ 295, 414 ], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java": [ +74 +], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java": [ 710 ], @@ -109,6 +121,10 @@ 84, 106 ], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ +829, +841 +], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ 67, 126 @@ -124,6 +140,11 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ 1948 ], +"org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +394, +839, +955 +], "org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ 120 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S1166.json b/its/ruling/src/test/resources/expected/guava/java-S1166.json index be641101d5b..558ae9f385a 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1166.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1166.json @@ -13,11 +13,15 @@ 372 ], "com.google.guava:guava:src/com/google/common/base/internal/Finalizer.java": [ +132, 205 ], "com.google.guava:guava:src/com/google/common/cache/AbstractLoadingCache.java": [ 52 ], +"com.google.guava:guava:src/com/google/common/cache/CacheBuilderSpec.java": [ +441 +], "com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [ 2393, 4008, @@ -39,6 +43,9 @@ 126, 128 ], +"com.google.guava:guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ +333 +], "com.google.guava:guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ 244 ], @@ -90,6 +97,9 @@ 3855, 3864 ], +"com.google.guava:guava:src/com/google/common/collect/Queues.java": [ +317 +], "com.google.guava:guava:src/com/google/common/collect/RegularContiguousSet.java": [ 124 ], @@ -139,16 +149,27 @@ "com.google.guava:guava:src/com/google/common/io/FileBackedOutputStream.java": [ 106 ], +"com.google.guava:guava:src/com/google/common/net/HostAndPort.java": [ +197 +], "com.google.guava:guava:src/com/google/common/net/HostSpecifier.java": [ 89, 143 ], "com.google.guava:guava:src/com/google/common/net/InetAddresses.java": [ +215, +279, 518 ], "com.google.guava:guava:src/com/google/common/net/InternetDomainName.java": [ 500 ], +"com.google.guava:guava:src/com/google/common/primitives/Doubles.java": [ +641 +], +"com.google.guava:guava:src/com/google/common/primitives/Floats.java": [ +617 +], "com.google.guava:guava:src/com/google/common/primitives/UnsignedBytes.java": [ 360, 378, @@ -156,14 +177,16 @@ ], "com.google.guava:guava:src/com/google/common/reflect/ClassPath.java": [ 333, -345 +345, +369 ], "com.google.guava:guava:src/com/google/common/reflect/TypeResolver.java": [ 208 ], "com.google.guava:guava:src/com/google/common/reflect/Types.java": [ 362, -386 +386, +571 ], "com.google.guava:guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 114, @@ -199,17 +222,38 @@ "com.google.guava:guava:src/com/google/common/util/concurrent/JdkFutureAdapters.java": [ 173 ], +"com.google.guava:guava:src/com/google/common/util/concurrent/Monitor.java": [ +399, +565, +589, +800 +], "com.google.guava:guava:src/com/google/common/util/concurrent/MoreExecutors.java": [ +215, 786, 801, 804, 807, -827 +810, +827, +964 ], "com.google.guava:guava:src/com/google/common/util/concurrent/SmoothRateLimiter.java": [ 362 ], "com.google.guava:guava:src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java": [ 68 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Uninterruptibles.java": [ +63, +91, +114, +144, +181, +211, +233, +261, +288, +330 ] } diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json index 26470f8bc2c..ce399f5383f 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json @@ -1,4 +1,7 @@ { +"jboss-ejb3-tutorial:enterprise_app_ejb_injection/webapp/src/main/java/org/jboss/tutorial/enterprise_app_ejb_injection/servlet/CalculatorActionServlet.java": [ +80 +], "jboss-ejb3-tutorial:interceptor/src/org/jboss/tutorial/interceptor/bean/AccountsCancelInterceptor.java": [ 67 ], diff --git a/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json b/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json index 93b5ebf99ac..8db47be7396 100644 --- a/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json +++ b/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json @@ -22,15 +22,30 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/ws/ValidateAction.java": [ 101 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java": [ +150 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/ConditionEvaluator.java": [ +61 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ 158 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ +182 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java": [ 140 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java": [ +158 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/notification/DefaultNotificationManager.java": [ 97 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java": [ +145 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/ws/CreateAction.java": [ 118 ], @@ -43,12 +58,30 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerIdGenerator.java": [ 114 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerIdManager.java": [ +82 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java": [ 59 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/IndexAction.java": [ +112 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/ProjectsWsSupport.java": [ 62 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/property/ws/IndexAction.java": [ +125 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java": [ +96 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/QualityGatesWs.java": [ +92 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java": [ +134 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java": [ 72, 83 @@ -67,6 +100,15 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/setting/ws/SetAction.java": [ 290 ], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/FloatTypeValidation.java": [ +40 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/IntegerTypeValidation.java": [ +40 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/LongTypeValidation.java": [ +39 +], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java": [ 43 ], From 13e444fd44bf885eda1974b53bb3b5ed4c55b2db Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 11:18:05 +0200 Subject: [PATCH 08/18] Fix check state cleanup regressions and clean up no-op clears - Remove no-op pre-clear guards before reassignment in NestedIfStatementsCheck and TypeParametersShadowingCheck - Revert unnecessary mutable copy in DepthOfInheritanceTreeCheck (filteredPatterns is a property cache, not AST state) - Fix indentation in ClassImportCouplingCheck finally block - Fix MissingPackageInfoCheckTest: remove assertion on internal state that is now properly cleared in endOfAnalysis Co-Authored-By: Claude Opus 4.6 --- .../sonar/java/checks/DepthOfInheritanceTreeCheck.java | 2 +- .../org/sonar/java/checks/NestedIfStatementsCheck.java | 3 --- .../sonar/java/checks/TypeParametersShadowingCheck.java | 3 --- .../java/checks/design/ClassImportCouplingCheck.java | 4 ++-- .../sonar/java/checks/MissingPackageInfoCheckTest.java | 9 +-------- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java index d2b4e2eb11f..0ef14bdba85 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DepthOfInheritanceTreeCheck.java @@ -112,7 +112,7 @@ private List getPatterns() { String.join(",", FRAMEWORK_EXCLUSION_PATTERNS), filteredClasses ); - filteredPatterns = new ArrayList<>(Arrays.asList(PatternUtils.createPatterns(permittedPatterns))); + filteredPatterns = Arrays.asList(PatternUtils.createPatterns(permittedPatterns)); } return filteredPatterns; } diff --git a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java index 62f8a972d70..bdc52bbef55 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/NestedIfStatementsCheck.java @@ -52,9 +52,6 @@ public class NestedIfStatementsCheck extends BaseTreeVisitor implements JavaFile @Override public void scanFile(final JavaFileScannerContext context) { this.context = context; - if (nestingLevel != null) { - nestingLevel.clear(); - } this.nestingLevel = new ArrayDeque<>(); try { scan(context.getTree()); diff --git a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java index 1c2d06dbe20..a07c8e21829 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TypeParametersShadowingCheck.java @@ -41,9 +41,6 @@ public class TypeParametersShadowingCheck extends BaseTreeVisitor implements Jav @Override public void scanFile(JavaFileScannerContext context) { this.context = context; - if (currentTypeParametersInScope != null) { - currentTypeParametersInScope.clear(); - } currentTypeParametersInScope = new HashMap<>(); try { scan(context.getTree()); diff --git a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java index f826aa0d06c..dcfa53fef0f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java @@ -64,11 +64,11 @@ public void scanFile(JavaFileScannerContext context) { } finally { if (imports != null) { imports.clear(); - imports = null; + imports = null; } if (secondaryLocations != null) { secondaryLocations.clear(); - secondaryLocations = null; + secondaryLocations = null; } } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java index 5f73965f384..641337d5f9c 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -68,8 +67,6 @@ void initVerifier() { @Test void no_package_info() { - MissingPackageInfoCheck check = new MissingPackageInfoCheck(); - CheckVerifier.newVerifier() .onFiles( mainCodeSourcesPath("DefaultPackage.java"), @@ -77,12 +74,8 @@ void no_package_info() { mainCodeSourcesPath("checks/packageInfo/package-info.java"), mainCodeSourcesPath("checks/packageInfo/nopackageinfo/HelloWorld.java"), mainCodeSourcesPath("checks/packageInfo/nopackageinfo/nopackageinfo.java")) - .withCheck(check) + .withCheck(new MissingPackageInfoCheck()) .verifyIssueOnProject(EXPECTED_MESSAGE); - - Set set = check.missingPackageWithoutPackageFile; - assertThat(set).hasSize(1); - assertThat(set.iterator().next()).isEqualTo(EXPECTED_PACKAGE); } @Test From 145e8dde2d1e8bbaff955abf41bfc5ba52ae54fa Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 11:36:16 +0200 Subject: [PATCH 09/18] Fix RedundantTypeCastCheck UOE, simplify ClassImportCouplingCheck, tighten test - RedundantTypeCastCheck: set warnings = null instead of calling .clear() on the unmodifiable list, which threw UnsupportedOperationException - ClassImportCouplingCheck: remove redundant guarded cleanup blocks that can never execute; simplify to try/finally with null assignments - CheckStateCleanupTest: fix isPropertyCache returning true on empty stream (allMatch vacuous truth) by requiring non-empty value types Co-Authored-By: Claude Opus 4.6 --- .../java/checks/RedundantTypeCastCheck.java | 4 +--- .../design/ClassImportCouplingCheck.java | 18 ++---------------- .../java/checks/CheckStateCleanupTest.java | 5 +++-- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java index 0b36d4a5dbe..09c0ced0581 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java @@ -114,9 +114,7 @@ private static boolean matchesWarning(JWarning warning, TypeCastTree tree) { } private void clearState() { - if (warnings != null) { - warnings.clear(); - } + warnings = null; } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java index dcfa53fef0f..b1723453c67 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/design/ClassImportCouplingCheck.java @@ -51,25 +51,11 @@ public class ClassImportCouplingCheck extends AbstractCouplingChecker { @Override public void scanFile(JavaFileScannerContext context) { - if (imports != null) { - imports.clear(); - imports = null; - } - if (secondaryLocations != null) { - secondaryLocations.clear(); - secondaryLocations = null; - } try { super.scanFile(context); } finally { - if (imports != null) { - imports.clear(); - imports = null; - } - if (secondaryLocations != null) { - secondaryLocations.clear(); - secondaryLocations = null; - } + imports = null; + secondaryLocations = null; } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java index 70c3e0d1f49..5f5a6c8855c 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -88,9 +88,10 @@ private static boolean isCollectionField(JavaField field) { } private static boolean isPropertyCache(JavaField field) { - return field.getAllInvolvedRawTypes().stream() + var valueTypes = field.getAllInvolvedRawTypes().stream() .filter(type -> !type.isAssignableTo(Collection.class) && !type.isAssignableTo(Map.class)) - .allMatch(CheckStateCleanupTest::isPropertyType); + .toList(); + return !valueTypes.isEmpty() && valueTypes.stream().allMatch(CheckStateCleanupTest::isPropertyType); } private static boolean isPropertyType(JavaClass type) { From 8eb787e7473c06d760c985e3e5e8e96603893f6a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 12:03:31 +0200 Subject: [PATCH 10/18] Extract clearState() hook in IssuableSubscriptionVisitor, revert S1166 ruling Add a protected clearState() hook to IssuableSubscriptionVisitor, called automatically from setContext() before each file analysis. Checks that need to clear per-file state now simply override clearState() instead of duplicating the setContext/leaveFile boilerplate in every file. This eliminates ~210 lines of duplicated code across 14 checks. Also reverts the S1166 ruling file updates that were based on the bug where exceptions/exceptionIdentifiers were incorrectly cleared. Co-Authored-By: Claude Opus 4.6 --- .../commons-beanutils/java-S1166.json | 35 +------------ .../expected/eclipse-jetty/java-S1166.json | 21 -------- .../resources/expected/guava/java-S1166.json | 50 ++----------------- .../jboss-ejb3-tutorial/java-S1166.json | 3 -- .../expected/sonar-server/java-S1166.json | 42 ---------------- .../AccessibilityChangeOnRecordsCheck.java | 18 +------ .../java/checks/RedundantTypeCastCheck.java | 21 +------- .../java/checks/SelfAssignmentCheck.java | 23 +-------- .../ServletMethodsExceptionsThrownCheck.java | 21 +------- .../checks/SynchronizedClassUsageCheck.java | 21 +------- .../checks/ThrowCheckedExceptionCheck.java | 21 +------- .../java/checks/UnreachableCatchCheck.java | 18 +------ .../sonar/java/checks/UselessImportCheck.java | 21 +------- .../VolatileVariablesOperationsCheck.java | 21 +------- .../security/DebugFeatureEnabledCheck.java | 21 +------- .../java/checks/security/ZipEntryCheck.java | 21 +------- .../DoubleCheckedLockingCheck.java | 18 +------ .../synchronization/TwoLocksWaitCheck.java | 18 +------ .../checks/tests/MockingAllMethodsCheck.java | 18 +------ .../unused/UnusedPrivateFieldCheck.java | 3 +- .../java/checks/CheckStateCleanupTest.java | 13 ++++- .../java/api/IssuableSubscriptionVisitor.java | 12 ++++- 22 files changed, 58 insertions(+), 402 deletions(-) diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json index 7fa60e1b10d..45d65366cbd 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1166.json @@ -7,28 +7,16 @@ ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanComparator.java": [ 168, -171, -174 +171 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java": [ 121 ], -"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ -826 -], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java": [ -284, -363, -394, -901, -939, -1057, -1110 +1057 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java": [ -307, 391, -396, 424 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java": [ @@ -48,15 +36,10 @@ "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java": [ 105, 111, -488, 515, 524 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [ -738, -843, -887, -967, 1075 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ @@ -66,16 +49,12 @@ 960, 1342, 1344, -1346, 1388, 1390, -1392, 1431, 1433, -1435, 1477, 1479, -1481, 1530, 1707 ], @@ -97,15 +76,5 @@ ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/expression/DefaultResolver.java": [ 99 -], -"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/LocaleBeanUtilsBean.java": [ -675, -731 -], -"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/BigDecimalLocaleConverter.java": [ -227 -], -"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java": [ -232 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json index 0e49ee16059..50eaf837e3e 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1166.json @@ -20,9 +20,6 @@ "org.eclipse.jetty:jetty-project:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ 103 ], -"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ -726 -], "org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ 219 ], @@ -75,9 +72,6 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java": [ 49 ], -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java": [ -153 -], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ManifestUtils.java": [ 54, 82 @@ -96,16 +90,10 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java": [ 190 ], -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ProcessorUtils.java": [ -41 -], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ 295, 414 ], -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java": [ -74 -], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java": [ 710 ], @@ -121,10 +109,6 @@ 84, 106 ], -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ -829, -841 -], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ 67, 126 @@ -140,11 +124,6 @@ "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ 1948 ], -"org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ -394, -839, -955 -], "org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ 120 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S1166.json b/its/ruling/src/test/resources/expected/guava/java-S1166.json index 558ae9f385a..be641101d5b 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1166.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1166.json @@ -13,15 +13,11 @@ 372 ], "com.google.guava:guava:src/com/google/common/base/internal/Finalizer.java": [ -132, 205 ], "com.google.guava:guava:src/com/google/common/cache/AbstractLoadingCache.java": [ 52 ], -"com.google.guava:guava:src/com/google/common/cache/CacheBuilderSpec.java": [ -441 -], "com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [ 2393, 4008, @@ -43,9 +39,6 @@ 126, 128 ], -"com.google.guava:guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ -333 -], "com.google.guava:guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ 244 ], @@ -97,9 +90,6 @@ 3855, 3864 ], -"com.google.guava:guava:src/com/google/common/collect/Queues.java": [ -317 -], "com.google.guava:guava:src/com/google/common/collect/RegularContiguousSet.java": [ 124 ], @@ -149,27 +139,16 @@ "com.google.guava:guava:src/com/google/common/io/FileBackedOutputStream.java": [ 106 ], -"com.google.guava:guava:src/com/google/common/net/HostAndPort.java": [ -197 -], "com.google.guava:guava:src/com/google/common/net/HostSpecifier.java": [ 89, 143 ], "com.google.guava:guava:src/com/google/common/net/InetAddresses.java": [ -215, -279, 518 ], "com.google.guava:guava:src/com/google/common/net/InternetDomainName.java": [ 500 ], -"com.google.guava:guava:src/com/google/common/primitives/Doubles.java": [ -641 -], -"com.google.guava:guava:src/com/google/common/primitives/Floats.java": [ -617 -], "com.google.guava:guava:src/com/google/common/primitives/UnsignedBytes.java": [ 360, 378, @@ -177,16 +156,14 @@ ], "com.google.guava:guava:src/com/google/common/reflect/ClassPath.java": [ 333, -345, -369 +345 ], "com.google.guava:guava:src/com/google/common/reflect/TypeResolver.java": [ 208 ], "com.google.guava:guava:src/com/google/common/reflect/Types.java": [ 362, -386, -571 +386 ], "com.google.guava:guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 114, @@ -222,38 +199,17 @@ "com.google.guava:guava:src/com/google/common/util/concurrent/JdkFutureAdapters.java": [ 173 ], -"com.google.guava:guava:src/com/google/common/util/concurrent/Monitor.java": [ -399, -565, -589, -800 -], "com.google.guava:guava:src/com/google/common/util/concurrent/MoreExecutors.java": [ -215, 786, 801, 804, 807, -810, -827, -964 +827 ], "com.google.guava:guava:src/com/google/common/util/concurrent/SmoothRateLimiter.java": [ 362 ], "com.google.guava:guava:src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java": [ 68 -], -"com.google.guava:guava:src/com/google/common/util/concurrent/Uninterruptibles.java": [ -63, -91, -114, -144, -181, -211, -233, -261, -288, -330 ] } diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json index ce399f5383f..26470f8bc2c 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1166.json @@ -1,7 +1,4 @@ { -"jboss-ejb3-tutorial:enterprise_app_ejb_injection/webapp/src/main/java/org/jboss/tutorial/enterprise_app_ejb_injection/servlet/CalculatorActionServlet.java": [ -80 -], "jboss-ejb3-tutorial:interceptor/src/org/jboss/tutorial/interceptor/bean/AccountsCancelInterceptor.java": [ 67 ], diff --git a/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json b/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json index 8db47be7396..93b5ebf99ac 100644 --- a/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json +++ b/its/ruling/src/test/resources/expected/sonar-server/java-S1166.json @@ -22,30 +22,15 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/ws/ValidateAction.java": [ 101 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java": [ -150 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/ConditionEvaluator.java": [ -61 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ 158 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ -182 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java": [ 140 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java": [ -158 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/notification/DefaultNotificationManager.java": [ 97 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java": [ -145 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/ws/CreateAction.java": [ 118 ], @@ -58,30 +43,12 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerIdGenerator.java": [ 114 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerIdManager.java": [ -82 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java": [ 59 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/IndexAction.java": [ -112 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/ProjectsWsSupport.java": [ 62 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/property/ws/IndexAction.java": [ -125 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java": [ -96 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/QualityGatesWs.java": [ -92 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java": [ -134 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java": [ 72, 83 @@ -100,15 +67,6 @@ "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/setting/ws/SetAction.java": [ 290 ], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/FloatTypeValidation.java": [ -40 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/IntegerTypeValidation.java": [ -40 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/LongTypeValidation.java": [ -39 -], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java": [ 43 ], diff --git a/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java index aa0273a93c2..658adac8a7f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AccessibilityChangeOnRecordsCheck.java @@ -108,24 +108,10 @@ private static Optional getIdentifierSymbol(MethodInvocationTree mit) { IdentifierTree identifier = (IdentifierTree) callee; return Optional.of(identifier.symbol()); } - private void clearState() { + @Override + protected void clearState() { primaryTargets.clear(); secondaryTargets.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java index 09c0ced0581..69a46c55fcf 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.Arrays; import java.util.List; import org.sonar.check.Rule; @@ -113,23 +110,9 @@ private static boolean matchesWarning(JWarning warning, TypeCastTree tree) { return tree.equals(warningTree); } - private void clearState() { - warnings = null; - } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + warnings = null; } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java index f3c55745184..608514a5305 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SelfAssignmentCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -141,25 +138,9 @@ private void updateWarnings(AssignmentExpressionTree tree) { warnings.removeIf(warning -> tree.equals(warning.syntaxTree())); } - private void clearState() { - if (warnings != null) { - warnings.clear(); - } - } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + warnings.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java index e08e556f4e4..7c0f541ef70 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ServletMethodsExceptionsThrownCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; @@ -146,24 +143,10 @@ private boolean isNotCaught(Type type) { .noneMatch(type::isSubtypeOf); } - private void clearState() { + @Override + protected void clearState() { shouldCheck.clear(); tryCatches.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java index 1c37b92740c..eb55e3cb70c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedClassUsageCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -201,24 +198,10 @@ private boolean isDeprecatedType(Type symbolType) { } } - private void clearState() { + @Override + protected void clearState() { exclusions.clear(); visited.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java index 3f931267ffa..a847c524ea1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ThrowCheckedExceptionCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; @@ -82,23 +79,9 @@ private static boolean isCompatibleWithThrows(Type exceptionType, List private static boolean isOverriding(MethodTree methodTree) { return Boolean.TRUE.equals(methodTree.isOverriding()); } - private void clearState() { - methods.clear(); - } - - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + methods.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java index ef803682360..bd531340e07 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java @@ -151,23 +151,9 @@ private static List childrenExceptionTypes(List targets, Map imp } return quickFix.build(); } - private void clearState() { + @Override + protected void clearState() { imports.clear(); importsNames.clear(); duplicatedImports.clear(); usedInJavaDoc.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java index 145350869c0..5820454c7f2 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -182,23 +179,9 @@ public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { } - private void clearState() { - visitedUnaryExpressions.clear(); - } - - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + visitedUnaryExpressions.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java index bb65e4076e9..fb8dec3df25 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/DebugFeatureEnabledCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks.security; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; @@ -134,23 +131,9 @@ private boolean enclosingClassExtendsThrowable() { return enclosingClass.peek() != null && enclosingClass.peek().type().isSubtypeOf("java.lang.Throwable"); } - private void clearState() { - enclosingClass.clear(); - } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + enclosingClass.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java index cf186f48c96..b7715e9ed1f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/ZipEntryCheck.java @@ -15,9 +15,6 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ package org.sonar.java.checks.security; - -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -110,23 +107,9 @@ public void leaveNode(Tree tree) { private void report(MethodInvocationTree mit) { reportIssue(ExpressionUtils.methodName(mit), ISSUE_MESSAGE); } - private void clearState() { - calls.clear(); - } - - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + calls.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java index ab896b931a4..fb0fd4ae35f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/synchronization/DoubleCheckedLockingCheck.java @@ -200,24 +200,10 @@ public CriticalSection(SynchronizedStatementTree synchronizedTree, int ifStackDe } } - private void clearState() { + @Override + protected void clearState() { ifFieldStack.clear(); synchronizedStmtStack.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java b/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java index 9daae50fbf8..1b3ca2379a5 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/synchronization/TwoLocksWaitCheck.java @@ -159,23 +159,9 @@ void decrement() { } } - private void clearState() { - synchronizedStack.clear(); - } - - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } + protected void clearState() { + synchronizedStack.clear(); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java index c6052b26cb2..7d4b8acbf5e 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/MockingAllMethodsCheck.java @@ -106,24 +106,10 @@ private static boolean isNonPrivateMethod(Symbol symbol) { return symbol.isMethodSymbol() && !symbol.isPrivate() && declaration != null && !declaration.is(Tree.Kind.CONSTRUCTOR); } - private void clearState() { + @Override + protected void clearState() { mockedMethodsPerObject.clear(); whenCalls.clear(); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - - @Override - public void leaveFile(JavaFileScannerContext context) { - try { - super.leaveFile(context); - } finally { - clearState(); - } - } - } diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index a5572dc91fa..bd5cd022e19 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -106,7 +106,8 @@ public void leaveFile(JavaFileScannerContext context) { } } - private void clearState() { + @Override + protected void clearState() { classes.clear(); assignments.clear(); unknownIdentifiers.clear(); diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java index 5f5a6c8855c..5371a7b3fc6 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -110,7 +110,8 @@ private static Set lifecycleMethods(JavaClass checkClass) { return methodsNamed(checkClass, "endOfAnalysis", ModuleScannerContext.class); } if (checkClass.isAssignableTo(IssuableSubscriptionVisitor.class)) { - Set methods = methodsNamed(checkClass, "setContext", JavaFileScannerContext.class); + Set methods = noArgMethodsNamed(checkClass, "clearState"); + methods.addAll(methodsNamed(checkClass, "setContext", JavaFileScannerContext.class)); methods.addAll(methodsNamed(checkClass, "leaveFile", JavaFileScannerContext.class)); return methods; } @@ -129,6 +130,16 @@ private static Set methodsNamed(JavaClass checkClass, String name, C return methods; } + private static Set noArgMethodsNamed(JavaClass checkClass, String name) { + Set methods = new HashSet<>(); + for (JavaMethod method : checkClass.getMethods()) { + if (method.getName().equals(name) && method.getRawParameterTypes().isEmpty()) { + methods.add(method); + } + } + return methods; + } + private static boolean clearsField(JavaCodeUnit method, JavaField field, JavaClass checkClass, Set visited) { if (!visited.add(method)) { return false; diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java index 00f8e7447a8..64b9545a191 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java @@ -26,6 +26,16 @@ */ public abstract class IssuableSubscriptionVisitor extends SubscriptionVisitor { + /** + * Override this method to release any per-file state (clear collections, null out AST references, etc.). + * It is called automatically before each file analysis (in {@link #setContext}) and after each file + * analysis (in {@link #leaveFile}), so subclasses do not need to override those two methods just + * for state cleanup. + */ + protected void clearState() { + // Default: nothing to clear. + } + /** * Override this method only if you need to execute some instructions prior to start the analysis of a file. * Don't forget to call super.setContext()`. @@ -34,8 +44,8 @@ public abstract class IssuableSubscriptionVisitor extends SubscriptionVisitor { */ @Override public void setContext(JavaFileScannerContext context) { + clearState(); super.setContext(context); - // Explicitly declares the method to make it appears in the IssuableSubscriptionVisitor's members. } /** From 006ad8573633832d5acb45a91528bdce49e939a6 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 15 Sep 2026 12:31:50 +0200 Subject: [PATCH 11/18] Call clearState() from leaveFile(), fix unused import, clean up redundant override - Add clearState() call in IssuableSubscriptionVisitor.leaveFile() so state is released after the last file analysis, not just before the next one - Remove redundant setContext() override in UnusedPrivateFieldCheck since the base class already calls clearState() in setContext() - Remove unused JavaFileScannerContext import in UnreachableCatchCheck (SQ QG) - Relax ArchUnit test to accept field cleared by any lifecycle method, since clearState() is called from both setContext() and leaveFile() Co-Authored-By: Claude Opus 4.6 --- .../sonar/java/checks/UnreachableCatchCheck.java | 2 -- .../checks/unused/UnusedPrivateFieldCheck.java | 15 +++------------ .../sonar/java/checks/CheckStateCleanupTest.java | 10 +++------- .../java/api/IssuableSubscriptionVisitor.java | 3 +-- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java index bd531340e07..dbe82afb9ab 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCatchCheck.java @@ -16,8 +16,6 @@ */ package org.sonar.java.checks; -import org.sonar.plugins.java.api.JavaFileScannerContext; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index bd5cd022e19..700100539da 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -89,21 +89,12 @@ public List nodesToVisit() { return Arrays.asList(Tree.Kind.CLASS, Tree.Kind.RECORD, Tree.Kind.METHOD, Tree.Kind.EXPRESSION_STATEMENT, Tree.Kind.IDENTIFIER); } - @Override - public void setContext(JavaFileScannerContext context) { - clearState(); - super.setContext(context); - } - @Override public void leaveFile(JavaFileScannerContext context) { - try { - if (!hasNativeMethod) { - classes.forEach(this::checkClassFields); - } - } finally { - clearState(); + if (!hasNativeMethod) { + classes.forEach(this::checkClassFields); } + super.leaveFile(context); } @Override diff --git a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java index 5371a7b3fc6..ae90c1c682f 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/CheckStateCleanupTest.java @@ -64,13 +64,9 @@ public void check(JavaClass checkClass, ConditionEvents events) { if (lifecycleMethods.isEmpty()) { events.add(SimpleConditionEvent.violated(checkClass, checkClass.getFullName() + " has collection state but no lifecycle method")); - } else { - for (JavaMethod lifecycleMethod : lifecycleMethods) { - if (!clearsField(lifecycleMethod, field, checkClass, new HashSet<>())) { - events.add(SimpleConditionEvent.violated(checkClass, - checkClass.getFullName() + " does not clear " + field.getName() + " in " + lifecycleMethod.getName())); - } - } + } else if (lifecycleMethods.stream().noneMatch(m -> clearsField(m, field, checkClass, new HashSet<>()))) { + events.add(SimpleConditionEvent.violated(checkClass, + checkClass.getFullName() + " does not clear " + field.getName() + " in any lifecycle method")); } } } diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java index 64b9545a191..55f4b773a43 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java @@ -79,8 +79,7 @@ public final void scanFile(JavaFileScannerContext context) { */ @Override public void leaveFile(JavaFileScannerContext context) { - // Explicitly declares the method to make it appears in the IssuableSubscriptionVisitor's members. - // Default behaviour is to do nothing + clearState(); } /** From 7a9ed3cf6fc3995d2810a894ac8d23a33e040657 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 16 Sep 2026 10:34:10 +0200 Subject: [PATCH 12/18] Refactor checks to use clearState() instead of manual cleanup in setContext/leaveFile Address review feedback: override clearState() in StaticFieldInitializationCheck, CookieHttpOnlyCheck, SecureCookieCheck, DateEnumsCheck, HardcodedURICheck, and WaitInWhileLoopCheck instead of duplicating cleanup logic in setContext/leaveFile. Co-Authored-By: Claude Opus 4.6 --- .../org/sonar/java/checks/DateEnumsCheck.java | 8 ++------ .../org/sonar/java/checks/HardcodedURICheck.java | 7 ++----- .../checks/StaticFieldInitializationCheck.java | 15 ++++++--------- .../sonar/java/checks/WaitInWhileLoopCheck.java | 9 ++++----- .../java/checks/security/CookieHttpOnlyCheck.java | 8 ++------ .../java/checks/security/SecureCookieCheck.java | 10 ++-------- 6 files changed, 18 insertions(+), 39 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java index 02576d074a9..44c9c18d730 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateEnumsCheck.java @@ -165,8 +165,7 @@ public class DateEnumsCheck extends AbstractMethodDetection implements JavaVersi private final Map> issuesByFile = new HashMap<>(); @Override - public void setContext(JavaFileScannerContext context) { - super.setContext(context); + protected void clearState() { importSupplier = null; currentFileTotalUsageCount = 0; currentFileNoEnumUsageCount = 0; @@ -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(); @@ -200,9 +198,7 @@ public void leaveFile(JavaFileScannerContext context) { } } - currentFileTotalUsageCount = 0; - currentFileNoEnumUsageCount = 0; - currentFileIssues.clear(); + super.leaveFile(context); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java b/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java index d10a77a4ffe..9407d0c4db6 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/HardcodedURICheck.java @@ -93,8 +93,7 @@ private record VariableData(Symbol symbol, String identifier, ExpressionTree ini private final List hardCodedUri = new ArrayList<>(); @Override - public void setContext(JavaFileScannerContext context) { - super.setContext(context); + protected void clearState() { annotationsStack.clear(); identifiersUsedInAnnotations.clear(); hardCodedUri.clear(); @@ -131,9 +130,7 @@ public void leaveFile(JavaFileScannerContext context) { reportHardcodedURI(v.initializer()); } } finally { - annotationsStack.clear(); - identifiersUsedInAnnotations.clear(); - hardCodedUri.clear(); + super.leaveFile(context); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java index 121a071cae1..a309b145d23 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldInitializationCheck.java @@ -55,21 +55,18 @@ public List nodesToVisit() { } @Override - public void setContext(JavaFileScannerContext context) { + protected void clearState() { classWithSynchronizedMethod.clear(); withinStaticInitializer.clear(); methodUsesLocks.clear(); - classWithSynchronizedMethod.push(false); - withinStaticInitializer.push(false); - methodUsesLocks.push(false); - super.setContext(context); } @Override - public void leaveFile(JavaFileScannerContext context) { - withinStaticInitializer.clear(); - methodUsesLocks.clear(); - classWithSynchronizedMethod.clear(); + public void setContext(JavaFileScannerContext context) { + super.setContext(context); + classWithSynchronizedMethod.push(false); + withinStaticInitializer.push(false); + methodUsesLocks.push(false); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java index 6f6bea6d50b..a872fe937c4 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WaitInWhileLoopCheck.java @@ -36,15 +36,14 @@ public class WaitInWhileLoopCheck extends AbstractMethodDetection { private Deque inWhileLoop = new LinkedList<>(); @Override - public void setContext(JavaFileScannerContext context) { + protected void clearState() { inWhileLoop.clear(); - inWhileLoop.push(false); - super.setContext(context); } @Override - public void leaveFile(JavaFileScannerContext context) { - inWhileLoop.clear(); + public void setContext(JavaFileScannerContext context) { + super.setContext(context); + inWhileLoop.push(false); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java index 6b75be22147..3e79c4e7dbe 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java @@ -146,12 +146,11 @@ private static final class ClassName { .build()); @Override - public void setContext(JavaFileScannerContext context) { + protected void clearState() { ignoredVariables.clear(); symbolConstructorMapToReport.clear(); settersToReport.clear(); newClassToReport.clear(); - super.setContext(context); } @Override @@ -167,10 +166,7 @@ public void leaveFile(JavaFileScannerContext context) { reportIssue(typeTree, MESSAGE); } } finally { - ignoredVariables.clear(); - symbolConstructorMapToReport.clear(); - settersToReport.clear(); - newClassToReport.clear(); + super.leaveFile(context); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java index 6c3e2da8e28..99583497d57 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/SecureCookieCheck.java @@ -148,14 +148,13 @@ public List nodesToVisit() { } @Override - public void setContext(JavaFileScannerContext context) { + protected void clearState() { unsecuredCookies.clear(); deletionCandidateCookies.clear(); cookieConstructors.clear(); maxAgeZeroSeen.clear(); deletionCandidateSecureCalls.clear(); enclosingClass.clear(); - super.setContext(context); } @Override @@ -168,12 +167,7 @@ public void leaveFile(JavaFileScannerContext context) { } }); } finally { - unsecuredCookies.clear(); - deletionCandidateCookies.clear(); - cookieConstructors.clear(); - maxAgeZeroSeen.clear(); - deletionCandidateSecureCalls.clear(); - enclosingClass.clear(); + super.leaveFile(context); } } From 6a3b8a8ea0a97710c790cd9bf4721c5c64597b06 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Thu, 17 Sep 2026 12:09:15 +0200 Subject: [PATCH 13/18] Document per-file state cleanup guidelines, make excludedCatchTrees final Add a "Per-File State Cleanup" section to CLAUDE.md explaining how rules should clear mutable state between files: via clearState() for IssuableSubscriptionVisitor, or in scanFile()'s finally block for BaseTreeVisitor/JavaFileScanner. Also make CatchUsesExceptionWithContextCheck's excludedCatchTrees field final to reflect it is never reassigned. Co-Authored-By: Claude Opus 4.6 --- AGENTS.md | 35 +++++++++++++++++++ .../CatchUsesExceptionWithContextCheck.java | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 27916a372ab..d6df7c6bc7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 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: diff --git a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java index 17227149ee6..b0a2943f563 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CatchUsesExceptionWithContextCheck.java @@ -106,7 +106,7 @@ public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implemen private Deque usageStatusStack; private Set exceptions; private Set exceptionIdentifiers; - private Set excludedCatchTrees = new HashSet<>(); + private final Set excludedCatchTrees = new HashSet<>(); @Override public void scanFile(JavaFileScannerContext context) { From 01211ca1d8b130207787db5e221e49179601809d Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 28 Sep 2026 17:04:27 +0200 Subject: [PATCH 14/18] Guarantee subscription visitor state cleanup after file analysis --- .../checks/AbstractInSynchronizeChecker.java | 1 + .../checks/MissingPackageInfoCheckTest.java | 9 ++- .../org/sonar/java/model/VisitorsBridge.java | 12 +++- .../java/api/IssuableSubscriptionVisitor.java | 12 +--- .../sonar/java/model/VisitorsBridgeTest.java | 63 +++++++++++++++++++ 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/AbstractInSynchronizeChecker.java b/java-checks/src/main/java/org/sonar/java/checks/AbstractInSynchronizeChecker.java index ce7a27b183b..f865390c5bd 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AbstractInSynchronizeChecker.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AbstractInSynchronizeChecker.java @@ -40,6 +40,7 @@ public void setContext(JavaFileScannerContext context) { @Override public void leaveFile(JavaFileScannerContext context) { withinSynchronizedBlock.clear(); + super.leaveFile(context); } @Override diff --git a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java index 641337d5f9c..5f73965f384 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -67,6 +68,8 @@ void initVerifier() { @Test void no_package_info() { + MissingPackageInfoCheck check = new MissingPackageInfoCheck(); + CheckVerifier.newVerifier() .onFiles( mainCodeSourcesPath("DefaultPackage.java"), @@ -74,8 +77,12 @@ void no_package_info() { mainCodeSourcesPath("checks/packageInfo/package-info.java"), mainCodeSourcesPath("checks/packageInfo/nopackageinfo/HelloWorld.java"), mainCodeSourcesPath("checks/packageInfo/nopackageinfo/nopackageinfo.java")) - .withCheck(new MissingPackageInfoCheck()) + .withCheck(check) .verifyIssueOnProject(EXPECTED_MESSAGE); + + Set set = check.missingPackageWithoutPackageFile; + assertThat(set).hasSize(1); + assertThat(set.iterator().next()).isEqualTo(EXPECTED_PACKAGE); } @Test diff --git a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java index be59a31d334..0385f895884 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java +++ b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java @@ -435,7 +435,17 @@ public void scanFile(JavaFileScannerContext javaFileScannerContext) { } catch (CheckFailureException e) { interruptIfFailFast(e); } finally { - issuableSubscriptionVisitorsDuration.stop(); + try { + forEach(subscriptionVisitors, visitor -> { + if (visitor instanceof IssuableSubscriptionVisitor issuableVisitor) { + issuableVisitor.clearStateAfterFile(); + } + }); + } catch (CheckFailureException e) { + interruptIfFailFast(e); + } finally { + issuableSubscriptionVisitorsDuration.stop(); + } } } diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java index 55f4b773a43..1754f769e55 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/IssuableSubscriptionVisitor.java @@ -29,8 +29,7 @@ public abstract class IssuableSubscriptionVisitor extends SubscriptionVisitor { /** * Override this method to release any per-file state (clear collections, null out AST references, etc.). * It is called automatically before each file analysis (in {@link #setContext}) and after each file - * analysis (in {@link #leaveFile}), so subclasses do not need to override those two methods just - * for state cleanup. + * analysis (by the visitor runner), even if analysis fails or {@link #leaveFile} is overridden. */ protected void clearState() { // Default: nothing to clear. @@ -72,13 +71,8 @@ public final void scanFile(JavaFileScannerContext context) { throw new UnsupportedOperationException("IssuableSubscriptionVisitor should not drive visit of file. Use leaveFile() instead."); } - /** - * Override this method only if you need to execute some instructions after the end of the analysis of a file. - * - * @param context the analysis context. - */ - @Override - public void leaveFile(JavaFileScannerContext context) { + /** Releases per-file state after analysis, independently of the leaveFile lifecycle callback. */ + public final void clearStateAfterFile() { clearState(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java index b28ad3e1bff..4c057620a93 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java @@ -169,6 +169,29 @@ void swallow_exception_when_hidden_property_set_to_false_with_IssuableSubscripti .containsOnly("IV1_ThrowingNPEVisitingClass - IV1"); } + @Test + void clear_state_when_leave_file_does_not_delegate() { + StatefulVisitorWithoutLeaveFileDelegation visitor = new StatefulVisitorWithoutLeaveFileDelegation(false); + + visitorsBridge(visitor, false).visitFile(COMPILATION_UNIT_TREE, false); + + assertThat(visitor.state).isFalse(); + assertThat(visitor.leaveCalled).isTrue(); + assertThat(visitor.clearCount).isEqualTo(2); + } + + @Test + void clear_state_when_visit_fails_before_leave_file() { + StatefulVisitorWithoutLeaveFileDelegation visitor = new StatefulVisitorWithoutLeaveFileDelegation(true); + + assertThatThrownBy(() -> visitorsBridge(visitor, true).visitFile(COMPILATION_UNIT_TREE, false)) + .isInstanceOf(AnalysisException.class); + + assertThat(visitor.state).isFalse(); + assertThat(visitor.leaveCalled).isFalse(); + assertThat(visitor.clearCount).isEqualTo(2); + } + @Test void swallow_exception_when_hidden_property_set_to_false_with_all_kinds_of_visisitors() { assertThatCode(() -> { @@ -753,6 +776,46 @@ public void leaveNode(Tree tree) { } } + private static class StatefulVisitorWithoutLeaveFileDelegation extends IssuableSubscriptionVisitor { + private final boolean failOnVisit; + private boolean state; + private boolean leaveCalled; + private int clearCount; + + private StatefulVisitorWithoutLeaveFileDelegation(boolean failOnVisit) { + this.failOnVisit = failOnVisit; + } + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.COMPILATION_UNIT); + } + + @Override + public void setContext(JavaFileScannerContext context) { + super.setContext(context); + state = true; + } + + @Override + public void visitNode(Tree tree) { + if (failOnVisit) { + throw NPE; + } + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + leaveCalled = true; + } + + @Override + protected void clearState() { + state = false; + clearCount++; + } + } + private static class VisitorWithIncompatibleVersion extends IssuableSubscriptionVisitor implements EndOfAnalysis, JavaVersionAwareVisitor { @Override public List nodesToVisit() { From 1f9df1963248ca811b1c98ce971476d0158b8065 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 28 Sep 2026 17:08:46 +0200 Subject: [PATCH 15/18] Assert package-info check state is cleared after analysis --- .../org/sonar/java/checks/MissingPackageInfoCheckTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java index 5f73965f384..513c27c10c2 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/MissingPackageInfoCheckTest.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -80,9 +79,7 @@ void no_package_info() { .withCheck(check) .verifyIssueOnProject(EXPECTED_MESSAGE); - Set set = check.missingPackageWithoutPackageFile; - assertThat(set).hasSize(1); - assertThat(set.iterator().next()).isEqualTo(EXPECTED_PACKAGE); + assertThat(check.missingPackageWithoutPackageFile).isEmpty(); } @Test From c60d974b9cdcd59f27a965e50c5c597ddd683f2d Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 29 Sep 2026 09:33:10 +0200 Subject: [PATCH 16/18] Extract visitors bridge creation out of assertThatThrownBy lambda Co-Authored-By: Claude Opus 5.5 (1M context) --- .../test/java/org/sonar/java/model/VisitorsBridgeTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java index 4c057620a93..4d395c0085c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java @@ -184,7 +184,9 @@ void clear_state_when_leave_file_does_not_delegate() { void clear_state_when_visit_fails_before_leave_file() { StatefulVisitorWithoutLeaveFileDelegation visitor = new StatefulVisitorWithoutLeaveFileDelegation(true); - assertThatThrownBy(() -> visitorsBridge(visitor, true).visitFile(COMPILATION_UNIT_TREE, false)) + VisitorsBridge visitorsBridge = visitorsBridge(visitor, true); + + assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) .isInstanceOf(AnalysisException.class); assertThat(visitor.state).isFalse(); From d1d5231896a6b6e9876b59b330f5435dc589d7fe Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 12 Oct 2026 10:25:06 +0200 Subject: [PATCH 17/18] Continue visitor state cleanup after individual failures --- .../org/sonar/java/model/VisitorsBridge.java | 21 +++++++-- .../sonar/java/model/VisitorsBridgeTest.java | 47 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java index 0385f895884..5b21e8ff73d 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java +++ b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java @@ -436,13 +436,24 @@ public void scanFile(JavaFileScannerContext javaFileScannerContext) { interruptIfFailFast(e); } finally { try { - forEach(subscriptionVisitors, visitor -> { + CheckFailureException cleanupFailure = null; + for (SubscriptionVisitor visitor : subscriptionVisitors) { if (visitor instanceof IssuableSubscriptionVisitor issuableVisitor) { - issuableVisitor.clearStateAfterFile(); + PerformanceMeasure.Duration visitorDuration = PerformanceMeasure.start(visitor); + try { + runScanner(issuableVisitor::clearStateAfterFile, visitor); + } catch (CheckFailureException e) { + if (cleanupFailure == null) { + cleanupFailure = e; + } + } finally { + visitorDuration.stop(); + } } - }); - } catch (CheckFailureException e) { - interruptIfFailFast(e); + } + if (cleanupFailure != null) { + interruptIfFailFast(cleanupFailure); + } } finally { issuableSubscriptionVisitorsDuration.stop(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java index 4d395c0085c..04d09b1df71 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.event.Level; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; @@ -194,6 +196,32 @@ void clear_state_when_visit_fails_before_leave_file() { assertThat(visitor.clearCount).isEqualTo(2); } + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void clear_all_visitors_state_when_cleanup_fails(boolean failOnException) { + RuntimeException firstFailure = new IllegalStateException("First cleanup failure"); + ThrowingCleanupVisitor firstVisitor = new ThrowingCleanupVisitor(firstFailure); + ThrowingCleanupVisitor secondVisitor = new ThrowingCleanupVisitor(new IllegalStateException("Second cleanup failure")); + StatefulVisitorWithoutLeaveFileDelegation lastVisitor = new StatefulVisitorWithoutLeaveFileDelegation(false); + VisitorsBridge visitorsBridge = visitorsBridge(List.of(firstVisitor, secondVisitor, lastVisitor), failOnException); + + if (failOnException) { + assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + .isInstanceOf(AnalysisException.class) + .hasRootCause(firstFailure); + } else { + assertThatCode(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)).doesNotThrowAnyException(); + } + + assertThat(lastVisitor.state).isFalse(); + assertThat(lastVisitor.clearCount).isEqualTo(2); + assertThat(firstVisitor.cleanupCount).isEqualTo(2); + assertThat(secondVisitor.cleanupCount).isEqualTo(2); + assertThat(logTester.logs(Level.ERROR).stream() + .filter(message -> message.contains(ThrowingCleanupVisitor.class.getName()))) + .hasSize(2); + } + @Test void swallow_exception_when_hidden_property_set_to_false_with_all_kinds_of_visisitors() { assertThatCode(() -> { @@ -818,6 +846,25 @@ protected void clearState() { } } + private static class ThrowingCleanupVisitor extends StatefulVisitorWithoutLeaveFileDelegation { + private final RuntimeException failure; + private int cleanupCount; + + private ThrowingCleanupVisitor(RuntimeException failure) { + super(false); + this.failure = failure; + } + + @Override + protected void clearState() { + super.clearState(); + cleanupCount++; + if (cleanupCount == 2) { + throw failure; + } + } + } + private static class VisitorWithIncompatibleVersion extends IssuableSubscriptionVisitor implements EndOfAnalysis, JavaVersionAwareVisitor { @Override public List nodesToVisit() { From bfd54f7a0f1360fdd03028d250eb99cb87ca113a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 12 Oct 2026 15:27:25 +0200 Subject: [PATCH 18/18] Do not let cleanup failures mask the original check failure When a subscription visitor fails during the visit and a state cleanup also fails, report the primary failure in fail-fast mode instead of letting the cleanup failure replace it from the finally block. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../org/sonar/java/model/VisitorsBridge.java | 52 ++++++++++++------- .../sonar/java/model/VisitorsBridgeTest.java | 14 +++++ 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java index 5b21e8ff73d..cbee2624114 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java +++ b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java @@ -428,36 +428,50 @@ public boolean scanWithoutParsing(InputFileScannerContext fileScannerContext) th @Override public void scanFile(JavaFileScannerContext javaFileScannerContext) { PerformanceMeasure.Duration issuableSubscriptionVisitorsDuration = PerformanceMeasure.start("IssuableSubscriptionVisitors"); + CheckFailureException primaryFailure = null; try { forEach(subscriptionVisitors, s -> s.setContext(javaFileScannerContext)); visit(javaFileScannerContext.getTree()); forEach(subscriptionVisitors, s -> s.leaveFile(javaFileScannerContext)); } catch (CheckFailureException e) { - interruptIfFailFast(e); + primaryFailure = e; } finally { + CheckFailureException cleanupFailure; try { - CheckFailureException cleanupFailure = null; - for (SubscriptionVisitor visitor : subscriptionVisitors) { - if (visitor instanceof IssuableSubscriptionVisitor issuableVisitor) { - PerformanceMeasure.Duration visitorDuration = PerformanceMeasure.start(visitor); - try { - runScanner(issuableVisitor::clearStateAfterFile, visitor); - } catch (CheckFailureException e) { - if (cleanupFailure == null) { - cleanupFailure = e; - } - } finally { - visitorDuration.stop(); - } - } - } - if (cleanupFailure != null) { - interruptIfFailFast(cleanupFailure); - } + cleanupFailure = clearAllStates(); } finally { issuableSubscriptionVisitorsDuration.stop(); } + CheckFailureException toReport = primaryFailure != null ? primaryFailure : cleanupFailure; + if (toReport != null) { + interruptIfFailFast(toReport); + } + } + } + + /** + * Clears the per-file state of every issuable subscription visitor, continuing even if some of them fail. + * + * @return the first cleanup failure, or {@code null} if all cleanups succeeded + */ + @Nullable + private CheckFailureException clearAllStates() { + CheckFailureException cleanupFailure = null; + for (SubscriptionVisitor visitor : subscriptionVisitors) { + if (visitor instanceof IssuableSubscriptionVisitor issuableVisitor) { + PerformanceMeasure.Duration visitorDuration = PerformanceMeasure.start(visitor); + try { + runScanner(issuableVisitor::clearStateAfterFile, visitor); + } catch (CheckFailureException e) { + if (cleanupFailure == null) { + cleanupFailure = e; + } + } finally { + visitorDuration.stop(); + } + } } + return cleanupFailure; } @Override diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java index 04d09b1df71..76df3e194d3 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java @@ -222,6 +222,20 @@ void clear_all_visitors_state_when_cleanup_fails(boolean failOnException) { .hasSize(2); } + @Test + void cleanup_failure_does_not_mask_visit_failure() { + StatefulVisitorWithoutLeaveFileDelegation failingVisitor = new StatefulVisitorWithoutLeaveFileDelegation(true); + ThrowingCleanupVisitor cleanupVisitor = new ThrowingCleanupVisitor(new IllegalStateException("Cleanup failure")); + VisitorsBridge visitorsBridge = visitorsBridge(List.of(failingVisitor, cleanupVisitor), true); + + assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + .isInstanceOf(AnalysisException.class) + .hasRootCause(NPE); + + assertThat(failingVisitor.clearCount).isEqualTo(2); + assertThat(cleanupVisitor.cleanupCount).isEqualTo(2); + } + @Test void swallow_exception_when_hidden_property_set_to_false_with_all_kinds_of_visisitors() { assertThatCode(() -> {