1 | //=======- RawPtrRefMemberChecker.cpp ----------------------------*- 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 | |
9 | #include "DiagOutputUtils.h" |
10 | #include "PtrTypesSemantics.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "clang/AST/DeclCXX.h" |
13 | #include "clang/AST/DynamicRecursiveASTVisitor.h" |
14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
15 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
16 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
17 | #include "clang/StaticAnalyzer/Core/Checker.h" |
18 | #include "llvm/Support/Casting.h" |
19 | #include <optional> |
20 | |
21 | using namespace clang; |
22 | using namespace ento; |
23 | |
24 | namespace { |
25 | |
26 | class RawPtrRefMemberChecker |
27 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
28 | private: |
29 | BugType Bug; |
30 | mutable BugReporter *BR; |
31 | mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore; |
32 | |
33 | protected: |
34 | mutable std::optional<RetainTypeChecker> RTC; |
35 | |
36 | public: |
37 | RawPtrRefMemberChecker(const char *description) |
38 | : Bug(this, description, "WebKit coding guidelines" ) {} |
39 | |
40 | virtual std::optional<bool> isUnsafePtr(QualType, |
41 | bool ignoreARC = false) const = 0; |
42 | virtual const char *typeName() const = 0; |
43 | virtual const char *invariant() const = 0; |
44 | |
45 | void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
46 | BugReporter &BRArg) const { |
47 | BR = &BRArg; |
48 | |
49 | // The calls to checkAST* from AnalysisConsumer don't |
50 | // visit template instantiations or lambda classes. We |
51 | // want to visit those, so we make our own RecursiveASTVisitor. |
52 | struct LocalVisitor : ConstDynamicRecursiveASTVisitor { |
53 | const RawPtrRefMemberChecker *Checker; |
54 | explicit LocalVisitor(const RawPtrRefMemberChecker *Checker) |
55 | : Checker(Checker) { |
56 | assert(Checker); |
57 | ShouldVisitTemplateInstantiations = true; |
58 | ShouldVisitImplicitCode = false; |
59 | } |
60 | |
61 | bool VisitTypedefDecl(const TypedefDecl *TD) override { |
62 | if (Checker->RTC) |
63 | Checker->RTC->visitTypedef(TD); |
64 | return true; |
65 | } |
66 | |
67 | bool VisitRecordDecl(const RecordDecl *RD) override { |
68 | Checker->visitRecordDecl(RD); |
69 | return true; |
70 | } |
71 | |
72 | bool VisitObjCContainerDecl(const ObjCContainerDecl *CD) override { |
73 | Checker->visitObjCDecl(CD); |
74 | return true; |
75 | } |
76 | }; |
77 | |
78 | LocalVisitor visitor(this); |
79 | if (RTC) |
80 | RTC->visitTranslationUnitDecl(TUD); |
81 | visitor.TraverseDecl(D: TUD); |
82 | } |
83 | |
84 | void visitRecordDecl(const RecordDecl *RD) const { |
85 | if (shouldSkipDecl(RD)) |
86 | return; |
87 | |
88 | for (auto *Member : RD->fields()) |
89 | visitMember(Member, RD); |
90 | } |
91 | |
92 | void visitMember(const FieldDecl *Member, const RecordDecl *RD) const { |
93 | auto QT = Member->getType(); |
94 | const Type *MemberType = QT.getTypePtrOrNull(); |
95 | |
96 | while (MemberType) { |
97 | auto IsUnsafePtr = isUnsafePtr(QT); |
98 | if (IsUnsafePtr && *IsUnsafePtr) |
99 | break; |
100 | if (!MemberType->isPointerType()) |
101 | return; |
102 | QT = MemberType->getPointeeType(); |
103 | MemberType = QT.getTypePtrOrNull(); |
104 | } |
105 | |
106 | if (!MemberType) |
107 | return; |
108 | |
109 | if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) |
110 | reportBug(Member, MemberType, Pointee: MemberCXXRD, ClassCXXRD: RD); |
111 | else if (auto *ObjCDecl = getObjCDecl(TypePtr: MemberType)) |
112 | reportBug(Member, MemberType, Pointee: ObjCDecl, ClassCXXRD: RD); |
113 | } |
114 | |
115 | ObjCInterfaceDecl *getObjCDecl(const Type *TypePtr) const { |
116 | auto *PointeeType = TypePtr->getPointeeType().getTypePtrOrNull(); |
117 | if (!PointeeType) |
118 | return nullptr; |
119 | auto *Desugared = PointeeType->getUnqualifiedDesugaredType(); |
120 | if (!Desugared) |
121 | return nullptr; |
122 | auto *ObjCType = dyn_cast<ObjCInterfaceType>(Val: Desugared); |
123 | if (!ObjCType) |
124 | return nullptr; |
125 | return ObjCType->getDecl(); |
126 | } |
127 | |
128 | void visitObjCDecl(const ObjCContainerDecl *CD) const { |
129 | if (BR->getSourceManager().isInSystemHeader(Loc: CD->getLocation())) |
130 | return; |
131 | |
132 | ObjCContainerDecl::PropertyMap map; |
133 | CD->collectPropertiesToImplement(PM&: map); |
134 | for (auto it : map) |
135 | visitObjCPropertyDecl(CD, PD: it.second); |
136 | |
137 | if (auto *ID = dyn_cast<ObjCInterfaceDecl>(Val: CD)) { |
138 | for (auto *Ivar : ID->ivars()) |
139 | visitIvarDecl(CD, Ivar); |
140 | return; |
141 | } |
142 | if (auto *ID = dyn_cast<ObjCImplementationDecl>(Val: CD)) { |
143 | for (auto *PropImpl : ID->property_impls()) |
144 | visitPropImpl(CD, PID: PropImpl); |
145 | for (auto *Ivar : ID->ivars()) |
146 | visitIvarDecl(CD, Ivar); |
147 | return; |
148 | } |
149 | } |
150 | |
151 | void visitIvarDecl(const ObjCContainerDecl *CD, |
152 | const ObjCIvarDecl *Ivar) const { |
153 | if (BR->getSourceManager().isInSystemHeader(Loc: Ivar->getLocation())) |
154 | return; |
155 | |
156 | if (IvarDeclsToIgnore.contains(V: Ivar)) |
157 | return; |
158 | |
159 | auto QT = Ivar->getType(); |
160 | const Type *IvarType = QT.getTypePtrOrNull(); |
161 | if (!IvarType) |
162 | return; |
163 | |
164 | auto IsUnsafePtr = isUnsafePtr(QT); |
165 | if (!IsUnsafePtr || !*IsUnsafePtr) |
166 | return; |
167 | |
168 | IvarDeclsToIgnore.insert(V: Ivar); |
169 | |
170 | if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl()) |
171 | reportBug(Member: Ivar, MemberType: IvarType, Pointee: MemberCXXRD, ClassCXXRD: CD); |
172 | else if (auto *ObjCDecl = getObjCDecl(TypePtr: IvarType)) |
173 | reportBug(Member: Ivar, MemberType: IvarType, Pointee: ObjCDecl, ClassCXXRD: CD); |
174 | } |
175 | |
176 | void visitObjCPropertyDecl(const ObjCContainerDecl *CD, |
177 | const ObjCPropertyDecl *PD) const { |
178 | if (BR->getSourceManager().isInSystemHeader(Loc: PD->getLocation())) |
179 | return; |
180 | |
181 | if (const ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(Val: CD)) { |
182 | if (!RTC || !RTC->defaultSynthProperties() || |
183 | ID->isObjCRequiresPropertyDefs()) |
184 | return; |
185 | } |
186 | |
187 | auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD); |
188 | if (!IsUnsafe) |
189 | return; |
190 | |
191 | if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl()) |
192 | reportBug(Member: PD, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD); |
193 | else if (auto *ObjCDecl = getObjCDecl(TypePtr: PropType)) |
194 | reportBug(Member: PD, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD); |
195 | } |
196 | |
197 | void visitPropImpl(const ObjCContainerDecl *CD, |
198 | const ObjCPropertyImplDecl *PID) const { |
199 | if (BR->getSourceManager().isInSystemHeader(Loc: PID->getLocation())) |
200 | return; |
201 | |
202 | if (PID->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) |
203 | return; |
204 | |
205 | auto *PropDecl = PID->getPropertyDecl(); |
206 | if (auto *IvarDecl = PID->getPropertyIvarDecl()) { |
207 | if (IvarDeclsToIgnore.contains(V: IvarDecl)) |
208 | return; |
209 | IvarDeclsToIgnore.insert(V: IvarDecl); |
210 | } |
211 | auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD: PropDecl); |
212 | if (!IsUnsafe) |
213 | return; |
214 | |
215 | if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl()) |
216 | reportBug(Member: PropDecl, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD); |
217 | else if (auto *ObjCDecl = getObjCDecl(TypePtr: PropType)) |
218 | reportBug(Member: PropDecl, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD); |
219 | } |
220 | |
221 | std::pair<bool, const Type *> |
222 | isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const { |
223 | if (!PD) |
224 | return {false, nullptr}; |
225 | |
226 | auto QT = PD->getType(); |
227 | const Type *PropType = QT.getTypePtrOrNull(); |
228 | if (!PropType) |
229 | return {false, nullptr}; |
230 | |
231 | // "assign" property doesn't retain even under ARC so treat it as unsafe. |
232 | bool ignoreARC = |
233 | !PD->isReadOnly() && PD->getSetterKind() == ObjCPropertyDecl::Assign; |
234 | auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC); |
235 | return {IsUnsafePtr && *IsUnsafePtr, PropType}; |
236 | } |
237 | |
238 | bool shouldSkipDecl(const RecordDecl *RD) const { |
239 | if (!RD->isThisDeclarationADefinition()) |
240 | return true; |
241 | |
242 | if (RD->isImplicit()) |
243 | return true; |
244 | |
245 | if (RD->isLambda()) |
246 | return true; |
247 | |
248 | // If the construct doesn't have a source file, then it's not something |
249 | // we want to diagnose. |
250 | const auto RDLocation = RD->getLocation(); |
251 | if (!RDLocation.isValid()) |
252 | return true; |
253 | |
254 | const auto Kind = RD->getTagKind(); |
255 | if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class && |
256 | Kind != TagTypeKind::Union) |
257 | return true; |
258 | |
259 | // Ignore CXXRecords that come from system headers. |
260 | if (BR->getSourceManager().isInSystemHeader(Loc: RDLocation)) |
261 | return true; |
262 | |
263 | // Ref-counted smartpointers actually have raw-pointer to uncounted type as |
264 | // a member but we trust them to handle it correctly. |
265 | auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(Val: RD); |
266 | if (CXXRD && isSmartPtr(Class: CXXRD)) |
267 | return true; |
268 | |
269 | return false; |
270 | } |
271 | |
272 | template <typename DeclType, typename PointeeType, typename ParentDeclType> |
273 | void reportBug(const DeclType *Member, const Type *MemberType, |
274 | const PointeeType *Pointee, |
275 | const ParentDeclType *ClassCXXRD) const { |
276 | assert(Member); |
277 | assert(MemberType); |
278 | assert(Pointee); |
279 | |
280 | SmallString<100> Buf; |
281 | llvm::raw_svector_ostream Os(Buf); |
282 | |
283 | if (isa<ObjCContainerDecl>(ClassCXXRD)) { |
284 | if (isa<ObjCPropertyDecl>(Member)) |
285 | Os << "Property " ; |
286 | else |
287 | Os << "Instance variable " ; |
288 | } else |
289 | Os << "Member variable " ; |
290 | printQuotedName(Os, Member); |
291 | Os << " in " ; |
292 | printQuotedQualifiedName(Os, ClassCXXRD); |
293 | if (Member->getType().getTypePtrOrNull() == MemberType) |
294 | Os << " is a " ; |
295 | else |
296 | Os << " contains a " ; |
297 | if (printPointer(Os, T: MemberType) == PrintDeclKind::Pointer) { |
298 | auto Typedef = MemberType->getAs<TypedefType>(); |
299 | assert(Typedef); |
300 | printQuotedQualifiedName(Os, D: Typedef->getDecl()); |
301 | } else |
302 | printQuotedQualifiedName(Os, Pointee); |
303 | Os << "; " << invariant() << "." ; |
304 | |
305 | PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(), |
306 | BR->getSourceManager()); |
307 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
308 | Report->addRange(R: Member->getSourceRange()); |
309 | BR->emitReport(R: std::move(Report)); |
310 | } |
311 | |
312 | enum class PrintDeclKind { Pointee, Pointer }; |
313 | virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, |
314 | const Type *T) const { |
315 | T = T->getUnqualifiedDesugaredType(); |
316 | bool IsPtr = isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T); |
317 | Os << (IsPtr ? "raw pointer" : "reference" ) << " to " << typeName() << " " ; |
318 | return PrintDeclKind::Pointee; |
319 | } |
320 | }; |
321 | |
322 | class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { |
323 | public: |
324 | NoUncountedMemberChecker() |
325 | : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to " |
326 | "reference-countable type" ) {} |
327 | |
328 | std::optional<bool> isUnsafePtr(QualType QT, bool) const final { |
329 | return isUncountedPtr(T: QT.getCanonicalType()); |
330 | } |
331 | |
332 | const char *typeName() const final { return "ref-countable type" ; } |
333 | |
334 | const char *invariant() const final { |
335 | return "member variables must be Ref, RefPtr, WeakRef, or WeakPtr" ; |
336 | } |
337 | }; |
338 | |
339 | class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { |
340 | public: |
341 | NoUncheckedPtrMemberChecker() |
342 | : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to " |
343 | "checked-pointer capable type" ) {} |
344 | |
345 | std::optional<bool> isUnsafePtr(QualType QT, bool) const final { |
346 | return isUncheckedPtr(T: QT.getCanonicalType()); |
347 | } |
348 | |
349 | const char *typeName() const final { return "CheckedPtr capable type" ; } |
350 | |
351 | const char *invariant() const final { |
352 | return "member variables must be a CheckedPtr, CheckedRef, WeakRef, or " |
353 | "WeakPtr" ; |
354 | } |
355 | }; |
356 | |
357 | class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { |
358 | public: |
359 | NoUnretainedMemberChecker() |
360 | : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to " |
361 | "retainable type" ) { |
362 | RTC = RetainTypeChecker(); |
363 | } |
364 | |
365 | std::optional<bool> isUnsafePtr(QualType QT, bool ignoreARC) const final { |
366 | return RTC->isUnretained(QT, ignoreARC); |
367 | } |
368 | |
369 | const char *typeName() const final { return "retainable type" ; } |
370 | |
371 | const char *invariant() const final { |
372 | return "member variables must be a RetainPtr" ; |
373 | } |
374 | |
375 | PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, |
376 | const Type *T) const final { |
377 | if (!isa<ObjCObjectPointerType>(Val: T) && T->getAs<TypedefType>()) { |
378 | Os << typeName() << " " ; |
379 | return PrintDeclKind::Pointer; |
380 | } |
381 | return RawPtrRefMemberChecker::printPointer(Os, T); |
382 | } |
383 | }; |
384 | |
385 | } // namespace |
386 | |
387 | void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) { |
388 | Mgr.registerChecker<NoUncountedMemberChecker>(); |
389 | } |
390 | |
391 | bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) { |
392 | return true; |
393 | } |
394 | |
395 | void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) { |
396 | Mgr.registerChecker<NoUncheckedPtrMemberChecker>(); |
397 | } |
398 | |
399 | bool ento::shouldRegisterNoUncheckedPtrMemberChecker( |
400 | const CheckerManager &Mgr) { |
401 | return true; |
402 | } |
403 | |
404 | void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) { |
405 | Mgr.registerChecker<NoUnretainedMemberChecker>(); |
406 | } |
407 | |
408 | bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) { |
409 | return true; |
410 | } |
411 | |