Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fe487e8
java: add ThreadSafe query (P3)
yoff Jan 13, 2025
328b535
java: add SafePublication query (P2)
yoff May 15, 2025
5b30153
java: add Escaping query (P1)
yoff May 15, 2025
096d5f2
java: implement SCC contraction of the call graph
yoff May 20, 2025
bf13869
java: update expectations for java-code-quality suite
yoff May 22, 2025
77734f8
java: better detection of thread safe fields.
yoff May 27, 2025
01ddc11
java: address some review comments
yoff Jun 9, 2025
821b1de
java: inline char pred
yoff Jun 9, 2025
a1671ea
java: small cleanups
yoff Oct 9, 2025
93fc287
java: add auto-generated overlay annotations
yoff Oct 9, 2025
830f02a
java: fixes from the CI bots
yoff Oct 9, 2025
26c1b2f
java: adjust test expectations; new queries are enabled in extended
yoff Oct 9, 2025
f90e9db
java: favour `inline_late` over `inline`
yoff Oct 9, 2025
1ad2394
java: move shared code into `Concurrency.qll`
yoff Oct 9, 2025
5109bab
java: add qldoc
yoff Oct 9, 2025
61a3e96
java: rewrite conflict detection
yoff Oct 16, 2025
3a0a899
java: fix ql alerts
yoff Oct 16, 2025
715acef
Apply suggestions from code review
yoff Oct 21, 2025
de05bfb
java: address review comments
yoff Oct 21, 2025
f4878b3
java: make as many predicates private as possible
yoff Oct 21, 2025
f183a72
java: add test for `notFullyMonitored`
yoff Oct 21, 2025
9e77e5b
java: add test with deeper paths
yoff Oct 21, 2025
83508ba
java: adjust qhelp and examples for SafePublication
yoff Oct 27, 2025
531b994
java: add test for aliasing
yoff Oct 27, 2025
406e48b
java: fix aliasing FP
yoff Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,18 @@ ql/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql
ql/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.ql
ql/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.ql
ql/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingWithInitRace.ql
ql/java/ql/src/Likely Bugs/Concurrency/Escaping.ql
ql/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.ql
ql/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql
ql/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.ql
ql/java/ql/src/Likely Bugs/Concurrency/SafePublication.ql
ql/java/ql/src/Likely Bugs/Concurrency/ScheduledThreadPoolExecutorZeroThread.ql
ql/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.ql
ql/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql
ql/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql
ql/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql
ql/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.ql
ql/java/ql/src/Likely Bugs/Concurrency/ThreadSafe.ql
ql/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.ql
ql/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.ql
ql/java/ql/src/Likely Bugs/Frameworks/JUnit/JUnit5MissingNestedAnnotation.ql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ ql/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.ql
ql/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql
ql/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.ql
ql/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingWithInitRace.ql
ql/java/ql/src/Likely Bugs/Concurrency/Escaping.ql
ql/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql
ql/java/ql/src/Likely Bugs/Concurrency/SafePublication.ql
ql/java/ql/src/Likely Bugs/Concurrency/ScheduledThreadPoolExecutorZeroThread.ql
ql/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql
ql/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql
ql/java/ql/src/Likely Bugs/Concurrency/ThreadSafe.ql
ql/java/ql/src/Likely Bugs/Frameworks/JUnit/JUnit5MissingNestedAnnotation.ql
ql/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.ql
ql/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.ql
Expand Down
184 changes: 184 additions & 0 deletions java/ql/lib/semmle/code/java/Concurrency.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,57 @@ overlay[local?]
module;

import java
import semmle.code.java.frameworks.Mockito

/**
* A Java type representing a lock.
* We identify a lock type as one that has both `lock` and `unlock` methods.
*/
class LockType extends RefType {
LockType() {
this.getAMethod().hasName("lock") and
this.getAMethod().hasName("unlock")
}

/** Gets a method that is locking this lock type. */
private Method getLockMethod() {
result.getDeclaringType() = this and
result.hasName(["lock", "lockInterruptibly", "tryLock"])
}

/** Gets a method that is unlocking this lock type. */
private Method getUnlockMethod() {
result.getDeclaringType() = this and
result.hasName("unlock")
}

/** Gets an `isHeldByCurrentThread` method of this lock type. */
private Method getIsHeldByCurrentThreadMethod() {
result.getDeclaringType() = this and
result.hasName("isHeldByCurrentThread")
}

/** Gets a call to a method that is locking this lock type. */
MethodCall getLockAccess() {
result.getMethod() = this.getLockMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodCall
}

/** Gets a call to a method that is unlocking this lock type. */
MethodCall getUnlockAccess() {
result.getMethod() = this.getUnlockMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodCall
}

/** Gets a call to a method that checks if the lock is held by the current thread. */
MethodCall getIsHeldByCurrentThreadAccess() {
result.getMethod() = this.getIsHeldByCurrentThreadMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodCall
}
}

