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
21using namespace clang;
22using namespace ento;
23
24namespace {
25
26class RawPtrRefMemberChecker
27 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
28private:
29 BugType Bug;
30 mutable BugReporter *BR;
31 mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore;
32
33protected:
34 mutable std::optional<RetainTypeChecker> RTC;
35
36public:
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
322class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
323public:
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
339class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
340public:
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
357class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
358public:
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
387void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
388 Mgr.registerChecker<NoUncountedMemberChecker>();
389}
390
391bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) {
392 return true;
393}
394
395void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) {
396 Mgr.registerChecker<NoUncheckedPtrMemberChecker>();
397}
398
399bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
400 const CheckerManager &Mgr) {
401 return true;
402}
403
404void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
405 Mgr.registerChecker<NoUnretainedMemberChecker>();
406}
407
408bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
409 return true;
410}
411