diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -4268,6 +4268,19 @@ return field_begin() == field_end(); } + FieldDecl *getLastField() { + FieldDecl *FD = nullptr; + for (FieldDecl *Field : fields()) + FD = Field; + return FD; + } + const FieldDecl *getLastField() const { + const FieldDecl *FD = nullptr; + for (const FieldDecl *Field : fields()) + FD = Field; + return FD; + } + /// Note that the definition of this type is now complete. virtual void completeDefinition(); diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -18,6 +18,7 @@ #include "clang/AST/DeclarationName.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" #include "llvm/ADT/ArrayRef.h" @@ -477,6 +478,12 @@ // Return true if this is a FileContext Decl. bool isFileContextDecl() const; + /// Whether it resembles a flexible array member. + static bool isFlexibleArrayMemberLike( + ASTContext &Context, const Decl *D, QualType Ty, + LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel, + bool IgnoreTemplateOrMacroSubstitution); + ASTContext &getASTContext() const LLVM_READONLY; /// Helper to get the language options from the ASTContext. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4234,3 +4234,18 @@ let Subjects = SubjectList<[TypedefName], ErrorDiag>; let Documentation = [Undocumented]; } + +def CountedBy : InheritableAttr { + let Spellings = [Clang<"counted_by">]; + let Subjects = SubjectList<[Field]>; + let Args = [IdentifierArgument<"CountedByField">]; + let Documentation = [CountedByDocs]; + let LangOpts = [COnly]; + code AdditionalMembers = [{ + private: + SourceRange countedByFieldLoc; + public: + SourceRange getCountedByFieldLoc(void) const { return countedByFieldLoc; } + void setCountedByFieldLoc(SourceRange Loc) { countedByFieldLoc = Loc; } + }]; +} diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -7193,3 +7193,69 @@ (if any) of these ``cleanup`` callback functions. }]; } + +def CountedByDocs : Documentation { + let Category = DocCatField; + let Content = [{ +Clang supports the ``counted_by`` attribute for the flexible array member of a +structure. The argument for the attribute is the name of a field member in the +same structure holding the count of elements in the flexible array. This +information can be used to improve the results of array bound sanitizer and the +``__builtin_dynamic_object_size``. + +For example, the following code: + +.. code-block:: c + + struct bar; + + struct foo { + size_t count; + char other; + struct bar *array[] __attribute__((counted_by(count))); + }; + +specifies that the flexible array member ``array`` has the number of elements +allocated for it stored in ``count``. This establishes a relationship between +``array`` and ``count``. Specifically, ``p->array`` must have at least +``p->count`` number of elements available. It's the user's responsibility to +ensure that this relationship is maintained through changes to the structure. + +In the following example, the allocated array erroneously has fewer elements +than what's specified by ``p->count``. This would result in an out-of-bounds +access not being detected. + +.. code-block:: c + + #define SIZE_INCR 42 + + struct foo *f; + + void foo_alloc(size_t count) { + f = malloc(MAX(sizeof(struct foo), + offsetof(struct foo, array[0]) + count * sizeof(struct bar *))); + f->count = count + SIZE_INCR; + } + +The next example updates ``f->count``, breaking the relationship requirement +that ``f->array`` must have at least ``f->count`` number of elements available: + +.. code-block:: c + + #define SIZE_INCR 42 + + struct foo *f; + + void foo_alloc(size_t count) { + f = malloc(MAX(sizeof(struct foo), + offsetof(struct foo, array[0]) + count * sizeof(struct bar *))); + f->count = count; + } + + void use_foo(int index) { + f->count += SIZE_INCR + 1; /* 'count' is now larger than the number of elements of 'array'. */ + f->array[index] = 0; /* the sanitizer can't properly check if this is an out-of-bounds access. */ + } + + }]; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6365,6 +6365,15 @@ "field %0 can overwrite instance variable %1 with variable sized type %2" " in superclass %3">, InGroup; +def warn_flexible_array_counted_by_attr_field_not_found : Warning< + "counted_by field %0 not found">; +def err_flexible_array_counted_by_attr_refers_to_self : Error< + "counted_by field %0 cannot refer to the flexible array">; +def err_flexible_array_counted_by_attr_field_not_integral : Error< + "counted_by field %0 is not an integral type">; +def note_flexible_array_counted_by_attr_field : Note< + "counted_by field %0 declared here">; + let CategoryName = "ARC Semantic Issue" in { // ARC-mode diagnostics. diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8975,6 +8975,10 @@ public: AttrImporter(ASTImporter &I) : Importer(I), NImporter(I) {} + // Useful for accessing the imported attribute. + template T *getAttrAs() { return cast(ToAttr); } + template const T *getAttrAs() const { return cast(ToAttr); } + // Create an "importer" for an attribute parameter. // Result of the 'value()' of that object is to be passed to the function // 'importAttr', in the order that is expected by the attribute class. @@ -9181,6 +9185,15 @@ From->args_size()); break; } + case attr::CountedBy: { + AI.cloneAttr(FromAttr); + const auto *ECA = cast(FromAttr); + Expected SR = Import(ECA->getCountedByFieldLoc()).get(); + if (!SR) + return SR.takeError(); + AI.getAttrAs()->setCountedByFieldLoc(SR.get()); + break; + } default: { // The default branch works for attributes that have no arguments to import. diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -29,7 +29,6 @@ #include "clang/AST/Type.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" -#include "clang/Basic/LangOptions.h" #include "clang/Basic/Module.h" #include "clang/Basic/ObjCRuntime.h" #include "clang/Basic/PartialDiagnostic.h" @@ -411,6 +410,80 @@ return DC && DC->isFileContext(); } +bool Decl::isFlexibleArrayMemberLike( + ASTContext &Ctx, const Decl *D, QualType Ty, + LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel, + bool IgnoreTemplateOrMacroSubstitution) { + // For compatibility with existing code, we treat arrays of length 0 or + // 1 as flexible array members. + const auto *CAT = Ctx.getAsConstantArrayType(Ty); + if (CAT) { + using FAMKind = LangOptions::StrictFlexArraysLevelKind; + + llvm::APInt Size = CAT->getSize(); + FAMKind StrictFlexArraysLevel = + Ctx.getLangOpts().getStrictFlexArraysLevel(); + + if (StrictFlexArraysLevel == FAMKind::IncompleteOnly) + return false; + + // GCC extension, only allowed to represent a FAM. + if (Size.isZero()) + return true; + + if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete && Size.uge(1)) + return false; + + if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete && Size.uge(2)) + return false; + } else if (!Ctx.getAsIncompleteArrayType(Ty)) { + return false; + } + + if (const auto *OID = dyn_cast_if_present(D)) + return OID->getNextIvar() == nullptr; + + const auto *FD = dyn_cast_if_present(D); + if (!FD) + return false; + + if (CAT) { + // GCC treats an array memeber of a union as an FAM if the size is one or + // zero. + llvm::APInt Size = CAT->getSize(); + if (FD->getParent()->isUnion() && (Size.isZero() || Size.isOne())) + return true; + } + + // Don't consider sizes resulting from macro expansions or template argument + // substitution to form C89 tail-padded arrays. + if (IgnoreTemplateOrMacroSubstitution) { + TypeSourceInfo *TInfo = FD->getTypeSourceInfo(); + while (TInfo) { + TypeLoc TL = TInfo->getTypeLoc(); + + // Look through typedefs. + if (TypedefTypeLoc TTL = TL.getAsAdjusted()) { + const TypedefNameDecl *TDL = TTL.getTypedefNameDecl(); + TInfo = TDL->getTypeSourceInfo(); + continue; + } + + if (auto CTL = TL.getAs()) { + const Expr *SizeExpr = dyn_cast(CTL.getSizeExpr()); + if (!SizeExpr || SizeExpr->getExprLoc().isMacroID()) + return false; + } + + break; + } + } + + RecordDecl::field_iterator FI( + DeclContext::decl_iterator(const_cast(FD))); + return ++FI == FD->getParent()->field_end(); +} + TranslationUnitDecl *Decl::getTranslationUnitDecl() { if (auto *TUD = dyn_cast(this)) return TUD; diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -205,85 +205,22 @@ } bool Expr::isFlexibleArrayMemberLike( - ASTContext &Context, + ASTContext &Ctx, LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel, bool IgnoreTemplateOrMacroSubstitution) const { - - // For compatibility with existing code, we treat arrays of length 0 or - // 1 as flexible array members. - const auto *CAT = Context.getAsConstantArrayType(getType()); - if (CAT) { - llvm::APInt Size = CAT->getSize(); - - using FAMKind = LangOptions::StrictFlexArraysLevelKind; - - if (StrictFlexArraysLevel == FAMKind::IncompleteOnly) - return false; - - // GCC extension, only allowed to represent a FAM. - if (Size == 0) - return true; - - if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete && Size.uge(1)) - return false; - - if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete && Size.uge(2)) - return false; - } else if (!Context.getAsIncompleteArrayType(getType())) - return false; - const Expr *E = IgnoreParens(); + const Decl *D = nullptr; - const NamedDecl *ND = nullptr; - if (const auto *DRE = dyn_cast(E)) - ND = DRE->getDecl(); - else if (const auto *ME = dyn_cast(E)) - ND = ME->getMemberDecl(); + if (const auto *ME = dyn_cast(E)) + D = ME->getMemberDecl(); + else if (const auto *DRE = dyn_cast(E)) + D = DRE->getDecl(); else if (const auto *IRE = dyn_cast(E)) - return IRE->getDecl()->getNextIvar() == nullptr; + D = IRE->getDecl(); - if (!ND) - return false; - - // A flexible array member must be the last member in the class. - // FIXME: If the base type of the member expr is not FD->getParent(), - // this should not be treated as a flexible array member access. - if (const auto *FD = dyn_cast(ND)) { - // GCC treats an array memeber of a union as an FAM if the size is one or - // zero. - if (CAT) { - llvm::APInt Size = CAT->getSize(); - if (FD->getParent()->isUnion() && (Size.isZero() || Size.isOne())) - return true; - } - - // Don't consider sizes resulting from macro expansions or template argument - // substitution to form C89 tail-padded arrays. - if (IgnoreTemplateOrMacroSubstitution) { - TypeSourceInfo *TInfo = FD->getTypeSourceInfo(); - while (TInfo) { - TypeLoc TL = TInfo->getTypeLoc(); - // Look through typedefs. - if (TypedefTypeLoc TTL = TL.getAsAdjusted()) { - const TypedefNameDecl *TDL = TTL.getTypedefNameDecl(); - TInfo = TDL->getTypeSourceInfo(); - continue; - } - if (ConstantArrayTypeLoc CTL = TL.getAs()) { - const Expr *SizeExpr = dyn_cast(CTL.getSizeExpr()); - if (!SizeExpr || SizeExpr->getExprLoc().isMacroID()) - return false; - } - break; - } - } - - RecordDecl::field_iterator FI( - DeclContext::decl_iterator(const_cast(FD))); - return ++FI == FD->getParent()->field_end(); - } - - return false; + return Decl::isFlexibleArrayMemberLike(Ctx, D, E->getType(), + StrictFlexArraysLevel, + IgnoreTemplateOrMacroSubstitution); } const ValueDecl * diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -853,6 +853,57 @@ } } + if (IsDynamic) { + const LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel = + getLangOpts().getStrictFlexArraysLevel(); + const Expr *Base = E->IgnoreParenImpCasts(); + + if (FieldDecl *FD = FindCountedByField(Base, StrictFlexArraysLevel)) { + const auto *ME = dyn_cast(Base); + llvm::Value *ObjectSize = nullptr; + + if (!ME) { + const auto *DRE = dyn_cast(Base); + ValueDecl *VD = nullptr; + + ObjectSize = ConstantInt::get( + ResType, + getContext().getTypeSize(DRE->getType()->getPointeeType()) / 8, + true); + + if (auto *RD = DRE->getType()->getPointeeType()->getAsRecordDecl()) + VD = RD->getLastField(); + + Expr *ICE = ImplicitCastExpr::Create( + getContext(), DRE->getType(), CK_LValueToRValue, + const_cast(cast(DRE)), nullptr, VK_PRValue, + FPOptionsOverride()); + ME = MemberExpr::CreateImplicit(getContext(), ICE, true, VD, + VD->getType(), VK_LValue, OK_Ordinary); + } + + // At this point, we know that \p ME is a flexible array member. + const auto *ArrayTy = dyn_cast(ME->getType()); + unsigned Size = getContext().getTypeSize(ArrayTy->getElementType()); + + llvm::Value *CountField = + EmitAnyExprToTemp(MemberExpr::CreateImplicit( + getContext(), const_cast(ME->getBase()), + ME->isArrow(), FD, FD->getType(), VK_LValue, + OK_Ordinary)) + .getScalarVal(); + + llvm::Value *Mul = Builder.CreateMul( + CountField, llvm::ConstantInt::get(CountField->getType(), Size / 8)); + Mul = Builder.CreateZExtOrTrunc(Mul, ResType); + + if (ObjectSize) + return Builder.CreateAdd(ObjectSize, Mul); + + return Mul; + } + } + // LLVM can't handle Type=3 appropriately, and __builtin_object_size shouldn't // evaluate E for side-effects. In either case, we shouldn't lower to // @llvm.objectsize. diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -30,6 +30,7 @@ #include "clang/Basic/CodeGenOptions.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/Intrinsics.h" @@ -925,16 +926,31 @@ if (CE->getCastKind() == CK_ArrayToPointerDecay && !CE->getSubExpr()->isFlexibleArrayMemberLike(CGF.getContext(), StrictFlexArraysLevel)) { + CodeGenFunction::SanitizerScope SanScope(&CGF); + IndexedType = CE->getSubExpr()->getType(); const ArrayType *AT = IndexedType->castAsArrayTypeUnsafe(); if (const auto *CAT = dyn_cast(AT)) return CGF.Builder.getInt(CAT->getSize()); - else if (const auto *VAT = dyn_cast(AT)) + + if (const auto *VAT = dyn_cast(AT)) return CGF.getVLASize(VAT).NumElts; // Ignore pass_object_size here. It's not applicable on decayed pointers. } + + if (FieldDecl *FD = CGF.FindCountedByField(Base, StrictFlexArraysLevel)) { + const auto *ME = dyn_cast(CE->getSubExpr()); + IndexedType = Base->getType(); + return CGF + .EmitAnyExprToTemp(MemberExpr::CreateImplicit( + CGF.getContext(), const_cast(ME->getBase()), + ME->isArrow(), FD, FD->getType(), VK_LValue, OK_Ordinary)) + .getScalarVal(); + } } + CodeGenFunction::SanitizerScope SanScope(&CGF); + QualType EltTy{Base->getType()->getPointeeOrArrayElementType(), 0}; if (llvm::Value *POS = CGF.LoadPassedObjectSize(Base, EltTy)) { IndexedType = Base->getType(); @@ -944,13 +960,53 @@ return nullptr; } +FieldDecl *CodeGenFunction::FindCountedByField( + const Expr *Base, + LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel) { + const ValueDecl *VD = nullptr; + + Base = Base->IgnoreParenImpCasts(); + + if (const auto *ME = dyn_cast(Base)) { + VD = dyn_cast(ME->getMemberDecl()); + } else if (const auto *DRE = dyn_cast(Base)) { + // Pointing to the full structure. + VD = dyn_cast(DRE->getDecl()); + + QualType Ty = VD->getType(); + if (Ty->isPointerType()) + Ty = Ty->getPointeeType(); + + if (const auto *RD = Ty->getAsRecordDecl()) + VD = RD->getLastField(); + } else if (const auto *CE = dyn_cast(Base)) { + if (const auto *ME = dyn_cast(CE->getSubExpr())) + VD = dyn_cast(ME->getMemberDecl()); + } + + const auto *FD = dyn_cast_if_present(VD); + if (!FD || !FD->getParent() || + !Decl::isFlexibleArrayMemberLike(getContext(), FD, FD->getType(), + StrictFlexArraysLevel, true)) + return nullptr; + + const auto *CBA = FD->getAttr(); + if (!CBA) + return nullptr; + + StringRef FieldName = CBA->getCountedByField()->getName(); + auto It = + llvm::find_if(FD->getParent()->fields(), [&](const FieldDecl *Field) { + return FieldName == Field->getName(); + }); + return It != FD->getParent()->field_end() ? *It : nullptr; +} + void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base, llvm::Value *Index, QualType IndexType, bool Accessed) { assert(SanOpts.has(SanitizerKind::ArrayBounds) && "should not be called unless adding bounds checks"); - SanitizerScope SanScope(this); - const LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel = getLangOpts().getStrictFlexArraysLevel(); @@ -960,6 +1016,8 @@ if (!Bound) return; + SanitizerScope SanScope(this); + bool IndexSigned = IndexType->isSignedIntegerOrEnumerationType(); llvm::Value *IndexVal = Builder.CreateIntCast(Index, SizeTy, IndexSigned); llvm::Value *BoundVal = Builder.CreateIntCast(Bound, SizeTy, false); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3022,6 +3022,12 @@ void EmitBoundsCheck(const Expr *E, const Expr *Base, llvm::Value *Index, QualType IndexType, bool Accessed); + /// Find the FieldDecl specified in a FAM's "counted_by" attribute. Returns + /// \p nullptr if either the attribute or the field doesn't exist. + FieldDecl *FindCountedByField( + const Expr *Base, + LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel); + llvm::Value *EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV, bool isInc, bool isPre); ComplexPairTy EmitComplexPrePostIncDec(const UnaryOperator *E, LValue LV, diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -17994,6 +17994,56 @@ "Broken injected-class-name"); } +template +static const FieldDecl *FindFieldIf(const RecordDecl *RD, Functor &Pred) { + for (const Decl *D : RD->decls()) { + if (const auto *FD = dyn_cast(D)) + if (Pred(FD)) + return FD; + + if (const auto *SubRD = dyn_cast(D)) + if (const FieldDecl *FD = FindFieldIf(SubRD, Pred)) + return FD; + } + + return nullptr; +} + +static void CheckCountedByAttr(Sema &S, const RecordDecl *RD, + const FieldDecl *FD) { + const auto *ECA = FD->getAttr(); + + auto Pred = [&](const Decl *D) { + if (const auto *Field = dyn_cast(D)) + return Field->getName() == ECA->getCountedByField()->getName(); + return false; + }; + const FieldDecl *Field = FindFieldIf(RD, Pred); + + if (!Field) { + // The "counted_by" field needs to exist within the struct. + SourceRange SR = ECA->getCountedByFieldLoc(); + S.Diag(SR.getBegin(), + diag::warn_flexible_array_counted_by_attr_field_not_found) + << ECA->getCountedByField() << SR; + } else if (Field->hasAttr()) { + // The "counted_by" field can't point to the flexible array member. + SourceRange SR = ECA->getCountedByFieldLoc(); + S.Diag(SR.getBegin(), + diag::err_flexible_array_counted_by_attr_refers_to_self) + << ECA->getCountedByField() << SR; + } else if (!Field->getType()->isIntegerType()) { + // The "counted_by" field must have an integer type. + SourceRange SR = Field->getLocation(); + S.Diag(SR.getBegin(), + diag::err_flexible_array_counted_by_attr_field_not_integral) + << Field << SR; + SR = ECA->getCountedByFieldLoc(); + S.Diag(SR.getBegin(), diag::note_flexible_array_counted_by_attr_field) + << ECA->getCountedByField() << SR; + } +} + void Sema::ActOnTagFinishDefinition(Scope *S, Decl *TagD, SourceRange BraceRange) { AdjustDeclIfTemplate(TagD); @@ -18051,6 +18101,18 @@ [](const FieldDecl *FD) { return FD->isBitField(); })) Diag(BraceRange.getBegin(), diag::warn_pragma_align_not_xl_compatible); } + + // Check the "counted_by" attribute to ensure that the count field exists in + // the struct. + if (const auto *RD = dyn_cast(Tag)) { + auto Pred = [](const Decl *D) { + if (const auto *FD = dyn_cast(D)) + return FD->hasAttr(); + return false; + }; + if (const FieldDecl *FD = FindFieldIf(RD, Pred)) + CheckCountedByAttr(*this, RD, FD); + } } void Sema::ActOnObjCContainerFinishDefinition() { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8335,6 +8335,20 @@ D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL)); } +static void handleCountedByAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (!AL.isArgIdent(0)) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_type) + << AL << AANT_ArgumentIdentifier; + return; + } + + IdentifierLoc *IL = AL.getArgAsIdent(0); + CountedByAttr *CBA = + ::new (S.Context) CountedByAttr(S.Context, AL, IL->Ident); + CBA->setCountedByFieldLoc(IL->Loc); + D->addAttr(CBA); +} + static void handleFunctionReturnThunksAttr(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef KindStr; @@ -9281,6 +9295,10 @@ handleAvailableOnlyInDefaultEvalMethod(S, D, AL); break; + case ParsedAttr::AT_CountedBy: + handleCountedByAttr(S, D, AL); + break; + // Microsoft attributes: case ParsedAttr::AT_LayoutVersion: handleLayoutVersion(S, D, AL); diff --git a/clang/test/CodeGen/attr-counted-by.c b/clang/test/CodeGen/attr-counted-by.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-counted-by.c @@ -0,0 +1,114 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3 +// 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 %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 %s + +#if !__has_attribute(counted_by) +#error "has attribute broken" +#endif + +#define __counted_by(member) __attribute__((__counted_by__(member))) + +typedef long unsigned int size_t; + +struct annotated { + unsigned long flags; + int count; + int array[] __counted_by(count); +}; + +// SANITIZE-LABEL: define dso_local void @test1( +// SANITIZE-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { +// SANITIZE-NEXT: entry: +// SANITIZE-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1 +// SANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2:![0-9]+]] +// SANITIZE-NEXT: [[TMP1:%.*]] = sext i32 [[INDEX]] to i64, !nosanitize !6 +// SANITIZE-NEXT: [[TMP2:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6 +// SANITIZE-NEXT: [[TMP3:%.*]] = icmp ult i64 [[TMP1]], [[TMP2]], !nosanitize !6 +// SANITIZE-NEXT: br i1 [[TMP3]], label [[CONT7:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7:![0-9]+]], !nosanitize !6 +// SANITIZE: handler.out_of_bounds: +// SANITIZE-NEXT: [[TMP4:%.*]] = zext i32 [[INDEX]] to i64, !nosanitize !6 +// SANITIZE-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB2:[0-9]+]], i64 [[TMP4]]) #[[ATTR2:[0-9]+]], !nosanitize !6 +// SANITIZE-NEXT: unreachable, !nosanitize !6 +// SANITIZE: cont7: +// SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[TMP1]] +// SANITIZE-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]] +// SANITIZE-NEXT: ret void +// +// NO-SANITIZE-LABEL: define dso_local void @test1( +// NO-SANITIZE-SAME: ptr nocapture noundef writeonly [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { +// NO-SANITIZE-NEXT: entry: +// NO-SANITIZE-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64 +// NO-SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[IDXPROM]] +// NO-SANITIZE-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2:![0-9]+]] +// NO-SANITIZE-NEXT: ret void +// +void test1(struct annotated *p, int index, int val) { + p->array[index] = val; +} + +// SANITIZE-LABEL: define dso_local void @test2( +// SANITIZE-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] { +// SANITIZE-NEXT: entry: +// SANITIZE-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1 +// SANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]] +// SANITIZE-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6 +// SANITIZE-NEXT: [[TMP2:%.*]] = icmp ugt i64 [[TMP1]], [[INDEX]], !nosanitize !6 +// SANITIZE-NEXT: br i1 [[TMP2]], label [[CONT12:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7]], !nosanitize !6 +// SANITIZE: handler.out_of_bounds: +// SANITIZE-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB4:[0-9]+]], i64 [[INDEX]]) #[[ATTR2]], !nosanitize !6 +// SANITIZE-NEXT: unreachable, !nosanitize !6 +// SANITIZE: cont12: +// SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]] +// SANITIZE-NEXT: [[TMP3:%.*]] = shl i32 [[TMP0]], 2 +// SANITIZE-NEXT: store i32 [[TMP3]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]] +// SANITIZE-NEXT: ret void +// +// NO-SANITIZE-LABEL: define dso_local void @test2( +// NO-SANITIZE-SAME: ptr nocapture noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { +// NO-SANITIZE-NEXT: entry: +// NO-SANITIZE-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1 +// NO-SANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]] +// NO-SANITIZE-NEXT: [[TMP1:%.*]] = shl i32 [[TMP0]], 2 +// NO-SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]] +// NO-SANITIZE-NEXT: store i32 [[TMP1]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]] +// NO-SANITIZE-NEXT: ret void +// +void test2(struct annotated *p, size_t index) { + p->array[index] = __builtin_dynamic_object_size(p->array, 1); +} + +// This test differs from 'test2' by checking bdos on the whole array and not +// just the FAM. + +// SANITIZE-LABEL: define dso_local void @test3( +// SANITIZE-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] { +// SANITIZE-NEXT: entry: +// SANITIZE-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1 +// SANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]] +// SANITIZE-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6 +// SANITIZE-NEXT: [[TMP2:%.*]] = icmp ugt i64 [[TMP1]], [[INDEX]], !nosanitize !6 +// SANITIZE-NEXT: br i1 [[TMP2]], label [[CONT12:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7]], !nosanitize !6 +// SANITIZE: handler.out_of_bounds: +// SANITIZE-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB5:[0-9]+]], i64 [[INDEX]]) #[[ATTR2]], !nosanitize !6 +// SANITIZE-NEXT: unreachable, !nosanitize !6 +// SANITIZE: cont12: +// SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]] +// SANITIZE-NEXT: [[TMP3:%.*]] = shl i32 [[TMP0]], 2 +// SANITIZE-NEXT: [[CONV:%.*]] = add i32 [[TMP3]], 16 +// SANITIZE-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]] +// SANITIZE-NEXT: ret void +// +// NO-SANITIZE-LABEL: define dso_local void @test3( +// NO-SANITIZE-SAME: ptr nocapture noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1]] { +// NO-SANITIZE-NEXT: entry: +// NO-SANITIZE-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1 +// NO-SANITIZE-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]] +// NO-SANITIZE-NEXT: [[TMP1:%.*]] = shl i32 [[TMP0]], 2 +// NO-SANITIZE-NEXT: [[CONV:%.*]] = add i32 [[TMP1]], 16 +// NO-SANITIZE-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]] +// NO-SANITIZE-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]] +// NO-SANITIZE-NEXT: ret void +// +void test3(struct annotated *p, size_t index) { + p->array[index] = __builtin_dynamic_object_size(p, 1); +} diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -56,6 +56,7 @@ // CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record) // CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record) // CHECK-NEXT: Convergent (SubjectMatchRule_function) +// CHECK-NEXT: CountedBy (SubjectMatchRule_field) // CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: Destructor (SubjectMatchRule_function) diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (65): +CHECK: Warnings without flags (66): CHECK-NEXT: ext_expected_semi_decl_list CHECK-NEXT: ext_explicit_specialization_storage_class @@ -52,6 +52,7 @@ CHECK-NEXT: warn_fe_cc_log_diagnostics_failure CHECK-NEXT: warn_fe_cc_print_header_failure CHECK-NEXT: warn_fe_macro_contains_embedded_newline +CHECK-NEXT: warn_flexible_array_counted_by_attr_field_not_found CHECK-NEXT: warn_ignoring_ftabstop_value CHECK-NEXT: warn_implements_nscopying CHECK-NEXT: warn_incompatible_qualified_id diff --git a/clang/test/Sema/attr-counted-by.c b/clang/test/Sema/attr-counted-by.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-counted-by.c @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple x86_64-linux -fstrict-flex-arrays=3 -fsanitize=array-bounds \ +// RUN: -fsyntax-only -verify %s + +#define __counted_by(f) __attribute__((counted_by(f))) + +struct bar; + +struct foo { + int bork; + struct bar *fam[] __counted_by(not_found); // expected-warning {{counted_by field 'not_found' not found}} +}; + +struct baz { + int bork; + struct bar *self[] __counted_by(self); // expected-error {{counted_by field 'self' cannot refer to the flexible array}} +}; + +struct qux { + double non_integer; // expected-error {{counted_by field 'non_integer' is not an integral type}} + struct bar *fam[] __counted_by(non_integer); // expected-note {{counted_by field 'non_integer' declared here}} +};