Coverage Report

Created: 2025-01-28 06:38

/src/hermes/lib/VM/JSLib/require.cpp
Line
Count
Source (jump to first uncovered line)
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 "JSLibInternal.h"
9
10
#include "hermes/Support/UTF8.h"
11
#include "hermes/VM/Domain.h"
12
#include "hermes/VM/Handle.h"
13
#include "hermes/VM/JSLib.h"
14
#include "hermes/VM/Operations.h"
15
#include "hermes/VM/RuntimeModule-inline.h"
16
#include "hermes/VM/StringPrimitive.h"
17
18
#include "llvh/Support/Path.h"
19
20
namespace hermes {
21
namespace vm {
22
23
CallResult<HermesValue> runRequireCall(
24
    Runtime &runtime,
25
    Handle<RequireContext> context,
26
    Handle<Domain> domain,
27
0
    uint32_t cjsModuleOffset) {
28
0
  {
29
0
    auto cachedExports = domain->getCachedExports(runtime, cjsModuleOffset);
30
0
    if (!cachedExports->isEmpty()) {
31
      // Fast path: require() completed, so just return exports immediately.
32
0
      return cachedExports.getHermesValue();
33
0
    }
34
0
  }
35
36
0
  if (auto module = domain->getModule(runtime, cjsModuleOffset)) {
37
0
    assert(module.get() != nullptr);
38
    // Still initializing, so return the current state of module.exports.
39
0
    auto res = JSObject::getNamed_RJS(
40
0
        runtime.makeHandle(std::move(module)),
41
0
        runtime,
42
0
        Predefined::getSymbolID(Predefined::exports));
43
0
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
44
0
      return ExecutionStatus::EXCEPTION;
45
0
    }
46
0
    return res->get();
47
0
  }
48
49
0
  GCScope gcScope{runtime};
50
  // If not initialized yet, start initializing and set the module object.
51
0
  Handle<JSObject> module = runtime.makeHandle(JSObject::create(runtime));
52
0
  Handle<JSObject> exports = runtime.makeHandle(JSObject::create(runtime));
53
0
  if (LLVM_UNLIKELY(
54
0
          JSObject::putNamed_RJS(
55
0
              module,
56
0
              runtime,
57
0
              Predefined::getSymbolID(Predefined::exports),
58
0
              exports) == ExecutionStatus::EXCEPTION)) {
59
0
    return ExecutionStatus::EXCEPTION;
60
0
  }
61
62
0
  domain->setModule(cjsModuleOffset, runtime, module);
63
64
0
  MutableHandle<JSObject> requireFn{runtime};
65
0
  if (context) {
66
    // Slow path.
67
    // If the context is provided, then it will be used to resolve string
68
    // requires in future calls to require().
69
0
    auto funcRes = BoundFunction::create(
70
0
        runtime,
71
0
        Handle<Callable>::vmcast(&runtime.requireFunction),
72
0
        1,
73
0
        ConstArgIterator(context.unsafeGetPinnedHermesValue() + 1));
74
0
    if (LLVM_UNLIKELY(funcRes == ExecutionStatus::EXCEPTION)) {
75
0
      return ExecutionStatus::EXCEPTION;
76
0
    }
77
0
    requireFn = vmcast<BoundFunction>(*funcRes);
78
79
    // Set the require.context property.
80
0
    PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags();
81
0
    pf.writable = 0;
82
0
    pf.configurable = 0;
83
0
    if (LLVM_UNLIKELY(
84
0
            JSObject::defineNewOwnProperty(
85
0
                requireFn,
86
0
                runtime,
87
0
                Predefined::getSymbolID(Predefined::context),
88
0
                pf,
89
0
                context) == ExecutionStatus::EXCEPTION)) {
90
0
      return ExecutionStatus::EXCEPTION;
91
0
    }
92
0
  } else {
93
    // Fast path.
94
    // The context must not be provided, and any actual calls to require()
95
    // should have been turned into requireFast() calls.
96
    // Calls to require() should throw.
97
0
    requireFn = domain->getThrowingRequire(runtime).get();
98
0
  }
99
100
0
  CodeBlock *codeBlock = domain->getRuntimeModule(runtime, cjsModuleOffset)
101
0
                             ->getCodeBlockMayAllocate(domain->getFunctionIndex(
102
0
                                 runtime, cjsModuleOffset));
103
104
0
  Handle<JSFunction> func = runtime.makeHandle(JSFunction::create(
105
0
      runtime,
106
0
      domain,
107
0
      Handle<JSObject>::vmcast(&runtime.functionPrototype),
108
0
      Runtime::makeNullHandle<Environment>(),
109
0
      codeBlock));
110
111
0
  if (LLVM_UNLIKELY(
112
0
          JSFunction::executeCall3(
113
0
              func,
114
0
              runtime,
115
0
              exports,
116
0
              exports.getHermesValue(),
117
0
              requireFn.getHermesValue(),
118
0
              module.getHermesValue()) == ExecutionStatus::EXCEPTION)) {
119
    // If initialization of the module throws, reset it so that calling require
120
    // again may succeed.
121
0
    domain->setModule(cjsModuleOffset, runtime, Runtime::getNullValue());
122
0
    return ExecutionStatus::EXCEPTION;
123
0
  }
124
125
  // The module.exports object may have been replaced during initialization,
126
  // so we have to run getNamed to ensure we pick up the changes.
127
0
  auto exportsRes = JSObject::getNamed_RJS(
128
0
      module, runtime, Predefined::getSymbolID(Predefined::exports));
129
0
  if (LLVM_UNLIKELY(exportsRes == ExecutionStatus::EXCEPTION)) {
130
0
    return ExecutionStatus::EXCEPTION;
131
0
  }
132
0
  domain->setCachedExports(cjsModuleOffset, runtime, exportsRes->get());
133
0
  return domain->getCachedExports(runtime, cjsModuleOffset).getHermesValue();
134
0
}
135
136
0
CallResult<HermesValue> requireFast(void *, Runtime &runtime, NativeArgs args) {
137
0
  assert(
138
0
      runtime.getStackFrames().begin()->getSavedCodeBlock() != nullptr &&
139
0
      "requireFast() cannot be called from native");
140
141
  // Find the RuntimeModule from which this require was called.
142
0
  RuntimeModule *runtimeModule =
143
0
      runtime.getStackFrames().begin()->getSavedCodeBlock()->getRuntimeModule();
144
0
  auto domain = runtimeModule->getDomain(runtime);
145
146
0
  uint32_t index = args.getArg(0).getNumberAs<uint32_t>();
147
0
  OptValue<uint32_t> cjsModuleOffset =
148
0
      domain->getCJSModuleOffset(runtime, index);
149
0
  if (LLVM_UNLIKELY(!cjsModuleOffset)) {
150
0
    return runtime.raiseTypeError(
151
0
        TwineChar16("Unable to find module with ID: ") + index);
152
0
  }
153
0
  return runRequireCall(
154
0
      runtime,
155
0
      Runtime::makeNullHandle<RequireContext>(),
156
0
      domain,
157
0
      *cjsModuleOffset);
158
0
}
159
160
static llvh::SmallString<32> canonicalizePath(
161
    Runtime &runtime,
162
    Handle<StringPrimitive> dirname,
163
0
    Handle<StringPrimitive> target) {
164
  // Copy the current path so we can modify it as necessary.
165
0
  llvh::SmallString<32> canonicalPath{};
166
167
0
  auto appendToCanonical = [&canonicalPath](
168
0
                               Handle<StringPrimitive> strPrim,
169
0
                               uint32_t start = 0) {
170
0
    SmallU16String<32> u16String{};
171
0
    strPrim->appendUTF16String(u16String);
172
0
    std::string str{};
173
0
    hermes::convertUTF16ToUTF8WithReplacements(
174
0
        str, UTF16Ref{u16String}.slice(start));
175
0
    llvh::sys::path::append(canonicalPath, llvh::sys::path::Style::posix, str);
176
0
  };
177
178
0
  if (target->getStringLength() > 0 && target->at(0) == u'/') {
179
    // If the dirname is absolute (starts with a '/'), resolve from the module
180
    // root.
181
0
    appendToCanonical(target, 1);
182
0
  } else {
183
    // Else, the dirname is relative. Resolve from the dirname.
184
0
    appendToCanonical(dirname);
185
0
    appendToCanonical(target);
186
0
  }
187
188
  // Remove all dots. This is done to get rid of ../ or anything like ././.
189
0
  llvh::sys::path::remove_dots(
190
0
      canonicalPath, true, llvh::sys::path::Style::posix);
191
192
0
  return canonicalPath;
193
0
}
194
195
0
CallResult<HermesValue> require(void *, Runtime &runtime, NativeArgs args) {
196
0
  GCScope gcScope{runtime};
197
198
0
  auto requireContext = args.vmcastThis<RequireContext>();
199
0
  auto domain =
200
0
      runtime.makeHandle(RequireContext::getDomain(runtime, *requireContext));
201
0
  auto dirname =
202
0
      runtime.makeHandle(RequireContext::getDirname(runtime, *requireContext));
203
0
  auto targetRes = toString_RJS(runtime, args.getArgHandle(0));
204
0
  if (LLVM_UNLIKELY(targetRes == ExecutionStatus::EXCEPTION)) {
205
0
    return ExecutionStatus::EXCEPTION;
206
0
  }
207
0
  auto target = runtime.makeHandle(std::move(*targetRes));
208
209
0
  auto canonicalPath = canonicalizePath(runtime, dirname, target);
210
211
0
  auto targetStrRes = StringPrimitive::create(runtime, canonicalPath);
212
0
  if (LLVM_UNLIKELY(targetStrRes == ExecutionStatus::EXCEPTION)) {
213
0
    return ExecutionStatus::EXCEPTION;
214
0
  }
215
216
0
  auto cr = stringToSymbolID(
217
0
      runtime, createPseudoHandle(vmcast<StringPrimitive>(*targetStrRes)));
218
0
  if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) {
219
0
    return ExecutionStatus::EXCEPTION;
220
0
  }
221
0
  Handle<SymbolID> targetID = *cr;
222
223
  // Find the relevant CJS module.
224
0
  OptValue<uint32_t> cjsModuleOffset = domain->getCJSModuleOffset(*targetID);
225
0
  if (LLVM_UNLIKELY(!cjsModuleOffset)) {
226
0
    return runtime.raiseTypeError(
227
0
        TwineChar16("Unable to find module: ") + target.get());
228
0
  }
229
230
0
  llvh::sys::path::remove_filename(
231
0
      canonicalPath, llvh::sys::path::Style::posix);
232
0
  auto dirnameRes = StringPrimitive::create(runtime, canonicalPath);
233
0
  if (LLVM_UNLIKELY(dirnameRes == ExecutionStatus::EXCEPTION)) {
234
0
    return ExecutionStatus::EXCEPTION;
235
0
  }
236
0
  auto dirnameHandle = runtime.makeHandle<StringPrimitive>(*dirnameRes);
237
238
0
  auto newRequireContext =
239
0
      RequireContext::create(runtime, domain, dirnameHandle);
240
241
0
  return runRequireCall(runtime, newRequireContext, domain, *cjsModuleOffset);
242
0
}
243
244
} // namespace vm
245
} // namespace hermes