Author: nudt_yixiao Date: 2026-10-03T13:58:56Z New Revision: 971833ee5513f01a57439b15040887932af114fc
URL: https://github.com/llvm/llvm-project/commit/971833ee5513f01a57439b15040887932af114fc DIFF: https://github.com/llvm/llvm-project/commit/971833ee5513f01a57439b15040887932af114fc.diff LOG: [Clang] Fix crash in alignment builtins with null pointers (#224549) Clang can crash when evaluating __builtin_align_up, __builtin_align_down, or __builtin_is_aligned with a null pointer. 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. Assisted-by: GPT-5.6 AI was used to assist with code analysis, debugging, and drafting the PR description. I personally debugged, reviewed and verified the changes, tested the code, and fully understand the submitted contribution. Fixes https://github.com/llvm/llvm-project/issues/216999 Co-authored-by: Timm Baeder <[email protected]> Added: Modified: clang/docs/LanguageExtensions.md clang/docs/ReleaseNotes.md clang/lib/AST/ByteCode/InterpBuiltin.cpp clang/lib/AST/ExprConstant.cpp clang/test/Sema/builtin-align.c clang/test/SemaCXX/builtin-align-cxx.cpp Removed: ################################################################################ diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md index 7ec585d836292..3db8d083c4075 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 2dca8fe3deed1..c99b871cc1e20 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -609,6 +609,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 5589ae141f66f..5ea3cc6bd2920 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -1315,6 +1315,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 68c5e275dd48f..34c3e39807c7f 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -10672,6 +10672,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 @@ -17110,6 +17124,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 c33ad8d1ad0ef..4600ab62aebc6 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@-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@-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 51e610ccc0cd1..d1feb0661b5b9 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); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
