[clang] [Clang] Fix crash in alignment builtins with null pointers (PR #224549)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 3 06:13:24 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/224549
>From c264ed946897171388ae179a7fe6a97c18f330be Mon Sep 17 00:00:00 2001
From: nudt_yixiao <1272950933 at qq.com>
Date: Thu, 1 Oct 2026 17:56:25 +0800
Subject: [PATCH] [Clang] Fix crash in alignment builtins with base-less
pointers
Avoid querying base alignment for pointers without an underlying object
during constant evaluation.
Null pointers are handled as always aligned values, while other
base-less pointers are rejected rather than interpreted using their
numeric address.
Add regression tests for null and integer-derived pointer operands.
---
clang/docs/LanguageExtensions.md | 5 +++++
clang/docs/ReleaseNotes.md | 4 ++++
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 16 +++++++++++++++
clang/lib/AST/ExprConstant.cpp | 26 ++++++++++++++++++++++++
clang/test/Sema/builtin-align.c | 24 ++++++++++++++++++++++
clang/test/SemaCXX/builtin-align-cxx.cpp | 9 ++++++++
6 files changed, 84 insertions(+)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 7ec585d83629278..3db8d083c407569 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -5297,6 +5297,11 @@ This means that arbitrary integer values stored in pointer-type variables must
not be passed to these builtins. For those use cases, the builtins can still be
used, but the operation must be performed on the pointer cast to `uintptr_t`.
+Null pointers are considered to be aligned to any requested alignment.
+Therefore, `__builtin_is_aligned` evaluates to true for null pointer
+arguments, and `__builtin_align_up` and `__builtin_align_down` preserve
+the null pointer value.
+
If Clang can determine that the alignment is not a power of two at compile time,
it will result in a compilation failure. If the alignment argument is not a
power of two at run time, the behavior of these builtins is undefined.
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf190df9769ddf0..28189cbac388333 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -595,6 +595,10 @@ features cannot lower the translation-unit ABI level;
reference to a vector type; `vec_step` (in C++ for OpenCL) and
`__builtin_ptrauth_type_discriminator` similarly no longer accept reference
types that their evaluation silently mishandled. (#GH216997)
+- Fixed a crash when constant-evaluating `__builtin_align_up`, `__builtin_align_down`,
+ or `__builtin_is_aligned` with pointers without an underlying object. Null pointers
+ are handled as aligned values, while other base-less pointers are rejected during constant
+ evaluation.
#### Bug Fixes to Attribute Support
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 3303a56f2c053cc..cd275cc93180422 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -1319,6 +1319,22 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
}
assert(FirstArgT == PT_Ptr);
const Pointer &Ptr = S.Stk.pop<Pointer>();
+
+ // Null pointers are always aligned. Preserve null pointers for
+ // align_up/align_down and return true for is_aligned.
+ if (Ptr.isZero()) {
+ if (BuiltinOp == Builtin::BI__builtin_is_aligned) {
+ S.Stk.push<Boolean>(true);
+ return true;
+ }
+
+ assert(BuiltinOp == Builtin::BI__builtin_align_up ||
+ BuiltinOp == Builtin::BI__builtin_align_down);
+
+ S.Stk.push<Pointer>(Ptr);
+ return true;
+ }
+
if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer()) {
S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_compute)
<< Alignment;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 91bbc6ef3c9c24d..119e8aedab9d7bb 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10692,6 +10692,20 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
if (!getAlignmentArgument(E->getArg(1), E->getArg(0)->getType(), Info,
Alignment))
return false;
+
+ if (!Result.Base) {
+ // Null pointers are always aligned and align_up/align_down preserve null.
+ if (Result.Offset.isZero())
+ return true;
+
+ // Non-null pointers without a base (for example, integer-to-pointer
+ // casts such as (void *)32) do not have enough information to perform
+ // pointer arithmetic during constant evaluation.
+ Info.FFDiag(E->getArg(0), diag::note_constexpr_alignment_adjust)
+ << Alignment;
+ return false;
+ }
+
CharUnits BaseAlignment = getBaseAlignment(Info, Result);
CharUnits PtrAlign = BaseAlignment.alignmentAtOffset(Result.Offset);
// For align_up/align_down, we can return the same value if the alignment
@@ -17079,6 +17093,18 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
// If we evaluated a pointer, check the minimum known alignment.
LValue Ptr;
Ptr.setFrom(Info.Ctx, Src);
+ if (!Ptr.Base) {
+ // Null pointers are always aligned.
+ if (Ptr.Offset.isZero())
+ return Success(1, E);
+
+ Info.FFDiag(E->getArg(0), diag::note_constexpr_alignment_compute)
+ << Alignment;
+ // Reject non-null pointers without an underlying object.
+ // Do not interpret the pointer offset as an integer address.
+ return false;
+ }
+
CharUnits BaseAlignment = getBaseAlignment(Info, Ptr);
CharUnits PtrAlign = BaseAlignment.alignmentAtOffset(Ptr.Offset);
// We can return true if the known alignment at the computed offset is
diff --git a/clang/test/Sema/builtin-align.c b/clang/test/Sema/builtin-align.c
index c33ad8d1ad0ef87..4600ab62aebc61f 100644
--- a/clang/test/Sema/builtin-align.c
+++ b/clang/test/Sema/builtin-align.c
@@ -1,6 +1,9 @@
// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_align_down -DRETURNS_BOOL=0 %s -fsyntax-only -verify -Wpedantic
// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_align_up -DRETURNS_BOOL=0 %s -fsyntax-only -verify -Wpedantic
// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_is_aligned -DRETURNS_BOOL=1 %s -fsyntax-only -verify -Wpedantic
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_align_down -DRETURNS_BOOL=0 %s -fsyntax-only -verify -Wpedantic -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_align_up -DRETURNS_BOOL=0 %s -fsyntax-only -verify -Wpedantic -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -DALIGN_BUILTIN=__builtin_is_aligned -DRETURNS_BOOL=1 %s -fsyntax-only -verify -Wpedantic -fexperimental-new-constant-interpreter
struct Aggregate {
int i;
@@ -115,6 +118,12 @@ void constant_expression(int x) {
_Static_assert(!__builtin_is_aligned(256, 512ULL), "");
_Static_assert(__builtin_align_up(33, 32) == 64, "");
_Static_assert(__builtin_align_down(33, 32) == 32, "");
+ _Static_assert(__builtin_is_aligned((void *)0, 1), ""); // expected-warning {{checking whether a value is aligned to 1 byte is always true}}
+ _Static_assert(__builtin_is_aligned((void *)0, 32), "");
+ _Static_assert(__builtin_is_aligned((void *)32, 32), ""); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot constant evaluate whether run-time alignment is at least 32}}
+ _Static_assert(!__builtin_is_aligned((void *)32, 64), ""); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot constant evaluate whether run-time alignment is at least 64}}
// But not if one of the arguments isn't constant:
_Static_assert(ALIGN_BUILTIN(33, x) != 100, ""); // expected-error {{static assertion expression is not an integral constant expression}}
@@ -125,6 +134,21 @@ void constant_expression(int x) {
int global1 = __builtin_align_down(33, 8);
int global2 = __builtin_align_up(33, 8);
_Bool global3 = __builtin_is_aligned(33, 8);
+_Bool global4 = __builtin_is_aligned((void *)33, 8); // expected-error {{initializer element is not a compile-time constant}}
+_Bool global5 = __builtin_is_aligned((void *)32, 32); // expected-error {{initializer element is not a compile-time constant}}
+_Bool global6 = __builtin_is_aligned((void *)32, 64); // expected-error {{initializer element is not a compile-time constant}}
+
+// Zero-valued null pointers are already aligned and should remain unchanged.
+void *null_align_up_1 = __builtin_align_up((void *)0, 1); // expected-warning {{aligning a value to 1 byte is a no-op}}
+void *null_align_up_32 = __builtin_align_up((void *)0, 32);
+void *null_align_down_1 = __builtin_align_down((void *)0, 1); // expected-warning {{aligning a value to 1 byte is a no-op}}
+void *null_align_down_32 = __builtin_align_down((void *)0, 32);
+
+// Check alignment builtins with non-zero integer-derived pointers.
+void *num_align_up_32 = __builtin_align_up((void *)32, 32); // expected-error {{initializer element is not a compile-time constant}}
+void *num_align_up_64 = __builtin_align_up((void *)32, 64); // expected-error {{initializer element is not a compile-time constant}}
+void *num_align_down_32 = __builtin_align_down((void *)32, 32); // expected-error {{initializer element is not a compile-time constant}}
+void *num_align_down_64 = __builtin_align_down((void *)32, 64); // expected-error {{initializer element is not a compile-time constant}}
extern void test_ptr(char *c);
char *test_array_and_fnptr(void) {
diff --git a/clang/test/SemaCXX/builtin-align-cxx.cpp b/clang/test/SemaCXX/builtin-align-cxx.cpp
index 51e610ccc0cd185..d1feb0661b5b97f 100644
--- a/clang/test/SemaCXX/builtin-align-cxx.cpp
+++ b/clang/test/SemaCXX/builtin-align-cxx.cpp
@@ -248,3 +248,12 @@ _Alignas(void) char align_void_array[1]; // expected-error {{invalid application
static_assert(!__builtin_is_aligned(&"", 4), ""); // expected-error {{not an integral constant expression}} \
// expected-note {{cannot constant evaluate whether run-time alignment is at least 4}}
+
+static_assert(__builtin_is_aligned((void *)0, 1), ""); // expected-warning {{checking whether a value is aligned to 1 byte is always true}}
+static_assert(__builtin_is_aligned((void *)0, 32), "");
+
+// Zero-valued null pointers are already aligned and should remain unchanged.
+void *null_align_up_1 = __builtin_align_up((void *)0, 1); // expected-warning {{aligning a value to 1 byte is a no-op}}
+void *null_align_up_32 = __builtin_align_up((void *)0, 32);
+void *null_align_down_1 = __builtin_align_down((void *)0, 1); // expected-warning {{aligning a value to 1 byte is a no-op}}
+void *null_align_down_32 = __builtin_align_down((void *)0, 32);
More information about the cfe-commits
mailing list