/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 |