Skip to content

Commit eb334fc

Browse files
committed
[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
1 parent d269ec3 commit eb334fc

16 files changed

+1763
-27
lines changed

clang/include/clang/AST/Type.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,26 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
24452445
return !isFunctionType();
24462446
}
24472447

2448+
/// \returns True if the type is incomplete and it is also a type that
2449+
/// cannot be completed by a later type definition.
2450+
///
2451+
/// E.g. For `void` this is true but for `struct ForwardDecl;` this is false
2452+
/// because a definition for `ForwardDecl` could be provided later on in the
2453+
/// translation unit.
2454+
///
2455+
/// Note even for types that this function returns true for it is still
2456+
/// possible for the declarations that contain this type to later have a
2457+
/// complete type in a translation unit. E.g.:
2458+
///
2459+
/// \code{.c}
2460+
/// // This decl has type 'char[]' which is incomplete and cannot be later
2461+
/// // completed by another by another type declaration.
2462+
/// extern char foo[];
2463+
/// // This decl how has complete type 'char[5]'.
2464+
/// char foo[5]; // foo has a complete type
2465+
/// \endcode
2466+
bool isIncompletableIncompleteType() const;
2467+
24482468
/// Determine whether this type is an object type.
24492469
bool isObjectType() const {
24502470
// C++ [basic.types]p8:
@@ -3352,6 +3372,8 @@ class CountAttributedType final
33523372
static bool classof(const Type *T) {
33533373
return T->getTypeClass() == CountAttributed;
33543374
}
3375+
3376+
StringRef GetAttributeName(bool WithMacroPrefix) const;
33553377
};
33563378

33573379
/// Represents a type which was implicitly adjusted by the semantic

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4385,6 +4385,7 @@ def err_mismatched_visibility: Error<"visibility does not match previous declara
43854385
def note_previous_attribute : Note<"previous attribute is here">;
43864386
def note_conflicting_attribute : Note<"conflicting attribute is here">;
43874387
def note_attribute : Note<"attribute is here">;
4388+
def note_named_attribute : Note<"%0 attribute is here">;
43884389
def err_mismatched_ms_inheritance : Error<
43894390
"inheritance model does not match %select{definition|previous declaration}0">;
43904391
def warn_ignored_ms_inheritance : Warning<
@@ -6643,6 +6644,42 @@ def err_counted_by_attr_pointee_unknown_size : Error<
66436644
"%select{|. This will be an error in a future compiler version}3"
66446645
""
66456646
"}2">;
6647+
def err_counted_by_on_incomplete_type_on_assign : Error <
6648+
"cannot %select{"
6649+
"assign to %select{object|'%1'}2 that has|" // AA_Assigning,
6650+
"pass argument to %select{parameter|parameter '%1'}2 that has|" // AA_Passing,
6651+
"return|" // AA_Returning,
6652+
"convert to|" // AA_Converting (UNUSED)
6653+
"%select{|implicitly }3initialize %select{object|'%1'}2 that has|" // AA_Initializing,
6654+
"pass argument to parameter that has|" // AA_Sending (UNUSED)
6655+
"cast to|" // AA_Casting (UNUSED)
6656+
"pass argument to parameter that has" // AA_Passing_CFAudited (UNUSED)
6657+
"}0 type %4 because the pointee type %6 is incomplete and the '%5' attribute "
6658+
"requires the pointee type be complete when %select{"
6659+
"assigning|" // AA_Assigning,
6660+
"passing|" // AA_Passing,
6661+
"returning|" // AA_Returning,
6662+
"converting|" // AA_Converting (UNUSED)
6663+
"initializing|" // AA_Initializing,
6664+
"passing|" // AA_Sending (UNUSED)
6665+
"casting|" // AA_Casting (UNUSED)
6666+
"passing" // AA_Passing_CFAudited (UNUSED)
6667+
"}0; "
6668+
"consider providing a complete definition for %6 or using the "
6669+
"'__sized_by%select{|_or_null}7' attribute"
6670+
>;
6671+
def err_counted_by_on_incomplete_type_on_use : Error <
6672+
"cannot %select{"
6673+
"use '%1'|" // Generic expr
6674+
"call '%1'" // CallExpr
6675+
"}0 with%select{"
6676+
"|" // Generic expr
6677+
" return" // CallExpr
6678+
"}0 type %2 because the pointee type %3 is incomplete "
6679+
"and the '%4' attribute requires the pointee type be complete in this context; "
6680+
"consider providing a complete definition for %3 or using the "
6681+
"'__sized_by%select{|_or_null}5' attribute"
6682+
>;
66466683

66476684
def warn_counted_by_attr_elt_type_unknown_size :
66486685
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,

