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 "CanRunScriptChecker.h"
6 #include "CustomMatchers.h"
7 
registerMatchers(MatchFinder * AstMatcher)8 void CanRunScriptChecker::registerMatchers(MatchFinder *AstMatcher) {
9   auto InvalidArg =
10       // We want to find any expression,
11       ignoreTrivials(expr(
12           // which has a refcounted pointer type,
13           hasType(pointerType(
14               pointee(hasDeclaration(cxxRecordDecl(isRefCounted()))))),
15           // and which is not this,
16           unless(cxxThisExpr()),
17           // and which is not a method call on a smart ptr,
18           unless(cxxMemberCallExpr(on(hasType(isSmartPtrToRefCounted())))),
19           // and which is not a parameter of the parent function,
20           unless(declRefExpr(to(parmVarDecl()))),
21           // and which is not a MOZ_KnownLive wrapped value.
22           unless(callExpr(callee(functionDecl(hasName("MOZ_KnownLive"))))),
23           expr().bind("invalidArg")));
24 
25   auto OptionalInvalidExplicitArg = anyOf(
26       // We want to find any argument which is invalid.
27       hasAnyArgument(InvalidArg),
28 
29       // This makes this matcher optional.
30       anything());
31 
32   // Please note that the hasCanRunScriptAnnotation() matchers are not present
33   // directly in the cxxMemberCallExpr, callExpr and constructExpr matchers
34   // because we check that the corresponding functions can run script later in
35   // the checker code.
36   AstMatcher->addMatcher(
37       expr(
38           anyOf(
39               // We want to match a method call expression,
40               cxxMemberCallExpr(
41                   // which optionally has an invalid arg,
42                   OptionalInvalidExplicitArg,
43                   // or which optionally has an invalid implicit this argument,
44                   anyOf(
45                       // which derefs into an invalid arg,
46                       on(cxxOperatorCallExpr(
47                           anyOf(hasAnyArgument(InvalidArg), anything()))),
48                       // or is an invalid arg.
49                       on(InvalidArg),
50 
51                       anything()),
52                   expr().bind("callExpr")),
53               // or a regular call expression,
54               callExpr(
55                   // which optionally has an invalid arg.
56                   OptionalInvalidExplicitArg, expr().bind("callExpr")),
57               // or a construct expression,
58               cxxConstructExpr(
59                   // which optionally has an invalid arg.
60                   OptionalInvalidExplicitArg, expr().bind("constructExpr"))),
61 
62           anyOf(
63               // We want to match the parent function.
64               forFunction(functionDecl().bind("nonCanRunScriptParentFunction")),
65 
66               // ... optionally.
67               anything())),
68       this);
69 }
70 
onStartOfTranslationUnit()71 void CanRunScriptChecker::onStartOfTranslationUnit() {
72   IsFuncSetBuilt = false;
73   CanRunScriptFuncs.clear();
74 }
75 
76 namespace {
77 /// This class is a callback used internally to match function declarations
78 /// with the MOZ_CAN_RUN_SCRIPT annotation, adding these functions and all
79 /// the methods they override to the can-run-script function set.
80 class FuncSetCallback : public MatchFinder::MatchCallback {
81 public:
FuncSetCallback(std::unordered_set<const FunctionDecl * > & FuncSet)82   FuncSetCallback(std::unordered_set<const FunctionDecl *> &FuncSet)
83       : CanRunScriptFuncs(FuncSet) {}
84 
85   void run(const MatchFinder::MatchResult &Result) override;
86 
87 private:
88   /// This method recursively adds all the methods overriden by the given
89   /// paremeter.
90   void addAllOverriddenMethodsRecursively(const CXXMethodDecl *Method);
91 
92   std::unordered_set<const FunctionDecl *> &CanRunScriptFuncs;
93 };
94 
run(const MatchFinder::MatchResult & Result)95 void FuncSetCallback::run(const MatchFinder::MatchResult &Result) {
96   const FunctionDecl *Func;
97   if (auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {
98     Func = Lambda->getCallOperator();
99     if (!Func || !hasCustomAnnotation(Func, "moz_can_run_script"))
100       return;
101   } else {
102     Func = Result.Nodes.getNodeAs<FunctionDecl>("canRunScriptFunction");
103   }
104 
105   CanRunScriptFuncs.insert(Func);
106 
107   // If this is a method, we check the methods it overrides.
108   if (auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
109     addAllOverriddenMethodsRecursively(Method);
110   }
111 }
112 
addAllOverriddenMethodsRecursively(const CXXMethodDecl * Method)113 void FuncSetCallback::addAllOverriddenMethodsRecursively(
114     const CXXMethodDecl *Method) {
115   for (auto OverriddenMethod : Method->overridden_methods()) {
116     CanRunScriptFuncs.insert(OverriddenMethod);
117 
118     // If this is not the definition, we also add the definition (if it
119     // exists) to the set.
120     if (!OverriddenMethod->isThisDeclarationADefinition()) {
121       if (auto Def = OverriddenMethod->getDefinition()) {
122         CanRunScriptFuncs.insert(Def);
123       }
124     }
125 
126     addAllOverriddenMethodsRecursively(OverriddenMethod);
127   }
128 }
129 } // namespace
130 
buildFuncSet(ASTContext * Context)131 void CanRunScriptChecker::buildFuncSet(ASTContext *Context) {
132   // We create a match finder.
133   MatchFinder Finder;
134   // We create the callback which will be called when we find a function with
135   // a MOZ_CAN_RUN_SCRIPT annotation.
136   FuncSetCallback Callback(CanRunScriptFuncs);
137   // We add the matcher to the finder, linking it to our callback.
138   Finder.addMatcher(
139       functionDecl(hasCanRunScriptAnnotation()).bind("canRunScriptFunction"),
140       &Callback);
141   Finder.addMatcher(
142       lambdaExpr().bind("lambda"),
143       &Callback);
144   // We start the analysis, given the ASTContext our main checker is in.
145   Finder.matchAST(*Context);
146 }
147 
check(const MatchFinder::MatchResult & Result)148 void CanRunScriptChecker::check(const MatchFinder::MatchResult &Result) {
149 
150   // If the set of functions which can run script is not yet built, then build
151   // it.
152   if (!IsFuncSetBuilt) {
153     buildFuncSet(Result.Context);
154     IsFuncSetBuilt = true;
155   }
156 
157   const char *ErrorInvalidArg =
158       "arguments must all be strong refs or parent parameters when calling a "
159       "function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object "
160       "argument)";
161 
162   const char *ErrorNonCanRunScriptParent =
163       "functions marked as MOZ_CAN_RUN_SCRIPT can only be called from "
164       "functions also marked as MOZ_CAN_RUN_SCRIPT";
165   const char *NoteNonCanRunScriptParent = "parent function declared here";
166 
167   const Expr *InvalidArg = Result.Nodes.getNodeAs<Expr>("invalidArg");
168 
169   const CallExpr *Call = Result.Nodes.getNodeAs<CallExpr>("callExpr");
170   // If we don't find the FunctionDecl linked to this call or if it's not marked
171   // as can-run-script, consider that we didn't find a match.
172   if (Call && (!Call->getDirectCallee() ||
173                !CanRunScriptFuncs.count(Call->getDirectCallee()))) {
174     Call = nullptr;
175   }
176 
177   const CXXConstructExpr *Construct =
178       Result.Nodes.getNodeAs<CXXConstructExpr>("constructExpr");
179 
180   // If we don't find the CXXConstructorDecl linked to this construct expression
181   // or if it's not marked as can-run-script, consider that we didn't find a
182   // match.
183   if (Construct && (!Construct->getConstructor() ||
184                     !CanRunScriptFuncs.count(Construct->getConstructor()))) {
185     Construct = nullptr;
186   }
187 
188   const FunctionDecl *ParentFunction =
189       Result.Nodes.getNodeAs<FunctionDecl>("nonCanRunScriptParentFunction");
190   // If the parent function can run script, consider that we didn't find a match
191   // because we only care about parent functions which can't run script.
192   //
193   // In addition, If the parent function is annotated as a
194   // CAN_RUN_SCRIPT_BOUNDARY, we don't want to complain about it calling a
195   // CAN_RUN_SCRIPT function. This is a mechanism to opt out of the infectious
196   // nature of CAN_RUN_SCRIPT which is necessary in some tricky code like
197   // Bindings.
198   if (ParentFunction &&
199       (CanRunScriptFuncs.count(ParentFunction) ||
200        hasCustomAnnotation(ParentFunction, "moz_can_run_script_boundary"))) {
201     ParentFunction = nullptr;
202   }
203 
204   // Get the call range from either the CallExpr or the ConstructExpr.
205   SourceRange CallRange;
206   if (Call) {
207     CallRange = Call->getSourceRange();
208   } else if (Construct) {
209     CallRange = Construct->getSourceRange();
210   } else {
211     // If we have neither a Call nor a Construct, we have nothing do to here.
212     return;
213   }
214 
215   // If we have an invalid argument in the call, we emit the diagnostic to
216   // signal it.
217   if (InvalidArg) {
218     diag(CallRange.getBegin(), ErrorInvalidArg, DiagnosticIDs::Error)
219         << CallRange;
220   }
221 
222   // If the parent function is not marked as MOZ_CAN_RUN_SCRIPT, we emit an
223   // error and a not indicating it.
224   if (ParentFunction) {
225     assert(!hasCustomAnnotation(ParentFunction, "moz_can_run_script") &&
226            "Matcher missed something");
227 
228     diag(CallRange.getBegin(), ErrorNonCanRunScriptParent, DiagnosticIDs::Error)
229         << CallRange;
230 
231     diag(ParentFunction->getLocation(), NoteNonCanRunScriptParent,
232          DiagnosticIDs::Note);
233   }
234 }
235