Skip to content

Commit 59eafd1

Browse files
authored
[BoundsSafety][Sema] Allow counted_by and counted_by_or_null on pointers where the pointee type is incomplete but potentially completable (#106321)
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 the swift lang 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 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 eec1af2 commit 59eafd1

15 files changed

+1654
-27
lines changed

clang/include/clang/AST/Type.h

+22
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,26 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
24462446
return !isFunctionType();
24472447
}
24482448

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

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

clang/include/clang/Basic/DiagnosticSemaKinds.td

+24
Original file line numberDiff line numberDiff line change
@@ -6738,6 +6738,30 @@ def err_counted_by_attr_pointee_unknown_size : Error<
67386738
"%select{|. This will be an error in a future compiler version}3"
67396739
""
67406740
"}2">;
6741+
def err_counted_by_on_incomplete_type_on_assign : Error <
6742+
"cannot %select{"
6743+
"assign to %select{object|'%1'}2 with|" // AA_Assigning,
6744+
"pass argument to %select{parameter|parameter '%1'}2 with|" // AA_Passing,
6745+
"return|" // AA_Returning,
6746+
"convert to|" // AA_Converting (UNUSED)
6747+
"%select{|implicitly }3initialize %select{object|'%1'}2 with|" // AA_Initializing,
6748+
"pass argument to parameter with|" // AA_Sending (UNUSED)
6749+
"cast to|" // AA_Casting (UNUSED)
6750+
"pass argument to parameter with" // AA_Passing_CFAudited (UNUSED)
6751+
"}0 '%5' attributed type %4 because the pointee type %6 is incomplete">;
6752+
6753+
def err_counted_by_on_incomplete_type_on_use : Error <
6754+
"cannot %select{"
6755+
"use '%1' with '%4' attributed|" // Generic expr
6756+
"call '%1' with '%4' attributed return" // CallExpr
6757+
"}0 type %2 because the pointee type %3 is incomplete">;
6758+
6759+
def note_counted_by_consider_completing_pointee_ty : Note<
6760+
"consider providing a complete definition for %0">;
6761+
6762+
def note_counted_by_consider_using_sized_by : Note<
6763+
"consider using '__sized_by%select{|_or_null}0' instead of "
6764+
"'__counted_by%select{|_or_null}0'">;
67416765

67426766
def warn_counted_by_attr_elt_type_unknown_size :
67436767
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,

clang/include/clang/Sema/Sema.h

+44
Original file line numberDiff line numberDiff line change
@@ -2100,6 +2100,50 @@ class Sema final : public SemaBase {
21002100
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
21012101
bool OrNull);
21022102

2103+
/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
2104+
/// `__counted_by_or_null` pointer type \param LHSTy.
2105+
///
2106+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2107+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2108+
/// \param RHSExpr The expression being assigned from.
2109+
/// \param Action The type assignment being performed
2110+
/// \param Loc The SourceLocation to use for error diagnostics
2111+
/// \param Assignee The ValueDecl being assigned. This is used to compute
2112+
/// the name of the assignee. If the assignee isn't known this can
2113+
/// be set to nullptr.
2114+
/// \param ShowFullyQualifiedAssigneeName If set to true when using \p
2115+
/// Assignee to compute the name of the assignee use the fully
2116+
/// qualified name, otherwise use the unqualified name.
2117+
///
2118+
/// \returns True iff no diagnostic where emitted, false otherwise.
2119+
bool BoundsSafetyCheckAssignmentToCountAttrPtr(
2120+
QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
2121+
SourceLocation Loc, const ValueDecl *Assignee,
2122+
bool ShowFullyQualifiedAssigneeName);
2123+
2124+
/// Perform Bounds Safety Semantic checks for initializing a Bounds Safety
2125+
/// pointer.
2126+
///
2127+
/// \param Entity The entity being initialized
2128+
/// \param Kind The kind of initialization being performed
2129+
/// \param Action The type assignment being performed
2130+
/// \param LHSTy The type being assigned to. Checks will only be performed if
2131+
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
2132+
/// \param RHSExpr The expression being used for initialization.
2133+
///
2134+
/// \returns True iff no diagnostic where emitted, false otherwise.
2135+
bool BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
2136+
const InitializationKind &Kind,
2137+
AssignmentAction Action,
2138+
QualType LHSType, Expr *RHSExpr);
2139+
2140+
/// Perform Bounds Safety semantic checks for uses of invalid uses counted_by
2141+
/// or counted_by_or_null pointers in \param E.
2142+
///
2143+
/// \param E the expression to check
2144+
///
2145+
/// \returns True iff no diagnostic where emitted, false otherwise.
2146+
bool BoundsSafetyCheckUseOfCountAttrPtr(const Expr *E);
21032147
///@}
21042148

21052149
//

clang/lib/AST/Type.cpp

+41
Original file line numberDiff line numberDiff line change
@@ -2499,6 +2499,22 @@ bool Type::isIncompleteType(NamedDecl **Def) const {
24992499
}
25002500
}
25012501

2502+
bool Type::isAlwaysIncompleteType() const {
2503+
if (!isIncompleteType())
2504+
return false;
2505+
2506+
// Forward declarations of structs, classes, enums, and unions could be later
2507+
// completed in a compilation unit by providing a type definition.
2508+
if (getAsTagDecl())
2509+
return false;
2510+
2511+
// Other types are incompletable.
2512+
//
2513+
// E.g. `char[]` and `void`. The type is incomplete and no future
2514+
// type declarations can make the type complete.
2515+
return true;
2516+
}
2517+
25022518
bool Type::isSizelessBuiltinType() const {
25032519
if (isSizelessVectorType())
25042520
return true;
@@ -3940,6 +3956,31 @@ CountAttributedType::CountAttributedType(
39403956
DeclSlot[i] = CoupledDecls[i];
39413957
}
39423958

3959+
StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const {
3960+
// TODO: This method isn't really ideal because it doesn't return the spelling
3961+
// of the attribute that was used in the user's code. This method is used for
3962+
// diagnostics so the fact it doesn't use the spelling of the attribute in
3963+
// the user's code could be confusing (#113585).
3964+
#define ENUMERATE_ATTRS(PREFIX) \
3965+
do { \
3966+
if (isCountInBytes()) { \
3967+
if (isOrNull()) \
3968+
return PREFIX "sized_by_or_null"; \
3969+
return PREFIX "sized_by"; \
3970+
} \
3971+
if (isOrNull()) \
3972+
return PREFIX "counted_by_or_null"; \
3973+
return PREFIX "counted_by"; \
3974+
} while (0)
3975+
3976+
if (WithMacroPrefix)
3977+
ENUMERATE_ATTRS("__");
3978+
else
3979+
ENUMERATE_ATTRS("");
3980+
3981+
#undef ENUMERATE_ATTRS
3982+
}
3983+
39433984
TypedefType::TypedefType(TypeClass tc, const TypedefNameDecl *D,
39443985
QualType Underlying, QualType can)
39453986
: Type(tc, can, toSemanticDependence(can->getDependence())),

0 commit comments

Comments
 (0)