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
/**
6
* This checker implements the "can run script" analysis. The idea is to detect
7
* functions that can run script that are being passed reference-counted
8
* arguments (including "this") whose refcount might go to zero as a result of
9
* the script running. We want to prevent that.
10
*
11
* The approach is to attempt to enforce the following invariants on the call
12
* graph:
13
*
14
* 1) Any caller of a MOZ_CAN_RUN_SCRIPT function is itself MOZ_CAN_RUN_SCRIPT.
15
* 2) If a virtual MOZ_CAN_RUN_SCRIPT method overrides a base class method,
16
* that base class method is also MOZ_CAN_RUN_SCRIPT.
17
*
18
* Invariant 2 ensures that we don't accidentally call a MOZ_CAN_RUN_SCRIPT
19
* function via a base-class virtual call. Invariant 1 ensures that
20
* the property of being able to run script propagates up the callstack. There
21
* is an opt-out for invariant 1: A function (declaration _or_ implementation)
22
* can be decorated with MOZ_CAN_RUN_SCRIPT_BOUNDARY to indicate that we do not
23
* require it or any of its callers to be MOZ_CAN_RUN_SCRIPT even if it calls
24
* MOZ_CAN_RUN_SCRIPT functions.
25
*
26
* There are two known holes in invariant 1, apart from the
27
* MOZ_CAN_RUN_SCRIPT_BOUNDARY opt-out:
28
*
29
* - Functions called via function pointers can be MOZ_CAN_RUN_SCRIPT even if
30
* their caller is not, because we have no way to determine from the function
31
* pointer what function is being called.
32
* - MOZ_CAN_RUN_SCRIPT destructors can happen in functions that are not
33
* MOZ_CAN_RUN_SCRIPT.
35
*
36
* Given those invariants we then require that when calling a MOZ_CAN_RUN_SCRIPT
37
* function all refcounted arguments (including "this") satisfy one of these
38
* conditions:
39
* a) The argument is held via a strong pointer on the stack.
40
* b) The argument is a const strong pointer member of "this". We know "this"
41
* is being kept alive, and a const strong pointer member can't drop its ref
42
* until "this" dies.
43
* c) The argument is an argument of the caller (and hence held by a strong
44
* pointer somewhere higher up the callstack).
45
* d) The argument is explicitly annotated with MOZ_KnownLive, which indicates
46
* that something is guaranteed to keep it alive (e.g. it's rooted via a JS
47
* reflector).
48
* e) The argument is constexpr and therefore cannot disappear.
49
*/
50
51
#include "CanRunScriptChecker.h"
52
#include "CustomMatchers.h"
53
#include "clang/Lex/Lexer.h"
54
55
void CanRunScriptChecker::registerMatchers(MatchFinder *AstMatcher) {
56
auto Refcounted = qualType(hasDeclaration(cxxRecordDecl(isRefCounted())));
57
auto StackSmartPtr =
58
ignoreTrivials(
59
declRefExpr(to(varDecl(hasAutomaticStorageDuration())),
60
hasType(isSmartPtrToRefCounted())));
61
auto ConstMemberOfThisSmartPtr =
62
memberExpr(hasType(isSmartPtrToRefCounted()),
63
hasType(isConstQualified()),
64
hasObjectExpression(cxxThisExpr()));
65
// A smartptr can be known-live for three reasons:
66
// 1) It's declared on the stack.
67
// 2) It's a const member of "this". We know "this" is alive (recursively)
68
// and const members can't change their value hence can't drop their
69
// reference until "this" gets destroyed.
70
// 3) It's an immediate temporary being constructed at the point where the
71
// call is happening.
72
auto KnownLiveSmartPtr = anyOf(
73
StackSmartPtr,
74
ConstMemberOfThisSmartPtr,
75
ignoreTrivials(cxxConstructExpr(hasType(isSmartPtrToRefCounted()))));
76
77
auto MozKnownLiveCall =
78
ignoreTrivials(callExpr(callee(functionDecl(hasName("MOZ_KnownLive")))));
79
80
// Params of the calling function are presumed live, because it itself should be
81
// MOZ_CAN_RUN_SCRIPT. Note that this is subject to
83
auto KnownLiveParam = anyOf(
84
// "this" is OK
85
cxxThisExpr(),
86
// A parameter of the calling function is OK.
87
declRefExpr(to(parmVarDecl())));
88
89
// A matcher that matches various things that are known to be live directly,
90
// without making any assumptions about operators.
91
auto KnownLiveBase = anyOf(
92
// Things that are known to be a stack or immutable refptr.
93
KnownLiveSmartPtr,
94
// MOZ_KnownLive() calls.
95
MozKnownLiveCall,
96
// Params of the caller function.
97
KnownLiveParam,
98
// Constexpr things.
99
declRefExpr(to(varDecl(isConstexpr()))));
100
101
// A matcher that matches various known-live things that don't involve
102
// non-unary operators.
103
auto KnownLiveSimple = anyOf(
104
// Things that are just known live.
105
KnownLiveBase,
106
// Method calls on a live things that are smart ptrs. Note that we don't
107
// want to allow general method calls on live things, because those can
108
// return non-live objects (e.g. consider "live_pointer->foo()" as an
109
// example). For purposes of this analysis we are assuming the method
110
// calls on smart ptrs all just return the pointer inside,
111
cxxMemberCallExpr(on(
112
allOf(hasType(isSmartPtrToRefCounted()),
113
KnownLiveBase))),
114
// operator* or operator-> on a thing that is already known to be live.
115
cxxOperatorCallExpr(
116
anyOf(hasOverloadedOperatorName("*"),
117
hasOverloadedOperatorName("->")),
118
hasAnyArgument(KnownLiveBase),
119
argumentCountIs(1)),
120
// A dereference on a thing that is known to be live. This is _not_
121
// caught by the "operator* or operator->" clause above, because
122
// cxxOperatorCallExpr() only catches cases when a class defines
123
// operator*. The default (built-in) operator* matches unaryOperator()
124
// instead.),
125
unaryOperator(
126
unaryDereferenceOperator(),
127
hasUnaryOperand(
128
// If we're doing *someArg, the argument of the dereference is an
129
// ImplicitCastExpr LValueToRValue which has the DeclRefExpr as an
130
// argument. We could try to match that explicitly with a custom
131
// matcher (none of the built-in matchers seem to match on the
132
// thing being cast for an implicitCastExpr), but it's simpler to
133
// just use ignoreTrivials to strip off the cast.
134
ignoreTrivials(KnownLiveBase))),
135
// Taking a pointer to a live reference. We explicitly want to exclude
136
// things that are not of type reference-to-refcounted or type refcounted,
137
// because if someone takes a pointer to a pointer to refcounted or a
138
// pointer to a smart ptr and passes those in to a callee that definitely
139
// does not guarantee liveness; in fact the callee could modify those
140
// things! In practice they would be the wrong type anyway, though, so
141
// it's hard to add a test for this.
142
unaryOperator(
143
hasOperatorName("&"),
144
hasUnaryOperand(allOf(
145
anyOf(
146
hasType(references(Refcounted)),
147
hasType(Refcounted)),
148
ignoreTrivials(KnownLiveBase))))
149
);
150
151
auto KnownLive = anyOf(
152
// Anything above, of course.
153
KnownLiveSimple,
154
// Conditional operators where both arms are live.
155
conditionalOperator(
156
hasFalseExpression(ignoreTrivials(KnownLiveSimple)),
157
hasTrueExpression(ignoreTrivials(KnownLiveSimple)))
158
// We're not handling cases like a dereference of a conditional operator,
159
// mostly because handling a dereference in general is so ugly. I
160
// _really_ wish I could just write a recursive matcher here easily.
161
);
162
163
auto InvalidArg =
164
ignoreTrivialsConditional(
165
// We want to consider things if there is anything refcounted involved,
166
// including in any of the trivials that we otherwise strip off.
167
anyOf(
168
hasType(Refcounted),
169
hasType(pointsTo(Refcounted)),
170
hasType(references(Refcounted)),
171
hasType(isSmartPtrToRefCounted())
172
),
173
// We want to find any expression,
174
expr(
175
// which is not known live,
176
unless(KnownLive),
177
// and which is not a default arg with value nullptr, since those are
178
// always safe,
179
unless(cxxDefaultArgExpr(isNullDefaultArg())),
180
// and which is not a literal nullptr,
181
unless(cxxNullPtrLiteralExpr()),
182
expr().bind("invalidArg")));
183
184
// A matcher which will mark the first invalid argument it finds invalid, but
185
// will always match, even if it finds no invalid arguments, so it doesn't
186
// preclude other matchers from running and maybe finding invalid args.
187
auto OptionalInvalidExplicitArg = anyOf(
188
// We want to find any argument which is invalid.
189
hasAnyArgument(InvalidArg),
190
191
// This makes this matcher optional.
192
anything());
193
194
// Please note that the hasCanRunScriptAnnotation() matchers are not present
195
// directly in the cxxMemberCallExpr, callExpr and constructExpr matchers
196
// because we check that the corresponding functions can run script later in
197
// the checker code.
198
AstMatcher->addMatcher(
199
expr(
200
anyOf(
201
// We want to match a method call expression,
202
cxxMemberCallExpr(
203
// which optionally has an invalid arg,
204
OptionalInvalidExplicitArg,
205
// or which optionally has an invalid this argument,
206
anyOf(
207
on(InvalidArg),
208
anything()
209
),
210
expr().bind("callExpr")),
211
// or a regular call expression,
212
callExpr(
213
// which optionally has an invalid arg.
214
OptionalInvalidExplicitArg, expr().bind("callExpr")),
215
// or a construct expression,
216
cxxConstructExpr(
217
// which optionally has an invalid arg.
218
OptionalInvalidExplicitArg, expr().bind("constructExpr"))),
219
220
anyOf(
221
// We want to match the parent function.
222
forFunction(functionDecl().bind("nonCanRunScriptParentFunction")),
223
224
// ... optionally.
225
anything())),
226
this);
227
}
228
229
void CanRunScriptChecker::onStartOfTranslationUnit() {
230
IsFuncSetBuilt = false;
231
CanRunScriptFuncs.clear();
232
}
233
234
namespace {
235
/// This class is a callback used internally to match function declarations with
236
/// the MOZ_CAN_RUN_SCRIPT annotation, adding these functions to the
237
/// can-run-script function set and making sure the functions they override (if
238
/// any) also have the annotation.
239
class FuncSetCallback : public MatchFinder::MatchCallback {
240
public:
241
FuncSetCallback(CanRunScriptChecker& Checker,
242
std::unordered_set<const FunctionDecl *> &FuncSet)
243
: CanRunScriptFuncs(FuncSet),
244
Checker(Checker) {}
245
246
void run(const MatchFinder::MatchResult &Result) override;
247
248
private:
249
/// This method checks the methods overriden by the given parameter.
250
void checkOverriddenMethods(const CXXMethodDecl *Method);
251
252
std::unordered_set<const FunctionDecl *> &CanRunScriptFuncs;
253
CanRunScriptChecker &Checker;
254
};
255
256
void FuncSetCallback::run(const MatchFinder::MatchResult &Result) {
257
const FunctionDecl *Func;
258
if (auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {
259
Func = Lambda->getCallOperator();
260
if (!Func || !hasCustomAttribute<moz_can_run_script>(Func))
261
return;
262
} else {
263
Func = Result.Nodes.getNodeAs<FunctionDecl>("canRunScriptFunction");
264
}
265
266
CanRunScriptFuncs.insert(Func);
267
268
// If this is a method, we check the methods it overrides.
269
if (auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
270
checkOverriddenMethods(Method);
271
}
272
}
273
274
void FuncSetCallback::checkOverriddenMethods(const CXXMethodDecl *Method) {
275
for (auto OverriddenMethod : Method->overridden_methods()) {
276
if (!hasCustomAttribute<moz_can_run_script>(OverriddenMethod)) {
277
const char *ErrorNonCanRunScriptOverridden =
278
"functions marked as MOZ_CAN_RUN_SCRIPT cannot override functions "
279
"that are not marked MOZ_CAN_RUN_SCRIPT";
280
const char* NoteNonCanRunScriptOverridden =
281
"overridden function declared here";
282
283
Checker.diag(Method->getLocation(), ErrorNonCanRunScriptOverridden,
284
DiagnosticIDs::Error);
285
Checker.diag(OverriddenMethod->getLocation(),
286
NoteNonCanRunScriptOverridden,
287
DiagnosticIDs::Note);
288
}
289
}
290
}
291
} // namespace
292
293
void CanRunScriptChecker::buildFuncSet(ASTContext *Context) {
294
// We create a match finder.
295
MatchFinder Finder;
296
// We create the callback which will be called when we find a function with
297
// a MOZ_CAN_RUN_SCRIPT annotation.
298
FuncSetCallback Callback(*this, CanRunScriptFuncs);
299
// We add the matcher to the finder, linking it to our callback.
300
Finder.addMatcher(
301
functionDecl(hasCanRunScriptAnnotation()).bind("canRunScriptFunction"),
302
&Callback);
303
Finder.addMatcher(
304
lambdaExpr().bind("lambda"),
305
&Callback);
306
// We start the analysis, given the ASTContext our main checker is in.
307
Finder.matchAST(*Context);
308
}
309
310
void CanRunScriptChecker::check(const MatchFinder::MatchResult &Result) {
311
312
// If the set of functions which can run script is not yet built, then build
313
// it.
314
if (!IsFuncSetBuilt) {
315
buildFuncSet(Result.Context);
316
IsFuncSetBuilt = true;
317
}
318
319
const char *ErrorInvalidArg =
320
"arguments must all be strong refs or caller's parameters when calling a "
321
"function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object "
322
"argument). '%0' is neither.";
323
324
const char *ErrorNonCanRunScriptParent =
325
"functions marked as MOZ_CAN_RUN_SCRIPT can only be called from "
326
"functions also marked as MOZ_CAN_RUN_SCRIPT";
327
const char *NoteNonCanRunScriptParent = "caller function declared here";
328
329
const Expr *InvalidArg;
330
if (const CXXDefaultArgExpr* defaultArg =
331
Result.Nodes.getNodeAs<CXXDefaultArgExpr>("invalidArg")) {
332
InvalidArg = defaultArg->getExpr();
333
} else {
334
InvalidArg = Result.Nodes.getNodeAs<Expr>("invalidArg");
335
}
336
337
const CallExpr *Call = Result.Nodes.getNodeAs<CallExpr>("callExpr");
338
// If we don't find the FunctionDecl linked to this call or if it's not marked
339
// as can-run-script, consider that we didn't find a match.
340
if (Call && (!Call->getDirectCallee() ||
341
!CanRunScriptFuncs.count(Call->getDirectCallee()))) {
342
Call = nullptr;
343
}
344
345
const CXXConstructExpr *Construct =
346
Result.Nodes.getNodeAs<CXXConstructExpr>("constructExpr");
347
348
// If we don't find the CXXConstructorDecl linked to this construct expression
349
// or if it's not marked as can-run-script, consider that we didn't find a
350
// match.
351
if (Construct && (!Construct->getConstructor() ||
352
!CanRunScriptFuncs.count(Construct->getConstructor()))) {
353
Construct = nullptr;
354
}
355
356
const FunctionDecl *ParentFunction =
357
Result.Nodes.getNodeAs<FunctionDecl>("nonCanRunScriptParentFunction");
358
// If the parent function can run script, consider that we didn't find a match
359
// because we only care about parent functions which can't run script.
360
//
361
// In addition, If the parent function is annotated as a
362
// CAN_RUN_SCRIPT_BOUNDARY, we don't want to complain about it calling a
363
// CAN_RUN_SCRIPT function. This is a mechanism to opt out of the infectious
364
// nature of CAN_RUN_SCRIPT which is necessary in some tricky code like
365
// Bindings.
366
if (ParentFunction &&
367
(CanRunScriptFuncs.count(ParentFunction) ||
368
hasCustomAttribute<moz_can_run_script_boundary>(ParentFunction))) {
369
ParentFunction = nullptr;
370
}
371
372
// Get the call range from either the CallExpr or the ConstructExpr.
373
SourceRange CallRange;
374
if (Call) {
375
CallRange = Call->getSourceRange();
376
} else if (Construct) {
377
CallRange = Construct->getSourceRange();
378
} else {
379
// If we have neither a Call nor a Construct, we have nothing do to here.
380
return;
381
}
382
383
// If we have an invalid argument in the call, we emit the diagnostic to
384
// signal it.
385
if (InvalidArg) {
386
const std::string invalidArgText =
387
Lexer::getSourceText(
388
CharSourceRange::getTokenRange(InvalidArg->getSourceRange()),
389
Result.Context->getSourceManager(),
390
Result.Context->getLangOpts());
391
diag(InvalidArg->getExprLoc(), ErrorInvalidArg, DiagnosticIDs::Error)
392
<< InvalidArg->getSourceRange() << invalidArgText;
393
}
394
395
// If the parent function is not marked as MOZ_CAN_RUN_SCRIPT, we emit an
396
// error and a not indicating it.
397
if (ParentFunction) {
398
assert(!hasCustomAttribute<moz_can_run_script>(ParentFunction) &&
399
"Matcher missed something");
400
401
diag(CallRange.getBegin(), ErrorNonCanRunScriptParent, DiagnosticIDs::Error)
402
<< CallRange;
403
404
diag(ParentFunction->getCanonicalDecl()->getLocation(),
405
NoteNonCanRunScriptParent, DiagnosticIDs::Note);
406
}
407
}