From fe32df2810c415b33a06538b3e713ea8c95396ac Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 3 Aug 2026 13:51:11 +0200 Subject: [PATCH] Fix S3252 false positive for Quarkus Panache entity static methods Panache uses bytecode generation to create static method implementations in entity subclasses, so accessing them via the derived type is the intended usage pattern. Skip the issue when the static member's owner is a Panache entity base class. Co-Authored-By: Claude Opus 4.6 --- .../StaticMemberAccessCheckSample.java | 15 +++++++++++++++ .../hibernate/orm/panache/PanacheEntityBase.java | 9 +++++++++ .../mongodb/panache/PanacheMongoEntityBase.java | 9 +++++++++ .../java/checks/StaticMemberAccessCheck.java | 6 ++++++ 4 files changed, 39 insertions(+) diff --git a/java-checks-test-sources/default/src/main/java/checks/S3252_StaticMemberAccessCheckSample/StaticMemberAccessCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/S3252_StaticMemberAccessCheckSample/StaticMemberAccessCheckSample.java index e72ba532427..fa769567b4f 100644 --- a/java-checks-test-sources/default/src/main/java/checks/S3252_StaticMemberAccessCheckSample/StaticMemberAccessCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/S3252_StaticMemberAccessCheckSample/StaticMemberAccessCheckSample.java @@ -34,6 +34,21 @@ void test() { } } +// Quarkus Panache: static methods are bytecode-generated in subclasses, so accessing them via the subclass is intended +class MeetingType extends io.quarkus.hibernate.orm.panache.PanacheEntityBase { + void doSomething() { + MeetingType.listAll(); // Compliant - Panache generates static methods in subclasses + MeetingType.count(); // Compliant + } +} + +class MongoMeetingType extends io.quarkus.mongodb.panache.PanacheMongoEntityBase { + void doSomething() { + MongoMeetingType.listAll(); // Compliant - Panache generates static methods in subclasses + MongoMeetingType.count(); // Compliant + } +} + class GuavaFP { // method is incorrectly resolved as Set.of, specifically excluded in implementation to avoid // see SONARJAVA-3095 diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java b/java-checks-test-sources/default/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java index 38b78403cb7..0f77ef7652f 100644 --- a/java-checks-test-sources/default/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java @@ -1,4 +1,13 @@ package io.quarkus.hibernate.orm.panache; +import java.util.List; + public abstract class PanacheEntityBase { + public static List listAll() { + return null; + } + + public static long count() { + return 0; + } } diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java b/java-checks-test-sources/default/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java index 03824cd350d..949de41cdad 100644 --- a/java-checks-test-sources/default/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java @@ -1,4 +1,13 @@ package io.quarkus.mongodb.panache; +import java.util.List; + public abstract class PanacheMongoEntityBase { + public static List listAll() { + return null; + } + + public static long count() { + return 0; + } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/StaticMemberAccessCheck.java b/java-checks/src/main/java/org/sonar/java/checks/StaticMemberAccessCheck.java index e9cbe1bad81..2536eca3be3 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/StaticMemberAccessCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/StaticMemberAccessCheck.java @@ -71,6 +71,7 @@ public void visitNode(Tree tree) { if (!staticType.isUnknown() && !expressionType.isUnknown() && !expressionType.erasure().equals(staticType.erasure()) && (memberOwnerIsInCurrentPackage || owner.isPublic()) + && !isPanacheEntityBase(staticType) ) { QuickFixHelper.newIssue(context) .forRule(this) @@ -87,6 +88,11 @@ private static String classPackage(Type classType) { return endPackage == -1 ? "" : classType.fullyQualifiedName().substring(0, endPackage); } + private static boolean isPanacheEntityBase(Type type) { + return type.isSubtypeOf("io.quarkus.hibernate.orm.panache.PanacheEntityBase") + || type.isSubtypeOf("io.quarkus.mongodb.panache.PanacheMongoEntityBase"); + } + private static boolean isListOrSetOf(MemberSelectExpressionTree mse) { // this is necessary because we incorrectly resolve to Set#of List#of methods on JDK11 // see SONARJAVA-3095