Skip to content

False Negative: DoubleCheckedLockingWithInitRace.ql misses initialization races once the double-checked pattern is split across helpers or early returns. #21546

@Carlson-JLQ

Description

@Carlson-JLQ

False Negative: DoubleCheckedLockingWithInitRace.ql misses initialization races once the double-checked pattern is split across helpers or early returns.

Version
codeql 2.24.3

Checker

  • Checker id: Likely Bugs/Concurrency/DoubleCheckedLockingWithInitRace.ql
  • Checker description: This checker detects a potential race condition in double-checked locking patterns where a field assignment inside a synchronized block may be visible to other threads before subsequent side-effect statements are executed.

Description of the false negative

These samples still publish f before the rest of the initialization work is complete. One variant moves the synchronized initialization into a helper, and the other rewrites the fast path as an early return, but the initialization race is the same.

Affected test cases

PosCase1_Var3.java

The helper call only hides the same double-checked-locking race where publication happens before later side effects.

// Double-checked locking with assignment to another field after the field assignment should be flagged as potential race condition.
package scensct.var.pos;

public class PosCase1_Var3 {
    private Object f;
    private Object otherField;

    public Object getF() {
        if (f == null) {
            initField();
        }
        return f;
    }

    private void initField() {
        synchronized (this) {
            if (f == null) {
                f = new Object();
                otherField = new Object();
            }
        }
    }
}

PosCase1_Var5.java

This still publishes the initialized object before a later field assignment completes, which is the race the query is meant to catch.

// Double-checked locking with assignment to another field after the field assignment should be flagged as potential race condition.
package scensct.var.pos;

public class PosCase1_Var5 {
    private Object f;
    private Object otherField;

    public Object getF() {
        if (f != null) {
            return f;
        }
        synchronized (this) {
            if (f == null) {
                f = new Object();
                otherField = new Object();
            }
            return f;
        }
    }
}

Cause analysis

The query appears too tied to one inline statement ordering pattern for double-checked initialization. Once the synchronized block is extracted into a helper or the fast path is expressed as an early return, it stops recognizing that f becomes visible before the remaining side effects complete.

That is a real concurrency gap. Initialization races often survive exactly this kind of refactoring.

References

None known.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions