Skip to content

Commit 00b6d03

Browse files
committed
[Clang] Implement the 'counted_by' attribute (#76348)
The 'counted_by' attribute is used on flexible array members. The argument for the attribute is the name of the field member holding the count of elements in the flexible array. This information is used to improve the results of the array bound sanitizer and the '__builtin_dynamic_object_size' builtin. The 'count' field member must be within the same non-anonymous, enclosing struct as the flexible array member. For example: ``` struct bar; struct foo { int count; struct inner { struct { int count; /* The 'count' referenced by 'counted_by' */ }; struct { /* ... */ struct bar *array[] __attribute__((counted_by(count))); }; } baz; }; ``` This example specifies that the flexible array member 'array' has the number of elements allocated for it in 'count': ``` struct bar; struct foo { size_t count; /* ... */ struct bar *array[] __attribute__((counted_by(count))); }; ``` This establishes a relationship between 'array' and 'count'; specifically that '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 throughout changes to the structure. In the following, the allocated array erroneously has fewer elements than what's specified by 'p->count'. This would result in an out-of-bounds access not not being detected: ``` struct foo *p; void foo_alloc(size_t count) { p = malloc(MAX(sizeof(struct foo), offsetof(struct foo, array[0]) + count * sizeof(struct bar *))); p->count = count + 42; } ``` The next example updates 'p->count', breaking the relationship requirement that 'p->array' must have at least 'p->count' number of elements available: ``` void use_foo(int index, int val) { p->count += 42; p->array[index] = val; /* The sanitizer can't properly check this access */ } ``` In this example, an update to 'p->count' maintains the relationship requirement: ``` void use_foo(int index, int val) { if (p->count == 0) return; --p->count; p->array[index] = val; } ```
1 parent 1b5f72c commit 00b6d03

20 files changed

+2877
-92
lines changed

clang/docs/ReleaseNotes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ C Language Changes
235235
- Enums will now be represented in TBAA metadata using their actual underlying
236236
integer type. Previously they were treated as chars, which meant they could
237237
alias with all other types.
238+
- Clang now supports the C-only attribute ``counted_by``. When applied to a
239+
struct's flexible array member, it points to the struct field that holds the
240+
number of elements in the flexible array member. This information can improve
241+
the results of the array bound sanitizer and the
242+
``__builtin_dynamic_object_size`` builtin.
238243

239244
C23 Feature Support
240245
^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/DeclBase.h

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/AST/SelectorLocationsKind.h"
2020
#include "clang/Basic/IdentifierTable.h"
2121
#include "clang/Basic/LLVM.h"
22+
#include "clang/Basic/LangOptions.h"
2223
#include "clang/Basic/SourceLocation.h"
2324
#include "clang/Basic/Specifiers.h"
2425
#include "llvm/ADT/ArrayRef.h"
@@ -488,6 +489,15 @@ class alignas(8) Decl {
488489
// Return true if this is a FileContext Decl.
489490
bool isFileContextDecl() const;
490491

492+
/// Whether it resembles a flexible array member. This is a static member
493+
/// because we want to be able to call it with a nullptr. That allows us to
494+
/// perform non-Decl specific checks based on the object's type and strict
495+
/// flex array level.
496+
static bool isFlexibleArrayMemberLike(
497+
ASTContext &Context, const Decl *D, QualType Ty,
498+
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel,
499+
bool IgnoreTemplateOrMacroSubstitution);
500+
491501
ASTContext &getASTContext() const LLVM_READONLY;
492502

493503
/// Helper to get the language options from the ASTContext.

clang/include/clang/Basic/Attr.td

+18
Original file line numberDiff line numberDiff line change
@@ -4393,3 +4393,21 @@ def CodeAlign: StmtAttr {
43934393
static constexpr int MaximumAlignment = 4096;
43944394
}];
43954395
}
4396+
4397+
def CountedBy : InheritableAttr {
4398+
let Spellings = [Clang<"counted_by">];
4399+
let Subjects = SubjectList<[Field]>;
4400+
let Args = [IdentifierArgument<"CountedByField">];
4401+
let Documentation = [CountedByDocs];
4402+
let LangOpts = [COnly];
4403+
// FIXME: This is ugly. Let using a DeclArgument would be nice, but a Decl
4404+
// isn't yet available due to the fact that we're still parsing the
4405+
// structure. Maybe that code could be changed sometime in the future.
4406+
code AdditionalMembers = [{
4407+
private:
4408+
SourceRange CountedByFieldLoc;
4409+
public:
4410+
SourceRange getCountedByFieldLoc() const { return CountedByFieldLoc; }
4411+
void setCountedByFieldLoc(SourceRange Loc) { CountedByFieldLoc = Loc; }
4412+
}];
4413+
}

