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/. */
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.
11 * The approach is to attempt to enforce the following invariants on the call
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.
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.
26 * There are two known holes in invariant 1, apart from the
27 * MOZ_CAN_RUN_SCRIPT_BOUNDARY opt-out:
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
34 * https://bugzilla.mozilla.org/show_bug.cgi?id=1535523 tracks this.
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
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
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
48 * e) The argument is constexpr and therefore cannot disappear.
51 #include "CanRunScriptChecker.h"
52 #include "CustomMatchers.h"
53 #include "clang/Lex/Lexer.h"
55 void CanRunScriptChecker::registerMatchers(MatchFinder
*AstMatcher
) {
56 auto Refcounted
= qualType(hasDeclaration(cxxRecordDecl(isRefCounted())));
57 auto StackSmartPtr
= ignoreTrivials(declRefExpr(to(varDecl(
58 hasAutomaticStorageDuration(), hasType(isSmartPtrToRefCounted())))));
59 auto ConstMemberOfThisSmartPtr
=
60 memberExpr(hasType(isSmartPtrToRefCounted()), hasType(isConstQualified()),
61 hasObjectExpression(cxxThisExpr()));
62 // A smartptr can be known-live for three reasons:
63 // 1) It's declared on the stack.
64 // 2) It's a const member of "this". We know "this" is alive (recursively)
65 // and const members can't change their value hence can't drop their
66 // reference until "this" gets destroyed.
67 // 3) It's an immediate temporary being constructed at the point where the
69 auto KnownLiveSmartPtr
= anyOf(
70 StackSmartPtr
, ConstMemberOfThisSmartPtr
,
71 ignoreTrivials(cxxConstructExpr(hasType(isSmartPtrToRefCounted()))));
73 auto MozKnownLiveCall
=
74 ignoreTrivials(callExpr(callee(functionDecl(hasName("MOZ_KnownLive")))));
76 // Params of the calling function are presumed live, because it itself should
77 // be MOZ_CAN_RUN_SCRIPT. Note that this is subject to
78 // https://bugzilla.mozilla.org/show_bug.cgi?id=1537656 at the moment.
79 auto KnownLiveParam
= anyOf(
82 // A parameter of the calling function is OK.
83 declRefExpr(to(parmVarDecl())));
85 auto KnownLiveMemberOfParam
=
86 memberExpr(hasKnownLiveAnnotation(),
87 hasObjectExpression(anyOf(
88 ignoreTrivials(KnownLiveParam
),
89 declRefExpr(to(varDecl(hasAutomaticStorageDuration()))))));
91 // A matcher that matches various things that are known to be live directly,
92 // without making any assumptions about operators.
93 auto KnownLiveBaseExceptRef
= anyOf(
94 // Things that are known to be a stack or immutable refptr.
96 // MOZ_KnownLive() calls.
98 // Params of the caller function.
100 // Members of the params that are marked as MOZ_KNOWN_LIVE
101 KnownLiveMemberOfParam
,
103 declRefExpr(to(varDecl(isConstexpr()))));
105 // A reference of smart ptr which is initialized with known live thing is OK.
106 // FIXME: This does not allow nested references.
107 auto RefToKnownLivePtr
= ignoreTrivials(declRefExpr(to(varDecl(
108 hasAutomaticStorageDuration(), hasType(referenceType()),
109 hasInitializer(anyOf(
110 KnownLiveSmartPtr
, KnownLiveParam
, KnownLiveMemberOfParam
,
112 hasFalseExpression(ignoreTrivials(anyOf(
113 KnownLiveSmartPtr
, KnownLiveParam
, KnownLiveMemberOfParam
,
114 declRefExpr(to(varDecl(isConstexpr()))),
115 // E.g., for RefPtr<T>::operator*()
117 hasOverloadedOperatorName("*"),
119 anyOf(KnownLiveBaseExceptRef
,
120 ignoreTrivials(KnownLiveMemberOfParam
))),
123 unaryOperator(unaryDereferenceOperator(),
125 ignoreTrivials(KnownLiveBaseExceptRef
)))))),
126 hasTrueExpression(ignoreTrivials(anyOf(
127 KnownLiveSmartPtr
, KnownLiveParam
, KnownLiveMemberOfParam
,
128 declRefExpr(to(varDecl(isConstexpr()))),
129 // E.g., for RefPtr<T>::operator*()
131 hasOverloadedOperatorName("*"),
133 anyOf(KnownLiveBaseExceptRef
,
134 ignoreTrivials(KnownLiveMemberOfParam
))),
137 unaryOperator(unaryDereferenceOperator(),
138 hasUnaryOperand(ignoreTrivials(
139 KnownLiveBaseExceptRef
)))))))))))));
141 // A matcher that matches various things that are known to be live directly,
142 // without making any assumptions about operators.
144 anyOf(KnownLiveBaseExceptRef
,
145 // Smart pointer refs initialized with known live smart ptrs.
148 // A matcher that matches various known-live things that don't involve
149 // non-unary operators.
150 auto KnownLiveSimple
= anyOf(
151 // Things that are just known live.
153 // Method calls on a live things that are smart ptrs. Note that we don't
154 // want to allow general method calls on live things, because those can
155 // return non-live objects (e.g. consider "live_pointer->foo()" as an
156 // example). For purposes of this analysis we are assuming the method
157 // calls on smart ptrs all just return the pointer inside,
159 on(anyOf(allOf(hasType(isSmartPtrToRefCounted()), KnownLiveBase
),
160 // Allow it if calling a member method which is marked as
162 KnownLiveMemberOfParam
))),
163 // operator* or operator-> on a thing that is already known to be live.
165 hasAnyOverloadedOperatorName("*", "->"),
167 anyOf(KnownLiveBase
, ignoreTrivials(KnownLiveMemberOfParam
))),
169 // A dereference on a thing that is known to be live. This is _not_
170 // caught by the "operator* or operator->" clause above, because
171 // cxxOperatorCallExpr() only catches cases when a class defines
172 // operator*. The default (built-in) operator* matches unaryOperator()
175 unaryDereferenceOperator(),
177 // If we're doing *someArg, the argument of the dereference is an
178 // ImplicitCastExpr LValueToRValue which has the DeclRefExpr as an
179 // argument. We could try to match that explicitly with a custom
180 // matcher (none of the built-in matchers seem to match on the
181 // thing being cast for an implicitCastExpr), but it's simpler to
182 // just use ignoreTrivials to strip off the cast.
183 ignoreTrivials(KnownLiveBase
))),
184 // Taking a pointer to a live reference. We explicitly want to exclude
185 // things that are not of type reference-to-refcounted or type refcounted,
186 // because if someone takes a pointer to a pointer to refcounted or a
187 // pointer to a smart ptr and passes those in to a callee that definitely
188 // does not guarantee liveness; in fact the callee could modify those
189 // things! In practice they would be the wrong type anyway, though, so
190 // it's hard to add a test for this.
191 unaryOperator(hasOperatorName("&"),
192 hasUnaryOperand(allOf(anyOf(hasType(references(Refcounted
)),
193 hasType(Refcounted
)),
194 ignoreTrivials(KnownLiveBase
)))));
196 auto KnownLive
= anyOf(
197 // Anything above, of course.
199 // Conditional operators where both arms are live.
200 conditionalOperator(hasFalseExpression(ignoreTrivials(KnownLiveSimple
)),
201 hasTrueExpression(ignoreTrivials(KnownLiveSimple
)))
202 // We're not handling cases like a dereference of a conditional operator,
203 // mostly because handling a dereference in general is so ugly. I
204 // _really_ wish I could just write a recursive matcher here easily.
207 auto InvalidArg
= ignoreTrivialsConditional(
208 // We want to consider things if there is anything refcounted involved,
209 // including in any of the trivials that we otherwise strip off.
210 anyOf(hasType(Refcounted
), hasType(pointsTo(Refcounted
)),
211 hasType(references(Refcounted
)), hasType(isSmartPtrToRefCounted())),
212 // We want to find any expression,
214 // which is not known live,
216 // and which is not a default arg with value nullptr, since those are
218 unless(cxxDefaultArgExpr(isNullDefaultArg())),
219 // and which is not a literal nullptr,
220 unless(cxxNullPtrLiteralExpr()), expr().bind("invalidArg")));
222 // A matcher which will mark the first invalid argument it finds invalid, but
223 // will always match, even if it finds no invalid arguments, so it doesn't
224 // preclude other matchers from running and maybe finding invalid args.
225 auto OptionalInvalidExplicitArg
= anyOf(
226 // We want to find any argument which is invalid.
227 hasAnyArgument(InvalidArg
),
229 // This makes this matcher optional.
232 // Please note that the hasCanRunScriptAnnotation() matchers are not present
233 // directly in the cxxMemberCallExpr, callExpr and constructExpr matchers
234 // because we check that the corresponding functions can run script later in
236 AstMatcher
->addMatcher(
239 // We want to match a method call expression,
241 // which optionally has an invalid arg,
242 OptionalInvalidExplicitArg
,
243 // or which optionally has an invalid this argument,
244 anyOf(on(InvalidArg
), anything()), expr().bind("callExpr")),
245 // or a regular call expression,
247 // which optionally has an invalid arg.
248 OptionalInvalidExplicitArg
, expr().bind("callExpr")),
249 // or a construct expression,
251 // which optionally has an invalid arg.
252 OptionalInvalidExplicitArg
, expr().bind("constructExpr"))),
255 // We want to match the parent function.
256 forFunction(functionDecl().bind("nonCanRunScriptParentFunction")),
263 void CanRunScriptChecker::onStartOfTranslationUnit() {
264 IsFuncSetBuilt
= false;
265 CanRunScriptFuncs
.clear();
269 /// This class is a callback used internally to match function declarations with
270 /// the MOZ_CAN_RUN_SCRIPT annotation, adding these functions to the
271 /// can-run-script function set and making sure the functions they override (if
272 /// any) also have the annotation.
273 class FuncSetCallback
: public MatchFinder::MatchCallback
{
275 FuncSetCallback(CanRunScriptChecker
&Checker
,
276 std::unordered_set
<const FunctionDecl
*> &FuncSet
)
277 : CanRunScriptFuncs(FuncSet
), Checker(Checker
) {}
279 void run(const MatchFinder::MatchResult
&Result
) override
;
282 /// This method checks the methods overriden by the given parameter.
283 void checkOverriddenMethods(const CXXMethodDecl
*Method
);
285 std::unordered_set
<const FunctionDecl
*> &CanRunScriptFuncs
;
286 CanRunScriptChecker
&Checker
;
289 void FuncSetCallback::run(const MatchFinder::MatchResult
&Result
) {
290 const FunctionDecl
*Func
;
291 if (auto *Lambda
= Result
.Nodes
.getNodeAs
<LambdaExpr
>("lambda")) {
292 Func
= Lambda
->getCallOperator();
293 if (!Func
|| !hasCustomAttribute
<moz_can_run_script
>(Func
))
296 Func
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("canRunScriptFunction");
298 const char *ErrorAttrInDefinition
=
299 "MOZ_CAN_RUN_SCRIPT must be put in front "
300 "of the declaration, not the definition";
301 const char *NoteAttrInDefinition
= "The first declaration exists here";
302 if (!Func
->isFirstDecl() &&
303 !hasCustomAttribute
<moz_can_run_script_for_definition
>(Func
)) {
304 const FunctionDecl
*FirstDecl
= Func
->getFirstDecl();
305 if (!hasCustomAttribute
<moz_can_run_script
>(FirstDecl
)) {
306 Checker
.diag(Func
->getLocation(), ErrorAttrInDefinition
,
307 DiagnosticIDs::Error
);
308 Checker
.diag(FirstDecl
->getLocation(), NoteAttrInDefinition
,
309 DiagnosticIDs::Note
);
314 CanRunScriptFuncs
.insert(Func
);
316 // If this is a method, we check the methods it overrides.
317 if (auto *Method
= dyn_cast
<CXXMethodDecl
>(Func
)) {
318 checkOverriddenMethods(Method
);
322 void FuncSetCallback::checkOverriddenMethods(const CXXMethodDecl
*Method
) {
323 for (auto OverriddenMethod
: Method
->overridden_methods()) {
324 if (!hasCustomAttribute
<moz_can_run_script
>(OverriddenMethod
)) {
325 const char *ErrorNonCanRunScriptOverridden
=
326 "functions marked as MOZ_CAN_RUN_SCRIPT cannot override functions "
327 "that are not marked MOZ_CAN_RUN_SCRIPT";
328 const char *NoteNonCanRunScriptOverridden
=
329 "overridden function declared here";
331 Checker
.diag(Method
->getLocation(), ErrorNonCanRunScriptOverridden
,
332 DiagnosticIDs::Error
);
333 Checker
.diag(OverriddenMethod
->getLocation(),
334 NoteNonCanRunScriptOverridden
, DiagnosticIDs::Note
);
340 void CanRunScriptChecker::buildFuncSet(ASTContext
*Context
) {
341 // We create a match finder.
343 // We create the callback which will be called when we find a function with
344 // a MOZ_CAN_RUN_SCRIPT annotation.
345 FuncSetCallback
Callback(*this, CanRunScriptFuncs
);
346 // We add the matcher to the finder, linking it to our callback.
348 functionDecl(hasCanRunScriptAnnotation()).bind("canRunScriptFunction"),
350 Finder
.addMatcher(lambdaExpr().bind("lambda"), &Callback
);
351 // We start the analysis, given the ASTContext our main checker is in.
352 Finder
.matchAST(*Context
);
355 void CanRunScriptChecker::check(const MatchFinder::MatchResult
&Result
) {
357 // If the set of functions which can run script is not yet built, then build
359 if (!IsFuncSetBuilt
) {
360 buildFuncSet(Result
.Context
);
361 IsFuncSetBuilt
= true;
364 const char *ErrorInvalidArg
=
365 "arguments must all be strong refs or caller's parameters when calling a "
366 "function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object "
367 "argument). '%0' is neither.";
369 const char *ErrorNonCanRunScriptParent
=
370 "functions marked as MOZ_CAN_RUN_SCRIPT can only be called from "
371 "functions also marked as MOZ_CAN_RUN_SCRIPT";
372 const char *NoteNonCanRunScriptParent
= "caller function declared here";
374 const Expr
*InvalidArg
;
375 if (const CXXDefaultArgExpr
*defaultArg
=
376 Result
.Nodes
.getNodeAs
<CXXDefaultArgExpr
>("invalidArg")) {
377 InvalidArg
= defaultArg
->getExpr();
379 InvalidArg
= Result
.Nodes
.getNodeAs
<Expr
>("invalidArg");
382 const CallExpr
*Call
= Result
.Nodes
.getNodeAs
<CallExpr
>("callExpr");
383 // If we don't find the FunctionDecl linked to this call or if it's not marked
384 // as can-run-script, consider that we didn't find a match.
385 if (Call
&& (!Call
->getDirectCallee() ||
386 !CanRunScriptFuncs
.count(Call
->getDirectCallee()))) {
390 const CXXConstructExpr
*Construct
=
391 Result
.Nodes
.getNodeAs
<CXXConstructExpr
>("constructExpr");
393 // If we don't find the CXXConstructorDecl linked to this construct expression
394 // or if it's not marked as can-run-script, consider that we didn't find a
396 if (Construct
&& (!Construct
->getConstructor() ||
397 !CanRunScriptFuncs
.count(Construct
->getConstructor()))) {
401 const FunctionDecl
*ParentFunction
=
402 Result
.Nodes
.getNodeAs
<FunctionDecl
>("nonCanRunScriptParentFunction");
403 // If the parent function can run script, consider that we didn't find a match
404 // because we only care about parent functions which can't run script.
406 // In addition, If the parent function is annotated as a
407 // CAN_RUN_SCRIPT_BOUNDARY, we don't want to complain about it calling a
408 // CAN_RUN_SCRIPT function. This is a mechanism to opt out of the infectious
409 // nature of CAN_RUN_SCRIPT which is necessary in some tricky code like
411 if (ParentFunction
&&
412 (CanRunScriptFuncs
.count(ParentFunction
) ||
413 hasCustomAttribute
<moz_can_run_script_boundary
>(ParentFunction
))) {
414 ParentFunction
= nullptr;
417 // Get the call range from either the CallExpr or the ConstructExpr.
418 SourceRange CallRange
;
420 CallRange
= Call
->getSourceRange();
421 } else if (Construct
) {
422 CallRange
= Construct
->getSourceRange();
424 // If we have neither a Call nor a Construct, we have nothing do to here.
428 // If we have an invalid argument in the call, we emit the diagnostic to
431 const StringRef invalidArgText
= Lexer::getSourceText(
432 CharSourceRange::getTokenRange(InvalidArg
->getSourceRange()),
433 Result
.Context
->getSourceManager(), Result
.Context
->getLangOpts());
434 diag(InvalidArg
->getExprLoc(), ErrorInvalidArg
, DiagnosticIDs::Error
)
435 << InvalidArg
->getSourceRange() << invalidArgText
;
438 // If the parent function is not marked as MOZ_CAN_RUN_SCRIPT, we emit an
439 // error and a not indicating it.
440 if (ParentFunction
) {
441 assert(!hasCustomAttribute
<moz_can_run_script
>(ParentFunction
) &&
442 "Matcher missed something");
444 diag(CallRange
.getBegin(), ErrorNonCanRunScriptParent
, DiagnosticIDs::Error
)
447 diag(ParentFunction
->getCanonicalDecl()->getLocation(),
448 NoteNonCanRunScriptParent
, DiagnosticIDs::Note
);