Skip to content

Commit cb71579

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 1601879 commit cb71579

16 files changed

+1773
-40
lines changed

clang/include/clang/AST/Type.h

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

2443+
/// \returns True if the type is incomplete and it is also a type that
2444+
/// cannot be completed by a later type definition.
2445+
///
2446+
/// E.g. For `void` this is true but for `struct ForwardDecl;` this is false
2447+
/// because a definition for `ForwardDecl` could be provided later on in the
2448+
/// translation unit.
2449+
///
2450+
/// Note even for types that this function returns true for it is still
2451+
/// possible for the declarations that contain this type to later have a
2452+
/// complete type in a translation unit. E.g.:
2453+
///
2454+
/// \code{.c}
2455+
/// // This decl has type 'char[]' which is incomplete and cannot be later
2456+
/// // completed by another by another type declaration.
2457+
/// extern char foo[];
2458+
/// // This decl how has complete type 'char[5]'.
2459+
/// char foo[5]; // foo has a complete type
2460+
/// \endcode
2461+
bool isIncompletableIncompleteType() const;
2462+
24432463
/// Determine whether this type is an object type.
24442464
bool isObjectType() const {
24452465
// C++ [basic.types]p8:
@@ -3343,6 +3363,8 @@ class CountAttributedType final
33433363
static bool classof(const Type *T) {
33443364
return T->getTypeClass() == CountAttributed;
33453365
}
3366+
3367+
StringRef GetAttributeName(bool WithMacroPrefix) const;
33463368
};
33473369

33483370
/// 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
@@ -4376,6 +4376,7 @@ def err_mismatched_visibility: Error<"visibility does not match previous declara
43764376
def note_previous_attribute : Note<"previous attribute is here">;
43774377
def note_conflicting_attribute : Note<"conflicting attribute is here">;
43784378
def note_attribute : Note<"attribute is here">;
4379+
def note_named_attribute : Note<"%0 attribute is here">;
43794380
def err_mismatched_ms_inheritance : Error<
43804381
"inheritance model does not match %select{definition|previous declaration}0">;
43814382
def warn_ignored_ms_inheritance : Warning<
@@ -6628,6 +6629,42 @@ def err_counted_by_attr_pointee_unknown_size : Error<
66286629
"%select{|. This will be an error in a future compiler version}3"
66296630
""
66306631
"}2">;
6632+
def err_counted_by_on_incomplete_type_on_assign : Error <
6633+
"cannot %select{"
6634+
"assign to %select{object|'%1'}2 that has|" // AA_Assigning,
6635+
"pass argument to %select{parameter|parameter '%1'}2 that has|" // AA_Passing,
6636+
"return|" // AA_Returning,
6637+
"convert to|" // AA_Converting (UNUSED)
6638+
"%select{|implicitly }3initialize %select{object|'%1'}2 that has|" // AA_Initializing,
6639+
"pass argument to parameter that has|" // AA_Sending (UNUSED)
6640+
"cast to|" // AA_Casting (UNUSED)
6641+
"pass argument to parameter that has" // AA_Passing_CFAudited (UNUSED)
6642+
"}0 type %4 because the pointee type %6 is incomplete and the '%5' attribute "
6643+
"requires the pointee type be complete when %select{"
6644+
"assigning|" // AA_Assigning,
6645+
"passing|" // AA_Passing,
6646+
"returning|" // AA_Returning,
6647+
"converting|" // AA_Converting (UNUSED)
6648+
"initializing|" // AA_Initializing,
6649+
"passing|" // AA_Sending (UNUSED)
6650+
"casting|" // AA_Casting (UNUSED)
6651+
"passing" // AA_Passing_CFAudited (UNUSED)
6652+
"}0; "
6653+
"consider providing a complete definition for %6 or using the "
6654+
"'__sized_by%select{|_or_null}7' attribute"
6655+
>;
6656+
def err_counted_by_on_incomplete_type_on_use : Error <
6657+
"cannot %select{"
6658+
"use '%1'|" // Generic expr
6659+
"call '%1'" // CallExpr
6660+
"}0 with%select{"
6661+
"|" // Generic expr
6662+
" return" // CallExpr
6663+
"}0 type %2 because the pointee type %3 is incomplete "
6664+
"and the '%4' attribute requires the pointee type be complete in this context; "
6665+
"consider providing a complete definition for %3 or using the "
6666+
"'__sized_by%select{|_or_null}5' attribute"
6667+
>;
66316668

