Timm =?utf-8?q?Bäder?= <[email protected]> Message-ID: In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/213017 >From 874fa8d064b2e8a38330238d1ee97da5a3c16c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Thu, 30 Jul 2026 14:52:59 +0200 Subject: [PATCH 1/2] test changes --- clang/test/CodeGen/object-size.c | 92 +-------------------------- clang/test/CodeGen/pass-object-size.c | 5 +- 2 files changed, 5 insertions(+), 92 deletions(-) diff --git a/clang/test/CodeGen/object-size.c b/clang/test/CodeGen/object-size.c index 00ab18cb22e84..ad8969f3644d3 100644 --- a/clang/test/CodeGen/object-size.c +++ b/clang/test/CodeGen/object-size.c @@ -1,5 +1,8 @@ // RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s // RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s + #ifndef DYNAMIC #define OBJECT_SIZE_BUILTIN __builtin_object_size @@ -59,93 +62,6 @@ void test6(void) { strcpy(&buf[4], "Hi there"); } -// CHECK-LABEL: define{{.*}} void @test7 -void test7(void) { - int i; - // Ensure we only evaluate the side-effect once. - // CHECK: = add - // CHECK-NOT: = add - // CHECK: = call ptr @__strcpy_chk(ptr @gbuf, ptr @.str, i64 63) - strcpy((++i, gbuf), "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test8 -void test8(void) { - char *buf[50]; - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(buf[++gi], "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test9 -void test9(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy((char *)((++gi) + gj), "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test10 -char **p; -void test10(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(*(++p), "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test11 -void test11(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr @gbuf, ptr @.str) - strcpy(gp = gbuf, "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test12 -void test12(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(++gp, "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test13 -void test13(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(gp++, "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test14 -void test14(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(--gp, "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test15 -void test15(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{..*}}, ptr @.str) - strcpy(gp--, "Hi there"); -} - -// CHECK-LABEL: define{{.*}} void @test16 -void test16(void) { - // CHECK-NOT: __strcpy_chk - // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str) - strcpy(gp += 1, "Hi there"); -} - -// CHECK-LABEL: @test17 -void test17(void) { - // CHECK: store i32 -1 - gi = OBJECT_SIZE_BUILTIN(gp++, 0); - // CHECK: store i32 -1 - gi = OBJECT_SIZE_BUILTIN(gp++, 1); - // CHECK: store i32 0 - gi = OBJECT_SIZE_BUILTIN(gp++, 2); - // CHECK: store i32 0 - gi = OBJECT_SIZE_BUILTIN(gp++, 3); -} - // CHECK-LABEL: @test18 unsigned test18(int cond) { int a[4], b[4]; @@ -337,8 +253,6 @@ void test26(void) { // CHECK: store i32 316 gi = OBJECT_SIZE_BUILTIN(&t[1].v[11], 0); - // CHECK: store i32 312 - gi = OBJECT_SIZE_BUILTIN(&t[1].v[12], 1); // CHECK: store i32 308 gi = OBJECT_SIZE_BUILTIN(&t[1].v[13], 2); // CHECK: store i32 0 diff --git a/clang/test/CodeGen/pass-object-size.c b/clang/test/CodeGen/pass-object-size.c index c7c505b0fb3e7..64a544642bfe0 100644 --- a/clang/test/CodeGen/pass-object-size.c +++ b/clang/test/CodeGen/pass-object-size.c @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s typedef unsigned long size_t; @@ -217,8 +218,6 @@ void test3(void) { void test4(struct Foo *t) { // CHECK: call i32 @_Z27NoViableOverloadObjectSize0PvU17pass_object_size0(ptr noundef %{{.*}}, i64 noundef %{{.*}}) gi = NoViableOverloadObjectSize0(&t[1]); - // CHECK: call i32 @_Z27NoViableOverloadObjectSize1PvU17pass_object_size1(ptr noundef %{{.*}}, i64 noundef %{{.*}}) - gi = NoViableOverloadObjectSize1(&t[1]); // CHECK: call i32 @_Z27NoViableOverloadObjectSize2PvU17pass_object_size2(ptr noundef %{{.*}}, i64 noundef %{{.*}}) gi = NoViableOverloadObjectSize2(&t[1]); // CHECK: call i32 @_Z27NoViableOverloadObjectSize3PvU17pass_object_size3(ptr noundef %{{.*}}, i64 noundef 0) >From d388237ef9e0399a8f01a070ddd45067aa8d9c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Thu, 30 Jul 2026 14:53:12 +0200 Subject: [PATCH 2/2] opaque pointers --- clang/lib/AST/ByteCode/Compiler.cpp | 44 +- clang/lib/AST/ByteCode/Context.cpp | 16 +- clang/lib/AST/ByteCode/Context.h | 2 +- clang/lib/AST/ByteCode/Interp.cpp | 131 +++++ clang/lib/AST/ByteCode/Interp.h | 123 +++-- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 234 +------- .../AST/ByteCode/InterpBuiltinObjectSize.cpp | 510 ++++++++++++++++++ clang/lib/AST/ByteCode/InterpHelpers.h | 3 +- clang/lib/AST/ByteCode/InterpState.h | 4 + clang/lib/AST/ByteCode/Opcodes.td | 6 + clang/lib/AST/ByteCode/Pointer.cpp | 54 ++ clang/lib/AST/ByteCode/Pointer.h | 94 +++- clang/lib/AST/ByteCode/Program.cpp | 6 +- clang/lib/AST/CMakeLists.txt | 1 + clang/lib/AST/ExprConstant.cpp | 5 +- .../builtin-object-size-codegen-cxx23.cpp | 15 + .../ByteCode/builtin-object-size-codegen.c | 64 +++ .../ByteCode/builtin-object-size-codegen.cpp | 188 +++++++ clang/test/AST/ByteCode/enable_if.c | 202 ------- .../CodeGen/attr-counted-by-with-sanitizers.c | 6 +- .../attr-counted-by-without-sanitizers.c | 8 +- clang/test/Sema/enable_if.c | 3 + clang/test/SemaCXX/new-delete.cpp | 16 +- 23 files changed, 1258 insertions(+), 477 deletions(-) create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp create mode 100644 clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp delete mode 100644 clang/test/AST/ByteCode/enable_if.c diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e72e7875f6937..4a74c886998d6 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -453,8 +453,12 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) { switch (E->getCastKind()) { case CK_LValueToRValue: { - if (ToLValue && E->getType()->isPointerType()) - return this->delegate(SubExpr); + if (ToLValue && E->getType()->isPointerType()) { + assert(!DiscardResult); + if (!this->visit(SubExpr)) + return false; + return this->emitLoadPopL(E); + } if (SubExpr->getType().isVolatileQualified()) return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, E); @@ -6037,6 +6041,33 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val, return false; } +/// A more selective version of E->IgnoreParenCasts for +/// tryEvaluateBuiltinObjectSize. This ignores some casts/parens that serve only +/// to change the type of E. +/// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo` +/// +/// Always returns an RValue with a pointer representation. +static const Expr *ignorePointerCastsAndParens(const Expr *E) { + assert(E->isPRValue() && E->getType()->hasPointerRepresentation()); + + const Expr *NoParens = E->IgnoreParens(); + const auto *Cast = dyn_cast<CastExpr>(NoParens); + if (Cast == nullptr) + return NoParens; + + // We only conservatively allow a few kinds of casts, because this code is + // inherently a simple solution that seeks to support the common case. + auto CastKind = Cast->getCastKind(); + if (CastKind != CK_NoOp && CastKind != CK_BitCast && + CastKind != CK_AddressSpaceConversion) + return NoParens; + + const auto *SubExpr = Cast->getSubExpr(); + if (!SubExpr->getType()->hasPointerRepresentation() || !SubExpr->isPRValue()) + return NoParens; + return ignorePointerCastsAndParens(SubExpr); +} + template <class Emitter> bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E, unsigned BuiltinID) { @@ -6095,7 +6126,7 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E, return false; } else { - if (!this->visitAsLValue(Arg0)) + if (!this->visitAsLValue(ignorePointerCastsAndParens(Arg0))) return false; } if (!this->visit(E->getArg(1))) @@ -8706,8 +8737,13 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc, template <class Emitter> bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E, bool CU) { assert(!DiscardResult && "Should've been checked before"); - unsigned DummyID = P.getOrCreateDummy(D, CU); + if (ToLValue) { + if (auto *VD = dyn_cast_if_present<ValueDecl>(D.dyn_cast<const Decl *>())) + return this->emitGetOpaquePtr(VD, E); + } + + unsigned DummyID = P.getOrCreateDummy(D, CU); if (!this->emitGetPtrGlobal(DummyID, E)) return false; if (E->getType()->isVoidType()) diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index b913d2a9f539c..6e3b93e2107b5 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -366,26 +366,24 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) { return Result; } -std::optional<uint64_t> -Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) { +std::optional<uint64_t> Context::tryEvaluateObjectSize(State &Parent, + const Expr *E, + unsigned Kind, + bool IsDynamic) { assert(Stk.empty()); Compiler<EvalEmitter> C(*this, *P, Parent, Stk); std::optional<uint64_t> Result; - auto PtrRes = C.interpretAsLValuePointer(E, [&](InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - const Descriptor *DeclDesc = Ptr.getDeclDesc(); - if (!DeclDesc) - return false; - - QualType T = DeclDesc->getType().getNonReferenceType(); + QualType T = Ptr.getType().getNonReferenceType(); if (T->isIncompleteType() || T->isFunctionType() || !T->isConstantSizeType()) return false; Pointer P = Ptr; - if (auto ObjectSize = evaluateBuiltinObjectSize(getASTContext(), Kind, P)) { + if (auto ObjectSize = + evaluateBuiltinObjectSize(getASTContext(), Kind, P, E, IsDynamic)) { Result = *ObjectSize; return true; } diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h index 47821a3e3a7f3..77566d35ad6e0 100644 --- a/clang/lib/AST/ByteCode/Context.h +++ b/clang/lib/AST/ByteCode/Context.h @@ -95,7 +95,7 @@ class Context final { /// bytes belonging to the same storage (stack, heap allocation, /// global variable) are considered. std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E, - unsigned Kind); + unsigned Kind, bool IsDynamic); std::optional<bool> evaluateWithSubstitution(State &Parent, const FunctionDecl *Callee, diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 475d206356de8..8e5d6ec28e246 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1612,6 +1612,31 @@ static bool getField(InterpState &S, CodePtr OpPC, const Pointer &Ptr, return false; } + if (Ptr.isOpaquePointer()) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl(); + if (!RD) + return false; + const Record *R = S.getContext().getRecord(RD); + if (!R) + return false; + + const Record::Field *F = R->findField(Off); + if (!F) + return false; + + PointerPathEntry *NewPath = S.allocPointerPath(OP.PathLength + 1); + if (OP.Path) + std::memcpy(NewPath, Ptr.asOpaquePointer().Path, + sizeof(PointerPathEntry) * OP.PathLength); + + NewPath[OP.PathLength] = PointerPathEntry::field(F->Decl); + + S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, F->Decl->getType().getTypePtr(), + NewPath, OP.PathLength + 1); + return true; + } + if (!Ptr.isBlockPointer()) { // If we're trying to get the field of a TypeId pointer, try to produce a // proper diagnostic. @@ -1646,6 +1671,33 @@ static bool getBase(InterpState &S, CodePtr OpPC, const Pointer &Ptr, if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Base)) return false; + if (Ptr.isOpaquePointer()) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl(); + if (!RD) + return false; + const Record *R = S.getContext().getRecord(RD); + assert(R); + + const Record::Base *B = R->findBase(Off); + if (!B) + return false; + + unsigned NewPathLength = OP.PathLength + 1; + PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength); + if (OP.Path) + std::memcpy(NewPath, OP.Path, + sizeof(PointerPathEntry) * (NewPathLength - 1)); + + NewPath[NewPathLength - 1] = + PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl)); + S.Stk.push<Pointer>( + OpaqueTag{}, OP.Base, + S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr(), NewPath, + NewPathLength); + return true; + } + if (!Ptr.isBlockPointer()) { if (!Ptr.isIntegralPointer()) return false; @@ -1906,6 +1958,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, S.Current = FrameBefore; return false; } + bool Call(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize) { @@ -3221,6 +3274,84 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth, return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI); } +bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const OpaquePointer &OP, + int64_t Offset) { + QualType ArrTy; + if (OP.PathLength > 0) { + if (OP.path().back().Kind == PointerPathEntry::Array) { + ArrTy = OP.getSurroundingArray(S.getASTContext()); + } else { + ArrTy = OP.getFieldType(); + } + } else { + ArrTy = OP.getObjectType(); + } + + QualType ElemType; + bool PastEnd = true; + if (ArrTy->isArrayType()) { + const auto *AT = ArrTy->getAsArrayTypeUnsafe(); + ElemType = AT->getElementType(); + if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) + PastEnd = Offset >= CAT->getSExtSize(); + } else if (ArrTy->isRecordType()) { + ElemType = ArrTy; + } else { + ElemType = ArrTy; + } + if (ElemType.isNull()) + return false; + + unsigned NewPathLength = OP.PathLength + 1; + PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength); + if (OP.Path) + std::memcpy(NewPath, OP.Path, + sizeof(PointerPathEntry) * (NewPathLength - 1)); + + NewPath[NewPathLength - 1] = PointerPathEntry::array(Offset); + S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, ElemType.getTypePtr(), NewPath, + NewPathLength, PastEnd); + return true; +} + +bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + uint64_t Offset, bool Add) { + assert(Ptr.isOpaquePointer()); + const OpaquePointer &OP = Ptr.asOpaquePointer(); + + QualType ArrTy = OP.getFieldType(); + QualType ElemTy; + if (ArrTy->isArrayType()) + ElemTy = ArrTy->getAsArrayTypeUnsafe()->getElementType(); + else + ElemTy = ArrTy; + + if (Offset != 0) { + if (isa<IncompleteArrayType>(ArrTy)) { + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_unsized_array_indexed); + return false; + } + + S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << Offset << /*non-array*/ true << 0; + } + + unsigned ElemSize = + S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity(); + unsigned NewOffset; + if (Add) + NewOffset = Ptr.getByteOffset() + (ElemSize * Offset); + else + NewOffset = Ptr.getByteOffset() - (ElemSize * Offset); + + bool PastEnd = NewOffset > 0; + + S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, OP.FieldType.getPointer(), OP.Path, + OP.PathLength, PastEnd, NewOffset); + return true; +} + // FIXME: Would be nice to generate this instead of hardcoding it here. [[maybe_unused]] static constexpr bool OpReturns(Opcode Op) { return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet || diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 6c187f218a540..384664f22ed19 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -2252,6 +2252,45 @@ bool LoadPop(InterpState &S, CodePtr OpPC) { return true; } +/// Like LoadPop above, but if any of the checks fail, we +/// turn the pointer into an opaque pointer of appropriate type. +inline bool LoadPopL(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + auto P = S.getEvalStatus().Diag; + S.getEvalStatus().Diag = nullptr; + + bool Failed = false; + if (!CheckLoad(S, OpPC, Ptr)) + Failed = true; + if (!Ptr.isBlockPointer()) + Failed = true; + if (!Ptr.canDeref(PT_Ptr)) + Failed = true; + S.getEvalStatus().Diag = P; + + if (Failed) { + if (Ptr.isOpaquePointer()) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + + if (!Ptr.asOpaquePointer().Base->getType()->isPointerType()) + return false; + + QualType T = Ptr.getType(); + S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, T.getTypePtr(), OP.Path, + OP.PathLength, OP.isOnePastEnd(), + Ptr.getByteOffset()); + + } else { + S.Stk.push<Pointer>(OpaqueTag{}, Ptr.getDeclDesc()->asValueDecl(), + Ptr.getType()->getPointeeType().getTypePtr()); + } + + } else { + S.Stk.push<Pointer>(Ptr.deref<Pointer>()); + } + return true; +} + template <PrimType Name, class T = typename PrimConv<Name>::T> bool Store(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop<T>(); @@ -2646,11 +2685,17 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC, return Ptr.atIndex(static_cast<uint64_t>(Result)); } +bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + uint64_t Offset, bool Add); template <PrimType Name, class T = typename PrimConv<Name>::T> bool AddOffset(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.pop<Pointer>().expand(); + if (Ptr.isOpaquePointer()) + return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset), + /*Add=*/true); + if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push<Pointer>(Result->narrow()); @@ -2664,6 +2709,10 @@ bool SubOffset(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.pop<Pointer>().expand(); + if (Ptr.isOpaquePointer()) + return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset), + /*Add=*/false); + if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push<Pointer>(Result->narrow()); @@ -2672,6 +2721,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) { return false; } +inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD) { + S.Stk.push<Pointer>(OpaqueTag{}, VD); + + return true; +} + template <ArithOp Op> static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { @@ -3432,18 +3487,16 @@ inline bool ExpandPtr(InterpState &S) { return true; } -// 1) Pops an integral value from the stack -// 2) Peeks a pointer -// 3) Pushes a new pointer that's a narrowed array -// element of the peeked pointer with the value -// from 1) added as offset. -// -// This leaves the original pointer on the stack and pushes a new one -// with the offset applied and narrowed. -template <PrimType Name, class T = typename PrimConv<Name>::T> -inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) { - const T &Offset = S.Stk.pop<T>(); - const Pointer &Ptr = S.Stk.peek<Pointer>(); +bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const OpaquePointer &OP, + int64_t Offset); + +// Implementation for ArrayElemPtr and ArrayElemPtrPop ops. +template <typename T> +inline bool arrayElemPtr(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + const T &Offset) { + if (Ptr.isOpaquePointer()) + return arrayElemPtrOpaque(S, OpPC, Ptr.asOpaquePointer(), + static_cast<int64_t>(Offset)); if (!Ptr.isZero() && !Offset.isZero()) { if (!CheckArray(S, OpPC, Ptr)) @@ -3467,38 +3520,31 @@ inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) { S.Stk.push<Pointer>(Result->narrow()); return true; } - return false; } +// 1) Pops an integral value from the stack +// 2) Peeks a pointer +// 3) Pushes a new pointer that's a narrowed array +// element of the peeked pointer with the value +// from 1) added as offset. +// +// This leaves the original pointer on the stack and pushes a new one +// with the offset applied and narrowed. template <PrimType Name, class T = typename PrimConv<Name>::T> -inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { +inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop<T>(); - const Pointer &Ptr = S.Stk.pop<Pointer>(); - - if (!Ptr.isZero() && !Offset.isZero()) { - if (!CheckArray(S, OpPC, Ptr)) - return false; - } + const Pointer &Ptr = S.Stk.peek<Pointer>(); - if (Offset.isZero()) { - if (const Descriptor *Desc = Ptr.getFieldDesc(); - Desc && Desc->isArray() && Ptr.getIndex() == 0) { - S.Stk.push<Pointer>(Ptr.atIndex(0).narrow()); - return true; - } - S.Stk.push<Pointer>(Ptr.narrow()); - return true; - } + return arrayElemPtr<T>(S, OpPC, Ptr, Offset); +} - assert(!Offset.isZero()); +template <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { + const T &Offset = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (std::optional<Pointer> Result = - OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) { - S.Stk.push<Pointer>(Result->narrow()); - return true; - } - return false; + return arrayElemPtr<T>(S, OpPC, Ptr, Offset); } template <PrimType Name, class T = typename PrimConv<Name>::T> @@ -3570,6 +3616,11 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) { return false; } + if (Ptr.isOpaquePointer()) { + S.Stk.push<Pointer>(Ptr); + return true; + } + if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) { S.Stk.push<Pointer>(Ptr.atIndex(0).narrow()); return true; diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 0e81059a0f6cf..e618948a25229 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2297,218 +2297,9 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC, return true; } -static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx, - const Descriptor *Desc) { - if (Desc->isPrimitive() || Desc->isArray()) - return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity(); - - if (Desc->isRecord()) { - // Can't use Descriptor::getType() as that may return a pointer type. Look - // at the decl directly. - return ASTCtx - .getTypeSizeInChars( - ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl())) - .getQuantity(); - } - - return std::nullopt; -} - -/// Compute the byte offset of \p Ptr in the full declaration. -static unsigned computePointerOffset(const ASTContext &ASTCtx, - const Pointer &Ptr) { - unsigned Result = 0; - - Pointer P = Ptr; - while (P.isField() || P.isArrayElement()) { - P = P.expand(); - const Descriptor *D = P.getFieldDesc(); - - if (P.isArrayElement()) { - unsigned ElemSize = - ASTCtx.getTypeSizeInChars(D->getElemQualType()).getQuantity(); - if (P.isOnePastEnd()) - Result += ElemSize * P.getNumElems(); - else - Result += ElemSize * P.getIndex(); - P = P.expand().getArray(); - } else if (P.isBaseClass()) { - const auto *RD = cast<CXXRecordDecl>(D->asDecl()); - bool IsVirtual = Ptr.isVirtualBaseClass(); - P = P.getBase(); - const Record *BaseRecord = P.getRecord(); - - const ASTRecordLayout &Layout = - ASTCtx.getASTRecordLayout(cast<CXXRecordDecl>(BaseRecord->getDecl())); - if (IsVirtual) - Result += Layout.getVBaseClassOffset(RD).getQuantity(); - else - Result += Layout.getBaseClassOffset(RD).getQuantity(); - } else if (P.isField()) { - const FieldDecl *FD = P.getField(); - const ASTRecordLayout &Layout = - ASTCtx.getASTRecordLayout(FD->getParent()); - unsigned FieldIndex = FD->getFieldIndex(); - uint64_t FieldOffset = - ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex)) - .getQuantity(); - Result += FieldOffset; - P = P.getBase(); - } else - llvm_unreachable("Unhandled descriptor type"); - } - - return Result; -} - -/// Does Ptr point to the last subobject? -static bool pointsToLastObject(const Pointer &Ptr) { - Pointer P = Ptr; - while (!P.isRoot()) { - - if (P.isArrayElement()) { - P = P.expand().getArray(); - continue; - } - if (P.isBaseClass()) { - if (P.getRecord()->getNumFields() > 0) - return false; - P = P.getBase(); - continue; - } - - Pointer Base = P.getBase(); - if (const Record *R = Base.getRecord()) { - assert(P.getField()); - if (P.getField()->getFieldIndex() != R->getNumFields() - 1) - return false; - } - P = Base; - } - - return true; -} - -/// Does Ptr point to the last object AND to a flexible array member? -static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr, - bool InvalidBase) { - auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) { - using FAMKind = LangOptions::StrictFlexArraysLevelKind; - FAMKind StrictFlexArraysLevel = - Ctx.getLangOpts().getStrictFlexArraysLevel(); - - if (StrictFlexArraysLevel == FAMKind::Default) - return true; - - unsigned NumElems = FieldDesc->getNumElems(); - if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly) - return true; - - if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete) - return true; - return false; - }; - - const Descriptor *FieldDesc = Ptr.getFieldDesc(); - if (!FieldDesc->isArray()) - return false; - - return InvalidBase && pointsToLastObject(Ptr) && - isFlexibleArrayMember(FieldDesc); -} - -UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx, - unsigned Kind, Pointer &Ptr) { - if (Ptr.isZero() || !Ptr.isBlockPointer()) - return std::nullopt; - - if (Ptr.isDummy() && Ptr.getType()->isPointerType()) - return std::nullopt; - - bool InvalidBase = false; - - if (Ptr.isDummy()) { - if (const VarDecl *VD = Ptr.getDeclDesc()->asVarDecl(); - VD && VD->getType()->isPointerType()) - InvalidBase = true; - } - - // 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) { - // Can't read beyond the pointer decl desc. - if (!ReportMinimum && DeclDesc->getType()->isPointerType()) - return std::nullopt; - - if (InvalidBase) - return std::nullopt; - } else { - if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) { - // 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 std::nullopt; - } - // For Type=1, defer to the runtime path on a true incomplete-array - // flexible array member (e.g. 'char fam[]') even when the base is a - // concrete local/global. Without this, the bytecode interpreter would - // happily fold &af.fam to 'NumElems * elemSize = 0' below; the default - // const-evaluator avoids the same trap, and CGBuiltin emits - // @llvm.objectsize for the correct layout-derived answer (matching - // GCC's __bos/__bdos on '&af.fam'). - if (Kind == 1 && pointsToLastObject(Ptr) && Ptr.getFieldDesc()->isArray() && - Ptr.getFieldDesc()->getType()->isIncompleteArrayType()) - return std::nullopt; - } - - // The "closest surrounding subobject" is NOT a base class, - // so strip the base class casts. - if (UseFieldDesc && Ptr.isBaseClass()) - Ptr = Ptr.stripBaseCasts(); - - const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc; - assert(Desc); - - std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc); - if (!FullSize) - return std::nullopt; - - unsigned ByteOffset; - if (UseFieldDesc) { - if (Ptr.isBaseClass()) { - assert(computePointerOffset(ASTCtx, Ptr.getBase()) <= - computePointerOffset(ASTCtx, Ptr)); - ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) - - computePointerOffset(ASTCtx, Ptr); - } else { - if (Ptr.inArray()) - ByteOffset = - computePointerOffset(ASTCtx, Ptr) - - computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow()); - else - ByteOffset = 0; - } - } else - ByteOffset = computePointerOffset(ASTCtx, Ptr); - - assert(ByteOffset <= *FullSize); - return *FullSize - ByteOffset; -} - static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC, const InterpFrame *Frame, - const CallExpr *Call) { + const CallExpr *Call, bool IsDynamic) { const ASTContext &ASTCtx = S.getASTContext(); // From the GCC docs: // Kind is an integer constant from 0 to 3. If the least significant bit is @@ -2524,14 +2315,28 @@ static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC, 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()); + pushInteger(S, Kind <= 1 ? (size_t)-1 : (size_t)0, Call->getType()); return true; } - if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) { + if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr, + Call->getArg(0), IsDynamic)) { pushInteger(S, *Result, Call->getType()); return true; } + + switch (S.EvalMode) { + case EvaluationMode::ConstantExpression: + case EvaluationMode::ConstantFold: + case EvaluationMode::IgnoreSideEffects: + // Leave it to IR generation. + return Invalid(S, OpPC); + case EvaluationMode::ConstantExpressionUnevaluated: + // Reduce it to a constant now. + pushInteger(S, ((Kind & 2u) ? (size_t)0 : (size_t)-1), Call->getType()); + return true; + } + return false; } @@ -5448,8 +5253,11 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, return interp__builtin_memchr(S, OpPC, Call, BuiltinID); case Builtin::BI__builtin_object_size: + return interp__builtin_object_size(S, OpPC, Frame, Call, + /*IsDynamic=*/false); case Builtin::BI__builtin_dynamic_object_size: - return interp__builtin_object_size(S, OpPC, Frame, Call); + return interp__builtin_object_size(S, OpPC, Frame, Call, + /*IsDynamic=*/true); case Builtin::BI__builtin_is_within_lifetime: return interp__builtin_is_within_lifetime(S, OpPC, Call); diff --git a/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp new file mode 100644 index 0000000000000..aa0a18042d334 --- /dev/null +++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp @@ -0,0 +1,510 @@ +//===------------- InterpBuiltinObjectSize.cpp ------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Implementation of the frontend part of the __builtin_object_size and +// __builtin_dynamic_object_size builtins. + +#include "InterpHelpers.h" +#include "Pointer.h" +#include "Record.h" +#include "clang/AST/RecordLayout.h" + +using namespace clang; +using namespace clang::interp; + +enum : uint8_t { + Regular = 1 << 0, + IgnoreBaseCasts = 1 << 1, + SurroundingArray = 1 << 2, +}; + +// Helper to check if a RecordDecl can be passed to +// ASTContext::getRecordLayout(). +static bool validRecordDecl(const RecordDecl *D) { + D = D->getDefinition(); + return D && !D->isInvalidDecl() && D->isCompleteDefinition(); +} + +// Same but for types. +static bool validType(QualType T) { + if (const RecordDecl *RD = T->getAsRecordDecl()) + return validRecordDecl(RD); + return true; +} + +static QualType computeFieldType(const ASTContext &ASTCtx, + const OpaquePointer &OP, + unsigned TypeModifier = 0) { + QualType CurType = OP.getObjectType(); + + unsigned Drop = 0; + if (TypeModifier & IgnoreBaseCasts && OP.PathLength != 0 && + OP.path().back().Kind == PointerPathEntry::Base) + Drop = 1; + + if (TypeModifier & SurroundingArray && OP.PathLength != 0 && + OP.path().back().Kind == PointerPathEntry::Array) + Drop = 1; + + for (const PointerPathEntry &Entry : OP.path().drop_back(Drop)) { + switch (Entry.Kind) { + case PointerPathEntry::Base: + CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer()); + break; + case PointerPathEntry::Field: + CurType = Entry.FD->getType(); + break; + case PointerPathEntry::Array: + if (!CurType->isArrayType()) + continue; + CurType = CurType->getAsArrayTypeUnsafe()->getElementType(); + } + } + + return CurType; +} + +static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx, + const Descriptor *Desc) { + if (Desc->isPrimitive() || Desc->isArray()) { + QualType T = Desc->getType(); + if (!validType(T)) + return std::nullopt; + return ASTCtx.getTypeSizeInChars(T).getQuantity(); + } + + if (Desc->isRecord()) { + // Can't use Descriptor::getType() as that may return a pointer type. Look + // at the decl directly. + + const RecordDecl *RD = Desc->ElemRecord->getDecl(); + if (!validRecordDecl(RD)) + return std::nullopt; + + return ASTCtx.getTypeSizeInChars(ASTCtx.getCanonicalTagType(RD)) + .getQuantity(); + } + + return std::nullopt; +} + +/// Compute the byte offset of \p Ptr in the full declaration. +static unsigned computePointerOffset(const ASTContext &ASTCtx, + const Pointer &Ptr) { + if (auto p = Ptr.computeLayoutOffset(ASTCtx)) + return *p; + return 0; +} + +/// Does Ptr point to the last subobject? +static bool pointsToLastObject(const Pointer &Ptr) { + Pointer P = Ptr; + while (!P.isRoot()) { + + if (P.isArrayElement()) { + P = P.expand().getArray(); + continue; + } + if (P.isBaseClass()) { + if (P.getRecord()->getNumFields() > 0) + return false; + P = P.getBase(); + continue; + } + + Pointer Base = P.getBase(); + if (const Record *R = Base.getRecord()) { + assert(P.getField()); + if (P.getField()->getFieldIndex() != R->getNumFields() - 1) + return false; + } + P = Base; + } + + return true; +} + +/// Does Ptr point to the last object AND to a flexible array member? +static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr, + bool InvalidBase) { + auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) { + using FAMKind = LangOptions::StrictFlexArraysLevelKind; + FAMKind StrictFlexArraysLevel = + Ctx.getLangOpts().getStrictFlexArraysLevel(); + + if (StrictFlexArraysLevel == FAMKind::Default) + return true; + + unsigned NumElems = FieldDesc->getNumElems(); + if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly) + return true; + + if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete) + return true; + return false; + }; + + const Descriptor *FieldDesc = Ptr.getFieldDesc(); + if (!FieldDesc->isArray()) + return false; + + return InvalidBase && pointsToLastObject(Ptr) && + isFlexibleArrayMember(FieldDesc); +} + +static bool isUserWritingOffTheEnd(const ASTContext &ASTCtx, + const OpaquePointer &OP) { + if (OP.PathLength == 0) + return false; + + QualType CurType = OP.getObjectType(); + for (unsigned I = 0; I != OP.PathLength; ++I) { + const PointerPathEntry &Entry = OP.Path[I]; + switch (Entry.Kind) { + case PointerPathEntry::Base: + return false; + case PointerPathEntry::Field: { + const FieldDecl *FD = OP.Path[I].FD; + if (!FD->getParent()->isUnion() && + FD->getFieldIndex() != FD->getParent()->getNumFields() - 1) + return false; + CurType = FD->getType(); + } break; + case PointerPathEntry::Array: { + if (I == OP.PathLength - 1) + break; + + if (!CurType->isArrayType()) + break; + + unsigned Index = OP.Path[I].Index; + const ArrayType *AT = CurType->getAsArrayTypeUnsafe(); + assert(AT); + if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) { + if (Index != CAT->getLimitedSize() - 1) + return false; + CurType = CAT->getElementType(); + } else { + return false; + } + } + } + } + + // We're pointing to the last field in the full object. + // CurType is now the most derived type. + if (!CurType->isArrayType()) + return false; + + if (isa<IncompleteArrayType>(CurType)) + return true; + + const auto *CAT = dyn_cast<ConstantArrayType>(CurType); + if (!CAT) + return false; + + using FAMKind = LangOptions::StrictFlexArraysLevelKind; + FAMKind StrictFlexArraysLevel = + ASTCtx.getLangOpts().getStrictFlexArraysLevel(); + + if (StrictFlexArraysLevel == FAMKind::Default) + return true; + + unsigned Size = CAT->getZExtSize(); + if (Size == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly) + return true; + + if (Size == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete) + return true; + return false; +} + +/// Determine the offset of the given pointer. Depending on \c +/// UseClosestSurroundingVariable, the offset is either relative to the full +/// object or to the closest surrounding field or array. +static std::optional<uint64_t> +computeOpaquePtrOffset(const ASTContext &ASTCtx, const Pointer &Ptr, + bool UseClosestSurroundingVariable, + bool &OffsetIsNegative) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + + uint64_t Offset = 0; + std::optional<uint64_t> SurroundingArrayOffset; + QualType CurType = OP.getObjectType(); + for (const PointerPathEntry &Entry : OP.path()) { + switch (Entry.Kind) { + case PointerPathEntry::Base: { + const RecordDecl *RD = CurType->getAsRecordDecl(); + if (!validRecordDecl(RD)) + return std::nullopt; + + const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD); + Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity(); + + CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer()); + } break; + + case PointerPathEntry::Field: { + const FieldDecl *FD = Entry.FD; + const RecordDecl *RD = FD->getParent(); + if (!validRecordDecl(RD)) + return std::nullopt; + + const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD); + Offset += + ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex())) + .getQuantity(); + + CurType = FD->getType(); + } break; + case PointerPathEntry::Array: { + int64_t Index = Entry.Index; + if (Index < 0) + OffsetIsNegative = true; + SurroundingArrayOffset = Offset; + if (!CurType->isArrayType()) { + Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity(); + continue; + } + const ArrayType *AT = CurType->getAsArrayTypeUnsafe(); + assert(AT); + QualType ElemTy = AT->getElementType(); + if (!validType(ElemTy)) + return std::nullopt; + Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity(); + CurType = AT->getElementType(); + } + } + } + + if (UseClosestSurroundingVariable && SurroundingArrayOffset) + return Offset - *SurroundingArrayOffset; + + QualType Ty = CurType.getNonReferenceType(); + + if (UseClosestSurroundingVariable && + (Ty->isIncompleteType() || Ty->isFunctionType())) + return std::nullopt; + + if (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field && + isa<IncompleteArrayType>(CurType)) { + return Offset; + } + + if (UseClosestSurroundingVariable) + return 0; + + return Offset; +} + +/// Check if the given pointer points to the complete object, i.e. either to the +/// very beginning or after the end (into the flexible array member) of the +/// object. +static bool pointsToCompleteObject(const ASTContext &ASTCtx, + const Pointer &Ptr) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + if (OP.PathLength == 0) + return true; + + QualType FieldType = computeFieldType(ASTCtx, OP); + return isa<IncompleteArrayType>(FieldType); +} + +static std::optional<unsigned> +computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr, + bool UseClosestSurroundingVariable) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + + CharUnits TypeSize; + // NOTE: Clang does not consider base casts. GCC does. + if (UseClosestSurroundingVariable) { + QualType FieldTy = + computeFieldType(ASTCtx, OP, SurroundingArray | IgnoreBaseCasts); + if (!validType(FieldTy)) + return std::nullopt; + TypeSize = ASTCtx.getTypeSizeInChars(FieldTy); + } else { + QualType ObjectTy = OP.getObjectType(); + if (!validType(ObjectTy)) + return std::nullopt; + TypeSize = ASTCtx.getTypeSizeInChars(ObjectTy); + } + + // Check if we need to add the flexible array member size. + const VarDecl *Base = dyn_cast<VarDecl>(OP.Base); + if (!Base || !Base->getType()->isRecordType()) + return TypeSize.getQuantity(); + + if (!Base->hasInit()) + return TypeSize.getQuantity(); + + CharUnits FlexibleArraySize = Base->getFlexibleArrayInitChars(ASTCtx); + return (TypeSize + FlexibleArraySize).getQuantity(); +} + +namespace clang { +namespace interp { + +/// Evaluate __builtin_object_size or __builtin_dynamic_object_size for the +/// given pointer and Kind. +/// +/// When computing the final result, the most important variable is +/// UseClosestSurroundingVariable. If it is true, we will use the field the +/// pointer points to, or the parent array of the element. +/// UseClosestSurroundingVariable is true for Kind 1 and 3. +UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx, + unsigned Kind, Pointer &Ptr, + const Expr *E, bool IsDynamic) { + if (Ptr.isZero()) + return std::nullopt; + + bool InvalidBase = false; + if (Ptr.isOpaquePointer()) { + bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3); + const OpaquePointer &OP = Ptr.asOpaquePointer(); + InvalidBase = OP.Base->getType()->isPointerType(); + bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr); + + // Either the size of the full variable (Kind = 0 or 2) or the size of the + // closest surrounding variable (Kind = 1 or 3). + std::optional<unsigned> FullSize = + computeOpaqueSize(ASTCtx, Ptr, UseClosestSurroundingVariable); + + if (!FullSize) + return std::nullopt; + + // Similar to the FullSize above, the offset is relative either to the full + // variable or to the closest surrounding variable. + bool OffsetIsNegative = false; + std::optional<uint64_t> Offset = computeOpaquePtrOffset( + ASTCtx, Ptr, UseClosestSurroundingVariable, OffsetIsNegative); + + if (!Offset) + return std::nullopt; + + if (OffsetIsNegative) + return 0u; + + // For __builtin_dynamic_object_size on a counted_by-annotated flexible + // array member, defer to IR generation (emitCountedBySize in CGBuiltin): + // its runtime computation uses the live 'count' field and is more accurate + // than the layout/initializer-derived size we'd produce here. Use the same + // findStructFieldAccess form-recognition CGBuiltin does, so we refuse to + // fold on exactly the shapes that path handles (and, importantly, *not* + // on '&af.fam' which designates the array-as-a-whole and stays on the + // layout-derived path to match GCC). + if (IsDynamic) { + const auto *ME = + dyn_cast_if_present<MemberExpr>(findStructFieldAccess(E)); + const auto *FD = ME ? dyn_cast<FieldDecl>(ME->getMemberDecl()) : nullptr; + if (FD && FD->getType()->isCountAttributedType()) + return std::nullopt; + } + + if (!UseClosestSurroundingVariable || DetermineForCompleteObject) { + // Kind=3 wants a lower bound, so we can't fall back to this. + if (Kind == 3 && !DetermineForCompleteObject) + return std::nullopt; + + if (InvalidBase) + return std::nullopt; + } + + *Offset += Ptr.getByteOffset(); + + if (*Offset > *FullSize) + return 0u; + + if (InvalidBase && isUserWritingOffTheEnd(ASTCtx, OP)) { + if (Kind == 1) + return std::nullopt; + } + + assert(*Offset <= *FullSize); + return static_cast<unsigned>(*FullSize - *Offset); + } + + // ---------------------------------------------------------------------------------------------------- + + if (Ptr.isDummy() && Ptr.getType()->isPointerType()) + return std::nullopt; + + if (!Ptr.isBlockPointer()) + return std::nullopt; + + if (Ptr.isDummy()) { + if (const VarDecl *VD = Ptr.getRootVarDecl(); + VD && VD->getType()->isPointerType()) + InvalidBase = true; + } + + bool UseFieldDesc = (Kind & 1u); + bool ReportMinimum = (Kind & 2u); + + // 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); + + if (!UseFieldDesc || DetermineForCompleteObject) { + // Can't read beyond the pointer decl desc. + if (!ReportMinimum && DeclDesc->getDataType(ASTCtx)->isPointerType()) + return std::nullopt; + + if (InvalidBase) + return std::nullopt; + } else { + if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) { + // 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 std::nullopt; + } + } + + // The "closest surrounding subobject" is NOT a base class, + // so strip the base class casts. + if (UseFieldDesc && Ptr.isBaseClass()) + Ptr = Ptr.stripBaseCasts(); + + const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc; + assert(Desc); + + std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc); + if (!FullSize) + return std::nullopt; + + unsigned ByteOffset; + if (UseFieldDesc) { + if (Ptr.isBaseClass()) { + assert(computePointerOffset(ASTCtx, Ptr.getBase()) <= + computePointerOffset(ASTCtx, Ptr)); + ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) - + computePointerOffset(ASTCtx, Ptr); + } else { + if (Ptr.inArray()) + ByteOffset = + computePointerOffset(ASTCtx, Ptr) - + computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow()); + else + ByteOffset = 0; + } + } else + ByteOffset = computePointerOffset(ASTCtx, Ptr); + + assert(ByteOffset <= *FullSize); + return *FullSize - ByteOffset; +} +} // namespace interp +} // namespace clang diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h index 1df570ac971c4..bfe57349c31ac 100644 --- a/clang/lib/AST/ByteCode/InterpHelpers.h +++ b/clang/lib/AST/ByteCode/InterpHelpers.h @@ -81,7 +81,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest); UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx, - unsigned Kind, Pointer &Ptr); + unsigned Kind, Pointer &Ptr, + const Expr *E, bool IsDynamic = false); template <typename T> bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) { diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h index 050fa4c77cd2f..4fdae266e5d86 100644 --- a/clang/lib/AST/ByteCode/InterpState.h +++ b/clang/lib/AST/ByteCode/InterpState.h @@ -127,6 +127,10 @@ class InterpState final : public State, public SourceMapper { return reinterpret_cast<const CXXRecordDecl **>( this->allocate(Length * sizeof(CXXRecordDecl *))); } + PointerPathEntry *allocPointerPath(unsigned Length) { + return reinterpret_cast<PointerPathEntry *>( + this->allocate(Length * sizeof(PointerPathEntry))); + } /// Note that a step has been executed. If there are no more steps remaining, /// diagnoses and returns \c false. diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 74d3cc84e06ab..aab408cc7fe56 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -564,6 +564,7 @@ class LoadOpcode : Opcode { def Load : LoadOpcode {} // [Pointer] -> [Value] def LoadPop : LoadOpcode {} +def LoadPopL : Opcode {} class StoreOpcode : Opcode { let Types = [AllTypeClass]; @@ -617,6 +618,11 @@ def AddOffset : Opcode { let Types = [IntegralTypeClass]; let HasGroup = 1; } + +def GetOpaquePtr : SuccessOpcode { + let Args = [ArgValueDecl]; +} + // [Pointer, Integral] -> [Pointer] def SubOffset : Opcode { let Types = [IntegralTypeClass]; diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index b987b350f9537..bc80eda50d45a 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -59,6 +59,9 @@ Pointer::Pointer(const Pointer &P) case Storage::Typeid: Typeid = P.Typeid; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } } @@ -78,6 +81,9 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) { case Storage::Typeid: Typeid = P.Typeid; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } } @@ -127,6 +133,10 @@ Pointer &Pointer::operator=(const Pointer &P) { break; case Storage::Typeid: Typeid = P.Typeid; + break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } return *this; } @@ -166,6 +176,10 @@ Pointer &Pointer::operator=(Pointer &&P) { break; case Storage::Typeid: Typeid = P.Typeid; + break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } return *this; } @@ -198,6 +212,11 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { /*OnePastTheEnd=*/false, /*IsNull=*/false); } + if (isOpaquePointer()) { + return APValue(APValue::LValueBase(), CharUnits::Zero(), Path, + /*IsOnePastEnd=*/false, /*IsNullPtr=*/true); + } + // Build the lvalue base from the block. const Descriptor *Desc = getDeclDesc(); APValue::LValueBase Base; @@ -347,6 +366,12 @@ void Pointer::print(llvm::raw_ostream &OS) const { OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", " << (const void *)asTypeidPointer().TypeInfoType << " + " << Offset << "}"; + break; + case Storage::Opaque: + OS << "(Opaque) { Base: " << Opaque.Base << ", " + << Opaque.FieldType.getPointer() << " Length: " << Opaque.PathLength + << ". PastEnd: " << Opaque.isOnePastEnd(); + OS << "} + " << Offset; } } @@ -371,6 +396,8 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const { return getIntegerRepresentation(); case Storage::Typeid: return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset; + case Storage::Opaque: + return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset; } auto getTypeSize = [&](QualType T) -> std::optional<size_t> { @@ -449,6 +476,8 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const { return getIntegerRepresentation(); case Storage::Typeid: return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset; + case Storage::Opaque: + return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset; } auto getTypeSize = [&](QualType T) -> std::optional<size_t> { @@ -1196,3 +1225,28 @@ IntPointer IntPointer::baseCast(const interp::Context &Ctx, std::nullopt, RD, false); return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()}; } + +QualType OpaquePointer::getSurroundingArray(const ASTContext &ASTCtx) const { + assert(PathLength != 0); + assert(Path[PathLength - 1].Kind == PointerPathEntry::Array); + + QualType CurType = getObjectType(); + for (const PointerPathEntry &Entry : path().drop_back(1)) { + switch (Entry.Kind) { + case PointerPathEntry::Base: + CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer()); + break; + case PointerPathEntry::Field: + CurType = Entry.FD->getType(); + break; + case PointerPathEntry::Array: { + if (!CurType->isArrayType()) + break; + const ArrayType *AT = CurType->getAsArrayTypeUnsafe(); + assert(AT); + CurType = AT->getElementType(); + } + } + } + return CurType; +} diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index c06347318dafa..a3ddf75f94180 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -370,7 +370,66 @@ struct TypeidPointer { const Type *TypeInfoType; }; -enum class Storage { Int, Block, Fn, Typeid }; +struct PointerPathEntry { + enum { Base, Field, Array } Kind; + union { + int64_t Index; + const FieldDecl *FD; + llvm::PointerIntPair<const CXXRecordDecl *, 1, bool> RD = {}; + }; + + static PointerPathEntry base(const CXXRecordDecl *RD, bool Virtual = false) { + PointerPathEntry E; + E.Kind = Base; + E.RD = {RD, Virtual}; + return E; + } + + static PointerPathEntry array(int64_t Index) { + PointerPathEntry E; + E.Kind = Array; + E.Index = Index; + return E; + } + + static PointerPathEntry field(const FieldDecl *FD) { + PointerPathEntry E; + E.Kind = Field; + E.FD = FD; + return E; + } +}; + +struct OpaquePointer { + const ValueDecl *Base = nullptr; + // FieldType and IsOnePastEnd bit. + llvm::PointerIntPair<const Type *, 1, bool> FieldType = {}; + const PointerPathEntry *Path = nullptr; + unsigned PathLength = 0; + + ArrayRef<PointerPathEntry> path() const { return ArrayRef(Path, PathLength); } + + QualType getObjectType() const { + QualType T = Base->getType(); + if (T->isPointerOrReferenceType()) + return T->getPointeeType(); + return T; + } + + QualType getFieldType() const { + if (FieldType.getPointer()->isPointerOrReferenceType()) + return FieldType.getPointer()->getPointeeType(); + return QualType(FieldType.getPointer(), 0); + } + + /// If this is pointing to an array element, return the array. + QualType getSurroundingArray(const ASTContext &ASTCtx) const; + + bool isOnePastEnd() const { return FieldType.getInt(); } +}; +struct OpaqueTag {}; + +enum class Storage { Int, Block, Fn, Typeid, Opaque }; /// A pointer to a memory block, live or dead. /// @@ -420,6 +479,30 @@ class Pointer { Typeid.TypePtr = TypePtr; Typeid.TypeInfoType = TypeInfoType; } + Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType, + uint64_t Offset = 0) + : Offset(Offset), StorageKind(Storage::Opaque) { + Opaque.FieldType = {FieldType, /*IsOnePastEnd=*/false}; + Opaque.Path = nullptr; + Opaque.PathLength = 0; + Opaque.Base = Base; + } + Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType, + const PointerPathEntry *Path, unsigned PathLength, bool OPE = false, + uint64_t Offset = 0) + : Offset(Offset), StorageKind(Storage::Opaque) { + Opaque.FieldType = {FieldType, OPE}; + Opaque.Path = Path; + Opaque.PathLength = PathLength; + Opaque.Base = Base; + } + Pointer(OpaqueTag, const ValueDecl *Base, uint64_t Offset = 0) + : Offset(Offset), StorageKind(Storage::Opaque) { + Opaque.FieldType = {Base->getType().getTypePtr(), /*IsOnePastEnd=*/false}; + Opaque.Path = nullptr; + Opaque.PathLength = 0; + Opaque.Base = Base; + } Pointer(Block *Pointee, unsigned Base, uint64_t Offset); explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {} @@ -514,6 +597,7 @@ class Pointer { case Storage::Fn: return !Fn.Func; case Storage::Typeid: + case Storage::Opaque: return false; } llvm_unreachable("Unknown clang::interp::Storage enum"); @@ -581,6 +665,8 @@ class Pointer { return Fn.Func->getDecl()->getType(); case Storage::Typeid: return QualType(Typeid.TypeInfoType, 0); + case Storage::Opaque: + return QualType(Opaque.FieldType.getPointer(), 0); } llvm_unreachable("Unhandled StorageKind"); } @@ -675,11 +761,16 @@ class Pointer { assert(isTypeidPointer()); return Typeid; } + [[nodiscard]] const OpaquePointer &asOpaquePointer() const { + assert(isOpaquePointer()); + return Opaque; + } bool isBlockPointer() const { return StorageKind == Storage::Block; } bool isIntegralPointer() const { return StorageKind == Storage::Int; } bool isFunctionPointer() const { return StorageKind == Storage::Fn; } bool isTypeidPointer() const { return StorageKind == Storage::Typeid; } + bool isOpaquePointer() const { return StorageKind == Storage::Opaque; } /// Returns the record descriptor of a class. const Record *getRecord() const { @@ -1072,6 +1163,7 @@ class Pointer { BlockPointer BS; FunctionPointer Fn; TypeidPointer Typeid; + OpaquePointer Opaque; }; }; diff --git a/clang/lib/AST/ByteCode/Program.cpp b/clang/lib/AST/ByteCode/Program.cpp index 378a190184be9..51f945d4d4308 100644 --- a/clang/lib/AST/ByteCode/Program.cpp +++ b/clang/lib/AST/ByteCode/Program.cpp @@ -143,14 +143,16 @@ unsigned Program::getOrCreateDummy(DeclTy D, bool IsConstexprUnknown) { const auto *VD = cast<ValueDecl>(cast<const Decl *>(D)); IsWeak = VD->isWeak(); QT = VD->getType(); - if (QT->isPointerOrReferenceType()) + + if (QT->isReferenceType()) QT = QT->getPointeeType(); } + assert(!QT.isNull()); Descriptor *Desc; if (OptPrimType T = Ctx.classify(QT)) - Desc = createDescriptor(D, *T, /*SourceTy=*/nullptr, std::nullopt, + Desc = createDescriptor(D, *T, /*SourceTy=*/QT.getTypePtr(), std::nullopt, /*IsConst=*/QT.isConstQualified()); else Desc = createDescriptor(D, QT.getTypePtr(), std::nullopt, diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index e3f74d73f21da..ac3a13a2d155d 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -78,6 +78,7 @@ add_clang_library(clangAST ByteCode/Function.cpp ByteCode/InterpBuiltin.cpp ByteCode/InterpBuiltinBitCast.cpp + ByteCode/InterpBuiltinObjectSize.cpp ByteCode/Floating.cpp ByteCode/EvaluationResult.cpp ByteCode/DynamicAllocator.cpp diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 9d69de2a7c6fd..351d69b48cabe 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -23001,7 +23001,10 @@ std::optional<uint64_t> Expr::tryEvaluateObjectSize(const ASTContext &Ctx, Expr::EvalStatus Status; EvalInfo Info(Ctx, Status, EvaluationMode::ConstantFold); if (Info.EnableNewConstInterp) - return Info.Ctx.getInterpContext().tryEvaluateObjectSize(Info, this, Type); + return Info.Ctx.getInterpContext().tryEvaluateObjectSize( + Info, this, Type, + /*IsDynamic=*/false); + return tryEvaluateBuiltinObjectSize(this, Type, Info); } diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp new file mode 100644 index 0000000000000..2f229fff28d11 --- /dev/null +++ b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -std=c++23 -fexperimental-new-constant-interpreter -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -std=c++23 -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s + +struct basic_filebuf { + char __extbuf_; + char __extbuf_min_[8]; +}; +// CHECK-LABEL: @_Z4swapR13basic_filebuf +void swap(basic_filebuf &__rhs) { + int gi; + // CHECK: store i32 8 + gi = __builtin_object_size(__rhs.__extbuf_min_, 0); +} + + diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.c b/clang/test/AST/ByteCode/builtin-object-size-codegen.c index 1b2561a89ebba..45d91ba197323 100644 --- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c +++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c @@ -45,3 +45,67 @@ void foo2(struct Foo *t) { void foo(void *p) { int i = __builtin_object_size(&p[2], 3); } + +struct DynStructVar { + char fst[16]; + char snd[]; +}; + +static struct DynStructVar D32 = { + .fst = {}, + .snd = { 0, 1, 2, 3, 4, 5, 6 }, +}; + +// CHECK-LABEL: @test32 +void test32(void) { + // CHECK: store i32 23 + gi = __builtin_object_size(&D32, 0); + // CHECK: store i32 23 + gi = __builtin_object_size(&D32, 1); + // CHECK: store i32 23 + gi = __builtin_object_size(&D32, 2); + // CHECK: store i32 23 + gi = __builtin_object_size(&D32, 3); + + // CHECK: store i32 7 + gi = __builtin_object_size(&D32.snd[0], 0); + // CHECK: store i32 1 + gi = __builtin_object_size(&D32.snd[6], 0); + // CHECK: store i32 0 + gi = __builtin_object_size(&D32.snd[10], 0); +} + +struct S { + char c[7]; + char k[]; +}; + +struct S s = { + .c = {1,2,3,4,5,6,7}, + .k = {1,2,3,4,5 } +}; + +// CHECK-LABEL: @testflex +void testflex() { + int gi; + // CHECK: store i32 5 + gi = __builtin_object_size(&s.k, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(&s.k, 1); + // CHECK: store i32 5 + gi = __builtin_object_size(&s.k, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(&s.k, 3); + + // CHECK: store i32 2 + gi = __builtin_object_size(&s.k[3], 0); + // CHECK: store i32 2 + gi = __builtin_object_size(&s.k[3], 1); + // CHECK: store i32 2 + gi = __builtin_object_size(&s.k[3], 2); + /// The following fails to evaluate in clang but returns 2 in GCC. + // store i32 0 + gi = __builtin_object_size(&s.k[3], 3); +} + + diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp index 7a7ac26c1b0be..7055d99d75635 100644 --- a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp +++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp @@ -51,6 +51,7 @@ typedef struct { double c[0]; float f; } foofoo0_t; + // CHECK-LABEL: @_Z6babar0P9foofoo0_t unsigned babar0(foofoo0_t *f) { // CHECK: ret i32 0 @@ -127,3 +128,190 @@ void nonPtrParam(C c) { gi = __builtin_object_size(&c.bs[0], 2); } + +struct X { + char p[7]; +}; + +struct Y: X { + char p[3]; +}; + +struct F { + Y y; +}; + +// CHECK-LABEL: @_Z6testXYv +void testXY() { + int gi; + Y y; + + // CHECK: store i32 10 + gi = __builtin_object_size(&y, 0); + // CHECK: store i32 10 + gi = __builtin_object_size(&y, 1); + // CHECK: store i32 10 + gi = __builtin_object_size(&y, 2); + // CHECK: store i32 10 + gi = __builtin_object_size(&y, 3); + + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&y, 0); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&y, 1); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&y, 2); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&y, 3); + + + F f; + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&f.y, 0); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&f.y, 1); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&f.y, 2); + // CHECK: store i32 10 + gi = __builtin_object_size((X*)&f.y, 3); + + + // CHECK: store i32 6 + gi = __builtin_object_size(&((X*)&f.y)->p[4], 0); + // CHECK: store i32 3 + gi = __builtin_object_size(&((X*)&f.y)->p[4], 1); + // CHECK: store i32 6 + gi = __builtin_object_size(&((X*)&f.y)->p[4], 2); + // CHECK: store i32 3 + gi = __builtin_object_size(&((X*)&f.y)->p[4], 3); +} + +// CHECK-LABEL: @_Z7testOPEv +int s; +void testOPE() { + int gi; + + // CHECK: store i32 4 + gi = __builtin_object_size(&s, 0); + // CHECK: store i32 4 + gi = __builtin_object_size(&s, 1); + // CHECK: store i32 4 + gi = __builtin_object_size(&s, 2); + // CHECK: store i32 4 + gi = __builtin_object_size(&s, 3); + + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 1, 0); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 1, 1); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 1, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 1, 3); + + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 20, 0); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 20, 1); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 20, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(&s + 20, 3); +} + +struct K {char p[6]; }; +// CHECK-LABEL: @_Z18testArrayAddOffsetv +void testArrayAddOffset() { + int gi; + + K ks[4]; + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 1, 0); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 1, 1); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 1, 2); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 1, 3); + + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 3 - 2, 0); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 3 - 2, 1); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 3 - 2, 2); + // CHECK: store i32 18 + gi = __builtin_object_size(ks + 3 - 2, 3); + + // CHECK: store i32 0 + gi = __builtin_object_size(ks - 5, 0); + // CHECK: store i32 0 + gi = __builtin_object_size(ks - 5, 1); + // CHECK: store i32 0 + gi = __builtin_object_size(ks - 5, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(ks - 5, 3); +} + + +struct LoadCommandInfo { + char *Ptr; + int a; + int b; +}; + +// CHECK-LABEL: @_Z16testNonConstBasev +void testNonConstBase() { + struct A { char buf[16]; }; + struct B : A {}; + struct C { int i; B bs[1]; } *c; + + LoadCommandInfo LC; + int gi; + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(LC.Ptr, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(LC.Ptr + 8, 0); +} + + +struct Ref_struct { + int RD, Sib; + int *Op; +}; + +struct NodeBase { + int Next; + Ref_struct RefData; +}; + +struct NodeAddr { + NodeBase *Addr; + int Id; +}; + +// CHECK-LABEL: @_Z9cloneNode8NodeAddr +void cloneNode(const NodeAddr B) { + NodeBase NA_0; + // memcpy(&NA_0, B.Addr, sizeof(NodeBase)); + + int gi; + + // CHECK: store i32 24 + gi = __builtin_object_size(&NA_0, 0); + // CHECK: store i32 24 + gi = __builtin_object_size(&NA_0, 1); + // CHECK: store i32 24 + gi = __builtin_object_size(&NA_0, 2); + // CHECK: store i32 24 + gi = __builtin_object_size(&NA_0, 3); + + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(B.Addr, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(B.Addr, 1); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 false) + gi = __builtin_object_size(B.Addr, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(B.Addr, 3); +} diff --git a/clang/test/AST/ByteCode/enable_if.c b/clang/test/AST/ByteCode/enable_if.c deleted file mode 100644 index 8148db2719449..0000000000000 --- a/clang/test/AST/ByteCode/enable_if.c +++ /dev/null @@ -1,202 +0,0 @@ -// RUN: %clang_cc1 -verify=ref,both %s -// RUN: %clang_cc1 -verify=expected,both %s -fexperimental-new-constant-interpreter - -// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s -// %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s - -/// This is the same file we have in test/Sema/, but there is one test that doesn't yet pass with the bytecode interpreter. -/// TODO: Delete this file and add an appropriate RUN line to the file in test/Sema/ instead. -/// -/// The problem is related to a wrongly computed value in the __builtin_object_size implementation. - -#define O_CREAT 0x100 -typedef int mode_t; -typedef unsigned long size_t; - -enum { TRUE = 1 }; - -int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & O_CREAT), "must specify mode when using O_CREAT"))) __attribute__((overloadable)); // both-note{{candidate disabled: must specify mode when using O_CREAT}} -int open(const char *pathname, int flags, mode_t mode) __attribute__((overloadable)); // both-note{{candidate function not viable: requires 3 arguments, but 2 were provided}} - -void test1(void) { -#ifndef CODEGEN - open("path", O_CREAT); // both-error{{no matching function for call to 'open'}} -#endif - open("path", O_CREAT, 0660); - open("path", 0); - open("path", 0, 0); -} - -size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len); - -size_t strnlen(const char *s, size_t maxlen) - __attribute__((overloadable)) - __asm__("strnlen_real1"); - -__attribute__((always_inline)) -inline size_t strnlen(const char *s, size_t maxlen) - __attribute__((overloadable)) - __attribute__((enable_if(__builtin_object_size(s, 0) != -1, - "chosen when target buffer size is known"))) -{ - return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0)); -} - -size_t strnlen(const char *s, size_t maxlen) - __attribute__((overloadable)) - __attribute__((enable_if(__builtin_object_size(s, 0) != -1, - "chosen when target buffer size is known"))) - __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0), - "chosen when 'maxlen' is known to be less than or equal to the buffer size"))) - __asm__("strnlen_real2"); - -size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been explicitly marked unavailable here}} - __attribute__((overloadable)) - __attribute__((enable_if(__builtin_object_size(s, 0) != -1, - "chosen when target buffer size is known"))) - __attribute__((enable_if(maxlen > __builtin_object_size(s, 0), - "chosen when 'maxlen' is larger than the buffer size"))) - __attribute__((unavailable("'maxlen' is larger than the buffer size"))); - -void test2(const char *s, int i) { -// CHECK: define {{.*}}void @test2 - const char c[123] = { 0 }; - strnlen(s, i); -// CHECK: call {{.*}}strnlen_real1 - strnlen(s, 999); -// CHECK: call {{.*}}strnlen_real1 - strnlen(c, 1); -// CHECK: call {{.*}}strnlen_real2 - strnlen(c, i); -// CHECK: call {{.*}}strnlen_chk -#ifndef CODEGEN - strnlen(c, 999); // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}} -#endif -} - -int isdigit(int c) __attribute__((overloadable)); -int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has been explicitly marked unavailable here}} - __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF"))) - __attribute__((unavailable("'c' must have the value of an unsigned char or EOF"))); - -void test3(int c) { - isdigit(c); // both-warning{{ignoring return value of function declared with pure attribute}} - isdigit(10); // both-warning{{ignoring return value of function declared with pure attribute}} -#ifndef CODEGEN - isdigit(-10); // both-error{{'isdigit' is unavailable: 'c' must have the value of an unsigned char or EOF}} -#endif -} - -// Verify that the alternate spelling __enable_if__ works as well. -int isdigit2(int c) __attribute__((overloadable)); -int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' has been explicitly marked unavailable here}} - __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF"))) - __attribute__((unavailable("'c' must have the value of an unsigned char or EOF"))); - -void test4(int c) { - isdigit2(c); - isdigit2(10); -#ifndef CODEGEN - isdigit2(-10); // both-error{{'isdigit2' is unavailable: 'c' must have the value of an unsigned char or EOF}} -#endif -} - -void test5(void) { - int (*p1)(int) = &isdigit2; - int (*p2)(int) = isdigit2; - void *p3 = (void *)&isdigit2; - void *p4 = (void *)isdigit2; -} - -#ifndef CODEGEN -__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); // both-error{{use of undeclared identifier 'n'}} - -int n __attribute__((enable_if(1, "always chosen"))); // both-warning{{'enable_if' attribute only applies to functions}} - -void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0))); // both-error{{expected string literal as argument of 'enable_if' attribute}} - -void f(int n) __attribute__((enable_if())); // both-error{{'enable_if' attribute requires exactly 2 arguments}} - -void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 'unresolvedid' is non-zero"))); // both-error{{use of undeclared identifier 'unresolvedid'}} - -int global; -void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is zero"))); // both-error{{'enable_if' attribute expression never produces a constant expression}} \ - // both-note{{subexpression not valid in a constant expression}} - -enum { cst = 7 }; -void return_cst(void) __attribute__((overloadable)) __attribute__((enable_if(cst == 7, "chosen when 'cst' is 7"))); -void test_return_cst(void) { return_cst(); } - -void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, "always chosen"))); // #f2_1 -void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never chosen"))); // #f2_2 -void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, "always chosen #2"))); // #f2_3 -void test6(void) { - void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \ - // both-note@#f2_1 {{candidate function}} \ - // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ - // both-note@#f2_3 {{candidate function}} - void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \ - // both-note@#f2_1 {{candidate function}} \ - // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ - // both-note@#f2_3 {{candidate function}} - void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \ - // both-note@#f2_1 {{candidate function}} \ - // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ - // both-note@#f2_3 {{candidate function}} - void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \ - // both-note@#f2_1 {{candidate function}} \ - // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ - // both-note@#f2_3 {{candidate function}} -} - -void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, "positive"))); // #f3_1 -void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, "negative"))); // #f3_2 -void test7(void) { - void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \ - // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ - // both-note@#f3_2 {{candidate function made ineligible by enable_if}} - void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \ - // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ - // both-note@#f3_2 {{candidate function made ineligible by enable_if}} - void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \ - // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ - // both-note@#f3_2 {{candidate function made ineligible by enable_if}} - void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \ - // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ - // both-note@#f3_2 {{candidate function made ineligible by enable_if}} -} - -void f4(int m) __attribute__((enable_if(0, ""))); -void test8(void) { - void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}} - void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}} -} - -void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 3{{declared here}} -void PR27122_ext(void) { - regular_enable_if(0, 2); // both-error{{too many arguments}} - regular_enable_if(1, 2); // both-error{{too many arguments}} - regular_enable_if(); // both-error{{too few arguments}} -} - -// We had a bug where we'd crash upon trying to evaluate varargs. -void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // both-note 6 {{disabled}} -void variadic_test(void) { - variadic_enable_if(1); - variadic_enable_if(1, 2); - variadic_enable_if(1, "c", 3); - - variadic_enable_if(0); // both-error{{no matching}} - variadic_enable_if(0, 2); // both-error{{no matching}} - variadic_enable_if(0, "c", 3); // both-error{{no matching}} - - int m; - variadic_enable_if(1); - variadic_enable_if(1, m); - variadic_enable_if(1, m, "c"); - - variadic_enable_if(0); // both-error{{no matching}} - variadic_enable_if(0, m); // both-error{{no matching}} - variadic_enable_if(0, m, 3); // both-error{{no matching}} -} -#endif diff --git a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c index e840db632957e..272ff5004d3e8 100644 --- a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c +++ b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c @@ -1,6 +1,8 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6 -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s #if !__has_attribute(counted_by) #error "has attribute broken" diff --git a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c index 7ceb51c8986bd..1dd307ff7f72b 100644 --- a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c +++ b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c @@ -1,6 +1,10 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6 -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s + #if !__has_attribute(counted_by) #error "has attribute broken" diff --git a/clang/test/Sema/enable_if.c b/clang/test/Sema/enable_if.c index 80f8cce5918ed..86b2645656b9a 100644 --- a/clang/test/Sema/enable_if.c +++ b/clang/test/Sema/enable_if.c @@ -1,6 +1,9 @@ // RUN: %clang_cc1 %s -verify // RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -verify +// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -DCODEGEN -emit-llvm -o - | FileCheck %s + #define O_CREAT 0x100 typedef int mode_t; typedef unsigned long size_t; diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp index 2a2f91186871e..0c1f95ae3d09a 100644 --- a/clang/test/SemaCXX/new-delete.cpp +++ b/clang/test/SemaCXX/new-delete.cpp @@ -718,12 +718,22 @@ int *fail = dependent_array_size("hello"); // expected-note {{instantiation of}} // FIXME: Our behavior here is incredibly inconsistent. GCC allows // constant-folding in array bounds in new-expressions. int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold]; -#if __cplusplus >= 201402L && !defined(NEW_INTERP) +#if __cplusplus >= 201402L // expected-error@-2 {{array size is not a constant expression}} // expected-note@-3 {{cannot refer to element 12 of non-array}} +#elif __cplusplus == 201103L +#if defined(NEW_INTERP) +// expected-error@-6 {{only the first dimension of an allocated array may have dynamic size}} +// expected-note@-7 {{cannot refer to element 12 of non-array}} +#endif #elif __cplusplus < 201103L -// expected-error@-5 {{cannot allocate object of variably modified type}} -// expected-warning@-6 {{variable length arrays in C++ are a Clang extension}} +#if defined(NEW_INTERP) +// expected-error@-11 {{only the first dimension of an allocated array may have dynamic size}} +// expected-note@-12 {{cannot refer to element 12 of non-array}} +#else +// expected-error@-14 {{cannot allocate object of variably modified type}} +// expected-warning@-15 {{variable length arrays in C++ are a Clang extension}} +#endif #endif #if __cplusplus >= 201103L _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
