[clang] [clang][bytecode] Use in Expr::tryEvaluateObjectSize() (PR #179197)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 2 03:52:43 PST 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/179197 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/179197
>From ba4b020c928ca735af31c93e7e3b45c54568f703 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Mon, 2 Feb 2026 11:20:56 +0100
Subject: [PATCH 1/2] [clang][bytecode] Use in Expr::tryEvaluateObjectSize()
---
clang/lib/AST/ByteCode/Context.cpp | 31 ++++
clang/lib/AST/ByteCode/Context.h | 13 ++
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 77 +++++----
clang/lib/AST/ByteCode/InterpHelpers.h | 3 +
clang/lib/AST/ExprConstant.cpp | 4 +
.../ByteCode/builtin-object-size-codegen.c | 37 +++++
.../AST/ByteCode/object-size-flex-array.c | 156 ++++++++++++++++++
clang/test/Sema/format-strings-nonnull.c | 1 +
8 files changed, 289 insertions(+), 33 deletions(-)
create mode 100644 clang/test/AST/ByteCode/builtin-object-size-codegen.c
create mode 100644 clang/test/AST/ByteCode/object-size-flex-array.c
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/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 73768f7dd612b..8a994f5c3dbd2 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -21781,6 +21781,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
>From 20269771216e3a3b8639436b95e55857fab437e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Mon, 2 Feb 2026 12:29:12 +0100
Subject: [PATCH 2/2] Remove function pointer special case
---
clang/lib/AST/ByteCode/EvalEmitter.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
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())
More information about the cfe-commits
mailing list