ExecutionStatus Domain::importCJSModuleTable()

in lib/VM/Domain.cpp [109:325]


ExecutionStatus Domain::importCJSModuleTable(
    Handle<Domain> self,
    Runtime &runtime,
    RuntimeModule *runtimeModule) {
  if (runtimeModule->getBytecode()->getCJSModuleTable().empty() &&
      runtimeModule->getBytecode()->getCJSModuleTableStatic().empty()) {
    // Nothing to do, avoid allocating and simply return.
    return ExecutionStatus::RETURNED;
  }

  static_assert(
      CJSModuleSize < 10, "CJSModuleSize must be small to avoid overflow");

  MutableHandle<ArrayStorage> cjsModules{runtime};

  if (!self->cjsModules_) {
    // Create the module table on first import.
    // If module IDs are contiguous and start from 0, we won't need to resize
    // for this RuntimeModule.

    const uint64_t firstSegmentModules =
        runtimeModule->getBytecode()->getCJSModuleTable().size() +
        runtimeModule->getBytecode()->getCJSModuleTableStatic().size();

    assert(
        firstSegmentModules <= std::numeric_limits<uint32_t>::max() &&
        "number of modules is 32 bits due to the bytecode format");

    // Use uint64_t to allow us to check for overflow.
    const uint64_t requiredSize = firstSegmentModules * CJSModuleSize;
    if (requiredSize > std::numeric_limits<uint32_t>::max()) {
      return runtime.raiseRangeError("Loaded module count exceeded limit");
    }

    auto cjsModulesRes = ArrayStorage::create(runtime, requiredSize);
    if (LLVM_UNLIKELY(cjsModulesRes == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    cjsModules = vmcast<ArrayStorage>(*cjsModulesRes);
    self->cjsRuntimeModules_.reserve(firstSegmentModules);
    for (size_t i = self->cjsRuntimeModules_.size(); i < firstSegmentModules;
         i++) {
      self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
    }

    auto requireFn = NativeFunction::create(
        runtime,
        Handle<JSObject>::vmcast(&runtime.functionPrototype),
        (void *)TypeErrorKind::InvalidDynamicRequire,
        throwTypeError,
        Predefined::getSymbolID(Predefined::emptyString),
        0,
        Runtime::makeNullHandle<JSObject>());

    auto context = RequireContext::create(
        runtime,
        self,
        runtime.getPredefinedStringHandle(Predefined::emptyString));

    // Set the require.context property.
    PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags();
    pf.writable = 0;
    pf.configurable = 0;
    if (LLVM_UNLIKELY(
            JSObject::defineNewOwnProperty(
                requireFn,
                runtime,
                Predefined::getSymbolID(Predefined::context),
                pf,
                context) == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }

    self->throwingRequire_.set(runtime, *requireFn, &runtime.getHeap());
  } else {
    cjsModules = self->cjsModules_.get(runtime);
  }

  assert(cjsModules && "cjsModules not set");

  // Find the maximum module ID so we can resize cjsModules at most once per
  // RuntimeModule.
  uint64_t maxModuleID = cjsModules->size() / CJSModuleSize;

  // The non-static module table does not store module IDs. They are assigned
  // during registration by counting insertions to cjsModuleTable_. Count the
  // insertions up front.
  for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
    SymbolID symbolId =
        runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
    if (self->cjsModuleTable_.find(symbolId) == self->cjsModuleTable_.end()) {
      ++maxModuleID;
    }
  }

  // The static module table stores module IDs in an arbitrary order. Scan for
  // the maximum ID.
  for (const auto &pair :
       runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
    const auto &moduleID = pair.first;
    if (moduleID > maxModuleID) {
      maxModuleID = moduleID;
    }
  }

  assert(
      maxModuleID <= std::numeric_limits<uint32_t>::max() &&
      "number of modules is 32 bits due to the bytecode format");

  // Use uint64_t to allow us to check for overflow.
  const uint64_t requiredSize = (maxModuleID + 1) * CJSModuleSize;
  if (requiredSize > std::numeric_limits<uint32_t>::max()) {
    return runtime.raiseRangeError("Loaded module count exceeded limit");
  }

  // Resize the array to allow for the new modules, if necessary.
  if (requiredSize > cjsModules->size()) {
    if (LLVM_UNLIKELY(
            ArrayStorage::resize(cjsModules, runtime, requiredSize) ==
            ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    self->cjsRuntimeModules_.reserve(maxModuleID + 1);
    for (size_t i = self->cjsRuntimeModules_.size(); i <= maxModuleID; i++) {
      self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
    }
  }

  /// \return Whether the module with ID \param moduleID has been registered in
  /// cjsModules. \pre Space has been allocated for this module's record in
  /// cjsModules.
  const auto isModuleRegistered = [&cjsModules,
                                   maxModuleID](uint32_t moduleID) -> bool {
    assert(
        moduleID <= maxModuleID &&
        "CJS module ID exceeds maximum known module ID");
    (void)maxModuleID;
    uint32_t index = moduleID * CJSModuleSize;
    uint32_t requiredSize = index + CJSModuleSize;
    assert(
        cjsModules->size() >= requiredSize &&
        "CJS module ID exceeds allocated storage");
    (void)requiredSize;
    return !cjsModules->at(index + FunctionIndexOffset).isEmpty();
  };

  /// Register CJS module \param moduleID in the runtime module table.
  /// \return The index into cjsModules where this module's record begins.
  /// \pre Space has been allocated for this module's record in cjsModules.
  /// \pre Space has been allocated for this module's RuntimeModule* in
  /// cjsRuntimeModules_.
  /// \pre There is no module already registered under moduleID.
  auto &cjsEntryModuleID = self->cjsEntryModuleID_;
  auto &cjsRuntimeModules = self->cjsRuntimeModules_;
  const auto registerModule =
      [&runtime,
       &cjsModules,
       &cjsRuntimeModules,
       runtimeModule,
       &isModuleRegistered,
       &cjsEntryModuleID](uint32_t moduleID, uint32_t functionID) -> uint32_t {
    assert(!isModuleRegistered(moduleID) && "CJS module ID collision occurred");
    (void)isModuleRegistered;
    if (LLVM_UNLIKELY(!cjsEntryModuleID.hasValue())) {
      cjsEntryModuleID = moduleID;
    }
    uint32_t index = moduleID * CJSModuleSize;
    cjsModules->set(
        index + CachedExportsOffset,
        HermesValue::encodeEmptyValue(),
        &runtime.getHeap());
    cjsModules->set(
        index + ModuleOffset,
        HermesValue::encodeNullValue(),
        &runtime.getHeap());
    cjsModules->set(
        index + FunctionIndexOffset,
        HermesValue::encodeNativeUInt32(functionID),
        &runtime.getHeap());
    cjsRuntimeModules[moduleID] = runtimeModule;
    assert(isModuleRegistered(moduleID) && "CJS module was not registered");
    return index;
  };

  // Import full table that allows dynamic requires.
  for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
    SymbolID symbolId =
        runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
    auto emplaceRes = self->cjsModuleTable_.try_emplace(symbolId, 0xffffffff);
    if (emplaceRes.second) {
      // This module has not been registered before.
      // Assign it an arbitrary unused module ID, because nothing will be
      // referencing that ID from outside Domain.
      // Counting insertions to cjsModuleTable_ is a valid source of unique IDs
      // since a given Domain uses either dynamic requires or statically
      // resolved requires.
      uint32_t moduleID = self->cjsModuleTable_.size() - 1;
      const auto functionID = pair.second;
      auto index = registerModule(moduleID, functionID);
      // Update the mapping from symbolId to an index into cjsModules.
      emplaceRes.first->second = index;
    }
  }

  // Import table to be used for requireFast.
  for (const auto &pair :
       runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
    const auto &moduleID = pair.first;
    const auto &functionID = pair.second;
    if (!isModuleRegistered(moduleID)) {
      registerModule(moduleID, functionID);
    }
  }

  self->cjsModules_.set(runtime, cjsModules.get(), &runtime.getHeap());
  return ExecutionStatus::RETURNED;
}