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
