xref: /freebsd/contrib/llvm-project/clang/lib/Sema/SemaBoundsSafety.cpp (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
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/Sema/Sema.h"
15 
16 namespace clang {
17 
18 static CountAttributedType::DynamicCountPointerKind
19 getCountAttrKind(bool CountInBytes, bool OrNull) {
20   if (CountInBytes)
21     return OrNull ? CountAttributedType::SizedByOrNull
22                   : CountAttributedType::SizedBy;
23   return OrNull ? CountAttributedType::CountedByOrNull
24                 : CountAttributedType::CountedBy;
25 }
26 
27 static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
28   const auto *RD = FD->getParent();
29   // An unnamed struct is anonymous struct only if it's not instantiated.
30   // However, the struct may not be fully processed yet to determine
31   // whether it's anonymous or not. In that case, this function treats it as
32   // an anonymous struct and tries to find a named parent.
33   while (RD && (RD->isAnonymousStructOrUnion() ||
34                 (!RD->isCompleteDefinition() && RD->getName().empty()))) {
35     const auto *Parent = dyn_cast<RecordDecl>(RD->getParent());
36     if (!Parent)
37       break;
38     RD = Parent;
39   }
40   return RD;
41 }
42 
43 enum class CountedByInvalidPointeeTypeKind {
44   INCOMPLETE,
45   SIZELESS,
46   FUNCTION,
47   FLEXIBLE_ARRAY_MEMBER,
48   VALID,
49 };
50 
51 bool Sema::CheckCountedByAttrOnField(
52     FieldDecl *FD, Expr *E,
53     llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls, bool CountInBytes,
54     bool OrNull) {
55   // Check the context the attribute is used in
56 
57   unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
58 
59   if (FD->getParent()->isUnion()) {
60     Diag(FD->getBeginLoc(), diag::err_count_attr_in_union)
61         << Kind << FD->getSourceRange();
62     return true;
63   }
64 
65   const auto FieldTy = FD->getType();
66   if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
67     Diag(FD->getBeginLoc(),
68          diag::err_count_attr_not_on_ptr_or_flexible_array_member)
69         << Kind << FD->getLocation() << /* suggest counted_by */ 1;
70     return true;
71   }
72   if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
73     Diag(FD->getBeginLoc(),
74          diag::err_count_attr_not_on_ptr_or_flexible_array_member)
75         << Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
76     return true;
77   }
78 
79   LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
80       LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
81   if (FieldTy->isArrayType() &&
82       !Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy,
83                                        StrictFlexArraysLevel, true)) {
84     Diag(FD->getBeginLoc(),
85          diag::err_counted_by_attr_on_array_not_flexible_array_member)
86         << Kind << FD->getLocation();
87     return true;
88   }
89 
90   CountedByInvalidPointeeTypeKind InvalidTypeKind =
91       CountedByInvalidPointeeTypeKind::VALID;
92   QualType PointeeTy;
93   int SelectPtrOrArr = 0;
94   if (FieldTy->isPointerType()) {
95     PointeeTy = FieldTy->getPointeeType();
96     SelectPtrOrArr = 0;
97   } else {
98     assert(FieldTy->isArrayType());
99     const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
100     PointeeTy = AT->getElementType();
101     SelectPtrOrArr = 1;
102   }
103   // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
104   // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
105   // when `FieldTy->isArrayType()`.
106   bool ShouldWarn = false;
107   if (PointeeTy->isIncompleteType() && !CountInBytes) {
108     InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
109   } else if (PointeeTy->isSizelessType()) {
110     InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
111   } else if (PointeeTy->isFunctionType()) {
112     InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
113   } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
114     if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) {
115       // This is a workaround for the Linux kernel that has already adopted
116       // `counted_by` on a FAM where the pointee is a struct with a FAM. This
117       // should be an error because computing the bounds of the array cannot be
118       // done correctly without manually traversing every struct object in the
119       // array at runtime. To allow the code to be built this error is
120       // downgraded to a warning.
121       ShouldWarn = true;
122     }
123     InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
124   }
125 
126   if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
127     unsigned DiagID = ShouldWarn
128                           ? diag::warn_counted_by_attr_elt_type_unknown_size
129                           : diag::err_counted_by_attr_pointee_unknown_size;
130     Diag(FD->getBeginLoc(), DiagID)
131         << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
132         << (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange();
133     return true;
134   }
135 
136   // Check the expression
137 
138   if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
139     Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
140         << Kind << E->getSourceRange();
141     return true;
142   }
143 
144   auto *DRE = dyn_cast<DeclRefExpr>(E);
145   if (!DRE) {
146     Diag(E->getBeginLoc(),
147          diag::err_count_attr_only_support_simple_decl_reference)
148         << Kind << E->getSourceRange();
149     return true;
150   }
151 
152   auto *CountDecl = DRE->getDecl();
153   FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
154   if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
155     CountFD = IFD->getAnonField();
156   }
157   if (!CountFD) {
158     Diag(E->getBeginLoc(), diag::err_count_attr_must_be_in_structure)
159         << CountDecl << Kind << E->getSourceRange();
160 
161     Diag(CountDecl->getBeginLoc(),
162          diag::note_flexible_array_counted_by_attr_field)
163         << CountDecl << CountDecl->getSourceRange();
164     return true;
165   }
166 
167   if (FD->getParent() != CountFD->getParent()) {
168     if (CountFD->getParent()->isUnion()) {
169       Diag(CountFD->getBeginLoc(), diag::err_count_attr_refer_to_union)
170           << Kind << CountFD->getSourceRange();
171       return true;
172     }
173     // Whether CountRD is an anonymous struct is not determined at this
174     // point. Thus, an additional diagnostic in case it's not anonymous struct
175     // is done later in `Parser::ParseStructDeclaration`.
176     auto *RD = GetEnclosingNamedOrTopAnonRecord(FD);
177     auto *CountRD = GetEnclosingNamedOrTopAnonRecord(CountFD);
178 
179     if (RD != CountRD) {
180       Diag(E->getBeginLoc(), diag::err_count_attr_param_not_in_same_struct)
181           << CountFD << Kind << FieldTy->isArrayType() << E->getSourceRange();
182       Diag(CountFD->getBeginLoc(),
183            diag::note_flexible_array_counted_by_attr_field)
184           << CountFD << CountFD->getSourceRange();
185       return true;
186     }
187   }
188 
189   Decls.push_back(TypeCoupledDeclRefInfo(CountFD, /*IsDref*/ false));
190   return false;
191 }
192 
193 } // namespace clang
194