From 69e981524e197a3019bcc43cd740b2971ad63269 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 10:53:59 +0200 Subject: [PATCH 01/14] Java: Added new query `java/visible-for-testing-abuse` --- .../VisibleForTestingAbuse.md | 39 ++++++ .../VisibleForTestingAbuse.ql | 128 ++++++++++++++++++ .../VisibleForTestingAbuse.expected | 4 + .../VisibleForTestingAbuse.qlref | 1 + .../packageone/AnnotatedClass.java | 6 + .../packageone/SourcePackage.java | 10 ++ .../packageone/VisibleForTesting.java | 4 + .../packagetwo/Annotated.java | 15 ++ .../packagetwo/Source.java | 12 ++ .../packagetwo/Test.java | 12 ++ 10 files changed, 231 insertions(+) create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packageone/AnnotatedClass.java create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packageone/VisibleForTesting.java create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md new file mode 100644 index 000000000000..bbda346aa9a5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md @@ -0,0 +1,39 @@ +# J-T-003: Accessing any method, field or class annotated with `@VisibleForTesting` from production code is discouraged + +Accessing class members annotated with `@VisibleForTesting` from production code goes against the intention of the annotation and may indicate programmer error. + +## Overview + +The `@VisibleForTesting` serves to increase visibility of methods, fields or classes for the purposes of testing. Accessing methods, fields or classes that are annotated with `@VisibleForTesting` in production code (not test code) abuses the intention of the annotation. + +## Recommendation + +Only access methods, fields or classes annotated with `@VisibleForTesting` from test code. If the visibility of the methods, fields or classes should generally be relaxed, use Java language access modifiers. + +## Example + +```java +public class Annotated { +@VisibleForTesting static int f(){} +} + +/* src/test/java/Test.java */ +int i = Annotated.f(); // COMPLIANT + +/* src/main/Source.java */ + int i = Annotated.f(); // NON_COMPLIANT + +``` + +## Implementation notes + +This rule alerts on any implementation of the annotation `VisibleForTesting`, regardless of where it is provided from. + +The rule also uses the following logic to determine what an abuse of the annotation is: + + 1) If public or protected member/type is annotated with `VisibleForTesting`, it's assumed that package-private access is enough for production code. Therefore the rule alerts when a public or protected member/type annotated with `VisibleForTesting` is used outside of its declaring package. + 2) If package-private member/type is annotated with `VisibleForTesting`, it's assumed that private access is enough for production code. Therefore the rule alerts when a package-private member/type annotated with `VisibleForTesting` is used outside its declaring class. + +## References +- Example Specific Implementation of a VisibleForTesting Annotation: [AssertJ VisibleForTesting](https://javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/util/VisibleForTesting.html) +- Assumptions of what level of access is permittable for each access modifier and the annotation: [JetBrains VisibleForTesting](https://javadoc.io/doc/org.jetbrains/annotations/22.0.0/org/jetbrains/annotations/VisibleForTesting.html) \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql new file mode 100644 index 000000000000..3900f8474485 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -0,0 +1,128 @@ +/** + * @id java/visible-for-testing-abuse + * @name Accessing any method, field or class annotated with `@VisibleForTesting` from production code is discouraged + * @description Accessing any method, field or class annotated with `@VisibleForTesting` from + * production code goes against the intention of the annotation and may indicate + * programmer error. + * @kind problem + * @precision high + * @problem.severity warning + * @tags maintainability + * readability + */ + +import java + +/** + * A `Callable` is within some `RefType` + */ +predicate isWithinType(Callable c, RefType t) { c.getDeclaringType() = t } + +/** + * A `Callable` is within same package as the `RefType` + */ +predicate isWithinPackage(Callable c, RefType t) { + c.getDeclaringType().getPackage() = t.getPackage() +} + +predicate withinStaticContext(NestedClass c) { + c.isStatic() or + c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2 +} + +RefType enclosingInstanceType(Class inner) { + not withinStaticContext(inner) and + result = inner.(NestedClass).getEnclosingType() +} + +class OuterClass extends Class { + OuterClass() { this = enclosingInstanceType+(_) } +} + +/** + * An innerclass is accessed outside of its outerclass + * and also outside of its fellow inner parallel classes + */ +predicate isWithinDirectOuterClassOrSiblingInner( + Callable classInstanceEnclosing, RefType typeBeingConstructed +) { + exists(NestedClass inner, OuterClass outer | + outer = enclosingInstanceType(inner) and + typeBeingConstructed = inner and + // where the inner is called from the outer class + classInstanceEnclosing.getDeclaringType() = outer + ) + or + // and inner is called from the a parallel inner + exists(NestedClass inner, OuterClass outer, NestedClass otherinner | + typeBeingConstructed = inner and + outer = enclosingInstanceType(otherinner) and + outer = enclosingInstanceType(inner) and + classInstanceEnclosing.getDeclaringType() = otherinner + ) +} + +from Annotatable annotated, Annotation annotation, Expr e +where + annotation.getType().hasName("VisibleForTesting") and + annotated.getAnAnnotation() = annotation and + ( + // field access + exists(FieldAccess v | + v = e and + v.getField() = annotated and + // depending on the visiblity of the field, using the annotation to abuse the visibility may/may not be occurring + ( + // if its package protected report when its used outside its class bc it should have been private (class only permitted) + v.getField().isPackageProtected() and + not isWithinType(v.getEnclosingCallable(), v.getField().getDeclaringType()) + or + // if public or protected report when its used outside its package because package protected should have been enough (package only permitted) + (v.getField().isPublic() or v.getField().isProtected()) and + not isWithinPackage(v.getEnclosingCallable(), v.getField().getDeclaringType()) + ) + ) + or + // class instantiation + exists(ClassInstanceExpr c | + c = e and + c.getConstructedType() = annotated and + // depending on the visiblity of the class, using the annotation to abuse the visibility may/may not be occurring + // if public report when its used outside its package because package protected should have been enough (package only permitted) + ( + c.getConstructedType().isPublic() and + not isWithinPackage(c.getEnclosingCallable(), c.getConstructedType()) + or + // if its package protected report when its used outside its outer class bc it should have been private (outer class only permitted) + c.getConstructedType().hasNoModifier() and + // and the class is an innerclass, because otherwise recommending a lower accessibility makes no sense (only inner classes can be private) + exists(enclosingInstanceType(c.getConstructedType())) and + not isWithinDirectOuterClassOrSiblingInner(c.getEnclosingCallable(), c.getConstructedType()) + ) + ) + or + // method access + exists(MethodCall c | + c = e and + c.getMethod() = annotated and + // depending on the visiblity of the method, using the annotation to abuse the visibility may/may not be occurring + ( + // if its package protected report when its used outside its class bc it should have been private (class only permitted) + c.getMethod().isPackageProtected() and + not isWithinType(c.getEnclosingCallable(), c.getMethod().getDeclaringType()) + or + // if public or protected report when its used outside its package because package protected should have been enough (package only permitted) + (c.getMethod().isPublic() or c.getMethod().isProtected()) and + not isWithinPackage(c.getEnclosingCallable(), c.getMethod().getDeclaringType()) + ) + ) + ) and + // not in a test where use is appropriate + not e.getEnclosingCallable() instanceof LikelyTestMethod and + // also omit our own ql unit test where it is acceptable + not e.getEnclosingCallable() + .getFile() + .getAbsolutePath() + .matches("%java/ql/test/query-tests/%Test.java") +select e, "Access of $@ annotated with VisibleForTesting found in production code.", annotated, + "element" diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected new file mode 100644 index 000000000000..3d1278c1418f --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -0,0 +1,4 @@ +| packageone/SourcePackage.java:8:21:8:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packagetwo/Source.java:7:17:7:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:12:16:12:16 | f | element | +| packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | +| packagetwo/Source.java:9:28:9:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref new file mode 100644 index 000000000000..6f789e7b47ba --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref @@ -0,0 +1 @@ +Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/AnnotatedClass.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/AnnotatedClass.java new file mode 100644 index 000000000000..1fdbea1571e2 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/AnnotatedClass.java @@ -0,0 +1,6 @@ +package packageone; + +@VisibleForTesting +public class AnnotatedClass { + public AnnotatedClass() {} +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java new file mode 100644 index 000000000000..118d021c2771 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java @@ -0,0 +1,10 @@ +package packageone; + +import packagetwo.Annotated; + +public class SourcePackage extends Annotated { + void f() { + AnnotatedClass a = new AnnotatedClass(); // COMPLIANT - same package + String s1 = Annotated.m1; // NON_COMPLIANT + } +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/VisibleForTesting.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/VisibleForTesting.java new file mode 100644 index 000000000000..28aedbf4e539 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/VisibleForTesting.java @@ -0,0 +1,4 @@ +package packageone; + +public @interface VisibleForTesting { +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java new file mode 100644 index 000000000000..e253e4a2cddc --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -0,0 +1,15 @@ +package packagetwo; + +import packageone.*; + +public class Annotated { + @VisibleForTesting + static String m; + @VisibleForTesting + static protected String m1; + + @VisibleForTesting + static int f() { + return 1; + } +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java new file mode 100644 index 000000000000..9d60b2cc5a7c --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java @@ -0,0 +1,12 @@ +package packagetwo; + +import packageone.*; + +public class Source { + void f() { + int i = Annotated.f(); // NON_COMPLIANT + String s = Annotated.m; // NON_COMPLIANT + AnnotatedClass a = new AnnotatedClass(); // NON_COMPLIANT + String s1 = Annotated.m1; // COMPLIANT - same package + } +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java new file mode 100644 index 000000000000..d4552657f159 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java @@ -0,0 +1,12 @@ +package packagetwo; + +import packageone.*; + +public class Test { + void f() { + int i = Annotated.f(); // COMPLIANT + String s = Annotated.m; // COMPLIANT + AnnotatedClass a = new AnnotatedClass(); // COMPLIANT + String s1 = Annotated.m1; // COMPLIANT + } +} From 87ff5e96c679baa24db42dc31501113192d1538a Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 11:23:02 +0200 Subject: [PATCH 02/14] Java: Added inline test expectations for `java/visible-for-testing-abuse` --- .../VisibleForTestingAbuse/VisibleForTestingAbuse.qlref | 3 ++- .../VisibleForTestingAbuse/packageone/SourcePackage.java | 2 +- .../VisibleForTestingAbuse/packagetwo/Source.java | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref index 6f789e7b47ba..57947f804319 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.qlref @@ -1 +1,2 @@ -Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +query: Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java index 118d021c2771..5198555d689c 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java @@ -5,6 +5,6 @@ public class SourcePackage extends Annotated { void f() { AnnotatedClass a = new AnnotatedClass(); // COMPLIANT - same package - String s1 = Annotated.m1; // NON_COMPLIANT + String s1 = Annotated.m1; // $ Alert } } diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java index 9d60b2cc5a7c..d513b9dd47fb 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java @@ -4,9 +4,9 @@ public class Source { void f() { - int i = Annotated.f(); // NON_COMPLIANT - String s = Annotated.m; // NON_COMPLIANT - AnnotatedClass a = new AnnotatedClass(); // NON_COMPLIANT + int i = Annotated.f(); // $ Alert + String s = Annotated.m; // $ Alert + AnnotatedClass a = new AnnotatedClass(); // $ Alert String s1 = Annotated.m1; // COMPLIANT - same package } } From 96709419a8b2c3dd14a40cef9b64a9f83540c18d Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 11:28:01 +0200 Subject: [PATCH 03/14] Java: Promoted `java/visible-for-testing-abuse` to quality --- .../java/query-suite/java-code-quality-extended.qls.expected | 1 + .../java/query-suite/java-code-quality.qls.expected | 1 + .../Implementation Hiding/VisibleForTestingAbuse.ql | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected b/java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected index 7a1a986b2aa1..385a537435f7 100644 --- a/java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected +++ b/java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected @@ -73,6 +73,7 @@ ql/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalR ql/java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql ql/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql ql/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql +ql/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql diff --git a/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected b/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected index 17253dbe0f89..8a6a605fdae8 100644 --- a/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected +++ b/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected @@ -71,6 +71,7 @@ ql/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalR ql/java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql ql/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql ql/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql +ql/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 3900f8474485..54ce53fcd4c4 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -7,7 +7,8 @@ * @kind problem * @precision high * @problem.severity warning - * @tags maintainability + * @tags quality + * maintainability * readability */ From 6844b78f682162c73da98ebadecccfafbf0e3614 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 12:55:08 +0200 Subject: [PATCH 04/14] Java: Expanded test suite of `java/visible-for-testing-abuse` --- .../VisibleForTestingAbuse.expected | 27 ++++++++- .../packageone/SourcePackage.java | 26 +++++++- .../packagetwo/Annotated.java | 60 +++++++++++++++++++ .../packagetwo/Source.java | 26 +++++++- .../packagetwo/Test.java | 26 +++++++- 5 files changed, 157 insertions(+), 8 deletions(-) diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index 3d1278c1418f..6dd775b2802c 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -1,4 +1,25 @@ -| packageone/SourcePackage.java:8:21:8:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | -| packagetwo/Source.java:7:17:7:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:12:16:12:16 | f | element | +| packageone/SourcePackage.java:9:21:9:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packageone/SourcePackage.java:10:21:10:32 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packageone/SourcePackage.java:16:18:16:36 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packageone/SourcePackage.java:17:18:17:39 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | +| packageone/SourcePackage.java:25:31:25:42 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packageone/SourcePackage.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packageone/SourcePackage.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packageone/SourcePackage.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | +| packagetwo/Annotated.java:49:31:49:31 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | +| packagetwo/Annotated.java:50:32:50:33 | m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packagetwo/Annotated.java:51:32:51:33 | m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packagetwo/Annotated.java:54:26:54:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | +| packagetwo/Annotated.java:56:32:56:40 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packagetwo/Annotated.java:57:35:57:46 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | +| packagetwo/Annotated.java:64:28:64:28 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | +| packagetwo/Annotated.java:69:26:69:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | -| packagetwo/Source.java:9:28:9:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | +| packagetwo/Source.java:14:17:14:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | +| packagetwo/Source.java:20:28:20:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | +| packagetwo/Source.java:24:30:24:40 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | +| packagetwo/Source.java:25:31:25:42 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packagetwo/Source.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packagetwo/Source.java:28:27:28:39 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | +| packagetwo/Source.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packagetwo/Source.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java index 5198555d689c..96363da79c97 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage.java @@ -4,7 +4,31 @@ public class SourcePackage extends Annotated { void f() { - AnnotatedClass a = new AnnotatedClass(); // COMPLIANT - same package + // Fields - cross-package access (only accessible ones) + // String s = Annotated.m; // Cannot access package-private from different package String s1 = Annotated.m1; // $ Alert + String s2 = Annotated.m2; // $ Alert + // String s3 = Annotated.m3; // Cannot access private field + + // Methods - cross-package access (only accessible ones) + // int i = Annotated.f(); // Cannot access package-private from different package + // int i1 = Annotated.fPrivate(); // Cannot access private method + int i2 = Annotated.fPublic(); // $ Alert + int i3 = Annotated.fProtected(); // $ Alert + + // Same package class + AnnotatedClass a = new AnnotatedClass(); // COMPLIANT - same package + + // Lambda usage - cross-package (only accessible members) + Runnable lambda = () -> { + // String lambdaS = Annotated.m; // Cannot access package-private + String lambdaS1 = Annotated.m1; // $ Alert + String lambdaS2 = Annotated.m2; // $ Alert + + // int lambdaI = Annotated.f(); // Cannot access package-private + int lambdaI2 = Annotated.fPublic(); // $ Alert + int lambdaI3 = Annotated.fProtected(); // $ Alert + }; + lambda.run(); } } diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java index e253e4a2cddc..cdf0cfeae51b 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -7,9 +7,69 @@ public class Annotated { static String m; @VisibleForTesting static protected String m1; + @VisibleForTesting + static public String m2; + @VisibleForTesting + static private String m3; @VisibleForTesting static int f() { return 1; } + + @VisibleForTesting + static private int fPrivate() { + return 1; + } + + @VisibleForTesting + static public int fPublic() { + return 1; + } + + @VisibleForTesting + static protected int fProtected() { + return 1; + } + + private static void resetPriorities() { + String priority = m; + String priority1 = m1; + String priority2 = m2; + String priority3 = m3; + + int result = f(); + int resultPrivate = fPrivate(); + int resultPublic = fPublic(); + int resultProtected = fProtected(); + } + + private static void resetPriorities2() { + Runnable task = () -> { + String priority = m; // $ SPURIOUS: Alert + String priority1 = m1; // $ SPURIOUS: Alert + String priority2 = m2; // $ SPURIOUS: Alert + String priority3 = m3; + + int result = f(); // $ SPURIOUS: Alert + int resultPrivate = fPrivate(); + int resultPublic = fPublic(); // $ SPURIOUS: Alert + int resultProtected = fProtected(); // $ SPURIOUS: Alert + }; + task.run(); + } + + private static class InnerClass { + void useVisibleForMembers() { + String field = m; // $ SPURIOUS: Alert + String field1 = m1; + String field2 = m2; + String field3 = m3; + + int method = f(); // $ SPURIOUS: Alert + int methodPrivate = fPrivate(); + int methodPublic = fPublic(); + int methodProtected = fProtected(); + } + } } diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java index d513b9dd47fb..358e145022c3 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java @@ -4,9 +4,31 @@ public class Source { void f() { - int i = Annotated.f(); // $ Alert + // Fields String s = Annotated.m; // $ Alert - AnnotatedClass a = new AnnotatedClass(); // $ Alert String s1 = Annotated.m1; // COMPLIANT - same package + String s2 = Annotated.m2; + // String s3 = Annotated.m3; // Cannot access private field + + // Methods + int i = Annotated.f(); // $ Alert + // int i1 = Annotated.fPrivate(); // Cannot access private method + int i2 = Annotated.fPublic(); + int i3 = Annotated.fProtected(); + + // Other class + AnnotatedClass a = new AnnotatedClass(); // $ Alert + + // Lambda usage + Runnable lambda = () -> { + String lambdaS = Annotated.m; // $ Alert + String lambdaS1 = Annotated.m1; // $ SPURIOUS: Alert + String lambdaS2 = Annotated.m2; // $ SPURIOUS: Alert + + int lambdaI = Annotated.f(); // $ Alert + int lambdaI2 = Annotated.fPublic(); // $ SPURIOUS: Alert + int lambdaI3 = Annotated.fProtected(); // $ SPURIOUS: Alert + }; + lambda.run(); } } diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java index d4552657f159..b861d921e9a3 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Test.java @@ -4,9 +4,31 @@ public class Test { void f() { - int i = Annotated.f(); // COMPLIANT + // Fields String s = Annotated.m; // COMPLIANT - AnnotatedClass a = new AnnotatedClass(); // COMPLIANT String s1 = Annotated.m1; // COMPLIANT + String s2 = Annotated.m2; // COMPLIANT + // String s3 = Annotated.m3; // Cannot access private field + + // Methods + int i = Annotated.f(); // COMPLIANT + // int i1 = Annotated.fPrivate(); // Cannot access private method + int i2 = Annotated.fPublic(); // COMPLIANT + int i3 = Annotated.fProtected(); // COMPLIANT + + // Other class + AnnotatedClass a = new AnnotatedClass(); // COMPLIANT + + // Lambda usage + Runnable lambda = () -> { + String lambdaS = Annotated.m; // COMPLIANT + String lambdaS1 = Annotated.m1; // COMPLIANT + String lambdaS2 = Annotated.m2; // COMPLIANT + + int lambdaI = Annotated.f(); // COMPLIANT + int lambdaI2 = Annotated.fPublic(); // COMPLIANT + int lambdaI3 = Annotated.fProtected(); // COMPLIANT + }; + lambda.run(); } } From fdb60bf4eb904da7a0bd73d65674a8244a348171 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 13:20:18 +0200 Subject: [PATCH 05/14] Java: enchanced check if it is within same package --- .../Implementation Hiding/VisibleForTestingAbuse.ql | 10 +++++----- .../VisibleForTestingAbuse.expected | 8 -------- .../VisibleForTestingAbuse/packagetwo/Annotated.java | 8 ++++---- .../VisibleForTestingAbuse/packagetwo/Source.java | 8 ++++---- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 54ce53fcd4c4..e41e1b897c42 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -22,8 +22,8 @@ predicate isWithinType(Callable c, RefType t) { c.getDeclaringType() = t } /** * A `Callable` is within same package as the `RefType` */ -predicate isWithinPackage(Callable c, RefType t) { - c.getDeclaringType().getPackage() = t.getPackage() +predicate isWithinPackage(Expr e, RefType t) { + e.getCompilationUnit().getPackage() = t.getPackage() } predicate withinStaticContext(NestedClass c) { @@ -80,7 +80,7 @@ where or // if public or protected report when its used outside its package because package protected should have been enough (package only permitted) (v.getField().isPublic() or v.getField().isProtected()) and - not isWithinPackage(v.getEnclosingCallable(), v.getField().getDeclaringType()) + not isWithinPackage(v, v.getField().getDeclaringType()) ) ) or @@ -92,7 +92,7 @@ where // if public report when its used outside its package because package protected should have been enough (package only permitted) ( c.getConstructedType().isPublic() and - not isWithinPackage(c.getEnclosingCallable(), c.getConstructedType()) + not isWithinPackage(c, c.getConstructedType()) or // if its package protected report when its used outside its outer class bc it should have been private (outer class only permitted) c.getConstructedType().hasNoModifier() and @@ -114,7 +114,7 @@ where or // if public or protected report when its used outside its package because package protected should have been enough (package only permitted) (c.getMethod().isPublic() or c.getMethod().isProtected()) and - not isWithinPackage(c.getEnclosingCallable(), c.getMethod().getDeclaringType()) + not isWithinPackage(c, c.getMethod().getDeclaringType()) ) ) ) and diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index 6dd775b2802c..044960e97af9 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -7,19 +7,11 @@ | packageone/SourcePackage.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | | packageone/SourcePackage.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | | packagetwo/Annotated.java:49:31:49:31 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | -| packagetwo/Annotated.java:50:32:50:33 | m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | -| packagetwo/Annotated.java:51:32:51:33 | m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packagetwo/Annotated.java:54:26:54:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | -| packagetwo/Annotated.java:56:32:56:40 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | -| packagetwo/Annotated.java:57:35:57:46 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | | packagetwo/Annotated.java:64:28:64:28 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | | packagetwo/Annotated.java:69:26:69:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | | packagetwo/Source.java:14:17:14:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:20:28:20:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | | packagetwo/Source.java:24:30:24:40 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | -| packagetwo/Source.java:25:31:25:42 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | -| packagetwo/Source.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packagetwo/Source.java:28:27:28:39 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | -| packagetwo/Source.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | -| packagetwo/Source.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java index cdf0cfeae51b..de0085cedb46 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -47,14 +47,14 @@ private static void resetPriorities() { private static void resetPriorities2() { Runnable task = () -> { String priority = m; // $ SPURIOUS: Alert - String priority1 = m1; // $ SPURIOUS: Alert - String priority2 = m2; // $ SPURIOUS: Alert + String priority1 = m1; + String priority2 = m2; String priority3 = m3; int result = f(); // $ SPURIOUS: Alert int resultPrivate = fPrivate(); - int resultPublic = fPublic(); // $ SPURIOUS: Alert - int resultProtected = fProtected(); // $ SPURIOUS: Alert + int resultPublic = fPublic(); + int resultProtected = fProtected(); }; task.run(); } diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java index 358e145022c3..94a5cccfd43b 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Source.java @@ -22,12 +22,12 @@ void f() { // Lambda usage Runnable lambda = () -> { String lambdaS = Annotated.m; // $ Alert - String lambdaS1 = Annotated.m1; // $ SPURIOUS: Alert - String lambdaS2 = Annotated.m2; // $ SPURIOUS: Alert + String lambdaS1 = Annotated.m1; + String lambdaS2 = Annotated.m2; int lambdaI = Annotated.f(); // $ Alert - int lambdaI2 = Annotated.fPublic(); // $ SPURIOUS: Alert - int lambdaI3 = Annotated.fProtected(); // $ SPURIOUS: Alert + int lambdaI2 = Annotated.fPublic(); + int lambdaI3 = Annotated.fProtected(); }; lambda.run(); } From d3e018287399745d47349c8f448817656e7aa626 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 13:28:16 +0200 Subject: [PATCH 06/14] Java: Enchanced `isWithinType` to also include lambdas, inner classes etc. --- .../Implementation Hiding/VisibleForTestingAbuse.ql | 8 ++++++-- .../VisibleForTestingAbuse.expected | 4 ---- .../VisibleForTestingAbuse/packagetwo/Annotated.java | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index e41e1b897c42..bd1ad0c6ce75 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -15,9 +15,13 @@ import java /** - * A `Callable` is within some `RefType` + * A `Callable` is within some `RefType` (including through lambdas and inner classes) */ -predicate isWithinType(Callable c, RefType t) { c.getDeclaringType() = t } +predicate isWithinType(Callable c, RefType t) { + c.getDeclaringType() = t + or + c.getDeclaringType().getEnclosingType*() = t +} /** * A `Callable` is within same package as the `RefType` diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index 044960e97af9..0668105426b4 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -6,10 +6,6 @@ | packageone/SourcePackage.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packageone/SourcePackage.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | | packageone/SourcePackage.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | -| packagetwo/Annotated.java:49:31:49:31 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | -| packagetwo/Annotated.java:54:26:54:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | -| packagetwo/Annotated.java:64:28:64:28 | m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | -| packagetwo/Annotated.java:69:26:69:28 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | | packagetwo/Source.java:14:17:14:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:20:28:20:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java index de0085cedb46..ae0bf49a03e6 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -46,12 +46,12 @@ private static void resetPriorities() { private static void resetPriorities2() { Runnable task = () -> { - String priority = m; // $ SPURIOUS: Alert + String priority = m; String priority1 = m1; String priority2 = m2; String priority3 = m3; - int result = f(); // $ SPURIOUS: Alert + int result = f(); int resultPrivate = fPrivate(); int resultPublic = fPublic(); int resultProtected = fProtected(); @@ -61,12 +61,12 @@ private static void resetPriorities2() { private static class InnerClass { void useVisibleForMembers() { - String field = m; // $ SPURIOUS: Alert + String field = m; String field1 = m1; String field2 = m2; String field3 = m3; - int method = f(); // $ SPURIOUS: Alert + int method = f(); int methodPrivate = fPrivate(); int methodPublic = fPublic(); int methodProtected = fProtected(); From 2042883be025fb39f83d9985eb6d76430a7fc7c7 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 13:33:37 +0200 Subject: [PATCH 07/14] Java: Fix Predicate QLDoc style. --- .../VisibleForTestingAbuse.ql | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index bd1ad0c6ce75..98b97ebe5116 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -15,7 +15,7 @@ import java /** - * A `Callable` is within some `RefType` (including through lambdas and inner classes) + * Holds if a `Callable` is within some `RefType` (including through lambdas and inner classes) */ predicate isWithinType(Callable c, RefType t) { c.getDeclaringType() = t @@ -24,28 +24,37 @@ predicate isWithinType(Callable c, RefType t) { } /** - * A `Callable` is within same package as the `RefType` + * Holds if a `Callable` is within same package as the `RefType` */ predicate isWithinPackage(Expr e, RefType t) { e.getCompilationUnit().getPackage() = t.getPackage() } +/** + * Holds if a nested class is within a static context + */ predicate withinStaticContext(NestedClass c) { c.isStatic() or c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2 } +/** + * Gets the enclosing instance type for a non-static inner class + */ RefType enclosingInstanceType(Class inner) { not withinStaticContext(inner) and result = inner.(NestedClass).getEnclosingType() } +/** + * A class that encloses one or more inner classes + */ class OuterClass extends Class { OuterClass() { this = enclosingInstanceType+(_) } } /** - * An innerclass is accessed outside of its outerclass + * Holds if an innerclass is accessed outside of its outerclass * and also outside of its fellow inner parallel classes */ predicate isWithinDirectOuterClassOrSiblingInner( From 9c5a7944d2a77b9d4f4f0f7412adb1db98f705f1 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 14:37:29 +0200 Subject: [PATCH 08/14] Java: Test @VisibleForTesting method accessing @VisibleForTesting members --- .../VisibleForTestingAbuse.expected | 9 ++++++++ .../packageone/SourcePackage1.java | 22 +++++++++++++++++++ .../packagetwo/Annotated.java | 17 ++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index 0668105426b4..89c6bac4a33b 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -1,3 +1,11 @@ +| packageone/SourcePackage1.java:9:21:9:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packageone/SourcePackage1.java:10:21:10:32 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packageone/SourcePackage1.java:12:18:12:36 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packageone/SourcePackage1.java:13:18:13:39 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | +| packageone/SourcePackage1.java:16:31:16:42 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | +| packageone/SourcePackage1.java:17:31:17:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | +| packageone/SourcePackage1.java:18:28:18:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | +| packageone/SourcePackage1.java:19:28:19:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | | packageone/SourcePackage.java:9:21:9:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | | packageone/SourcePackage.java:10:21:10:32 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packageone/SourcePackage.java:16:18:16:36 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | @@ -6,6 +14,7 @@ | packageone/SourcePackage.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packageone/SourcePackage.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | | packageone/SourcePackage.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | +| packagetwo/Annotated.java:89:20:89:34 | getSize(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:79:13:79:19 | getSize | element | | packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | | packagetwo/Source.java:14:17:14:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:20:28:20:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java new file mode 100644 index 000000000000..b09fbc904cb4 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java @@ -0,0 +1,22 @@ +package packageone; + +import packagetwo.Annotated; + +public class SourcePackage1 extends Annotated { + @VisibleForTesting + public void f() { + + String s1 = Annotated.m1; // $ SPURIOUS: Alert + String s2 = Annotated.m2; // $ SPURIOUS: Alert + + int i2 = Annotated.fPublic(); // $ SPURIOUS: Alert + int i3 = Annotated.fProtected(); // $ SPURIOUS: Alert + + Runnable lambda = () -> { + String lambdaS1 = Annotated.m1; // $ SPURIOUS: Alert + String lambdaS2 = Annotated.m2; // $ SPURIOUS: Alert + int lambdaI2 = Annotated.fPublic(); // $ SPURIOUS: Alert + int lambdaI3 = Annotated.fProtected(); // $ SPURIOUS: Alert + }; + } +} diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java index ae0bf49a03e6..eb6d0ad59f32 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -72,4 +72,21 @@ void useVisibleForMembers() { int methodProtected = fProtected(); } } + + @VisibleForTesting + static class InnerTestClass { + @VisibleForTesting + int getSize() { + return 42; + } + + @VisibleForTesting + private String data; + } + + private void useInnerClass() { + InnerTestClass inner = new InnerTestClass(); + int size = inner.getSize(); // $ SPURIOUS: Alert + String value = inner.data; + } } From cce3365a1c4a6efc34441fc6cc71a991b17be864 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 14:40:19 +0200 Subject: [PATCH 09/14] Java: Resolve spurious VisibleForTestingAbuse alerts for inner class access patterns --- .../Implementation Hiding/VisibleForTestingAbuse.ql | 11 +++++++---- .../VisibleForTestingAbuse.expected | 1 - .../VisibleForTestingAbuse/packagetwo/Annotated.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 98b97ebe5116..ef356b37ef71 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -15,12 +15,15 @@ import java /** - * Holds if a `Callable` is within some `RefType` (including through lambdas and inner classes) + * Holds if a `Callable` is within the same type hierarchy as `RefType` + * (including through lambdas, inner classes, and outer classes) */ predicate isWithinType(Callable c, RefType t) { - c.getDeclaringType() = t - or - c.getDeclaringType().getEnclosingType*() = t + // Either the callable is in the target type, or they share a common enclosing type + exists(RefType commonType | + (c.getDeclaringType() = commonType or c.getDeclaringType().getEnclosingType*() = commonType) and + (t = commonType or t.getEnclosingType*() = commonType) + ) } /** diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index 89c6bac4a33b..cc755cf74737 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -14,7 +14,6 @@ | packageone/SourcePackage.java:26:31:26:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packageone/SourcePackage.java:29:28:29:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | | packageone/SourcePackage.java:30:28:30:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | -| packagetwo/Annotated.java:89:20:89:34 | getSize(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:79:13:79:19 | getSize | element | | packagetwo/Source.java:8:20:8:30 | Annotated.m | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:7:19:7:19 | m | element | | packagetwo/Source.java:14:17:14:29 | f(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:16:16:16:16 | f | element | | packagetwo/Source.java:20:28:20:47 | new AnnotatedClass(...) | Access of $@ annotated with VisibleForTesting found in production code. | packageone/AnnotatedClass.java:4:14:4:27 | AnnotatedClass | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java index eb6d0ad59f32..ad5dbed3f9b6 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/Annotated.java @@ -86,7 +86,7 @@ int getSize() { private void useInnerClass() { InnerTestClass inner = new InnerTestClass(); - int size = inner.getSize(); // $ SPURIOUS: Alert + int size = inner.getSize(); String value = inner.data; } } From 8c0678f0194019655d7136ef3a241d6568b6e0ca Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 14:57:16 +0200 Subject: [PATCH 10/14] Java: Exclude @VisibleForTesting-to-@VisibleForTesting access from VisibleForTestingAbuse alerts --- .../VisibleForTestingAbuse.ql | 11 +++++++++++ .../VisibleForTestingAbuse.expected | 8 -------- .../packageone/SourcePackage1.java | 16 ++++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index ef356b37ef71..334d85563825 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -33,6 +33,15 @@ predicate isWithinPackage(Expr e, RefType t) { e.getCompilationUnit().getPackage() = t.getPackage() } +/** + * Holds if a callable or any of its enclosing callables is annotated with @VisibleForTesting + */ +predicate isWithinVisibleForTestingContext(Callable c) { + c.getAnAnnotation().getType().hasName("VisibleForTesting") + or + isWithinVisibleForTestingContext(c.getEnclosingCallable()) +} + /** * Holds if a nested class is within a static context */ @@ -136,6 +145,8 @@ where ) and // not in a test where use is appropriate not e.getEnclosingCallable() instanceof LikelyTestMethod and + // not when the accessing method or any enclosing method is @VisibleForTesting (test-to-test communication) + not isWithinVisibleForTestingContext(e.getEnclosingCallable()) and // also omit our own ql unit test where it is acceptable not e.getEnclosingCallable() .getFile() diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected index cc755cf74737..0668105426b4 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/VisibleForTestingAbuse.expected @@ -1,11 +1,3 @@ -| packageone/SourcePackage1.java:9:21:9:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | -| packageone/SourcePackage1.java:10:21:10:32 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | -| packageone/SourcePackage1.java:12:18:12:36 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | -| packageone/SourcePackage1.java:13:18:13:39 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | -| packageone/SourcePackage1.java:16:31:16:42 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | -| packageone/SourcePackage1.java:17:31:17:42 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | -| packageone/SourcePackage1.java:18:28:18:46 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | -| packageone/SourcePackage1.java:19:28:19:49 | fProtected(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:31:26:31:35 | fProtected | element | | packageone/SourcePackage.java:9:21:9:32 | Annotated.m1 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:9:29:9:30 | m1 | element | | packageone/SourcePackage.java:10:21:10:32 | Annotated.m2 | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:11:26:11:27 | m2 | element | | packageone/SourcePackage.java:16:18:16:36 | fPublic(...) | Access of $@ annotated with VisibleForTesting found in production code. | packagetwo/Annotated.java:26:23:26:29 | fPublic | element | diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java index b09fbc904cb4..d47aa167b46c 100644 --- a/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packageone/SourcePackage1.java @@ -6,17 +6,17 @@ public class SourcePackage1 extends Annotated { @VisibleForTesting public void f() { - String s1 = Annotated.m1; // $ SPURIOUS: Alert - String s2 = Annotated.m2; // $ SPURIOUS: Alert + String s1 = Annotated.m1; + String s2 = Annotated.m2; - int i2 = Annotated.fPublic(); // $ SPURIOUS: Alert - int i3 = Annotated.fProtected(); // $ SPURIOUS: Alert + int i2 = Annotated.fPublic(); + int i3 = Annotated.fProtected(); Runnable lambda = () -> { - String lambdaS1 = Annotated.m1; // $ SPURIOUS: Alert - String lambdaS2 = Annotated.m2; // $ SPURIOUS: Alert - int lambdaI2 = Annotated.fPublic(); // $ SPURIOUS: Alert - int lambdaI3 = Annotated.fProtected(); // $ SPURIOUS: Alert + String lambdaS1 = Annotated.m1; + String lambdaS2 = Annotated.m2; + int lambdaI2 = Annotated.fPublic(); + int lambdaI3 = Annotated.fProtected(); }; } } From a0b3aa65ad45a9d341db0b09955ca3cd756c5e82 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 17:03:31 +0200 Subject: [PATCH 11/14] Java: Refactor VisibleForTestingAbuse query to reduce complexity --- .../VisibleForTestingAbuse.ql | 82 ++++--------------- 1 file changed, 15 insertions(+), 67 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 334d85563825..10675222f3e6 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -42,56 +42,9 @@ predicate isWithinVisibleForTestingContext(Callable c) { isWithinVisibleForTestingContext(c.getEnclosingCallable()) } -/** - * Holds if a nested class is within a static context - */ -predicate withinStaticContext(NestedClass c) { - c.isStatic() or - c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2 -} - -/** - * Gets the enclosing instance type for a non-static inner class - */ -RefType enclosingInstanceType(Class inner) { - not withinStaticContext(inner) and - result = inner.(NestedClass).getEnclosingType() -} - -/** - * A class that encloses one or more inner classes - */ -class OuterClass extends Class { - OuterClass() { this = enclosingInstanceType+(_) } -} - -/** - * Holds if an innerclass is accessed outside of its outerclass - * and also outside of its fellow inner parallel classes - */ -predicate isWithinDirectOuterClassOrSiblingInner( - Callable classInstanceEnclosing, RefType typeBeingConstructed -) { - exists(NestedClass inner, OuterClass outer | - outer = enclosingInstanceType(inner) and - typeBeingConstructed = inner and - // where the inner is called from the outer class - classInstanceEnclosing.getDeclaringType() = outer - ) - or - // and inner is called from the a parallel inner - exists(NestedClass inner, OuterClass outer, NestedClass otherinner | - typeBeingConstructed = inner and - outer = enclosingInstanceType(otherinner) and - outer = enclosingInstanceType(inner) and - classInstanceEnclosing.getDeclaringType() = otherinner - ) -} - -from Annotatable annotated, Annotation annotation, Expr e +from Annotatable annotated, Expr e where - annotation.getType().hasName("VisibleForTesting") and - annotated.getAnAnnotation() = annotation and + annotated.getAnAnnotation().getType().hasName("VisibleForTesting") and ( // field access exists(FieldAccess v | @@ -109,24 +62,6 @@ where ) ) or - // class instantiation - exists(ClassInstanceExpr c | - c = e and - c.getConstructedType() = annotated and - // depending on the visiblity of the class, using the annotation to abuse the visibility may/may not be occurring - // if public report when its used outside its package because package protected should have been enough (package only permitted) - ( - c.getConstructedType().isPublic() and - not isWithinPackage(c, c.getConstructedType()) - or - // if its package protected report when its used outside its outer class bc it should have been private (outer class only permitted) - c.getConstructedType().hasNoModifier() and - // and the class is an innerclass, because otherwise recommending a lower accessibility makes no sense (only inner classes can be private) - exists(enclosingInstanceType(c.getConstructedType())) and - not isWithinDirectOuterClassOrSiblingInner(c.getEnclosingCallable(), c.getConstructedType()) - ) - ) - or // method access exists(MethodCall c | c = e and @@ -142,6 +77,19 @@ where not isWithinPackage(c, c.getMethod().getDeclaringType()) ) ) + or + // Class instantiation - report if used outside appropriate scope + exists(ClassInstanceExpr c | + c = e and + c.getConstructedType() = annotated and + ( + c.getConstructedType().isPublic() and not isWithinPackage(c, c.getConstructedType()) + or + c.getConstructedType().hasNoModifier() and + c.getConstructedType() instanceof NestedClass and + not isWithinType(c.getEnclosingCallable(), c.getConstructedType()) + ) + ) ) and // not in a test where use is appropriate not e.getEnclosingCallable() instanceof LikelyTestMethod and From cc497bed26eea005ba0bb1046484bcf14392299e Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 17:44:35 +0200 Subject: [PATCH 12/14] Java: Fix VisibleForTestingAbuse false positives in annotations --- .../VisibleForTestingAbuse.ql | 2 ++ .../packagetwo/UseWithinAnnotation.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/UseWithinAnnotation.java diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 10675222f3e6..0833440ca1b9 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -95,6 +95,8 @@ where not e.getEnclosingCallable() instanceof LikelyTestMethod and // not when the accessing method or any enclosing method is @VisibleForTesting (test-to-test communication) not isWithinVisibleForTestingContext(e.getEnclosingCallable()) and + // not when used in annotation contexts + not e.getParent*() instanceof Annotation and // also omit our own ql unit test where it is acceptable not e.getEnclosingCallable() .getFile() diff --git a/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/UseWithinAnnotation.java b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/UseWithinAnnotation.java new file mode 100644 index 000000000000..f6cdb32d53c7 --- /dev/null +++ b/java/ql/test/query-tests/VisibleForTestingAbuse/packagetwo/UseWithinAnnotation.java @@ -0,0 +1,18 @@ +package packagetwo; + +import packageone.*; + +@interface Range { + int min() default 0; + int max() default 100; +} + +public class UseWithinAnnotation { + @VisibleForTesting + static final int MAX_LISTING_LENGTH_MIN = 1; + @VisibleForTesting + static final int MAX_LISTING_LENGTH_MAX = 1000; + + @Range(min = MAX_LISTING_LENGTH_MIN, max = MAX_LISTING_LENGTH_MAX) + private int maxListingLength = MAX_LISTING_LENGTH_MAX; +} From 66482b6c268c80aa767559265eaddfdd0f5093a2 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 6 Aug 2025 17:55:16 +0200 Subject: [PATCH 13/14] Java: updated `visible-for-testing-abuse` meta data and docs. --- .../VisibleForTestingAbuse.md | 19 ++++++++----------- .../VisibleForTestingAbuse.ql | 4 ++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md index bbda346aa9a5..5b1320d6c852 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.md @@ -1,10 +1,8 @@ -# J-T-003: Accessing any method, field or class annotated with `@VisibleForTesting` from production code is discouraged +## Overview Accessing class members annotated with `@VisibleForTesting` from production code goes against the intention of the annotation and may indicate programmer error. -## Overview - -The `@VisibleForTesting` serves to increase visibility of methods, fields or classes for the purposes of testing. Accessing methods, fields or classes that are annotated with `@VisibleForTesting` in production code (not test code) abuses the intention of the annotation. +The `@VisibleForTesting` annotation serves to increase visibility of methods, fields or classes for the purposes of testing. Accessing these annotated elements in production code (not test code) abuses the intention of the annotation. ## Recommendation @@ -14,15 +12,14 @@ Only access methods, fields or classes annotated with `@VisibleForTesting` from ```java public class Annotated { -@VisibleForTesting static int f(){} + @VisibleForTesting static int f() { return 42; } } /* src/test/java/Test.java */ int i = Annotated.f(); // COMPLIANT /* src/main/Source.java */ - int i = Annotated.f(); // NON_COMPLIANT - +int i = Annotated.f(); // NON_COMPLIANT ``` ## Implementation notes @@ -31,9 +28,9 @@ This rule alerts on any implementation of the annotation `VisibleForTesting`, re The rule also uses the following logic to determine what an abuse of the annotation is: - 1) If public or protected member/type is annotated with `VisibleForTesting`, it's assumed that package-private access is enough for production code. Therefore the rule alerts when a public or protected member/type annotated with `VisibleForTesting` is used outside of its declaring package. - 2) If package-private member/type is annotated with `VisibleForTesting`, it's assumed that private access is enough for production code. Therefore the rule alerts when a package-private member/type annotated with `VisibleForTesting` is used outside its declaring class. + 1. If a public or protected member/type is annotated with `@VisibleForTesting`, it's assumed that package-private access is enough for production code. Therefore the rule alerts when a public or protected member/type annotated with `@VisibleForTesting` is used outside of its declaring package. + 2. If a package-private member/type is annotated with `@VisibleForTesting`, it's assumed that private access is enough for production code. Therefore the rule alerts when a package-private member/type annotated with `@VisibleForTesting` is used outside its declaring class. ## References -- Example Specific Implementation of a VisibleForTesting Annotation: [AssertJ VisibleForTesting](https://javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/util/VisibleForTesting.html) -- Assumptions of what level of access is permittable for each access modifier and the annotation: [JetBrains VisibleForTesting](https://javadoc.io/doc/org.jetbrains/annotations/22.0.0/org/jetbrains/annotations/VisibleForTesting.html) \ No newline at end of file +- Javadoc: [AssertJ VisibleForTesting](https://javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/util/VisibleForTesting.html). +- Javadoc: [JetBrains VisibleForTesting](https://javadoc.io/doc/org.jetbrains/annotations/22.0.0/org/jetbrains/annotations/VisibleForTesting.html). diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 0833440ca1b9..21ef1fed0af7 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -1,7 +1,7 @@ /** * @id java/visible-for-testing-abuse - * @name Accessing any method, field or class annotated with `@VisibleForTesting` from production code is discouraged - * @description Accessing any method, field or class annotated with `@VisibleForTesting` from + * @name Use of VisibleForTesting in production code + * @description Accessing methods, fields or classes annotated with `@VisibleForTesting` from * production code goes against the intention of the annotation and may indicate * programmer error. * @kind problem From 87081308c0bc6037963753356e787ffb0082b774 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 7 Aug 2025 10:14:40 +0200 Subject: [PATCH 14/14] Update java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Implementation Hiding/VisibleForTestingAbuse.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql index 21ef1fed0af7..a1580d5dd3cd 100644 --- a/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/VisibleForTestingAbuse.ql @@ -50,7 +50,7 @@ where exists(FieldAccess v | v = e and v.getField() = annotated and - // depending on the visiblity of the field, using the annotation to abuse the visibility may/may not be occurring + // depending on the visibility of the field, using the annotation to abuse the visibility may/may not be occurring ( // if its package protected report when its used outside its class bc it should have been private (class only permitted) v.getField().isPackageProtected() and @@ -66,7 +66,7 @@ where exists(MethodCall c | c = e and c.getMethod() = annotated and - // depending on the visiblity of the method, using the annotation to abuse the visibility may/may not be occurring + // depending on the visibility of the method, using the annotation to abuse the visibility may/may not be occurring ( // if its package protected report when its used outside its class bc it should have been private (class only permitted) c.getMethod().isPackageProtected() and