Skip to content

Commit

Permalink
Turn 'counted_by' into a type attribute and parse it into 'CountAttri…
Browse files Browse the repository at this point in the history
…butedType' (#78000)

In `-fbounds-safety`, bounds annotations are considered type attributes
rather than declaration attributes. Constructing them as type attributes
allows us to extend the attribute to apply nested pointers, which is
essential to annotate functions that involve out parameters: `void
foo(int *__counted_by(*out_count) *out_buf, int *out_count)`.

We introduce a new sugar type to support bounds annotated types,
`CountAttributedType`. In order to maintain extra data (the bounds
expression and the dependent declaration information) that is not
trackable in `AttributedType` we create a new type dedicate to this
functionality.

This patch also extends the parsing logic to parse the `counted_by`
argument as an expression, which will allow us to extend the model to
support arguments beyond an identifier, e.g., `__counted_by(n + m)` in
the future as specified by `-fbounds-safety`.

This also adjusts `__bdos` and array-bounds sanitizer code that already
uses `CountedByAttr` to check `CountAttributedType` instead to get the
field referred to by the attribute.
  • Loading branch information
rapidsna authored Mar 20, 2024
1 parent b2082a9 commit 3eb9ff3
Show file tree
Hide file tree
Showing 38 changed files with 835 additions and 187 deletions.
7 changes: 7 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
DependentBitIntTypes;
llvm::FoldingSet<BTFTagAttributedType> BTFTagAttributedTypes;

mutable llvm::FoldingSet<CountAttributedType> CountAttributedTypes;

mutable llvm::FoldingSet<QualifiedTemplateName> QualifiedTemplateNames;
mutable llvm::FoldingSet<DependentTemplateName> DependentTemplateNames;
mutable llvm::FoldingSet<SubstTemplateTemplateParmStorage>
Expand Down Expand Up @@ -1341,6 +1343,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
return CanQualType::CreateUnsafe(getPointerType((QualType) T));
}

QualType
getCountAttributedType(QualType T, Expr *CountExpr, bool CountInBytes,
bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const;

/// Return the uniqued reference to a type adjusted from the original
/// type to a new type.
QualType getAdjustedType(QualType Orig, QualType New) const;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/PropertiesBase.td
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def UInt32 : CountPropertyType<"uint32_t">;
def UInt64 : CountPropertyType<"uint64_t">;
def UnaryTypeTransformKind : EnumPropertyType<"UnaryTransformType::UTTKind">;
def VectorKind : EnumPropertyType<"VectorKind">;
def TypeCoupledDeclRefInfo : PropertyType;

def ExceptionSpecInfo : PropertyType<"FunctionProtoType::ExceptionSpecInfo"> {
let BufferElementTypes = [ QualType ];
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,12 @@ DEF_TRAVERSE_TYPE(InjectedClassNameType, {})
DEF_TRAVERSE_TYPE(AttributedType,
{ TRY_TO(TraverseType(T->getModifiedType())); })

DEF_TRAVERSE_TYPE(CountAttributedType, {
if (T->getCountExpr())
TRY_TO(TraverseStmt(T->getCountExpr()));
TRY_TO(TraverseType(T->desugar()));
})

DEF_TRAVERSE_TYPE(BTFTagAttributedType,
{ TRY_TO(TraverseType(T->getWrappedType())); })

Expand Down Expand Up @@ -1401,6 +1407,9 @@ DEF_TRAVERSE_TYPELOC(MacroQualifiedType,
DEF_TRAVERSE_TYPELOC(AttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getModifiedLoc())); })

DEF_TRAVERSE_TYPELOC(CountAttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })

DEF_TRAVERSE_TYPELOC(BTFTagAttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); })

Expand Down
156 changes: 156 additions & 0 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class BTFTypeTagAttr;
class ExtQuals;
class QualType;
class ConceptDecl;
class ValueDecl;
class TagDecl;
class TemplateParameterList;
class Type;
Expand Down Expand Up @@ -2000,6 +2001,21 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
unsigned NumExpansions;
};