/**
* Holds if `e` is synchronized by a local synchronized statement `sync` on the variable `v`.
Expand Down Expand Up @@ -49,3 +100,136 @@ class SynchronizedCallable extends Callable {
)
}
}

/**
* This module provides predicates, chiefly `locallyMonitors`, to check if a given expression is synchronized on a specific monitor.
*/
module Monitors {
/**
* A monitor is any object that is used to synchronize access to a shared resource.
* This includes locks as well as variables used in synchronized blocks (including `this`).
*/
newtype TMonitor =
/** Either a lock or a variable used in a synchronized block. */
TVariableMonitor(Variable v) {
v.getType() instanceof LockType or locallySynchronizedOn(_, _, v)
} or
/** An instance reference used as a monitor. */
TInstanceMonitor(RefType thisType) { locallySynchronizedOnThis(_, thisType) } or
/** A class used as a monitor. */
TClassMonitor(RefType classType) { locallySynchronizedOnClass(_, classType) }

/**
* A monitor is any object that is used to synchronize access to a shared resource.
* This includes locks as well as variables used in synchronized blocks (including `this`).
*/
class Monitor extends TMonitor {
/** Gets the location of this monitor. */
abstract Location getLocation();

/** Gets a textual representation of this element. */
abstract string toString();
}

/**
* A variable used as a monitor.
* The variable is either a lock or is used in a synchronized block.
* E.g `synchronized (m) { ... }` or `m.lock();`
*/
class VariableMonitor extends Monitor, TVariableMonitor {
override Location getLocation() { result = this.getVariable().getLocation() }

override string toString() { result = "VariableMonitor(" + this.getVariable().toString() + ")" }

/** Gets the variable being used as a monitor. */
Variable getVariable() { this = TVariableMonitor(result) }
}

/**
* An instance reference used as a monitor.
* Either via `synchronized (this) { ... }` or by marking a non-static method as `synchronized`.
*/
class InstanceMonitor extends Monitor, TInstanceMonitor {
override Location getLocation() { result = this.getThisType().getLocation() }

override string toString() { result = "InstanceMonitor(" + this.getThisType().toString() + ")" }

/** Gets the instance reference being used as a monitor. */
RefType getThisType() { this = TInstanceMonitor(result) }
}

/**
* A class used as a monitor.
* This is achieved by marking a static method as `synchronized`.
*/
class ClassMonitor extends Monitor, TClassMonitor {
override Location getLocation() { result = this.getClassType().getLocation() }

override string toString() { result = "ClassMonitor(" + this.getClassType().toString() + ")" }

/** Gets the class being used as a monitor. */
RefType getClassType() { this = TClassMonitor(result) }
}

/** Holds if the expression `e` is synchronized on the monitor `m`. */
predicate locallyMonitors(Expr e, Monitor m) {
exists(Variable v | v = m.(VariableMonitor).getVariable() |
locallyLockedOn(e, v)
or
locallySynchronizedOn(e, _, v)
)
or
locallySynchronizedOnThis(e, m.(InstanceMonitor).getThisType())
or
locallySynchronizedOnClass(e, m.(ClassMonitor).getClassType())
}

/** Gets the control flow node that must dominate `e` when `e` is synchronized on a lock. */
ControlFlowNode getNodeToBeDominated(Expr e) {
// If `e` is the LHS of an assignment, use the control flow node for the assignment
exists(Assignment asgn | asgn.getDest() = e | result = asgn.getControlFlowNode())
or
// if `e` is not the LHS of an assignment, use the default control flow node
not exists(Assignment asgn | asgn.getDest() = e) and
result = e.getControlFlowNode()
}

/** A field storing a lock. */
class LockField extends Field {
LockField() { this.getType() instanceof LockType }

/** Gets a call to a method locking the lock stored in this field. */
MethodCall getLockCall() {
result.getQualifier() = this.getRepresentative().getAnAccess() and
result = this.getType().(LockType).getLockAccess()
}

/** Gets a call to a method unlocking the lock stored in this field. */
MethodCall getUnlockCall() {
result.getQualifier() = this.getRepresentative().getAnAccess() and
result = this.getType().(LockType).getUnlockAccess()
}

/**
* Gets a variable representing this field.
* It can be the field itself or a local variable initialized to the field.
*/
private Variable getRepresentative() {
result = this
or
result.getInitializer() = this.getAnAccess()
}
}

/** Holds if `e` is synchronized on the `Lock` `lock` by a locking call. */
predicate locallyLockedOn(Expr e, LockField lock) {
exists(MethodCall lockCall, MethodCall unlockCall |
lockCall = lock.getLockCall() and
unlockCall = lock.getUnlockCall()
|
dominates(lockCall.getControlFlowNode(), unlockCall.getControlFlowNode()) and
dominates(lockCall.getControlFlowNode(), getNodeToBeDominated(e)) and
postDominates(unlockCall.getControlFlowNode(), getNodeToBeDominated(e))
)
}
}
Loading