1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "frontend/NameFunctions.h"
8
9 #include "mozilla/MemoryChecking.h"
10 #include "mozilla/ScopeExit.h"
11 #include "mozilla/Sprintf.h"
12
13 #include "frontend/BytecodeCompiler.h"
14 #include "frontend/ParseNode.h"
15 #include "frontend/ParseNodeVisitor.h"
16 #include "frontend/SharedContext.h"
17 #include "util/Poison.h"
18 #include "util/StringBuffer.h"
19 #include "vm/JSFunction.h"
20
21 using namespace js;
22 using namespace js::frontend;
23
24 namespace {
25
26 class NameResolver : public ParseNodeVisitor<NameResolver> {
27 using Base = ParseNodeVisitor;
28
29 static const size_t MaxParents = 100;
30
31 RootedAtom prefix_;
32
33 // Number of nodes in the parents array.
34 size_t nparents_;
35
36 // Stack of ParseNodes from the root to the current node.
37 // Only elements 0..nparents_ are initialized.
38 MOZ_INIT_OUTSIDE_CTOR
39 ParseNode* parents_[MaxParents];
40
41 // When naming a function, the buffer where the name is built.
42 // When we are not under resolveFun, buf_ is empty.
43 StringBuffer buf_;
44
45 /* Test whether a ParseNode represents a function invocation */
isCall(ParseNode * pn)46 bool isCall(ParseNode* pn) {
47 return pn && pn->isKind(ParseNodeKind::CallExpr);
48 }
49
50 /*
51 * Append a reference to a property named |name| to |buf_|. If |name| is
52 * a proper identifier name, then we append '.name'; otherwise, we
53 * append '["name"]'.
54 *
55 * Note that we need the IsIdentifier check for atoms from both
56 * ParseNodeKind::Name nodes and ParseNodeKind::String nodes:
57 * given code like a["b c"], the front end will produce a ParseNodeKind::Dot
58 * with a ParseNodeKind::Name child whose name contains spaces.
59 */
appendPropertyReference(JSAtom * name)60 bool appendPropertyReference(JSAtom* name) {
61 if (IsIdentifier(name)) {
62 return buf_.append('.') && buf_.append(name);
63 }
64
65 /* Quote the string as needed. */
66 UniqueChars source = QuoteString(cx_, name, '"');
67 return source && buf_.append('[') &&
68 buf_.append(source.get(), strlen(source.get())) && buf_.append(']');
69 }
70
71 /* Append a number to buf_. */
appendNumber(double n)72 bool appendNumber(double n) {
73 char number[30];
74 int digits = SprintfLiteral(number, "%g", n);
75 return buf_.append(number, digits);
76 }
77
78 // Append "[<n>]" to buf_, referencing a property named by a numeric literal.
appendNumericPropertyReference(double n)79 bool appendNumericPropertyReference(double n) {
80 return buf_.append("[") && appendNumber(n) && buf_.append(']');
81 }
82
83 /*
84 * Walk over the given ParseNode, attempting to convert it to a stringified
85 * name that respresents where the function is being assigned to.
86 *
87 * |*foundName| is set to true if a name is found for the expression.
88 */
nameExpression(ParseNode * n,bool * foundName)89 bool nameExpression(ParseNode* n, bool* foundName) {
90 switch (n->getKind()) {
91 case ParseNodeKind::DotExpr: {
92 PropertyAccess* prop = &n->as<PropertyAccess>();
93 if (!nameExpression(&prop->expression(), foundName)) {
94 return false;
95 }
96 if (!*foundName) {
97 return true;
98 }
99 return appendPropertyReference(prop->right()->as<NameNode>().atom());
100 }
101
102 case ParseNodeKind::Name:
103 case ParseNodeKind::PrivateName:
104 *foundName = true;
105 return buf_.append(n->as<NameNode>().atom());
106
107 case ParseNodeKind::ThisExpr:
108 *foundName = true;
109 return buf_.append("this");
110
111 case ParseNodeKind::ElemExpr: {
112 PropertyByValue* elem = &n->as<PropertyByValue>();
113 if (!nameExpression(&elem->expression(), foundName)) {
114 return false;
115 }
116 if (!*foundName) {
117 return true;
118 }
119 if (!buf_.append('[') || !nameExpression(elem->right(), foundName)) {
120 return false;
121 }
122 if (!*foundName) {
123 return true;
124 }
125 return buf_.append(']');
126 }
127
128 case ParseNodeKind::NumberExpr:
129 *foundName = true;
130 return appendNumber(n->as<NumericLiteral>().value());
131
132 default:
133 // We're confused as to what to call this function.
134 *foundName = false;
135 return true;
136 }
137 }
138
139 /*
140 * When naming an anonymous function, the process works loosely by walking
141 * up the AST and then translating that to a string. The stringification
142 * happens from some far-up assignment and then going back down the parse
143 * tree to the function definition point.
144 *
145 * This function will walk up the parse tree, gathering relevant nodes used
146 * for naming, and return the assignment node if there is one. The provided
147 * array and size will be filled in, and the returned node could be nullptr
148 * if no assignment is found. The first element of the array will be the
149 * innermost node relevant to naming, and the last element will be the
150 * outermost node.
151 */
gatherNameable(ParseNode ** nameable,size_t * size)152 ParseNode* gatherNameable(ParseNode** nameable, size_t* size) {
153 MOZ_ASSERT(nparents_ > 0);
154 MOZ_ASSERT(parents_[nparents_ - 1]->is<FunctionNode>());
155
156 *size = 0;
157
158 for (int pos = nparents_ - 2; pos >= 0; pos--) {
159 ParseNode* cur = parents_[pos];
160 if (cur->is<AssignmentNode>()) {
161 return cur;
162 }
163
164 switch (cur->getKind()) {
165 case ParseNodeKind::PrivateName:
166 case ParseNodeKind::Name:
167 return cur; // found the initialized declaration
168 case ParseNodeKind::ThisExpr:
169 return cur; // setting a property of 'this'
170 case ParseNodeKind::Function:
171 return nullptr; // won't find an assignment or declaration
172
173 case ParseNodeKind::ReturnStmt:
174 // Normally the relevant parent of a node is its direct parent, but
175 // sometimes with code like:
176 //
177 // var foo = (function() { return function() {}; })();
178 //
179 // the outer function is just a helper to create a scope for the
180 // returned function. Hence the name of the returned function should
181 // actually be 'foo'. This loop sees if the current node is a
182 // ParseNodeKind::Return, and if there is a direct function
183 // call we skip to that.
184 for (int tmp = pos - 1; tmp > 0; tmp--) {
185 if (isDirectCall(tmp, cur)) {
186 pos = tmp;
187 break;
188 }
189 if (isCall(cur)) {
190 // Don't skip too high in the tree.
191 break;
192 }
193 cur = parents_[tmp];
194 }
195 break;
196
197 case ParseNodeKind::PropertyDefinition:
198 case ParseNodeKind::Shorthand:
199 // Record the ParseNodeKind::PropertyDefinition/Shorthand but skip the
200 // ParseNodeKind::Object so we're not flagged as a contributor.
201 pos--;
202 [[fallthrough]];
203
204 default:
205 // Save any other nodes we encounter on the way up.
206 MOZ_ASSERT(*size < MaxParents);
207 nameable[(*size)++] = cur;
208 break;
209 }
210 }
211
212 return nullptr;
213 }
214
215 /*
216 * Resolve the name of a function. If the function already has a name
217 * listed, then it is skipped. Otherwise an intelligent name is guessed to
218 * assign to the function's displayAtom field.
219 */
resolveFun(FunctionNode * funNode,MutableHandleAtom retAtom)220 MOZ_MUST_USE bool resolveFun(FunctionNode* funNode,
221 MutableHandleAtom retAtom) {
222 MOZ_ASSERT(funNode != nullptr);
223 FunctionBox* funbox = funNode->funbox();
224
225 MOZ_ASSERT(buf_.empty());
226 auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); });
227
228 retAtom.set(nullptr);
229
230 // If the function already has a name, use that.
231 if (funbox->displayAtom() != nullptr) {
232 if (prefix_ == nullptr) {
233 retAtom.set(funbox->displayAtom());
234 return true;
235 }
236 if (!buf_.append(prefix_) || !buf_.append('/') ||
237 !buf_.append(funbox->displayAtom())) {
238 return false;
239 }
240 retAtom.set(buf_.finishAtom());
241 return !!retAtom;
242 }
243
244 // If a prefix is specified, then it is a form of namespace.
245 if (prefix_ != nullptr) {
246 if (!buf_.append(prefix_) || !buf_.append('/')) {
247 return false;
248 }
249 }
250
251 // Gather all nodes relevant to naming.
252 ParseNode* toName[MaxParents];
253 size_t size;
254 ParseNode* assignment = gatherNameable(toName, &size);
255
256 // If the function is assigned to something, then that is very relevant.
257 if (assignment) {
258 if (assignment->is<AssignmentNode>()) {
259 assignment = assignment->as<AssignmentNode>().left();
260 }
261 bool foundName = false;
262 if (!nameExpression(assignment, &foundName)) {
263 return false;
264 }
265 if (!foundName) {
266 return true;
267 }
268 }
269
270 // Other than the actual assignment, other relevant nodes to naming are
271 // those in object initializers and then particular nodes marking a
272 // contribution.
273 for (int pos = size - 1; pos >= 0; pos--) {
274 ParseNode* node = toName[pos];
275
276 if (node->isKind(ParseNodeKind::PropertyDefinition) ||
277 node->isKind(ParseNodeKind::Shorthand)) {
278 ParseNode* left = node->as<BinaryNode>().left();
279 if (left->isKind(ParseNodeKind::ObjectPropertyName) ||
280 left->isKind(ParseNodeKind::StringExpr)) {
281 if (!appendPropertyReference(left->as<NameNode>().atom())) {
282 return false;
283 }
284 } else if (left->isKind(ParseNodeKind::NumberExpr)) {
285 if (!appendNumericPropertyReference(
286 left->as<NumericLiteral>().value())) {
287 return false;
288 }
289 } else {
290 MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName) ||
291 left->isKind(ParseNodeKind::BigIntExpr));
292 }
293 } else {
294 // Don't have consecutive '<' characters, and also don't start
295 // with a '<' character.
296 if (!buf_.empty() && buf_.getChar(buf_.length() - 1) != '<' &&
297 !buf_.append('<')) {
298 return false;
299 }
300 }
301 }
302
303 // functions which are "genuinely anonymous" but are contained in some
304 // other namespace are rather considered as "contributing" to the outer
305 // function, so give them a contribution symbol here.
306 if (!buf_.empty() && buf_.getChar(buf_.length() - 1) == '/' &&
307 !buf_.append('<')) {
308 return false;
309 }
310
311 if (buf_.empty()) {
312 return true;
313 }
314
315 retAtom.set(buf_.finishAtom());
316 if (!retAtom) {
317 return false;
318 }
319
320 // Skip assigning the guessed name if the function has a (dynamically)
321 // computed inferred name.
322 if (!funNode->isDirectRHSAnonFunction()) {
323 funbox->setGuessedAtom(retAtom);
324 }
325 return true;
326 }
327
328 /*
329 * Tests whether parents_[pos] is a function call whose callee is cur.
330 * This is the case for functions which do things like simply create a scope
331 * for new variables and then return an anonymous function using this scope.
332 */
isDirectCall(int pos,ParseNode * cur)333 bool isDirectCall(int pos, ParseNode* cur) {
334 return pos >= 0 && isCall(parents_[pos]) &&
335 parents_[pos]->as<BinaryNode>().left() == cur;
336 }
337
338 public:
visitFunction(FunctionNode * pn)339 MOZ_MUST_USE bool visitFunction(FunctionNode* pn) {
340 RootedAtom savedPrefix(cx_, prefix_);
341 RootedAtom newPrefix(cx_);
342 if (!resolveFun(pn, &newPrefix)) {
343 return false;
344 }
345
346 // If a function looks like (function(){})() where the parent node
347 // of the definition of the function is a call, then it shouldn't
348 // contribute anything to the namespace, so don't bother updating
349 // the prefix to whatever was returned.
350 if (!isDirectCall(nparents_ - 2, pn)) {
351 prefix_ = newPrefix;
352 }
353
354 bool ok = Base::visitFunction(pn);
355
356 prefix_ = savedPrefix;
357 return ok;
358 }
359
360 // Skip this type of node. It never contains functions.
visitCallSiteObj(CallSiteNode * callSite)361 MOZ_MUST_USE bool visitCallSiteObj(CallSiteNode* callSite) {
362 // This node only contains internal strings or undefined and an array -- no
363 // user-controlled expressions.
364 return true;
365 }
366
367 // Skip walking the list of string parts, which never contains functions.
visitTaggedTemplateExpr(BinaryNode * taggedTemplate)368 MOZ_MUST_USE bool visitTaggedTemplateExpr(BinaryNode* taggedTemplate) {
369 ParseNode* tag = taggedTemplate->left();
370
371 // The leading expression, e.g. |tag| in |tag`foo`|,
372 // that might contain functions.
373 if (!visit(tag)) {
374 return false;
375 }
376
377 // The callsite object node is first. This node only contains
378 // internal strings or undefined and an array -- no user-controlled
379 // expressions.
380 CallSiteNode* element =
381 &taggedTemplate->right()->as<ListNode>().head()->as<CallSiteNode>();
382 #ifdef DEBUG
383 {
384 ListNode* rawNodes = &element->head()->as<ListNode>();
385 MOZ_ASSERT(rawNodes->isKind(ParseNodeKind::ArrayExpr));
386 for (ParseNode* raw : rawNodes->contents()) {
387 MOZ_ASSERT(raw->isKind(ParseNodeKind::TemplateStringExpr));
388 }
389 for (ParseNode* cooked : element->contentsFrom(rawNodes->pn_next)) {
390 MOZ_ASSERT(cooked->isKind(ParseNodeKind::TemplateStringExpr) ||
391 cooked->isKind(ParseNodeKind::RawUndefinedExpr));
392 }
393 }
394 #endif
395
396 // Next come any interpolated expressions in the tagged template.
397 ParseNode* interpolated = element->pn_next;
398 for (; interpolated; interpolated = interpolated->pn_next) {
399 if (!visit(interpolated)) {
400 return false;
401 }
402 }
403
404 return true;
405 }
406
407 private:
408 // Speed hack: this type of node can't contain functions, so skip walking it.
internalVisitSpecList(ListNode * pn)409 MOZ_MUST_USE bool internalVisitSpecList(ListNode* pn) {
410 // Import/export spec lists contain import/export specs containing
411 // only pairs of names. Alternatively, an export spec list may
412 // contain a single export batch specifier.
413 #ifdef DEBUG
414 bool isImport = pn->isKind(ParseNodeKind::ImportSpecList);
415 ParseNode* item = pn->head();
416 if (!isImport && item && item->isKind(ParseNodeKind::ExportBatchSpecStmt)) {
417 MOZ_ASSERT(item->is<NullaryNode>());
418 } else {
419 for (ParseNode* item : pn->contents()) {
420 BinaryNode* spec = &item->as<BinaryNode>();
421 MOZ_ASSERT(spec->isKind(isImport ? ParseNodeKind::ImportSpec
422 : ParseNodeKind::ExportSpec));
423 MOZ_ASSERT(spec->left()->isKind(ParseNodeKind::Name));
424 MOZ_ASSERT(spec->right()->isKind(ParseNodeKind::Name));
425 }
426 }
427 #endif
428 return true;
429 }
430
431 public:
visitImportSpecList(ListNode * pn)432 MOZ_MUST_USE bool visitImportSpecList(ListNode* pn) {
433 return internalVisitSpecList(pn);
434 }
435
visitExportSpecList(ListNode * pn)436 MOZ_MUST_USE bool visitExportSpecList(ListNode* pn) {
437 return internalVisitSpecList(pn);
438 }
439
NameResolver(JSContext * cx)440 explicit NameResolver(JSContext* cx)
441 : ParseNodeVisitor(cx), prefix_(cx), nparents_(0), buf_(cx) {}
442
443 /*
444 * Resolve names for all anonymous functions in the given ParseNode tree.
445 */
visit(ParseNode * pn)446 MOZ_MUST_USE bool visit(ParseNode* pn) {
447 // Push pn to the parse node stack.
448 if (nparents_ >= MaxParents) {
449 // Silently skip very deeply nested functions.
450 return true;
451 }
452 auto initialParents = nparents_;
453 parents_[initialParents] = pn;
454 nparents_++;
455
456 bool ok = Base::visit(pn);
457
458 // Pop pn from the parse node stack.
459 nparents_--;
460 MOZ_ASSERT(initialParents == nparents_, "nparents imbalance detected");
461 MOZ_ASSERT(parents_[initialParents] == pn,
462 "pushed child shouldn't change underneath us");
463 AlwaysPoison(&parents_[initialParents], JS_OOB_PARSE_NODE_PATTERN,
464 sizeof(parents_[initialParents]), MemCheckKind::MakeUndefined);
465
466 return ok;
467 }
468 };
469
470 } /* anonymous namespace */
471
NameFunctions(JSContext * cx,ParseNode * pn)472 bool frontend::NameFunctions(JSContext* cx, ParseNode* pn) {
473 AutoTraceLog traceLog(TraceLoggerForCurrentThread(cx),
474 TraceLogger_BytecodeNameFunctions);
475 NameResolver nr(cx);
476 return nr.visit(pn);
477 }
478