class CountAttributedTypeBitfields {
friend class CountAttributedType;

LLVM_PREFERRED_TYPE(TypeBitfields)
unsigned : NumTypeBits;

static constexpr unsigned NumCoupledDeclsBits = 4;
unsigned NumCoupledDecls : NumCoupledDeclsBits;
LLVM_PREFERRED_TYPE(bool)
unsigned CountInBytes : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned OrNull : 1;
};
static_assert(sizeof(CountAttributedTypeBitfields) <= sizeof(unsigned));

union {
TypeBitfields TypeBits;
ArrayTypeBitfields ArrayTypeBits;
Expand All @@ -2022,6 +2038,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
DependentTemplateSpecializationTypeBitfields
DependentTemplateSpecializationTypeBits;
PackExpansionTypeBitfields PackExpansionTypeBits;
CountAttributedTypeBitfields CountAttributedTypeBits;
};

private:
Expand Down Expand Up @@ -2264,6 +2281,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
bool isPointerType() const;
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
bool isCountAttributedType() const;
bool isBlockPointerType() const;
bool isVoidPointerType() const;
bool isReferenceType() const;
Expand Down Expand Up @@ -2724,6 +2742,14 @@ template <> const TemplateSpecializationType *Type::getAs() const;
/// until it reaches an AttributedType or a non-sugared type.
template <> const AttributedType *Type::getAs() const;

/// This will check for a BoundsAttributedType by removing any existing
/// sugar until it reaches an BoundsAttributedType or a non-sugared type.
template <> const BoundsAttributedType *Type::getAs() const;

/// This will check for a CountAttributedType by removing any existing
/// sugar until it reaches an CountAttributedType or a non-sugared type.
template <> const CountAttributedType *Type::getAs() const;

// We can do canonical leaf types faster, because we don't have to
// worry about preserving child type decoration.
#define TYPE(Class, Base)
Expand Down Expand Up @@ -2922,6 +2948,136 @@ class PointerType : public Type, public llvm::FoldingSetNode {
static bool classof(const Type *T) { return T->getTypeClass() == Pointer; }
};

/// [BoundsSafety] Represents information of declarations referenced by the
/// arguments of the `counted_by` attribute and the likes.
class TypeCoupledDeclRefInfo {
public:
using BaseTy = llvm::PointerIntPair<ValueDecl *, 1, unsigned>;

private:
enum {
DerefShift = 0,
DerefMask = 1,
};
BaseTy Data;

public:
/// \p D is to a declaration referenced by the argument of attribute. \p Deref
/// indicates whether \p D is referenced as a dereferenced form, e.g., \p
/// Deref is true for `*n` in `int *__counted_by(*n)`.
TypeCoupledDeclRefInfo(ValueDecl *D = nullptr, bool Deref = false);

bool isDeref() const;
ValueDecl *getDecl() const;
unsigned getInt() const;
void *getOpaqueValue() const;
bool operator==(const TypeCoupledDeclRefInfo &Other) const;
void setFromOpaqueValue(void *V);
};

/// [BoundsSafety] Represents a parent type class for CountAttributedType and
/// similar sugar types that will be introduced to represent a type with a
/// bounds attribute.
///
/// Provides a common interface to navigate declarations referred to by the
/// bounds expression.

class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
QualType WrappedTy;

protected:
ArrayRef<TypeCoupledDeclRefInfo> Decls; // stored in trailing objects

BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon);

public:
bool isSugared() const { return true; }
QualType desugar() const { return WrappedTy; }

using decl_iterator = const TypeCoupledDeclRefInfo *;
using decl_range = llvm::iterator_range<decl_iterator>;

decl_iterator dependent_decl_begin() const { return Decls.begin(); }
decl_iterator dependent_decl_end() const { return Decls.end(); }

unsigned getNumCoupledDecls() const { return Decls.size(); }

decl_range dependent_decls() const {
return decl_range(dependent_decl_begin(), dependent_decl_end());
}