66326669
def warn_counted_by_attr_elt_type_unknown_size :
66336670
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,

clang/include/clang/Sema/Sema.h

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,70 @@ class Sema final : public SemaBase {
20992099
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
21002100
bool OrNull);
21012101

2102+
// AssignmentAction - This is used by all the assignment diagnostic functions
2103+
// to represent what is actually causing the operation
2104+
enum AssignmentAction {
2105+
AA_Assigning,
2106+
AA_Passing,
2107+
AA_Returning,
2108+
AA_Converting,
2109+
AA_Initializing,
2110+
AA_Sending,
2111+
AA_Casting,
2112+
AA_Passing_CFAudited
2113+
};
2114+
2115+
/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
2116+
/// `__counted_by_or_null` pointer type \param LHSTy.
2117+
///
2118+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2119+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2120+
/// \param RHSExpr The expression being assigned from.
2121+
/// \param Action The type assignment being performed
2122+
/// \param Loc The SourceLocation to use for error diagnostics
2123+
/// \param ComputeAssignee If provided this function will be called before
2124+
/// emitting a diagnostic. The function should return the name of
2125+
/// entity being assigned to or an empty string if this cannot be
2126+
/// determined.
2127+
///
2128+
/// \returns True iff no diagnostic where emitted, false otherwise.
2129+
bool BoundsSafetyCheckAssignmentToCountAttrPtr(
2130+
QualType LHSTy, Expr *RHSExpr, Sema::AssignmentAction Action,
2131+
SourceLocation Loc,
2132+
std::function<std::string()> ComputeAssignee = nullptr);
2133+
2134+
/// Perform Bounds Safety Semantic checks for initializing a Bounds Safety
2135+
/// pointer.
2136+
///
2137+
/// \param Entity The entity being initialized
2138+
/// \param Kind The kind of initialization being performed
2139+
/// \param Action The type assignment being performed
2140+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2141+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2142+
/// \param RHSExpr The expression being used for initialization.
2143+
///
2144+
/// \returns True iff no diagnostic where emitted, false otherwise.
2145+
bool BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
2146+
const InitializationKind &Kind,
2147+
Sema::AssignmentAction Action,
2148+
QualType LHSType, Expr *RHSExpr);
2149+
2150+
/// Perform Bounds Safety semantic checks for uses of invalid uses counted_by
2151+
/// or counted_by_or_null pointers in \param E.
2152+
///
2153+
/// \param E the expression to check
2154+
///
2155+
/// \returns True iff no diagnostic where emitted, false otherwise.
2156+
bool BoundsSafetyCheckUseOfCountAttrPtr(Expr *E);
2157+
2158+
/// \returns A SourceRange for \param CATy.
2159+
///
2160+
/// This method tries to compute the most useful SourceRange for diagnostics.
2161+
/// If this fails the returned SourceRange will be the same as
2162+
/// `CATy->getCountExpr()->getSourceRange()`.
2163+
///
2164+
SourceRange BoundsSafetySourceRangeFor(const CountAttributedType *CATy);
2165+
21022166
///@}
21032167

21042168
//
@@ -6490,19 +6554,6 @@ class Sema final : public SemaBase {
64906554
/// cleanup that are created by the current full expression.
64916555
SmallVector<ExprWithCleanups::CleanupObject, 8> ExprCleanupObjects;
64926556

6493-
// AssignmentAction - This is used by all the assignment diagnostic functions
6494-
// to represent what is actually causing the operation
6495-
enum AssignmentAction {
6496-
AA_Assigning,
6497-
AA_Passing,
6498-
AA_Returning,
6499-
AA_Converting,
6500-
AA_Initializing,
6501-
AA_Sending,
6502-
AA_Casting,
6503-
AA_Passing_CFAudited
6504-
};
6505-
65066557
/// Determine whether the use of this declaration is valid, without
65076558
/// emitting diagnostics.
65086559
bool CanUseDecl(NamedDecl *D, bool TreatUnavailableAsInvalid);

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;
@@ -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)