Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / frontend / NameFunctions.cpp
blob46b5bb074c8b67eb98b6549f07c6843da751f7c6
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/. */
7 #include "frontend/NameFunctions.h"
9 #include "mozilla/ScopeExit.h"
10 #include "mozilla/Sprintf.h"
12 #include "frontend/ParseNode.h"
13 #include "frontend/ParseNodeVisitor.h"
14 #include "frontend/ParserAtom.h" // ParserAtomsTable
15 #include "frontend/SharedContext.h"
16 #include "util/Poison.h"
17 #include "util/StringBuffer.h"
19 using namespace js;
20 using namespace js::frontend;
22 namespace {
24 class NameResolver : public ParseNodeVisitor<NameResolver> {
25 using Base = ParseNodeVisitor;
27 static const size_t MaxParents = 100;
29 FrontendContext* fc_;
30 ParserAtomsTable& parserAtoms_;
31 TaggedParserAtomIndex prefix_;
33 // Number of nodes in the parents array.
34 size_t nparents_;
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];
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_;
45 /* Test whether a ParseNode represents a function invocation */
46 bool isCall(ParseNode* pn) {
47 return pn && pn->isKind(ParseNodeKind::CallExpr);
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"]'.
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.
60 bool appendPropertyReference(TaggedParserAtomIndex name) {
61 if (parserAtoms_.isIdentifier(name)) {
62 return buf_.append('.') && buf_.append(parserAtoms_, name);
65 /* Quote the string as needed. */
66 UniqueChars source = parserAtoms_.toQuotedString(name);
67 if (!source) {
68 ReportOutOfMemory(fc_);
69 return false;
71 return buf_.append('[') &&
72 buf_.append(source.get(), strlen(source.get())) && buf_.append(']');
75 /* Append a number to buf_. */
76 bool appendNumber(double n) {
77 char number[30];
78 int digits = SprintfLiteral(number, "%g", n);
79 return buf_.append(number, digits);
82 // Append "[<n>]" to buf_, referencing a property named by a numeric literal.
83 bool appendNumericPropertyReference(double n) {
84 return buf_.append("[") && appendNumber(n) && buf_.append(']');
88 * Walk over the given ParseNode, attempting to convert it to a stringified
89 * name that represents where the function is being assigned to.
91 * |*foundName| is set to true if a name is found for the expression.
93 bool nameExpression(ParseNode* n, bool* foundName) {
94 switch (n->getKind()) {
95 case ParseNodeKind::ArgumentsLength:
96 case ParseNodeKind::DotExpr: {
97 PropertyAccess* prop = &n->as<PropertyAccess>();
98 if (!nameExpression(&prop->expression(), foundName)) {
99 return false;
101 if (!*foundName) {
102 return true;
104 return appendPropertyReference(prop->right()->as<NameNode>().atom());
107 case ParseNodeKind::Name:
108 case ParseNodeKind::PrivateName: {
109 *foundName = true;
110 return buf_.append(parserAtoms_, n->as<NameNode>().atom());
113 case ParseNodeKind::ThisExpr:
114 *foundName = true;
115 return buf_.append("this");
117 case ParseNodeKind::ElemExpr: {
118 PropertyByValue* elem = &n->as<PropertyByValue>();
119 if (!nameExpression(&elem->expression(), foundName)) {
120 return false;
122 if (!*foundName) {
123 return true;
125 if (!buf_.append('[') || !nameExpression(elem->right(), foundName)) {
126 return false;
128 if (!*foundName) {
129 return true;
131 return buf_.append(']');
134 case ParseNodeKind::NumberExpr:
135 *foundName = true;
136 return appendNumber(n->as<NumericLiteral>().value());
138 default:
139 // We're confused as to what to call this function.
140 *foundName = false;
141 return true;
146 * When naming an anonymous function, the process works loosely by walking
147 * up the AST and then translating that to a string. The stringification
148 * happens from some far-up assignment and then going back down the parse
149 * tree to the function definition point.
151 * This function will walk up the parse tree, gathering relevant nodes used
152 * for naming, and return the assignment node if there is one. The provided
153 * array and size will be filled in, and the returned node could be nullptr
154 * if no assignment is found. The first element of the array will be the
155 * innermost node relevant to naming, and the last element will be the
156 * outermost node.
158 ParseNode* gatherNameable(ParseNode** nameable, size_t* size) {
159 MOZ_ASSERT(nparents_ > 0);
160 MOZ_ASSERT(parents_[nparents_ - 1]->is<FunctionNode>());
162 *size = 0;
164 for (int pos = nparents_ - 2; pos >= 0; pos--) {
165 ParseNode* cur = parents_[pos];
166 if (cur->is<AssignmentNode>()) {
167 return cur;
170 switch (cur->getKind()) {
171 case ParseNodeKind::PrivateName:
172 case ParseNodeKind::Name:
173 return cur; // found the initialized declaration
174 case ParseNodeKind::ThisExpr:
175 return cur; // setting a property of 'this'
176 case ParseNodeKind::Function:
177 return nullptr; // won't find an assignment or declaration
179 case ParseNodeKind::ReturnStmt:
180 // Normally the relevant parent of a node is its direct parent, but
181 // sometimes with code like:
183 // var foo = (function() { return function() {}; })();
185 // the outer function is just a helper to create a scope for the
186 // returned function. Hence the name of the returned function should
187 // actually be 'foo'. This loop sees if the current node is a
188 // ParseNodeKind::Return, and if there is a direct function
189 // call we skip to that.
190 for (int tmp = pos - 1; tmp > 0; tmp--) {
191 if (isDirectCall(tmp, cur)) {
192 pos = tmp;
193 break;
195 if (isCall(cur)) {
196 // Don't skip too high in the tree.
197 break;
199 cur = parents_[tmp];
201 break;
203 case ParseNodeKind::PropertyDefinition:
204 case ParseNodeKind::Shorthand:
205 // Record the ParseNodeKind::PropertyDefinition/Shorthand but skip the
206 // ParseNodeKind::Object so we're not flagged as a contributor.
207 pos--;
208 [[fallthrough]];
210 default:
211 // Save any other nodes we encounter on the way up.
212 MOZ_ASSERT(*size < MaxParents);
213 nameable[(*size)++] = cur;
214 break;
218 return nullptr;
222 * Resolve the name of a function. If the function already has a name
223 * listed, then it is skipped. Otherwise an intelligent name is guessed to
224 * assign to the function's displayAtom field.
226 [[nodiscard]] bool resolveFun(FunctionNode* funNode,
227 TaggedParserAtomIndex* retId) {
228 MOZ_ASSERT(funNode != nullptr);
230 FunctionBox* funbox = funNode->funbox();
232 MOZ_ASSERT(buf_.empty());
233 auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); });
235 *retId = TaggedParserAtomIndex::null();
237 // If the function already has a name, use that.
238 if (funbox->displayAtom()) {
239 if (!prefix_) {
240 *retId = funbox->displayAtom();
241 return true;
243 if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/') ||
244 !buf_.append(parserAtoms_, funbox->displayAtom())) {
245 return false;
247 *retId = buf_.finishParserAtom(parserAtoms_, fc_);
248 return !!*retId;
251 // If a prefix is specified, then it is a form of namespace.
252 if (prefix_) {
253 if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/')) {
254 return false;
258 // Gather all nodes relevant to naming.
259 ParseNode* toName[MaxParents];
260 size_t size;
261 ParseNode* assignment = gatherNameable(toName, &size);
263 // If the function is assigned to something, then that is very relevant.
264 if (assignment) {
265 // e.g, foo = function() {}
266 if (assignment->is<AssignmentNode>()) {
267 assignment = assignment->as<AssignmentNode>().left();
269 bool foundName = false;
270 if (!nameExpression(assignment, &foundName)) {
271 return false;
273 if (!foundName) {
274 return true;
278 // Other than the actual assignment, other relevant nodes to naming are
279 // those in object initializers and then particular nodes marking a
280 // contribution.
281 for (int pos = size - 1; pos >= 0; pos--) {
282 ParseNode* node = toName[pos];
284 if (node->isKind(ParseNodeKind::PropertyDefinition) ||
285 node->isKind(ParseNodeKind::Shorthand)) {
286 ParseNode* left = node->as<BinaryNode>().left();
287 if (left->isKind(ParseNodeKind::ObjectPropertyName) ||
288 left->isKind(ParseNodeKind::StringExpr)) {
289 // Here we handle two cases:
290 // 1) ObjectPropertyName category, e.g `foo: function() {}`
291 // 2) StringExpr category, e.g `"foo": function() {}`
292 if (!appendPropertyReference(left->as<NameNode>().atom())) {
293 return false;
295 } else if (left->isKind(ParseNodeKind::NumberExpr)) {
296 // This case handles Number expression Anonymous Functions
297 // for example: `{ 10: function() {} }`.
298 if (!appendNumericPropertyReference(
299 left->as<NumericLiteral>().value())) {
300 return false;
302 } else if (left->isKind(ParseNodeKind::ComputedName) &&
303 (left->as<UnaryNode>().kid()->isKind(
304 ParseNodeKind::StringExpr) ||
305 left->as<UnaryNode>().kid()->isKind(
306 ParseNodeKind::NumberExpr)) &&
307 node->as<PropertyDefinition>().accessorType() ==
308 AccessorType::None) {
309 // In this branch we handle computed property with string
310 // or numeric literal:
311 // e.g, `{ ["foo"]: function(){} }`, and `{ [10]: function() {} }`.
313 // Note we only handle the names that are known at compile time,
314 // so if we have `var x = "foo"; ({ [x]: function(){} })`, we don't
315 // handle that here, it's handled at runtime by JSOp::SetFunName.
316 // The accessor type of the property must be AccessorType::None,
317 // given getters and setters need prefix and we cannot handle it here.
318 ParseNode* kid = left->as<UnaryNode>().kid();
319 if (kid->isKind(ParseNodeKind::StringExpr)) {
320 if (!appendPropertyReference(kid->as<NameNode>().atom())) {
321 return false;
323 } else {
324 MOZ_ASSERT(kid->isKind(ParseNodeKind::NumberExpr));
325 if (!appendNumericPropertyReference(
326 kid->as<NumericLiteral>().value())) {
327 return false;
330 } else {
331 MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName) ||
332 left->isKind(ParseNodeKind::BigIntExpr));
334 } else {
335 // Don't have consecutive '<' characters, and also don't start
336 // with a '<' character.
337 if (!buf_.empty() && buf_.getChar(buf_.length() - 1) != '<' &&
338 !buf_.append('<')) {
339 return false;
344 // functions which are "genuinely anonymous" but are contained in some
345 // other namespace are rather considered as "contributing" to the outer
346 // function, so give them a contribution symbol here.
347 if (!buf_.empty() && buf_.getChar(buf_.length() - 1) == '/' &&
348 !buf_.append('<')) {
349 return false;
352 if (buf_.empty()) {
353 return true;
356 *retId = buf_.finishParserAtom(parserAtoms_, fc_);
357 if (!*retId) {
358 return false;
361 // Skip assigning the guessed name if the function has a (dynamically)
362 // computed inferred name.
363 if (!funNode->isDirectRHSAnonFunction()) {
364 funbox->setGuessedAtom(*retId);
366 return true;
370 * Tests whether parents_[pos] is a function call whose callee is cur.
371 * This is the case for functions which do things like simply create a scope
372 * for new variables and then return an anonymous function using this scope.
374 bool isDirectCall(int pos, ParseNode* cur) {
375 return pos >= 0 && isCall(parents_[pos]) &&
376 parents_[pos]->as<BinaryNode>().left() == cur;
379 public:
380 [[nodiscard]] bool visitFunction(FunctionNode* pn) {
381 TaggedParserAtomIndex savedPrefix = prefix_;
382 TaggedParserAtomIndex newPrefix;
383 if (!resolveFun(pn, &newPrefix)) {
384 return false;
387 // If a function looks like (function(){})() where the parent node
388 // of the definition of the function is a call, then it shouldn't
389 // contribute anything to the namespace, so don't bother updating
390 // the prefix to whatever was returned.
391 if (!isDirectCall(nparents_ - 2, pn)) {
392 prefix_ = newPrefix;
395 bool ok = Base::visitFunction(pn);
397 prefix_ = savedPrefix;
398 return ok;
401 // Skip this type of node. It never contains functions.
402 [[nodiscard]] bool visitCallSiteObj(CallSiteNode* callSite) {
403 // This node only contains internal strings or undefined and an array -- no
404 // user-controlled expressions.
405 return true;
408 // Skip walking the list of string parts, which never contains functions.
409 [[nodiscard]] bool visitTaggedTemplateExpr(BinaryNode* taggedTemplate) {
410 ParseNode* tag = taggedTemplate->left();
412 // The leading expression, e.g. |tag| in |tag`foo`|,
413 // that might contain functions.
414 if (!visit(tag)) {
415 return false;
418 // The callsite object node is first. This node only contains
419 // internal strings or undefined and an array -- no user-controlled
420 // expressions.
421 CallSiteNode* element =
422 &taggedTemplate->right()->as<ListNode>().head()->as<CallSiteNode>();
423 #ifdef DEBUG
425 ListNode* rawNodes = &element->head()->as<ListNode>();
426 MOZ_ASSERT(rawNodes->isKind(ParseNodeKind::ArrayExpr));
427 for (ParseNode* raw : rawNodes->contents()) {
428 MOZ_ASSERT(raw->isKind(ParseNodeKind::TemplateStringExpr));
430 for (ParseNode* cooked : element->contentsFrom(rawNodes->pn_next)) {
431 MOZ_ASSERT(cooked->isKind(ParseNodeKind::TemplateStringExpr) ||
432 cooked->isKind(ParseNodeKind::RawUndefinedExpr));
435 #endif
437 // Next come any interpolated expressions in the tagged template.
438 ParseNode* interpolated = element->pn_next;
439 for (; interpolated; interpolated = interpolated->pn_next) {
440 if (!visit(interpolated)) {
441 return false;
445 return true;
448 private:
449 // Speed hack: this type of node can't contain functions, so skip walking it.
450 [[nodiscard]] bool internalVisitSpecList(ListNode* pn) {
451 // Import/export spec lists contain import/export specs containing only
452 // pairs of names or strings. Alternatively, an export spec list may
453 // contain a single export batch specifier.
454 #ifdef DEBUG
455 bool isImport = pn->isKind(ParseNodeKind::ImportSpecList);
456 ParseNode* item = pn->head();
457 if (!isImport && item && item->isKind(ParseNodeKind::ExportBatchSpecStmt)) {
458 MOZ_ASSERT(item->is<NullaryNode>());
459 } else {
460 for (ParseNode* item : pn->contents()) {
461 if (item->is<UnaryNode>()) {
462 auto* spec = &item->as<UnaryNode>();
463 MOZ_ASSERT(spec->isKind(isImport
464 ? ParseNodeKind::ImportNamespaceSpec
465 : ParseNodeKind::ExportNamespaceSpec));
466 MOZ_ASSERT(spec->kid()->isKind(ParseNodeKind::Name) ||
467 spec->kid()->isKind(ParseNodeKind::StringExpr));
468 } else {
469 auto* spec = &item->as<BinaryNode>();
470 MOZ_ASSERT(spec->isKind(isImport ? ParseNodeKind::ImportSpec
471 : ParseNodeKind::ExportSpec));
472 MOZ_ASSERT(spec->left()->isKind(ParseNodeKind::Name) ||
473 spec->left()->isKind(ParseNodeKind::StringExpr));
474 MOZ_ASSERT(spec->right()->isKind(ParseNodeKind::Name) ||
475 spec->right()->isKind(ParseNodeKind::StringExpr));
479 #endif
480 return true;
483 public:
484 [[nodiscard]] bool visitImportSpecList(ListNode* pn) {
485 return internalVisitSpecList(pn);
488 [[nodiscard]] bool visitExportSpecList(ListNode* pn) {
489 return internalVisitSpecList(pn);
492 NameResolver(FrontendContext* fc, ParserAtomsTable& parserAtoms)
493 : ParseNodeVisitor(fc),
494 fc_(fc),
495 parserAtoms_(parserAtoms),
496 nparents_(0),
497 buf_(fc) {}
500 * Resolve names for all anonymous functions in the given ParseNode tree.
502 [[nodiscard]] bool visit(ParseNode* pn) {
503 // Push pn to the parse node stack.
504 if (nparents_ >= MaxParents) {
505 // Silently skip very deeply nested functions.
506 return true;
508 auto initialParents = nparents_;
509 parents_[initialParents] = pn;
510 nparents_++;
512 bool ok = Base::visit(pn);
514 // Pop pn from the parse node stack.
515 nparents_--;
516 MOZ_ASSERT(initialParents == nparents_, "nparents imbalance detected");
517 MOZ_ASSERT(parents_[initialParents] == pn,
518 "pushed child shouldn't change underneath us");
519 AlwaysPoison(&parents_[initialParents], JS_OOB_PARSE_NODE_PATTERN,
520 sizeof(parents_[initialParents]), MemCheckKind::MakeUndefined);
522 return ok;
526 } /* anonymous namespace */
528 bool frontend::NameFunctions(FrontendContext* fc, ParserAtomsTable& parserAtoms,
529 ParseNode* pn) {
530 NameResolver nr(fc, parserAtoms);
531 return nr.visit(pn);