Uh oh!
There was an error while loading. Please reload this page.
Java: Queries for thread-safe classes - #19539
Conversation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
886f5b2 to
d32cd70CompareQHelp previews: java/ql/src/Likely Bugs/Concurrency/Escaping.qhelpEscapingIn a thread-safe class, non-final fields should generally be private (or possibly volatile) to ensure that they cannot be accessed by other threads in an unsafe manner. RecommendationIf the field does not change, mark it as References
java/ql/src/Likely Bugs/Concurrency/SafePublication.qhelpSafe publicationIn a thread-safe class, values must be published safely to avoid inconsistent or unexpected behavior caused by visibility issues between threads. If a value is not safely published, one thread may see a stale or partially constructed value written by another thread, leading to subtle concurrency bugs. In particular, values of primitive types should not be initialised to anything but their default values (which for Techniques for safe publication include:
RecommendationChoose a safe publication technique that fits your use case. If the value only needs to be written once, say for a singleton, consider using the ExampleIn the following example, the values of publicclassUnsafePublication {
privateObjectvalue;
privateintserver_id;
publicUnsafePublication() {
value = newObject(); // Not safely published, other threads may see the default value nullserver_id = 1; // Not safely published, other threads may see the default value 0
}
publicObjectgetValue() {
returnvalue;
}
publicintgetServerId() {
returnserver_id;
}
}To fix this example, we declare the field publicclassSafePublication {
privatevolatileObjectvalue;
privatefinalintserver_id;
publicSafePublication() {
value = newObject(); // Safely published as volatileserver_id = 1; // Safely published as final
}
publicsynchronizedObjectgetValue() {
returnvalue;
}
publicintgetServerId() {
returnserver_id;
}
}References
java/ql/src/Likely Bugs/Concurrency/ThreadSafe.qhelpNot thread-safeIn a thread-safe class, all field accesses that can be caused by calls to public methods must be properly synchronized. RecommendationProtect the field access with a lock. Alternatively mark the field as References
|
d32cd70 to
126f974CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
126f974 to
c2374e5CompareUh oh!
There was an error while loading. Please reload this page.
aschackmull
commented
May 28, 2025
Commented offline. |
Notes from offline review:
|
There was a problem hiding this comment.
Pull Request Overview
This PR introduces three CodeQL queries for analyzing thread-safe classes in Java, ensuring they properly implement thread safety properties. The queries correspond to the classical thread safety properties: no escaping (P1), safe publication (P2), and correct synchronization (P3).
- Adds queries to detect data races in
@ThreadSafe-annotated classes - Introduces queries to detect unsafe field publication and field escaping
- Implements comprehensive monitor analysis including support for complex call graphs with loops
Reviewed Changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
ConflictingAccess.qll | Core library providing monitor analysis and synchronization detection for thread-safe classes |
ThreadSafe.ql | Query detecting data races in classes marked as @ThreadSafe |
SafePublication.ql | Query detecting fields that are not safely published in thread-safe classes |
Escaping.ql | Query detecting potentially escaping fields in thread-safe classes |
| Test files | Comprehensive test examples demonstrating various thread safety scenarios |
| Change notes | Documentation for the three new queries |
| Query suite | Integration of new queries into the code quality suite |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Raúl Pardo <raul.pardo@protonmail.com> Co-authored-by: SimonJorgensenMancofi <simon.jorgensen@mancofi.dk> Co-authored-by: Bjørnar Haugstad Jåtten <bjornjaat@hotmail.com>
Our monitor analysis would be fooled by cycles in the call graph, since it required all edges on a path to a conflicting access to be either - targetting a method where the access is monitored (recursively) or - monitored locally, that is the call is monitored in the calling method For access to be monitored (first case) all outgoing edges (towards an access) need to satisfy this property. For a loop, that is too strong, only edges out of the loop actually need to be protected. This led to FPs.
Identified by triage of DCA results. Previously, we did not use the erased type, so would not recgnize `CompletableFuture<R>`. We now also recognize safe initializers.
- add missing qldoc - remove use of `getErasure` - remove use of `getTypeDescriptor` - define `ExposedField`
1a61230 to
a1671eaCompareUh oh!
There was an error while loading. Please reload this page.
- favour unary predicates over binary ones (the natural "conflicting access" is binary) - switch to a dual solution to trade recursion through forall for simple existentials. Co-authored-by: Anders Schack-Mulligen <aschackmull@github.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
8159678 to
61a3e96CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Anders Schack-Mulligen <aschackmull@users.noreply.github.com>
- do not use `getQualifiedName` - use camelCase - rework alert predicates
40632d2 to
f4878b3Compareaschackmull
commented
Oct 21, 2025
Code change looks good. If you haven't already, then it's probably good to triage a few results from a MRVA run to check that the latest version still looks good. Also, some basic performance checking is in order - let's do a dca run, but remember that it can be limited to these 3 queries to make it smaller/faster. |
also format test files
yoff
commented
Oct 21, 2025
Not sure if the failing test is simply unrelated? |
aschackmull
commented
Oct 22, 2025
I think it's unrelated. I just tried to rerun, so let's see if that helps. |
found by triage
reorganise code, adding `LockField`
yoff
commented
Oct 27, 2025
Triage of new results: java/unreleased-lock, 21 new results
java/safe-publication, 9 results
java/not-threadsafe, 75 results
|
yoff
commented
Oct 27, 2025
I will do an extra DCA run on nightly for timing, since I changed the code, but otherwise I am ready to merge this :-) |
yoff
commented
Oct 28, 2025
Timing has actually improved with the code change: palatable/lambda is now slightly slower, but apache/commons now sees no slow-down. So the project with the very many prefixes is the only one where running the new queries is even visible. |
Uh oh!
There was an error while loading. Please reload this page.
This PR introduces three queries for thread-safe classes, corresponding to three properties that such classes must possess, known as
Each query is introduced in its own commit, P3 first since it comes with some definitions that make P2 and P1 simple to write. And then there is a fourth commit fixing up the query for P3, since its detection in the first version only works when there are no loops in the call graph. I chose to show both versions, since the first is quite simple to understand and the second is basically the same idea but operating on SCCs of the call graph.
Some considerations:
Monitorsbe moved intoconcurrency.qll?error?