Author: Timm Baeder
Date: 2026-02-02T14:03:13+01:00
New Revision: d3687732492c955e02b4d6b691c369a82255e38c

URL: 
https://github.com/llvm/llvm-project/commit/d3687732492c955e02b4d6b691c369a82255e38c
DIFF: 
https://github.com/llvm/llvm-project/commit/d3687732492c955e02b4d6b691c369a82255e38c.diff

LOG: [clang][bytecode] Use in Expr::tryEvaluateObjectSize() (#179197)

This is like https://github.com/llvm/llvm-project/pull/179033, which
broke a few builders for reasons I still don't really understand. I ran
the other clang tests and this version fixes a few of the introduced
regressions.

This still regresses `CodeGen/pass-object-size.c`, but that's a
pre-existing issue.

Patch is of coursed based on #179033 by @mariusdr.

Added: 
    clang/test/AST/ByteCode/builtin-object-size-codegen.c
    clang/test/AST/ByteCode/object-size-flex-array.c

Modified: 
    clang/lib/AST/ByteCode/Context.cpp
    clang/lib/AST/ByteCode/Context.h
    clang/lib/AST/ByteCode/EvalEmitter.cpp
    clang/lib/AST/ByteCode/InterpBuiltin.cpp
    clang/lib/AST/ByteCode/InterpHelpers.h
    clang/lib/AST/ExprConstant.cpp
    clang/test/Sema/format-strings-nonnull.c

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/Context.cpp 
b/clang/lib/AST/ByteCode/Context.cpp
index d6fdf581baaec..3dc36ce1a5204 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -327,6 +327,37 @@ bool Context::evaluateStrlen(State &Parent, const Expr *E, 
uint64_t &Result) {
   return true;
 }
 
+bool Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned 
Kind,
+                                    uint64_t &Result) {
+  assert(Stk.empty());
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+
+  auto PtrRes = C.interpretAsPointer(E, [&](const Pointer &Ptr) {
+    const Descriptor *DeclDesc = Ptr.getDeclDesc();
+    if (!DeclDesc)
+      return false;
+
+    QualType T = DeclDesc->getType().getNonReferenceType();
+    if (T->isIncompleteType() || T->isFunctionType() ||
+        !T->isConstantSizeType())
+      return false;
+
+    Pointer P = Ptr;
+    if (auto ObjectSize = evaluateBuiltinObjectSize(getASTContext(), Kind, P)) 
{
+      Result = *ObjectSize;
+      return true;
+    }
+    return false;
+  });
+
+  if (PtrRes.isInvalid()) {
+    C.cleanup();
+    Stk.clear();
+    return false;
+  }
+  return true;
+}
+
 const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }
 
 static PrimType integralTypeToPrimTypeS(unsigned BitWidth) {

diff  --git a/clang/lib/AST/ByteCode/Context.h 
b/clang/lib/AST/ByteCode/Context.h
index a21bb3ed8fbe7..313c040f84743 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -75,6 +75,19 @@ class Context final {
   /// run strlen() on it.
   bool evaluateStrlen(State &Parent, const Expr *E, uint64_t &Result);
 
+  /// If \param E evaluates to a pointer the number of accessible bytes
+  /// past the pointer is estimated in \param Result as if evaluated by
+  /// the builtin function __builtin_object_size. This is a best effort
+  /// approximation, when Kind & 2 == 0 the object size is less
+  /// than or equal to the estimated size, when Kind & 2 == 1 the
+  /// true value is greater than or equal to the estimated size.
+  /// When Kind & 1 == 1 only bytes belonging to the same subobject
+  /// as the one referred to by E are considered, when Kind & 1 == 0
+  /// bytes belonging to the same storage (stack, heap allocation,
+  /// global variable) are considered.
+  bool tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind,
+                             uint64_t &Result);
+
   /// Returns the AST context.
   ASTContext &getASTContext() const { return Ctx; }
   /// Returns the language options.

diff  --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp 
b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 7d44c32d73555..7c120b9ecc17c 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -193,12 +193,6 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo 
Info) {
     return true;
 
   const Pointer &Ptr = S.Stk.pop<Pointer>();
-
-  if (Ptr.isFunctionPointer()) {
-    EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext()));
-    return true;
-  }
-
   // If we're returning a raw pointer, call our callback.
   if (this->PtrCB)
     return (*this->PtrCB)(Ptr);
