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