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 "DanglingOnTemporaryChecker.h"
6
#include "CustomMatchers.h"
7
#include "VariableUsageHelpers.h"
8
9
void DanglingOnTemporaryChecker::registerMatchers(MatchFinder *AstMatcher) {
10
////////////////////////////////////////
11
// Quick annotation conflict checkers //
12
////////////////////////////////////////
13
14
AstMatcher->addMatcher(
15
// This is a matcher on a method declaration,
16
cxxMethodDecl(
17
// which is marked as no dangling on temporaries,
18
noDanglingOnTemporaries(),
19
20
// and which is && ref-qualified.
21
isRValueRefQualified(),
22
23
decl().bind("invalidMethodRefQualified")),
24
this);
25
26
AstMatcher->addMatcher(
27
// This is a matcher on a method declaration,
28
cxxMethodDecl(
29
// which is marked as no dangling on temporaries,
30
noDanglingOnTemporaries(),
31
32
// which returns a primitive type,
33
returns(builtinType()),
34
35
// and which doesn't return a pointer.
36
unless(returns(pointerType())),
37
38
decl().bind("invalidMethodPointer")),
39
this);
40
41
//////////////////
42
// Main checker //
43
//////////////////
44
45
auto hasParentCall = hasParent(expr(
46
anyOf(cxxOperatorCallExpr(
47
// If we're in a lamda, we may have an operator call expression
48
// ancestor in the AST, but the temporary we're matching
49
// against is not going to have the same lifetime as the
50
// constructor call.
51
unless(has(expr(ignoreTrivials(lambdaExpr())))),
52
expr().bind("parentOperatorCallExpr")),
53
callExpr(
54
// If we're in a lamda, we may have a call expression
55
// ancestor in the AST, but the temporary we're matching
56
// against is not going to have the same lifetime as the
57
// function call.
58
unless(has(expr(ignoreTrivials(lambdaExpr())))),
59
expr().bind("parentCallExpr")),
60
objcMessageExpr(
61
// If we're in a lamda, we may have an objc message expression
62
// ancestor in the AST, but the temporary we're matching
63
// against is not going to have the same lifetime as the
64
// function call.
65
unless(has(expr(ignoreTrivials(lambdaExpr())))),
66
expr().bind("parentObjCMessageExpr")),
67
cxxConstructExpr(
68
// If we're in a lamda, we may have a construct expression
69
// ancestor in the AST, but the temporary we're matching
70
// against is not going to have the same lifetime as the
71
// constructor call.
72
unless(has(expr(ignoreTrivials(lambdaExpr())))),
73
expr().bind("parentConstructExpr")))));
74
75
AstMatcher->addMatcher(
76
// This is a matcher on a method call,
77
cxxMemberCallExpr(
78
// which is in first party code,
79
isFirstParty(),
80
81
// and which is performed on a temporary,
82
on(allOf(unless(hasType(pointerType())), isTemporary(),
83
// but which is not `this`.
84
unless(cxxThisExpr()))),
85
86
// and which is marked as no dangling on temporaries.
87
callee(cxxMethodDecl(noDanglingOnTemporaries())),
88
89
expr().bind("memberCallExpr"),
90
91
// We optionally match a parent call expression or a parent construct
92
// expression because using a temporary inside a call is fine as long
93
// as the pointer doesn't escape the function call.
94
anyOf(
95
// This is the case where the call is the direct parent, so we
96
// know that the member call expression is the argument.
97
allOf(hasParentCall, expr().bind("parentCallArg")),
98
99
// This is the case where the call is not the direct parent, so we
100
// get its child to know in which argument tree we are.
101
hasAncestor(expr(hasParentCall, expr().bind("parentCallArg"))),
102
// To make it optional.
103
anything())),
104
this);
105
}
106
107
void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult &Result) {
108
///////////////////////////////////////
109
// Quick annotation conflict checker //
110
///////////////////////////////////////
111
112
const char *ErrorInvalidRefQualified = "methods annotated with "
113
"MOZ_NO_DANGLING_ON_TEMPORARIES "
114
"cannot be && ref-qualified";
115
116
const char *ErrorInvalidPointer = "methods annotated with "
117
"MOZ_NO_DANGLING_ON_TEMPORARIES must "
118
"return a pointer";
119
120
if (auto InvalidRefQualified =
121
Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) {
122
diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified,
123
DiagnosticIDs::Error);
124
return;
125
}
126
127
if (auto InvalidPointer =
128
Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) {
129
diag(InvalidPointer->getLocation(), ErrorInvalidPointer,
130
DiagnosticIDs::Error);
131
return;
132
}
133
134
//////////////////
135
// Main checker //
136
//////////////////
137
138
const char *Error = "calling `%0` on a temporary, potentially allowing use "
139
"after free of the raw pointer";
140
141
const char *EscapeStmtNote =
142
"the raw pointer escapes the function scope here";
143
144
const ObjCMessageExpr *ParentObjCMessageExpr =
145
Result.Nodes.getNodeAs<ObjCMessageExpr>("parentObjCMessageExpr");
146
147
// We don't care about cases in ObjC message expressions.
148
if (ParentObjCMessageExpr) {
149
return;
150
}
151
152
const CXXMemberCallExpr *MemberCall =
153
Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr");
154
155
const CallExpr *ParentCallExpr =
156
Result.Nodes.getNodeAs<CallExpr>("parentCallExpr");
157
const CXXConstructExpr *ParentConstructExpr =
158
Result.Nodes.getNodeAs<CXXConstructExpr>("parentConstructExpr");
159
const CXXOperatorCallExpr *ParentOperatorCallExpr =
160
Result.Nodes.getNodeAs<CXXOperatorCallExpr>("parentOperatorCallExpr");
161
const Expr *ParentCallArg = Result.Nodes.getNodeAs<Expr>("parentCallArg");
162
163
// Just in case.
164
if (!MemberCall) {
165
return;
166
}
167
168
// If we have a parent call, we check whether or not we escape the function
169
// being called.
170
if (ParentOperatorCallExpr || ParentCallExpr || ParentConstructExpr) {
171
// Just in case.
172
if (!ParentCallArg) {
173
return;
174
}
175
176
// No default constructor so we can't construct it using if/else.
177
auto FunctionEscapeData =
178
ParentOperatorCallExpr
179
? escapesFunction(ParentCallArg, ParentOperatorCallExpr)
180
: ParentCallExpr
181
? escapesFunction(ParentCallArg, ParentCallExpr)
182
: escapesFunction(ParentCallArg, ParentConstructExpr);
183
184
// If there was an error in the escapesFunction call.
185
if (std::error_code ec = FunctionEscapeData.getError()) {
186
// FIXME: For now we ignore the variadic case and just consider that the
187
// argument doesn't escape the function. Same for the case where we can't
188
// find the function declaration or if the function is builtin.
189
if (static_cast<EscapesFunctionError>(ec.value()) ==
190
EscapesFunctionError::FunctionIsVariadic ||
191
static_cast<EscapesFunctionError>(ec.value()) ==
192
EscapesFunctionError::FunctionDeclNotFound ||
193
static_cast<EscapesFunctionError>(ec.value()) ==
194
EscapesFunctionError::FunctionIsBuiltin) {
195
return;
196
}
197
198
// We emit the internal checker error and return.
199
diag(MemberCall->getExprLoc(),
200
std::string(ec.category().name()) + " error: " + ec.message(),
201
DiagnosticIDs::Error);
202
return;
203
}
204
205
// We deconstruct the function escape data.
206
const Stmt *EscapeStmt;
207
const Decl *EscapeDecl;
208
std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData;
209
210
// If we didn't escape a parent function, we're done: we don't emit any
211
// diagnostic.
212
if (!EscapeStmt || !EscapeDecl) {
213
return;
214
}
215
216
// We emit the error diagnostic indicating that we are calling the method
217
// temporary.
218
diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
219
<< MemberCall->getMethodDecl()->getName()
220
<< MemberCall->getSourceRange();
221
222
// We indicate the escape statement.
223
diag(EscapeStmt->getBeginLoc(), EscapeStmtNote, DiagnosticIDs::Note)
224
<< EscapeStmt->getSourceRange();
225
226
// We build the escape note along with its source range.
227
StringRef EscapeDeclNote;
228
SourceRange EscapeDeclRange;
229
if (isa<ParmVarDecl>(EscapeDecl)) {
230
EscapeDeclNote = "through the parameter declared here";
231
EscapeDeclRange = EscapeDecl->getSourceRange();
232
} else if (isa<VarDecl>(EscapeDecl)) {
233
EscapeDeclNote = "through the variable declared here";
234
EscapeDeclRange = EscapeDecl->getSourceRange();
235
} else if (isa<FieldDecl>(EscapeDecl)) {
236
EscapeDeclNote = "through the field declared here";
237
EscapeDeclRange = EscapeDecl->getSourceRange();
238
} else if (auto FuncDecl = dyn_cast<FunctionDecl>(EscapeDecl)) {
239
EscapeDeclNote = "through the return value of the function declared here";
240
EscapeDeclRange = FuncDecl->getReturnTypeSourceRange();
241
} else {
242
return;
243
}
244
245
// We emit the declaration note indicating through which decl the argument
246
// escapes.
247
diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note)
248
<< EscapeDeclRange;
249
} else {
250
// We emit the error diagnostic indicating that we are calling the method
251
// temporary.
252
diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
253
<< MemberCall->getMethodDecl()->getName()
254
<< MemberCall->getSourceRange();
255
}
256
}