Skip to content

Commit 3985c3f

Browse files
rniwaRyosuke Niwa
authored andcommitted
[alpha.webkit.NoUnretainedMemberChecker] Add a new WebKit checker for unretained member variables and ivars. (llvm#128641)
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 7314aa9 commit 3985c3f

File tree

5 files changed

+211
-20
lines changed

5 files changed

+211
-20
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3479,6 +3479,19 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef
34793479
34803480
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
34813481
3482+
alpha.webkit.NoUnretainedMemberChecker
3483+
""""""""""""""""""""""""""""""""""""""""
3484+
Raw pointers and references to a NS or CF object can't be used as class members or ivars. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled. Only RetainPtr is allowed for NS types when ARC is disabled.
3485+
3486+
.. code-block:: cpp
3487+
3488+
struct Foo {
3489+
NSObject *ptr; // warn
3490+
// ...
3491+
};
3492+
3493+
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
3494+
34823495
alpha.webkit.UnretainedLambdaCapturesChecker
34833496
""""""""""""""""""""""""""""""""""""""""""""
34843497
Raw pointers and references to NS or CF types can't be captured in lambdas. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled, and only RetainPtr is allowed for NS types when ARC is disabled.

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

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

1798+
def NoUnretainedMemberChecker : Checker<"NoUnretainedMemberChecker">,
1799+
HelpText<"Check for no unretained member variables.">,
1800+
Documentation<HasDocumentation>;
1801+
17981802
def UnretainedLambdaCapturesChecker : Checker<"UnretainedLambdaCapturesChecker">,
17991803
HelpText<"Check unretained lambda captures.">,
18001804
Documentation<HasDocumentation>;

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

Lines changed: 95 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;
@@ -58,6 +62,12 @@ class RawPtrRefMemberChecker
5862
bool shouldVisitTemplateInstantiations() const { return true; }
5963
bool shouldVisitImplicitCode() const { return false; }
6064

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

7282
LocalVisitor visitor(this);
83+
if (RTC)
84+
RTC->visitTranslationUnitDecl(TUD);
7385
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
7486
}
7587

@@ -78,16 +90,22 @@ class RawPtrRefMemberChecker
7890
return;
7991

8092
for (auto *Member : RD->fields()) {
81-
const Type *MemberType = Member->getType().getTypePtrOrNull();
93+
auto QT = Member->getType();
94+
const Type *MemberType = QT.getTypePtrOrNull();
8295
if (!MemberType)
8396
continue;
8497

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

109127
void visitIvarDecl(const ObjCContainerDecl *CD,
110128
const ObjCIvarDecl *Ivar) const {
111-
const Type *IvarType = Ivar->getType().getTypePtrOrNull();
129+
auto QT = Ivar->getType();
130+
const Type *IvarType = QT.getTypePtrOrNull();
112131
if (!IvarType)
113132
return;
114133
if (auto *IvarCXXRD = IvarType->getPointeeCXXRecordDecl()) {
115-
std::optional<bool> IsCompatible = isPtrCompatible(IvarCXXRD);
134+
std::optional<bool> IsCompatible = isPtrCompatible(QT, IvarCXXRD);
116135
if (IsCompatible && *IsCompatible)
117136
reportBug(Ivar, IvarType, IvarCXXRD, CD);
118137
}
@@ -152,13 +171,13 @@ class RawPtrRefMemberChecker
152171
return false;
153172
}
154173

155-
template <typename DeclType, typename ParentDeclType>
174+
template <typename DeclType, typename PointeeType, typename ParentDeclType>
156175
void reportBug(const DeclType *Member, const Type *MemberType,
157-
const CXXRecordDecl *MemberCXXRD,
176+
const PointeeType *Pointee,
158177
const ParentDeclType *ClassCXXRD) const {
159178
assert(Member);
160179
assert(MemberType);
161-
assert(MemberCXXRD);
180+
assert(Pointee);
162181

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

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

187218
class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
@@ -191,8 +222,9 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
191222
"reference-countable type") {}
192223

193224
std::optional<bool>
194-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
195-
return isRefCountable(R);
225+
isPtrCompatible(const clang::QualType,
226+
const clang::CXXRecordDecl *R) const final {
227+
return R && isRefCountable(R);
196228
}
197229

198230
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -213,8 +245,9 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
213245
"checked-pointer capable type") {}
214246

215247
std::optional<bool>
216-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
217-
return isCheckedPtrCapable(R);
248+
isPtrCompatible(const clang::QualType,
249+
const clang::CXXRecordDecl *R) const final {
250+
return R && isCheckedPtrCapable(R);
218251
}
219252

220253
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -229,6 +262,40 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
229262
}
230263
};
231264

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+
232299
} // namespace
233300

234301
void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
@@ -247,3 +314,11 @@ bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
247314
const CheckerManager &Mgr) {
248315
return true;
249316
}
317+
318+
void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
319+
Mgr.registerChecker<NoUnretainedMemberChecker>();
320+
}
321+
322+
bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
323+
return true;
324+
}
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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
// No warning.
15+
16+
protected:
17+
RetainPtr<SomeObj> b;
18+
// No warning.
19+
20+
public:
21+
SomeObj* c = nullptr;
22+
// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a raw pointer to retainable type}}
23+
RetainPtr<SomeObj> d;
24+
25+
CFMutableArrayRef e = nullptr;
26+
// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
27+
};
28+
29+
template<class T, class S>
30+
struct FooTmpl {
31+
T* a;
32+
// expected-warning@-1{{Member variable 'a' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
33+
S b;
34+
// expected-warning@-1{{Member variable 'b' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
35+
};
36+
37+
void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
38+
39+
struct [[clang::suppress]] FooSuppressed {
40+
private:
41+
SomeObj* a = nullptr;
42+
// No warning.
43+
};
44+
45+
}
46+
47+
namespace ignore_unions {
48+
union Foo {
49+
SomeObj* a;
50+
RetainPtr<SomeObj> b;
51+
CFMutableArrayRef c;
52+
};
53+
54+
template<class T>
55+
union RefPtr {
56+
T* a;
57+
};
58+
59+
void forceTmplToInstantiate(RefPtr<SomeObj>) {}
60+
}

0 commit comments

Comments
 (0)