From 9399a0cda1a55a18e33d68be331284c654ea7c6b Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Tue, 9 Sep 2025 10:50:27 -0700 Subject: [PATCH 1/2] Fix __builtin_object_size calculation for references of unknown origin in C++23 This addresses an issue introduced by 0a9c08c59ba61e727e9dee6d71883d9106963442, which implemented P2280R4. This fixes the issue reported in https://github.com/llvm/llvm-project/pull/95474#issuecomment-3025887023. rdar://149897839 --- clang/lib/AST/ExprConstant.cpp | 16 ++++++++- clang/test/Sema/builtin-object-size.cpp | 48 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/builtin-object-size.cpp diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 2376e482a19f5..229211c4f2cb7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -13279,6 +13279,9 @@ static bool refersToCompleteObject(const LValue &LVal) { if (LVal.Designator.Invalid) return false; + if (LVal.AllowConstexprUnknown) + return false; + if (!LVal.Designator.Entries.empty()) return LVal.Designator.isMostDerivedAnUnsizedArray(); @@ -13328,7 +13331,7 @@ static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const LValue &LVal) { return false; }; - return LVal.InvalidBase && + return (LVal.InvalidBase || LVal.AllowConstexprUnknown) && Designator.Entries.size() == Designator.MostDerivedPathLength && Designator.MostDerivedIsArrayElement && isFlexibleArrayMember() && isDesignatorAtObjectEnd(Ctx, LVal); @@ -13396,6 +13399,17 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc, if (LVal.InvalidBase) return false; + // We cannot deterimine the end offset of the enitre object if this is an + // unknown reference. + if (Type == 0 && LVal.AllowConstexprUnknown) + return false; + + // We cannot deterimine the end offset of the subobject if this is an + // unknown reference and the subobject designator is invalid (e.g., unsized + // array designator). + if (Type == 1 && LVal.Designator.Invalid && LVal.AllowConstexprUnknown) + return false; + QualType BaseTy = getObjectType(LVal.getLValueBase()); const bool Ret = CheckedHandleSizeof(BaseTy, EndOffset); addFlexibleArrayMemberInitSize(Info, BaseTy, LVal, EndOffset); diff --git a/clang/test/Sema/builtin-object-size.cpp b/clang/test/Sema/builtin-object-size.cpp new file mode 100644 index 0000000000000..3995e1880ec81 --- /dev/null +++ b/clang/test/Sema/builtin-object-size.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fstrict-flex-arrays=0 -DSTRICT0 -std=c++23 -verify %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fstrict-flex-arrays=1 -DSTRICT1 -std=c++23 -verify %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fstrict-flex-arrays=2 -DSTRICT2 -std=c++23 -verify %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fstrict-flex-arrays=3 -DSTRICT3 -std=c++23 -verify %s + +struct EmptyS { + int i; + char a[]; +}; + +template +struct S { + int i; + char a[N]; +}; + +extern S<2> &s2; +static_assert(__builtin_object_size(s2.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(s2.a, 1) == 2); +#if defined(STRICT0) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif +static_assert(__builtin_object_size(s2.a, 2) == 4); +static_assert(__builtin_object_size(s2.a, 3) == 2); + +extern S<1> &s1; +static_assert(__builtin_object_size(s1.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(s1.a, 1) == 1); +#if defined(STRICT0) || defined(STRICT1) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif +static_assert(__builtin_object_size(s1.a, 2) == 4); +static_assert(__builtin_object_size(s1.a, 3) == 1); + +extern S<0> &s0; +static_assert(__builtin_object_size(s0.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(s0.a, 1) == 0); +#if defined(STRICT0) || defined(STRICT1) || defined(STRICT2) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif +static_assert(__builtin_object_size(s0.a, 2) == 0); +static_assert(__builtin_object_size(s0.a, 3) == 0); + +extern EmptyS ∅ +static_assert(__builtin_object_size(empty.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(empty.a, 1)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(empty.a, 2) == 0); +static_assert(__builtin_object_size(empty.a, 3)); // expected-error {{static assertion expression is not an integral constant expression}} From 036d1411f4bc9368a032dda6a724768186bf33c0 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Thu, 11 Sep 2025 13:12:41 -0700 Subject: [PATCH 2/2] Address review comments. - Bail out in more cases. - Test potential constant expression. --- clang/lib/AST/ExprConstant.cpp | 34 ++++++++++++------- clang/test/CodeGenCXX/builtin-object-size.cpp | 30 ++++++++++++++++ .../{Sema => SemaCXX}/builtin-object-size.cpp | 33 +++++++++++++++--- .../SemaCXX/constant-expression-p2280r4.cpp | 15 ++++++++ 4 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 clang/test/CodeGenCXX/builtin-object-size.cpp rename clang/test/{Sema => SemaCXX}/builtin-object-size.cpp (53%) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 229211c4f2cb7..43f1378d6859e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -13399,16 +13399,18 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc, if (LVal.InvalidBase) return false; - // We cannot deterimine the end offset of the enitre object if this is an - // unknown reference. - if (Type == 0 && LVal.AllowConstexprUnknown) - return false; + if (LVal.AllowConstexprUnknown) { + // We cannot deterimine the end offset of the enitre object. + if ((Type == 0 || Type == 2)) + return false; - // We cannot deterimine the end offset of the subobject if this is an - // unknown reference and the subobject designator is invalid (e.g., unsized - // array designator). - if (Type == 1 && LVal.Designator.Invalid && LVal.AllowConstexprUnknown) - return false; + // We cannot deterimine the end offset of the subobject if the subobject + // designator is invalid (e.g., unsized array designator). + if (LVal.Designator.Invalid) { + assert(Type != 3 && "cannot be Type 3"); + return false; + } + } QualType BaseTy = getObjectType(LVal.getLValueBase()); const bool Ret = CheckedHandleSizeof(BaseTy, EndOffset); @@ -13435,10 +13437,14 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc, return convertUnsignedAPIntToCharUnits(APEndOffset, EndOffset); // If we cannot determine the size of the initial allocation, then we can't - // given an accurate upper-bound. However, we are still able to give - // conservative lower-bounds for Type=3. + // give an accurate upper-bound. if (Type == 1) return false; + + // However, we are still able to give conservative lower-bounds if Type=3 + // and this isn't an unknown reference. + if (LVal.AllowConstexprUnknown) + return false; } CharUnits BytesPerElem; @@ -13455,6 +13461,10 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc, uint64_t ArrayIndex = Designator.Entries.back().getAsArrayIndex(); ElemsRemaining = ArraySize <= ArrayIndex ? 0 : ArraySize - ArrayIndex; } else { + // If this is an unknown reference and there are no subobject designators, + // we cannot determine whether the object is an array element. + if (LVal.AllowConstexprUnknown && LVal.Designator.Entries.empty()) + return false; ElemsRemaining = Designator.isOnePastTheEnd() ? 0 : 1; } @@ -13570,7 +13580,7 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, case EvalInfo::EM_ConstantFold: case EvalInfo::EM_IgnoreSideEffects: // Leave it to IR generation. - return Error(E); + return Info.CheckingPotentialConstantExpression ? false : Error(E); case EvalInfo::EM_ConstantExpressionUnevaluated: // Reduce it to a constant now. return Success((Type & 2) ? 0 : -1, E); diff --git a/clang/test/CodeGenCXX/builtin-object-size.cpp b/clang/test/CodeGenCXX/builtin-object-size.cpp new file mode 100644 index 0000000000000..3fc8304d42139 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-object-size.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fstrict-flex-arrays=0 -std=c++23 -emit-llvm -o - %s | FileCheck %s + +struct EmptyS { + int i; + char a[]; +}; + +template +struct S { + int i; + char a[N]; +}; + +// CHECK-LABEL: define noundef i32 @_Z4testRK6EmptyS( +// CHECK: ret i32 0 +unsigned test(const EmptyS &empty) { + return __builtin_object_size(empty.a, 3); +} + +// CHECK-LABEL: define noundef i32 @_Z4testRK1SILj2EE( +// CHECK: ret i32 0 +unsigned test(const S<2> &s2) { + return __builtin_object_size(s2.a, 3); +} + +// CHECK-LABEL: define noundef i32 @_Z4testRi( +// CHECK: ret i32 0 +unsigned test(int &i) { + return __builtin_object_size(&i, 3); +} diff --git a/clang/test/Sema/builtin-object-size.cpp b/clang/test/SemaCXX/builtin-object-size.cpp similarity index 53% rename from clang/test/Sema/builtin-object-size.cpp rename to clang/test/SemaCXX/builtin-object-size.cpp index 3995e1880ec81..b3e9d06b086a1 100644 --- a/clang/test/Sema/builtin-object-size.cpp +++ b/clang/test/SemaCXX/builtin-object-size.cpp @@ -14,14 +14,21 @@ struct S { char a[N]; }; +struct T { + int a, b, c; +}; + extern S<2> &s2; static_assert(__builtin_object_size(s2.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(s2.a, 1) == 2); #if defined(STRICT0) // expected-error@-2 {{static assertion expression is not an integral constant expression}} #endif -static_assert(__builtin_object_size(s2.a, 2) == 4); +static_assert(__builtin_object_size(s2.a, 2)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(s2.a, 3) == 2); +#if defined(STRICT0) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif extern S<1> &s1; static_assert(__builtin_object_size(s1.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} @@ -29,8 +36,11 @@ static_assert(__builtin_object_size(s1.a, 1) == 1); #if defined(STRICT0) || defined(STRICT1) // expected-error@-2 {{static assertion expression is not an integral constant expression}} #endif -static_assert(__builtin_object_size(s1.a, 2) == 4); +static_assert(__builtin_object_size(s1.a, 2)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(s1.a, 3) == 1); +#if defined(STRICT0) || defined(STRICT1) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif extern S<0> &s0; static_assert(__builtin_object_size(s0.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} @@ -38,11 +48,26 @@ static_assert(__builtin_object_size(s0.a, 1) == 0); #if defined(STRICT0) || defined(STRICT1) || defined(STRICT2) // expected-error@-2 {{static assertion expression is not an integral constant expression}} #endif -static_assert(__builtin_object_size(s0.a, 2) == 0); +static_assert(__builtin_object_size(s0.a, 2)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(s0.a, 3) == 0); +#if defined(STRICT0) || defined(STRICT1) || defined(STRICT2) +// expected-error@-2 {{static assertion expression is not an integral constant expression}} +#endif extern EmptyS ∅ static_assert(__builtin_object_size(empty.a, 0)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(empty.a, 1)); // expected-error {{static assertion expression is not an integral constant expression}} -static_assert(__builtin_object_size(empty.a, 2) == 0); +static_assert(__builtin_object_size(empty.a, 2)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(__builtin_object_size(empty.a, 3)); // expected-error {{static assertion expression is not an integral constant expression}} + +extern T &t; +static_assert(__builtin_object_size(&t.b, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(&t.b, 1) == 4); +static_assert(__builtin_object_size(&t.b, 2)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(&t.b, 3) == 4); + +extern int &i; +static_assert(__builtin_object_size(&i, 0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(&i, 1)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(&i, 2)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(__builtin_object_size(&i, 3)); // expected-error {{static assertion expression is not an integral constant expression}} diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp index 78e2e17016280..1b1ac65fecbe1 100644 --- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp +++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp @@ -431,3 +431,18 @@ namespace InvalidConstexprFn { static_assert(sub(arr, arr) == 0); static_assert(add(arr[0]) == &arr[3]); } + +namespace BuiltinObjectSize { + constexpr int f0(int &a) { + return 1 / (__builtin_object_size(&a, 0) - 4); + } + constexpr int f1(int &a) { + return 1 / (__builtin_object_size(&a, 1) - 4); + } + constexpr int f2(int &a) { + return 1 / (__builtin_object_size(&a, 2) - 4); + } + constexpr int f3(int &a) { + return 1 / (__builtin_object_size(&a, 3) - 4); + } +}