Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/VM/Domain.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#include "hermes/VM/Domain.h"
9
10
#include "hermes/VM/Callable.h"
11
#include "hermes/VM/GCPointer-inline.h"
12
#include "hermes/VM/JSLib.h"
13
#include "hermes/VM/Profiler/SamplingProfiler.h"
14
#pragma GCC diagnostic push
15
16
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
17
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
18
#endif
19
namespace hermes {
20
namespace vm {
21
22
const VTable Domain::vt{
23
    CellKind::DomainKind,
24
    cellSize<Domain>(),
25
    _finalizeImpl,
26
    _mallocSizeImpl,
27
    nullptr
28
#ifdef HERMES_MEMORY_INSTRUMENTATION
29
    ,
30
    VTable::HeapSnapshotMetadata{
31
        HeapSnapshot::NodeType::Code,
32
        nullptr,
33
        Domain::_snapshotAddEdgesImpl,
34
        Domain::_snapshotAddNodesImpl,
35
        nullptr}
36
#endif
37
};
38
39
1
void DomainBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
40
1
  const auto *self = static_cast<const Domain *>(cell);
41
1
  mb.setVTable(&Domain::vt);
42
1
  mb.addField("cjsModules", &self->cjsModules_);
43
1
  mb.addField("throwingRequire", &self->throwingRequire_);
44
1
}
45
46
373
PseudoHandle<Domain> Domain::create(Runtime &runtime) {
47
373
  auto *cell = runtime.makeAFixed<Domain, HasFinalizer::Yes>();
48
373
  auto self = createPseudoHandle(cell);
49
373
  return self;
50
373
}
51
52
373
void Domain::_finalizeImpl(GCCell *cell, GC &gc) {
53
373
  auto *self = vmcast<Domain>(cell);
54
453
  for (RuntimeModule *rm : self->runtimeModules_) {
55
453
    gc.getIDTracker().untrackNative(rm);
56
453
  }
57
373
  self->~Domain();
58
373
}
59
60
PseudoHandle<NativeFunction> Domain::getThrowingRequire(
61
0
    Runtime &runtime) const {
62
0
  return createPseudoHandle(throwingRequire_.get(runtime));
63
0
}
64
65
0
size_t Domain::_mallocSizeImpl(GCCell *cell) {
66
0
  auto *self = vmcast<Domain>(cell);
67
0
  size_t rmSize = 0;
68
0
  for (auto *rm : self->runtimeModules_)
69
0
    rmSize += sizeof(RuntimeModule) + rm->additionalMemorySize();
70
71
0
  return self->cjsRuntimeModules_.capacity_in_bytes() +
72
0
      self->cjsModuleTable_.getMemorySize() +
73
0
      self->runtimeModules_.capacity_in_bytes() + rmSize;
74
0
}
75
76
#ifdef HERMES_MEMORY_INSTRUMENTATION
77
0
void Domain::_snapshotAddEdgesImpl(GCCell *cell, GC &gc, HeapSnapshot &snap) {
78
0
  auto *const self = vmcast<Domain>(cell);
79
0
  for (RuntimeModule *rm : self->runtimeModules_)
80
0
    snap.addNamedEdge(
81
0
        HeapSnapshot::EdgeType::Internal, "RuntimeModule", gc.getNativeID(rm));
82
0
}
83
84
0
void Domain::_snapshotAddNodesImpl(GCCell *cell, GC &gc, HeapSnapshot &snap) {
85
0
  auto *const self = vmcast<Domain>(cell);
86
0
  for (RuntimeModule *rm : self->runtimeModules_) {
87
    // Create a native node for each RuntimeModule owned by this domain.
88
0
    rm->snapshotAddNodes(gc, snap);
89
0
    snap.beginNode();
90
0
    rm->snapshotAddEdges(gc, snap);
91
0
    snap.endNode(
92
0
        HeapSnapshot::NodeType::Native,
93
0
        "RuntimeModule",
94
0
        gc.getNativeID(rm),
95
0
        sizeof(RuntimeModule) + rm->additionalMemorySize(),
96
0
        0);
97
0
  }
98
0
}
99
#endif
100
101
ExecutionStatus Domain::importCJSModuleTable(
102
    Handle<Domain> self,
103
    Runtime &runtime,
104
260
    RuntimeModule *runtimeModule) {
105
260
  if (runtimeModule->getBytecode()->getCJSModuleTable().empty() &&
106
260
      runtimeModule->getBytecode()->getCJSModuleTableStatic().empty()) {
107
    // Nothing to do, avoid allocating and simply return.
108
260
    return ExecutionStatus::RETURNED;
109
260
  }
110
111
0
  static_assert(
112
0
      CJSModuleSize < 10, "CJSModuleSize must be small to avoid overflow");
113
114
0
  MutableHandle<ArrayStorage> cjsModules{runtime};
115
116
0
  if (!self->cjsModules_) {
117
    // Create the module table on first import.
118
    // If module IDs are contiguous and start from 0, we won't need to resize
119
    // for this RuntimeModule.
120
121
0
    const uint64_t firstSegmentModules =
122
0
        runtimeModule->getBytecode()->getCJSModuleTable().size() +
123
0
        runtimeModule->getBytecode()->getCJSModuleTableStatic().size();
124
125
0
    assert(
126
0
        firstSegmentModules <= std::numeric_limits<uint32_t>::max() &&
127
0
        "number of modules is 32 bits due to the bytecode format");
128
129
    // Use uint64_t to allow us to check for overflow.
130
0
    const uint64_t requiredSize = firstSegmentModules * CJSModuleSize;
131
0
    if (requiredSize > std::numeric_limits<uint32_t>::max()) {
132
0
      return runtime.raiseRangeError("Loaded module count exceeded limit");
133
0
    }
134
135
0
    auto cjsModulesRes = ArrayStorage::create(runtime, requiredSize);
136
0
    if (LLVM_UNLIKELY(cjsModulesRes == ExecutionStatus::EXCEPTION)) {
137
0
      return ExecutionStatus::EXCEPTION;
138
0
    }
139
0
    cjsModules = vmcast<ArrayStorage>(*cjsModulesRes);
140
0
    self->cjsRuntimeModules_.reserve(firstSegmentModules);
141
0
    for (size_t i = self->cjsRuntimeModules_.size(); i < firstSegmentModules;
142
0
         i++) {
143
0
      self->cjsRuntimeModules_.push_back(nullptr, runtime.getHeap());
144
0
    }
145
146
0
    auto requireFn = NativeFunction::create(
147
0
        runtime,
148
0
        Handle<JSObject>::vmcast(&runtime.functionPrototype),
149
0
        (void *)TypeErrorKind::InvalidDynamicRequire,
150
0
        throwTypeError,
151
0
        Predefined::getSymbolID(Predefined::emptyString),
152
0
        0,
153
0
        Runtime::makeNullHandle<JSObject>());
154
155
0
    auto context = RequireContext::create(
156
0
        runtime,
157
0
        self,
158
0
        runtime.getPredefinedStringHandle(Predefined::emptyString));
159
160
    // Set the require.context property.
161
0
    PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags();
162
0
    pf.writable = 0;
163
0
    pf.configurable = 0;
164
0
    if (LLVM_UNLIKELY(
165
0
            JSObject::defineNewOwnProperty(
166
0
                requireFn,
167
0
                runtime,
168
0
                Predefined::getSymbolID(Predefined::context),
169
0
                pf,
170
0
                context) == ExecutionStatus::EXCEPTION)) {
171
0
      return ExecutionStatus::EXCEPTION;
172
0
    }
173
174
0
    self->throwingRequire_.set(runtime, *requireFn, runtime.getHeap());
175
0
  } else {
176
0
    cjsModules = self->cjsModules_.get(runtime);
177
0
  }
178
179
0
  assert(cjsModules && "cjsModules not set");
180
181
  // Find the maximum module ID so we can resize cjsModules at most once per
182
  // RuntimeModule.
183
0
  uint64_t maxModuleID = cjsModules->size() / CJSModuleSize;
184
185
  // The non-static module table does not store module IDs. They are assigned
186
  // during registration by counting insertions to cjsModuleTable_. Count the
187
  // insertions up front.
188
0
  for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
189
0
    SymbolID symbolId =
190
0
        runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
191
0
    if (self->cjsModuleTable_.find(symbolId) == self->cjsModuleTable_.end()) {
192
0
      ++maxModuleID;
193
0
    }
194
0
  }
195
196
  // The static module table stores module IDs in an arbitrary order. Scan for
197
  // the maximum ID.
198
0
  for (const auto &pair :
199
0
       runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
200
0
    const auto &moduleID = pair.first;
201
0
    if (moduleID > maxModuleID) {
202
0
      maxModuleID = moduleID;
203
0
    }
204
0
  }
205
206
0
  assert(
207
0
      maxModuleID <= std::numeric_limits<uint32_t>::max() &&
208
0
      "number of modules is 32 bits due to the bytecode format");
209
210
  // Use uint64_t to allow us to check for overflow.
211
0
  const uint64_t requiredSize = (maxModuleID + 1) * CJSModuleSize;
212
0
  if (requiredSize > std::numeric_limits<uint32_t>::max()) {
213
0
    return runtime.raiseRangeError("Loaded module count exceeded limit");
214
0
  }
215
216
  // Resize the array to allow for the new modules, if necessary.
217
0
  if (requiredSize > cjsModules->size()) {
218
0
    if (LLVM_UNLIKELY(
219
0
            ArrayStorage::resize(cjsModules, runtime, requiredSize) ==
220
0
            ExecutionStatus::EXCEPTION)) {
221
0
      return ExecutionStatus::EXCEPTION;
222
0
    }
223
0
    self->cjsRuntimeModules_.reserve(maxModuleID + 1);
224
0
    for (size_t i = self->cjsRuntimeModules_.size(); i <= maxModuleID; i++) {
225
0
      self->cjsRuntimeModules_.push_back(nullptr, runtime.getHeap());
226
0
    }
227
0
  }
228
229
  /// \return Whether the module with ID \param moduleID has been registered in
230
  /// cjsModules. \pre Space has been allocated for this module's record in
231
  /// cjsModules.
232
0
  const auto isModuleRegistered = [&cjsModules,
233
0
                                   maxModuleID](uint32_t moduleID) -> bool {
234
0
    assert(
235
0
        moduleID <= maxModuleID &&
236
0
        "CJS module ID exceeds maximum known module ID");
237
0
    (void)maxModuleID;
238
0
    uint32_t index = moduleID * CJSModuleSize;
239
0
    uint32_t requiredSize = index + CJSModuleSize;
240
0
    assert(
241
0
        cjsModules->size() >= requiredSize &&
242
0
        "CJS module ID exceeds allocated storage");
243
0
    (void)requiredSize;
244
0
    return !cjsModules->at(index + FunctionIndexOffset).isEmpty();
245
0
  };
246
247
  /// Register CJS module \param moduleID in the runtime module table.
248
  /// \return The index into cjsModules where this module's record begins.
249
  /// \pre Space has been allocated for this module's record in cjsModules.
250
  /// \pre Space has been allocated for this module's RuntimeModule* in
251
  /// cjsRuntimeModules_.
252
  /// \pre There is no module already registered under moduleID.
253
0
  auto &cjsEntryModuleID = self->cjsEntryModuleID_;
254
0
  auto &cjsRuntimeModules = self->cjsRuntimeModules_;
255
0
  const auto registerModule =
256
0
      [&runtime,
257
0
       &cjsModules,
258
0
       &cjsRuntimeModules,
259
0
       runtimeModule,
260
0
       &isModuleRegistered,
261
0
       &cjsEntryModuleID](uint32_t moduleID, uint32_t functionID) -> uint32_t {
262
0
    assert(!isModuleRegistered(moduleID) && "CJS module ID collision occurred");
263
0
    (void)isModuleRegistered;
264
0
    if (LLVM_UNLIKELY(!cjsEntryModuleID.hasValue())) {
265
0
      cjsEntryModuleID = moduleID;
266
0
    }
267
0
    uint32_t index = moduleID * CJSModuleSize;
268
0
    cjsModules->set(
269
0
        index + CachedExportsOffset,
270
0
        HermesValue::encodeEmptyValue(),
271
0
        runtime.getHeap());
272
0
    cjsModules->set(
273
0
        index + ModuleOffset,
274
0
        HermesValue::encodeNullValue(),
275
0
        runtime.getHeap());
276
0
    cjsModules->set(
277
0
        index + FunctionIndexOffset,
278
0
        HermesValue::encodeNativeUInt32(functionID),
279
0
        runtime.getHeap());
280
0
    cjsRuntimeModules[moduleID] = runtimeModule;
281
0
    assert(isModuleRegistered(moduleID) && "CJS module was not registered");
282
0
    return index;
283
0
  };
284
285
  // Import full table that allows dynamic requires.
286
0
  for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
287
0
    SymbolID symbolId =
288
0
        runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
289
0
    auto emplaceRes = self->cjsModuleTable_.try_emplace(symbolId, 0xffffffff);
290
0
    if (emplaceRes.second) {
291
      // This module has not been registered before.
292
      // Assign it an arbitrary unused module ID, because nothing will be
293
      // referencing that ID from outside Domain.
294
      // Counting insertions to cjsModuleTable_ is a valid source of unique IDs
295
      // since a given Domain uses either dynamic requires or statically
296
      // resolved requires.
297
0
      uint32_t moduleID = self->cjsModuleTable_.size() - 1;
298
0
      const auto functionID = pair.second;
299
0
      auto index = registerModule(moduleID, functionID);
300
      // Update the mapping from symbolId to an index into cjsModules.
301
0
      emplaceRes.first->second = index;
302
0
    }
303
0
  }
