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 "ASTUtils.h"
10#include "DiagOutputUtils.h"
11#include "PtrTypesSemantics.h"
12#include "clang/AST/Decl.h"
13#include "clang/AST/DeclCXX.h"
14#include "clang/AST/DynamicRecursiveASTVisitor.h"
15#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
18#include "clang/StaticAnalyzer/Core/Checker.h"
19#include "llvm/Support/Casting.h"
20#include <optional>
21
22using namespace clang;
23using namespace ento;
24
25namespace {
26
27class RawPtrRefMemberChecker
28 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
29private:
30 BugType Bug;
31 mutable BugReporter *BR;
32 mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore;
33
34protected:
35 mutable std::optional<RetainTypeChecker> RTC;
36
37public:
38 RawPtrRefMemberChecker(const char *description)
39 : Bug(this, description, "WebKit coding guidelines") {}
40
41 virtual std::optional<bool> isUnsafePtr(QualType,
42 bool ignoreARC = false) const = 0;
43 virtual const char *typeName() 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 = getObjCDeclFromObjCPtr(TypePtr: MemberType))
112 reportBug(Member, MemberType, Pointee: ObjCDecl, ClassCXXRD: RD);
113 }
114
115 void visitObjCDecl(const ObjCContainerDecl *CD) const {
116 if (BR->getSourceManager().isInSystemHeader(Loc: CD->getLocation()))
117 return;
118
119 if (auto *ID = dyn_cast<ObjCImplementationDecl>(Val: CD)) {
120 ObjCContainerDecl::PropertyMap map;
121 CD->collectPropertiesToImplement(PM&: map);
122 for (auto it : map)
123 visitObjCPropertyDecl(CD, PD: it.second);
124
125 if (auto *Interface = ID->getClassInterface()) {
126 for (auto *Ivar : Interface->ivars())
127 visitIvarDecl(CD, Ivar);
128 }
129 for (auto *PropImpl : ID->property_impls())
130 visitPropImpl(CD, PID: PropImpl);
131 for (auto *Ivar : ID->ivars())
132 visitIvarDecl(CD, Ivar);
133 return;
134 }
135 }
136
137 void visitIvarDecl(const ObjCContainerDecl *CD,
138 const ObjCIvarDecl *Ivar) const {
139 if (BR->getSourceManager().isInSystemHeader(Loc: Ivar->getLocation()))
140 return;
141
142 if (IvarDeclsToIgnore.contains(V: Ivar))
143 return;
144
145 auto QT = Ivar->getType();
146 const Type *IvarType = QT.getTypePtrOrNull();
147 if (!IvarType)
148 return;
149
150 auto IsUnsafePtr = isUnsafePtr(QT);
151 if (!IsUnsafePtr || !*IsUnsafePtr)
152 return;
153
154 IvarDeclsToIgnore.insert(V: Ivar);
155
156 if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl())
157 reportBug(Member: Ivar, MemberType: IvarType, Pointee: MemberCXXRD, ClassCXXRD: CD);
158 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: IvarType))
159 reportBug(Member: Ivar, MemberType: IvarType, Pointee: ObjCDecl, ClassCXXRD: CD);
160 }
161
162 void visitObjCPropertyDecl(const ObjCContainerDecl *CD,
163 const ObjCPropertyDecl *PD) const {
164 if (BR->getSourceManager().isInSystemHeader(Loc: PD->getLocation()))
165 return;
166
167 if (const ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(Val: CD)) {
168 if (!RTC || !RTC->defaultSynthProperties() ||
169 ID->isObjCRequiresPropertyDefs())
170 return;
171 }
172
173 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD);
174 if (!IsUnsafe)
175 return;
176
177 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
178 reportBug(Member: PD, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD);
179 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: PropType))
180 reportBug(Member: PD, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD);
181 }
182
183 void visitPropImpl(const ObjCContainerDecl *CD,
184 const ObjCPropertyImplDecl *PID) const {
185 if (BR->getSourceManager().isInSystemHeader(Loc: PID->getLocation()))
186 return;
187
188 if (PID->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
189 return;
190
191 auto *PropDecl = PID->getPropertyDecl();
192 if (auto *IvarDecl = PID->getPropertyIvarDecl()) {
193 if (IvarDeclsToIgnore.contains(V: IvarDecl))
194 return;
195 IvarDeclsToIgnore.insert(V: IvarDecl);
196 }
197 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD: PropDecl);
198 if (!IsUnsafe)
199 return;
200
201 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
202 reportBug(Member: PropDecl, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD);
203 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: PropType))
204 reportBug(Member: PropDecl, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD);
205 }
206
207 std::pair<bool, const Type *>
208 isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const {
209 if (!PD)
210 return {false, nullptr};
211
212 auto QT = PD->getType();
213 const Type *PropType = QT.getTypePtrOrNull();
214 if (!PropType)
215 return {false, nullptr};
216
217 // "assign" property doesn't retain even under ARC so treat it as unsafe.
218 bool ignoreARC =
219 !PD->isReadOnly() && PD->getSetterKind() == ObjCPropertyDecl::Assign;
220 bool IsWeak =
221 PD->getPropertyAttributes() & ObjCPropertyAttribute::kind_weak;
222 bool HasSafeAttr = PD->isRetaining() || IsWeak;
223 auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC);
224 return {IsUnsafePtr && *IsUnsafePtr && !HasSafeAttr, PropType};
225 }
226
227 bool shouldSkipDecl(const RecordDecl *RD) const {
228 if (!RD->isThisDeclarationADefinition())
229 return true;
230
231 if (RD->isImplicit())
232 return true;
233
234 if (RD->isLambda())
235 return true;
236
237 // If the construct doesn't have a source file, then it's not something
238 // we want to diagnose.
239 const auto RDLocation = RD->getLocation();
240 if (!RDLocation.isValid())
241 return true;
242
243 const auto Kind = RD->getTagKind();
244 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class &&
245 Kind != TagTypeKind::Union)
246 return true;
247
248 // Ignore CXXRecords that come from system headers.
249 if (BR->getSourceManager().isInSystemHeader(Loc: RDLocation))
250 return true;
251
252 // Ref-counted smartpointers actually have raw-pointer to uncounted type as
253 // a member but we trust them to handle it correctly.
254 auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(Val: RD);
255 if (CXXRD && isSmartPtr(Class: CXXRD))
256 return true;
257
258 return false;
259 }
260
261 template <typename DeclType, typename PointeeType, typename ParentDeclType>
262 void reportBug(const DeclType *Member, const Type *MemberType,
263 const PointeeType *Pointee,
264 const ParentDeclType *ClassCXXRD) const {
265 assert(Member);
266 assert(MemberType);
267 assert(Pointee);
268
269 SmallString<100> Buf;
270 llvm::raw_svector_ostream Os(Buf);
271
272 if (isa<ObjCContainerDecl>(ClassCXXRD)) {
273 if (isa<ObjCPropertyDecl>(Member))
274 Os << "Property ";
275 else
276 Os << "Instance variable ";
277 } else
278 Os << "Member variable ";
279 printQuotedName(Os, Member);
280 Os << " (of ";
281 printQuotedQualifiedName(Os, ClassCXXRD);
282 Os << ")";
283 if (Member->getType().getTypePtrOrNull() == MemberType)
284 Os << " is a ";
285 else
286 Os << " contains a ";
287 if (printPointer(Os, T: MemberType) == PrintDeclKind::Pointer) {
288 auto Typedef = MemberType->getAs<TypedefType>();
289 assert(Typedef);
290 printQuotedQualifiedName(Os, D: Typedef->getDecl());
291 } else
292 printQuotedQualifiedName(Os, Pointee);
293
294 PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),
295 BR->getSourceManager());
296 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
297 Report->addRange(R: Member->getSourceRange());
298 BR->emitReport(R: std::move(Report));
299 }
300
301 enum class PrintDeclKind { Pointee, Pointer };
302 virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
303 const Type *T) const {
304 T = T->getUnqualifiedDesugaredType();
305 bool IsPtr = isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T);
306 Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " ";
307 return PrintDeclKind::Pointee;
308 }
309};
310
311class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
312public:
313 NoUncountedMemberChecker()
314 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
315 "reference-countable type") {}
316
317 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
318 return isUncountedPtr(T: QT.getCanonicalType());
319 }
320
321 const char *typeName() const final { return "RefPtr-capable type"; }
322};
323
324class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
325public:
326 NoUncheckedPtrMemberChecker()
327 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
328 "checked-pointer capable type") {}
329
330 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
331 return isUncheckedPtr(T: QT.getCanonicalType());
332 }
333
334 const char *typeName() const final { return "CheckedPtr-capable type"; }
335};
336
337class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
338public:
339 NoUnretainedMemberChecker()
340 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
341 "retainable type") {
342 RTC = RetainTypeChecker();
343 }
344
345 std::optional<bool> isUnsafePtr(QualType QT, bool ignoreARC) const final {
346 if (QT.hasStrongOrWeakObjCLifetime())
347 return false;
348 return RTC->isUnretained(QT, ignoreARC);
349 }
350
351 const char *typeName() const final { return "RetainPtr-capable type"; }
352
353 PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
354 const Type *T) const final {
355 if (!isa<ObjCObjectPointerType>(Val: T) && T->getAs<TypedefType>()) {
356 Os << typeName() << " ";
357 return PrintDeclKind::Pointer;
358 }
359 return RawPtrRefMemberChecker::printPointer(Os, T);
360 }
361};
362
363} // namespace
364
365void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
366 Mgr.registerChecker<NoUncountedMemberChecker>();
367}
368
369bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) {
370 return true;
371}
372
373void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) {
374 Mgr.registerChecker<NoUncheckedPtrMemberChecker>();
375}
376
377bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
378 const CheckerManager &Mgr) {
379 return true;
380}
381
382void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
383 Mgr.registerChecker<NoUnretainedMemberChecker>();
384}
385
386bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
387 return true;
388}
389