clang/include/clang/Sema/Sema.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,6 +2050,57 @@ class Sema final : public SemaBase {
20502050
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
20512051
bool OrNull);
20522052

2053+
/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
2054+
/// `__counted_by_or_null` pointer type \param LHSTy.
2055+
///
2056+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2057+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2058+
/// \param RHSExpr The expression being assigned from.
2059+
/// \param Action The type assignment being performed
2060+
/// \param Loc The SourceLocation to use for error diagnostics
2061+
/// \param ComputeAssignee If provided this function will be called before
2062+
/// emitting a diagnostic. The function should return the name of
2063+
/// entity being assigned to or an empty string if this cannot be
2064+
/// determined.
2065+
///
2066+
/// \returns True iff no diagnostic where emitted, false otherwise.
2067+
bool BoundsSafetyCheckAssignmentToCountAttrPtr(
2068+
QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
2069+
SourceLocation Loc,
2070+
std::function<std::string()> ComputeAssignee = nullptr);
2071+
2072+
/// Perform Bounds Safety Semantic checks for initializing a Bounds Safety
2073+
/// pointer.
2074+
///
2075+
/// \param Entity The entity being initialized
2076+
/// \param Kind The kind of initialization being performed
2077+
/// \param Action The type assignment being performed
2078+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2079+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2080+
/// \param RHSExpr The expression being used for initialization.
2081+
///
2082+
/// \returns True iff no diagnostic where emitted, false otherwise.
2083+
bool BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
2084+
const InitializationKind &Kind,
2085+
AssignmentAction Action,
2086+
QualType LHSType, Expr *RHSExpr);
2087+
2088+
/// Perform Bounds Safety semantic checks for uses of invalid uses counted_by
2089+
/// or counted_by_or_null pointers in \param E.
2090+
///
2091+
/// \param E the expression to check
2092+
///
2093+
/// \returns True iff no diagnostic where emitted, false otherwise.
2094+
bool BoundsSafetyCheckUseOfCountAttrPtr(Expr *E);
2095+
2096+
/// \returns A SourceRange for \param CATy.
2097+
///
2098+
/// This method tries to compute the most useful SourceRange for diagnostics.
2099+
/// If this fails the returned SourceRange will be the same as
2100+
/// `CATy->getCountExpr()->getSourceRange()`.
2101+
///
2102+
SourceRange BoundsSafetySourceRangeFor(const CountAttributedType *CATy);
2103+
20532104
///@}
20542105

20552106
//

clang/lib/AST/Type.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,6 +2438,22 @@ bool Type::isIncompleteType(NamedDecl **Def) const {
24382438
}
24392439
}
24402440

2441+
bool Type::isIncompletableIncompleteType() const {
2442+
if (!isIncompleteType())
2443+
return false;
2444+
2445+
// Forward declarations of structs, classes, enums, and unions could be later
2446+
// completed in a compilation unit by providing a type definition.
2447+
if (isStructureOrClassType() || isEnumeralType() || isUnionType())
2448+
return false;
2449+
2450+
// Other types are incompletable.
2451+
//
2452+
// E.g. `char[]` and `void`. The type is incomplete and no future
2453+
// type declarations can make the type complete.
2454+
return true;
2455+
}
2456+
24412457
bool Type::isSizelessBuiltinType() const {
24422458
if (isSizelessVectorType())
24432459
return true;
@@ -3873,6 +3889,27 @@ CountAttributedType::CountAttributedType(
38733889
DeclSlot[i] = CoupledDecls[i];
38743890
}
38753891

3892+
StringRef CountAttributedType::GetAttributeName(bool WithMacroPrefix) const {
3893+
#define ENUMERATE_ATTRS(PREFIX) \
3894+
do { \
3895+
if (isCountInBytes()) { \
3896+
if (isOrNull()) \
3897+
return PREFIX "sized_by_or_null"; \
3898+
return PREFIX "sized_by"; \
3899+
} \
3900+
if (isOrNull()) \
3901+
return PREFIX "counted_by_or_null"; \
3902+
return PREFIX "counted_by"; \
3903+
} while (0)
3904+
3905+
if (WithMacroPrefix)
3906+
ENUMERATE_ATTRS("__");
3907+
else
3908+
ENUMERATE_ATTRS("");
3909+
3910+
#undef ENUMERATE_ATTRS
3911+
}
3912+
38763913
TypedefType::TypedefType(TypeClass tc, const TypedefNameDecl *D,
38773914
QualType Underlying, QualType can)
38783915
: Type(tc, can, toSemanticDependence(can->getDependence())),

0 commit comments

Comments
 (0)