Skip to content

Commit e058a44

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 876ee11 commit e058a44

16 files changed

+1760
-27
lines changed

clang/include/clang/AST/Type.h

+22
Original file line numberDiff line numberDiff line change
@@ -2417,6 +2417,26 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
24172417
return !isFunctionType();
24182418
}
24192419

2420+
/// \returns True if the type is incomplete and it is also a type that
2421+
/// cannot be completed by a later type definition.
2422+
///
2423+
/// E.g. For `void` this is true but for `struct ForwardDecl;` this is false
2424+
/// because a definition for `ForwardDecl` could be provided later on in the
2425+
/// translation unit.
2426+
///
2427+
/// Note even for types that this function returns true for it is still
2428+
/// possible for the declarations that contain this type to later have a
2429+
/// complete type in a translation unit. E.g.:
2430+
///
2431+
/// \code{.c}
2432+
/// // This decl has type 'char[]' which is incomplete and cannot be later
2433+
/// // completed by another by another type declaration.
2434+
/// extern char foo[];
2435+
/// // This decl how has complete type 'char[5]'.
2436+
/// char foo[5]; // foo has a complete type
2437+
/// \endcode
2438+
bool isIncompletableIncompleteType() const;
2439+
24202440
/// Determine whether this type is an object type.
24212441
bool isObjectType() const {
24222442
// C++ [basic.types]p8:
@@ -3320,6 +3340,8 @@ class CountAttributedType final
33203340
static bool classof(const Type *T) {
33213341
return T->getTypeClass() == CountAttributed;
33223342
}
3343+
3344+
StringRef GetAttributeName(bool WithMacroPrefix) const;
33233345
};
33243346

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

clang/include/clang/Basic/DiagnosticSemaKinds.td

+37
Original file line numberDiff line numberDiff line change
@@ -4370,6 +4370,7 @@ def err_mismatched_visibility: Error<"visibility does not match previous declara
43704370
def note_previous_attribute : Note<"previous attribute is here">;
43714371
def note_conflicting_attribute : Note<"conflicting attribute is here">;
43724372
def note_attribute : Note<"attribute is here">;
4373+
def note_named_attribute : Note<"%0 attribute is here">;
43734374
def err_mismatched_ms_inheritance : Error<
43744375
"inheritance model does not match %select{definition|previous declaration}0">;
43754376
def warn_ignored_ms_inheritance : Warning<
@@ -6619,6 +6620,42 @@ def err_counted_by_attr_pointee_unknown_size : Error<
66196620
"%select{|. This will be an error in a future compiler version}3"
66206621
""
66216622
"}2">;
6623+
def err_counted_by_on_incomplete_type_on_assign : Error <
6624+
"cannot %select{"
6625+
"assign to %select{object|'%1'}2 that has|" // AA_Assigning,
6626+
"pass argument to %select{parameter|parameter '%1'}2 that has|" // AA_Passing,
6627+
"return|" // AA_Returning,
6628+
"convert to|" // AA_Converting (UNUSED)
6629+
"%select{|implicitly }3initialize %select{object|'%1'}2 that has|" // AA_Initializing,
6630+
"pass argument to parameter that has|" // AA_Sending (UNUSED)
6631+
"cast to|" // AA_Casting (UNUSED)
6632+
"pass argument to parameter that has" // AA_Passing_CFAudited (UNUSED)
6633+
"}0 type %4 because the pointee type %6 is incomplete and the '%5' attribute "
6634+
"requires the pointee type be complete when %select{"
6635+
"assigning|" // AA_Assigning,
6636+
"passing|" // AA_Passing,
6637+
"returning|" // AA_Returning,
6638+
"converting|" // AA_Converting (UNUSED)
6639+
"initializing|" // AA_Initializing,
6640+
"passing|" // AA_Sending (UNUSED)
6641+
"casting|" // AA_Casting (UNUSED)
6642+
"passing" // AA_Passing_CFAudited (UNUSED)
6643+
"}0; "
6644+
"consider providing a complete definition for %6 or using the "
6645+
"'__sized_by%select{|_or_null}7' attribute"
6646+
>;
6647+
def err_counted_by_on_incomplete_type_on_use : Error <
6648+
"cannot %select{"
6649+
"use '%1'|" // Generic expr
6650+
"call '%1'" // CallExpr
6651+
"}0 with%select{"
6652+
"|" // Generic expr
6653+
" return" // CallExpr
6654+
"}0 type %2 because the pointee type %3 is incomplete "
6655+
"and the '%4' attribute requires the pointee type be complete in this context; "
6656+
"consider providing a complete definition for %3 or using the "
6657+
"'__sized_by%select{|_or_null}5' attribute"
6658+
>;
66226659