clang/include/clang/Basic/AttrDocs.td

+78
Original file line numberDiff line numberDiff line change
@@ -7797,3 +7797,81 @@ but do not pass them to the underlying coroutine or pass them by value.
77977797
.. _`CRT`: https://clang.llvm.org/docs/AttributeReference.html#coro-return-type
77987798
}];
77997799
}
7800+
7801+
def CountedByDocs : Documentation {
7802+
let Category = DocCatField;
7803+
let Content = [{
7804+
Clang supports the ``counted_by`` attribute on the flexible array member of a
7805+
structure in C. The argument for the attribute is the name of a field member
7806+
holding the count of elements in the flexible array. This information can be
7807+
used to improve the results of the array bound sanitizer and the
7808+
``__builtin_dynamic_object_size`` builtin. The ``count`` field member must be
7809+
within the same non-anonymous, enclosing struct as the flexible array member.
7810+
7811+
This example specifies that the flexible array member ``array`` has the number
7812+
of elements allocated for it in ``count``:
7813+
7814+
.. code-block:: c
7815+
7816+
struct bar;
7817+
7818+
struct foo {
7819+
size_t count;
7820+
char other;
7821+
struct bar *array[] __attribute__((counted_by(count)));
7822+
};
7823+
7824+
This establishes a relationship between ``array`` and ``count``. Specifically,
7825+
``array`` must have at least ``count`` number of elements available. It's the
7826+
user's responsibility to ensure that this relationship is maintained through
7827+
changes to the structure.
7828+
7829+
In the following example, the allocated array erroneously has fewer elements
7830+
than what's specified by ``p->count``. This would result in an out-of-bounds
7831+
access not being detected.
7832+
7833+
.. code-block:: c
7834+
7835+
#define SIZE_INCR 42
7836+
7837+
struct foo *p;
7838+
7839+
void foo_alloc(size_t count) {
7840+
p = malloc(MAX(sizeof(struct foo),
7841+
offsetof(struct foo, array[0]) + count * sizeof(struct bar *)));
7842+
p->count = count + SIZE_INCR;
7843+
}
7844+
7845+
The next example updates ``p->count``, but breaks the relationship requirement
7846+
that ``p->array`` must have at least ``p->count`` number of elements available:
7847+
7848+
.. code-block:: c
7849+
7850+
#define SIZE_INCR 42
7851+
7852+
struct foo *p;
7853+
7854+
void foo_alloc(size_t count) {
7855+
p = malloc(MAX(sizeof(struct foo),
7856+
offsetof(struct foo, array[0]) + count * sizeof(struct bar *)));
7857+
p->count = count;
7858+
}
7859+
7860+
void use_foo(int index, int val) {
7861+
p->count += SIZE_INCR + 1; /* 'count' is now larger than the number of elements of 'array'. */
7862+
p->array[index] = val; /* The sanitizer can't properly check this access. */
7863+
}
7864+
7865+
In this example, an update to ``p->count`` maintains the relationship
7866+
requirement:
7867+
7868+
.. code-block:: c
7869+
7870+
void use_foo(int index, int val) {
7871+
if (p->count == 0)
7872+
return;
7873+
--p->count;
7874+
p->array[index] = val;
7875+
}
7876+
}];
7877+
}

clang/include/clang/Basic/DiagnosticSemaKinds.td

+13
Original file line numberDiff line numberDiff line change
@@ -6447,6 +6447,19 @@ def warn_superclass_variable_sized_type_not_at_end : Warning<
64476447
"field %0 can overwrite instance variable %1 with variable sized type %2"
64486448
" in superclass %3">, InGroup<ObjCFlexibleArray>;
64496449

