Author: Timm Baeder Date: 2026-09-02T13:57:08+02:00 New Revision: 71b1aad2404290bb71fdc0d763cfbb2e4727d749
URL: https://github.com/llvm/llvm-project/commit/71b1aad2404290bb71fdc0d763cfbb2e4727d749 DIFF: https://github.com/llvm/llvm-project/commit/71b1aad2404290bb71fdc0d763cfbb2e4727d749.diff LOG: [clang][bytecode] Add opaque pointers to support type-only pointers (#213017) This adds a new pointer type to represent something we don't have data for, but can still refer to. They are similar to LValue APValues: it's a base (currently always a `VarDecl`) and a "lvalue designator" path. For the first argument of `__builtin_object_size` and `__builtin_dynamic_object_size`, when we see an unknown variable, we emit an opaque pointer for the variable instead of a dummy pointer. Also if any load in that argument fails, we don't abort the evaluation but instead push an opaque pointer of the appropriate type on the stack. This fixes the review complaint from https://github.com/llvm/llvm-project/pull/196548, namely that we didn't have a way to just point to an opaque type if a load fails. Cases that still don't work: 1) The stuff in `test/Sema/builtin-object-size-cxx14.cpp` that use non-constexpr functions in the first argument of `__builtin_object_size` (everything in the `InvalidBase` namespace). This behavior matches GCC. 2) From `test/CodeGen/object-size.c`, the sample ```c void test26(void) { struct { int v[10]; } t[10]; // CHECK: store i32 312 gi = OBJECT_SIZE_BUILTIN(&t[1].v[12], 1); } results in 0, not 312. I don't know why it would result in 312. For modes 1 and 3, we should consider the closest surrounding variable, wich only has 10 elements, so at index 12, we have 0 bytes we can read from. GCC also returns 0. 3) From `test/CodeGen/object-size.c`, functions `test7` through `test17`, because they modify variables in the first argument of `__builtin_object_size`. 4) This line from `test/CodeGen/pass-object-size.c`: ```c gi = NoViableOverloadObjectSize1(&t[1]); ``` This might seem overkill for now, but I plan on using opaque pointers as replacement for the current "dummy pointer" mechanism as well. Added: clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp Modified: clang/lib/AST/ByteCode/Compiler.cpp clang/lib/AST/ByteCode/Context.cpp clang/lib/AST/ByteCode/Context.h clang/lib/AST/ByteCode/EvalEmitter.cpp clang/lib/AST/ByteCode/Interp.cpp clang/lib/AST/ByteCode/Interp.h clang/lib/AST/ByteCode/InterpBuiltin.cpp clang/lib/AST/ByteCode/InterpHelpers.h clang/lib/AST/ByteCode/InterpState.h clang/lib/AST/ByteCode/Opcodes.td clang/lib/AST/ByteCode/Pointer.cpp clang/lib/AST/ByteCode/Pointer.h clang/lib/AST/ByteCode/Program.cpp clang/lib/AST/CMakeLists.txt clang/lib/AST/ExprConstShared.h clang/lib/AST/ExprConstant.cpp clang/test/AST/ByteCode/builtin-object-size-codegen.c clang/test/AST/ByteCode/builtin-object-size-codegen.cpp clang/test/AST/ByteCode/codegen.c clang/test/AST/ByteCode/literals.cpp clang/test/CodeGen/attr-counted-by-with-sanitizers.c clang/test/CodeGen/attr-counted-by-without-sanitizers.c clang/test/Sema/enable_if.c clang/test/SemaCXX/new-delete.cpp Removed: clang/test/AST/ByteCode/enable_if.c ################################################################################ diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 1aae6f525c2ac..b1e4c83cf333a 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Compiler.h" +#include "../ExprConstShared.h" #include "ByteCodeEmitter.h" #include "Context.h" #include "FixedPoint.h" @@ -455,8 +456,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); @@ -6102,7 +6107,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))) @@ -8724,8 +8729,13 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc, template <class Emitter> bool Compiler<Emitter>::emitDummyPtr(DeclOrExpr D, const Expr *E, bool CU) { assert(!DiscardResult && "Should've been checked before"); - unsigned DummyID = P.getOrCreateDummy(D, CU); + if (ToLValue) { + if (const auto *VD = D.asValueDecl()) + return this->emitGetOpaquePtr(VD, CU, 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 0212574e4accd..ab53ec1eda1db 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -410,26 +410,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 586b600614596..2a250ad119842 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/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp index 9d38113ce77c2..80eb593c2f6c4 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.cpp +++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp @@ -260,6 +260,8 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { // Function pointers are always returned as lvalues. if (Ptr.isFunctionPointer()) { + if (ConvertResultToRValue && Ptr.asFunctionPointer().Func->getDecl()) + return false; EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext())); return true; } diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 8be502e851504..a41cc1f5a564b 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1608,6 +1608,29 @@ 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.extendPointerPath( + OP.PathLength + 1, OP.Path, PointerPathEntry::field(F->Decl)); + + S.Stk.push<Pointer>(OP.withPath(NewPath, OP.PathLength + 1, + F->Decl->getType().getTypePtr()), + Ptr.getByteOffset()); + + return true; + } + if (!Ptr.isBlockPointer()) { // If we're trying to get the field of a TypeId pointer, try to produce a // proper diagnostic. @@ -1642,6 +1665,29 @@ 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; + + PointerPathEntry *NewPath = S.extendPointerPath( + OP.PathLength + 1, OP.Path, + PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl))); + S.Stk.push<Pointer>( + OP.withPath( + NewPath, OP.PathLength + 1, + S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr()), + Ptr.getByteOffset()); + return true; + } + if (!Ptr.isBlockPointer()) { if (!Ptr.isIntegralPointer()) return false; @@ -1902,6 +1948,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) { @@ -3235,6 +3282,128 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth, return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI); } +static bool validType(QualType T) { + if (const RecordDecl *RD = T->getAsRecordDecl()) + return ASTContext::hasLayout(RD); + return true; +} + +bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + APSInt &&Index, bool AllowReplace) { + const OpaquePointer &OP = Ptr.asOpaquePointer(); + QualType ArrTy = OP.getSurroundingArray(); + + if (isa<VariableArrayType>(ArrTy) && OP.PathLength != 0) + return false; + + QualType ElemType; + if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe()) + ElemType = AT->getElementType(); + else + ElemType = ArrTy; + + if (ArrTy->isArrayType()) { + unsigned NewPathLength; + if (AllowReplace && OP.isArrayElement()) { + // This is what happens after an array-to-pointer-decay. We don't enter + // the array element but simply change the index in the array we're + // already pointing into. + NewPathLength = OP.PathLength; + } else { + NewPathLength = OP.PathLength + 1; + } + + PointerPathEntry NewEntry; + if (Index.isNonNegative()) + NewEntry = PointerPathEntry::array(Index.getZExtValue()); + else + NewEntry = PointerPathEntry::negativeArray((-Index).getZExtValue()); + + PointerPathEntry *NewPath = + S.extendPointerPath(NewPathLength, OP.Path, NewEntry); + S.Stk.push<Pointer>( + OP.withPath(NewPath, NewPathLength, ElemType.getTypePtr()), + Ptr.getByteOffset()); + + } else { + if (!validType(ElemType)) + return false; + unsigned ElemSize = + S.getASTContext().getTypeSizeInChars(ElemType).getQuantity(); + size_t NewOffset = Ptr.getByteOffset() + (Index.getZExtValue() * ElemSize); + bool PastEnd = Index != 0; + + S.Stk.push<Pointer>(OP.withFieldType(ElemType.getTypePtr(), PastEnd), + NewOffset); + } + return true; +} + +std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC, + const Pointer &Ptr, APSInt &&Offset, + ArithOp Op) { + assert(Ptr.isOpaquePointer()); + if (Offset.isZero()) + return Ptr; + + const OpaquePointer &OP = Ptr.asOpaquePointer(); + QualType ArrTy = OP.getSurroundingArray(); + QualType ElemTy = ArrTy; + unsigned NumElems = 1; + if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe()) { + ElemTy = AT->getElementType(); + if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) + NumElems = CAT->getZExtSize(); + } + + if (isa<IncompleteArrayType>(ArrTy)) { + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_unsized_array_indexed); + return std::nullopt; + } + + if (Offset > NumElems) { + if (Op == ArithOp::Add) + S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems; + else + S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << -Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems; + } + + if (!validType(ElemTy) || !validType(ArrTy)) { + Invalid(S, OpPC); + return std::nullopt; + } + + if (Offset.getActiveBits() > 64) + return std::nullopt; + + // If the pointer is an array element, advance that index. + if (OP.isArrayElement()) { + unsigned NewPathLength = OP.PathLength; + PointerPathEntry *NewPath = S.allocPointerPath(OP.PathLength, OP.Path); + + if (Op == ArithOp::Add) + NewPath[NewPathLength - 1].Index += Offset.getZExtValue(); + else + NewPath[NewPathLength - 1].Index -= Offset.getZExtValue(); + return OP.withPath(NewPath, NewPathLength, OP.FieldType.getPointer()); + } + + unsigned ElemSize = + S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity(); + unsigned NewOffset; + if (Op == ArithOp::Add) + NewOffset = Ptr.getByteOffset() + (ElemSize * Offset.getZExtValue()); + else + NewOffset = Ptr.getByteOffset() - (ElemSize * Offset.getZExtValue()); + + // We already checked offset != before, so this is a non-array type being + // offset by > 0. + return Pointer(OP.withPastEnd(true), NewOffset); +} + // 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 d58f3ce46d41a..2aa5aee788230 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -2225,6 +2225,46 @@ 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 (!Failed && !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>(OP.withFieldType(T.getTypePtr()), + Ptr.getByteOffset()); + return true; + } + + // Convert the block pointer to an opaque pointer. + if (!Ptr.isBlockPointer()) + return false; + // FIXME: I *think* we need more information here than just the base. + S.Stk.push<Pointer>(Ptr.getDeclDesc()->asValueDecl()); + } 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>(); @@ -2631,11 +2671,23 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC, return Ptr.atIndex(static_cast<uint64_t>(Result)); } +std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC, + const Pointer &Ptr, APSInt &&Offset, + ArithOp Op); 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()) { + if (std::optional<Pointer> Result = + addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Add)) { + S.Stk.push<Pointer>(*Result); + return true; + } + return false; + } + if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push<Pointer>(Result->narrow()); @@ -2649,6 +2701,15 @@ bool SubOffset(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.pop<Pointer>().expand(); + if (Ptr.isOpaquePointer()) { + if (std::optional<Pointer> Result = + addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Sub)) { + S.Stk.push<Pointer>(*Result); + return true; + } + return false; + } + if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push<Pointer>(Result->narrow()); @@ -2657,6 +2718,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) { return false; } +inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD, + bool ConstexprUnknown) { + S.Stk.push<Pointer>(VD, ConstexprUnknown); + return true; +} + template <ArithOp Op> static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { @@ -2672,6 +2739,15 @@ static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC, // Get the current value on the stack. S.Stk.push<Pointer>(P); + if (P.isOpaquePointer()) { + if (std::optional<Pointer> Result = + addSubOffsetOpaque(S, OpPC, P, APSInt(APInt(1, 1), true), Op)) { + Ptr.deref<Pointer>() = *Result; + return true; + } + return false; + } + // Now the current Ptr again and a constant 1. OneT One = OneT::from(1); if (std::optional<Pointer> Result = @@ -3420,18 +3496,15 @@ 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 Pointer &Ptr, + APSInt &&Index, bool AllowReplace = true); + +// 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, Offset.toAPSInt()); if (Offset.isZero()) { if (const Descriptor *Desc = Ptr.getFieldDesc(); @@ -3450,33 +3523,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>(); + 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> @@ -3548,18 +3619,33 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) { return false; } - if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) { + if (Ptr.isRoot() || !Ptr.isUnknownSizeArray() || !S.inConstantContext()) { + if (Ptr.isBlockPointer()) { + S.Stk.push<Pointer>(Ptr.atIndex(0).narrow()); + return true; + } + if (Ptr.isStringPointer()) { S.Stk.push<Pointer>(Ptr.asStringPointer().decay()); return true; } - S.Stk.push<Pointer>(Ptr.atIndex(0).narrow()); - return true; - } - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_unsupported_unsized_array); + if (!Ptr.isOpaquePointer()) { + S.Stk.push<Pointer>(Ptr); + return true; + } + + if (!Ptr.getType()->isArrayType()) { + S.Stk.push<Pointer>(Ptr); + return true; + } + return arrayElemPtrOpaque(S, OpPC, Ptr, + APSInt(APInt::getZero(1), /*IsUnsigned=*/true), + /*AllowReplace=*/false); + } + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_unsupported_unsized_array); return false; } diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 1a464247b5ce2..91d6a32c136ac 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2340,218 +2340,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 @@ -2564,17 +2355,31 @@ static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC, assert(Kind <= 3 && "unexpected kind"); Pointer Ptr = S.Stk.pop<Pointer>(); + if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr, + Call->getArg(0), IsDynamic)) { + pushInteger(S, *Result, Call->getType()); + return true; + } + 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)) { - pushInteger(S, *Result, Call->getType()); + 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; } @@ -5552,8 +5357,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..c1a69e03c0e73 --- /dev/null +++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp @@ -0,0 +1,538 @@ +//===------------- 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 Type can be passed to +// ASTContext::getRecordLayout(). +static bool validType(QualType T) { + if (const RecordDecl *RD = T->getAsRecordDecl()) + return ASTContext::hasLayout(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: + case PointerPathEntry::NegativeArray: + 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 (!ASTContext::hasLayout(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) { + return Ptr.computeLayoutOffset(ASTCtx).value_or(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; + } + } break; + case PointerPathEntry::NegativeArray: + 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 (!ASTContext::hasLayout(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 (!ASTContext::hasLayout(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: + case PointerPathEntry::NegativeArray: { + bool Add = (Entry.Kind == PointerPathEntry::Array); + uint64_t Index = Entry.Index; + if (!Add) { + // NegativeArray is always > 0. + OffsetIsNegative = true; + } + SurroundingArrayOffset = Offset; + if (!CurType->isArrayType()) { + if (Add) + Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity(); + else + Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity(); + continue; + } + const ArrayType *AT = CurType->getAsArrayTypeUnsafe(); + assert(AT); + QualType ElemTy = AT->getElementType(); + if (!validType(ElemTy) || isa<VariableArrayType>(AT)) + return std::nullopt; + if (Add) + Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity(); + else + Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity(); + CurType = AT->getElementType(); + } break; + } + } + + if (UseClosestSurroundingVariable && SurroundingArrayOffset) + return Offset - *SurroundingArrayOffset; + + QualType Ty = CurType.getNonReferenceType(); + + if (UseClosestSurroundingVariable && + (Ty->isIncompleteType() || Ty->isFunctionType())) + return std::nullopt; + + if (isa<VariableArrayType>(Ty)) + 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); + if (OP.isArrayElement()) + FieldType = OP.getSurroundingArray(); + return isa<IncompleteArrayType>(FieldType); +} + +static std::optional<unsigned> +computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr, + bool UseClosestSurroundingVariable, bool WritingOffTheEnd, + bool DetermineForCompleteObject) { + 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); + } + + // The Flexible array member should only be checked if we're pointing to the + // object as a whole, or if we're looking for the whole object size. + if (!WritingOffTheEnd && !DetermineForCompleteObject) + return TypeSize.getQuantity(); + + // Check if we need to add the flexible array member size. + const VarDecl *Base = dyn_cast<VarDecl>(OP.Base); + if (!Base) + return TypeSize.getQuantity(); + + // If the base type is an incomplete array type (not a flexible array member + // of a struct), and we're looking for the complete object... we can't. + if (DetermineForCompleteObject && isa<IncompleteArrayType>(Base->getType())) + return std::nullopt; + + if (!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); + bool WritingOffTheEnd = isUserWritingOffTheEnd(ASTCtx, OP); + + // 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, + WritingOffTheEnd, DetermineForCompleteObject); + + 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; + + QualType ObjectTy = OP.getObjectType(); + if (ObjectTy->isIncompleteType() || isa<VariableArrayType>(ObjectTy) || + ObjectTy->isFunctionType()) + return std::nullopt; + } + + *Offset += Ptr.getByteOffset(); + + if (*Offset > *FullSize) + return 0u; + + if (Kind == 1 && InvalidBase && WritingOffTheEnd) + 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 61fc864ba62b2..4c60670ac5a0c 100644 --- a/clang/lib/AST/ByteCode/InterpHelpers.h +++ b/clang/lib/AST/ByteCode/InterpHelpers.h @@ -83,7 +83,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 5977162df445c..ac7eb8060e032 100644 --- a/clang/lib/AST/ByteCode/InterpState.h +++ b/clang/lib/AST/ByteCode/InterpState.h @@ -121,6 +121,27 @@ class InterpState final : public State { return reinterpret_cast<const CXXRecordDecl **>( this->allocate(Length * sizeof(CXXRecordDecl *))); } + PointerPathEntry *allocPointerPath(unsigned Length, + const PointerPathEntry *OldPP) { + assert(Length != 0); + auto *PP = reinterpret_cast<PointerPathEntry *>( + this->allocate(Length * sizeof(PointerPathEntry))); + if (OldPP) + std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * Length); + return PP; + } + /// Allocate a new pointer path of Length \c NewLength. + /// NewLength - 1 elements are copied form \c OldPP. + PointerPathEntry *extendPointerPath(unsigned NewLength, + const PointerPathEntry *OldPP, + PointerPathEntry NewEntry) { + auto *PP = reinterpret_cast<PointerPathEntry *>( + this->allocate(NewLength * sizeof(PointerPathEntry))); + if (OldPP) + std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * (NewLength - 1)); + PP[NewLength - 1] = NewEntry; + return PP; + } /// 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 a8e46f5c68a17..4be5495a7ed25 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -559,6 +559,7 @@ class LoadOpcode : Opcode { def Load : LoadOpcode {} // [Pointer] -> [Value] def LoadPop : LoadOpcode {} +def LoadPopL : Opcode {} class StoreOpcode : Opcode { let Types = [AllTypeClass]; @@ -612,6 +613,11 @@ def AddOffset : Opcode { let Types = [IntegralTypeClass]; let HasGroup = 1; } + +def GetOpaquePtr : SuccessOpcode { + let Args = [ArgValueDecl, ArgBool]; +} + // [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 0e16c34fcbdc8..81fa2beaedb24 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -25,6 +25,14 @@ using namespace clang; using namespace clang::interp; +// Helper to check if a Type can be passed to +// ASTContext::getRecordLayout(). +static bool validType(QualType T) { + if (const RecordDecl *RD = T->getAsRecordDecl()) + return ASTContext::hasLayout(RD); + return true; +} + Pointer::Pointer(Block *Pointee) : Pointer(Pointee, Pointee->getMetadataSize(), Pointee->getMetadataSize()) { } @@ -62,6 +70,9 @@ Pointer::Pointer(const Pointer &P) case Storage::String: Str = P.Str; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } } @@ -84,6 +95,9 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) { case Storage::String: Str = P.Str; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } } @@ -137,6 +151,9 @@ Pointer &Pointer::operator=(const Pointer &P) { case Storage::String: Str = P.Str; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } return *this; } @@ -180,15 +197,13 @@ Pointer &Pointer::operator=(Pointer &&P) { case Storage::String: Str = P.Str; break; + case Storage::Opaque: + Opaque = P.Opaque; + break; } return *this; } -static bool validRecordDecl(const RecordDecl *D) { - D = D->getDefinition(); - return D && !D->isInvalidDecl() && D->isCompleteDefinition(); -} - APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { llvm::SmallVector<APValue::LValuePathEntry, 5> Path; @@ -227,6 +242,9 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { return APValue(APValue::LValueBase(Str.Base), CharUnits::fromQuantity(Offset * elemSize()), Path, /*OnePastTheEnd=*/false, /*IsNull=*/false); + case Storage::Opaque: + return APValue(APValue::LValueBase(Opaque.Base), CharUnits::Zero(), Path, + /*IsOnePastEnd=*/Opaque.isOnePastEnd(), /*IsNullPtr=*/false); } assert(isBlockPointer()); @@ -249,8 +267,10 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { CharUnits Offset = CharUnits::Zero(); auto getFieldOffset = [&](const FieldDecl *FD) -> std::optional<CharUnits> { - if (!validRecordDecl(FD->getParent())) + if (!ASTContext::hasLayout(FD->getParent())) return std::nullopt; + // This shouldn't happen, but if it does, don't crash inside + // getASTRecordLayout. const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent()); unsigned FieldIndex = FD->getFieldIndex(); return ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex)); @@ -324,7 +344,7 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { Ptr = Ptr.getBase(); const Record *BaseRecord = Ptr.getRecord(); - if (!validRecordDecl(BaseRecord->getDecl())) + if (!ASTContext::hasLayout(BaseRecord->getDecl())) return APValue(); const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout( @@ -392,6 +412,13 @@ void Pointer::print(llvm::raw_ostream &OS) const { OS << "(String) { " << (const void *)Str.getLiteral() << ' '; Str.getLiteral()->outputString(OS); OS << ". ID: " << Str.ID << " + " << Offset << "}"; + break; + case Storage::Opaque: + OS << "(Opaque) { Base: " << Opaque.Base << ", " + << Opaque.FieldType.getPointer() << " Length: " << Opaque.PathLength + << ". PastEnd: " << Opaque.isOnePastEnd(); + OS << "} + " << Offset; + break; } } @@ -418,14 +445,13 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const { return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset; case Storage::String: return reinterpret_cast<uintptr_t>(Str.getLiteral()) + Offset; + case Storage::Opaque: + return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset; } auto getTypeSize = [&](QualType T) -> std::optional<size_t> { - if (const RecordType *RT = T->getAs<RecordType>()) { - // We cannot get the type size of a forward declaration. - if (!RT->getDecl()->getDefinition()) - return std::nullopt; - } + if (!validType(T)) + return std::nullopt; return ASTCtx.getTypeSizeInChars(T).getQuantity(); }; @@ -500,14 +526,13 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const { return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset; case Storage::String: return Offset * Str.getLiteral()->getCharByteWidth(); + case Storage::Opaque: + return Opaque.computeLayoutOffset(ASTCtx); } auto getTypeSize = [&](QualType T) -> std::optional<size_t> { - if (const RecordType *RT = T->getAs<RecordType>()) { - // We cannot get the type size of a forward declaration. - if (!RT->getDecl()->getDefinition()) - return std::nullopt; - } + if (!validType(T)) + return std::nullopt; return ASTCtx.getTypeSizeInChars(T).getQuantity(); }; @@ -1184,6 +1209,8 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx, const VarDecl *Pointer::getRootVarDecl() const { if (isBlockPointer()) return getDeclDesc()->asVarDecl(); + if (isOpaquePointer()) + return dyn_cast<VarDecl>(Opaque.Base); return nullptr; } @@ -1252,3 +1279,172 @@ IntPointer IntPointer::baseCast(const interp::Context &Ctx, std::nullopt, RD, false); return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()}; } + +std::optional<size_t> +OpaquePointer::computeLayoutOffset(const ASTContext &ASTCtx) const { + size_t Offset = 0; + QualType CurType = getObjectType(); + for (const PointerPathEntry &Entry : path()) { + switch (Entry.Kind) { + case PointerPathEntry::Base: { + const RecordDecl *RD = CurType->getAsRecordDecl(); + if (!ASTContext::hasLayout(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 (!ASTContext::hasLayout(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: + case PointerPathEntry::NegativeArray: { + bool Add = (Entry.Kind == PointerPathEntry::Array); + uint64_t Index = Entry.Index; + if (!CurType->isArrayType()) { + if (Add) + Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity(); + else + Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity(); + continue; + } + const ArrayType *AT = CurType->getAsArrayTypeUnsafe(); + assert(AT); + QualType ElemTy = AT->getElementType(); + if (!validType(ElemTy) || isa<VariableArrayType>(AT)) + return std::nullopt; + if (Add) + Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity(); + else + Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity(); + CurType = AT->getElementType(); + } break; + } + } + + return Offset; +} + +QualType OpaquePointer::getSurroundingArray() const { + if (PathLength == 0) + return getObjectType(); + if (Path[PathLength - 1].Kind != PointerPathEntry::Array) + return getFieldType(); + + assert(Path[PathLength - 1].Kind == PointerPathEntry::Array); + assert(isArrayElement()); + + QualType CurType = getObjectType(); + for (const PointerPathEntry &Entry : path().drop_back(1)) { + switch (Entry.Kind) { + case PointerPathEntry::Base: + CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType( + Entry.RD.getPointer()); + break; + case PointerPathEntry::Field: + CurType = Entry.FD->getType(); + break; + case PointerPathEntry::Array: + case PointerPathEntry::NegativeArray: + if (!CurType->isArrayType()) + break; + CurType = CurType->getAsArrayTypeUnsafe()->getElementType(); + } + } + return CurType; +} + +/// Check if the pointer has offset 0. +// As an optimization, don't actually compute the offset. +bool OpaquePointer::isRoot() const { + QualType CurType = getObjectType(); + for (const PointerPathEntry &Entry : path()) { + switch (Entry.Kind) { + case PointerPathEntry::Base: + if (Entry.RD.getInt()) + return false; + CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType( + Entry.RD.getPointer()); + break; + case PointerPathEntry::Field: + if (!Entry.FD->getParent()->isUnion() && Entry.FD->getFieldIndex() != 0) + return false; + CurType = Entry.FD->getType(); + break; + case PointerPathEntry::Array: + if (Entry.Index != 0) + return false; + if (!CurType->isArrayType()) + continue; + CurType = CurType->getAsArrayTypeUnsafe()->getElementType(); + break; + case PointerPathEntry::NegativeArray: + return false; + } + } + return true; +} + +bool OpaquePointer::isUnknownSizeArray() const { + QualType FieldType = getFieldType(); + + if (isArrayElement()) + FieldType = getSurroundingArray(); + + bool Result = false; + // If the field type is an IncompleteArrayType, we still need to check the + // base to see if this array is a flexible array member _and_ has actually + // been initialized by data we know the size of. + if (isa<IncompleteArrayType>(FieldType)) { + const VarDecl *Base = cast<VarDecl>(this->Base); + if (!Base || !Base->getType()->isRecordType() || !Base->hasInit()) + Result = true; + else + Result = !Base->hasFlexibleArrayInit(Base->getASTContext()); + } else if (isa<VariableArrayType>(FieldType)) + Result = true; + + return Result; +} + +/// This is used in Pointer::isOnePastEnd(). We cannot read from such pointers. +/// We can of course never read from opaque pointers anyway but we diagnose +/// one-past-the-end pointers diff erently. +/// +/// In contrast, OpaquePointer::isOnePastEnd() only uses the past-end bit. That +/// is used for the APValue conversion. +bool OpaquePointer::isOnePastEndOrElementPastEnd() const { + if (isOnePastEnd()) + return true; + + if (PathLength == 0) + return false; + + if (Path[PathLength - 1].Kind != PointerPathEntry::Array) + return false; + + QualType ArrTy = getSurroundingArray(); + if (!ArrTy->isArrayType()) + return false; + // FIXME: Flexible array members? + if (const auto *CAT = + dyn_cast<ConstantArrayType>(ArrTy->getAsArrayTypeUnsafe())) { + if (Path[PathLength - 1].Index >= CAT->getZExtSize()) + return true; + } + + return false; +} diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index a280d10a8ecee..fd09b4908f0b5 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -392,7 +392,111 @@ struct StringPointer { } }; -enum class Storage { Int, Block, Fn, Typeid, String }; +struct PointerPathEntry { + enum { Base, Field, Array, NegativeArray } Kind; + union { + uint64_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 negativeArray(int64_t Index) { + PointerPathEntry E; + E.Kind = NegativeArray; + 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/IsConstexprUnknown bits. + llvm::PointerIntPair<const Type *, 2, unsigned> FieldType = {}; + const PointerPathEntry *Path = nullptr; + unsigned PathLength = 0; + + ArrayRef<PointerPathEntry> path() const { return ArrayRef(Path, PathLength); } + + OpaquePointer + withFieldType(const Type *FieldTy, + std::optional<bool> PastEnd = std::nullopt) const { + unsigned NewBitFieldValue = FieldType.getInt(); + if (PastEnd) + NewBitFieldValue = + (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd); + return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength}; + } + + OpaquePointer withPath(const PointerPathEntry *Path, unsigned PathLength, + const Type *FieldTy, + std::optional<bool> PastEnd = std::nullopt) const { + unsigned NewBitFieldValue = FieldType.getInt(); + if (PastEnd) + NewBitFieldValue = + (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd); + return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength}; + } + + OpaquePointer withPastEnd(bool PastEnd) const { + return OpaquePointer{Base, + {FieldType.getPointer(), + FieldType.getInt() | static_cast<unsigned>(PastEnd)}, + 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); + } + + bool isArrayElement() const { + return PathLength != 0 && + Path[PathLength - 1].Kind == PointerPathEntry::Array; + } + + std::optional<size_t> computeLayoutOffset(const ASTContext &ASTCtx) const; + /// If this is pointing to an array element, return the array. + QualType getSurroundingArray() const; + + bool isOnePastEnd() const { return FieldType.getInt() & 1u; } + bool isOnePastEndOrElementPastEnd() const; + bool isConstexprUnknown() const { return FieldType.getInt() & 2u; } + bool isUnknownSizeArray() const; + bool isRoot() const; +}; +struct OpaqueTag {}; + +enum class Storage { Int, Block, Fn, Typeid, String, Opaque }; /// A pointer to a memory block, live or dead. /// @@ -446,6 +550,16 @@ class Pointer { : Offset(0), StorageKind(Storage::String), Str{Base, Id} {} Pointer(StringPointer Str, uint64_t Offset = 0) : Offset(Offset), StorageKind(Storage::String), Str(Str) {} + Pointer(const ValueDecl *Base, bool ConstexprUnknown = false) + : Offset(0), StorageKind(Storage::Opaque) { + Opaque.Base = Base; + Opaque.FieldType = {Base->getType().getTypePtr(), + ConstexprUnknown ? 2u : 0u}; + Opaque.Path = nullptr; + Opaque.PathLength = 0; + } + Pointer(OpaquePointer OP, uint64_t Offset = 0) + : Offset(Offset), StorageKind(Storage::Opaque), Opaque(OP) {} Pointer(Block *Pointee, unsigned Base, uint64_t Offset); explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {} @@ -549,6 +663,7 @@ class Pointer { return !Fn.Func; case Storage::Typeid: case Storage::String: + case Storage::Opaque: return false; } llvm_unreachable("Unknown clang::interp::Storage enum"); @@ -597,7 +712,7 @@ class Pointer { /// Accessors for information about the innermost field. const Descriptor *getFieldDesc() const { - if (isIntegralPointer()) + if (!isBlockPointer()) return nullptr; if (isRoot()) @@ -623,6 +738,8 @@ class Pointer { ->getAsArrayTypeUnsafe() ->getElementType(); return Str.getLiteral()->getType(); + case Storage::Opaque: + return Opaque.getFieldType(); } llvm_unreachable("Unhandled StorageKind"); } @@ -681,9 +798,11 @@ class Pointer { } /// Checks if the structure is an array of unknown size. bool isUnknownSizeArray() const { - if (!isBlockPointer()) - return false; - return getFieldDesc()->isUnknownSizeArray(); + if (isBlockPointer()) + return getFieldDesc()->isUnknownSizeArray(); + if (isOpaquePointer()) + return Opaque.isUnknownSizeArray(); + return false; } /// Checks if the pointer points to an array. bool isArrayElement() const { @@ -694,9 +813,13 @@ class Pointer { } /// Pointer points directly to a block. bool isRoot() const { - if (isZero() || !isBlockPointer()) + if (isZero()) return true; - return view().isRoot(); + if (isBlockPointer()) + return view().isRoot(); + if (isOpaquePointer()) + return Opaque.isRoot(); + return true; } /// If this pointer has an InlineDescriptor we can use to initialize. bool canBeInitialized() const { @@ -726,12 +849,17 @@ class Pointer { assert(isStringPointer()); return Str; } + [[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 isStringPointer() const { return StorageKind == Storage::String; } + bool isOpaquePointer() const { return StorageKind == Storage::Opaque; } /// Returns the record descriptor of a class. const Record *getRecord() const { @@ -853,6 +981,8 @@ class Pointer { return Int.Value + Offset; if (isTypeidPointer()) return reinterpret_cast<uintptr_t>(Typeid.TypePtr) + Offset; + if (isOpaquePointer()) + return Offset; if (isOnePastEnd()) return PtrView::PastEndMark; return Offset; @@ -897,6 +1027,9 @@ class Pointer { bool isOnePastEnd() const { if (isStringPointer()) return Offset == (Str.getLiteral()->getLength() + 1); + if (isOpaquePointer()) + return Opaque.isOnePastEndOrElementPastEnd(); + if (!isBlockPointer()) return false; @@ -923,6 +1056,8 @@ class Pointer { bool isZeroSizeArray() const { if (isFunctionPointer()) return false; + if (isOpaquePointer()) + return false; // FIXME: Can actually happen I think? if (const auto *Desc = getFieldDesc()) return Desc->isZeroSizeArray(); return false; @@ -1029,9 +1164,11 @@ class Pointer { } bool isConstexprUnknown() const { - if (!isBlockPointer()) - return false; - return getDeclDesc()->IsConstexprUnknown; + if (isOpaquePointer()) + return Opaque.isConstexprUnknown(); + if (isBlockPointer()) + return getDeclDesc()->IsConstexprUnknown; + return false; } /// Whether this block can be read from at all. This is only true for @@ -1209,6 +1346,7 @@ class Pointer { FunctionPointer Fn; TypeidPointer Typeid; StringPointer Str; + OpaquePointer Opaque; }; }; diff --git a/clang/lib/AST/ByteCode/Program.cpp b/clang/lib/AST/ByteCode/Program.cpp index fdd9d5506160d..137ca8c03fad5 100644 --- a/clang/lib/AST/ByteCode/Program.cpp +++ b/clang/lib/AST/ByteCode/Program.cpp @@ -79,9 +79,11 @@ unsigned Program::getOrCreateDummy(DeclOrExpr D, bool IsConstexprUnknown) { const auto *VD = D.asValueDecl(); IsWeak = VD->isWeak(); QT = VD->getType(); - if (QT->isPointerOrReferenceType()) + + if (QT->isReferenceType()) QT = QT->getPointeeType(); } + assert(!QT.isNull()); Descriptor *Desc; diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index 9c3c118b22fed..c703151f78772 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -79,6 +79,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/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index cdf2a5697528e..0eee03dce57ab 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -110,4 +110,6 @@ std::optional<llvm::APFloat> EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B, std::optional<llvm::APSInt> RoundingMode, bool IsMin); +const Expr *ignorePointerCastsAndParens(const Expr *E); + #endif diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index fcce51fccb21a..43d49da015d4e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -16557,7 +16557,7 @@ static QualType getObjectType(APValue::LValueBase B) { /// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo` /// /// Always returns an RValue with a pointer representation. -static const Expr *ignorePointerCastsAndParens(const Expr *E) { +const Expr *ignorePointerCastsAndParens(const Expr *E) { assert(E->isPRValue() && E->getType()->hasPointerRepresentation()); const Expr *NoParens = E->IgnoreParens(); @@ -23107,7 +23107,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..445a95f5487b9 100644 --- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c +++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c @@ -45,3 +45,92 @@ 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); +} + +// CHECK-LABEL: @vlas +void vlas(int size) { + char z[size]; + + int gi; + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(z, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false) + gi = __builtin_object_size(z, 1); + // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 false) + gi = __builtin_object_size(z, 2); + // CHECK: store i32 0 + gi = __builtin_object_size(z, 3); +} + +struct hh { + char s1[0]; + char * s2; +}; +// CHECK-LABEL: @f17 +void f17(void) { + struct hh h0; + int gi; + // CHECK: store i32 8 + gi = __builtin_object_size(h0.s1, 0); +} 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/codegen.c b/clang/test/AST/ByteCode/codegen.c index 7f877bb4240fe..598e8f130ddab 100644 --- a/clang/test/AST/ByteCode/codegen.c +++ b/clang/test/AST/ByteCode/codegen.c @@ -27,3 +27,25 @@ int test(void) { return i23; } // CHECK: @test.i23 = internal global i32 4, align 4 + +union reg +{ + unsigned char b[2][20]; + unsigned short w[2]; + unsigned int d; +}; +struct cpu +{ + union reg pc; +}; +extern struct cpu cpu; +struct svar +{ + void *ptr; +}; + +// CHECK: @svars2 = {{(dso_local )?}}global [1 x %struct.svar] [%struct.svar { ptr getelementptr (i8, ptr @cpu, i64 1) }] +struct svar svars2[] = +{ + { &((cpu.pc).b[0][1]) } +}; 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/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp index 3b24b166e39a7..97eab655032b6 100644 --- a/clang/test/AST/ByteCode/literals.cpp +++ b/clang/test/AST/ByteCode/literals.cpp @@ -30,6 +30,9 @@ static_assert(!__objc_no, ""); static_assert((long long)0x00000000FFFF0000 == 4294901760, ""); +int kk[3]; +static_assert(kk + 3 == &kk[3], ""); + constexpr bool b = number; static_assert(b, ""); constexpr int one = true; diff --git a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c index 3dbbc7807c691..96368bb0e07b9 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..fd7408c6f4c30 100644 --- a/clang/test/Sema/enable_if.c +++ b/clang/test/Sema/enable_if.c @@ -1,5 +1,10 @@ // RUN: %clang_cc1 %s -verify // RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 %s -verify -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | 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; diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp index f8a1743521415..595d0325be12f 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
