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 @@ -4233,3 +4233,22 @@ let Documentation = [Undocumented]; } +def CountedBy : InheritableAttr { + let Spellings = [Clang<"counted_by">]; + let Subjects = SubjectList<[Field]>; + let Args = [VariadicIdentifierArgument<"CountedByFields">]; + let Documentation = [CountedByDocs]; + let LangOpts = [COnly]; + code AdditionalMembers = [{ + private: + SmallVector CountFieldRanges; + public: + void addCountFieldSourceRange(ArrayRef Ranges) { + for (SourceRange SR : Ranges) + CountFieldRanges.push_back(SR); + } + SourceRange getCountFieldSourceRange(unsigned Idx) const { + return CountFieldRanges[Idx]; + } + }]; +} 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 @@ -7189,3 +7189,72 @@ (if any) of these ``cleanup`` callback functions. }]; } + +def CountedByDocs : Documentation { + let Category = DocCatField; + let Content = [{ +Clang supports the ``counted_by`` attribute for flexible array members. The +argument for the ``counted_by`` attribute is the name of a field member in +the same structure holding the count of elements in the flexible array. + +For example: + +.. code-block:: c + + struct bar; + + struct foo { + size_t num_fam_elements; + /* ... */ + struct bar *fam[] __attribute__((counted_by(num_fam_elements))); + }; + + struct foo *foo_alloc(size_t num_elements) { + struct foo *f; + + f = malloc(sizeof(struct foo) + num_elements * sizeof(struct bar *)); + f->num_fam_elements = num_elements; + return f; + } + +This attribute is used with the ``-fsantize=array-bounds`` flag to trap if a +flexible array access is outside of the number of elements. + +.. code-block:: c + + void mux(struct foo *); + + struct bar *baz(void) { + struct foo *f = foo_alloc(128); + + mux(f); /* Fills in fam element. */ + + return f->fam[256]; /* Trap. */ + } + +It's possible to specify an element in a substructure. The attribute syntax can +take a list of field names to the element count. + +For example: + +.. code-block:: c + + struct bar; + + struct foo { + struct { + size_t num_fam_elements; + } bork; + /* ... */ + struct bar *fam[] __attribute__((counted_by(bork, num_fam_elements))); + }; + + struct foo *foo_alloc(size_t num_elements) { + struct foo *f; + + f = malloc(sizeof(struct foo) + num_elements * sizeof(struct bar *)); + f->bork.num_fam_elements = num_elements; + return f; + } + }]; +} 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 @@ -3416,6 +3416,9 @@ def err_alignment_dependent_typedef_name : Error< "requested alignment is dependent but declaration is not dependent">; +def warn_counted_by_placeholder : Warning< + "counted_by count %0 doesn't exist">; + def warn_alignment_builtin_useless : Warning< "%select{aligning a value|the result of checking whether a value is aligned}0" " to 1 byte is %select{a no-op|always true}0">, InGroup; 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 @@ -8887,6 +8887,12 @@ 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. @@ -9093,6 +9099,17 @@ From->args_size()); break; } + case attr::CountedBy: { + AI.cloneAttr(FromAttr); + const auto *ECA = cast(FromAttr); + for (unsigned I = 0, E = ECA->countedByFields_size(); I != E; ++I) { + Expected SR = Import(ECA->getCountFieldSourceRange(I)).get(); + if (!SR) + return SR.takeError(); + AI.getAttrAs()->addCountFieldSourceRange(SR.get()); + } + break; + } default: { // The default branch works for attributes that have no arguments to import. diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -980,7 +980,8 @@ // Register callbacks to schedule sanitizer passes at the appropriate part // of the pipeline. - if (LangOpts.Sanitize.has(SanitizerKind::LocalBounds)) + if (LangOpts.Sanitize.has(SanitizerKind::LocalBounds) || + LangOpts.Sanitize.has(SanitizerKind::ArrayBounds)) PB.registerScalarOptimizerLateEPCallback( [](FunctionPassManager &FPM, OptimizationLevel Level) { FPM.addPass(BoundsCheckingPass()); 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 @@ -933,6 +933,50 @@ return CGF.getVLASize(VAT).NumElts; // Ignore pass_object_size here. It's not applicable on decayed pointers. } + + if (const auto *ME = dyn_cast(CE->getSubExpr())) { + if (ME->isFlexibleArrayMemberLike(CGF.getContext(), + StrictFlexArraysLevel, true)) { + if (const auto *MD = dyn_cast(ME->getMemberDecl())) { + if (const auto *ECA = MD->getAttr()) { + const RecordDecl *RD = MD->getParent(); + MemberExpr *Mem = nullptr; + + for (const IdentifierInfo *CountField : ECA->countedByFields()) { + MemberExpr *TmpMem = nullptr; + + for (FieldDecl *FD : RD->fields()) { + if (FD->getName() != CountField->getName()) + continue; + + if (Mem) + TmpMem = MemberExpr::CreateImplicit( + CGF.getContext(), Mem, Mem->getType()->isPointerType(), + FD, FD->getType(), VK_LValue, OK_Ordinary); + else + TmpMem = MemberExpr::CreateImplicit( + CGF.getContext(), const_cast(ME->getBase()), + ME->isArrow(), FD, FD->getType(), VK_LValue, + OK_Ordinary); + + if (FD->getType()->isRecordType()) + RD = FD->getType()->getAsRecordDecl(); + + break; + } + + Mem = TmpMem; + if (!Mem) break; + } + + if (Mem) { + IndexedType = Base->getType(); + return CGF.EmitAnyExprToTemp(Mem).getScalarVal(); + } + } + } + } + } } QualType EltTy{Base->getType()->getPointeeOrArrayElementType(), 0}; @@ -949,8 +993,6 @@ 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 +1002,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/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -17895,6 +17895,44 @@ "Broken injected-class-name"); } +static const FieldDecl *FindFieldWithCountedByAttr(const RecordDecl *RD) { + for (const Decl *D : RD->decls()) { + if (const auto *FD = dyn_cast(D)) + if (FD->hasAttr()) + return FD; + + if (const auto *SubRD = dyn_cast(D)) + if (const FieldDecl *FD = FindFieldWithCountedByAttr(SubRD)) + return FD; + } + + return nullptr; +} + +static const IdentifierInfo * +CheckCountedByAttr(const RecordDecl *RD, const FieldDecl *FD, + SourceRange &Loc) { + const CountedByAttr *ECA = FD->getAttr(); + unsigned Idx = 0; + + for (const IdentifierInfo *Field : ECA->countedByFields()) { + Loc = ECA->getCountFieldSourceRange(Idx++); + + auto DeclIter = llvm::find_if( + RD->fields(), [&](const FieldDecl *FD){ + return Field->getName() == FD->getName(); + }); + + if (DeclIter == RD->field_end()) + return Field; + + if (auto *SubRD = DeclIter->getType()->getAsRecordDecl()) + RD = SubRD; + } + + return nullptr; +} + void Sema::ActOnTagFinishDefinition(Scope *S, Decl *TagD, SourceRange BraceRange) { AdjustDeclIfTemplate(TagD); @@ -17952,6 +17990,19 @@ [](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 RecordDecl *RD = dyn_cast(Tag)) { + if (const FieldDecl *FD = FindFieldWithCountedByAttr(RD)) { + SourceRange SR; + const IdentifierInfo *Unknown = CheckCountedByAttr(RD, FD, SR); + + if (Unknown) + Diag(SR.getBegin(), diag::warn_counted_by_placeholder) + << Unknown << SR; + } + } } 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 @@ -8370,6 +8370,30 @@ D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL)); } +static void handleCountedByAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + // TODO: Probably needs more processing here. See Sema::AddAlignValueAttr. + SmallVector Names; + SmallVector Ranges; + + for (unsigned I = 0, E = getNumAttributeArgs(AL); I != E; ++I) { + if (!AL.isArgIdent(I)) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_type) + << AL << AANT_ArgumentIdentifier; + return; + } + + IdentifierLoc *IL = AL.getArgAsIdent(I); + Names.push_back(IL->Ident); + Ranges.push_back(IL->Loc); + } + + CountedByAttr *CBA = ::new (S.Context) CountedByAttr(S.Context, AL, + Names.data(), + Names.size()); + CBA->addCountFieldSourceRange(Ranges); + D->addAttr(CBA); +} + static void handleFunctionReturnThunksAttr(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef KindStr; @@ -9316,6 +9340,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/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)