[Clang] Implement the 'counted_by' attribute (#68750)
The 'counted_by' attribute is used on flexible array members. The
argument for the attribute is the name of the field member in the same
structure holding the count of elements in the flexible array. This
information can be used to improve the results of the array bound
sanitizer and the '__builtin_dynamic_object_size' builtin.
This example specifies the that the flexible array member 'array' has
the number of elements allocated for it in 'count':
struct bar;
struct foo {
size_t count;
/* ... */
struct bar *array[] __attribute__((counted_by(count)));
};
This establishes a relationship between 'array' and 'count',
specifically that 'p->array' must have *at least* 'p->count' number of
elements available. It's the user's responsibility to ensure that this
relationship is maintained through changes to the structure.
In the following, the allocated array erroneously has fewer elements
than what's specified by 'p->count'. This would result in an
out-of-bounds access not not being detected:
struct foo *p;
void foo_alloc(size_t count) {
p = malloc(MAX(sizeof(struct foo),
offsetof(struct foo, array[0]) + count *
sizeof(struct bar *)));
p->count = count + 42;
}
The next example updates 'p->count', breaking the relationship
requirement that 'p->array' must have at least 'p->count' number of
elements available:
struct foo *p;
void foo_alloc(size_t count) {
p = malloc(MAX(sizeof(struct foo),
offsetof(struct foo, array[0]) + count *
sizeof(struct bar *)));
p->count = count + 42;
}
void use_foo(int index) {
p->count += 42;
p->array[index] = 0; /* The sanitizer cannot properly check this access */
}
Reviewed By: nickdesaulniers, aaron.ballman
Differential Revision: https://reviews.llvm.org/D148381
This commit is contained in:
1 parent
7060422265
commit
769bc11f68
21 files changed
+762
-89
No files matched your search
@@ -157,6 +157,11 @@ C Language Changes
|
||||
- ``structs``, ``unions``, and ``arrays`` that are const may now be used as
|
||||
constant expressions. This change is more consistent with the behavior of
|
||||
GCC.
|
||||
- Clang now supports the C-only attribute ``counted_by``. When applied to a
|
||||
struct's flexible array member, it points to the struct field that holds the
|
||||
number of elements in the flexible array member. This information can improve
|
||||
the results of the array bound sanitizer and the
|
||||
``__builtin_dynamic_object_size`` builtin.
|
||||
|
||||
C23 Feature Support
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -4302,6 +4302,30 @@ public:
|
||||
return field_begin() == field_end();
|
||||
}
|
||||
|
||||
FieldDecl *getLastField() {
|
||||
FieldDecl *FD = nullptr;
|
||||
for (FieldDecl *Field : fields())
|
||||
FD = Field;
|
||||
return FD;
|
||||
}
|
||||
const FieldDecl *getLastField() const {
|
||||
return const_cast<RecordDecl *>(this)->getLastField();
|
||||
}
|
||||
|
||||
template <typename Functor>
|
||||
const FieldDecl *findFieldIf(Functor &Pred) const {
|
||||
for (const Decl *D : decls()) {
|
||||
if (const auto *FD = dyn_cast<FieldDecl>(D); FD && Pred(FD))
|
||||
return FD;
|
||||
|
||||
if (const auto *RD = dyn_cast<RecordDecl>(D))
|
||||
if (const FieldDecl *FD = RD->findFieldIf(Pred))
|
||||
return FD;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Note that the definition of this type is now complete.
|
||||
virtual void completeDefinition();
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "clang/AST/DeclarationName.h"
|
||||
#include "clang/Basic/IdentifierTable.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Basic/Specifiers.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
@@ -477,6 +478,15 @@ public:
|
||||
// Return true if this is a FileContext Decl.
|
||||
bool isFileContextDecl() const;
|
||||
|
||||
/// Whether it resembles a flexible array member. This is a static member
|
||||
/// because we want to be able to call it with a nullptr. That allows us to
|
||||
/// perform non-Decl specific checks based on the object's type and strict
|
||||
/// flex array level.
|
||||
static bool isFlexibleArrayMemberLike(
|
||||
ASTContext &Context, const Decl *D, QualType Ty,
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel,
|
||||
bool IgnoreTemplateOrMacroSubstitution);
|
||||
|
||||
ASTContext &getASTContext() const LLVM_READONLY;
|
||||
|
||||
/// Helper to get the language options from the ASTContext.
|
||||
|
||||
@@ -4246,3 +4246,21 @@ def AvailableOnlyInDefaultEvalMethod : InheritableAttr {
|
||||
let Subjects = SubjectList<[TypedefName], ErrorDiag>;
|
||||
let Documentation = [Undocumented];
|
||||
}
|
||||
|
||||
def CountedBy : InheritableAttr {
|
||||
let Spellings = [Clang<"counted_by">];
|
||||
let Subjects = SubjectList<[Field]>;
|
||||
let Args = [IdentifierArgument<"CountedByField">];
|
||||
let Documentation = [CountedByDocs];
|
||||
let LangOpts = [COnly];
|
||||
// FIXME: This is ugly. Let using a DeclArgument would be nice, but a Decl
|
||||
// isn't yet available due to the fact that we're still parsing the
|
||||
// structure. Maybe that code could be changed sometime in the future.
|
||||
code AdditionalMembers = [{
|
||||
private:
|
||||
SourceRange CountedByFieldLoc;
|
||||
public:
|
||||
SourceRange getCountedByFieldLoc() const { return CountedByFieldLoc; }
|
||||
void setCountedByFieldLoc(SourceRange Loc) { CountedByFieldLoc = Loc; }
|
||||
}];
|
||||
}
|
||||
@@ -7275,3 +7275,69 @@ relative ordering of values is important. For example:
|
||||
attribute, they default to the value ``65535``.
|
||||
}];
|
||||
}
|
||||
|
||||
def CountedByDocs : Documentation {
|
||||
let Category = DocCatField;
|
||||
let Content = [{
|
||||
Clang supports the ``counted_by`` attribute on the flexible array member of a
|
||||
structure in C. The argument for the attribute is the name of a field member in
|
||||
the same structure holding the count of elements in the flexible array. This
|
||||
information can be used to improve the results of the array bound sanitizer and
|
||||
the ``__builtin_dynamic_object_size`` builtin.
|
||||
|
||||
For example, the following code:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct bar;
|
||||
|
||||
struct foo {
|
||||
size_t count;
|
||||
char other;
|
||||
struct bar *array[] __attribute__((counted_by(count)));
|
||||
};
|
||||
|
||||
specifies that the flexible array member ``array`` has the number of elements
|
||||
allocated for it stored in ``count``. This establishes a relationship between
|
||||
``array`` and ``count``. Specifically, ``p->array`` must have at least
|
||||
``p->count`` number of elements available. It's the user's responsibility to
|
||||
ensure that this relationship is maintained through changes to the structure.
|
||||
|
||||
In the following example, the allocated array erroneously has fewer elements
|
||||
than what's specified by ``p->count``. This would result in an out-of-bounds
|
||||
access not being detected.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#define SIZE_INCR 42
|
||||
|
||||
struct foo *p;
|
||||
|
||||
void foo_alloc(size_t count) {
|
||||
p = malloc(MAX(sizeof(struct foo),
|
||||
offsetof(struct foo, array[0]) + count * sizeof(struct bar *)));
|
||||
p->count = count + SIZE_INCR;
|
||||
}
|
||||
|
||||
The next example updates ``p->count``, breaking the relationship requirement
|
||||
that ``p->array`` must have at least ``p->count`` number of elements available:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#define SIZE_INCR 42
|
||||
|
||||
struct foo *p;
|
||||
|
||||
void foo_alloc(size_t count) {
|
||||
p = malloc(MAX(sizeof(struct foo),
|
||||
offsetof(struct foo, array[0]) + count * sizeof(struct bar *)));
|
||||
p->count = count;
|
||||
}
|
||||
|
||||
void use_foo(int index) {
|
||||
p->count += SIZE_INCR + 1; /* 'count' is now larger than the number of elements of 'array'. */
|
||||
p->array[index] = 0; /* the sanitizer can't properly check if this is an out-of-bounds access. */
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
@@ -6389,6 +6389,17 @@ def warn_superclass_variable_sized_type_not_at_end : Warning<
|
||||
"field %0 can overwrite instance variable %1 with variable sized type %2"
|
||||
" in superclass %3">, InGroup<ObjCFlexibleArray>;
|
||||
|
||||
def err_counted_by_attr_not_on_flexible_array_member : Error<
|
||||
"'counted_by' only applies to flexible array members">;
|
||||
def err_counted_by_attr_refers_to_flexible_array : Error<
|
||||
"'counted_by' cannot refer to the flexible array %0">;
|
||||
def err_counted_by_must_be_in_structure : Error<
|
||||
"field %0 in 'counted_by' not inside structure">;
|
||||
def err_flexible_array_counted_by_attr_field_not_integer : Error<
|
||||
"field %0 in 'counted_by' must be a non-boolean integer type">;
|
||||
def note_flexible_array_counted_by_attr_field : Note<
|
||||
"field %0 declared here">;
|
||||
|
||||
let CategoryName = "ARC Semantic Issue" in {
|
||||
|
||||
// ARC-mode diagnostics.
|
||||
|
||||
@@ -4795,6 +4795,8 @@ public:
|
||||
bool CheckAlwaysInlineAttr(const Stmt *OrigSt, const Stmt *CurSt,
|
||||
const AttributeCommonInfo &A);
|
||||
|
||||
bool CheckCountedByAttr(Scope *Scope, const FieldDecl *FD);
|
||||
|
||||
/// Adjust the calling convention of a method to be the ABI default if it
|
||||
/// wasn't specified explicitly. This handles method types formed from
|
||||
/// function type typedefs and typename template arguments.
|
||||
@@ -5638,6 +5640,7 @@ public:
|
||||
CorrectionCandidateCallback &CCC,
|
||||
TemplateArgumentListInfo *ExplicitTemplateArgs = nullptr,
|
||||
ArrayRef<Expr *> Args = std::nullopt,
|
||||
DeclContext *LookupCtx = nullptr,
|
||||
TypoExpr **Out = nullptr);
|
||||
|
||||
DeclResult LookupIvarInObjCMethod(LookupResult &Lookup, Scope *S,
|
||||
|
||||
@@ -282,7 +282,7 @@ class CorrectionCandidateCallback {
|
||||
public:
|
||||
static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
|
||||
|
||||
explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
|
||||
explicit CorrectionCandidateCallback(const IdentifierInfo *Typo = nullptr,
|
||||
NestedNameSpecifier *TypoNNS = nullptr)
|
||||
: Typo(Typo), TypoNNS(TypoNNS) {}
|
||||
|
||||
@@ -319,7 +319,7 @@ public:
|
||||
/// this method.
|
||||
virtual std::unique_ptr<CorrectionCandidateCallback> clone() = 0;
|
||||
|
||||
void setTypoName(IdentifierInfo *II) { Typo = II; }
|
||||
void setTypoName(const IdentifierInfo *II) { Typo = II; }
|
||||
void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
|
||||
|
||||
// Flags for context-dependent keywords. WantFunctionLikeCasts is only
|
||||
@@ -345,13 +345,13 @@ protected:
|
||||
candidate.getCorrectionSpecifier() == TypoNNS;
|
||||
}
|
||||
|
||||
IdentifierInfo *Typo;
|
||||
const IdentifierInfo *Typo;
|
||||
NestedNameSpecifier *TypoNNS;
|
||||
};
|
||||
|
||||
class DefaultFilterCCC final : public CorrectionCandidateCallback {
|
||||
public:
|
||||
explicit DefaultFilterCCC(IdentifierInfo *Typo = nullptr,
|
||||
explicit DefaultFilterCCC(const IdentifierInfo *Typo = nullptr,
|
||||
NestedNameSpecifier *TypoNNS = nullptr)
|
||||
: CorrectionCandidateCallback(Typo, TypoNNS) {}
|
||||
|
||||
@@ -365,6 +365,10 @@ public:
|
||||
template <class C>
|
||||
class DeclFilterCCC final : public CorrectionCandidateCallback {
|
||||
public:
|
||||
explicit DeclFilterCCC(const IdentifierInfo *Typo = nullptr,
|
||||
NestedNameSpecifier *TypoNNS = nullptr)
|
||||
: CorrectionCandidateCallback(Typo, TypoNNS) {}
|
||||
|
||||
bool ValidateCandidate(const TypoCorrection &candidate) override {
|
||||
return candidate.getCorrectionDeclAs<C>();
|
||||
}
|
||||
|
||||
@@ -8978,6 +8978,10 @@ class AttrImporter {
|
||||
public:
|
||||
AttrImporter(ASTImporter &I) : Importer(I), NImporter(I) {}
|
||||
|
||||
// Useful for accessing the imported attribute.
|
||||
template <typename T> T *castAttrAs() { return cast<T>(ToAttr); }
|
||||
template <typename T> const T *castAttrAs() const { return cast<T>(ToAttr); }
|
||||
|
||||
// Create an "importer" for an attribute parameter.
|
||||
// Result of the 'value()' of that object is to be passed to the function
|
||||
// 'importAttr', in the order that is expected by the attribute class.
|
||||
@@ -9184,6 +9188,15 @@ Expected<Attr *> ASTImporter::Import(const Attr *FromAttr) {
|
||||
From->args_size());
|
||||
break;
|
||||
}
|
||||
case attr::CountedBy: {
|
||||
AI.cloneAttr(FromAttr);
|
||||
const auto *CBA = cast<CountedByAttr>(FromAttr);
|
||||
Expected<SourceRange> SR = Import(CBA->getCountedByFieldLoc()).get();
|
||||
if (!SR)
|
||||
return SR.takeError();
|
||||
AI.castAttrAs<CountedByAttr>()->setCountedByFieldLoc(SR.get());
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// The default branch works for attributes that have no arguments to import.
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/Basic/IdentifierTable.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/Module.h"
|
||||
#include "clang/Basic/ObjCRuntime.h"
|
||||
#include "clang/Basic/PartialDiagnostic.h"
|
||||
@@ -411,6 +410,82 @@ bool Decl::isFileContextDecl() const {
|
||||
return DC && DC->isFileContext();
|
||||
}
|
||||
|
||||
bool Decl::isFlexibleArrayMemberLike(
|
||||
ASTContext &Ctx, const Decl *D, QualType Ty,
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel,
|
||||
bool IgnoreTemplateOrMacroSubstitution) {
|
||||
// For compatibility with existing code, we treat arrays of length 0 or
|
||||
// 1 as flexible array members.
|
||||
const auto *CAT = Ctx.getAsConstantArrayType(Ty);
|
||||
if (CAT) {
|
||||
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
|
||||
|
||||
llvm::APInt Size = CAT->getSize();
|
||||
FAMKind StrictFlexArraysLevel =
|
||||
Ctx.getLangOpts().getStrictFlexArraysLevel();
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::IncompleteOnly)
|
||||
return false;
|
||||
|
||||
// GCC extension, only allowed to represent a FAM.
|
||||
if (Size.isZero())
|
||||
return true;
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete && Size.uge(1))
|
||||
return false;
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete && Size.uge(2))
|
||||
return false;
|
||||
} else if (!Ctx.getAsIncompleteArrayType(Ty)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto *OID = dyn_cast_if_present<ObjCIvarDecl>(D))
|
||||
return OID->getNextIvar() == nullptr;
|
||||
|
||||
const auto *FD = dyn_cast_if_present<FieldDecl>(D);
|
||||
if (!FD)
|
||||
return false;
|
||||
|
||||
if (CAT) {
|
||||
// GCC treats an array memeber of a union as an FAM if the size is one or
|
||||
// zero.
|
||||
llvm::APInt Size = CAT->getSize();
|
||||
if (FD->getParent()->isUnion() && (Size.isZero() || Size.isOne()))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't consider sizes resulting from macro expansions or template argument
|
||||
// substitution to form C89 tail-padded arrays.
|
||||
if (IgnoreTemplateOrMacroSubstitution) {
|
||||
TypeSourceInfo *TInfo = FD->getTypeSourceInfo();
|
||||
while (TInfo) {
|
||||
TypeLoc TL = TInfo->getTypeLoc();
|
||||
|
||||
// Look through typedefs.
|
||||
if (TypedefTypeLoc TTL = TL.getAsAdjusted<TypedefTypeLoc>()) {
|
||||
const TypedefNameDecl *TDL = TTL.getTypedefNameDecl();
|
||||
TInfo = TDL->getTypeSourceInfo();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto CTL = TL.getAs<ConstantArrayTypeLoc>()) {
|
||||
if (const Expr *SizeExpr =
|
||||
dyn_cast_if_present<IntegerLiteral>(CTL.getSizeExpr());
|
||||
!SizeExpr || SizeExpr->getExprLoc().isMacroID())
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the field is the last in the structure.
|
||||
RecordDecl::field_iterator FI(
|
||||
DeclContext::decl_iterator(const_cast<FieldDecl *>(FD)));
|
||||
return ++FI == FD->getParent()->field_end();
|
||||
}
|
||||
|
||||
TranslationUnitDecl *Decl::getTranslationUnitDecl() {
|
||||
if (auto *TUD = dyn_cast<TranslationUnitDecl>(this))
|
||||
return TUD;
|
||||
|
||||
+10
-73
@@ -205,85 +205,22 @@ bool Expr::isKnownToHaveBooleanValue(bool Semantic) const {
|
||||
}
|
||||
|
||||
bool Expr::isFlexibleArrayMemberLike(
|
||||
ASTContext &Context,
|
||||
ASTContext &Ctx,
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel,
|
||||
bool IgnoreTemplateOrMacroSubstitution) const {
|
||||
|
||||
// For compatibility with existing code, we treat arrays of length 0 or
|
||||
// 1 as flexible array members.
|
||||
const auto *CAT = Context.getAsConstantArrayType(getType());
|
||||
if (CAT) {
|
||||
llvm::APInt Size = CAT->getSize();
|
||||
|
||||
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::IncompleteOnly)
|
||||
return false;
|
||||
|
||||
// GCC extension, only allowed to represent a FAM.
|
||||
if (Size == 0)
|
||||
return true;
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete && Size.uge(1))
|
||||
return false;
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete && Size.uge(2))
|
||||
return false;
|
||||
} else if (!Context.getAsIncompleteArrayType(getType()))
|
||||
return false;
|
||||
|
||||
const Expr *E = IgnoreParens();
|
||||
const Decl *D = nullptr;
|
||||
|
||||
const NamedDecl *ND = nullptr;
|
||||
if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
|
||||
ND = DRE->getDecl();
|
||||
else if (const auto *ME = dyn_cast<MemberExpr>(E))
|
||||
ND = ME->getMemberDecl();
|
||||
if (const auto *ME = dyn_cast<MemberExpr>(E))
|
||||
D = ME->getMemberDecl();
|
||||
else if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
|
||||
D = DRE->getDecl();
|
||||
else if (const auto *IRE = dyn_cast<ObjCIvarRefExpr>(E))
|
||||
return IRE->getDecl()->getNextIvar() == nullptr;
|
||||
D = IRE->getDecl();
|
||||
|
||||
if (!ND)
|
||||
return false;
|
||||
|
||||
// A flexible array member must be the last member in the class.
|
||||
// FIXME: If the base type of the member expr is not FD->getParent(),
|
||||
// this should not be treated as a flexible array member access.
|
||||
if (const auto *FD = dyn_cast<FieldDecl>(ND)) {
|
||||
// GCC treats an array memeber of a union as an FAM if the size is one or
|
||||
// zero.
|
||||
if (CAT) {
|
||||
llvm::APInt Size = CAT->getSize();
|
||||
if (FD->getParent()->isUnion() && (Size.isZero() || Size.isOne()))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't consider sizes resulting from macro expansions or template argument
|
||||
// substitution to form C89 tail-padded arrays.
|
||||
if (IgnoreTemplateOrMacroSubstitution) {
|
||||
TypeSourceInfo *TInfo = FD->getTypeSourceInfo();
|
||||
while (TInfo) {
|
||||
TypeLoc TL = TInfo->getTypeLoc();
|
||||
// Look through typedefs.
|
||||
if (TypedefTypeLoc TTL = TL.getAsAdjusted<TypedefTypeLoc>()) {
|
||||
const TypedefNameDecl *TDL = TTL.getTypedefNameDecl();
|
||||
TInfo = TDL->getTypeSourceInfo();
|
||||
continue;
|
||||
}
|
||||
if (ConstantArrayTypeLoc CTL = TL.getAs<ConstantArrayTypeLoc>()) {
|
||||
const Expr *SizeExpr = dyn_cast<IntegerLiteral>(CTL.getSizeExpr());
|
||||
if (!SizeExpr || SizeExpr->getExprLoc().isMacroID())
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RecordDecl::field_iterator FI(
|
||||
DeclContext::decl_iterator(const_cast<FieldDecl *>(FD)));
|
||||
return ++FI == FD->getParent()->field_end();
|
||||
}
|
||||
|
||||
return false;
|
||||
return Decl::isFlexibleArrayMemberLike(Ctx, D, E->getType(),
|
||||
StrictFlexArraysLevel,
|
||||
IgnoreTemplateOrMacroSubstitution);
|
||||
}
|
||||
|
||||
const ValueDecl *
|
||||
|
||||
@@ -853,6 +853,57 @@ CodeGenFunction::emitBuiltinObjectSize(const Expr *E, unsigned Type,
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDynamic) {
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
|
||||
getLangOpts().getStrictFlexArraysLevel();
|
||||
const Expr *Base = E->IgnoreParenImpCasts();
|
||||
|
||||
if (FieldDecl *FD = FindCountedByField(Base, StrictFlexArraysLevel)) {
|
||||
const auto *ME = dyn_cast<MemberExpr>(Base);
|
||||
llvm::Value *ObjectSize = nullptr;
|
||||
|
||||
if (!ME) {
|
||||
const auto *DRE = dyn_cast<DeclRefExpr>(Base);
|
||||
ValueDecl *VD = nullptr;
|
||||
|
||||
ObjectSize = ConstantInt::get(
|
||||
ResType,
|
||||
getContext().getTypeSize(DRE->getType()->getPointeeType()) / 8,
|
||||
true);
|
||||
|
||||
if (auto *RD = DRE->getType()->getPointeeType()->getAsRecordDecl())
|
||||
VD = RD->getLastField();
|
||||
|
||||
Expr *ICE = ImplicitCastExpr::Create(
|
||||
getContext(), DRE->getType(), CK_LValueToRValue,
|
||||
const_cast<Expr *>(cast<Expr>(DRE)), nullptr, VK_PRValue,
|
||||
FPOptionsOverride());
|
||||
ME = MemberExpr::CreateImplicit(getContext(), ICE, true, VD,
|
||||
VD->getType(), VK_LValue, OK_Ordinary);
|
||||
}
|
||||
|
||||
// At this point, we know that \p ME is a flexible array member.
|
||||
const auto *ArrayTy = getContext().getAsArrayType(ME->getType());
|
||||
unsigned Size = getContext().getTypeSize(ArrayTy->getElementType());
|
||||
|
||||
llvm::Value *CountField =
|
||||
EmitAnyExprToTemp(MemberExpr::CreateImplicit(
|
||||
getContext(), const_cast<Expr *>(ME->getBase()),
|
||||
ME->isArrow(), FD, FD->getType(), VK_LValue,
|
||||
OK_Ordinary))
|
||||
.getScalarVal();
|
||||
|
||||
llvm::Value *Mul = Builder.CreateMul(
|
||||
CountField, llvm::ConstantInt::get(CountField->getType(), Size / 8));
|
||||
Mul = Builder.CreateZExtOrTrunc(Mul, ResType);
|
||||
|
||||
if (ObjectSize)
|
||||
return Builder.CreateAdd(ObjectSize, Mul);
|
||||
|
||||
return Mul;
|
||||
}
|
||||
}
|
||||
|
||||
// LLVM can't handle Type=3 appropriately, and __builtin_object_size shouldn't
|
||||
// evaluate E for side-effects. In either case, we shouldn't lower to
|
||||
// @llvm.objectsize.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clang/Basic/CodeGenOptions.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "llvm/ADT/Hashing.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/IR/DataLayout.h"
|
||||
#include "llvm/IR/Intrinsics.h"
|
||||
@@ -931,16 +932,31 @@ static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
|
||||
if (CE->getCastKind() == CK_ArrayToPointerDecay &&
|
||||
!CE->getSubExpr()->isFlexibleArrayMemberLike(CGF.getContext(),
|
||||
StrictFlexArraysLevel)) {
|
||||
CodeGenFunction::SanitizerScope SanScope(&CGF);
|
||||
|
||||
IndexedType = CE->getSubExpr()->getType();
|
||||
const ArrayType *AT = IndexedType->castAsArrayTypeUnsafe();
|
||||
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
|
||||
return CGF.Builder.getInt(CAT->getSize());
|
||||
else if (const auto *VAT = dyn_cast<VariableArrayType>(AT))
|
||||
|
||||
if (const auto *VAT = dyn_cast<VariableArrayType>(AT))
|
||||
return CGF.getVLASize(VAT).NumElts;
|
||||
// Ignore pass_object_size here. It's not applicable on decayed pointers.
|
||||
}
|
||||
|
||||
if (FieldDecl *FD = CGF.FindCountedByField(Base, StrictFlexArraysLevel)) {
|
||||
const auto *ME = dyn_cast<MemberExpr>(CE->getSubExpr());
|
||||
IndexedType = Base->getType();
|
||||
return CGF
|
||||
.EmitAnyExprToTemp(MemberExpr::CreateImplicit(
|
||||
CGF.getContext(), const_cast<Expr *>(ME->getBase()),
|
||||
ME->isArrow(), FD, FD->getType(), VK_LValue, OK_Ordinary))
|
||||
.getScalarVal();
|
||||
}
|
||||
}
|
||||
|
||||
CodeGenFunction::SanitizerScope SanScope(&CGF);
|
||||
|
||||
QualType EltTy{Base->getType()->getPointeeOrArrayElementType(), 0};
|
||||
if (llvm::Value *POS = CGF.LoadPassedObjectSize(Base, EltTy)) {
|
||||
IndexedType = Base->getType();
|
||||
@@ -950,13 +966,53 @@ static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FieldDecl *CodeGenFunction::FindCountedByField(
|
||||
const Expr *Base,
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel) {
|
||||
const ValueDecl *VD = nullptr;
|
||||
|
||||
Base = Base->IgnoreParenImpCasts();
|
||||
|
||||
if (const auto *ME = dyn_cast<MemberExpr>(Base)) {
|
||||
VD = dyn_cast<ValueDecl>(ME->getMemberDecl());
|
||||
} else if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) {
|
||||
// Pointing to the full structure.
|
||||
VD = dyn_cast<ValueDecl>(DRE->getDecl());
|
||||
|
||||
QualType Ty = VD->getType();
|
||||
if (Ty->isPointerType())
|
||||
Ty = Ty->getPointeeType();
|
||||
|
||||
if (const auto *RD = Ty->getAsRecordDecl())
|
||||
VD = RD->getLastField();
|
||||
} else if (const auto *CE = dyn_cast<CastExpr>(Base)) {
|
||||
if (const auto *ME = dyn_cast<MemberExpr>(CE->getSubExpr()))
|
||||
VD = dyn_cast<ValueDecl>(ME->getMemberDecl());
|
||||
}
|
||||
|
||||
const auto *FD = dyn_cast_if_present<FieldDecl>(VD);
|
||||
if (!FD || !FD->getParent() ||
|
||||
!Decl::isFlexibleArrayMemberLike(getContext(), FD, FD->getType(),
|
||||
StrictFlexArraysLevel, true))
|
||||
return nullptr;
|
||||
|
||||
const auto *CBA = FD->getAttr<CountedByAttr>();
|
||||
if (!CBA)
|
||||
return nullptr;
|
||||
|
||||
StringRef FieldName = CBA->getCountedByField()->getName();
|
||||
auto It =
|
||||
llvm::find_if(FD->getParent()->fields(), [&](const FieldDecl *Field) {
|
||||
return FieldName == Field->getName();
|
||||
});
|
||||
return It != FD->getParent()->field_end() ? *It : nullptr;
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base,
|
||||
llvm::Value *Index, QualType IndexType,
|
||||
bool Accessed) {
|
||||
assert(SanOpts.has(SanitizerKind::ArrayBounds) &&
|
||||
"should not be called unless adding bounds checks");
|
||||
SanitizerScope SanScope(this);
|
||||
|
||||
const LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
|
||||
getLangOpts().getStrictFlexArraysLevel();
|
||||
|
||||
@@ -966,6 +1022,8 @@ void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base,
|
||||
if (!Bound)
|
||||
return;
|
||||
|
||||
SanitizerScope SanScope(this);
|
||||
|
||||
bool IndexSigned = IndexType->isSignedIntegerOrEnumerationType();
|
||||
llvm::Value *IndexVal = Builder.CreateIntCast(Index, SizeTy, IndexSigned);
|
||||
llvm::Value *BoundVal = Builder.CreateIntCast(Bound, SizeTy, false);
|
||||
|
||||
@@ -3022,6 +3022,12 @@ public:
|
||||
void EmitBoundsCheck(const Expr *E, const Expr *Base, llvm::Value *Index,
|
||||
QualType IndexType, bool Accessed);
|
||||
|
||||
/// Find the FieldDecl specified in a FAM's "counted_by" attribute. Returns
|
||||
/// \p nullptr if either the attribute or the field doesn't exist.
|
||||
FieldDecl *FindCountedByField(
|
||||
const Expr *Base,
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel);
|
||||
|
||||
llvm::Value *EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
|
||||
bool isInc, bool isPre);
|
||||
ComplexPairTy EmitComplexPrePostIncDec(const UnaryOperator *E, LValue LV,
|
||||
|
||||
@@ -19447,6 +19447,20 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
|
||||
CDecl->setIvarRBraceLoc(RBrac);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the "counted_by" attribute to ensure that the count field exists in
|
||||
// the struct. Make sure we're performing this check on the outer-most
|
||||
// record. This is a C-only feature.
|
||||
if (!getLangOpts().CPlusPlus && Record &&
|
||||
!isa<RecordDecl>(Record->getParent())) {
|
||||
auto Pred = [](const Decl *D) {
|
||||
if (const auto *FD = dyn_cast_if_present<FieldDecl>(D))
|
||||
return FD->hasAttr<CountedByAttr>();
|
||||
return false;
|
||||
};
|
||||
if (const FieldDecl *FD = Record->findFieldIf(Pred))
|
||||
CheckCountedByAttr(S, FD);
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether the given integral value is representable within
|
||||
|
||||
@@ -8361,6 +8361,92 @@ static void handleZeroCallUsedRegsAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL));
|
||||
}
|
||||
|
||||
static void handleCountedByAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
if (!AL.isArgIdent(0)) {
|
||||
S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
||||
<< AL << AANT_ArgumentIdentifier;
|
||||
return;
|
||||
}
|
||||
|
||||
IdentifierLoc *IL = AL.getArgAsIdent(0);
|
||||
CountedByAttr *CBA =
|
||||
::new (S.Context) CountedByAttr(S.Context, AL, IL->Ident);
|
||||
CBA->setCountedByFieldLoc(IL->Loc);
|
||||
D->addAttr(CBA);
|
||||
}
|
||||
|
||||
bool Sema::CheckCountedByAttr(Scope *S, const FieldDecl *FD) {
|
||||
const auto *CBA = FD->getAttr<CountedByAttr>();
|
||||
const IdentifierInfo *FieldName = CBA->getCountedByField();
|
||||
DeclarationNameInfo NameInfo(FieldName,
|
||||
CBA->getCountedByFieldLoc().getBegin());
|
||||
|
||||
LookupResult MemResult(*this, NameInfo, Sema::LookupMemberName);
|
||||
LookupName(MemResult, S);
|
||||
|
||||
if (MemResult.empty()) {
|
||||
// The "counted_by" field needs to exist within the struct.
|
||||
LookupResult OrdResult(*this, NameInfo, Sema::LookupOrdinaryName);
|
||||
LookupName(OrdResult, S);
|
||||
|
||||
if (!OrdResult.empty()) {
|
||||
SourceRange SR = FD->getLocation();
|
||||
Diag(SR.getBegin(), diag::err_counted_by_must_be_in_structure)
|
||||
<< FieldName << SR;
|
||||
|
||||
if (auto *ND = OrdResult.getAsSingle<NamedDecl>()) {
|
||||
SR = ND->getLocation();
|
||||
Diag(SR.getBegin(), diag::note_flexible_array_counted_by_attr_field)
|
||||
<< ND << SR;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CXXScopeSpec SS;
|
||||
DeclFilterCCC<FieldDecl> Filter(FieldName);
|
||||
return DiagnoseEmptyLookup(S, SS, MemResult, Filter, nullptr, std::nullopt,
|
||||
const_cast<DeclContext *>(FD->getDeclContext()));
|
||||
}
|
||||
|
||||
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
|
||||
Context.getLangOpts().getStrictFlexArraysLevel();
|
||||
|
||||
if (!Decl::isFlexibleArrayMemberLike(Context, FD, FD->getType(),
|
||||
StrictFlexArraysLevel, true)) {
|
||||
// The "counted_by" attribute must be on a flexible array member.
|
||||
SourceRange SR = FD->getLocation();
|
||||
Diag(SR.getBegin(), diag::err_counted_by_attr_not_on_flexible_array_member)
|
||||
<< SR;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const FieldDecl *Field = MemResult.getAsSingle<FieldDecl>()) {
|
||||
if (Field->hasAttr<CountedByAttr>()) {
|
||||
// The "counted_by" field can't point to the flexible array member.
|
||||
SourceRange SR = CBA->getCountedByFieldLoc();
|
||||
Diag(SR.getBegin(), diag::err_counted_by_attr_refers_to_flexible_array)
|
||||
<< CBA->getCountedByField() << SR;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Field->getType()->isIntegerType() ||
|
||||
Field->getType()->isBooleanType()) {
|
||||
// The "counted_by" field must have an integer type.
|
||||
SourceRange SR = CBA->getCountedByFieldLoc();
|
||||
Diag(SR.getBegin(),
|
||||
diag::err_flexible_array_counted_by_attr_field_not_integer)
|
||||
<< CBA->getCountedByField() << SR;
|
||||
|
||||
SR = Field->getLocation();
|
||||
Diag(SR.getBegin(), diag::note_flexible_array_counted_by_attr_field)
|
||||
<< Field << SR;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
|
||||
const ParsedAttr &AL) {
|
||||
StringRef KindStr;
|
||||
@@ -9314,6 +9400,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
|
||||
handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
|
||||
break;
|
||||
|
||||
case ParsedAttr::AT_CountedBy:
|
||||
handleCountedByAttr(S, D, AL);
|
||||
break;
|
||||
|
||||
// Microsoft attributes:
|
||||
case ParsedAttr::AT_LayoutVersion:
|
||||
handleLayoutVersion(S, D, AL);
|
||||
|
||||
@@ -2458,7 +2458,8 @@ bool Sema::DiagnoseDependentMemberLookup(const LookupResult &R) {
|
||||
bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
|
||||
CorrectionCandidateCallback &CCC,
|
||||
TemplateArgumentListInfo *ExplicitTemplateArgs,
|
||||
ArrayRef<Expr *> Args, TypoExpr **Out) {
|
||||
ArrayRef<Expr *> Args, DeclContext *LookupCtx,
|
||||
TypoExpr **Out) {
|
||||
DeclarationName Name = R.getLookupName();
|
||||
|
||||
unsigned diagnostic = diag::err_undeclared_var_use;
|
||||
@@ -2474,7 +2475,8 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
|
||||
// unqualified lookup. This is useful when (for example) the
|
||||
// original lookup would not have found something because it was a
|
||||
// dependent name.
|
||||
DeclContext *DC = SS.isEmpty() ? CurContext : nullptr;
|
||||
DeclContext *DC =
|
||||
LookupCtx ? LookupCtx : (SS.isEmpty() ? CurContext : nullptr);
|
||||
while (DC) {
|
||||
if (isa<CXXRecordDecl>(DC)) {
|
||||
LookupQualifiedName(R, DC);
|
||||
@@ -2517,12 +2519,12 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
|
||||
emitEmptyLookupTypoDiagnostic(TC, *this, SS, Name, TypoLoc, Args,
|
||||
diagnostic, diagnostic_suggest);
|
||||
},
|
||||
nullptr, CTK_ErrorRecovery);
|
||||
nullptr, CTK_ErrorRecovery, LookupCtx);
|
||||
if (*Out)
|
||||
return true;
|
||||
} else if (S &&
|
||||
(Corrected = CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(),
|
||||
S, &SS, CCC, CTK_ErrorRecovery))) {
|
||||
} else if (S && (Corrected =
|
||||
CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), S,
|
||||
&SS, CCC, CTK_ErrorRecovery, LookupCtx))) {
|
||||
std::string CorrectedStr(Corrected.getAsString(getLangOpts()));
|
||||
bool DroppedSpecifier =
|
||||
Corrected.WillReplaceSpecifier() && Name.getAsString() == CorrectedStr;
|
||||
@@ -2812,7 +2814,7 @@ Sema::ActOnIdExpression(Scope *S, CXXScopeSpec &SS,
|
||||
// a template name, but we happen to have always already looked up the name
|
||||
// before we get here if it must be a template name.
|
||||
if (DiagnoseEmptyLookup(S, SS, R, CCC ? *CCC : DefaultValidator, nullptr,
|
||||
std::nullopt, &TE)) {
|
||||
std::nullopt, nullptr, &TE)) {
|
||||
if (TE && KeywordReplacement) {
|
||||
auto &State = getTypoExprState(TE);
|
||||
auto BestTC = State.Consumer->getNextCorrection();
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s
|
||||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
|
||||
|
||||
#if !__has_attribute(counted_by)
|
||||
#error "has attribute broken"
|
||||
#endif
|
||||
|
||||
#ifdef COUNTED_BY
|
||||
#define __counted_by(member) __attribute__((__counted_by__(member)))
|
||||
#else
|
||||
#define __counted_by(member)
|
||||
#endif
|
||||
|
||||
typedef long unsigned int size_t;
|
||||
|
||||
struct annotated {
|
||||
unsigned long flags;
|
||||
int count;
|
||||
int array[] __counted_by(count);
|
||||
};
|
||||
|
||||
// SANITIZE-WITH-ATTR-LABEL: define dso_local void @test1(
|
||||
// SANITIZE-WITH-ATTR-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
|
||||
// SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2:![0-9]+]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = sext i32 [[INDEX]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = icmp ult i64 [[TMP1]], [[TMP2]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP3]], label [[CONT7:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7:![0-9]+]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: handler.out_of_bounds:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP4:%.*]] = zext i32 [[INDEX]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB2:[0-9]+]], i64 [[TMP4]]) #[[ATTR2:[0-9]+]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: cont7:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[TMP1]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITH-ATTR-LABEL: define dso_local void @test1(
|
||||
// NO-SANITIZE-WITH-ATTR-SAME: ptr nocapture noundef writeonly [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[IDXPROM]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2:![0-9]+]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test1(
|
||||
// SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[IDXPROM]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2:![0-9]+]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test1(
|
||||
// NO-SANITIZE-WITHOUT-ATTR-SAME: ptr nocapture noundef writeonly [[P:%.*]], i32 noundef [[INDEX:%.*]], i32 noundef [[VAL:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[IDXPROM]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: store i32 [[VAL]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2:![0-9]+]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
void test1(struct annotated *p, int index, int val) {
|
||||
p->array[index] = val;
|
||||
}
|
||||
|
||||
// SANITIZE-WITH-ATTR-LABEL: define dso_local void @test2(
|
||||
// SANITIZE-WITH-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = icmp ugt i64 [[TMP1]], [[INDEX]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP2]], label [[CONT12:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: handler.out_of_bounds:
|
||||
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB4:[0-9]+]], i64 [[INDEX]]) #[[ATTR2]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: cont12:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = shl i32 [[TMP0]], 2
|
||||
// SANITIZE-WITH-ATTR-NEXT: store i32 [[TMP3]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITH-ATTR-LABEL: define dso_local void @test2(
|
||||
// NO-SANITIZE-WITH-ATTR-SAME: ptr nocapture noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = shl i32 [[TMP0]], 2
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: store i32 [[TMP1]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test2(
|
||||
// SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test2(
|
||||
// NO-SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
void test2(struct annotated *p, size_t index) {
|
||||
p->array[index] = __builtin_dynamic_object_size(p->array, 1);
|
||||
}
|
||||
|
||||
// SANITIZE-WITH-ATTR-LABEL: define dso_local void @test3(
|
||||
// SANITIZE-WITH-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = icmp ugt i64 [[TMP1]], [[INDEX]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP2]], label [[CONT12:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: handler.out_of_bounds:
|
||||
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB5:[0-9]+]], i64 [[INDEX]]) #[[ATTR2]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: cont12:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = shl i32 [[TMP0]], 2
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[CONV:%.*]] = add i32 [[TMP3]], 16
|
||||
// SANITIZE-WITH-ATTR-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITH-ATTR-LABEL: define dso_local void @test3(
|
||||
// NO-SANITIZE-WITH-ATTR-SAME: ptr nocapture noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1]] {
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[COUNT:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = load i32, ptr [[COUNT]], align 8, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = shl i32 [[TMP0]], 2
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[CONV:%.*]] = add i32 [[TMP1]], 16
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test3(
|
||||
// SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test3(
|
||||
// NO-SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i64 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1]] {
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED:%.*]], ptr [[P]], i64 0, i32 2, i64 [[INDEX]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
void test3(struct annotated *p, size_t index) {
|
||||
// This test differs from 'test2' by checking bdos on the whole array and not
|
||||
// just the FAM.
|
||||
p->array[index] = __builtin_dynamic_object_size(p, 1);
|
||||
}
|
||||
|
||||
struct annotated_with_anon_struct {
|
||||
unsigned long flags;
|
||||
struct {
|
||||
unsigned char count;
|
||||
int array[] __counted_by(count);
|
||||
};
|
||||
};
|
||||
|
||||
// SANITIZE-WITH-ATTR-LABEL: define dso_local void @test4(
|
||||
// SANITIZE-WITH-ATTR-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED_WITH_ANON_STRUCT:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 8, !tbaa [[TBAA8:![0-9]+]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = sext i32 [[INDEX]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = zext i8 [[TMP1]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP4:%.*]] = icmp ult i64 [[TMP2]], [[TMP3]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP4]], label [[CONT18:%.*]], label [[HANDLER_OUT_OF_BOUNDS:%.*]], !prof [[PROF7]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: handler.out_of_bounds:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP5:%.*]] = zext i32 [[INDEX]] to i64, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB6:[0-9]+]], i64 [[TMP5]]) #[[ATTR2]], !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize !6
|
||||
// SANITIZE-WITH-ATTR: cont18:
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[ARRAY:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 12
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i32], ptr [[ARRAY]], i64 0, i64 [[TMP2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[TMP6:%.*]] = shl i8 [[TMP1]], 2
|
||||
// SANITIZE-WITH-ATTR-NEXT: [[CONV:%.*]] = zext i8 [[TMP6]] to i32
|
||||
// SANITIZE-WITH-ATTR-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITH-ATTR-LABEL: define dso_local void @test4(
|
||||
// NO-SANITIZE-WITH-ATTR-SAME: ptr nocapture noundef [[P:%.*]], i32 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1]] {
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = getelementptr inbounds [[STRUCT_ANNOTATED_WITH_ANON_STRUCT:%.*]], ptr [[P]], i64 0, i32 1
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 8, !tbaa [[TBAA6:![0-9]+]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = shl i8 [[TMP1]], 2
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[CONV:%.*]] = zext i8 [[TMP2]] to i32
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[ARRAY:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 12
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i32], ptr [[ARRAY]], i64 0, i64 [[IDXPROM]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: store i32 [[CONV]], ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITH-ATTR-NEXT: ret void
|
||||
//
|
||||
// SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test4(
|
||||
// SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR0]] {
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAY:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 12
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i32], ptr [[ARRAY]], i64 0, i64 [[IDXPROM]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
// NO-SANITIZE-WITHOUT-ATTR-LABEL: define dso_local void @test4(
|
||||
// NO-SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[P:%.*]], i32 noundef [[INDEX:%.*]]) local_unnamed_addr #[[ATTR1]] {
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: entry:
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAY:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 12
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i32], ptr [[ARRAY]], i64 0, i64 [[IDXPROM]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: store i32 -1, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA2]]
|
||||
// NO-SANITIZE-WITHOUT-ATTR-NEXT: ret void
|
||||
//
|
||||
void test4(struct annotated_with_anon_struct *p, int index) {
|
||||
p->array[index] = __builtin_dynamic_object_size(p->array, 1);
|
||||
}
|
||||
@@ -69,7 +69,6 @@ int f7(union U *u, int i) {
|
||||
return u->c[i];
|
||||
}
|
||||
|
||||
|
||||
char B[10];
|
||||
char B2[10];
|
||||
// CHECK-LABEL: @f8
|
||||
@@ -82,3 +81,12 @@ void f8(int i, int k) {
|
||||
// NOOPTARRAY: call void @llvm.ubsantrap(i8 4)
|
||||
B2[k] = '\0';
|
||||
}
|
||||
|
||||
// See commit 9a954c6 that caused a SEGFAULT in this code.
|
||||
struct S {
|
||||
__builtin_va_list ap;
|
||||
} *s;
|
||||
// CHECK-LABEL: @f9
|
||||
struct S *f9(int i) {
|
||||
return &s[i];
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
// CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record)
|
||||
// CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record)
|
||||
// CHECK-NEXT: Convergent (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: CountedBy (SubjectMatchRule_field)
|
||||
// CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
|
||||
// CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
|
||||
// CHECK-NEXT: Destructor (SubjectMatchRule_function)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// RUN: %clang_cc1 -fstrict-flex-arrays=3 -fsyntax-only -verify %s
|
||||
|
||||
#define __counted_by(f) __attribute__((counted_by(f)))
|
||||
|
||||
struct bar;
|
||||
|
||||
struct not_found {
|
||||
int count;
|
||||
struct bar *fam[] __counted_by(bork); // expected-error {{use of undeclared identifier 'bork'}}
|
||||
};
|
||||
|
||||
struct not_found_suggest {
|
||||
int bork; // expected-note {{'bork' declared here}}
|
||||
struct bar *fam[] __counted_by(blork); // expected-error {{use of undeclared identifier 'blork'; did you mean 'bork'?}}
|
||||
};
|
||||
|
||||
int global; // expected-note {{'global' declared here}}
|
||||
|
||||
struct found_outside_of_struct {
|
||||
int bork;
|
||||
struct bar *fam[] __counted_by(global); // expected-error {{field 'global' in 'counted_by' not inside structure}}
|
||||
};
|
||||
|
||||
struct self_referrential {
|
||||
int bork;
|
||||
struct bar *self[] __counted_by(self); // expected-error {{'counted_by' cannot refer to the flexible array 'self'}}
|
||||
};
|
||||
|
||||
struct non_int_count {
|
||||
double dbl_count; // expected-note {{field 'dbl_count' declared here}}
|
||||
struct bar *fam[] __counted_by(dbl_count); // expected-error {{field 'dbl_count' in 'counted_by' must be a non-boolean integer type}}
|
||||
};
|
||||
|
||||
struct array_of_ints_count {
|
||||
int integers[2]; // expected-note {{field 'integers' declared here}}
|
||||
struct bar *fam[] __counted_by(integers); // expected-error {{field 'integers' in 'counted_by' must be a non-boolean integer type}}
|
||||
};
|
||||
|
||||
struct not_a_fam {
|
||||
int count;
|
||||
struct bar *non_fam __counted_by(count); // expected-error {{'counted_by' only applies to flexible array members}}
|
||||
};
|
||||
|
||||
struct annotated_with_anon_struct {
|
||||
unsigned long flags;
|
||||
struct {
|
||||
unsigned char count; // expected-note {{'count' declared here}}
|
||||
int array[] __counted_by(crount); // expected-error {{use of undeclared identifier 'crount'; did you mean 'count'?}}
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user