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 if (auto *ObjCType = dyn_cast<ObjCInterfaceType>(Val: Desugared))
123 return ObjCType->getDecl();
124 if (auto *ObjCType = dyn_cast<ObjCObjectType>(Val: Desugared))
125 return ObjCType->getInterface();
126 return nullptr;
127 }
128
129 void visitObjCDecl(const ObjCContainerDecl *CD) const {
130 if (BR->getSourceManager().isInSystemHeader(Loc: CD->getLocation()))
131 return;
132
133 if (auto *ID = dyn_cast<ObjCImplementationDecl>(Val: CD)) {
134 ObjCContainerDecl::PropertyMap map;
135 CD->collectPropertiesToImplement(PM&: map);
136 for (auto it : map)
137 visitObjCPropertyDecl(CD, PD: it.second);
138
139 if (auto *Interface = ID->getClassInterface()) {
140 for (auto *Ivar : Interface->ivars())
141 visitIvarDecl(CD, Ivar);
142 }
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 bool IsWeak =
235 PD->getPropertyAttributes() & ObjCPropertyAttribute::kind_weak;
236 bool HasSafeAttr = PD->isRetaining() || IsWeak;
237 auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC);
238 return {IsUnsafePtr && *IsUnsafePtr && !HasSafeAttr, PropType};
239 }
240
241 bool shouldSkipDecl(const RecordDecl *RD) const {
242 if (!RD->isThisDeclarationADefinition())
243 return true;
244
245 if (RD->isImplicit())
246 return true;
247
248 if (RD->isLambda())
249 return true;
250
251 // If the construct doesn't have a source file, then it's not something
252 // we want to diagnose.
253 const auto RDLocation = RD->getLocation();
254 if (!RDLocation.isValid())
255 return true;
256
257 const auto Kind = RD->getTagKind();
258 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class &&
259 Kind != TagTypeKind::Union)
260 return true;
261
262 // Ignore CXXRecords that come from system headers.
263 if (BR->getSourceManager().isInSystemHeader(Loc: RDLocation))
264 return true;
265
266 // Ref-counted smartpointers actually have raw-pointer to uncounted type as
267 // a member but we trust them to handle it correctly.
268 auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(Val: RD);
269 if (CXXRD && isSmartPtr(Class: CXXRD))
270 return true;
271
272 return false;
273 }
274
275 template <typename DeclType, typename PointeeType, typename ParentDeclType>
276 void reportBug(const DeclType *Member, const Type *MemberType,
277 const PointeeType *Pointee,
278 const ParentDeclType *ClassCXXRD) const {
279 assert(Member);
280 assert(MemberType);
281 assert(Pointee);
282
283 SmallString<100> Buf;
284 llvm::raw_svector_ostream Os(Buf);
285
286 if (isa<ObjCContainerDecl>(ClassCXXRD)) {
287 if (isa<ObjCPropertyDecl>(Member))
288 Os << "Property ";
289 else
290 Os << "Instance variable ";
291 } else
292 Os << "Member variable ";
293 printQuotedName(Os, Member);
294 Os << " in ";
295 printQuotedQualifiedName(Os, ClassCXXRD);
296 if (Member->getType().getTypePtrOrNull() == MemberType)
297 Os << " is a ";
298 else
299 Os << " contains a ";
300 if (printPointer(Os, T: MemberType) == PrintDeclKind::Pointer) {
301 auto Typedef = MemberType->getAs<TypedefType>();
302 assert(Typedef);
303 printQuotedQualifiedName(Os, D: Typedef->getDecl());
304 } else
305 printQuotedQualifiedName(Os, Pointee);
306 Os << "; " << invariant() << ".";
307
308 PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),
309 BR->getSourceManager());
310 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
311 Report->addRange(R: Member->getSourceRange());
312 BR->emitReport(R: std::move(Report));
313 }
314
315 enum class PrintDeclKind { Pointee, Pointer };
316 virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
317 const Type *T) const {
318 T = T->getUnqualifiedDesugaredType();
319 bool IsPtr = isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T);
320 Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " ";
321 return PrintDeclKind::Pointee;
322 }
323};
324
325class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
326public:
327 NoUncountedMemberChecker()
328 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
329 "reference-countable type") {}
330
331 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
332 return isUncountedPtr(T: QT.getCanonicalType());
333 }
334
335 const char *typeName() const final { return "ref-countable type"; }
336
337 const char *invariant() const final {
338 return "member variables must be Ref, RefPtr, WeakRef, or WeakPtr";
339 }
340};
341
342class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
343public:
344 NoUncheckedPtrMemberChecker()
345 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
346 "checked-pointer capable type") {}
347
348 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
349 return isUncheckedPtr(T: QT.getCanonicalType());
350 }
351
352 const char *typeName() const final { return "CheckedPtr capable type"; }
353
354 const char *invariant() const final {
355 return "member variables must be a CheckedPtr, CheckedRef, WeakRef, or "
356 "WeakPtr";
357 }
358};
359
360class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
361public:
362 NoUnretainedMemberChecker()
363 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
364 "retainable type") {
365 RTC = RetainTypeChecker();
366 }
367
368 std::optional<bool> isUnsafePtr(QualType QT, bool ignoreARC) const final {
369 if (QT.hasStrongOrWeakObjCLifetime())
370 return false;
371 return RTC->isUnretained(QT, ignoreARC);
372 }
373
374 const char *typeName() const final { return "retainable type"; }
375
376 const char *invariant() const final {
377 return "member variables must be a RetainPtr or OSObjectPtr";
378 }
379
380 PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
381 const Type *T) const final {
382 if (!isa<ObjCObjectPointerType>(Val: T) && T->getAs<TypedefType>()) {
383 Os << typeName() << " ";
384 return PrintDeclKind::Pointer;
385 }
386 return RawPtrRefMemberChecker::printPointer(Os, T);
387 }
388};
389
390} // namespace
391
392void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
393 Mgr.registerChecker<NoUncountedMemberChecker>();
394}
395
396bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) {
397 return true;
398}
399
400void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) {
401 Mgr.registerChecker<NoUncheckedPtrMemberChecker>();
402}
403
404bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
405 const CheckerManager &Mgr) {
406 return true;
407}
408
409void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
410 Mgr.registerChecker<NoUnretainedMemberChecker>();
411}
412
413bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
414 return true;
415}
416