304
305
  // Import table to be used for requireFast.
306
0
  for (const auto &pair :
307
0
       runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
308
0
    const auto &moduleID = pair.first;
309
0
    const auto &functionID = pair.second;
310
0
    if (!isModuleRegistered(moduleID)) {
311
0
      registerModule(moduleID, functionID);
312
0
    }
313
0
  }
314
315
0
  self->cjsModules_.set(runtime, cjsModules.get(), runtime.getHeap());
316
0
  return ExecutionStatus::RETURNED;
317
0
}
318
319
const ObjectVTable RequireContext::vt{
320
    VTable(CellKind::RequireContextKind, cellSize<RequireContext>()),
321
    RequireContext::_getOwnIndexedRangeImpl,
322
    RequireContext::_haveOwnIndexedImpl,
323
    RequireContext::_getOwnIndexedPropertyFlagsImpl,
324
    RequireContext::_getOwnIndexedImpl,
325
    RequireContext::_setOwnIndexedImpl,
326
    RequireContext::_deleteOwnIndexedImpl,
327
    RequireContext::_checkAllOwnIndexedImpl,
328
};
329
330
1
void RequireContextBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
331
1
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<RequireContext>());
332
1
  JSObjectBuildMeta(cell, mb);
333
1
  const auto *self = static_cast<const RequireContext *>(cell);
334
1
  mb.setVTable(&RequireContext::vt);
335
1
  mb.addField(&self->domain_);
336
1
  mb.addField(&self->dirname_);
337
1
}
338
339
Handle<RequireContext> RequireContext::create(
340
    Runtime &runtime,
341
    Handle<Domain> domain,
342
0
    Handle<StringPrimitive> dirname) {
343
0
  auto objProto = Handle<JSObject>::vmcast(&runtime.objectPrototype);
344
0
  auto *cell = runtime.makeAFixed<RequireContext>(
345
0
      runtime,
346
0
      objProto,
347
0
      runtime.getHiddenClassForPrototype(
348
0
          *objProto, numOverlapSlots<RequireContext>()));
349
0
  auto self = JSObjectInit::initToHandle(runtime, cell);
350
0
  self->domain_.set(runtime, *domain, runtime.getHeap());
351
0
  self->dirname_.set(runtime, *dirname, runtime.getHeap());
352
0
  return self;
353
0
}
354
355
} // namespace vm
356
} // namespace hermes