-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Java: Queries for thread-safe classes #19539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
886f5b2 to
d32cd70
Compare
|
QHelp 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 public class UnsafePublication {
private Object value;
private int server_id;
public UnsafePublication() {
value = new Object(); // Not safely published, other threads may see the default value null
server_id = 1; // Not safely published, other threads may see the default value 0
}
public Object getValue() {
return value;
}
public int getServerId() {
return server_id;
}
}To fix this example, we declare the field public class SafePublication {
private volatile Object value;
private final int server_id;
public SafePublication() {
value = new Object(); // Safely published as volatile
server_id = 1; // Safely published as final
}
public synchronized Object getValue() {
return value;
}
public int getServerId() {
return server_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
126f974
Compare
126f974 to
c2374e5
Compare
|
Commented offline. |
|
Notes from offline review:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
Co-authored-by: Raúl Pardo <[email protected]> Co-authored-by: SimonJorgensenMancofi <[email protected]> Co-authored-by: Bjørnar Haugstad Jåtten <[email protected]>
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
a1671ea
Compare
- 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 <[email protected]>
8159678 to
61a3e96
Compare
Co-authored-by: Anders Schack-Mulligen <[email protected]>
- do not use `getQualifiedName` - use camelCase - rework alert predicates
40632d2 to
f4878b3
Compare
aschackmull
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM now.
|
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
|
Not sure if the failing test is simply unrelated? |
I think it's unrelated. I just tried to rerun, so let's see if that helps. |
found by triage
reorganise code, adding `LockField`
|
Triage of new results: java/unreleased-lock, 21 new results
java/safe-publication, 9 results
java/not-threadsafe, 75 results
|
|
I will do an extra DCA run on nightly for timing, since I changed the code, but otherwise I am ready to merge this :-) |
|
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. |
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?