tor-browser

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

UsedNameTracker.h (9840B)


      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 #ifndef frontend_UsedNameTracker_h
      8 #define frontend_UsedNameTracker_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/Maybe.h"
     12 
     13 #include <stdint.h>
     14 
     15 #include "frontend/ParserAtom.h"                   // TaggedParserAtomIndex
     16 #include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
     17 #include "frontend/Token.h"
     18 #include "js/AllocPolicy.h"
     19 #include "js/HashTable.h"
     20 #include "js/Vector.h"
     21 
     22 namespace js {
     23 namespace frontend {
     24 
     25 // A data structure for tracking used names per parsing session in order to
     26 // compute which bindings are closed over. Scripts and scopes are numbered
     27 // monotonically in textual order and unresolved uses of a name are tracked by
     28 // lists of identifier uses, which are a pair of (ScriptId,ScopeId).
     29 //
     30 // For an identifier `i` with a use (ScriptId,ScopeId) in the Used list,
     31 // ScriptId tracks the most nested script that has a use of u, and ScopeId
     32 // tracks the most nested scope that is still being parsed (as the lists will be
     33 // filtered as we finish processing a particular scope).
     34 //
     35 // ScriptId is used to answer the question "is `i` used by a nested function?"
     36 // ScopeId is used to answer the question "is `i` used in any scopes currently
     37 //                                         being parsed?"
     38 //
     39 // The algorithm:
     40 //
     41 // Let Used be a map of names to lists.
     42 // Let Declared(ScopeId) be a list of declarations for a scope numbered with
     43 // ScopeId
     44 //
     45 // 1. Number all scopes in monotonic increasing order in textual order.
     46 // 2. Number all scripts in monotonic increasing order in textual order.
     47 // 3. When an identifier `i` is used in (ScriptId,ScopeId), append that use to
     48 //    the list Used[i] (creating the list and table entry if necessary).
     49 // 4. When an identifier `i` is declared in a scope numbered ScopeId, append `i`
     50 //    to Declared(ScopeId).
     51 // 5. When we finish parsing a scope numbered with ScopeId, in script numbered
     52 //    ScriptId, for each declared name d in Declared(ScopeId):
     53 //   a. If d is found in Used, mark d as closed over if there is a value
     54 //      (UsedScriptId, UsedScopeId) in Used[d] such that UsedScriptId > ScriptId
     55 //      and UsedScopeId > ScopeId.
     56 //   b. Remove all values uses in Used[d] where UsedScopeId > ScopeId.
     57 //
     58 // Steps 1 and 2 are implemented by UsedNameTracker::next{Script,Scope}Id.
     59 // Step 3 is implemented by UsedNameTracker::noteUsedInScope.
     60 // Step 4 is implemented by ParseContext::Scope::addDeclaredName.
     61 // Step 5 is implemented by UsedNameTracker::noteBoundInScope and
     62 // Parser::propagateFreeNamesAndMarkClosedOverBindings
     63 //
     64 // The following is a worked example to show how the algorithm works on a
     65 // relatively simple piece of code. (clang-format is disabled due to the width
     66 // of the example).
     67 
     68 // clang-format off
     69 //
     70 // // Script 1, Scope 1
     71 // var x = 1;                              // Declared(1) = [x];
     72 // function f() {// Script 2, Scope 2
     73 //     if (x > 10) { //Scope 3             // Used[x] = [(2,2)];
     74 //         var x = 12;                     // Declared(3) = [x];
     75 //         function g() // Script 3
     76 //         { // Scope 4
     77 //             return x;                   // Used[x] = [(2,2), (3,4)]
     78 //         }                               // Leaving Script 3, Scope 4: No declared variables.
     79 //     }                                   // Leaving Script 2, Scope 3: Declared(3) = [x];
     80 //                                         // - Used[x][1] = (2,2) is not > (2,3)
     81 //                                         // - Used[x][2] = (3,4) is > (2,3), so mark x as closed over.
     82 //                                         // - Update Used[x]: [] -- Makes sense, as at this point we have
     83 //                                         //                         bound all the unbound x to a particlar
     84 //                                         //                         declaration..
     85 //
     86 //     else { // Scope 5
     87 //         var x = 14;                     // Declared(5) = [x]
     88 //         function g() // Script 4
     89 //         { // Scope 6
     90 //             return y;                   // Used[y] = [(4,6)]
     91 //         }                               // Leaving Script 4, Scope 6: No declarations.
     92 //     }                                   // Leaving Script 2, Scope 5: Declared(5) = [x]
     93 //                                         // - Used[x] = [], so don't mark x as closed over.
     94 //     var y = 12;                         // Declared(2) = [y]
     95 // }                                       // Leaving Script 2, Scope 2: Declared(2) = [y]
     96 //                                         // - Used[y][1] = (4,6) is > (2,2), so mark y as closed over.
     97 //                                         // - Update Used[y]: [].
     98 
     99 // clang-format on
    100 
    101 struct UnboundPrivateName {
    102  TaggedParserAtomIndex atom;
    103  TokenPos position;
    104 
    105  UnboundPrivateName(TaggedParserAtomIndex atom, TokenPos position)
    106      : atom(atom), position(position) {}
    107 };
    108 
    109 class UsedNameTracker {
    110 public:
    111  struct Use {
    112    uint32_t scriptId;
    113    uint32_t scopeId;
    114  };
    115 
    116  class UsedNameInfo {
    117    friend class UsedNameTracker;
    118 
    119    Vector<Use, 6> uses_;
    120 
    121    void resetToScope(uint32_t scriptId, uint32_t scopeId);
    122 
    123    NameVisibility visibility_ = NameVisibility::Public;
    124 
    125    // The first place this name was used. This is important to track
    126    // for private names, as we will use this location to issue
    127    // diagnostics for using a name that's not defined lexically.
    128    mozilla::Maybe<TokenPos> firstUsePos_;
    129 
    130   public:
    131    explicit UsedNameInfo(FrontendContext* fc, NameVisibility visibility,
    132                          mozilla::Maybe<TokenPos> position)
    133        : uses_(fc), visibility_(visibility), firstUsePos_(position) {}
    134 
    135    UsedNameInfo(UsedNameInfo&& other) = default;
    136 
    137    bool noteUsedInScope(uint32_t scriptId, uint32_t scopeId) {
    138      if (uses_.empty() || uses_.back().scopeId < scopeId) {
    139        return uses_.append(Use{scriptId, scopeId});
    140      }
    141      return true;
    142    }
    143 
    144    void noteBoundInScope(uint32_t scriptId, uint32_t scopeId,
    145                          bool* closedOver) {
    146      *closedOver = false;
    147      while (!uses_.empty()) {
    148        Use& innermost = uses_.back();
    149        if (innermost.scopeId < scopeId) {
    150          break;
    151        }
    152        if (innermost.scriptId > scriptId) {
    153          *closedOver = true;
    154        }
    155        uses_.popBack();
    156      }
    157    }
    158 
    159    bool isUsedInScript(uint32_t scriptId) const {
    160      return !uses_.empty() && uses_.back().scriptId >= scriptId;
    161    }
    162 
    163    bool isClosedOver(uint32_t scriptId) const {
    164      return !uses_.empty() && uses_.back().scriptId > scriptId;
    165    }
    166 
    167    // To allow disambiguating public and private symbols
    168    bool isPublic() { return visibility_ == NameVisibility::Public; }
    169 
    170    bool empty() const { return uses_.empty(); }
    171 
    172    mozilla::Maybe<TokenPos> pos() { return firstUsePos_; }
    173 
    174    // When we leave a scope, and subsequently find a new private name
    175    // reference, we don't want our error messages to be attributed to an old
    176    // scope, so we update the position in that scenario.
    177    void maybeUpdatePos(mozilla::Maybe<TokenPos> p) {
    178      MOZ_ASSERT_IF(!isPublic(), p.isSome());
    179 
    180      if (empty() && !isPublic()) {
    181        firstUsePos_ = p;
    182      }
    183    }
    184  };
    185 
    186  using UsedNameMap =
    187      HashMap<TaggedParserAtomIndex, UsedNameInfo, TaggedParserAtomIndexHasher>;
    188 
    189 private:
    190  // The map of names to chains of uses.
    191  UsedNameMap map_;
    192 
    193  // Monotonically increasing id for all nested scripts.
    194  uint32_t scriptCounter_;
    195 
    196  // Monotonically increasing id for all nested scopes.
    197  uint32_t scopeCounter_;
    198 
    199  // Set if a private name was encountered.
    200  // Used to short circuit some private field early error checks
    201  bool hasPrivateNames_;
    202 
    203 public:
    204  explicit UsedNameTracker(FrontendContext* fc)
    205      : map_(fc),
    206        scriptCounter_(0),
    207        scopeCounter_(0),
    208        hasPrivateNames_(false) {}
    209 
    210  uint32_t nextScriptId() {
    211    MOZ_ASSERT(scriptCounter_ != UINT32_MAX,
    212               "ParseContext::Scope::init should have prevented wraparound");
    213    return scriptCounter_++;
    214  }
    215 
    216  uint32_t nextScopeId() {
    217    MOZ_ASSERT(scopeCounter_ != UINT32_MAX);
    218    return scopeCounter_++;
    219  }
    220 
    221  UsedNameMap::Ptr lookup(TaggedParserAtomIndex name) const {
    222    return map_.lookup(name);
    223  }
    224 
    225  [[nodiscard]] bool noteUse(
    226      FrontendContext* fc, TaggedParserAtomIndex name,
    227      NameVisibility visibility, uint32_t scriptId, uint32_t scopeId,
    228      mozilla::Maybe<TokenPos> tokenPosition = mozilla::Nothing());
    229 
    230  // Fill maybeUnboundName with the first (source order) unbound name, or
    231  // Nothing() if there are no unbound names.
    232  [[nodiscard]] bool hasUnboundPrivateNames(
    233      FrontendContext* fc,
    234      mozilla::Maybe<UnboundPrivateName>& maybeUnboundName);
    235 
    236  // Return a list of unbound private names, sorted by increasing location in
    237  // the source.
    238  [[nodiscard]] bool getUnboundPrivateNames(
    239      Vector<UnboundPrivateName, 8>& unboundPrivateNames);
    240 
    241  struct RewindToken {
    242   private:
    243    friend class UsedNameTracker;
    244    uint32_t scriptId;
    245    uint32_t scopeId;
    246  };
    247 
    248  RewindToken getRewindToken() const {
    249    RewindToken token;
    250    token.scriptId = scriptCounter_;
    251    token.scopeId = scopeCounter_;
    252    return token;
    253  }
    254 
    255  // Resets state so that scriptId and scopeId are the innermost script and
    256  // scope, respectively. Used for rewinding state on syntax parse failure.
    257  void rewind(RewindToken token);
    258 
    259  const UsedNameMap& map() const { return map_; }
    260 
    261 #if defined(DEBUG) || defined(JS_JITSPEW)
    262  void dump(ParserAtomsTable& table);
    263 #endif
    264 };
    265 
    266 }  // namespace frontend
    267 }  // namespace js
    268 
    269 #endif