Skip to content

Commit a4cd301

Browse files
committed
[alpha.webkit.NoUnretainedMemberChecker] Add a new WebKit checker for unretained member variables and ivars.
Add a new WebKit checker for member variables and instance variables of NS and CF types. A member variable or instance variable to a CF type should be RetainPtr regardless of whether ARC is enabled or disabled, and that of a NS type should be RetainPtr when ARC is disabled.
1 parent b335d5a commit a4cd301

File tree

5 files changed

+210
-20
lines changed

5 files changed

+210
-20
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3487,6 +3487,19 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef
34873487
34883488
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
34893489
3490+
alpha.webkit.NoUnretainedMemberChecker
3491+
""""""""""""""""""""""""""""""""""""""""
3492+
Raw pointers and references to a NS or CF object can't be used as class members or ivars. Only RetainPtr is allowed.
3493+
3494+
.. code-block:: cpp
3495+
3496+
struct Foo {
3497+
NSObject *ptr; // warn
3498+
// ...
3499+
};
3500+
3501+
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
3502+
34903503
.. _alpha-webkit-UncountedCallArgsChecker:
34913504
34923505
alpha.webkit.UncountedCallArgsChecker

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,10 @@ def NoUncheckedPtrMemberChecker : Checker<"NoUncheckedPtrMemberChecker">,
17661766
HelpText<"Check for no unchecked member variables.">,
17671767
Documentation<HasDocumentation>;
17681768

1769+
def NoUnretainedMemberChecker : Checker<"NoUnretainedMemberChecker">,
1770+
HelpText<"Check for no unretained member variables.">,
1771+
Documentation<HasDocumentation>;
1772+
17691773
def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">,
17701774
HelpText<"Check uncounted call arguments.">,
17711775
Documentation<HasDocumentation>;

clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ class RawPtrRefMemberChecker
3131
BugType Bug;
3232
mutable BugReporter *BR;
3333

34+
protected:
35+
mutable std::optional<RetainTypeChecker> RTC;
36+
3437
public:
3538
RawPtrRefMemberChecker(const char *description)
3639
: Bug(this, description, "WebKit coding guidelines") {}
3740

3841
virtual std::optional<bool>
39-
isPtrCompatible(const clang::CXXRecordDecl *) const = 0;
42+
isPtrCompatible(const clang::QualType,
43+
const clang::CXXRecordDecl *R) const = 0;
4044
virtual bool isPtrCls(const clang::CXXRecordDecl *) const = 0;
4145
virtual const char *typeName() const = 0;
4246
virtual const char *invariant() const = 0;
@@ -57,6 +61,12 @@ class RawPtrRefMemberChecker
5761
ShouldVisitImplicitCode = false;
5862
}
5963

64+
bool VisitTypedefDecl(const TypedefDecl *TD) override {
65+
if (Checker->RTC)
66+
Checker->RTC->visitTypedef(TD);
67+
return true;
68+
}
69+
6070
bool VisitRecordDecl(const RecordDecl *RD) override {
6171
Checker->visitRecordDecl(RD);
6272
return true;
@@ -69,6 +79,8 @@ class RawPtrRefMemberChecker
6979
};
7080

7181
LocalVisitor visitor(this);
82+
if (RTC)
83+
RTC->visitTranslationUnitDecl(TUD);
7284
visitor.TraverseDecl(TUD);
7385
}
7486

@@ -77,16 +89,22 @@ class RawPtrRefMemberChecker
7789
return;
7890

7991
for (auto *Member : RD->fields()) {
80-
const Type *MemberType = Member->getType().getTypePtrOrNull();
92+
auto QT = Member->getType();
93+
const Type *MemberType = QT.getTypePtrOrNull();
8194
if (!MemberType)
8295
continue;
8396

8497
if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) {
85-
// If we don't see the definition we just don't know.
86-
if (MemberCXXRD->hasDefinition()) {
87-
std::optional<bool> isRCAble = isPtrCompatible(MemberCXXRD);
88-
if (isRCAble && *isRCAble)
89-
reportBug(Member, MemberType, MemberCXXRD, RD);
98+
std::optional<bool> IsCompatible = isPtrCompatible(QT, MemberCXXRD);
99+
if (IsCompatible && *IsCompatible)
100+
reportBug(Member, MemberType, MemberCXXRD, RD);
101+
} else {
102+
std::optional<bool> IsCompatible = isPtrCompatible(QT, nullptr);
103+
auto* PointeeType = MemberType->getPointeeType().getTypePtrOrNull();
104+
if (IsCompatible && *IsCompatible) {
105+
auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
106+
if (auto *ObjCType = dyn_cast_or_null<ObjCInterfaceType>(Desugared))
107+
reportBug(Member, MemberType, ObjCType->getDecl(), RD);
90108
}
91109
}
92110
}
@@ -107,11 +125,12 @@ class RawPtrRefMemberChecker
107125

108126
void visitIvarDecl(const ObjCContainerDecl *CD,
109127
const ObjCIvarDecl *Ivar) const {
110-
const Type *IvarType = Ivar->getType().getTypePtrOrNull();
128+
auto QT = Ivar->getType();
129+
const Type *IvarType = QT.getTypePtrOrNull();
111130
if (!IvarType)
112131
return;
113132
if (auto *IvarCXXRD = IvarType->getPointeeCXXRecordDecl()) {
114-
std::optional<bool> IsCompatible = isPtrCompatible(IvarCXXRD);
133+
std::optional<bool> IsCompatible = isPtrCompatible(QT, IvarCXXRD);
115134
if (IsCompatible && *IsCompatible)
116135
reportBug(Ivar, IvarType, IvarCXXRD, CD);
117136
}
@@ -151,13 +170,13 @@ class RawPtrRefMemberChecker
151170
return false;
152171
}
153172

154-
template <typename DeclType, typename ParentDeclType>
173+
template <typename DeclType, typename PointeeType, typename ParentDeclType>
155174
void reportBug(const DeclType *Member, const Type *MemberType,
156-
const CXXRecordDecl *MemberCXXRD,
175+
const PointeeType *Pointee,
157176
const ParentDeclType *ClassCXXRD) const {
158177
assert(Member);
159178
assert(MemberType);
160-
assert(MemberCXXRD);
179+
assert(Pointee);
161180

162181
SmallString<100> Buf;
163182
llvm::raw_svector_ostream Os(Buf);
@@ -169,10 +188,13 @@ class RawPtrRefMemberChecker
169188
printQuotedName(Os, Member);
170189
Os << " in ";
171190
printQuotedQualifiedName(Os, ClassCXXRD);
172-
Os << " is a "
173-
<< (isa<PointerType>(MemberType) ? "raw pointer" : "reference") << " to "
174-
<< typeName() << " ";
175-
printQuotedQualifiedName(Os, MemberCXXRD);
191+
Os << " is a ";
192+
if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) {
193+
auto Typedef = MemberType->getAs<TypedefType>();
194+
assert(Typedef);
195+
printQuotedQualifiedName(Os, Typedef->getDecl());
196+
} else
197+
printQuotedQualifiedName(Os, Pointee);
176198
Os << "; " << invariant() << ".";
177199

178200
PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),
@@ -181,6 +203,16 @@ class RawPtrRefMemberChecker
181203
Report->addRange(Member->getSourceRange());
182204
BR->emitReport(std::move(Report));
183205
}
206+
207+
enum class PrintDeclKind { Pointee, Pointer };
208+
virtual PrintDeclKind printPointer(llvm::raw_svector_ostream& Os,
209+
const Type *T) const {
210+
T = T->getUnqualifiedDesugaredType();
211+
bool IsPtr = isa<PointerType>(T) || isa<ObjCObjectPointerType>(T);
212+
Os << (IsPtr ? "raw pointer" : "reference") << " to "
213+
<< typeName() << " ";
214+
return PrintDeclKind::Pointee;
215+
}
184216
};
185217

186218
class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
@@ -190,8 +222,9 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
190222
"reference-countable type") {}
191223

192224
std::optional<bool>
193-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
194-
return isRefCountable(R);
225+
isPtrCompatible(const clang::QualType,
226+
const clang::CXXRecordDecl *R) const final {
227+
return R && isRefCountable(R);
195228
}
196229

197230
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -212,8 +245,9 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
212245
"checked-pointer capable type") {}
213246

214247
std::optional<bool>
215-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
216-
return isCheckedPtrCapable(R);
248+
isPtrCompatible(const clang::QualType,
249+
const clang::CXXRecordDecl *R) const final {
250+
return R && isCheckedPtrCapable(R);
217251
}
218252

219253
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -228,6 +262,40 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
228262
}
229263
};
230264

265+
class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
266+
public:
267+
NoUnretainedMemberChecker()
268+
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
269+
"retainable type") {
270+
RTC = RetainTypeChecker();
271+
}
272+
273+
std::optional<bool>
274+
isPtrCompatible(const clang::QualType QT,
275+
const clang::CXXRecordDecl *) const final {
276+
return RTC->isUnretained(QT);
277+
}
278+
279+
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
280+
return isRetainPtr(R);
281+
}
282+
283+
const char *typeName() const final { return "retainable type"; }
284+
285+
const char *invariant() const final {
286+
return "member variables must be a RetainPtr";
287+
}
288+
289+
PrintDeclKind printPointer(llvm::raw_svector_ostream& Os,
290+
const Type *T) const final {
291+
if (!isa<ObjCObjectPointerType>(T) && T->getAs<TypedefType>()) {
292+
Os << typeName() << " ";
293+
return PrintDeclKind::Pointer;
294+
}
295+
return RawPtrRefMemberChecker::printPointer(Os, T);
296+
}
297+
};
298+
231299
} // namespace
232300

233301
void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
@@ -246,3 +314,12 @@ bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
246314
const CheckerManager &Mgr) {
247315
return true;
248316
}
317+
318+
void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
319+
Mgr.registerChecker<NoUnretainedMemberChecker>();
320+
}
321+
322+
bool ento::shouldRegisterNoUnretainedMemberChecker(
323+
const CheckerManager &Mgr) {
324+
return true;
325+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -fobjc-arc -verify %s
2+
3+
#include "objc-mock-types.h"
4+
5+
namespace members {
6+
7+
struct Foo {
8+
private:
9+
SomeObj* a = nullptr;
10+
11+
[[clang::suppress]]
12+
SomeObj* a_suppressed = nullptr;
13+
14+
protected:
15+
RetainPtr<SomeObj> b;
16+
17+
public:
18+
SomeObj* c = nullptr;
19+
RetainPtr<SomeObj> d;
20+
21+
CFMutableArrayRef e = nullptr;
22+
// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
23+
};
24+
25+
template<class T, class S>
26+
struct FooTmpl {
27+
T* x;
28+
S y;
29+
// expected-warning@-1{{Member variable 'y' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
30+
};
31+
32+
void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
33+
34+
struct [[clang::suppress]] FooSuppressed {
35+
private:
36+
SomeObj* a = nullptr;
37+
};
38+
39+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -verify %s
2+
3+
#include "objc-mock-types.h"
4+
5+
namespace members {
6+
7+
struct Foo {
8+
private:
9+
SomeObj* a = nullptr;
10+
// expected-warning@-1{{Member variable 'a' in 'members::Foo' is a raw pointer to retainable type}}
11+
12+
[[clang::suppress]]
13+
SomeObj* a_suppressed = nullptr;
14+
15+
protected:
16+
RetainPtr<SomeObj> b;
17+
18+
public:
19+
SomeObj* c = nullptr;
20+
// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a raw pointer to retainable type}}
21+
RetainPtr<SomeObj> d;
22+
23+
CFMutableArrayRef e = nullptr;
24+
// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
25+
};
26+
27+
template<class T, class S>
28+
struct FooTmpl {
29+
T* a;
30+
// expected-warning@-1{{Member variable 'a' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
31+
S b;
32+
// expected-warning@-1{{Member variable 'b' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
33+
};
34+
35+
void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
36+
37+
struct [[clang::suppress]] FooSuppressed {
38+
private:
39+
SomeObj* a = nullptr;
40+
};
41+
42+
}
43+
44+
namespace ignore_unions {
45+
union Foo {
46+
SomeObj* a;
47+
RetainPtr<SomeObj> b;
48+
CFMutableArrayRef c;
49+
};
50+
51+
template<class T>
52+
union RefPtr {
53+
T* a;
54+
};
55+
56+
void forceTmplToInstantiate(RefPtr<SomeObj>) {}
57+
}

0 commit comments

Comments
 (0)