66236660
def warn_counted_by_attr_elt_type_unknown_size :
66246661
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,

clang/include/clang/Sema/Sema.h

+51
Original file line numberDiff line numberDiff line change
@@ -15086,6 +15086,57 @@ class Sema final : public SemaBase {
1508615086
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
1508715087
bool OrNull);
1508815088

15089+
/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
15090+
/// `__counted_by_or_null` pointer type \param LHSTy.
15091+
///
15092+
/// \param LHSTy The type being assigned to. Checks will only be performed if
15093+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
15094+
/// \param RHSExpr The expression being assigned from.
15095+
/// \param Action The type assignment being performed
15096+
/// \param Loc The SourceLocation to use for error diagnostics
15097+
/// \param ComputeAssignee If provided this function will be called before
15098+
/// emitting a diagnostic. The function should return the name of
15099+
/// entity being assigned to or an empty string if this cannot be
15100+
/// determined.
15101+
///
15102+
/// \returns True iff no diagnostic where emitted, false otherwise.
15103+
bool BoundsSafetyCheckAssignmentToCountAttrPtr(
15104+
QualType LHSTy, Expr *RHSExpr, Sema::AssignmentAction Action,
15105+
SourceLocation Loc,
15106+
std::function<std::string()> ComputeAssignee = nullptr);
15107+
15108+
/// Perform Bounds Safety Semantic checks for initializing a Bounds Safety
15109+
/// pointer.
15110+
///
15111+
/// \param Entity The entity being initialized
15112+
/// \param Kind The kind of initialization being performed
15113+
/// \param Action The type assignment being performed
15114+
/// \param LHSTy The type being assigned to. Checks will only be performed if
15115+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
15116+
/// \param RHSExpr The expression being used for initialization.
15117+
///
15118+
/// \returns True iff no diagnostic where emitted, false otherwise.
15119+
bool BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
15120+
const InitializationKind &Kind,
15121+
Sema::AssignmentAction Action,
15122+
QualType LHSType, Expr *RHSExpr);
15123+
15124+
/// Perform Bounds Safety semantic checks for uses of invalid uses counted_by
15125+
/// or counted_by_or_null pointers in \param E.
15126+
///
15127+
/// \param E the expression to check
15128+
///
15129+
/// \returns True iff no diagnostic where emitted, false otherwise.
15130+
bool BoundsSafetyCheckUseOfCountAttrPtr(Expr *E);
15131+
15132+
/// \returns A SourceRange for \param CATy.
15133+
///
15134+
/// This method tries to compute the most useful SourceRange for diagnostics.
15135+
/// If this fails the returned SourceRange will be the same as
15136+
/// `CATy->getCountExpr()->getSourceRange()`.
15137+
///
15138+
SourceRange BoundsSafetySourceRangeFor(const CountAttributedType *CATy);
15139+
1508915140
///@}
1509015141
};
1509115142

clang/lib/AST/Type.cpp

+37
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;
@@ -3862,6 +3878,27 @@ CountAttributedType::CountAttributedType(
38623878
DeclSlot[i] = CoupledDecls[i];
38633879
}
38643880

3881+
StringRef CountAttributedType::GetAttributeName(bool WithMacroPrefix) const {
3882+
#define ENUMERATE_ATTRS(PREFIX) \
3883+
do { \
3884+
if (isCountInBytes()) { \
3885+
if (isOrNull()) \
3886+
return PREFIX "sized_by_or_null"; \
3887+
return PREFIX "sized_by"; \
3888+
} \
3889+
if (isOrNull()) \
3890+
return PREFIX "counted_by_or_null"; \
3891+
return PREFIX "counted_by"; \
3892+
} while (0)
3893+
3894+
if (WithMacroPrefix)
3895+
ENUMERATE_ATTRS("__");
3896+
else
3897+
ENUMERATE_ATTRS("");
3898+
3899+
#undef ENUMERATE_ATTRS
3900+
}
3901+
38653902
TypedefType::TypedefType(TypeClass tc, const TypedefNameDecl *D,
38663903
QualType Underlying, QualType can)
38673904
: Type(tc, can, toSemanticDependence(can->getDependence())),

0 commit comments

Comments
 (0)