[clang] Fix __builtin_object_size calculation for references of unknown origin in C++23 (PR #157778)

Akira Hatanaka via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 18 14:43:48 PDT 2025


https://github.com/ahatanak updated https://github.com/llvm/llvm-project/pull/157778

>From 9399a0cda1a55a18e33d68be331284c654ea7c6b Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <ahatanak at gmail.com>
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 <unsigned N>
+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 at -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 at -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 at -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 <ahatanak at gmail.com>
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 <unsigned N>
+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 at -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 at -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 at -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 at -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 at -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 at -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);
+  }
+}



More information about the cfe-commits mailing list