@@ -208,6 +202,12 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo 
Info) {
   if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
     return false;
 
+  // Function pointers are alway returned as lvalues.
+  if (Ptr.isFunctionPointer()) {
+    EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext()));
+    return true;
+  }
+
   // Implicitly convert lvalue to rvalue, if requested.
   if (ConvertResultToRValue) {
     if (!Ptr.isZero() && !Ptr.isDereferencable())

diff  --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 42ed44ff3c3ea..2921618b0c630 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2304,54 +2304,41 @@ static bool isUserWritingOffTheEnd(const ASTContext 
&Ctx, const Pointer &Ptr) {
          isFlexibleArrayMember(FieldDesc);
 }
 
-static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
-                                        const InterpFrame *Frame,
-                                        const CallExpr *Call) {
-  const ASTContext &ASTCtx = S.getASTContext();
-  // From the GCC docs:
-  // Kind is an integer constant from 0 to 3. If the least significant bit is
-  // clear, objects are whole variables. If it is set, a closest surrounding
-  // subobject is considered the object a pointer points to. The second bit
-  // determines if maximum or minimum of remaining bytes is computed.
-  unsigned Kind = popToUInt64(S, Call->getArg(1));
-  assert(Kind <= 3 && "unexpected kind");
-  bool UseFieldDesc = (Kind & 1u);
-  bool ReportMinimum = (Kind & 2u);
-  Pointer Ptr = S.Stk.pop<Pointer>();
-
-  if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
-    // "If there are any side effects in them, it returns (size_t) -1
-    // for type 0 or 1 and (size_t) 0 for type 2 or 3."
-    pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
-    return true;
-  }
-
+UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
+                                         unsigned Kind, Pointer &Ptr) {
   if (Ptr.isZero() || !Ptr.isBlockPointer())
-    return false;
+    return std::nullopt;
 
-  // We can't load through pointers.
   if (Ptr.isDummy() && Ptr.getType()->isPointerType())
-    return false;
+    return std::nullopt;
+
+  // According to the GCC documentation, we want the size of the subobject
+  // denoted by the pointer. But that's not quite right -- what we actually
+  // want is the size of the immediately-enclosing array, if there is one.
+  if (Ptr.isArrayElement())
+    Ptr = Ptr.expand();
 
   bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
   const Descriptor *DeclDesc = Ptr.getDeclDesc();
   assert(DeclDesc);
 
+  bool UseFieldDesc = (Kind & 1u);
+  bool ReportMinimum = (Kind & 2u);
   if (!UseFieldDesc || DetermineForCompleteObject) {
     // Lower bound, so we can't fall back to this.
     if (ReportMinimum && !DetermineForCompleteObject)
-      return false;
+      return std::nullopt;
 
     // Can't read beyond the pointer decl desc.
     if (!UseFieldDesc && !ReportMinimum && 
DeclDesc->getType()->isPointerType())
-      return false;
+      return std::nullopt;
   } else {
-    if (isUserWritingOffTheEnd(ASTCtx, Ptr.expand())) {
+    if (isUserWritingOffTheEnd(ASTCtx, Ptr)) {
       // 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.
       if (Kind == 1)
-        return false;
+        return std::nullopt;
     }
   }
 
@@ -2365,7 +2352,7 @@ static bool interp__builtin_object_size(InterpState &S, 
CodePtr OpPC,
 
   std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
   if (!FullSize)
-    return false;
+    return std::nullopt;
 
   unsigned ByteOffset;
   if (UseFieldDesc) {
@@ -2386,10 +2373,34 @@ static bool interp__builtin_object_size(InterpState &S, 
CodePtr OpPC,
     ByteOffset = computePointerOffset(ASTCtx, Ptr);
 
   assert(ByteOffset <= *FullSize);
-  unsigned Result = *FullSize - ByteOffset;
+  return *FullSize - ByteOffset;
+}
 
-  pushInteger(S, Result, Call->getType());
-  return true;
+static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
+                                        const InterpFrame *Frame,
+                                        const CallExpr *Call) {
+  const ASTContext &ASTCtx = S.getASTContext();
+  // From the GCC docs:
+  // Kind is an integer constant from 0 to 3. If the least significant bit is
+  // clear, objects are whole variables. If it is set, a closest surrounding
+  // subobject is considered the object a pointer points to. The second bit
+  // determines if maximum or minimum of remaining bytes is computed.
+  unsigned Kind = popToUInt64(S, Call->getArg(1));
+  assert(Kind <= 3 && "unexpected kind");
+  Pointer Ptr = S.Stk.pop<Pointer>();
+
+  if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
+    // "If there are any side effects in them, it returns (size_t) -1
+    // for type 0 or 1 and (size_t) 0 for type 2 or 3."
+    pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
+    return true;
+  }
+
+  if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) {
+    pushInteger(S, *Result, Call->getType());
+    return true;
+  }
+  return false;
 }
 
 static bool interp__builtin_is_within_lifetime(InterpState &S, CodePtr OpPC,

diff  --git a/clang/lib/AST/ByteCode/InterpHelpers.h 
b/clang/lib/AST/ByteCode/InterpHelpers.h
index 6bf89d318378c..905bf1b43bfab 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -66,6 +66,9 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
 /// Copy the contents of Src into Dest.
 bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest);
 
+UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
+                                         unsigned Kind, Pointer &Ptr);
+
 template <typename T>
 static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
   const Expr *E = S.Current->getExpr(OpPC);

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 48054c2fda2ad..8f4907e6db01e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -21666,6 +21666,10 @@ bool Expr::tryEvaluateObjectSize(uint64_t &Result, 
ASTContext &Ctx,
 
   Expr::EvalStatus Status;
   EvalInfo Info(Ctx, Status, EvaluationMode::ConstantFold);
+  if (Info.EnableNewConstInterp) {
+    return Info.Ctx.getInterpContext().tryEvaluateObjectSize(Info, this, Type,
+                                                             Result);
+  }
   return tryEvaluateBuiltinObjectSize(this, Type, Info, Result);
 }
 

