NameFunctions.cpp (18487B)
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/ScopeExit.h" 10 #include "mozilla/Sprintf.h" 11 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/StringBuilder.h" 18 19 using namespace js; 20 using namespace js::frontend; 21 22 namespace { 23 24 class NameResolver : public ParseNodeVisitor<NameResolver> { 25 using Base = ParseNodeVisitor; 26 27 static const size_t MaxParents = 100; 28 29 FrontendContext* fc_; 30 ParserAtomsTable& parserAtoms_; 31 TaggedParserAtomIndex 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 StringBuilder buf_; 44 45 /* Test whether a ParseNode represents a function invocation */ 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 */ 60 bool appendPropertyReference(TaggedParserAtomIndex name) { 61 if (parserAtoms_.isIdentifier(name)) { 62 return buf_.append('.') && buf_.append(parserAtoms_, name); 63 } 64 65 /* Quote the string as needed. */ 66 UniqueChars source = parserAtoms_.toQuotedString(name); 67 if (!source) { 68 ReportOutOfMemory(fc_); 69 return false; 70 } 71 return buf_.append('[') && 72 buf_.append(source.get(), strlen(source.get())) && buf_.append(']'); 73 } 74 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); 80 } 81 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(']'); 85 } 86 87 /* 88 * Walk over the given ParseNode, attempting to convert it to a stringified 89 * name that represents where the function is being assigned to. 90 * 91 * |*foundName| is set to true if a name is found for the expression. 92 */ 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; 100 } 101 if (!*foundName) { 102 return true; 103 } 104 return appendPropertyReference(prop->right()->as<NameNode>().atom()); 105 } 106 107 case ParseNodeKind::Name: 108 case ParseNodeKind::PrivateName: { 109 *foundName = true; 110 return buf_.append(parserAtoms_, n->as<NameNode>().atom()); 111 } 112 113 case ParseNodeKind::ThisExpr: 114 *foundName = true; 115 return buf_.append("this"); 116 117 case ParseNodeKind::ElemExpr: { 118 PropertyByValue* elem = &n->as<PropertyByValue>(); 119 if (!nameExpression(&elem->expression(), foundName)) { 120 return false; 121 } 122 if (!*foundName) { 123 return true; 124 } 125 if (!buf_.append('[') || !nameExpression(elem->right(), foundName)) { 126 return false; 127 } 128 if (!*foundName) { 129 return true; 130 } 131 return buf_.append(']'); 132 } 133 134 case ParseNodeKind::NumberExpr: 135 *foundName = true; 136 return appendNumber(n->as<NumericLiteral>().value()); 137 138 default: 139 // We're confused as to what to call this function. 140 *foundName = false; 141 return true; 142 } 143 } 144 145 /* 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. 150 * 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. 157 */ 158 ParseNode* gatherNameable(ParseNode** nameable, size_t* size) { 159 MOZ_ASSERT(nparents_ > 0); 160 MOZ_ASSERT(parents_[nparents_ - 1]->is<FunctionNode>()); 161 162 *size = 0; 163 164 for (int pos = nparents_ - 2; pos >= 0; pos--) { 165 ParseNode* cur = parents_[pos]; 166 if (cur->is<AssignmentNode>()) { 167 return cur; 168 } 169 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 178 179 case ParseNodeKind::ReturnStmt: 180 // Normally the relevant parent of a node is its direct parent, but 181 // sometimes with code like: 182 // 183 // var foo = (function() { return function() {}; })(); 184 // 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; 194 } 195 if (isCall(cur)) { 196 // Don't skip too high in the tree. 197 break; 198 } 199 cur = parents_[tmp]; 200 } 201 break; 202 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]]; 209 210 default: 211 // Save any other nodes we encounter on the way up. 212 MOZ_ASSERT(*size < MaxParents); 213 nameable[(*size)++] = cur; 214 break; 215 } 216 } 217 218 return nullptr; 219 } 220 221 /* 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. 225 */ 226 [[nodiscard]] bool resolveFun(FunctionNode* funNode, 227 TaggedParserAtomIndex* retId) { 228 MOZ_ASSERT(funNode != nullptr); 229 230 FunctionBox* funbox = funNode->funbox(); 231 232 MOZ_ASSERT(buf_.empty()); 233 auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); }); 234 235 *retId = TaggedParserAtomIndex::null(); 236 237 // If the function already has a name, use that. 238 if (funbox->displayAtom()) { 239 if (!prefix_) { 240 *retId = funbox->displayAtom(); 241 return true; 242 } 243 if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/') || 244 !buf_.append(parserAtoms_, funbox->displayAtom())) { 245 return false; 246 } 247 *retId = buf_.finishParserAtom(parserAtoms_, fc_); 248 return !!*retId; 249 } 250 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; 255 } 256 } 257 258 // Gather all nodes relevant to naming. 259 ParseNode* toName[MaxParents]; 260 size_t size; 261 ParseNode* assignment = gatherNameable(toName, &size); 262 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(); 268 } 269 bool foundName = false; 270 if (!nameExpression(assignment, &foundName)) { 271 return false; 272 } 273 if (!foundName) { 274 return true; 275 } 276 } 277 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]; 283 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; 294 } 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; 301 } 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() {} }`. 312 // 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; 322 } 323 } else { 324 MOZ_ASSERT(kid->isKind(ParseNodeKind::NumberExpr)); 325 if (!appendNumericPropertyReference( 326 kid->as<NumericLiteral>().value())) { 327 return false; 328 } 329 } 330 } else { 331 MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName) || 332 left->isKind(ParseNodeKind::BigIntExpr)); 333 } 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; 340 } 341 } 342 } 343 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; 350 } 351 352 if (buf_.empty()) { 353 return true; 354 } 355 356 *retId = buf_.finishParserAtom(parserAtoms_, fc_); 357 if (!*retId) { 358 return false; 359 } 360 361 // Skip assigning the guessed name if the function has a (dynamically) 362 // computed inferred name. 363 if (!funNode->isDirectRHSAnonFunction()) { 364 funbox->setGuessedAtom(*retId); 365 } 366 return true; 367 } 368 369 /* 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. 373 */ 374 bool isDirectCall(int pos, ParseNode* cur) { 375 return pos >= 0 && isCall(parents_[pos]) && 376 parents_[pos]->as<BinaryNode>().left() == cur; 377 } 378 379 public: 380 [[nodiscard]] bool visitFunction(FunctionNode* pn) { 381 TaggedParserAtomIndex savedPrefix = prefix_; 382 TaggedParserAtomIndex newPrefix; 383 if (!resolveFun(pn, &newPrefix)) { 384 return false; 385 } 386 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; 393 } 394 395 bool ok = Base::visitFunction(pn); 396 397 prefix_ = savedPrefix; 398 return ok; 399 } 400 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; 406 } 407 408 // Skip walking the list of string parts, which never contains functions. 409 [[nodiscard]] bool visitTaggedTemplateExpr(BinaryNode* taggedTemplate) { 410 ParseNode* tag = taggedTemplate->left(); 411 412 // The leading expression, e.g. |tag| in |tag`foo`|, 413 // that might contain functions. 414 if (!visit(tag)) { 415 return false; 416 } 417 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 424 { 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)); 429 } 430 for (ParseNode* cooked : element->contentsFrom(rawNodes->pn_next)) { 431 MOZ_ASSERT(cooked->isKind(ParseNodeKind::TemplateStringExpr) || 432 cooked->isKind(ParseNodeKind::RawUndefinedExpr)); 433 } 434 } 435 #endif 436 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; 442 } 443 } 444 445 return true; 446 } 447 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)); 476 } 477 } 478 } 479 #endif 480 return true; 481 } 482 483 public: 484 [[nodiscard]] bool visitImportSpecList(ListNode* pn) { 485 return internalVisitSpecList(pn); 486 } 487 488 [[nodiscard]] bool visitExportSpecList(ListNode* pn) { 489 return internalVisitSpecList(pn); 490 } 491 492 NameResolver(FrontendContext* fc, ParserAtomsTable& parserAtoms) 493 : ParseNodeVisitor(fc), 494 fc_(fc), 495 parserAtoms_(parserAtoms), 496 nparents_(0), 497 buf_(fc) {} 498 499 /* 500 * Resolve names for all anonymous functions in the given ParseNode tree. 501 */ 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; 507 } 508 auto initialParents = nparents_; 509 parents_[initialParents] = pn; 510 nparents_++; 511 512 bool ok = Base::visit(pn); 513 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); 521 522 return ok; 523 } 524 }; 525 526 } /* anonymous namespace */ 527 528 bool frontend::NameFunctions(FrontendContext* fc, ParserAtomsTable& parserAtoms, 529 ParseNode* pn) { 530 NameResolver nr(fc, parserAtoms); 531 return nr.visit(pn); 532 }