| 1 | //===-- SemaBoundsSafety.cpp - Bounds Safety specific routines-*- C++ -*---===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// \file |
| 9 | /// This file declares semantic analysis functions specific to `-fbounds-safety` |
| 10 | /// (Bounds Safety) and also its attributes when used without `-fbounds-safety` |
| 11 | /// (e.g. `counted_by`) |
| 12 | /// |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | #include "clang/Lex/Lexer.h" |
| 15 | #include "clang/Sema/Initialization.h" |
| 16 | #include "clang/Sema/Sema.h" |
| 17 | |
| 18 | namespace clang { |
| 19 | |
| 20 | static CountAttributedType::DynamicCountPointerKind |
| 21 | getCountAttrKind(bool CountInBytes, bool OrNull) { |
| 22 | if (CountInBytes) |
| 23 | return OrNull ? CountAttributedType::SizedByOrNull |
| 24 | : CountAttributedType::SizedBy; |
| 25 | return OrNull ? CountAttributedType::CountedByOrNull |
| 26 | : CountAttributedType::CountedBy; |
| 27 | } |
| 28 | |
| 29 | static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) { |
| 30 | const auto *RD = FD->getParent(); |
| 31 | // An unnamed struct is treated as anonymous struct at this point. |
| 32 | // A struct may not be fully processed yet to determine |
| 33 | // whether it's anonymous or not. In that case, this function treats it as |
| 34 | // an anonymous struct and tries to find a named parent. |
| 35 | while (RD && (RD->isAnonymousStructOrUnion() || RD->getName().empty())) { |
| 36 | const auto *Parent = dyn_cast<RecordDecl>(Val: RD->getParent()); |
| 37 | if (!Parent) |
| 38 | break; |
| 39 | RD = Parent; |
| 40 | } |
| 41 | return RD; |
| 42 | } |
| 43 | |
| 44 | enum class CountedByInvalidPointeeTypeKind { |
| 45 | INCOMPLETE, |
| 46 | SIZELESS, |
| 47 | FUNCTION, |
| 48 | FLEXIBLE_ARRAY_MEMBER, |
| 49 | VALID, |
| 50 | }; |
| 51 | |
| 52 | bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, |
| 53 | bool OrNull) { |
| 54 | // Check the context the attribute is used in |
| 55 | |
| 56 | unsigned Kind = getCountAttrKind(CountInBytes, OrNull); |
| 57 | |
| 58 | if (FD->getParent()->isUnion()) { |
| 59 | Diag(Loc: FD->getBeginLoc(), DiagID: diag::err_count_attr_in_union) |
| 60 | << Kind << FD->getSourceRange(); |
| 61 | return true; |
| 62 | } |
| 63 | |
| 64 | const auto FieldTy = FD->getType(); |
| 65 | if (FieldTy->isArrayType() && (CountInBytes || OrNull)) { |
| 66 | Diag(Loc: FD->getBeginLoc(), |
| 67 | DiagID: diag::err_count_attr_not_on_ptr_or_flexible_array_member) |
| 68 | << Kind << FD->getLocation() << /* suggest counted_by */ 1; |
| 69 | return true; |
| 70 | } |
| 71 | if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) { |
| 72 | Diag(Loc: FD->getBeginLoc(), |
| 73 | DiagID: diag::err_count_attr_not_on_ptr_or_flexible_array_member) |
| 74 | << Kind << FD->getLocation() << /* do not suggest counted_by */ 0; |
| 75 | return true; |
| 76 | } |
| 77 | |
| 78 | LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel = |
| 79 | LangOptions::StrictFlexArraysLevelKind::IncompleteOnly; |
| 80 | if (FieldTy->isArrayType() && |
| 81 | !Decl::isFlexibleArrayMemberLike(Context: getASTContext(), D: FD, Ty: FieldTy, |
| 82 | StrictFlexArraysLevel, IgnoreTemplateOrMacroSubstitution: true)) { |
| 83 | Diag(Loc: FD->getBeginLoc(), |
| 84 | DiagID: diag::err_counted_by_attr_on_array_not_flexible_array_member) |
| 85 | << Kind << FD->getLocation(); |
| 86 | return true; |
| 87 | } |
| 88 | |
| 89 | CountedByInvalidPointeeTypeKind InvalidTypeKind = |
| 90 | CountedByInvalidPointeeTypeKind::VALID; |
| 91 | QualType PointeeTy; |
| 92 | int SelectPtrOrArr = 0; |
| 93 | if (FieldTy->isPointerType()) { |
| 94 | PointeeTy = FieldTy->getPointeeType(); |
| 95 | SelectPtrOrArr = 0; |
| 96 | } else { |
| 97 | assert(FieldTy->isArrayType()); |
| 98 | const ArrayType *AT = getASTContext().getAsArrayType(T: FieldTy); |
| 99 | PointeeTy = AT->getElementType(); |
| 100 | SelectPtrOrArr = 1; |
| 101 | } |
| 102 | // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means |
| 103 | // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable |
| 104 | // when `FieldTy->isArrayType()`. |
| 105 | bool ShouldWarn = false; |
| 106 | if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) { |
| 107 | // In general using `counted_by` or `counted_by_or_null` on |
| 108 | // pointers where the pointee is an incomplete type are problematic. This is |
| 109 | // because it isn't possible to compute the pointer's bounds without knowing |
| 110 | // the pointee type size. At the same time it is common to forward declare |
| 111 | // types in header files. |
| 112 | // |
| 113 | // E.g.: |
| 114 | // |
| 115 | // struct Handle; |
| 116 | // struct Wrapper { |
| 117 | // size_t count; |
| 118 | // struct Handle* __counted_by(count) handles; |
| 119 | // } |
| 120 | // |
| 121 | // To allow the above code pattern but still prevent the pointee type from |
| 122 | // being incomplete in places where bounds checks are needed the following |
| 123 | // scheme is used: |
| 124 | // |
| 125 | // * When the pointee type might not always be an incomplete type (i.e. |
| 126 | // a type that is currently incomplete but might be completed later |
| 127 | // on in the translation unit) the attribute is allowed by this method |
| 128 | // but later uses of the FieldDecl are checked that the pointee type |
| 129 | // is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`, |
| 130 | // `BoundsSafetyCheckInitialization`, and |
| 131 | // `BoundsSafetyCheckUseOfCountAttrPtr` |
| 132 | // |
| 133 | // * When the pointee type is always an incomplete type (e.g. |
| 134 | // `void` in strict C mode) the attribute is disallowed by this method |
| 135 | // because we know the type can never be completed so there's no reason |
| 136 | // to allow it. |
| 137 | // |
| 138 | // Exception: void has an implicit size of 1 byte for pointer arithmetic |
| 139 | // (following GNU convention). Therefore, counted_by on void* is allowed |
| 140 | // and behaves equivalently to sized_by (treating the count as bytes). |
| 141 | bool IsVoidPtr = PointeeTy->isVoidType(); |
| 142 | if (IsVoidPtr) { |
| 143 | // Emit a warning that this is a GNU extension. |
| 144 | Diag(Loc: FD->getBeginLoc(), DiagID: diag::ext_gnu_counted_by_void_ptr) << Kind; |
| 145 | Diag(Loc: FD->getBeginLoc(), DiagID: diag::note_gnu_counted_by_void_ptr_use_sized_by) |
| 146 | << Kind; |
| 147 | assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID); |
| 148 | } else { |
| 149 | InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE; |
| 150 | } |
| 151 | } else if (PointeeTy->isSizelessType()) { |
| 152 | InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS; |
| 153 | } else if (PointeeTy->isFunctionType()) { |
| 154 | InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION; |
| 155 | } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) { |
| 156 | if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) { |
| 157 | // This is a workaround for the Linux kernel that has already adopted |
| 158 | // `counted_by` on a FAM where the pointee is a struct with a FAM. This |
| 159 | // should be an error because computing the bounds of the array cannot be |
| 160 | // done correctly without manually traversing every struct object in the |
| 161 | // array at runtime. To allow the code to be built this error is |
| 162 | // downgraded to a warning. |
| 163 | ShouldWarn = true; |
| 164 | } |
| 165 | InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER; |
| 166 | } |
| 167 | |
| 168 | if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) { |
| 169 | unsigned DiagID = ShouldWarn |
| 170 | ? diag::warn_counted_by_attr_elt_type_unknown_size |
| 171 | : diag::err_counted_by_attr_pointee_unknown_size; |
| 172 | Diag(Loc: FD->getBeginLoc(), DiagID) |
| 173 | << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind |
| 174 | << (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange(); |
| 175 | return true; |
| 176 | } |
| 177 | |
| 178 | // Check the expression |
| 179 | |
| 180 | if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) { |
| 181 | Diag(Loc: E->getBeginLoc(), DiagID: diag::err_count_attr_argument_not_integer) |
| 182 | << Kind << E->getSourceRange(); |
| 183 | return true; |
| 184 | } |
| 185 | |
| 186 | auto *DRE = dyn_cast<DeclRefExpr>(Val: E); |
| 187 | if (!DRE) { |
| 188 | Diag(Loc: E->getBeginLoc(), |
| 189 | DiagID: diag::err_count_attr_only_support_simple_decl_reference) |
| 190 | << Kind << E->getSourceRange(); |
| 191 | return true; |
| 192 | } |
| 193 | |
| 194 | auto *CountDecl = DRE->getDecl(); |
| 195 | FieldDecl *CountFD = dyn_cast<FieldDecl>(Val: CountDecl); |
| 196 | if (auto *IFD = dyn_cast<IndirectFieldDecl>(Val: CountDecl)) { |
| 197 | CountFD = IFD->getAnonField(); |
| 198 | } |
| 199 | if (!CountFD) { |
| 200 | Diag(Loc: E->getBeginLoc(), DiagID: diag::err_count_attr_must_be_in_structure) |
| 201 | << CountDecl << Kind << E->getSourceRange(); |
| 202 | |
| 203 | Diag(Loc: CountDecl->getBeginLoc(), |
| 204 | DiagID: diag::note_flexible_array_counted_by_attr_field) |
| 205 | << CountDecl << CountDecl->getSourceRange(); |
| 206 | return true; |
| 207 | } |
| 208 | |
| 209 | if (FD->getParent() != CountFD->getParent()) { |
| 210 | if (CountFD->getParent()->isUnion()) { |
| 211 | Diag(Loc: CountFD->getBeginLoc(), DiagID: diag::err_count_attr_refer_to_union) |
| 212 | << Kind << CountFD->getSourceRange(); |
| 213 | return true; |
| 214 | } |
| 215 | // Whether CountRD is an anonymous struct is not determined at this |
| 216 | // point. Thus, an additional diagnostic in case it's not anonymous struct |
| 217 | // is done later in `Parser::ParseStructDeclaration`. |
| 218 | auto *RD = GetEnclosingNamedOrTopAnonRecord(FD); |
| 219 | auto *CountRD = GetEnclosingNamedOrTopAnonRecord(FD: CountFD); |
| 220 | |
| 221 | if (RD != CountRD) { |
| 222 | Diag(Loc: E->getBeginLoc(), DiagID: diag::err_count_attr_param_not_in_same_struct) |
| 223 | << CountFD << Kind << FieldTy->isArrayType() << E->getSourceRange(); |
| 224 | Diag(Loc: CountFD->getBeginLoc(), |
| 225 | DiagID: diag::note_flexible_array_counted_by_attr_field) |
| 226 | << CountFD << CountFD->getSourceRange(); |
| 227 | return true; |
| 228 | } |
| 229 | } |
| 230 | return false; |
| 231 | } |
| 232 | |
| 233 | static void EmitIncompleteCountedByPointeeNotes(Sema &S, |
| 234 | const CountAttributedType *CATy, |
| 235 | NamedDecl *IncompleteTyDecl) { |
| 236 | assert(IncompleteTyDecl == nullptr || isa<TypeDecl>(IncompleteTyDecl)); |
| 237 | |
| 238 | if (IncompleteTyDecl) { |
| 239 | // Suggest completing the pointee type if its a named typed (i.e. |
| 240 | // IncompleteTyDecl isn't nullptr). Suggest this first as it is more likely |
| 241 | // to be the correct fix. |
| 242 | // |
| 243 | // Note the `IncompleteTyDecl` type is the underlying type which might not |
| 244 | // be the same as `CATy->getPointeeType()` which could be a typedef. |
| 245 | // |
| 246 | // The diagnostic printed will be at the location of the underlying type but |
| 247 | // the diagnostic text will print the type of `CATy->getPointeeType()` which |
| 248 | // could be a typedef name rather than the underlying type. This is ok |
| 249 | // though because the diagnostic will print the underlying type name too. |
| 250 | S.Diag(Loc: IncompleteTyDecl->getBeginLoc(), |
| 251 | DiagID: diag::note_counted_by_consider_completing_pointee_ty) |
| 252 | << CATy->getPointeeType(); |
| 253 | } |
| 254 | |
| 255 | // Suggest using __sized_by(_or_null) instead of __counted_by(_or_null) as |
| 256 | // __sized_by(_or_null) doesn't have the complete type restriction. |
| 257 | // |
| 258 | // We use the source range of the expression on the CountAttributedType as an |
| 259 | // approximation for the source range of the attribute. This isn't quite right |
| 260 | // but isn't easy to fix right now. |
| 261 | // |
| 262 | // TODO: Implement logic to find the relevant TypeLoc for the attribute and |
| 263 | // get the SourceRange from that (#113582). |
| 264 | // |
| 265 | // TODO: We should emit a fix-it here. |
| 266 | SourceRange AttrSrcRange = CATy->getCountExpr()->getSourceRange(); |
| 267 | S.Diag(Loc: AttrSrcRange.getBegin(), DiagID: diag::note_counted_by_consider_using_sized_by) |
| 268 | << CATy->isOrNull() << AttrSrcRange; |
| 269 | } |
| 270 | |
| 271 | static std::tuple<const CountAttributedType *, QualType> |
| 272 | GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) { |
| 273 | auto *CATy = Ty->getAs<CountAttributedType>(); |
| 274 | // Incomplete pointee type is only a problem for |
| 275 | // counted_by/counted_by_or_null |
| 276 | if (!CATy || CATy->isCountInBytes()) |
| 277 | return {}; |
| 278 | |
| 279 | auto PointeeTy = CATy->getPointeeType(); |
| 280 | if (PointeeTy.isNull()) { |
| 281 | // Reachable if `CountAttributedType` wraps an IncompleteArrayType |
| 282 | return {}; |
| 283 | } |
| 284 | |
| 285 | if (!PointeeTy->isIncompleteType(Def: ND)) |
| 286 | return {}; |
| 287 | |
| 288 | if (PointeeTy->isVoidType()) |
| 289 | return {}; |
| 290 | |
| 291 | return {CATy, PointeeTy}; |
| 292 | } |
| 293 | |
| 294 | /// Perform Checks for assigning to a `__counted_by` or |
| 295 | /// `__counted_by_or_null` pointer type \param LHSTy where the pointee type |
| 296 | /// is incomplete which is invalid. |
| 297 | /// |
| 298 | /// \param S The Sema instance. |
| 299 | /// \param LHSTy The type being assigned to. Checks will only be performed if |
| 300 | /// the type is a `counted_by` or `counted_by_or_null ` pointer. |
| 301 | /// \param RHSExpr The expression being assigned from. |
| 302 | /// \param Action The type assignment being performed |
| 303 | /// \param Loc The SourceLocation to use for error diagnostics |
| 304 | /// \param Assignee The ValueDecl being assigned. This is used to compute |
| 305 | /// the name of the assignee. If the assignee isn't known this can |
| 306 | /// be set to nullptr. |
| 307 | /// \param ShowFullyQualifiedAssigneeName If set to true when using \p |
| 308 | /// Assignee to compute the name of the assignee use the fully |
| 309 | /// qualified name, otherwise use the unqualified name. |
| 310 | /// |
| 311 | /// \returns True iff no diagnostic where emitted, false otherwise. |
| 312 | static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy( |
| 313 | Sema &S, QualType LHSTy, Expr *RHSExpr, AssignmentAction Action, |
| 314 | SourceLocation Loc, const ValueDecl *Assignee, |
| 315 | bool ShowFullyQualifiedAssigneeName) { |
| 316 | NamedDecl *IncompleteTyDecl = nullptr; |
| 317 | auto [CATy, PointeeTy] = |
| 318 | GetCountedByAttrOnIncompletePointee(Ty: LHSTy, ND: &IncompleteTyDecl); |
| 319 | if (!CATy) |
| 320 | return true; |
| 321 | |
| 322 | std::string AssigneeStr; |
| 323 | if (Assignee) { |
| 324 | if (ShowFullyQualifiedAssigneeName) { |
| 325 | AssigneeStr = Assignee->getQualifiedNameAsString(); |
| 326 | } else { |
| 327 | AssigneeStr = Assignee->getNameAsString(); |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | S.Diag(Loc, DiagID: diag::err_counted_by_on_incomplete_type_on_assign) |
| 332 | << static_cast<int>(Action) << AssigneeStr << (AssigneeStr.size() > 0) |
| 333 | << isa<ImplicitValueInitExpr>(Val: RHSExpr) << LHSTy |
| 334 | << CATy->getAttributeName(/*WithMacroPrefix=*/true) << PointeeTy |
| 335 | << CATy->isOrNull() << RHSExpr->getSourceRange(); |
| 336 | |
| 337 | EmitIncompleteCountedByPointeeNotes(S, CATy, IncompleteTyDecl); |
| 338 | return false; // check failed |
| 339 | } |
| 340 | |
| 341 | bool Sema::BoundsSafetyCheckAssignmentToCountAttrPtr( |
| 342 | QualType LHSTy, Expr *RHSExpr, AssignmentAction Action, SourceLocation Loc, |
| 343 | const ValueDecl *Assignee, bool ShowFullyQualifiedAssigneeName) { |
| 344 | return CheckAssignmentToCountAttrPtrWithIncompletePointeeTy( |
| 345 | S&: *this, LHSTy, RHSExpr, Action, Loc, Assignee, |
| 346 | ShowFullyQualifiedAssigneeName); |
| 347 | } |
| 348 | |
| 349 | bool Sema::BoundsSafetyCheckInitialization(const InitializedEntity &Entity, |
| 350 | const InitializationKind &Kind, |
| 351 | AssignmentAction Action, |
| 352 | QualType LHSType, Expr *RHSExpr) { |
| 353 | auto SL = Kind.getLocation(); |
| 354 | |
| 355 | // Note: We don't call `BoundsSafetyCheckAssignmentToCountAttrPtr` here |
| 356 | // because we need conditionalize what is checked. In downstream |
| 357 | // Clang `counted_by` is supported on variable definitions and in that |
| 358 | // implementation an error diagnostic will be emitted on the variable |
| 359 | // definition if the pointee is an incomplete type. To avoid warning about the |
| 360 | // same problem twice (once when the variable is defined, once when Sema |
| 361 | // checks the initializer) we skip checking the initializer if it's a |
| 362 | // variable. |
| 363 | if (Action == AssignmentAction::Initializing && |
| 364 | Entity.getKind() != InitializedEntity::EK_Variable) { |
| 365 | |
| 366 | if (!CheckAssignmentToCountAttrPtrWithIncompletePointeeTy( |
| 367 | S&: *this, LHSTy: LHSType, RHSExpr, Action, Loc: SL, |
| 368 | Assignee: dyn_cast_or_null<ValueDecl>(Val: Entity.getDecl()), |
| 369 | /*ShowFullQualifiedAssigneeName=*/ShowFullyQualifiedAssigneeName: true)) { |
| 370 | return false; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | return true; |
| 375 | } |
| 376 | |
| 377 | bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(const Expr *E) { |
| 378 | QualType T = E->getType(); |
| 379 | if (!T->isPointerType()) |
| 380 | return true; |
| 381 | |
| 382 | NamedDecl *IncompleteTyDecl = nullptr; |
| 383 | auto [CATy, PointeeTy] = |
| 384 | GetCountedByAttrOnIncompletePointee(Ty: T, ND: &IncompleteTyDecl); |
| 385 | if (!CATy) |
| 386 | return true; |
| 387 | |
| 388 | // Generate a string for the diagnostic that describes the "use". |
| 389 | // The string is specialized for direct calls to produce a better |
| 390 | // diagnostic. |
| 391 | SmallString<64> UseStr; |
| 392 | bool IsDirectCall = false; |
| 393 | if (const auto *CE = dyn_cast<CallExpr>(Val: E->IgnoreParens())) { |
| 394 | if (const auto *FD = CE->getDirectCallee()) { |
| 395 | UseStr = FD->getName(); |
| 396 | IsDirectCall = true; |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | if (!IsDirectCall) { |
| 401 | llvm::raw_svector_ostream SS(UseStr); |
| 402 | E->printPretty(OS&: SS, Helper: nullptr, Policy: getPrintingPolicy()); |
| 403 | } |
| 404 | |
| 405 | Diag(Loc: E->getBeginLoc(), DiagID: diag::err_counted_by_on_incomplete_type_on_use) |
| 406 | << IsDirectCall << UseStr << T << PointeeTy |
| 407 | << CATy->getAttributeName(/*WithMacroPrefix=*/true) << CATy->isOrNull() |
| 408 | << E->getSourceRange(); |
| 409 | |
| 410 | EmitIncompleteCountedByPointeeNotes(S&: *this, CATy, IncompleteTyDecl); |
| 411 | return false; |
| 412 | } |
| 413 | |
| 414 | } // namespace clang |
| 415 | |