tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }