[clang] [BoundsSafety][Sema] Allow counted_by and counted_by_or_null on pointers where the pointee type is incomplete but potentially completable (PR #106321)

Dan Liew via cfe-commits cfe-commits at lists.llvm.org
Tue Jan 7 11:12:39 PST 2025


================
@@ -186,4 +218,216 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
   return false;
 }
 
+SourceRange Sema::BoundsSafetySourceRangeFor(const CountAttributedType *CATy) {
+  // This is an approximation that's not quite right. This points to the
+  // the expression inside the attribute rather than the attribute itself.
+  //
+  // TODO: Implement logic to find the relevant TypeLoc for the attribute and
+  // get the SourceRange from that (#113582).
+  return CATy->getCountExpr()->getSourceRange();
+}
+
+static void EmitIncompleteCountedByPointeeNotes(Sema &S,
+                                                const CountAttributedType *CATy,
+                                                NamedDecl *IncompleteTyDecl,
+                                                bool NoteAttrLocation = true) {
+  assert(IncompleteTyDecl == nullptr || isa<TypeDecl>(IncompleteTyDecl));
+
+  if (NoteAttrLocation) {
+    // Note where the attribute is declared
+    auto AttrSrcRange = S.BoundsSafetySourceRangeFor(CATy);
+    S.Diag(AttrSrcRange.getBegin(), diag::note_named_attribute)
+        << CATy->getAttributeName(/*WithMacroPrefix=*/true) << AttrSrcRange;
+  }
+
+  if (!IncompleteTyDecl)
+    return;
+
+  // If there's an associated forward declaration display it to emphasize
+  // why the type is incomplete (all we have is a forward declaration).
+
+  // Note the `IncompleteTyDecl` type is the underlying type which might not
+  // be the same as `CATy->getPointeeType()` which could be a typedef.
+  //
+  // The diagnostic printed will be at the location of the underlying type but
+  // the diagnostic text will print the type of `CATy->getPointeeType()` which
+  // could be a typedef name rather than the underlying type. This is ok
+  // though because the diagnostic will print the underlying type name too.
+  // E.g:
+  //
+  // `forward declaration of 'Incomplete_Struct_t'
+  //  (aka 'struct IncompleteStructTy')`
+  //
+  // If this ends up being confusing we could emit a second diagnostic (one
+  // explaining where the typedef is) but that seems overly verbose.
+
+  S.Diag(IncompleteTyDecl->getBeginLoc(), diag::note_forward_declaration)
+      << CATy->getPointeeType();
+}
+
+static bool
+HasCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND,
+                                    const CountAttributedType **CATyOut,
+                                    QualType *PointeeTyOut) {
+  auto *CATy = Ty->getAs<CountAttributedType>();
+  if (!CATy)
+    return false;
+
+  // Incomplete pointee type is only a problem for
+  // counted_by/counted_by_or_null
+  if (CATy->isCountInBytes())
+    return false;
+
+  auto PointeeTy = CATy->getPointeeType();
+  if (PointeeTy.isNull())
+    return false; // Reachable?
+
+  if (!PointeeTy->isIncompleteType(ND))
+    return false;
+
+  if (CATyOut)
+    *CATyOut = CATy;
+  if (PointeeTyOut)
+    *PointeeTyOut = PointeeTy;
+  return true;
+}
+
+/// Perform Checks for assigning to a `__counted_by` or
+/// `__counted_by_or_null` pointer type \param LHSTy where the pointee type
+/// is incomplete which is invalid.
+///
+/// \param S The Sema instance.
+/// \param LHSTy The type being assigned to. Checks will only be performed if
+///              the type is a `counted_by` or `counted_by_or_null ` pointer.
+/// \param RHSExpr The expression being assigned from.
+/// \param Action The type assignment being performed
+/// \param Loc The SourceLocation to use for error diagnostics
+/// \param ComputeAssignee If provided this function will be called before
+///        emitting a diagnostic. The function should return the name of
+///        entity being assigned to or an empty string if this cannot be
+///        determined.
+///
+/// \returns True iff no diagnostic where emitted, false otherwise.
+static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
+    Sema &S, QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
+    SourceLocation Loc, llvm::function_ref<std::string()> ComputeAssignee) {
----------------
delcypher wrote:

> All the uses I saw at least end up passing in the name of some declaration—or are there cases where we need/want to pass in some other string?

* So in some cases there isn't a named assignee. This is the case for  return values as can be seen [here](https://github.com/swiftlang/llvm-project/blob/b0bbd7e8a003990aff9ce373bc6b83407630b5f5/clang/lib/Sema/SemaChecking.cpp#L9505). This is also the case for unnamed parameters as can be seen [here](https://github.com/swiftlang/llvm-project/blob/b0bbd7e8a003990aff9ce373bc6b83407630b5f5/clang/lib/Sema/SemaBoundsSafety.cpp#L640)
* In some cases we want the fully qualified name of a field in the struct. You can see this [here](https://github.com/swiftlang/llvm-project/blob/b0bbd7e8a003990aff9ce373bc6b83407630b5f5/clang/lib/Sema/SemaBoundsSafety.cpp#L537) for initializer lists and [here](https://github.com/swiftlang/llvm-project/blob/b0bbd7e8a003990aff9ce373bc6b83407630b5f5/clang/lib/Sema/SemaExpr.cpp#L16743) for assignment expressions

The reason the function takes a `function_ref` is I realized it was pretty wasteful to always allocate a `std::string` and compute it in the cases where the diagnostic is not emitted (the common case). So by making the allocation and computation of the string a `function_ref` this can be performed **only in the case that the diagnostic is emitted**.

That being said it might be possible to replace the `function_ref` by instead passing a `std::optional<clang::NamedDecl>` instead. I'll need to investigate if I can make this work correclty.

https://github.com/llvm/llvm-project/pull/106321


More information about the cfe-commits mailing list