From 8ffddb9a88c594fcbd224da80538a706a71759d9 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 4 Aug 2026 14:23:36 +0200 Subject: [PATCH 1/3] SONARJAVA-6716 Fix FN in S8909 for classes with explicit constructors but no no-arg constructor The hasPublicNoArgsConstructor() method incorrectly returned true for public classes that had explicit constructors but no no-arg constructor. The orElse(classTree.symbol().isPublic()) fallback conflated two cases: classes with no explicit constructors (implicit public no-arg) and classes with explicit constructors that exclude a no-arg option. Split the logic: if no constructors are declared, check class visibility (implicit constructor inherits it); if constructors exist but none is no-arg, return false. Co-Authored-By: Claude Opus 4.6 --- .../CacheKeyGeneratorInstantiableCheckSample.java | 14 ++++++++++++++ .../CacheKeyGeneratorInstantiableCheck.java | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index e113baea4f6..b2dadacd80f 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -99,6 +99,20 @@ public Object generate(Method method, Object... methodParams) { } } +class NoncompliantPublicWithExplicitConstructor implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private final ConfigService configService; + + public NoncompliantPublicWithExplicitConstructor(ConfigService configService) { + this.configService = configService; + } + + @Override + public Object generate(Method method, Object... methodParams) { + return configService.getPrefix() + methodParams[0]; + } +} + class NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index 51743458ea2..9d7d102f5b5 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -95,12 +95,17 @@ private static boolean matchesAnnotation(AnnotationTree annotationTree, String f private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { Collection constructors = classTree.symbol().lookupSymbols(""); + if (constructors.isEmpty()) { + // No explicit constructors: the compiler generates an implicit no-arg constructor + // with the same visibility as the class. + return classTree.symbol().isPublic(); + } return constructors.stream() .map(Symbol.MethodSymbol.class::cast) .filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor) .findFirst() .map(Symbol::isPublic) - .orElse(classTree.symbol().isPublic()); + .orElse(false); } private static boolean isNoArgConstructor(Symbol.MethodSymbol constructor) { From 0c721cc2b6f3b87c9ac23a5ba1336892f37efd06 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 4 Aug 2026 16:13:09 +0200 Subject: [PATCH 2/3] SONARJAVA-6716 Add compliant test case for public class with implicit constructor Co-Authored-By: Claude Opus 4.6 --- .../quarkus/CacheKeyGeneratorInstantiableCheckSample.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index b2dadacd80f..06d2c1d4967 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -121,6 +121,13 @@ public Object generate(Method method, Object... methodParams) { } } +public class CacheKeyGeneratorInstantiableCheckSample implements CacheKeyGenerator { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + class CompliantExplicitNoArgsConstructor implements CacheKeyGenerator { public CompliantExplicitNoArgsConstructor() {} From 4f66a26d28c0395ffb929e56aa646ac4cbf25c6f Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 4 Aug 2026 16:17:26 +0200 Subject: [PATCH 3/3] SONARJAVA-6716 Make public class test case non-compliant in S8909 Add an arg constructor to CacheKeyGeneratorInstantiableCheckSample so it no longer has an implicit public no-arg constructor, making it a non-compliant example for the CacheKeyGeneratorInstantiableCheck rule. Co-Authored-By: Claude Opus 4.6 --- .../CacheKeyGeneratorInstantiableCheckSample.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index 06d2c1d4967..6625cdcf396 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -121,10 +121,17 @@ public Object generate(Method method, Object... methodParams) { } } -public class CacheKeyGeneratorInstantiableCheckSample implements CacheKeyGenerator { +public class CacheKeyGeneratorInstantiableCheckSample implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private final String prefix; + + public CacheKeyGeneratorInstantiableCheckSample(String prefix) { + this.prefix = prefix; + } + @Override public Object generate(Method method, Object... methodParams) { - return methodParams[0]; + return prefix + methodParams[0]; } }