6450+
def err_flexible_array_count_not_in_same_struct : Error<
6451+
"'counted_by' field %0 isn't within the same struct as the flexible array">;
6452+
def err_counted_by_attr_not_on_flexible_array_member : Error<
6453+
"'counted_by' only applies to C99 flexible array members">;
6454+
def err_counted_by_attr_refers_to_flexible_array : Error<
6455+
"'counted_by' cannot refer to the flexible array %0">;
6456+
def err_counted_by_must_be_in_structure : Error<
6457+
"field %0 in 'counted_by' not inside structure">;
6458+
def err_flexible_array_counted_by_attr_field_not_integer : Error<
6459+
"field %0 in 'counted_by' must be a non-boolean integer type">;
6460+
def note_flexible_array_counted_by_attr_field : Note<
6461+
"field %0 declared here">;
6462+
64506463
let CategoryName = "ARC Semantic Issue" in {
64516464

64526465
// ARC-mode diagnostics.

clang/include/clang/Sema/Sema.h

+3
Original file line numberDiff line numberDiff line change
@@ -4799,6 +4799,8 @@ class Sema final {
47994799
bool CheckAlwaysInlineAttr(const Stmt *OrigSt, const Stmt *CurSt,
48004800
const AttributeCommonInfo &A);
48014801

4802+
bool CheckCountedByAttr(Scope *Scope, const FieldDecl *FD);
4803+
48024804
/// Adjust the calling convention of a method to be the ABI default if it
48034805
/// wasn't specified explicitly. This handles method types formed from
48044806
/// function type typedefs and typename template arguments.
@@ -5642,6 +5644,7 @@ class Sema final {
56425644
CorrectionCandidateCallback &CCC,
56435645
TemplateArgumentListInfo *ExplicitTemplateArgs = nullptr,
56445646
ArrayRef<Expr *> Args = std::nullopt,
5647+
DeclContext *LookupCtx = nullptr,
56455648
TypoExpr **Out = nullptr);
56465649

56475650
DeclResult LookupIvarInObjCMethod(LookupResult &Lookup, Scope *S,

clang/include/clang/Sema/TypoCorrection.h

+8-4
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ class CorrectionCandidateCallback {
282282
public:
283283
static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
284284

285-
explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
285+
explicit CorrectionCandidateCallback(const IdentifierInfo *Typo = nullptr,
286286
NestedNameSpecifier *TypoNNS = nullptr)
287287
: Typo(Typo), TypoNNS(TypoNNS) {}
288288

@@ -319,7 +319,7 @@ class CorrectionCandidateCallback {
319319
/// this method.
320320
virtual std::unique_ptr<CorrectionCandidateCallback> clone() = 0;
321321

322-
void setTypoName(IdentifierInfo *II) { Typo = II; }
322+
void setTypoName(const IdentifierInfo *II) { Typo = II; }
323323
void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
324324

325325
// Flags for context-dependent keywords. WantFunctionLikeCasts is only
@@ -345,13 +345,13 @@ class CorrectionCandidateCallback {
345345
candidate.getCorrectionSpecifier() == TypoNNS;
346346
}
347347

348-
IdentifierInfo *Typo;
348+
const IdentifierInfo *Typo;
349349
NestedNameSpecifier *TypoNNS;
350350
};
351351

352352
class DefaultFilterCCC final : public CorrectionCandidateCallback {
353353
public:
354-
explicit DefaultFilterCCC(IdentifierInfo *Typo = nullptr,
354+
explicit DefaultFilterCCC(const IdentifierInfo *Typo = nullptr,
355355
NestedNameSpecifier *TypoNNS = nullptr)
356356
: CorrectionCandidateCallback(Typo, TypoNNS) {}
357357

@@ -365,6 +365,10 @@ class DefaultFilterCCC final : public CorrectionCandidateCallback {
365365
template <class C>
366366
class DeclFilterCCC final : public CorrectionCandidateCallback {
367367
public:
368+
explicit DeclFilterCCC(const IdentifierInfo *Typo = nullptr,
369+
NestedNameSpecifier *TypoNNS = nullptr)
370+
: CorrectionCandidateCallback(Typo, TypoNNS) {}
371+
368372
bool ValidateCandidate(const TypoCorrection &candidate) override {
369373
return candidate.getCorrectionDeclAs<C>();
370374
}

clang/lib/AST/ASTImporter.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -9044,6 +9044,10 @@ class AttrImporter {
90449044
public:
90459045
AttrImporter(ASTImporter &I) : Importer(I), NImporter(I) {}
90469046

9047+
// Useful for accessing the imported attribute.
9048+
template <typename T> T *castAttrAs() { return cast<T>(ToAttr); }
9049+
template <typename T> const T *castAttrAs() const { return cast<T>(ToAttr); }
9050+
90479051
// Create an "importer" for an attribute parameter.
90489052
// Result of the 'value()' of that object is to be passed to the function
90499053
// 'importAttr', in the order that is expected by the attribute class.
@@ -9257,6 +9261,15 @@ Expected<Attr *> ASTImporter::Import(const Attr *FromAttr) {
92579261
From->args_size());
92589262
break;
92599263
}
9264+
case attr::CountedBy: {
9265+
AI.cloneAttr(FromAttr);
9266+
const auto *CBA = cast<CountedByAttr>(FromAttr);
9267+
Expected<SourceRange> SR = Import(CBA->getCountedByFieldLoc()).get();
9268+
if (!SR)
9269+
return SR.takeError();
9270+
AI.castAttrAs<CountedByAttr>()->setCountedByFieldLoc(SR.get());
9271+
break;
9272+
}
92609273

92619274
default: {
92629275
// The default branch works for attributes that have no arguments to import.

clang/lib/AST/DeclBase.cpp

+73-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#include "clang/AST/Type.h"
3030
#include "clang/Basic/IdentifierTable.h"
3131
#include "clang/Basic/LLVM.h"
32-
#include "clang/Basic/LangOptions.h"
3332
#include "clang/Basic/Module.h"
3433
#include "clang/Basic/ObjCRuntime.h"
3534
#include "clang/Basic/PartialDiagnostic.h"
@@ -411,6 +410,79 @@ bool Decl::isFileContextDecl() const {
411410
return DC && DC->isFileContext();
412411
}
413412

413+
bool Decl::isFlexibleArrayMemberLike(
414+
ASTContext &Ctx, const Decl *D, QualType Ty,
415+
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel,
416+
bool IgnoreTemplateOrMacroSubstitution) {
417+
// For compatibility with existing code, we treat arrays of length 0 or
418+
// 1 as flexible array members.
419+
const auto *CAT = Ctx.getAsConstantArrayType(Ty);
420+
if (CAT) {
421+
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
422+
423+
llvm::APInt Size = CAT->getSize();
424+
if (StrictFlexArraysLevel == FAMKind::IncompleteOnly)
425+
return false;
426+
427+
// GCC extension, only allowed to represent a FAM.
428+
if (Size.isZero())
429+
return true;
430+
431+
if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete && Size.uge(1))
432+
return false;
433+
434+
if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete && Size.uge(2))
435+
return false;
436+
} else if (!Ctx.getAsIncompleteArrayType(Ty)) {
437+
return false;
438+
}
439+
440+
if (const auto *OID = dyn_cast_if_present<ObjCIvarDecl>(D))
441+
return OID->getNextIvar() == nullptr;
442+
443+
const auto *FD = dyn_cast_if_present<FieldDecl>(D);
444+
if (!FD)
445+
return false;
446+
447+
if (CAT) {
448+
// GCC treats an array memeber of a union as an FAM if the size is one or
449+
// zero.
450+
llvm::APInt Size = CAT->getSize();
451+
if (FD->getParent()->isUnion() && (Size.isZero() || Size.isOne()))
452+
return true;
453+
}
454+
455+
// Don't consider sizes resulting from macro expansions or template argument
456+
// substitution to form C89 tail-padded arrays.
457+
if (IgnoreTemplateOrMacroSubstitution) {
458+
TypeSourceInfo *TInfo = FD->getTypeSourceInfo();
459+
while (TInfo) {
460+
TypeLoc TL = TInfo->getTypeLoc();
461+
462+
// Look through typedefs.
463+
if (TypedefTypeLoc TTL = TL.getAsAdjusted<TypedefTypeLoc>()) {
464+
const TypedefNameDecl *TDL = TTL.getTypedefNameDecl();
465+
TInfo = TDL->getTypeSourceInfo();
466+
continue;
467+
}
468+
469+
if (auto CTL = TL.getAs<ConstantArrayTypeLoc>()) {
470+
if (const Expr *SizeExpr =
471+
dyn_cast_if_present<IntegerLiteral>(CTL.getSizeExpr());
472+
!SizeExpr || SizeExpr->getExprLoc().isMacroID())
473+
return false;
474+
}
475+
476+
break;
477+
}
478+
}
479+
480+
// Test that the field is the last in the structure.
481+
RecordDecl::field_iterator FI(
482+
DeclContext::decl_iterator(const_cast<FieldDecl *>(FD)));
483+
return ++FI == FD->getParent()->field_end();
484+
}
485+
414486
TranslationUnitDecl *Decl::getTranslationUnitDecl() {
415487
if (auto *TUD = dyn_cast<TranslationUnitDecl>(this))
416488
return TUD;

0 commit comments

Comments
 (0)