From f88daff45f5d9cf1a1f57d7f69b9c3a30863db99 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 30 Sep 2025 13:56:27 +0100 Subject: [PATCH] Java: note that classes with entirely private constructors can't be subclassed --- .../Concurrency/StartInConstructor.ql | 10 +++++++++- .../query-tests/StartInConstructor/Test.java | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql index 3c80e4519518..700b1fab8968 100644 --- a/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql +++ b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql @@ -15,6 +15,10 @@ import java +private predicate hasASubclass(RefType t) { + exists(RefType sub | sub != t | sub.getAnAncestor() = t) +} + /** * Holds if this type is either `final` or * `private` and without subtypes. @@ -24,7 +28,11 @@ private predicate cannotBeExtended(RefType t) { or // If the class is private, all possible subclasses are known. t.isPrivate() and - not exists(RefType sub | sub != t | sub.getAnAncestor() = t) + not hasASubclass(t) + or + // If the class only has private constructors, all possible subclasses are known. + forex(Constructor c | c.getDeclaringType() = t | c.isPrivate()) and + not hasASubclass(t) } from MethodCall m, Constructor c, Class clazz diff --git a/java/ql/test/query-tests/StartInConstructor/Test.java b/java/ql/test/query-tests/StartInConstructor/Test.java index 4c5a57d8b4be..ae8148af7873 100644 --- a/java/ql/test/query-tests/StartInConstructor/Test.java +++ b/java/ql/test/query-tests/StartInConstructor/Test.java @@ -30,4 +30,18 @@ public Private() { } } -} \ No newline at end of file + + public static class AllPrivateConstructors { + Thread myThread; + + private AllPrivateConstructors() { + myThread = new Thread("myThread"); + // OK - class cannot be extended outside this file, and is not in fact extended + myThread.start(); + } + + public static AllPrivateConstructors create() { + return new AllPrivateConstructors(); + } + } +}