diff  --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.c 
b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
new file mode 100644
index 0000000000000..c290385935ba4
--- /dev/null
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -triple 
x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1                                         -triple 
x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+
+
+#define PS(N) __attribute__((pass_object_size(N)))
+  int ObjectSize0(void *const p PS(0)) {
+    return __builtin_object_size(p, 0);
+  }
+
+  int ObjectSize1(void *const p PS(1)) {
+    return __builtin_object_size(p, 1);
+  }
+
+  int ObjectSize2(void *const p PS(2)) {
+    return __builtin_object_size(p, 2);
+  }
+
+  int ObjectSize3(void *const p PS(3)) {
+    return __builtin_object_size(p, 3);
+  }
+
+  struct Foo {
+    int t[10];
+  };
+
+
+  int gi;
+  void test1(unsigned long sz) {
+    struct Foo t[10];
+
+    // CHECK: call i32 @ObjectSize0(ptr noundef %{{.*}}, i64 noundef 360)
+    gi = ObjectSize0(&t[1]);
+    // call i32 @ObjectSize1(ptr noundef %{{.*}}, i64 noundef 360)
+    // gi = ObjectSize2(&t[1]);
+    // gi = ObjectSize2(&t[1].t[1]);
+  }
+

diff  --git a/clang/test/AST/ByteCode/object-size-flex-array.c 
b/clang/test/AST/ByteCode/object-size-flex-array.c
new file mode 100644
index 0000000000000..4f2a476209985
--- /dev/null
+++ b/clang/test/AST/ByteCode/object-size-flex-array.c
@@ -0,0 +1,156 @@
+// RUN: %clang -fexperimental-new-constant-interpreter                        
-target x86_64 -O2 -S -emit-llvm %s -o - 2>&1 | FileCheck 
--check-prefixes=CHECK,CHECK-NO-STRICT %s
+// RUN: %clang -fexperimental-new-constant-interpreter -fstrict-flex-arrays=0 
-target x86_64 -O2 -S -emit-llvm %s -o - 2>&1 | FileCheck 
--check-prefixes=CHECK,CHECK-STRICT-0 %s
+// RUN: %clang -fexperimental-new-constant-interpreter -fstrict-flex-arrays=1 
-target x86_64 -O2 -S -emit-llvm %s -o - 2>&1 | FileCheck 
--check-prefixes=CHECK,CHECK-STRICT-1 %s
+// RUN: %clang -fexperimental-new-constant-interpreter -fstrict-flex-arrays=2 
-target x86_64 -O2 -S -emit-llvm %s -o - 2>&1 | FileCheck 
--check-prefixes=CHECK,CHECK-STRICT-2 %s
+// RUN: %clang -fexperimental-new-constant-interpreter -fstrict-flex-arrays=3 
-target x86_64 -O2 -S -emit-llvm %s -o - 2>&1 | FileCheck 
--check-prefixes=CHECK,CHECK-STRICT-3 %s
+
+#define OBJECT_SIZE_BUILTIN __builtin_object_size
+
+typedef struct {
+  float f;
+  double c[];
+} foo_t;
+
+typedef struct {
+  float f;
+  double c[0];
+} foo0_t;
+
+typedef struct {
+  float f;
+  double c[1];
+} foo1_t;
+
+typedef struct {
+  float f;
+  double c[2];
+} foo2_t;
+
+// CHECK-LABEL: @bar(
+unsigned bar(foo_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 -1
+  // CHECK-STRICT-3: ret i32 -1
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @bar0(
+unsigned bar0(foo0_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 -1
+  // CHECK-STRICT-3: ret i32 0
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @bar1(
+unsigned bar1(foo1_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 8
+  // CHECK-STRICT-3: ret i32 8
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @bar2(
+unsigned bar2(foo2_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 16
+  // CHECK-STRICT-2: ret i32 16
+  // CHECK-STRICT-3: ret i32 16
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+#define DYNAMIC_OBJECT_SIZE_BUILTIN __builtin_dynamic_object_size
+
+// CHECK-LABEL: @dyn_bar(
+unsigned dyn_bar(foo_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 -1
+  // CHECK-STRICT-3: ret i32 -1
+  return DYNAMIC_OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @dyn_bar0(
+unsigned dyn_bar0(foo0_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 -1
+  // CHECK-STRICT-3: ret i32 0
+  return DYNAMIC_OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @dyn_bar1(
+unsigned dyn_bar1(foo1_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 -1
+  // CHECK-STRICT-2: ret i32 8
+  // CHECK-STRICT-3: ret i32 8
+  return DYNAMIC_OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @dyn_bar2(
+unsigned dyn_bar2(foo2_t *f) {
+  // CHECK-NO-STRICT: ret i32 -1
+  // CHECK-STRICT-0: ret i32 -1
+  // CHECK-STRICT-1: ret i32 16
+  // CHECK-STRICT-2: ret i32 16
+  // CHECK-STRICT-3: ret i32 16
+  return DYNAMIC_OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// Also checks for non-trailing flex-array like members
+
+typedef struct {
+  double c[0];
+  float f;
+} foofoo0_t;
+
+typedef struct {
+  double c[1];
+  float f;
+} foofoo1_t;
+
+typedef struct {
+  double c[2];
+  float f;
+} foofoo2_t;
+
+// CHECK-LABEL: @babar0(
+unsigned babar0(foofoo0_t *f) {
+  // CHECK-NO-STRICT: ret i32 0
+  // CHECK-STRICT-0: ret i32 0
+  // CHECK-STRICT-1: ret i32 0
+  // CHECK-STRICT-2: ret i32 0
+  // CHECK-STRICT-3: ret i32 0
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @babar1(
+unsigned babar1(foofoo1_t *f) {
+  // CHECK-NO-STRICT: ret i32 8
+  // CHECK-STRICT-0: ret i32 8
+  // CHECK-STRICT-1: ret i32 8
+  // CHECK-STRICT-2: ret i32 8
+  // CHECK-STRICT-3: ret i32 8
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}
+
+// CHECK-LABEL: @babar2(
+unsigned babar2(foofoo2_t *f) {
+  // CHECK-NO-STRICT: ret i32 16
+  // CHECK-STRICT-0: ret i32 16
+  // CHECK-STRICT-1: ret i32 16
+  // CHECK-STRICT-2: ret i32 16
+  // CHECK-STRICT-3: ret i32 16
+  return OBJECT_SIZE_BUILTIN(f->c, 1);
+}

diff  --git a/clang/test/Sema/format-strings-nonnull.c 
b/clang/test/Sema/format-strings-nonnull.c
index b9eeb5954ffb6..1204bf1cde305 100644
--- a/clang/test/Sema/format-strings-nonnull.c
+++ b/clang/test/Sema/format-strings-nonnull.c
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only --std=c23 -verify -Wnonnull 
-Wno-format-security %s
+// RUN: %clang_cc1 -fsyntax-only --std=c23 -verify -Wnonnull 
-Wno-format-security -fexperimental-new-constant-interpreter %s
 
 #define NULL  (void*)0
 


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to