[clang] [BoundsSafety][Sema] Allow counted_by and counted_by_or_null on pointers where the pointee type is incomplete but potentially completable (PR #106321)
Dan Liew via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 24 08:50:57 PDT 2024
https://github.com/delcypher updated https://github.com/llvm/llvm-project/pull/106321
>From eb334fc34cfc58ca37c0b0e78fd2c4422d413611 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 15 Aug 2024 16:33:04 -0700
Subject: [PATCH 1/8] [BoundsSafety][Sema] Allow counted_by and
counted_by_or_null on pointers where the pointee type is incomplete but
potentially completable
Previously using the `counted_by` or `counted_by_or_null` attribute on a
pointer with an incomplete pointee type was forbidden. Unfortunately
this prevented a situation like the following from being allowed.
Header file:
```
struct EltTy; // Incomplete type
struct Buffer {
size_t count;
struct EltTy* __counted_by(count) buffer; // error before this patch
};
```
Implementation file:
```
struct EltTy {
// definition
};
void allocBuffer(struct Buffer* b) {
b->buffer = malloc(sizeof(EltTy)* b->count);
}
```
To allow code like the above but still enforce that the pointee
type size is known in locations where `-fbounds-safety` needs to
emit bounds checks the following scheme is used.
* For incomplete pointee types that can never be completed (e.g. `void`)
these are treated as error where the attribute is written (just like
before this patch).
* For incomplete pointee types that might be completable later on
(struct, union, and enum forward declarations)
in the translation unit, writing the attribute on the incomplete
pointee type is allowed on the FieldDecl declaration but "uses" of the
declared pointer are forbidden if at the point of "use" the pointee
type is still incomplete.
For this patch a "use" of a FieldDecl covers:
* Explicit and Implicit initialization (note see **Tentative Definition
Initialization** for an exception to this)
* Assignment
* Conversion to an lvalue (e.g. for use in an expression)
In Apple's internal fork of Clang the `counted_by` and
`counted_by_or_null` attribute are allowed in many more contexts. That
isn't the case for upstream Clang so the "use" checks for the attribute
on VarDecl, ParamVarDecl, and function return type have been omitted
from this patch because they can't be tested. However, the
`BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy` and
`BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy` functions
retain the ability to emit diagnostics for these other contexts to avoid
unnecessary divergence between upstream Clang and Apple's internal fork.
Support for checking "uses" will be upstreamed when upstream Clang
allows the `counted_by` and `counted_by_or_null` attribute in additional
contexts.
This patch also includes the new `Sema::BoundsSafetySourceRangeFor`
method which allows a more accurate SourceRange for the `counted_by`
and `counted_by_or_null` attributes to be shown in diagnostics. This
is used by the new `diag::note_named_attribute` diagnostic introduced in
this patch. This method has several shortcomings but it didn't seem
worth covering them given this is essentially a workaround for the
fact `CountAttributedType` doesn't store the SourceLocation for its
attribute.
This patch has a few limitations:
** 1. Tentative Defition Initialization **
This patch currently allows something like:
```
struct IncompleteTy;
struct Buffer {
int count;
struct IncompleteTy* __counted_by(count) buf;
};
// Tentative definition
struct Buffer GlobalBuf;
```
The Tentative definition in this example becomes an actual definition
whose initialization **should be checked** but it currently isn't.
Addressing this problem will be done in a subseqent patch.
** 2. When the incomplete pointee type is a typedef diagnostics are slightly misleading **
For this situation:
```
struct IncompleteTy;
typedef struct IncompleteTy Incomplete_t;
struct Buffer {
int count;
struct IncompleteTy* __counted_by(count) buf;
};
void use(struct Buffer b) {
b.buf = 0x0;
}
```
This code emits `note: forward declaration of 'Incomplete_t' (aka
'struct IncompleteTy')` but the location is on the `struct
IncompleteTy;` forward declaration. This is misleading because
`Incomplete_t` isn't actually forward declared there (instead the
underlying type is). This could be resolved by additional diagnostics
that walk the chain of typedefs and explain each step of the walk.
However, that would be very verbose and didn't seem like a direction
worth pursuing.
rdar://133600117
---
clang/include/clang/AST/Type.h | 22 +
.../clang/Basic/DiagnosticSemaKinds.td | 37 ++
clang/include/clang/Sema/Sema.h | 51 ++
clang/lib/AST/Type.cpp | 37 ++
clang/lib/Sema/SemaBoundsSafety.cpp | 402 +++++++++++-
clang/lib/Sema/SemaExpr.cpp | 24 +
clang/lib/Sema/SemaInit.cpp | 18 +
.../attr-counted-by-late-parsed-struct-ptrs.c | 5 +-
.../Sema/attr-counted-by-or-null-last-field.c | 4 +-
...unted-by-or-null-late-parsed-struct-ptrs.c | 5 +-
.../attr-counted-by-or-null-struct-ptrs.c | 12 +-
...ruct-ptrs-completable-incomplete-pointee.c | 584 ++++++++++++++++++
clang/test/Sema/attr-counted-by-struct-ptrs.c | 12 +-
clang/test/Sema/attr-counted-by-vla.c | 3 +-
clang/unittests/Sema/CMakeLists.txt | 1 +
.../Sema/GetCountedByAttrSourceRange.cpp | 573 +++++++++++++++++
16 files changed, 1763 insertions(+), 27 deletions(-)
create mode 100644 clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
create mode 100644 clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index ba3161c366f4d9..e7e86897e3e7a8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -2445,6 +2445,26 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
return !isFunctionType();
}
+ /// \returns True if the type is incomplete and it is also a type that
+ /// cannot be completed by a later type definition.
+ ///
+ /// E.g. For `void` this is true but for `struct ForwardDecl;` this is false
+ /// because a definition for `ForwardDecl` could be provided later on in the
+ /// translation unit.
+ ///
+ /// Note even for types that this function returns true for it is still
+ /// possible for the declarations that contain this type to later have a
+ /// complete type in a translation unit. E.g.:
+ ///
+ /// \code{.c}
+ /// // This decl has type 'char[]' which is incomplete and cannot be later
+ /// // completed by another by another type declaration.
+ /// extern char foo[];
+ /// // This decl how has complete type 'char[5]'.
+ /// char foo[5]; // foo has a complete type
+ /// \endcode
+ bool isIncompletableIncompleteType() const;
+
/// Determine whether this type is an object type.
bool isObjectType() const {
// C++ [basic.types]p8:
@@ -3352,6 +3372,8 @@ class CountAttributedType final
static bool classof(const Type *T) {
return T->getTypeClass() == CountAttributed;
}
+
+ StringRef GetAttributeName(bool WithMacroPrefix) const;
};
/// Represents a type which was implicitly adjusted by the semantic
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8e4718008ece72..48efe9c57a9dcb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4385,6 +4385,7 @@ def err_mismatched_visibility: Error<"visibility does not match previous declara
def note_previous_attribute : Note<"previous attribute is here">;
def note_conflicting_attribute : Note<"conflicting attribute is here">;
def note_attribute : Note<"attribute is here">;
+def note_named_attribute : Note<"%0 attribute is here">;
def err_mismatched_ms_inheritance : Error<
"inheritance model does not match %select{definition|previous declaration}0">;
def warn_ignored_ms_inheritance : Warning<
@@ -6643,6 +6644,42 @@ def err_counted_by_attr_pointee_unknown_size : Error<
"%select{|. This will be an error in a future compiler version}3"
""
"}2">;
+def err_counted_by_on_incomplete_type_on_assign : Error <
+ "cannot %select{"
+ "assign to %select{object|'%1'}2 that has|" // AA_Assigning,
+ "pass argument to %select{parameter|parameter '%1'}2 that has|" // AA_Passing,
+ "return|" // AA_Returning,
+ "convert to|" // AA_Converting (UNUSED)
+ "%select{|implicitly }3initialize %select{object|'%1'}2 that has|" // AA_Initializing,
+ "pass argument to parameter that has|" // AA_Sending (UNUSED)
+ "cast to|" // AA_Casting (UNUSED)
+ "pass argument to parameter that has" // AA_Passing_CFAudited (UNUSED)
+ "}0 type %4 because the pointee type %6 is incomplete and the '%5' attribute "
+ "requires the pointee type be complete when %select{"
+ "assigning|" // AA_Assigning,
+ "passing|" // AA_Passing,
+ "returning|" // AA_Returning,
+ "converting|" // AA_Converting (UNUSED)
+ "initializing|" // AA_Initializing,
+ "passing|" // AA_Sending (UNUSED)
+ "casting|" // AA_Casting (UNUSED)
+ "passing" // AA_Passing_CFAudited (UNUSED)
+ "}0; "
+ "consider providing a complete definition for %6 or using the "
+ "'__sized_by%select{|_or_null}7' attribute"
+>;
+def err_counted_by_on_incomplete_type_on_use : Error <
+ "cannot %select{"
+ "use '%1'|" // Generic expr
+ "call '%1'" // CallExpr
+ "}0 with%select{"
+ "|" // Generic expr
+ " return" // CallExpr
+ "}0 type %2 because the pointee type %3 is incomplete "
+ "and the '%4' attribute requires the pointee type be complete in this context; "
+ "consider providing a complete definition for %3 or using the "
+ "'__sized_by%select{|_or_null}5' attribute"
+>;
def warn_counted_by_attr_elt_type_unknown_size :
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9e6b04bc3f8f7c..b1eb0b1bcf1d08 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2050,6 +2050,57 @@ class Sema final : public SemaBase {
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull);
+ /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
+ /// `__counted_by_or_null` pointer type \param LHSTy.
+ ///
+ /// \param LHSTy The type being assigned to. Checks will only be performed if
+ /// the type is a `counted_by` or `counted_by_or_null ` pointer.
+ /// \param RHSExpr The expression being assigned from.
+ /// \param Action The type assignment being performed
+ /// \param Loc The SourceLocation to use for error diagnostics
+ /// \param ComputeAssignee If provided this function will be called before
+ /// emitting a diagnostic. The function should return the name of
+ /// entity being assigned to or an empty string if this cannot be
+ /// determined.
+ ///
+ /// \returns True iff no diagnostic where emitted, false otherwise.
+ bool BoundsSafetyCheckAssignmentToCountAttrPtr(
+ QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
+ SourceLocation Loc,
+ std::function<std::string()> ComputeAssignee = nullptr);
+
+ /// Perform Bounds Safety Semantic checks for initializing a Bounds Safety
+ /// pointer.
+ ///
+ /// \param Entity The entity being initialized
+ /// \param Kind The kind of initialization being performed
+ /// \param Action The type assignment being performed
+ /// \param LHSTy The type being assigned to. Checks will only be performed if
+ /// the type is a `counted_by` or `counted_by_or_null ` pointer.
+ /// \param RHSExpr The expression being used for initialization.
+ ///
+ /// \returns True iff no diagnostic where emitted, false otherwise.
+ bool BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
+ const InitializationKind &Kind,
+ AssignmentAction Action,
+ QualType LHSType, Expr *RHSExpr);
+
+ /// Perform Bounds Safety semantic checks for uses of invalid uses counted_by
+ /// or counted_by_or_null pointers in \param E.
+ ///
+ /// \param E the expression to check
+ ///
+ /// \returns True iff no diagnostic where emitted, false otherwise.
+ bool BoundsSafetyCheckUseOfCountAttrPtr(Expr *E);
+
+ /// \returns A SourceRange for \param CATy.
+ ///
+ /// This method tries to compute the most useful SourceRange for diagnostics.
+ /// If this fails the returned SourceRange will be the same as
+ /// `CATy->getCountExpr()->getSourceRange()`.
+ ///
+ SourceRange BoundsSafetySourceRangeFor(const CountAttributedType *CATy);
+
///@}
//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 113d4a100528f8..93b576bc55938a 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2438,6 +2438,22 @@ bool Type::isIncompleteType(NamedDecl **Def) const {
}
}
+bool Type::isIncompletableIncompleteType() const {
+ if (!isIncompleteType())
+ return false;
+
+ // Forward declarations of structs, classes, enums, and unions could be later
+ // completed in a compilation unit by providing a type definition.
+ if (isStructureOrClassType() || isEnumeralType() || isUnionType())
+ return false;
+
+ // Other types are incompletable.
+ //
+ // E.g. `char[]` and `void`. The type is incomplete and no future
+ // type declarations can make the type complete.
+ return true;
+}
+
bool Type::isSizelessBuiltinType() const {
if (isSizelessVectorType())
return true;
@@ -3873,6 +3889,27 @@ CountAttributedType::CountAttributedType(
DeclSlot[i] = CoupledDecls[i];
}
+StringRef CountAttributedType::GetAttributeName(bool WithMacroPrefix) const {
+#define ENUMERATE_ATTRS(PREFIX) \
+ do { \
+ if (isCountInBytes()) { \
+ if (isOrNull()) \
+ return PREFIX "sized_by_or_null"; \
+ return PREFIX "sized_by"; \
+ } \
+ if (isOrNull()) \
+ return PREFIX "counted_by_or_null"; \
+ return PREFIX "counted_by"; \
+ } while (0)
+
+ if (WithMacroPrefix)
+ ENUMERATE_ATTRS("__");
+ else
+ ENUMERATE_ATTRS("");
+
+#undef ENUMERATE_ATTRS
+}
+
TypedefType::TypedefType(TypeClass tc, const TypedefNameDecl *D,
QualType Underlying, QualType can)
: Type(tc, can, toSemanticDependence(can->getDependence())),
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index d63a2389ea11de..eaeb40618e76bf 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -11,6 +11,9 @@
/// (e.g. `counted_by`)
///
//===----------------------------------------------------------------------===//
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Sema/Initialization.h"
#include "clang/Sema/Sema.h"
namespace clang {
@@ -102,7 +105,36 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
// only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
// when `FieldTy->isArrayType()`.
bool ShouldWarn = false;
- if (PointeeTy->isIncompleteType() && !CountInBytes) {
+ if (PointeeTy->isIncompletableIncompleteType() && !CountInBytes) {
+ // In general using `counted_by` or `counted_by_or_null` on
+ // pointers where the pointee is an incomplete type are problematic. This is
+ // because it isn't possible to compute the pointer's bounds without knowing
+ // the pointee type size. At the same time it is common to forward declare
+ // types in header files.
+ //
+ // E.g.:
+ //
+ // struct Handle;
+ // struct Wrapper {
+ // size_t size;
+ // struct Handle* __counted_by(count) handles;
+ // }
+ //
+ // To allow the above code pattern but still prevent the pointee type from
+ // being incomplete in places where bounds checks are needed the following
+ // scheme is used:
+ //
+ // * When the pointee type is a "completable incomplete" type (i.e.
+ // a type that is currently incomplete but might be completed later
+ // on in the translation unit) the attribute is allowed by this method
+ // but later uses of the FieldDecl are checked that the pointee type
+ // is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`,
+ // `BoundsSafetyCheckInitialization`, and
+ // `BoundsSafetyCheckUseOfCountAttrPtr`
+ //
+ // * When the pointee type is a "incompletable incomplete" type (e.g.
+ // `void`) the attribute is disallowed by this method because we know the
+ // type can never be completed so there's no reason to allow it.
InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
} else if (PointeeTy->isSizelessType()) {
InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
@@ -186,4 +218,372 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
return false;
}
+SourceRange Sema::BoundsSafetySourceRangeFor(const CountAttributedType *CATy) {
+ // Note: This implementation relies on `CountAttributedType` being unique.
+ // E.g.:
+ //
+ // struct Foo {
+ // int count;
+ // char* __counted_by(count) buffer;
+ // char* __counted_by(count) buffer2;
+ // };
+ //
+ // The types of `buffer` and `buffer2` are unique. The types being
+ // unique means the SourceLocation of the `counted_by` expression can be used
+ // to find where the attribute was written.
+
+ auto Fallback = CATy->getCountExpr()->getSourceRange();
+ auto CountExprBegin = CATy->getCountExpr()->getBeginLoc();
+
+ // FIXME: We currently don't support the count expression being a macro
+ // itself. E.g.:
+ //
+ // #define ZERO 0
+ // int* __counted_by(ZERO) x;
+ //
+ if (SourceMgr.isMacroBodyExpansion(CountExprBegin))
+ return Fallback;
+
+ auto FetchIdentifierTokenFromOffset =
+ [&](ssize_t Offset) -> std::optional<Token> {
+ SourceLocation OffsetLoc = CountExprBegin.getLocWithOffset(Offset);
+ Token Result;
+ if (Lexer::getRawToken(OffsetLoc, Result, SourceMgr, getLangOpts()))
+ return std::optional<Token>(); // Failed
+
+ if (!Result.isAnyIdentifier())
+ return std::optional<Token>(); // Failed
+
+ return Result; // Success
+ };
+
+ auto CountExprEnd = CATy->getCountExpr()->getEndLoc();
+ auto FindRParenTokenAfter = [&]() -> std::optional<Token> {
+ auto CountExprEndSpelling = SourceMgr.getSpellingLoc(CountExprEnd);
+ auto MaybeRParenTok = Lexer::findNextToken(
+ CountExprEndSpelling, getSourceManager(), getLangOpts());
+
+ if (!MaybeRParenTok.has_value())
+ return std::nullopt;
+
+ if (!MaybeRParenTok->is(tok::r_paren))
+ return std::nullopt;
+
+ return *MaybeRParenTok;
+ };
+
+ // Step back two characters to point at the last character of the attribute
+ // text.
+ //
+ // __counted_by(count)
+ // ^ ^
+ // | |
+ // ---
+ auto MaybeLastAttrCharToken = FetchIdentifierTokenFromOffset(-2);
+ if (!MaybeLastAttrCharToken)
+ return Fallback;
+
+ auto LastAttrCharToken = MaybeLastAttrCharToken.value();
+
+ if (LastAttrCharToken.getLength() > 1) {
+ // Special case: When the character is part of a macro the Token we get
+ // is the whole macro name (e.g. `__counted_by`).
+ if (LastAttrCharToken.getRawIdentifier() !=
+ CATy->GetAttributeName(/*WithMacroPrefix=*/true))
+ return Fallback;
+
+ // Found the beginning of the `__counted_by` macro
+ SourceLocation Begin = LastAttrCharToken.getLocation();
+ // Now try to find the closing `)` of the macro.
+ auto MaybeRParenTok = FindRParenTokenAfter();
+ if (!MaybeRParenTok.has_value())
+ return Fallback;
+
+ return SourceRange(Begin, MaybeRParenTok->getLocation());
+ }
+
+ assert(LastAttrCharToken.getLength() == 1);
+ // The Token we got back is just the last character of the identifier.
+ // This means a macro is not being used and instead the attribute is being
+ // used directly. We need to find the beginning of the identifier. We support
+ // two cases:
+ //
+ // * Non-affixed version. E.g: `counted_by`
+ // * Affixed version. E.g.: `__counted_by__`
+
+ // Try non-affixed version. E.g.:
+ //
+ // __attribute__((counted_by(count)))
+ // ^ ^
+ // | |
+ // ------------
+
+ // +1 is for `(`
+ const ssize_t NonAffixedSkipCount =
+ CATy->GetAttributeName(/*WithMacroPrefix=*/false).size() + 1;
+ auto MaybeNonAffixedBeginToken =
+ FetchIdentifierTokenFromOffset(-NonAffixedSkipCount);
+ if (!MaybeNonAffixedBeginToken)
+ return Fallback;
+
+ auto NonAffixedBeginToken = MaybeNonAffixedBeginToken.value();
+ if (NonAffixedBeginToken.getRawIdentifier() ==
+ CATy->GetAttributeName(/*WithMacroPrefix=*/false)) {
+ // Found the beginning of the `counted_by`-like attribute
+ auto SL = NonAffixedBeginToken.getLocation();
+
+ // Now try to find the closing `)` of the attribute
+ auto MaybeRParenTok = FindRParenTokenAfter();
+ if (!MaybeRParenTok.has_value())
+ return Fallback;
+
+ return SourceRange(SL, MaybeRParenTok->getLocation());
+ }
+
+ // Try affixed version. E.g.:
+ //
+ // __attribute__((__counted_by__(count)))
+ // ^ ^
+ // | |
+ // ----------------
+ std::string AffixedTokenStr =
+ (llvm::Twine("__") + CATy->GetAttributeName(/*WithMacroPrefix=*/false) +
+ llvm::Twine("__"))
+ .str();
+ // +1 is for `(`
+ // +4 is for the 4 `_` characters
+ const ssize_t AffixedSkipCount =
+ CATy->GetAttributeName(/*WithMacroPrefix=*/false).size() + 1 + 4;
+ auto MaybeAffixedBeginToken =
+ FetchIdentifierTokenFromOffset(-AffixedSkipCount);
+ if (!MaybeAffixedBeginToken)
+ return Fallback;
+
+ auto AffixedBeginToken = MaybeAffixedBeginToken.value();
+ if (AffixedBeginToken.getRawIdentifier() != AffixedTokenStr)
+ return Fallback;
+
+ // Found the beginning of the `__counted_by__`-like like attribute.
+ auto SL = AffixedBeginToken.getLocation();
+ // Now try to find the closing `)` of the attribute
+ auto MaybeRParenTok = FindRParenTokenAfter();
+ if (!MaybeRParenTok.has_value())
+ return Fallback;
+
+ return SourceRange(SL, MaybeRParenTok->getLocation());
+}
+
+static void EmitIncompleteCountedByPointeeNotes(Sema &S,
+ const CountAttributedType *CATy,
+ NamedDecl *IncompleteTyDecl,
+ bool NoteAttrLocation = true) {
+ assert(IncompleteTyDecl == nullptr || isa<TypeDecl>(IncompleteTyDecl));
+
+ if (NoteAttrLocation) {
+ // Note where the attribute is declared
+ auto AttrSrcRange = S.BoundsSafetySourceRangeFor(CATy);
+ S.Diag(AttrSrcRange.getBegin(), diag::note_named_attribute)
+ << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << AttrSrcRange;
+ }
+
+ if (!IncompleteTyDecl)
+ return;
+
+ // If there's an associated forward declaration display it to emphasize
+ // why the type is incomplete (all we have is a forward declaration).
+
+ // Note the `IncompleteTyDecl` type is the underlying type which might not
+ // be the same as `CATy->getPointeeType()` which could be a typedef.
+ //
+ // The diagnostic printed will be at the location of the underlying type but
+ // the diagnostic text will print the type of `CATy->getPointeeType()` which
+ // could be a typedef name rather than the underlying type. This is ok
+ // though because the diagnostic will print the underlying type name too.
+ // E.g:
+ //
+ // `forward declaration of 'Incomplete_Struct_t'
+ // (aka 'struct IncompleteStructTy')`
+ //
+ // If this ends up being confusing we could emit a second diagnostic (one
+ // explaining where the typedef is) but that seems overly verbose.
+
+ S.Diag(IncompleteTyDecl->getBeginLoc(), diag::note_forward_declaration)
+ << CATy->getPointeeType();
+}
+
+static bool
+HasCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND,
+ const CountAttributedType **CATyOut,
+ QualType *PointeeTyOut) {
+ auto *CATy = Ty->getAs<CountAttributedType>();
+ if (!CATy)
+ return false;
+
+ // Incomplete pointee type is only a problem for
+ // counted_by/counted_by_or_null
+ if (CATy->isCountInBytes())
+ return false;
+
+ auto PointeeTy = CATy->getPointeeType();
+ if (PointeeTy.isNull())
+ return false; // Reachable?
+
+ if (!PointeeTy->isIncompleteType(ND))
+ return false;
+
+ if (CATyOut)
+ *CATyOut = CATy;
+ if (PointeeTyOut)
+ *PointeeTyOut = PointeeTy;
+ return true;
+}
+
+/// Perform Checks for assigning to a `__counted_by` or
+/// `__counted_by_or_null` pointer type \param LHSTy where the pointee type
+/// is incomplete which is invalid.
+///
+/// \param S The Sema instance.
+/// \param LHSTy The type being assigned to. Checks will only be performed if
+/// the type is a `counted_by` or `counted_by_or_null ` pointer.
+/// \param RHSExpr The expression being assigned from.
+/// \param Action The type assignment being performed
+/// \param Loc The SourceLocation to use for error diagnostics
+/// \param ComputeAssignee If provided this function will be called before
+/// emitting a diagnostic. The function should return the name of
+/// entity being assigned to or an empty string if this cannot be
+/// determined.
+///
+/// \returns True iff no diagnostic where emitted, false otherwise.
+static bool BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+ Sema &S, QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
+ SourceLocation Loc, std::function<std::string()> ComputeAssignee) {
+ NamedDecl *IncompleteTyDecl = nullptr;
+ const CountAttributedType *CATy = nullptr;
+ QualType PointeeTy;
+ if (!HasCountedByAttrOnIncompletePointee(LHSTy, &IncompleteTyDecl, &CATy,
+ &PointeeTy))
+ return true;
+ assert(CATy && !CATy->isCountInBytes() && !PointeeTy.isNull());
+
+ // It's not expected that the diagnostic be emitted in these cases.
+ // It's not necessarily a problem but we should catch when this starts
+ // to happen.
+ assert(Action != AssignmentAction::Converting &&
+ Action != AssignmentAction::Sending &&
+ Action != AssignmentAction::Casting &&
+ Action != AssignmentAction::Passing_CFAudited);
+
+ // By having users provide a function we only pay the cost of allocation the
+ // string and computing when a diagnostic is emitted.
+ std::string Assignee = ComputeAssignee ? ComputeAssignee() : "";
+ {
+ auto D = S.Diag(Loc, diag::err_counted_by_on_incomplete_type_on_assign)
+ << /*0*/ (int)Action << /*1*/ Assignee
+ << /*2*/ (Assignee.size() > 0)
+ << /*3*/ isa<ImplicitValueInitExpr>(RHSExpr) << /*4*/ LHSTy
+ << /*5*/ CATy->GetAttributeName(/*WithMacroPrefix=*/true)
+ << /*6*/ PointeeTy << /*7*/ CATy->isOrNull();
+
+ if (RHSExpr->getSourceRange().isValid())
+ D << RHSExpr->getSourceRange();
+ }
+
+ EmitIncompleteCountedByPointeeNotes(S, CATy, IncompleteTyDecl);
+ return false; // check failed
+}
+
+bool Sema::BoundsSafetyCheckAssignmentToCountAttrPtr(
+ QualType LHSTy, Expr *RHSExpr, AssignmentAction Action, SourceLocation Loc,
+ std::function<std::string()> ComputeAssignee) {
+ return BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+ *this, LHSTy, RHSExpr, Action, Loc, ComputeAssignee);
+}
+
+bool Sema::BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
+ const InitializationKind &Kind,
+ AssignmentAction Action,
+ QualType LHSType, Expr *RHSExpr) {
+ bool ChecksPassed = true;
+ auto SL = Kind.getLocation();
+
+ // Note: We don't call `BoundsSafetyCheckAssignmentToCountAttrPtr` here
+ // because we need conditionalize what is checked. In downstream
+ // Clang `counted_by` is supported on variable definitions and in that
+ // implementation an error diagnostic will be emitted on the variable
+ // definition if the pointee is an incomplete type. To avoid warning about the
+ // same problem twice (once when the variable is defined, once when Sema
+ // checks the initializer) we skip checking the initializer if it's a
+ // variable.
+ if (Action == AssignmentAction::Initializing &&
+ Entity.getKind() != InitializedEntity::EK_Variable) {
+
+ if (!BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+ *this, LHSType, RHSExpr, Action, SL, [&Entity]() -> std::string {
+ if (const auto *VD =
+ dyn_cast_or_null<ValueDecl>(Entity.getDecl())) {
+ return VD->getQualifiedNameAsString();
+ }
+ return "";
+ })) {
+ ChecksPassed = false;
+
+ // It's not necessarily bad if this assert fails but we should catch
+ // if this happens.
+ assert(Entity.getKind() == InitializedEntity::EK_Member);
+ }
+ }
+
+ return ChecksPassed;
+}
+
+static bool BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy(Sema &S,
+ Expr *E) {
+ QualType T = E->getType();
+ assert(T->isPointerType());
+
+ const CountAttributedType *CATy = nullptr;
+ QualType PointeeTy;
+ NamedDecl *IncompleteTyDecl = nullptr;
+ if (!HasCountedByAttrOnIncompletePointee(T, &IncompleteTyDecl, &CATy,
+ &PointeeTy))
+ return true;
+ assert(CATy && !CATy->isCountInBytes() && !PointeeTy.isNull());
+
+ // Generate a string for the diagnostic that describes the "use".
+ // The string is specialized for direct calls to produce a better
+ // diagnostic.
+ StringRef DirectCallFn;
+ std::string UseStr;
+ if (const auto *CE = dyn_cast<CallExpr>(E->IgnoreParens())) {
+ if (const auto *FD = CE->getDirectCallee()) {
+ DirectCallFn = FD->getName();
+ }
+ }
+ int SelectExprKind = DirectCallFn.size() > 0 ? 1 : 0;
+ if (SelectExprKind) {
+ UseStr = DirectCallFn;
+ } else {
+ llvm::raw_string_ostream SS(UseStr);
+ E->printPretty(SS, nullptr, PrintingPolicy(S.getPrintingPolicy()));
+ }
+ assert(UseStr.size() > 0);
+
+ S.Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
+ << /*0*/ SelectExprKind << /*1*/ UseStr << /*2*/ T << /*3*/ PointeeTy
+ << /*4*/ CATy->GetAttributeName(/*WithMacroPrefix=*/true)
+ << /*5*/ CATy->isOrNull() << E->getSourceRange();
+
+ EmitIncompleteCountedByPointeeNotes(S, CATy, IncompleteTyDecl);
+ return false;
+}
+
+bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(Expr *E) {
+
+ QualType T = E->getType();
+ if (!T->isPointerType())
+ return true;
+
+ return BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy(*this, E);
+}
+
} // namespace clang
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 6807f44562f6ca..c7712d8b5cb7ad 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -734,6 +734,9 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {
if (E->getType().isDestructedType() == QualType::DK_nontrivial_c_struct)
Cleanup.setExprNeedsCleanups(true);
+ if (!BoundsSafetyCheckUseOfCountAttrPtr(Res.get()))
+ return ExprError();
+
// C++ [conv.lval]p3:
// If T is cv std::nullptr_t, the result is a null pointer constant.
CastKind CK = T->isNullPtrType() ? CK_NullToPointer : CK_LValueToRValue;
@@ -13642,6 +13645,27 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
QualType LHSType = LHSExpr->getType();
QualType RHSType = CompoundType.isNull() ? RHS.get()->getType() :
CompoundType;
+
+ if (RHS.isUsable()) {
+ // Even if this check fails don't return early to allow the best
+ // possible error recovery and to allow any subsequent diagnostics to
+ // work.
+ (void)BoundsSafetyCheckAssignmentToCountAttrPtr(
+ LHSType, RHS.get(), AssignmentAction::Assigning, Loc,
+ [&LHSExpr]() -> std::string {
+ // In simple cases describe what is being assigned to
+ if (auto *DR = dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenCasts())) {
+ auto *II = DR->getDecl()->getDeclName().getAsIdentifierInfo();
+ if (II)
+ return II->getName().str();
+ } else if (auto *ME =
+ dyn_cast<MemberExpr>(LHSExpr->IgnoreParenCasts())) {
+ return ME->getMemberDecl()->getQualifiedNameAsString();
+ }
+ return "";
+ });
+ }
+
// OpenCL v1.2 s6.1.1.1 p2:
// The half data type can only be used to declare a pointer to a buffer that
// contains half values
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index f560865681fa5a..eee16a5ca00e72 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8207,6 +8207,15 @@ ExprResult InitializationSequence::Perform(Sema &S,
Kind.getRange().getEnd());
} else {
CurInit = new (S.Context) ImplicitValueInitExpr(Step->Type);
+ // Note the return value isn't used to return early
+ // to preserve the AST as best as possible even though an error
+ // might have occurred. For struct initialization it also allows
+ // all field assignments to be checked rather than bailing on the
+ // first error.
+ S.BoundsSafetyCheckInitialization(Entity, Kind,
+ AssignmentAction::Initializing,
+ /*LHSType=*/Step->Type,
+ /*RHSExpr=*/CurInit.get());
}
break;
}
@@ -8253,6 +8262,15 @@ ExprResult InitializationSequence::Perform(Sema &S,
}
}
+ // Note the return value isn't used to return early so that additional
+ // diagnostics can be emitted and to preserve the AST as best as possible
+ // even though an error might have occurred. For struct initialization it
+ // also allows all field assignments to be checked rather than bailing on
+ // the first error.
+ (void)S.BoundsSafetyCheckInitialization(
+ Entity, Kind, /*Action=*/getAssignmentAction(Entity, true),
+ /*LHSType=*/Step->Type, /*RHSExpr=*/InitialCurInit.get());
+
bool Complained;
if (S.DiagnoseAssignmentResult(ConvTy, Kind.getLocation(),
Step->Type, SourceType,
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
index 9ff3b080f6576b..8d4e0c510603a2 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
@@ -19,13 +19,12 @@ struct on_member_pointer_complete_ty {
};
struct on_member_pointer_incomplete_ty {
- struct size_unknown * buf __counted_by(count); // expected-error{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
+ struct size_unknown * buf __counted_by(count); // ok
int count;
};
struct on_member_pointer_const_incomplete_ty {
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * buf __counted_by(count);
+ const struct size_unknown * buf __counted_by(count); // ok
int count;
};
diff --git a/clang/test/Sema/attr-counted-by-or-null-last-field.c b/clang/test/Sema/attr-counted-by-or-null-last-field.c
index 12c0b6de44f726..60a1f571b19e95 100644
--- a/clang/test/Sema/attr-counted-by-or-null-last-field.c
+++ b/clang/test/Sema/attr-counted-by-or-null-last-field.c
@@ -118,12 +118,12 @@ struct annotated_with_anon_struct {
struct count_unknown;
struct on_member_ptr_incomplete_ty_ty_pos {
int count;
- struct count_unknown * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct count_unknown' is an incomplete type}}
+ struct count_unknown * ptr __counted_by_or_null(count); // ok
};
struct on_member_ptr_incomplete_const_ty_ty_pos {
int count;
- const struct count_unknown * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const struct count_unknown' is an incomplete type}}
+ const struct count_unknown * ptr __counted_by_or_null(count); // ok
};
struct on_member_ptr_void_ty_ty_pos {
diff --git a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
index 95f517e3144f72..2150c81f9e9bec 100644
--- a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
@@ -20,13 +20,12 @@ struct on_member_pointer_complete_ty {
};
struct on_member_pointer_incomplete_ty {
- struct size_unknown * buf __counted_by_or_null(count); // expected-error{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
+ struct size_unknown * buf __counted_by_or_null(count); // ok
int count;
};
struct on_member_pointer_const_incomplete_ty {
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * buf __counted_by_or_null(count);
+ const struct size_unknown * buf __counted_by_or_null(count); // ok
int count;
};
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
index 708bb727ce09da..0bb09059c97f91 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
@@ -22,14 +22,12 @@ struct on_member_pointer_complete_ty {
struct on_member_pointer_incomplete_ty {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
- struct size_unknown * buf __counted_by_or_null(count);
+ struct size_unknown * buf __counted_by_or_null(count); // ok
};
struct on_member_pointer_const_incomplete_ty {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * buf __counted_by_or_null(count);
+ const struct size_unknown * buf __counted_by_or_null(count); // ok
};
struct on_member_pointer_void_ty {
@@ -116,14 +114,12 @@ struct on_member_pointer_complete_ty_ty_pos {
struct on_member_pointer_incomplete_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
- struct size_unknown * __counted_by_or_null(count) buf;
+ struct size_unknown * __counted_by_or_null(count) buf; // ok
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * __counted_by_or_null(count) buf;
+ const struct size_unknown * __counted_by_or_null(count) buf; // ok
};
struct on_member_pointer_void_ty_ty_pos {
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
new file mode 100644
index 00000000000000..8e2e163822b1fe
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
@@ -0,0 +1,584 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+// =============================================================================
+// # Struct incomplete type with attribute in the decl position
+// =============================================================================
+
+// Note: This could be considered misleading. The typedef isn't actually on this
+// line. Also note the discrepancy in diagnostic count (27 vs 51) is due to
+// the pointer arithmetic on incomplete pointee type diagnostic always using
+// diagnostic text that refers to the underlying forward decl, even when the
+// typedef is used.
+// expected-note at +1 27{{forward declaration of 'Incomplete_t' (aka 'struct IncompleteTy')}}
+struct IncompleteTy; // expected-note 51{{forward declaration of 'struct IncompleteTy'}}
+
+typedef struct IncompleteTy Incomplete_t;
+
+struct CBBufDeclPos {
+ int count;
+ struct IncompleteTy* buf __counted_by(count); // OK expected-note 27{{__counted_by attribute is here}}
+ Incomplete_t* buf_typedef __counted_by(count); // OK expected-note 27{{__counted_by attribute is here}}
+};
+
+void consume_struct_IncompleteTy(struct IncompleteTy* buf);
+
+int idx(void);
+
+
+
+void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
+ // ===========================================================================
+ // ## Local variable initialization
+ // ===========================================================================
+ struct CBBufDeclPos explicit_desig_init = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+ };
+ // Variable is not currently marked as invalid so uses of the variable allows
+ // diagnostics to fire.
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf_typedef = 0x0;
+ // expected-error at +1{{cannot use 'explicit_desig_init.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ void *tmp = explicit_desig_init.buf;
+ // expected-error at +1{{cannot use 'explicit_desig_init.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ void *tmp2 = explicit_desig_init.buf_typedef;
+
+ struct CBBufDeclPos partial_explicit_desig_init = {
+ .count = 0,
+ // .buf and .buf_typedef are implicit zero initialized
+ // expected-error at +2{{cannot implicitly initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ // expected-error at +1{{cannot implicitly initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ };
+
+ struct CBBufDeclPos implicit_full_init = {
+ 0
+ // expected-error at +2{{cannot implicitly initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ // expected-error at +1{{cannot implicitly initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ };
+ // Variable is not currently marked as invalid so uses of the variable allows
+ // diagnostics to fire.
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ implicit_full_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ implicit_full_init.buf_typedef = 0x0;
+ // expected-error at +1{{cannot use 'implicit_full_init.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ void* tmp3 = implicit_full_init.buf;
+ // expected-error at +1{{cannot use 'implicit_full_init.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ void* tmp4 = implicit_full_init.buf_typedef;
+
+ struct CBBufDeclPos explicit_non_desig_init = {
+ 0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ 0x0
+ };
+
+ // ===========================================================================
+ // ## Assignment to fields
+ // ===========================================================================
+ struct CBBufDeclPos uninit;
+ uninit.count = 0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ uninit.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ uninit.buf_typedef = 0x0;
+ ptr->count = 0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ ptr->buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ ptr->buf_typedef = 0x0;
+
+
+ // ===========================================================================
+ // ## Make sure modifying the fields through other assignment operators is not
+ // allowed
+ // ===========================================================================
+ uninit.buf++; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ ++uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ uninit.buf += 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ uninit.buf_typedef++; // // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ ++uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+
+ uninit.buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ --uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ uninit.buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ uninit.buf_typedef--; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ --uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+
+ ptr->buf++; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ ++ptr->buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ ptr->buf += 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ ptr->buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ --ptr->buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+ ptr->buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
+
+ ptr->buf_typedef++; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ ++ptr->buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ ptr->buf_typedef += 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ ptr->buf_typedef--; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ --ptr->buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+ ptr->buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
+
+ // ===========================================================================
+ // ## Use of fields in expressions
+ // ===========================================================================
+ // expected-error at +2{{cannot use 'uninit.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ void* addr =
+ ((char*) uninit.buf ) + 1;
+ // expected-error at +2{{cannot use 'uninit.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ void* addr_typedef =
+ ((char*) uninit.buf_typedef ) + 1;
+ // expected-error at +2{{cannot use 'ptr->buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ void* addr_ptr =
+ ((char*) ptr->buf ) + 1;
+ // expected-error at +2{{cannot use 'ptr->buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ void* addr_ptr_typedef =
+ ((char*) ptr->buf_typedef ) + 1;
+
+ // ===========================================================================
+ // ## Take address of fields
+ // ===========================================================================
+ // TODO: This should be forbidden, not because of the incomplete pointee type
+ // but because in the -fbounds-safety language model the address of a
+ // `counted_by` pointer cannot be taken to avoid it being possible to modify
+ // the `counted_by` pointer through another pointer. Whether or not this
+ // should be forbidden when `-fbounds-safety` is off is TBD.
+ //
+ // The incomplete pointee type isn't actually a problem here for
+ // `-fbounds-safety` because taking the address of a pointer returns a pointer
+ // that have the bounds of a single `void*`, so bounds checks on the resulting
+ // pointer don't need to know `sizeof(struct IncompleteTy)` but instead
+ // `sizeof(struct IncompleteTy* buf __counted_by(count))` which is just the
+ // size of a pointer.
+ void* take_addr = &uninit.buf;
+ void* take_addr_typedef = &uninit.buf_typedef;
+ void* take_addr_ptr = &ptr->buf;
+ void* take_addr_ptr_typedef = &ptr->buf_typedef;
+
+ // expected-error at +1{{cannot use 'uninit.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_zero = &uninit.buf[0];
+ // expected-error at +1{{cannot use 'uninit.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_idx = &uninit.buf[idx()];
+
+ // expected-error at +1{{cannot use 'uninit.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_zero_typedef = &uninit.buf_typedef[0];
+ // expected-error at +1{{cannot use 'uninit.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_idx_typedef = &uninit.buf_typedef[idx()];
+
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_zero_ptr = &ptr->buf[0];
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_idx_ptr = &ptr->buf[idx()];
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_zero_ptr_typedef = &ptr->buf_typedef[0];
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ struct IncompleteTy* addr_elt_idx_ptr_typedef = &ptr->buf_typedef[idx()];
+
+ // ===========================================================================
+ // ## Use fields as call arguments
+ // ===========================================================================
+ // expected-error at +1{{cannot use 'uninit.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy(uninit.buf);
+ // expected-error at +1{{cannot use 'uninit.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy(uninit.buf_typedef);
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy(ptr->buf);
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy(ptr->buf_typedef);
+
+ // ===========================================================================
+ // ## Use [] operator on fields
+ // ===========================================================================
+ // expected-error at +1 2{{cannot use 'uninit.buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ uninit.buf[0] = uninit.buf[1];
+ // expected-error at +1 2{{cannot use 'uninit.buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ uninit.buf_typedef[0] = uninit.buf_typedef[1];
+ // expected-error at +1 2{{cannot use 'ptr->buf' with type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ ptr->buf[0] = ptr->buf[1];
+ // expected-error at +1 2{{cannot use 'ptr->buf_typedef' with type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ ptr->buf_typedef[0] = ptr->buf_typedef[1];
+}
+
+// =============================================================================
+// ## Global initialization
+// =============================================================================
+
+struct CBBufDeclPos global_explicit_desig_init = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+};
+
+void use_global_explicit_desig_init(void) {
+ // Variable isn't marked as invalid so diagnostics still fire
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ global_explicit_desig_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ global_explicit_desig_init.buf_typedef = 0x0;
+}
+
+struct CBBufDeclPos global_partial_explicit_desig_init = {
+ .count = 0,
+ // .buf and .buf_typedef are implicit zero initialized
+ // expected-error at +2{{cannot implicitly initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ // expected-error at +1{{cannot implicitly initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+};
+
+struct CBBufDeclPos global_implicit_full_init = {
+ 0
+ // expected-error at +2{{cannot implicitly initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ // expected-error at +1{{cannot implicitly initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+};
+
+struct CBBufDeclPos global_explicit_non_desig_init = {
+ 0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' that has type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy' or using the '__sized_by' attribute}}
+ 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf_typedef' that has type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_t' or using the '__sized_by' attribute}}
+ 0x0
+};
+
+extern struct CBBufDeclPos global_declaration; // OK
+
+// TODO: These tentative definitions are implicitly empty initialized to zero.
+// This should generate an error diagnostic but currently doesn't. There should
+// be a carve out to allow `__counted_by(0)` which is the only constant count
+// version of the attribute where it is valid to assign NULL.
+struct CBBufDeclPos global_tentative_defn;
+static struct CBBufDeclPos global_tentative_defn2;
+
+// =============================================================================
+// ## Completing the definition of the type allows use of CBBufDeclPos fields
+// =============================================================================
+struct IncompleteTy {
+ int field;
+};
+
+void test_CBBufDeclPos_completed(struct CBBufDeclPos* ptr) {
+ // Initialization is ok
+ struct CBBufDeclPos explicit_desig_init = {
+ .count = 0,
+ .buf = 0x0,
+ .buf_typedef = 0x0
+ };
+
+ struct CBBufDeclPos partial_explicit_desig_init = {
+ .count = 0,
+ // .buf and .buf_typedef are implicit zero initialized
+ };
+
+ struct CBBufDeclPos implicit_full_init = {0};
+
+ struct CBBufDeclPos explicit_non_desig_init = {
+ 0,
+ 0x0,
+ 0x0
+ };
+
+ // Assignment to fields is ok
+ ptr->buf = 0x0;
+ ptr->buf_typedef = 0x0;
+
+ // Use of fields in expressions is ok
+ void* tmp = ptr->buf;
+ void* tmp2 = ptr->buf_typedef;
+
+ // Take address of fields is ok
+ void* take_addr_ptr = &ptr->buf;
+ void* take_addr_ptr_typedef = &ptr->buf_typedef;
+
+ struct IncompleteTy* addr_elt_zero_ptr = &ptr->buf[0];
+ struct IncompleteTy* addr_elt_idx_ptr = &ptr->buf[idx()];
+ struct IncompleteTy* addr_elt_zero_ptr_typedef = &ptr->buf_typedef[0];
+ struct IncompleteTy* addr_elt_idx_ptr_typedef = &ptr->buf_typedef[idx()];
+
+ // As call arguments is ok
+ consume_struct_IncompleteTy(ptr->buf);
+ consume_struct_IncompleteTy(ptr->buf_typedef);
+
+ // In [] operator is ok
+ ptr->buf[0] = ptr->buf[1];
+ ptr->buf_typedef[0] = ptr->buf_typedef[1];
+}
+
+// Global initialization is ok
+
+struct CBBufDeclPos global_explicit_desig_init_completed = {
+ .count = 0,
+ .buf = 0x0,
+ .buf_typedef = 0x0
+};
+
+struct CBBufDeclPos global_partial_explicit_desig_init_completed = {
+ .count = 0,
+ // .buf and .buf_typedef are implicit zero initialized
+};
+
+struct CBBufDeclPos global_implicit_full_init_completed = {0};
+
+struct CBBufDeclPos global_explicit_non_desig_init_completed = {
+ 0,
+ 0x0,
+ 0x0
+};
+
+extern struct CBBufDeclPos global_declaration;
+struct CBBufDeclPos global_tentative_defn;
+static struct CBBufDeclPos global_tentative_defn2;
+
+// =============================================================================
+// # Struct incomplete type with attribute in the pointer position
+// =============================================================================
+
+// expected-note at +1 8{{forward declaration of 'Incomplete_ty2' (aka 'struct IncompleteTy2')}}
+struct IncompleteTy2; // expected-note 8{{forward declaration of 'struct IncompleteTy2'}}
+typedef struct IncompleteTy2 Incomplete_ty2;
+
+void consume_struct_IncompleteTy2(struct IncompleteTy2* buf);
+
+struct CBBufTyPos {
+ int count;
+ struct IncompleteTy2* __counted_by(count) buf ; // OK expected-note 8{{__counted_by attribute is here}}
+ Incomplete_ty2 *__counted_by(count) buf_typedef; // OK expected-note 8{{__counted_by attribute is here}}
+};
+
+void use_CBBufTyPos(struct CBBufTyPos* ptr) {
+ struct CBBufTyPos explicit_desig_init = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufTyPos::buf' that has type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufTyPos::buf_typedef' that has type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+ };
+
+ // Assignment
+ // expected-error at +1{{cannot assign to 'CBBufTyPos::buf' that has type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufTyPos::buf_typedef' that has type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf_typedef = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufTyPos::buf' that has type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ ptr->buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufTyPos::buf_typedef' that has type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ ptr->buf_typedef = 0x0;
+
+ // Use
+ // expected-error at +2{{cannot use 'ptr->buf' with type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ void* addr =
+ ((char*) ptr->buf ) + 1;
+ // expected-error at +2{{cannot use 'ptr->buf_typedef' with type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ void* addr_typedef =
+ ((char*) ptr->buf_typedef ) + 1;
+
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy2(ptr->buf);
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteTy2(ptr->buf_typedef);
+
+ // expected-error at +1 2{{cannot use 'ptr->buf' with type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ ptr->buf[0] = ptr->buf[1];
+ // expected-error at +1 2{{cannot use 'ptr->buf_typedef' with type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ ptr->buf_typedef[0] = ptr->buf_typedef[1];
+}
+
+struct CBBufTyPos global_explicit_desig_init_struct_type_pos = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufTyPos::buf' that has type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'struct IncompleteTy2' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufTyPos::buf_typedef' that has type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'Incomplete_ty2' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+};
+
+// Defining the type makes `CBBufTyPos` fields usable
+struct IncompleteTy2 {
+ int field;
+};
+
+void use_CBBufTyPos_completed(struct CBBufTyPos* ptr) {
+ ptr->buf = 0x0;
+ ptr->buf_typedef = 0x0;
+ void* addr = ((char*) ptr->buf) + 1;
+ void* addr_typedef = ((char*) ptr->buf_typedef) + 1;
+}
+
+// =============================================================================
+// # union incomplete type
+// =============================================================================
+
+// expected-note at +1 8{{forward declaration of 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy')}}
+union IncompleteUnionTy; // expected-note 8{{forward declaration of 'union IncompleteUnionTy'}}
+typedef union IncompleteUnionTy IncompleteUnion_ty;
+
+void consume_struct_IncompleteUnionTy(union IncompleteUnionTy* buf);
+
+struct CBBufUnionTyPos {
+ int count;
+ union IncompleteUnionTy* __counted_by(count) buf ; // OK expected-note 8{{__counted_by attribute is here}}
+ IncompleteUnion_ty *__counted_by(count) buf_typedef; // OK expected-note 8{{__counted_by attribute is here}}
+};
+
+void use_CBBufUnionTyPos(struct CBBufUnionTyPos* ptr) {
+ struct CBBufUnionTyPos explicit_desig_init = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufUnionTyPos::buf' that has type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufUnionTyPos::buf_typedef' that has type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+ };
+
+ // Assignment
+ // expected-error at +1{{cannot assign to 'CBBufUnionTyPos::buf' that has type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufUnionTyPos::buf_typedef' that has type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf_typedef = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufUnionTyPos::buf' that has type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ ptr->buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufUnionTyPos::buf_typedef' that has type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ ptr->buf_typedef = 0x0;
+
+ // Use
+ // expected-error at +2{{cannot use 'ptr->buf' with type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ void* addr =
+ ((char*) ptr->buf ) + 1;
+ // expected-error at +2{{cannot use 'ptr->buf_typedef' with type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ void* addr_typedef =
+ ((char*) ptr->buf_typedef ) + 1;
+
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteUnionTy(ptr->buf);
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteUnionTy(ptr->buf_typedef);
+
+ // expected-error at +1 2{{cannot use 'ptr->buf' with type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ ptr->buf[0] = ptr->buf[1];
+ // expected-error at +1 2{{cannot use 'ptr->buf_typedef' with type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ ptr->buf_typedef[0] = ptr->buf_typedef[1];
+}
+
+struct CBBufUnionTyPos global_explicit_desig_init_union_type_pos = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufUnionTyPos::buf' that has type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'union IncompleteUnionTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufUnionTyPos::buf_typedef' that has type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'IncompleteUnion_ty' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+};
+
+// Defining the type makes `CBBufUnionTyPos` fields usable
+union IncompleteUnionTy {
+ int field;
+};
+
+void use_CBBufUnionTyPos_completed(struct CBBufUnionTyPos* ptr) {
+ ptr->buf = 0x0;
+ ptr->buf_typedef = 0x0;
+ void* addr = ((char*) ptr->buf) + 1;
+ void* addr_typedef = ((char*) ptr->buf_typedef) + 1;
+}
+
+// =============================================================================
+// # enum incomplete type
+// =============================================================================
+
+// expected-note at +1 8{{forward declaration of 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy')}}
+enum IncompleteEnumTy; // expected-note 8{{forward declaration of 'enum IncompleteEnumTy'}}
+typedef enum IncompleteEnumTy IncompleteEnum_ty;
+
+void consume_struct_IncompleteEnumTy(enum IncompleteEnumTy* buf);
+
+struct CBBufEnumTyPos {
+ int count;
+ enum IncompleteEnumTy* __counted_by(count) buf ; // OK expected-note 8{{__counted_by attribute is here}}
+ IncompleteEnum_ty *__counted_by(count) buf_typedef; // OK expected-note 8{{__counted_by attribute is here}}
+};
+
+void use_CBBufEnumTyPos(struct CBBufEnumTyPos* ptr) {
+ struct CBBufEnumTyPos explicit_desig_init = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufEnumTyPos::buf' that has type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufEnumTyPos::buf_typedef' that has type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+ };
+
+ // Assignment
+ // expected-error at +1{{cannot assign to 'CBBufEnumTyPos::buf' that has type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufEnumTyPos::buf_typedef' that has type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ explicit_desig_init.buf_typedef = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufEnumTyPos::buf' that has type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ ptr->buf = 0x0;
+ // expected-error at +1{{cannot assign to 'CBBufEnumTyPos::buf_typedef' that has type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when assigning; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ ptr->buf_typedef = 0x0;
+
+ // Use
+ // expected-error at +2{{cannot use 'ptr->buf' with type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ void* addr =
+ ((char*) ptr->buf ) + 1;
+ // expected-error at +2{{cannot use 'ptr->buf_typedef' with type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ void* addr_typedef =
+ ((char*) ptr->buf_typedef ) + 1;
+
+ // expected-error at +1{{cannot use 'ptr->buf' with type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteEnumTy(ptr->buf);
+ // expected-error at +1{{cannot use 'ptr->buf_typedef' with type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ consume_struct_IncompleteEnumTy(ptr->buf_typedef);
+
+ // expected-error at +1 2{{cannot use 'ptr->buf' with type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ ptr->buf[0] = ptr->buf[1];
+ // expected-error at +1 2{{cannot use 'ptr->buf_typedef' with type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete in this context; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ ptr->buf_typedef[0] = ptr->buf_typedef[1];
+}
+
+struct CBBufEnumTyPos global_explicit_desig_init_enum_type_pos = {
+ .count = 0,
+ // expected-error at +1{{cannot initialize 'CBBufEnumTyPos::buf' that has type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'enum IncompleteEnumTy' or using the '__sized_by' attribute}}
+ .buf = 0x0,
+ // expected-error at +1{{cannot initialize 'CBBufEnumTyPos::buf_typedef' that has type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete and the '__counted_by' attribute requires the pointee type be complete when initializing; consider providing a complete definition for 'IncompleteEnum_ty' or using the '__sized_by' attribute}}
+ .buf_typedef = 0x0
+};
+
+// Defining the type makes `CBBufEnumTyPos` fields usable
+enum IncompleteEnumTy {
+ ONE,
+ TWO
+};
+
+void use_CBBufEnumTyPos_completed(struct CBBufEnumTyPos* ptr) {
+ ptr->buf = 0x0;
+ ptr->buf_typedef = 0x0;
+ void* addr = ((char*) ptr->buf) + 1;
+ void* addr_typedef = ((char*) ptr->buf_typedef) + 1;
+}
+
+// =============================================================================
+// # Array of __counted_by pointers
+// =============================================================================
+
+struct IncompleteTy3;
+
+struct CBBufFAMofCountedByPtrs {
+ int size;
+ // TODO: This is misleading. The attribute is written in the type position
+ // but clang currently doesn't treat it like that and it gets treated as
+ // an attribute on the array, rather than on the element type.
+ struct IncompleteTy3* __counted_by(size) arr[];
+};
+
+void arr_of_counted_by_ptr(struct CBBufFAMofCountedByPtrs* ptr) {
+ // TODO: Should be disallowed once parsing attributes in the type position
+ // works.
+ ptr->arr[0] = 0x0;
+ void* addr = ((char*) ptr->arr[0]) + 1;
+}
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c
index 321d6aafbeba24..c05d18262e2b77 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -21,14 +21,12 @@ struct on_member_pointer_complete_ty {
struct on_member_pointer_incomplete_ty {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
- struct size_unknown * buf __counted_by(count);
+ struct size_unknown * buf __counted_by(count); // ok
};
struct on_member_pointer_const_incomplete_ty {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * buf __counted_by(count);
+ const struct size_unknown * buf __counted_by(count); // ok
};
struct on_member_pointer_void_ty {
@@ -115,14 +113,12 @@ struct on_member_pointer_complete_ty_ty_pos {
struct on_member_pointer_incomplete_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct size_unknown' is an incomplete type}}
- struct size_unknown * __counted_by(count) buf;
+ struct size_unknown * __counted_by(count) buf; // ok
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const struct size_unknown' is an incomplete type}}
- const struct size_unknown * __counted_by(count) buf;
+ const struct size_unknown * __counted_by(count) buf; // ok
};
struct on_member_pointer_void_ty_ty_pos {
diff --git a/clang/test/Sema/attr-counted-by-vla.c b/clang/test/Sema/attr-counted-by-vla.c
index 35737c03f3222e..5b1b4833dce7df 100644
--- a/clang/test/Sema/attr-counted-by-vla.c
+++ b/clang/test/Sema/attr-counted-by-vla.c
@@ -98,8 +98,7 @@ struct array_of_ints_count {
struct not_a_fam {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct bar' is an incomplete type}}
- struct bar *non_fam __counted_by(count);
+ struct bar *non_fam __counted_by(count); // ok
};
struct not_a_c99_fam {
diff --git a/clang/unittests/Sema/CMakeLists.txt b/clang/unittests/Sema/CMakeLists.txt
index 7ded562e8edfa5..ebc829e82af6b5 100644
--- a/clang/unittests/Sema/CMakeLists.txt
+++ b/clang/unittests/Sema/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_unittest(SemaTests
ExternalSemaSourceTest.cpp
CodeCompleteTest.cpp
+ GetCountedByAttrSourceRange.cpp
GslOwnerPointerInference.cpp
SemaLookupTest.cpp
SemaNoloadLookupTest.cpp
diff --git a/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp b/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
new file mode 100644
index 00000000000000..3333ed046bff82
--- /dev/null
+++ b/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
@@ -0,0 +1,573 @@
+//=== unittests/Sema/GetCountedByAttrSourceRange.cpp =========================//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+// FIXME: audit these header files
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Sema/Sema.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Testing/Annotations/Annotations.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace clang::ast_matchers;
+
+using CallBackTy =
+ std::function<void(CompilerInstance &, const CountAttributedType *)>;
+
+bool runOnToolAndCheckAttrSourceRange(llvm::Annotations &Code,
+ bool TestForFallback = false) {
+ class RunFnAfterSema : public clang::ASTFrontendAction {
+ llvm::Annotations &Code;
+ bool TestForFallback;
+
+ public:
+ RunFnAfterSema(llvm::Annotations &Code, bool TestForFallback)
+ : Code(Code), TestForFallback(TestForFallback) {}
+ void EndSourceFile() override {
+ auto &S = getCompilerInstance().getSema();
+ // Find `TestStruct::ptr`
+ auto Matched = match(
+ recordDecl(hasName("TestStruct"),
+ hasDescendant(fieldDecl(hasName("ptr")).bind("ptr"))),
+ S.getASTContext());
+ EXPECT_EQ(Matched.size(), 1u);
+ auto *FD = Matched[0].getNodeAs<FieldDecl>("ptr");
+ ASSERT_NE(FD, nullptr);
+
+ const auto *CATy = FD->getType()->getAs<CountAttributedType>();
+
+ auto SR = S.BoundsSafetySourceRangeFor(CATy);
+
+ if (TestForFallback) {
+ // Make sure for these types of test cases that there are no
+ // annotations. The presence of annotations for this type of test would
+ // be very misleading because they aren't being checked.
+ const auto &Ranges = Code.all_ranges();
+ ASSERT_EQ(Ranges.size(), 0U);
+
+ // The fallback is using the SourceRange of the CountExpr.
+ ASSERT_EQ(SR, CATy->getCountExpr()->getSourceRange());
+ // Don't test for precise column position in this case.
+ // The code below doesn't really work correctly when the
+ // count expression is itself a macro.
+ return;
+ }
+
+ // Check Begin
+ auto ExpectedBegin = Code.range("attr").Begin;
+ auto Begin = SR.getBegin();
+ auto BeginSpellingLoc = S.getSourceManager().getSpellingLoc(Begin);
+ auto BeginFileOffset =
+ S.getSourceManager().getFileOffset(BeginSpellingLoc);
+ ASSERT_EQ(BeginFileOffset, ExpectedBegin);
+ // Check End
+ auto ExpectedEnd = Code.range("attr").End - 1;
+ auto End = SR.getEnd();
+ auto EndSpellingLoc = S.getSourceManager().getSpellingLoc(End);
+ auto EndFileOffset = S.getSourceManager().getFileOffset(EndSpellingLoc);
+ ASSERT_EQ(EndFileOffset, ExpectedEnd);
+
+ ASTFrontendAction::EndSourceFile();
+ }
+
+ std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+ StringRef InFile) override {
+ return std::make_unique<ASTConsumer>();
+ }
+ };
+ auto Action = std::make_unique<RunFnAfterSema>(Code, TestForFallback);
+ return clang::tooling::runToolOnCodeWithArgs(std::move(Action), Code.code(),
+ {"-std=c11"}, "test.c");
+}
+
+//==============================================================================
+// counted_by
+//==============================================================================
+
+TEST(GetCountedByAttrSourceRange, GNUAttrNoAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by(X) __attribute__((counted_by(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[counted_by(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by(X) __attribute__((counted_by(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((counted_by(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, GNUAttrAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by(X) __attribute__((__counted_by__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[counted_by(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, GNUAttrAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by(X) __attribute__((__counted_by__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((counted_by(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, MacroNoAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by(X) __attribute__((counted_by(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__counted_by(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, MacroAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by(X) __attribute__((__counted_by__(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__counted_by(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, CustomMacro) {
+ // For this code no special SourceRange is computed.
+ llvm::Annotations Code(
+ R"C(
+#define custom_cb(X) __attribute__((__counted_by__(X)))
+struct TestStruct {
+ int count;
+ char* custom_cb(count) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByAttrSourceRange, MacroArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by(X) __attribute__((__counted_by__(X)))
+struct TestStruct {
+ int count;
+ char* __counted_by(COUNT) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code, true);
+ ASSERT_TRUE(result);
+}
+
+//==============================================================================
+// counted_by_or_null
+//==============================================================================
+
+TEST(GetCountedByOrNullAttrSourceRange, GNUAttrNoAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[counted_by_or_null(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((counted_by_or_null(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, GNUAttrAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[counted_by_or_null(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, GNUAttrAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((counted_by_or_null(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, MacroNoAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__counted_by_or_null(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, MacroAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__counted_by_or_null(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, CustomMacro) {
+ // For this code no special SourceRange is computed.
+ llvm::Annotations Code(
+ R"C(
+#define custom_cbon(X) __attribute__((__counted_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* custom_cbon(count) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetCountedByOrNullAttrSourceRange, MacroArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __counted_by_or_null(COUNT) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code, true);
+ ASSERT_TRUE(result);
+}
+
+//==============================================================================
+// sized_by
+//==============================================================================
+
+TEST(GetSizedByAttrSourceRange, GNUAttrNoAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by(X) __attribute__((sized_by(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[sized_by(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by(X) __attribute__((sized_by(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((sized_by(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, GNUAttrAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by(X) __attribute__((__sized_by__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[sized_by(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, GNUAttrAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by(X) __attribute__((__sized_by__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((sized_by(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, MacroNoAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by(X) __attribute__((sized_by(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__sized_by(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, MacroAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by(X) __attribute__((__sized_by__(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__sized_by(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, CustomMacro) {
+ // For this code no special SourceRange is computed.
+ llvm::Annotations Code(
+ R"C(
+#define custom_sb(X) __attribute__((__sized_by__(X)))
+struct TestStruct {
+ int count;
+ char* custom_sb(count) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByAttrSourceRange, MacroArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by(X) __attribute__((__sized_by__(X)))
+struct TestStruct {
+ int count;
+ char* __sized_by(COUNT) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code, true);
+ ASSERT_TRUE(result);
+}
+
+//==============================================================================
+// sized_by_or_null
+//==============================================================================
+
+TEST(GetSizedByOrNullAttrSourceRange, GNUAttrNoAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[sized_by_or_null(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((sized_by_or_null(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, GNUAttrAffix) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__(($attr[[sized_by_or_null(count)]])) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, GNUAttrAffixArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __attribute__((sized_by_or_null(COUNT))) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, MacroNoAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__sized_by_or_null(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, MacroAffixImpl) {
+ llvm::Annotations Code(
+ R"C(
+#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* $attr[[__sized_by_or_null(count)]] ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, CustomMacro) {
+ // For this code no special SourceRange is computed.
+ llvm::Annotations Code(
+ R"C(
+#define custom_sbon(X) __attribute__((__sized_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* custom_sbon(count) ptr;
+};
+)C");
+ bool result =
+ runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
+ ASSERT_TRUE(result);
+}
+
+TEST(GetSizedByOrNullAttrSourceRange, MacroArgIsMacro) {
+ // For this code no special SourceRange is computed
+ // FIXME: This shouldn't use the fallback path.
+ llvm::Annotations Code(
+ R"C(
+#define COUNT count
+#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
+struct TestStruct {
+ int count;
+ char* __sized_by_or_null(COUNT) ptr;
+};
+)C");
+ bool result = runOnToolAndCheckAttrSourceRange(Code, true);
+ ASSERT_TRUE(result);
+}
>From ccbece81976f45d8ffe1145ad0dd59bf99a5fc03 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 07:58:36 -0700
Subject: [PATCH 2/8] Drop some unneeded comments and a void cast
---
clang/lib/Sema/SemaInit.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index eee16a5ca00e72..01429dcdaf3ebd 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8214,8 +8214,8 @@ ExprResult InitializationSequence::Perform(Sema &S,
// first error.
S.BoundsSafetyCheckInitialization(Entity, Kind,
AssignmentAction::Initializing,
- /*LHSType=*/Step->Type,
- /*RHSExpr=*/CurInit.get());
+ Step->Type,
+ CurInit.get());
}
break;
}
@@ -8267,9 +8267,9 @@ ExprResult InitializationSequence::Perform(Sema &S,
// even though an error might have occurred. For struct initialization it
// also allows all field assignments to be checked rather than bailing on
// the first error.
- (void)S.BoundsSafetyCheckInitialization(
- Entity, Kind, /*Action=*/getAssignmentAction(Entity, true),
- /*LHSType=*/Step->Type, /*RHSExpr=*/InitialCurInit.get());
+ S.BoundsSafetyCheckInitialization(Entity, Kind,
+ getAssignmentAction(Entity, true),
+ Step->Type, InitialCurInit.get());
bool Complained;
if (S.DiagnoseAssignmentResult(ConvTy, Kind.getLocation(),
>From 683591114cedc210e3c2f9773330e9e42d38a610 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:02:42 -0700
Subject: [PATCH 3/8] Drop `BoundsSafety` prefix in
`BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy`
---
clang/lib/Sema/SemaBoundsSafety.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index eaeb40618e76bf..1ac3be9e3dec26 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -454,7 +454,7 @@ HasCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND,
/// determined.
///
/// \returns True iff no diagnostic where emitted, false otherwise.
-static bool BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
Sema &S, QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
SourceLocation Loc, std::function<std::string()> ComputeAssignee) {
NamedDecl *IncompleteTyDecl = nullptr;
@@ -495,7 +495,7 @@ static bool BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
bool Sema::BoundsSafetyCheckAssignmentToCountAttrPtr(
QualType LHSTy, Expr *RHSExpr, AssignmentAction Action, SourceLocation Loc,
std::function<std::string()> ComputeAssignee) {
- return BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+ return CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
*this, LHSTy, RHSExpr, Action, Loc, ComputeAssignee);
}
@@ -517,7 +517,7 @@ bool Sema::BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
if (Action == AssignmentAction::Initializing &&
Entity.getKind() != InitializedEntity::EK_Variable) {
- if (!BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+ if (!CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
*this, LHSType, RHSExpr, Action, SL, [&Entity]() -> std::string {
if (const auto *VD =
dyn_cast_or_null<ValueDecl>(Entity.getDecl())) {
>From 179d3814ac24d6525993505a3d51f5408d24c86b Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:09:52 -0700
Subject: [PATCH 4/8] Inline
`BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy` and remove.
---
clang/lib/Sema/SemaBoundsSafety.cpp | 21 ++++++---------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 1ac3be9e3dec26..7890111a951687 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -536,10 +536,10 @@ bool Sema::BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
return ChecksPassed;
}
-static bool BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy(Sema &S,
- Expr *E) {
+bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(Expr *E) {
QualType T = E->getType();
- assert(T->isPointerType());
+ if (!T->isPointerType())
+ return true;
const CountAttributedType *CATy = nullptr;
QualType PointeeTy;
@@ -564,26 +564,17 @@ static bool BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy(Sema &S,
UseStr = DirectCallFn;
} else {
llvm::raw_string_ostream SS(UseStr);
- E->printPretty(SS, nullptr, PrintingPolicy(S.getPrintingPolicy()));
+ E->printPretty(SS, nullptr, PrintingPolicy(getPrintingPolicy()));
}
assert(UseStr.size() > 0);
- S.Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
+ Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
<< /*0*/ SelectExprKind << /*1*/ UseStr << /*2*/ T << /*3*/ PointeeTy
<< /*4*/ CATy->GetAttributeName(/*WithMacroPrefix=*/true)
<< /*5*/ CATy->isOrNull() << E->getSourceRange();
- EmitIncompleteCountedByPointeeNotes(S, CATy, IncompleteTyDecl);
+ EmitIncompleteCountedByPointeeNotes(*this, CATy, IncompleteTyDecl);
return false;
}
-bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(Expr *E) {
-
- QualType T = E->getType();
- if (!T->isPointerType())
- return true;
-
- return BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy(*this, E);
-}
-
} // namespace clang
>From 9028cf2048e670b2843f37489a5e6958d1d25ba1 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:14:10 -0700
Subject: [PATCH 5/8] Drop numeric comments on diagnostics that allow easily
matching up the numbering with diagnostic text
---
clang/lib/Sema/SemaBoundsSafety.cpp | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 7890111a951687..54cb7b2e7b5e10 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -478,11 +478,10 @@ static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
std::string Assignee = ComputeAssignee ? ComputeAssignee() : "";
{
auto D = S.Diag(Loc, diag::err_counted_by_on_incomplete_type_on_assign)
- << /*0*/ (int)Action << /*1*/ Assignee
- << /*2*/ (Assignee.size() > 0)
- << /*3*/ isa<ImplicitValueInitExpr>(RHSExpr) << /*4*/ LHSTy
- << /*5*/ CATy->GetAttributeName(/*WithMacroPrefix=*/true)
- << /*6*/ PointeeTy << /*7*/ CATy->isOrNull();
+ << (int)Action << Assignee << (Assignee.size() > 0)
+ << isa<ImplicitValueInitExpr>(RHSExpr) << LHSTy
+ << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << PointeeTy
+ << CATy->isOrNull();
if (RHSExpr->getSourceRange().isValid())
D << RHSExpr->getSourceRange();
@@ -569,9 +568,9 @@ bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(Expr *E) {
assert(UseStr.size() > 0);
Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
- << /*0*/ SelectExprKind << /*1*/ UseStr << /*2*/ T << /*3*/ PointeeTy
- << /*4*/ CATy->GetAttributeName(/*WithMacroPrefix=*/true)
- << /*5*/ CATy->isOrNull() << E->getSourceRange();
+ << SelectExprKind << UseStr << T << PointeeTy
+ << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << CATy->isOrNull()
+ << E->getSourceRange();
EmitIncompleteCountedByPointeeNotes(*this, CATy, IncompleteTyDecl);
return false;
>From b5481e98f457cb2e0c4fa51fb7b77198af5a4393 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:29:57 -0700
Subject: [PATCH 6/8] Rip out hacky implemenation of
Sema::BoundsSafetySourceRangeFor and corresponding unit test
---
clang/lib/Sema/SemaBoundsSafety.cpp | 156 +----
clang/unittests/Sema/CMakeLists.txt | 1 -
.../Sema/GetCountedByAttrSourceRange.cpp | 573 ------------------
3 files changed, 5 insertions(+), 725 deletions(-)
delete mode 100644 clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 54cb7b2e7b5e10..ababe74c5eb01d 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -219,158 +219,12 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
}
SourceRange Sema::BoundsSafetySourceRangeFor(const CountAttributedType *CATy) {
- // Note: This implementation relies on `CountAttributedType` being unique.
- // E.g.:
+ // This is an approximation that's not quite right. This points to the
+ // the expression inside the attribute rather than the attribute itself.
//
- // struct Foo {
- // int count;
- // char* __counted_by(count) buffer;
- // char* __counted_by(count) buffer2;
- // };
- //
- // The types of `buffer` and `buffer2` are unique. The types being
- // unique means the SourceLocation of the `counted_by` expression can be used
- // to find where the attribute was written.
-
- auto Fallback = CATy->getCountExpr()->getSourceRange();
- auto CountExprBegin = CATy->getCountExpr()->getBeginLoc();
-
- // FIXME: We currently don't support the count expression being a macro
- // itself. E.g.:
- //
- // #define ZERO 0
- // int* __counted_by(ZERO) x;
- //
- if (SourceMgr.isMacroBodyExpansion(CountExprBegin))
- return Fallback;
-
- auto FetchIdentifierTokenFromOffset =
- [&](ssize_t Offset) -> std::optional<Token> {
- SourceLocation OffsetLoc = CountExprBegin.getLocWithOffset(Offset);
- Token Result;
- if (Lexer::getRawToken(OffsetLoc, Result, SourceMgr, getLangOpts()))
- return std::optional<Token>(); // Failed
-
- if (!Result.isAnyIdentifier())
- return std::optional<Token>(); // Failed
-
- return Result; // Success
- };
-
- auto CountExprEnd = CATy->getCountExpr()->getEndLoc();
- auto FindRParenTokenAfter = [&]() -> std::optional<Token> {
- auto CountExprEndSpelling = SourceMgr.getSpellingLoc(CountExprEnd);
- auto MaybeRParenTok = Lexer::findNextToken(
- CountExprEndSpelling, getSourceManager(), getLangOpts());
-
- if (!MaybeRParenTok.has_value())
- return std::nullopt;
-
- if (!MaybeRParenTok->is(tok::r_paren))
- return std::nullopt;
-
- return *MaybeRParenTok;
- };
-
- // Step back two characters to point at the last character of the attribute
- // text.
- //
- // __counted_by(count)
- // ^ ^
- // | |
- // ---
- auto MaybeLastAttrCharToken = FetchIdentifierTokenFromOffset(-2);
- if (!MaybeLastAttrCharToken)
- return Fallback;
-
- auto LastAttrCharToken = MaybeLastAttrCharToken.value();
-
- if (LastAttrCharToken.getLength() > 1) {
- // Special case: When the character is part of a macro the Token we get
- // is the whole macro name (e.g. `__counted_by`).
- if (LastAttrCharToken.getRawIdentifier() !=
- CATy->GetAttributeName(/*WithMacroPrefix=*/true))
- return Fallback;
-
- // Found the beginning of the `__counted_by` macro
- SourceLocation Begin = LastAttrCharToken.getLocation();
- // Now try to find the closing `)` of the macro.
- auto MaybeRParenTok = FindRParenTokenAfter();
- if (!MaybeRParenTok.has_value())
- return Fallback;
-
- return SourceRange(Begin, MaybeRParenTok->getLocation());
- }
-
- assert(LastAttrCharToken.getLength() == 1);
- // The Token we got back is just the last character of the identifier.
- // This means a macro is not being used and instead the attribute is being
- // used directly. We need to find the beginning of the identifier. We support
- // two cases:
- //
- // * Non-affixed version. E.g: `counted_by`
- // * Affixed version. E.g.: `__counted_by__`
-
- // Try non-affixed version. E.g.:
- //
- // __attribute__((counted_by(count)))
- // ^ ^
- // | |
- // ------------
-
- // +1 is for `(`
- const ssize_t NonAffixedSkipCount =
- CATy->GetAttributeName(/*WithMacroPrefix=*/false).size() + 1;
- auto MaybeNonAffixedBeginToken =
- FetchIdentifierTokenFromOffset(-NonAffixedSkipCount);
- if (!MaybeNonAffixedBeginToken)
- return Fallback;
-
- auto NonAffixedBeginToken = MaybeNonAffixedBeginToken.value();
- if (NonAffixedBeginToken.getRawIdentifier() ==
- CATy->GetAttributeName(/*WithMacroPrefix=*/false)) {
- // Found the beginning of the `counted_by`-like attribute
- auto SL = NonAffixedBeginToken.getLocation();
-
- // Now try to find the closing `)` of the attribute
- auto MaybeRParenTok = FindRParenTokenAfter();
- if (!MaybeRParenTok.has_value())
- return Fallback;
-
- return SourceRange(SL, MaybeRParenTok->getLocation());
- }
-
- // Try affixed version. E.g.:
- //
- // __attribute__((__counted_by__(count)))
- // ^ ^
- // | |
- // ----------------
- std::string AffixedTokenStr =
- (llvm::Twine("__") + CATy->GetAttributeName(/*WithMacroPrefix=*/false) +
- llvm::Twine("__"))
- .str();
- // +1 is for `(`
- // +4 is for the 4 `_` characters
- const ssize_t AffixedSkipCount =
- CATy->GetAttributeName(/*WithMacroPrefix=*/false).size() + 1 + 4;
- auto MaybeAffixedBeginToken =
- FetchIdentifierTokenFromOffset(-AffixedSkipCount);
- if (!MaybeAffixedBeginToken)
- return Fallback;
-
- auto AffixedBeginToken = MaybeAffixedBeginToken.value();
- if (AffixedBeginToken.getRawIdentifier() != AffixedTokenStr)
- return Fallback;
-
- // Found the beginning of the `__counted_by__`-like like attribute.
- auto SL = AffixedBeginToken.getLocation();
- // Now try to find the closing `)` of the attribute
- auto MaybeRParenTok = FindRParenTokenAfter();
- if (!MaybeRParenTok.has_value())
- return Fallback;
-
- return SourceRange(SL, MaybeRParenTok->getLocation());
+ // TODO: Implement logic to find the relevant TypeLoc for the attribute and
+ // get the SourceRange from that (#113582).
+ return CATy->getCountExpr()->getSourceRange();
}
static void EmitIncompleteCountedByPointeeNotes(Sema &S,
diff --git a/clang/unittests/Sema/CMakeLists.txt b/clang/unittests/Sema/CMakeLists.txt
index ebc829e82af6b5..7ded562e8edfa5 100644
--- a/clang/unittests/Sema/CMakeLists.txt
+++ b/clang/unittests/Sema/CMakeLists.txt
@@ -6,7 +6,6 @@ set(LLVM_LINK_COMPONENTS
add_clang_unittest(SemaTests
ExternalSemaSourceTest.cpp
CodeCompleteTest.cpp
- GetCountedByAttrSourceRange.cpp
GslOwnerPointerInference.cpp
SemaLookupTest.cpp
SemaNoloadLookupTest.cpp
diff --git a/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp b/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
deleted file mode 100644
index 3333ed046bff82..00000000000000
--- a/clang/unittests/Sema/GetCountedByAttrSourceRange.cpp
+++ /dev/null
@@ -1,573 +0,0 @@
-//=== unittests/Sema/GetCountedByAttrSourceRange.cpp =========================//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-// FIXME: audit these header files
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "clang/ASTMatchers/ASTMatchers.h"
-#include "clang/Frontend/CompilerInstance.h"
-#include "clang/Frontend/FrontendActions.h"
-#include "clang/Sema/Sema.h"
-#include "clang/Tooling/Tooling.h"
-#include "llvm/Testing/Annotations/Annotations.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-using namespace clang;
-using namespace clang::ast_matchers;
-
-using CallBackTy =
- std::function<void(CompilerInstance &, const CountAttributedType *)>;
-
-bool runOnToolAndCheckAttrSourceRange(llvm::Annotations &Code,
- bool TestForFallback = false) {
- class RunFnAfterSema : public clang::ASTFrontendAction {
- llvm::Annotations &Code;
- bool TestForFallback;
-
- public:
- RunFnAfterSema(llvm::Annotations &Code, bool TestForFallback)
- : Code(Code), TestForFallback(TestForFallback) {}
- void EndSourceFile() override {
- auto &S = getCompilerInstance().getSema();
- // Find `TestStruct::ptr`
- auto Matched = match(
- recordDecl(hasName("TestStruct"),
- hasDescendant(fieldDecl(hasName("ptr")).bind("ptr"))),
- S.getASTContext());
- EXPECT_EQ(Matched.size(), 1u);
- auto *FD = Matched[0].getNodeAs<FieldDecl>("ptr");
- ASSERT_NE(FD, nullptr);
-
- const auto *CATy = FD->getType()->getAs<CountAttributedType>();
-
- auto SR = S.BoundsSafetySourceRangeFor(CATy);
-
- if (TestForFallback) {
- // Make sure for these types of test cases that there are no
- // annotations. The presence of annotations for this type of test would
- // be very misleading because they aren't being checked.
- const auto &Ranges = Code.all_ranges();
- ASSERT_EQ(Ranges.size(), 0U);
-
- // The fallback is using the SourceRange of the CountExpr.
- ASSERT_EQ(SR, CATy->getCountExpr()->getSourceRange());
- // Don't test for precise column position in this case.
- // The code below doesn't really work correctly when the
- // count expression is itself a macro.
- return;
- }
-
- // Check Begin
- auto ExpectedBegin = Code.range("attr").Begin;
- auto Begin = SR.getBegin();
- auto BeginSpellingLoc = S.getSourceManager().getSpellingLoc(Begin);
- auto BeginFileOffset =
- S.getSourceManager().getFileOffset(BeginSpellingLoc);
- ASSERT_EQ(BeginFileOffset, ExpectedBegin);
- // Check End
- auto ExpectedEnd = Code.range("attr").End - 1;
- auto End = SR.getEnd();
- auto EndSpellingLoc = S.getSourceManager().getSpellingLoc(End);
- auto EndFileOffset = S.getSourceManager().getFileOffset(EndSpellingLoc);
- ASSERT_EQ(EndFileOffset, ExpectedEnd);
-
- ASTFrontendAction::EndSourceFile();
- }
-
- std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
- StringRef InFile) override {
- return std::make_unique<ASTConsumer>();
- }
- };
- auto Action = std::make_unique<RunFnAfterSema>(Code, TestForFallback);
- return clang::tooling::runToolOnCodeWithArgs(std::move(Action), Code.code(),
- {"-std=c11"}, "test.c");
-}
-
-//==============================================================================
-// counted_by
-//==============================================================================
-
-TEST(GetCountedByAttrSourceRange, GNUAttrNoAffix) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by(X) __attribute__((counted_by(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[counted_by(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by(X) __attribute__((counted_by(X)))
-struct TestStruct {
- int count;
- char* __attribute__((counted_by(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, GNUAttrAffix) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by(X) __attribute__((__counted_by__(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[counted_by(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, GNUAttrAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by(X) __attribute__((__counted_by__(X)))
-struct TestStruct {
- int count;
- char* __attribute__((counted_by(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, MacroNoAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by(X) __attribute__((counted_by(X)))
-struct TestStruct {
- int count;
- char* $attr[[__counted_by(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, MacroAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by(X) __attribute__((__counted_by__(X)))
-struct TestStruct {
- int count;
- char* $attr[[__counted_by(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, CustomMacro) {
- // For this code no special SourceRange is computed.
- llvm::Annotations Code(
- R"C(
-#define custom_cb(X) __attribute__((__counted_by__(X)))
-struct TestStruct {
- int count;
- char* custom_cb(count) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByAttrSourceRange, MacroArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by(X) __attribute__((__counted_by__(X)))
-struct TestStruct {
- int count;
- char* __counted_by(COUNT) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code, true);
- ASSERT_TRUE(result);
-}
-
-//==============================================================================
-// counted_by_or_null
-//==============================================================================
-
-TEST(GetCountedByOrNullAttrSourceRange, GNUAttrNoAffix) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[counted_by_or_null(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
-struct TestStruct {
- int count;
- char* __attribute__((counted_by_or_null(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, GNUAttrAffix) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[counted_by_or_null(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, GNUAttrAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __attribute__((counted_by_or_null(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, MacroNoAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by_or_null(X) __attribute__((counted_by_or_null(X)))
-struct TestStruct {
- int count;
- char* $attr[[__counted_by_or_null(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, MacroAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* $attr[[__counted_by_or_null(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, CustomMacro) {
- // For this code no special SourceRange is computed.
- llvm::Annotations Code(
- R"C(
-#define custom_cbon(X) __attribute__((__counted_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* custom_cbon(count) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetCountedByOrNullAttrSourceRange, MacroArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __counted_by_or_null(X) __attribute__((__counted_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __counted_by_or_null(COUNT) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code, true);
- ASSERT_TRUE(result);
-}
-
-//==============================================================================
-// sized_by
-//==============================================================================
-
-TEST(GetSizedByAttrSourceRange, GNUAttrNoAffix) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by(X) __attribute__((sized_by(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[sized_by(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by(X) __attribute__((sized_by(X)))
-struct TestStruct {
- int count;
- char* __attribute__((sized_by(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, GNUAttrAffix) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by(X) __attribute__((__sized_by__(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[sized_by(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, GNUAttrAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by(X) __attribute__((__sized_by__(X)))
-struct TestStruct {
- int count;
- char* __attribute__((sized_by(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, MacroNoAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by(X) __attribute__((sized_by(X)))
-struct TestStruct {
- int count;
- char* $attr[[__sized_by(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, MacroAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by(X) __attribute__((__sized_by__(X)))
-struct TestStruct {
- int count;
- char* $attr[[__sized_by(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, CustomMacro) {
- // For this code no special SourceRange is computed.
- llvm::Annotations Code(
- R"C(
-#define custom_sb(X) __attribute__((__sized_by__(X)))
-struct TestStruct {
- int count;
- char* custom_sb(count) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByAttrSourceRange, MacroArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by(X) __attribute__((__sized_by__(X)))
-struct TestStruct {
- int count;
- char* __sized_by(COUNT) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code, true);
- ASSERT_TRUE(result);
-}
-
-//==============================================================================
-// sized_by_or_null
-//==============================================================================
-
-TEST(GetSizedByOrNullAttrSourceRange, GNUAttrNoAffix) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[sized_by_or_null(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, GNUAttrNoAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
-struct TestStruct {
- int count;
- char* __attribute__((sized_by_or_null(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, GNUAttrAffix) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __attribute__(($attr[[sized_by_or_null(count)]])) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, GNUAttrAffixArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __attribute__((sized_by_or_null(COUNT))) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, MacroNoAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by_or_null(X) __attribute__((sized_by_or_null(X)))
-struct TestStruct {
- int count;
- char* $attr[[__sized_by_or_null(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, MacroAffixImpl) {
- llvm::Annotations Code(
- R"C(
-#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* $attr[[__sized_by_or_null(count)]] ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, CustomMacro) {
- // For this code no special SourceRange is computed.
- llvm::Annotations Code(
- R"C(
-#define custom_sbon(X) __attribute__((__sized_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* custom_sbon(count) ptr;
-};
-)C");
- bool result =
- runOnToolAndCheckAttrSourceRange(Code, /*TestForFallback=*/true);
- ASSERT_TRUE(result);
-}
-
-TEST(GetSizedByOrNullAttrSourceRange, MacroArgIsMacro) {
- // For this code no special SourceRange is computed
- // FIXME: This shouldn't use the fallback path.
- llvm::Annotations Code(
- R"C(
-#define COUNT count
-#define __sized_by_or_null(X) __attribute__((__sized_by_or_null__(X)))
-struct TestStruct {
- int count;
- char* __sized_by_or_null(COUNT) ptr;
-};
-)C");
- bool result = runOnToolAndCheckAttrSourceRange(Code, true);
- ASSERT_TRUE(result);
-}
>From 631618a52de160d9c6c7a4c884c4450dcd83f2bf Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:39:18 -0700
Subject: [PATCH 7/8] * Rename `CountAttributedType::getAttributeName` to use
the appropriate convention * Leave a note about how the method isn't ideal
and refer to the issue tracking it.
---
clang/include/clang/AST/Type.h | 2 +-
clang/lib/AST/Type.cpp | 6 +++++-
clang/lib/Sema/SemaBoundsSafety.cpp | 6 +++---
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e7e86897e3e7a8..815d05385da6f8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -3373,7 +3373,7 @@ class CountAttributedType final
return T->getTypeClass() == CountAttributed;
}
- StringRef GetAttributeName(bool WithMacroPrefix) const;
+ StringRef getAttributeName(bool WithMacroPrefix) const;
};
/// Represents a type which was implicitly adjusted by the semantic
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 93b576bc55938a..ca372ff8bdc363 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3889,7 +3889,11 @@ CountAttributedType::CountAttributedType(
DeclSlot[i] = CoupledDecls[i];
}
-StringRef CountAttributedType::GetAttributeName(bool WithMacroPrefix) const {
+StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const {
+// TODO: This method isn't really ideal because it doesn't return the spelling
+// of the attribute that was used in the user's code. This method is used for
+// diagnostics so the fact it doesn't use the spelling of the attribute in
+// the user's code could be confusing (#113585).
#define ENUMERATE_ATTRS(PREFIX) \
do { \
if (isCountInBytes()) { \
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index ababe74c5eb01d..1201ccc076fb06 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -237,7 +237,7 @@ static void EmitIncompleteCountedByPointeeNotes(Sema &S,
// Note where the attribute is declared
auto AttrSrcRange = S.BoundsSafetySourceRangeFor(CATy);
S.Diag(AttrSrcRange.getBegin(), diag::note_named_attribute)
- << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << AttrSrcRange;
+ << CATy->getAttributeName(/*WithMacroPrefix=*/true) << AttrSrcRange;
}
if (!IncompleteTyDecl)
@@ -334,7 +334,7 @@ static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
auto D = S.Diag(Loc, diag::err_counted_by_on_incomplete_type_on_assign)
<< (int)Action << Assignee << (Assignee.size() > 0)
<< isa<ImplicitValueInitExpr>(RHSExpr) << LHSTy
- << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << PointeeTy
+ << CATy->getAttributeName(/*WithMacroPrefix=*/true) << PointeeTy
<< CATy->isOrNull();
if (RHSExpr->getSourceRange().isValid())
@@ -423,7 +423,7 @@ bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(Expr *E) {
Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
<< SelectExprKind << UseStr << T << PointeeTy
- << CATy->GetAttributeName(/*WithMacroPrefix=*/true) << CATy->isOrNull()
+ << CATy->getAttributeName(/*WithMacroPrefix=*/true) << CATy->isOrNull()
<< E->getSourceRange();
EmitIncompleteCountedByPointeeNotes(*this, CATy, IncompleteTyDecl);
>From 94f1aa4e2fc02d15f7172c8895e1cbe1415b99cc Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 24 Oct 2024 08:50:32 -0700
Subject: [PATCH 8/8] Tweak `Type::isIncompletableIncompleteType`
---
clang/lib/AST/Type.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index ca372ff8bdc363..e95496a72ec79b 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2444,7 +2444,7 @@ bool Type::isIncompletableIncompleteType() const {
// Forward declarations of structs, classes, enums, and unions could be later
// completed in a compilation unit by providing a type definition.
- if (isStructureOrClassType() || isEnumeralType() || isUnionType())
+ if (getAsTagDecl())
return false;
// Other types are incompletable.
More information about the cfe-commits
mailing list