[clang] [Clang] Fix crash on `__atomic_always_lock_free` with a size of zero (PR #229031)
Akash Manna via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 5 02:58:12 PDT 2026
https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/229031
Fixes #170139
Fixes #120082
`__atomic_always_lock_free` and `__atomic_is_lock_free` check the size with `CharUnits::isPowerOfTwo()`, which is also true for zero. So a size of 0 was treated as a lock-free size, and when the pointer argument was a constant we went on to test its alignment against that size, which builds `llvm::Align(0)` and asserts. Without assertions nothing crashed, but the answer depended on how the pointer was written: `__atomic_always_lock_free(0, p)` was true for a null pointer or `&obj` and false for `(void *)4`.
Both constant evaluators now require the size to be positive, so 0 is handled like any other size that isn't lock-free, such as 3: `__atomic_always_lock_free` is false, and `__atomic_is_lock_free` and `__c11_atomic_is_lock_free` are not folded and end up as the runtime call. GCC does the same. I left `CharUnits::isPowerOfTwo()` alone because ObjC property codegen relies on it accepting a zero-sized ivar. The positive-size check also rejects `2^63`, which wraps to a negative `CharUnits` and used to be reported as lock-free.
LLM tools (Gemini 3.1 Pro, not Agent) were used for this contribution. I've reviewed, built, and tested the change myself before pushing to GitHub.
>From 93feb51113746ea61bf58266f44da658db975d2a Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 5 Oct 2026 15:22:30 +0530
Subject: [PATCH] [Clang] Fix crash on `__atomic_always_lock_free` with a size
of zero
`CharUnits::isPowerOfTwo()` is also true for zero, so the constant
evaluators accepted a size of 0 as lock-free and built `llvm::Align(0)`
when checking the alignment of a constant pointer argument.
Require a positive size in the classic evaluator and in the bytecode
interpreter. A size of 0 is now handled like any other size that is not
lock-free: `__atomic_always_lock_free` is false, and
`__atomic_is_lock_free` and `__c11_atomic_is_lock_free` are not folded.
Fixes #170139
Fixes #120082
---
clang/docs/ReleaseNotes.md | 2 ++
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 4 ++--
clang/lib/AST/ExprConstant.cpp | 2 +-
clang/test/AST/ByteCode/atomic.c | 14 ++++++++++++++
clang/test/CodeGen/atomic-ops.c | 16 ++++++++++++++++
clang/test/Sema/atomic-ops.c | 12 ++++++++++++
6 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf194e87f76a6..6ff60826e5648 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -605,6 +605,8 @@ features cannot lower the translation-unit ABI level;
inside a member function call synthesized by ``__builtin_invoke``. (#GH185241)
- Fixed a crash in ``__builtin_dump_struct`` when ``-Werror`` promotes
format warnings to errors. (#GH211943)
+- Fixed a crash when `__atomic_always_lock_free` or `__atomic_is_lock_free` is
+ called with a size of zero. (#GH170139, #GH120082)
- Fixed wrong code generation in `__builtin_clear_padding` wherein the wrong
bits of the following types were cleared: `_BitInt`, struct bitfields, and
packed boolean vectors. (#GH215809), (#GH216063), (#GH224033)
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 5ea3cc6bd2920..48b896e2ccece 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -1181,7 +1181,7 @@ static bool interp__builtin_atomic_lock_free(InterpState &S, CodePtr OpPC,
// Check power-of-two.
CharUnits Size = CharUnits::fromQuantity(SizeVal);
- if (Size.isPowerOfTwo()) {
+ if (Size.isPositive() && Size.isPowerOfTwo()) {
// Check against inlining width.
unsigned InlineWidthBits =
S.getASTContext().getTargetInfo().getMaxAtomicInlineWidth();
@@ -1239,7 +1239,7 @@ static bool interp__builtin_c11_atomic_is_lock_free(InterpState &S,
return false;
CharUnits Size = CharUnits::fromQuantity(SizeVal);
- if (Size.isPowerOfTwo()) {
+ if (Size.isPositive() && Size.isPowerOfTwo()) {
// Check against inlining width.
unsigned InlineWidthBits =
S.getASTContext().getTargetInfo().getMaxAtomicInlineWidth();
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 34c3e39807c7f..98ed45ceaa0fa 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -18084,7 +18084,7 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
// Check power-of-two.
CharUnits Size = CharUnits::fromQuantity(SizeVal.getZExtValue());
- if (Size.isPowerOfTwo()) {
+ if (Size.isPositive() && Size.isPowerOfTwo()) {
// Check against inlining width.
unsigned InlineWidthBits =
Info.Ctx.getTargetInfo().getMaxAtomicInlineWidth();
diff --git a/clang/test/AST/ByteCode/atomic.c b/clang/test/AST/ByteCode/atomic.c
index a93787ccd1e02..ff85c98a0f7b7 100644
--- a/clang/test/AST/ByteCode/atomic.c
+++ b/clang/test/AST/ByteCode/atomic.c
@@ -65,9 +65,23 @@ _Static_assert(!__atomic_always_lock_free(4, (void*)2), "");
_Static_assert(!__atomic_always_lock_free(4, (void*)-2), "");
_Static_assert(__atomic_always_lock_free(4, (void*)4), "");
_Static_assert(__atomic_always_lock_free(4, (void*)-4), "");
+_Static_assert(!__atomic_always_lock_free(2, (void*)1), "");
+_Static_assert(__atomic_always_lock_free(2, (void*)2), "");
+_Static_assert(!__atomic_always_lock_free(8, (void*)4), "");
+_Static_assert(__atomic_always_lock_free(8, (void*)8), "");
_Static_assert(__atomic_always_lock_free(1, "string"), "");
_Static_assert(!__atomic_always_lock_free(2, "string"), "");
_Static_assert(__atomic_always_lock_free(2, (int[2]){}), "");
void dummyfn();
_Static_assert(__atomic_always_lock_free(2, dummyfn) || 1, "");
+
+// GH170139, GH120082
+_Static_assert(!__atomic_always_lock_free(0, 0), "");
+_Static_assert(!__atomic_always_lock_free(0, 1), ""); // both-error {{incompatible integer to pointer conversion}}
+_Static_assert(!__atomic_always_lock_free(0, (void*)4), "");
+_Static_assert(!__atomic_always_lock_free(0, &ai), "");
+_Static_assert(__atomic_is_lock_free(0, 0), ""); // both-error {{not an integral constant expression}}
+_Static_assert(__atomic_is_lock_free(0, (void*)4), ""); // both-error {{not an integral constant expression}}
+_Static_assert(__atomic_is_lock_free(0, &ai), ""); // both-error {{not an integral constant expression}}
+_Static_assert(__c11_atomic_is_lock_free(0), ""); // both-error {{not an integral constant expression}}
diff --git a/clang/test/CodeGen/atomic-ops.c b/clang/test/CodeGen/atomic-ops.c
index 8716d3aeb9516..c2318c8512956 100644
--- a/clang/test/CodeGen/atomic-ops.c
+++ b/clang/test/CodeGen/atomic-ops.c
@@ -370,6 +370,22 @@ int lock_free(struct Incomplete *incomplete) {
return __c11_atomic_is_lock_free(sizeof(_Atomic(int)));
}
+int gh170139(void) {
+ // CHECK-LABEL: @gh170139
+
+ // CHECK: call zeroext i1 @__atomic_is_lock_free(i32 noundef 0, ptr noundef null)
+ __atomic_is_lock_free(0, 0);
+
+ // CHECK: call zeroext i1 @__atomic_is_lock_free(i32 noundef 0, ptr noundef null)
+ __c11_atomic_is_lock_free(0);
+
+ // CHECK-NOT: call
+ __atomic_always_lock_free(0, 0);
+
+ // CHECK: ret i32 0
+ return __atomic_always_lock_free(0, 0);
+}
+
// Tests for atomic operations on big values. These should call the functions
// defined here:
// http://gcc.gnu.org/wiki/Atomic/GCCMM/LIbrary#The_Library_interface
diff --git a/clang/test/Sema/atomic-ops.c b/clang/test/Sema/atomic-ops.c
index a3bc8d2ec75e7..cebbc78ea2262 100644
--- a/clang/test/Sema/atomic-ops.c
+++ b/clang/test/Sema/atomic-ops.c
@@ -147,6 +147,18 @@ _Static_assert(__atomic_always_lock_free(2, (int[2]){}), "");
void dummyfn();
_Static_assert(__atomic_always_lock_free(2, dummyfn) || 1, "");
+// GH170139, GH120082
+_Static_assert(!__atomic_always_lock_free(0, 0), "");
+_Static_assert(!__atomic_always_lock_free(0, 1), ""); // expected-error {{incompatible integer to pointer conversion}}
+_Static_assert(!__atomic_always_lock_free(0, (void*)4), "");
+_Static_assert(!__atomic_always_lock_free(0, &i32), "");
+_Static_assert(__atomic_is_lock_free(0, 0), ""); // expected-error {{not an integral constant expression}}
+_Static_assert(__atomic_is_lock_free(0, (void*)4), ""); // expected-error {{not an integral constant expression}}
+_Static_assert(__atomic_is_lock_free(0, &i32), ""); // expected-error {{not an integral constant expression}}
+_Static_assert(__c11_atomic_is_lock_free(0), ""); // expected-error {{not an integral constant expression}}
+_Static_assert(!__atomic_always_lock_free(SIZE_MAX / 2 + 1, 0), "");
+_Static_assert(__c11_atomic_is_lock_free(SIZE_MAX / 2 + 1), ""); // expected-error {{not an integral constant expression}}
+
#define _AS1 __attribute__((address_space(1)))
More information about the cfe-commits
mailing list