Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "RefCountedInsideLambdaChecker.h"
6
#include "CustomMatchers.h"
7
8
RefCountedMap RefCountedClasses;
9
10
void RefCountedInsideLambdaChecker::registerMatchers(MatchFinder *AstMatcher) {
11
// We want to reject any code which captures a pointer to an object of a
12
// refcounted type, and then lets that value escape. As a primitive analysis,
13
// we reject any occurances of the lambda as a template parameter to a class
14
// (which could allow it to escape), as well as any presence of such a lambda
15
// in a return value (either from lambdas, or in c++14, auto functions).
16
//
17
// We check these lambdas' capture lists for raw pointers to refcounted types.
18
AstMatcher->addMatcher(functionDecl(returns(recordType(hasDeclaration(
19
cxxRecordDecl(isLambdaDecl()).bind("decl"))))),
20
this);
21
AstMatcher->addMatcher(lambdaExpr().bind("lambdaExpr"), this);
22
AstMatcher->addMatcher(
23
classTemplateSpecializationDecl(
24
hasAnyTemplateArgument(refersToType(recordType(
25
hasDeclaration(cxxRecordDecl(isLambdaDecl()).bind("decl")))))),
26
this);
27
}
28
29
void RefCountedInsideLambdaChecker::emitDiagnostics(SourceLocation Loc,
30
StringRef Name,
31
QualType Type) {
32
diag(Loc,
33
"Refcounted variable '%0' of type %1 cannot be captured by a lambda",
34
DiagnosticIDs::Error)
35
<< Name << Type;
36
diag(Loc, "Please consider using a smart pointer", DiagnosticIDs::Note);
37
}
38
39
void RefCountedInsideLambdaChecker::check(
40
const MatchFinder::MatchResult &Result) {
41
static DenseSet<const CXXRecordDecl *> CheckedDecls;
42
43
const CXXRecordDecl *Lambda = Result.Nodes.getNodeAs<CXXRecordDecl>("decl");
44
45
if (const LambdaExpr *OuterLambda =
46
Result.Nodes.getNodeAs<LambdaExpr>("lambdaExpr")) {
47
const CXXMethodDecl *OpCall = OuterLambda->getCallOperator();
48
QualType ReturnTy = OpCall->getReturnType();
49
if (const CXXRecordDecl *Record = ReturnTy->getAsCXXRecordDecl()) {
50
Lambda = Record;
51
}
52
}
53
54
if (!Lambda || !Lambda->isLambda()) {
55
return;
56
}
57
58
// Don't report errors on the same declarations more than once.
59
if (CheckedDecls.count(Lambda)) {
60
return;
61
}
62
CheckedDecls.insert(Lambda);
63
64
bool StrongRefToThisCaptured = false;
65
66
for (const LambdaCapture &Capture : Lambda->captures()) {
67
// Check if any of the captures are ByRef. If they are, we have nothing to
68
// report, as it's OK to capture raw pointers to refcounted objects so long
69
// as the Lambda doesn't escape the current scope, which is required by
70
// ByRef captures already.
71
if (Capture.getCaptureKind() == LCK_ByRef) {
72
return;
73
}
74
75
// Check if this capture is byvalue, and captures a strong reference to
76
// this.
77
// XXX: Do we want to make sure that this type which we are capturing is a
78
// "Smart Pointer" somehow?
79
if (!StrongRefToThisCaptured && Capture.capturesVariable() &&
80
Capture.getCaptureKind() == LCK_ByCopy) {
81
const VarDecl *Var = Capture.getCapturedVar();
82
if (Var->hasInit()) {
83
const Stmt *Init = Var->getInit();
84
85
// Ignore single argument constructors, and trivial nodes.
86
while (true) {
87
auto NewInit = IgnoreTrivials(Init);
88
if (auto ConstructExpr = dyn_cast<CXXConstructExpr>(NewInit)) {
89
if (ConstructExpr->getNumArgs() == 1) {
90
NewInit = ConstructExpr->getArg(0);
91
}
92
}
93
if (Init == NewInit) {
94
break;
95
}
96
Init = NewInit;
97
}
98
99
if (isa<CXXThisExpr>(Init)) {
100
StrongRefToThisCaptured = true;
101
}
102
}
103
}
104
}
105
106
// Now we can go through and produce errors for any captured variables or this
107
// pointers.
108
for (const LambdaCapture &Capture : Lambda->captures()) {
109
if (Capture.capturesVariable()) {
110
QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType();
111
112
if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
113
emitDiagnostics(Capture.getLocation(),
114
Capture.getCapturedVar()->getName(), Pointee);
115
return;
116
}
117
}
118
119
// The situation with captures of `this` is more complex. All captures of
120
// `this` look the same-ish (they are LCK_This). We want to complain about
121
// captures of `this` where `this` is a refcounted type, and the capture is
122
// actually used in the body of the lambda (if the capture isn't used, then
123
// we don't care, because it's only being captured in order to give access
124
// to private methods).
125
//
126
// In addition, we don't complain about this, even if it is used, if it was
127
// captured implicitly when the LambdaCaptureDefault was LCD_ByRef, as that
128
// expresses the intent that the lambda won't leave the enclosing scope.
129
bool ImplicitByRefDefaultedCapture =
130
Capture.isImplicit() && Lambda->getLambdaCaptureDefault() == LCD_ByRef;
131
if (Capture.capturesThis() && !ImplicitByRefDefaultedCapture &&
132
!StrongRefToThisCaptured) {
133
ThisVisitor V(*this);
134
bool NotAborted = V.TraverseDecl(
135
const_cast<CXXMethodDecl *>(Lambda->getLambdaCallOperator()));
136
if (!NotAborted) {
137
return;
138
}
139
}
140
}
141
}
142
143
bool RefCountedInsideLambdaChecker::ThisVisitor::VisitCXXThisExpr(
144
CXXThisExpr *This) {
145
QualType Pointee = This->getType()->getPointeeType();
146
if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
147
Checker.emitDiagnostics(This->getBeginLoc(), "this", Pointee);
148
return false;
149
}
150
151
return true;
152
}