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