tor-browser

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

findGeneratedBindingFromPosition.js (9296B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      4 
      5 import { locColumn } from "./locColumn";
      6 import { mappingContains } from "./mappingContains";
      7 
      8 // eslint-disable-next-line max-len
      9 
     10 import { clientCommands } from "../../../client/firefox";
     11 
     12 /**
     13 * Given a mapped range over the generated source, attempt to resolve a real
     14 * binding descriptor that can be used to access the value.
     15 */
     16 export async function findGeneratedReference(applicableBindings) {
     17  // We can adjust this number as we go, but these are a decent start as a
     18  // general heuristic to assume the bindings were bad or just map a chunk of
     19  // whole line or something.
     20  if (applicableBindings.length > 4) {
     21    // Babel's for..of generates at least 3 bindings inside one range for
     22    // block-scoped loop variables, so we shouldn't go below that.
     23    applicableBindings = [];
     24  }
     25 
     26  for (const applicable of applicableBindings) {
     27    const result = await mapBindingReferenceToDescriptor(applicable);
     28    if (result) {
     29      return result;
     30    }
     31  }
     32  return null;
     33 }
     34 
     35 export async function findGeneratedImportReference(applicableBindings) {
     36  // When wrapped, for instance as `Object(ns.default)`, the `Object` binding
     37  // will be the first in the list. To avoid resolving `Object` as the
     38  // value of the import itself, we potentially skip the first binding.
     39  applicableBindings = applicableBindings.filter((applicable, i) => {
     40    if (
     41      !applicable.firstInRange ||
     42      applicable.binding.loc.type !== "ref" ||
     43      applicable.binding.loc.meta
     44    ) {
     45      return true;
     46    }
     47 
     48    const next =
     49      i + 1 < applicableBindings.length ? applicableBindings[i + 1] : null;
     50 
     51    return !next || next.binding.loc.type !== "ref" || !next.binding.loc.meta;
     52  });
     53 
     54  // We can adjust this number as we go, but these are a decent start as a
     55  // general heuristic to assume the bindings were bad or just map a chunk of
     56  // whole line or something.
     57  if (applicableBindings.length > 2) {
     58    // Babel's for..of generates at least 3 bindings inside one range for
     59    // block-scoped loop variables, so we shouldn't go below that.
     60    applicableBindings = [];
     61  }
     62 
     63  for (const applicable of applicableBindings) {
     64    const result = await mapImportReferenceToDescriptor(applicable);
     65    if (result) {
     66      return result;
     67    }
     68  }
     69 
     70  return null;
     71 }
     72 
     73 /**
     74 * Given a mapped range over the generated source and the name of the imported
     75 * value that is referenced, attempt to resolve a binding descriptor for
     76 * the import's value.
     77 */
     78 export async function findGeneratedImportDeclaration(
     79  applicableBindings,
     80  importName
     81 ) {
     82  // We can adjust this number as we go, but these are a decent start as a
     83  // general heuristic to assume the bindings were bad or just map a chunk of
     84  // whole line or something.
     85  if (applicableBindings.length > 10) {
     86    // Import declarations tend to have a large number of bindings for
     87    // for things like 'require' and 'interop', so this number is larger
     88    // than other binding count checks.
     89    applicableBindings = [];
     90  }
     91 
     92  let result = null;
     93 
     94  for (const { binding } of applicableBindings) {
     95    if (binding.loc.type === "ref") {
     96      continue;
     97    }
     98 
     99    const namespaceDesc = await binding.desc();
    100    if (isPrimitiveValue(namespaceDesc)) {
    101      continue;
    102    }
    103    if (!isObjectValue(namespaceDesc)) {
    104      // We want to handle cases like
    105      //
    106      //   var _mod = require(...);
    107      //   var _mod2 = _interopRequire(_mod);
    108      //
    109      // where "_mod" is optimized out because it is only referenced once. To
    110      // allow that, we track the optimized-out value as a possible result,
    111      // but allow later binding values to overwrite the result.
    112      result = {
    113        name: binding.name,
    114        desc: namespaceDesc,
    115        expression: binding.name,
    116      };
    117      continue;
    118    }
    119 
    120    const desc = await readDescriptorProperty(namespaceDesc, importName);
    121    const expression = `${binding.name}.${importName}`;
    122 
    123    if (desc) {
    124      result = {
    125        name: binding.name,
    126        desc,
    127        expression,
    128      };
    129      break;
    130    }
    131  }
    132 
    133  return result;
    134 }
    135 
    136 /**
    137 * Given a generated binding, and a range over the generated code, statically
    138 * check if the given binding matches the range.
    139 */
    140 async function mapBindingReferenceToDescriptor({
    141  binding,
    142  range,
    143  firstInRange,
    144  firstOnLine,
    145 }) {
    146  // Allow the mapping to point anywhere within the generated binding
    147  // location to allow for less than perfect sourcemaps. Since you also
    148  // need at least one character between identifiers, we also give one
    149  // characters of space at the front the generated binding in order
    150  // to increase the probability of finding the right mapping.
    151  if (
    152    range.start.line === binding.loc.start.line &&
    153    // If a binding is the first on a line, Babel will extend the mapping to
    154    // include the whitespace between the newline and the binding. To handle
    155    // that, we skip the range requirement for starting location.
    156    (firstInRange ||
    157      firstOnLine ||
    158      locColumn(range.start) >= locColumn(binding.loc.start)) &&
    159    locColumn(range.start) <= locColumn(binding.loc.end)
    160  ) {
    161    return {
    162      name: binding.name,
    163      desc: await binding.desc(),
    164      expression: binding.name,
    165    };
    166  }
    167 
    168  return null;
    169 }
    170 
    171 /**
    172 * Given an generated binding, and a range over the generated code, statically
    173 * evaluate accessed properties within the mapped range to resolve the actual
    174 * imported value.
    175 */
    176 async function mapImportReferenceToDescriptor({ binding, range }) {
    177  if (binding.loc.type !== "ref") {
    178    return null;
    179  }
    180 
    181  // Expression matches require broader searching because sourcemaps usage
    182  // varies in how they map certain things. For instance given
    183  //
    184  //   import { bar } from "mod";
    185  //   bar();
    186  //
    187  // The "bar()" expression is generally expanded into one of two possibly
    188  // forms, both of which map the "bar" identifier in different ways. See
    189  // the "^^" markers below for the ranges.
    190  //
    191  //   (0, foo.bar)()    // Babel
    192  //       ^^^^^^^       // mapping
    193  //       ^^^           // binding
    194  // vs
    195  //
    196  //   __webpack_require__.i(foo.bar)() // Webpack 2
    197  //   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   // mapping
    198  //                         ^^^        // binding
    199  // vs
    200  //
    201  //   Object(foo.bar)() // Webpack >= 3
    202  //   ^^^^^^^^^^^^^^^   // mapping
    203  //          ^^^        // binding
    204  //
    205  // Unfortunately, Webpack also has a tendancy to over-map past the call
    206  // expression to the start of the next line, at least when there isn't
    207  // anything else on that line that is mapped, e.g.
    208  //
    209  //   Object(foo.bar)()
    210  //   ^^^^^^^^^^^^^^^^^
    211  //   ^                 // wrapped to column 0 of next line
    212 
    213  if (!mappingContains(range, binding.loc)) {
    214    return null;
    215  }
    216 
    217  // Webpack 2's import declarations wrap calls with an identity fn, so we
    218  // need to make sure to skip that binding because it is mapped to the
    219  // location of the original binding usage.
    220  if (
    221    binding.name === "__webpack_require__" &&
    222    binding.loc.meta &&
    223    binding.loc.meta.type === "member" &&
    224    binding.loc.meta.property === "i"
    225  ) {
    226    return null;
    227  }
    228 
    229  let expression = binding.name;
    230  let desc = await binding.desc();
    231 
    232  if (binding.loc.type === "ref") {
    233    const { meta } = binding.loc;
    234 
    235    // Limit to 2 simple property or inherits operartions, since it would
    236    // just be more work to search more and it is very unlikely that
    237    // bindings would be mapped to more than a single member + inherits
    238    // wrapper.
    239    for (
    240      let op = meta, index = 0;
    241      op && mappingContains(range, op) && desc && index < 2;
    242      index++, op = op?.parent
    243    ) {
    244      // Calling could potentially trigger side-effects, which would not
    245      // be ideal for this case.
    246      if (op.type === "call") {
    247        return null;
    248      }
    249 
    250      if (op.type === "inherit") {
    251        continue;
    252      }
    253 
    254      desc = await readDescriptorProperty(desc, op.property);
    255      expression += `.${op.property}`;
    256    }
    257  }
    258 
    259  return desc
    260    ? {
    261        name: binding.name,
    262        desc,
    263        expression,
    264      }
    265    : null;
    266 }
    267 
    268 function isPrimitiveValue(desc) {
    269  return desc && (!desc.value || typeof desc.value !== "object");
    270 }
    271 function isObjectValue(desc) {
    272  return (
    273    desc &&
    274    !isPrimitiveValue(desc) &&
    275    desc.value.type === "object" &&
    276    // Note: The check for `.type` might already cover the optimizedOut case
    277    // but not 100% sure, so just being cautious.
    278    !desc.value.optimizedOut
    279  );
    280 }
    281 
    282 async function readDescriptorProperty(desc, property) {
    283  if (!desc) {
    284    return null;
    285  }
    286 
    287  if (typeof desc.value !== "object" || !desc.value) {
    288    // If accessing a property on a primitive type, just return 'undefined'
    289    // as the value.
    290    return {
    291      value: {
    292        type: "undefined",
    293      },
    294    };
    295  }
    296 
    297  if (!isObjectValue(desc)) {
    298    // If we got a non-primitive descriptor but it isn't an object, then
    299    // it's definitely not the namespace and it is probably an error.
    300    return desc;
    301  }
    302 
    303  const objectFront = clientCommands.createObjectFront(desc.value);
    304  return (await objectFront.getProperty(property)).descriptor;
    305 }