ArrayRef<TypeCoupledDeclRefInfo> getCoupledDecls() const {
return {dependent_decl_begin(), dependent_decl_end()};
}

bool referencesFieldDecls() const;

static bool classof(const Type *T) {
// Currently, only `class CountAttributedType` inherits
// `BoundsAttributedType` but the subclass will grow as we add more bounds
// annotations.
switch (T->getTypeClass()) {
case CountAttributed:
return true;
default:
return false;
}
}
};

/// Represents a sugar type with `__counted_by` or `__sized_by` annotations,
/// including their `_or_null` variants.
class CountAttributedType final
: public BoundsAttributedType,
public llvm::TrailingObjects<CountAttributedType,
TypeCoupledDeclRefInfo> {
friend class ASTContext;

Expr *CountExpr;
/// \p CountExpr represents the argument of __counted_by or the likes. \p
/// CountInBytes indicates that \p CountExpr is a byte count (i.e.,
/// __sized_by(_or_null)) \p OrNull means it's an or_null variant (i.e.,
/// __counted_by_or_null or __sized_by_or_null) \p CoupledDecls contains the
/// list of declarations referenced by \p CountExpr, which the type depends on
/// for the bounds information.
CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr,
bool CountInBytes, bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);

unsigned numTrailingObjects(OverloadToken<TypeCoupledDeclRefInfo>) const {
return CountAttributedTypeBits.NumCoupledDecls;
}

public:
enum DynamicCountPointerKind {
CountedBy = 0,
SizedBy,
CountedByOrNull,
SizedByOrNull,
};

Expr *getCountExpr() const { return CountExpr; }
bool isCountInBytes() const { return CountAttributedTypeBits.CountInBytes; }
bool isOrNull() const { return CountAttributedTypeBits.OrNull; }

DynamicCountPointerKind getKind() const {
if (isOrNull())
return isCountInBytes() ? SizedByOrNull : CountedByOrNull;
return isCountInBytes() ? SizedBy : CountedBy;
}

void Profile(llvm::FoldingSetNodeID &ID) {
Profile(ID, desugar(), CountExpr, isCountInBytes(), isOrNull());
}

static void Profile(llvm::FoldingSetNodeID &ID, QualType WrappedTy,
Expr *CountExpr, bool CountInBytes, bool Nullable);

static bool classof(const Type *T) {
return T->getTypeClass() == CountAttributed;
}
};

/// Represents a type which was implicitly adjusted by the semantic
/// engine for arbitrary reasons. For example, array and function types can
/// decay, and function types can have their calling conventions adjusted.
Expand Down
26 changes: 26 additions & 0 deletions clang/include/clang/AST/TypeLoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,32 @@ class ObjCInterfaceTypeLoc : public ConcreteTypeLoc<ObjCObjectTypeLoc,
}
};

struct BoundsAttributedLocInfo {};
class BoundsAttributedTypeLoc
: public ConcreteTypeLoc<UnqualTypeLoc, BoundsAttributedTypeLoc,
BoundsAttributedType, BoundsAttributedLocInfo> {
public:
TypeLoc getInnerLoc() const { return getInnerTypeLoc(); }
QualType getInnerType() const { return getTypePtr()->desugar(); }
void initializeLocal(ASTContext &Context, SourceLocation Loc) {
// nothing to do
}
// LocalData is empty and TypeLocBuilder doesn't handle DataSize 1.
unsigned getLocalDataSize() const { return 0; }
};

class CountAttributedTypeLoc final
: public InheritingConcreteTypeLoc<BoundsAttributedTypeLoc,
CountAttributedTypeLoc,
CountAttributedType> {
public:
Expr *getCountExpr() const { return getTypePtr()->getCountExpr(); }
bool isCountInBytes() const { return getTypePtr()->isCountInBytes(); }
bool isOrNull() const { return getTypePtr()->isOrNull(); }

SourceRange getLocalSourceRange() const;
};

