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"
20 using namespace js::frontend
;
24 class NameResolver
: public ParseNodeVisitor
<NameResolver
> {
25 using Base
= ParseNodeVisitor
;
27 static const size_t MaxParents
= 100;
30 ParserAtomsTable
& parserAtoms_
;
31 TaggedParserAtomIndex prefix_
;
33 // Number of nodes in the parents array.
36 // Stack of ParseNodes from the root to the current node.
37 // Only elements 0..nparents_ are initialized.
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.
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
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
);
68 ReportOutOfMemory(fc_
);
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
) {
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
)) {
104 return appendPropertyReference(prop
->right()->as
<NameNode
>().atom());
107 case ParseNodeKind::Name
:
108 case ParseNodeKind::PrivateName
: {
110 return buf_
.append(parserAtoms_
, n
->as
<NameNode
>().atom());
113 case ParseNodeKind::ThisExpr
:
115 return buf_
.append("this");
117 case ParseNodeKind::ElemExpr
: {
118 PropertyByValue
* elem
= &n
->as
<PropertyByValue
>();
119 if (!nameExpression(&elem
->expression(), foundName
)) {
125 if (!buf_
.append('[') || !nameExpression(elem
->right(), foundName
)) {
131 return buf_
.append(']');
134 case ParseNodeKind::NumberExpr
:
136 return appendNumber(n
->as
<NumericLiteral
>().value());
139 // We're confused as to what to call this function.
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
158 ParseNode
* gatherNameable(ParseNode
** nameable
, size_t* size
) {
159 MOZ_ASSERT(nparents_
> 0);
160 MOZ_ASSERT(parents_
[nparents_
- 1]->is
<FunctionNode
>());
164 for (int pos
= nparents_
- 2; pos
>= 0; pos
--) {
165 ParseNode
* cur
= parents_
[pos
];
166 if (cur
->is
<AssignmentNode
>()) {
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
)) {
196 // Don't skip too high in the tree.
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.
211 // Save any other nodes we encounter on the way up.
212 MOZ_ASSERT(*size
< MaxParents
);
213 nameable
[(*size
)++] = cur
;
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()) {
240 *retId
= funbox
->displayAtom();
243 if (!buf_
.append(parserAtoms_
, prefix_
) || !buf_
.append('/') ||
244 !buf_
.append(parserAtoms_
, funbox
->displayAtom())) {
247 *retId
= buf_
.finishParserAtom(parserAtoms_
, fc_
);
251 // If a prefix is specified, then it is a form of namespace.
253 if (!buf_
.append(parserAtoms_
, prefix_
) || !buf_
.append('/')) {
258 // Gather all nodes relevant to naming.
259 ParseNode
* toName
[MaxParents
];
261 ParseNode
* assignment
= gatherNameable(toName
, &size
);
263 // If the function is assigned to something, then that is very relevant.
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
)) {
278 // Other than the actual assignment, other relevant nodes to naming are
279 // those in object initializers and then particular nodes marking a
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())) {
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())) {
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())) {
324 MOZ_ASSERT(kid
->isKind(ParseNodeKind::NumberExpr
));
325 if (!appendNumericPropertyReference(
326 kid
->as
<NumericLiteral
>().value())) {
331 MOZ_ASSERT(left
->isKind(ParseNodeKind::ComputedName
) ||
332 left
->isKind(ParseNodeKind::BigIntExpr
));
335 // Don't have consecutive '<' characters, and also don't start
336 // with a '<' character.
337 if (!buf_
.empty() && buf_
.getChar(buf_
.length() - 1) != '<' &&
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) == '/' &&
356 *retId
= buf_
.finishParserAtom(parserAtoms_
, fc_
);
361 // Skip assigning the guessed name if the function has a (dynamically)
362 // computed inferred name.
363 if (!funNode
->isDirectRHSAnonFunction()) {
364 funbox
->setGuessedAtom(*retId
);
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
;
380 [[nodiscard
]] bool visitFunction(FunctionNode
* pn
) {
381 TaggedParserAtomIndex savedPrefix
= prefix_
;
382 TaggedParserAtomIndex newPrefix
;
383 if (!resolveFun(pn
, &newPrefix
)) {
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
)) {
395 bool ok
= Base::visitFunction(pn
);
397 prefix_
= savedPrefix
;
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.
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.
418 // The callsite object node is first. This node only contains
419 // internal strings or undefined and an array -- no user-controlled
421 CallSiteNode
* element
=
422 &taggedTemplate
->right()->as
<ListNode
>().head()->as
<CallSiteNode
>();
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
));
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
)) {
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.
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
>());
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
));
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
));
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
),
495 parserAtoms_(parserAtoms
),
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.
508 auto initialParents
= nparents_
;
509 parents_
[initialParents
] = pn
;
512 bool ok
= Base::visit(pn
);
514 // Pop pn from the parse node stack.
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
);
526 } /* anonymous namespace */
528 bool frontend::NameFunctions(FrontendContext
* fc
, ParserAtomsTable
& parserAtoms
,
530 NameResolver
nr(fc
, parserAtoms
);