Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
Loading
from

Conversation

yoff
Copy link
Contributor

@yoff yoff commented May 20, 2025

This PR introduces three queries for thread-safe classes, corresponding to three properties that such classes must possess, known as

  • P1: No escaping
  • P2: Safe publication
  • P3: Correct synchronization
    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:

  • should (parts of) the module Monitors be moved into concurrency.qll?
  • should the severity be error?
  • the queries are being included in the code quality suite

@yoff yoff force-pushed the java/conflicting-access branch from 886f5b2 to d32cd70 Compare May 21, 2025 10:09
Copy link
Contributor

QHelp previews:

java/ql/src/Likely Bugs/Concurrency/Escaping.qhelp

Escaping

In 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.

Recommendation

If the field does not change, mark it as final. If the field is mutable, mark it as private and provide properly synchronized accessors.

References

java/ql/src/Likely Bugs/Concurrency/SafePublication.qhelp

Safe publication

In 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 Object is null) unless this happens in a static context.

Techniques for safe publication include:

  • Using synchronized blocks or methods to ensure that a value is fully constructed before it is published.
  • Using volatile fields to ensure visibility of changes across threads.
  • Using thread-safe collections or classes that provide built-in synchronization, such as are found in java.util.concurrent.
  • Using the final keyword to ensure that a reference to an object is safely published when the object is constructed.

Recommendation

Choose 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 final keyword. If the value is mutable and needs to be shared across threads, consider using synchronized blocks or methods, or using a thread-safe collection from java.util.concurrent.

Example

In the following example, the value of value is not safely published. The produce method creates a new object and assigns it to the field value. However, the field is not declared as volatile, and there are no synchronization mechanisms in place to ensure that the value is fully constructed before it is published.

@ThreadSafe
public class UnsafePublication {
    private Object value;

    public void produce() {
        value = new Object(); // Not safely published, other threads may see the default value null
    }

    public Object getValue() {
        return value;
    }
}

To fix this example, declare the field value as volatile, or use synchronized blocks or methods to ensure that the value is fully constructed before it is published. We illustrate the latter with the following example:

public class SafePublication {
    private Object value;

    public synchronized void produce() {
        value = new Object(); // Safely published using synchronization
    }

    public synchronized Object getValue() {
        return value;
    }
}

References

java/ql/src/Likely Bugs/Concurrency/ThreadSafe.qhelp

Not thread-safe

In a thread-safe class, all field accesses that can be caused by calls to public methods must be properly synchronized.

Recommendation

Protect the field access with a lock. Alternatively mark the field as volatile if the write operation is atomic. You can also choose to use a data type that guarantees atomic access. If the field is immutable, mark it as final.

References

@yoff yoff force-pushed the java/conflicting-access branch from d32cd70 to 126f974 Compare May 22, 2025 11:06
yoff and others added 4 commits May 22, 2025 13:40
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.
@yoff yoff force-pushed the java/conflicting-access branch from 126f974 to c2374e5 Compare May 22, 2025 11:42
@yoff yoff added the Awaiting evaluation Do not merge yet, this PR is waiting for an evaluation to finish label May 22, 2025
@yoff yoff marked this pull request as ready for review May 22, 2025 13:14
@yoff yoff requested a review from a team as a code owner May 22, 2025 13:14
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.
predicate isThreadSafeType(Type t) {
t.getErasure().getName().matches(["Atomic%", "Concurrent%"])
or
t.getErasure().getName() in ["ThreadLocal"]

Check warning

Code scanning / CodeQL

Singleton set literal Warning

Singleton set literal can be replaced by its member.
@aschackmull
Copy link
Contributor

Commented offline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting evaluation Do not merge yet, this PR is waiting for an evaluation to finish documentation Java
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.