struct MacroQualifiedLocInfo {
SourceLocation ExpansionLoc;
};
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/AST/TypeProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ let Class = PointerType in {
def : Creator<[{ return ctx.getPointerType(pointeeType); }]>;
}

let Class = CountAttributedType in {
def : Property<"WrappedTy", QualType> {
let Read = [{ node->desugar() }];
}
def : Property<"CountExpr", ExprRef> {
let Read = [{ node->getCountExpr() }];
}
def : Property<"CountInBytes", Bool> {
let Read = [{ node->isCountInBytes() }];
}
def : Property<"OrNull", Bool> {
let Read = [{ node->isOrNull() }];
}
def : Property<"CoupledDecls", Array<TypeCoupledDeclRefInfo>> {
let Read = [{ node->getCoupledDecls() }];
}
def : Creator<[{ return ctx.getCountAttributedType(WrappedTy, CountExpr, CountInBytes, OrNull, CoupledDecls); }]>;
}

let Class = AdjustedType in {
def : Property<"originalType", QualType> {
let Read = [{ node->getOriginalType() }];
Expand Down
27 changes: 9 additions & 18 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,15 @@ def TypeNullUnspecified : TypeAttr {
let Documentation = [TypeNullUnspecifiedDocs];
}

def CountedBy : DeclOrTypeAttr {
let Spellings = [Clang<"counted_by">];
let Subjects = SubjectList<[Field], ErrorDiag>;
let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel">];
let ParseArgumentsAsUnevaluated = 1;
let Documentation = [CountedByDocs];
let LangOpts = [COnly];
}

// This is a marker used to indicate that an __unsafe_unretained qualifier was
// ignored because ARC is not enabled. The usual representation for this
// qualifier is as an ObjCOwnership attribute with Kind == "none".
Expand Down Expand Up @@ -4487,21 +4496,3 @@ def CodeAlign: StmtAttr {
static constexpr int MaximumAlignment = 4096;
}];
}

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; }
}];
}
14 changes: 10 additions & 4 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -6519,12 +6519,18 @@ def err_flexible_array_count_not_in_same_struct : Error<
"'counted_by' field %0 isn't within the same struct as the flexible array">;
def err_counted_by_attr_not_on_flexible_array_member : Error<
"'counted_by' only applies to C99 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_attr_refer_to_itself : Error<
"'counted_by' cannot refer to the flexible array member %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 err_counted_by_attr_argument_not_integer : Error<
"'counted_by' requires a non-boolean integer type argument">;
def err_counted_by_attr_only_support_simple_decl_reference : Error<
"'counted_by' argument must be a simple declaration reference">;
def err_counted_by_attr_in_union : Error<
"'counted_by' cannot be applied to a union member">;
def err_counted_by_attr_refer_to_union : Error<
"'counted_by' argument cannot refer to a union member">;
def note_flexible_array_counted_by_attr_field : Note<
"field %0 declared here">;

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/TypeNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ def ObjCTypeParamType : TypeNode<Type>, NeverCanonical;
def ObjCObjectType : TypeNode<Type>;
def ObjCInterfaceType : TypeNode<ObjCObjectType>, LeafType;
def ObjCObjectPointerType : TypeNode<Type>;
def BoundsAttributedType : TypeNode<Type, 1>;
def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical;
def PipeType : TypeNode<Type>;
def AtomicType : TypeNode<Type>;
def BitIntType : TypeNode<Type>;
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -3073,6 +3073,11 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
ParsedAttr::Form Form);

void ParseBoundsAttribute(IdentifierInfo &AttrName,
SourceLocation AttrNameLoc, ParsedAttributes &Attrs,
IdentifierInfo *ScopeName, SourceLocation ScopeLoc,
ParsedAttr::Form Form);

void ParseTypeofSpecifier(DeclSpec &DS);
SourceLocation ParseDecltypeSpecifier(DeclSpec &DS);
void AnnotateExistingDecltypeSpecifier(const DeclSpec &DS,
Expand Down
Loading

0 comments on commit 3eb9ff3

Please sign in to comment.