/src/hermes/lib/VM/JSLib/HermesInternal.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 "JSLibInternal.h" |
9 | | |
10 | | #include "hermes/BCGen/HBC/BytecodeFileFormat.h" |
11 | | #include "hermes/Support/Base64vlq.h" |
12 | | #include "hermes/Support/OSCompat.h" |
13 | | #include "hermes/VM/Callable.h" |
14 | | #include "hermes/VM/JSArray.h" |
15 | | #include "hermes/VM/JSArrayBuffer.h" |
16 | | #include "hermes/VM/JSLib.h" |
17 | | #include "hermes/VM/JSTypedArray.h" |
18 | | #include "hermes/VM/JSWeakMapImpl.h" |
19 | | #include "hermes/VM/Operations.h" |
20 | | #include "hermes/VM/StackFrame-inline.h" |
21 | | #include "hermes/VM/StringView.h" |
22 | | |
23 | | #include <cstring> |
24 | | #include <random> |
25 | | #pragma GCC diagnostic push |
26 | | |
27 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
28 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
29 | | #endif |
30 | | namespace hermes { |
31 | | namespace vm { |
32 | | |
33 | | /// \return a SymbolID for a given C string \p s. |
34 | | static inline CallResult<Handle<SymbolID>> symbolForCStr( |
35 | | Runtime &rt, |
36 | 0 | const char *s) { |
37 | 0 | return rt.getIdentifierTable().getSymbolHandle(rt, ASCIIRef{s, strlen(s)}); |
38 | 0 | } |
39 | | |
40 | | // ES7 24.1.1.3 |
41 | | CallResult<HermesValue> |
42 | 0 | hermesInternalDetachArrayBuffer(void *, Runtime &runtime, NativeArgs args) { |
43 | 0 | auto buffer = args.dyncastArg<JSArrayBuffer>(0); |
44 | 0 | if (!buffer) { |
45 | 0 | return runtime.raiseTypeError( |
46 | 0 | "Cannot use detachArrayBuffer on something which " |
47 | 0 | "is not an ArrayBuffer foo"); |
48 | 0 | } |
49 | 0 | if (LLVM_UNLIKELY( |
50 | 0 | JSArrayBuffer::detach(runtime, buffer) == ExecutionStatus::EXCEPTION)) |
51 | 0 | return ExecutionStatus::EXCEPTION; |
52 | | // "void" return |
53 | 0 | return HermesValue::encodeUndefinedValue(); |
54 | 0 | } |
55 | | |
56 | | CallResult<HermesValue> |
57 | 0 | hermesInternalGetEpilogues(void *, Runtime &runtime, NativeArgs args) { |
58 | | // Create outer array with one element per module. |
59 | 0 | auto eps = runtime.getEpilogues(); |
60 | 0 | auto outerLen = eps.size(); |
61 | 0 | auto outerResult = JSArray::create(runtime, outerLen, outerLen); |
62 | |
|
63 | 0 | if (outerResult == ExecutionStatus::EXCEPTION) { |
64 | 0 | return ExecutionStatus::EXCEPTION; |
65 | 0 | } |
66 | 0 | auto outer = *outerResult; |
67 | 0 | if (outer->setStorageEndIndex(outer, runtime, outerLen) == |
68 | 0 | ExecutionStatus::EXCEPTION) { |
69 | 0 | return ExecutionStatus::EXCEPTION; |
70 | 0 | } |
71 | | // Set each element to a Uint8Array holding the epilogue for that module. |
72 | 0 | for (unsigned i = 0; i < outerLen; ++i) { |
73 | 0 | auto innerLen = eps[i].size(); |
74 | 0 | if (innerLen != 0) { |
75 | 0 | auto result = Uint8Array::allocate(runtime, innerLen); |
76 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
77 | 0 | return ExecutionStatus::EXCEPTION; |
78 | 0 | } |
79 | 0 | auto ta = result.getValue(); |
80 | 0 | std::memcpy(ta->begin(runtime), eps[i].begin(), innerLen); |
81 | 0 | const auto shv = SmallHermesValue::encodeObjectValue(*ta, runtime); |
82 | 0 | JSArray::unsafeSetExistingElementAt(*outer, runtime, i, shv); |
83 | 0 | } |
84 | 0 | } |
85 | 0 | return HermesValue::encodeObjectValue(*outer); |
86 | 0 | } |
87 | | |
88 | | /// Used for testing, determines how many live values |
89 | | /// are in the given WeakMap or WeakSet. |
90 | | CallResult<HermesValue> |
91 | 0 | hermesInternalGetWeakSize(void *, Runtime &runtime, NativeArgs args) { |
92 | 0 | if (auto M = args.dyncastArg<JSWeakMap>(0)) { |
93 | 0 | return HermesValue::encodeUntrustedNumberValue( |
94 | 0 | JSWeakMap::debugFreeSlotsAndGetSize(runtime, *M)); |
95 | 0 | } |
96 | | |
97 | 0 | if (auto S = args.dyncastArg<JSWeakSet>(0)) { |
98 | 0 | return HermesValue::encodeUntrustedNumberValue( |
99 | 0 | JSWeakSet::debugFreeSlotsAndGetSize(runtime, *S)); |
100 | 0 | } |
101 | | |
102 | 0 | return runtime.raiseTypeError( |
103 | 0 | "getWeakSize can only be called on a WeakMap/WeakSet"); |
104 | 0 | } |
105 | | |
106 | | /// \return an object containing various instrumented statistics. |
107 | | CallResult<HermesValue> |
108 | 0 | hermesInternalGetInstrumentedStats(void *, Runtime &runtime, NativeArgs args) { |
109 | 0 | GCScope gcScope(runtime); |
110 | 0 | auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); |
111 | 0 | MutableHandle<> valHandle{runtime}; |
112 | | |
113 | | /// Adds \p key with \p val to the resultHandle object. |
114 | 0 | auto addToResultHandle = |
115 | 0 | [&gcScope, &runtime]( |
116 | 0 | Handle<JSObject> obj, llvh::StringRef key, Handle<> val) { |
117 | 0 | GCScopeMarkerRAII marker{gcScope}; |
118 | 0 | auto keySym = symbolForCStr(runtime, key.data()); |
119 | 0 | if (LLVM_UNLIKELY(keySym == ExecutionStatus::EXCEPTION)) { |
120 | 0 | return ExecutionStatus::EXCEPTION; |
121 | 0 | } |
122 | | |
123 | 0 | return JSObject::defineNewOwnProperty( |
124 | 0 | obj, |
125 | 0 | runtime, |
126 | 0 | **keySym, |
127 | 0 | PropertyFlags::defaultNewNamedPropertyFlags(), |
128 | 0 | val); |
129 | 0 | }; |
130 | | |
131 | | /// Adds a property to resultHandle. \p key provides its name, and \p val, |
132 | | /// its value. |
133 | 0 | #define ADD_PROP(obj, name, value) \ |
134 | 0 | valHandle = HermesValue::encodeUntrustedNumberValue(value); \ |
135 | 0 | if (LLVM_UNLIKELY( \ |
136 | 0 | addToResultHandle(obj, name, valHandle) == \ |
137 | 0 | ExecutionStatus::EXCEPTION)) { \ |
138 | 0 | return ExecutionStatus::EXCEPTION; \ |
139 | 0 | } |
140 | |
|
141 | 0 | auto &heap = runtime.getHeap(); |
142 | 0 | GCBase::HeapInfo info; |
143 | 0 | heap.getHeapInfo(info); |
144 | |
|
145 | 0 | ADD_PROP(resultHandle, "js_VMExperiments", runtime.getVMExperimentFlags()); |
146 | 0 | ADD_PROP(resultHandle, "js_numGCs", heap.getNumGCs()); |
147 | 0 | ADD_PROP(resultHandle, "js_gcCPUTime", heap.getGCCPUTime()); |
148 | 0 | ADD_PROP( |
149 | 0 | resultHandle, "js_avgGCCPUTime", info.generalStats.gcCPUTime.average()); |
150 | 0 | ADD_PROP(resultHandle, "js_maxGCCPUTime", info.generalStats.gcCPUTime.max()); |
151 | 0 | ADD_PROP(resultHandle, "js_gcTime", heap.getGCTime()); |
152 | 0 | ADD_PROP( |
153 | 0 | resultHandle, "js_avgGCTime", info.generalStats.gcWallTime.average()); |
154 | 0 | ADD_PROP(resultHandle, "js_maxGCTime", info.generalStats.gcWallTime.max()); |
155 | 0 | ADD_PROP(resultHandle, "js_totalAllocatedBytes", info.totalAllocatedBytes); |
156 | 0 | ADD_PROP(resultHandle, "js_allocatedBytes", info.allocatedBytes); |
157 | 0 | ADD_PROP(resultHandle, "js_heapSize", info.heapSize); |
158 | 0 | ADD_PROP(resultHandle, "js_mallocSizeEstimate", info.mallocSizeEstimate); |
159 | 0 | ADD_PROP(resultHandle, "js_vaSize", info.va); |
160 | 0 | ADD_PROP(resultHandle, "js_externalBytes", info.externalBytes); |
161 | 0 | ADD_PROP( |
162 | 0 | resultHandle, |
163 | 0 | "js_peakAllocatedBytes", |
164 | 0 | info.generalStats.usedBefore.max()); |
165 | 0 | ADD_PROP( |
166 | 0 | resultHandle, "js_peakLiveAfterGC", info.generalStats.usedAfter.max()); |
167 | |
|
168 | 0 | #ifdef HERMESVM_GC_HADES |
169 | 0 | Handle<JSObject> specificStatsHandle = |
170 | 0 | runtime.makeHandle(JSObject::create(runtime)); |
171 | 0 | auto res = |
172 | 0 | addToResultHandle(resultHandle, "js_gcSpecific", specificStatsHandle); |
173 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
174 | 0 | return ExecutionStatus::EXCEPTION; |
175 | 0 | } |
176 | | |
177 | 0 | ADD_PROP( |
178 | 0 | specificStatsHandle, |
179 | 0 | "js_numYGCollections", |
180 | 0 | info.youngGenStats.numCollections); |
181 | 0 | ADD_PROP( |
182 | 0 | specificStatsHandle, |
183 | 0 | "js_numOGCollections", |
184 | 0 | info.fullStats.numCollections); |
185 | 0 | ADD_PROP(specificStatsHandle, "js_numCompactions", info.numCompactions); |
186 | 0 | #endif |
187 | 0 | #undef ADD_PROP |
188 | |
|
189 | 0 | return resultHandle.getHermesValue(); |
190 | 0 | } |
191 | | |
192 | | /// \return a static string summarising the presence and resolution type of |
193 | | /// CommonJS modules across all RuntimeModules that have been loaded into \c |
194 | | /// runtime. |
195 | 0 | static const char *getCJSModuleModeDescription(Runtime &runtime) { |
196 | 0 | bool hasCJSModulesDynamic = false; |
197 | 0 | bool hasCJSModulesStatic = false; |
198 | 0 | for (const auto &runtimeModule : runtime.getRuntimeModules()) { |
199 | 0 | if (runtimeModule.hasCJSModules()) { |
200 | 0 | hasCJSModulesDynamic = true; |
201 | 0 | } |
202 | 0 | if (runtimeModule.hasCJSModulesStatic()) { |
203 | 0 | hasCJSModulesStatic = true; |
204 | 0 | } |
205 | 0 | } |
206 | 0 | if (hasCJSModulesDynamic && hasCJSModulesStatic) { |
207 | 0 | return "Mixed dynamic/static"; |
208 | 0 | } |
209 | 0 | if (hasCJSModulesDynamic) { |
210 | 0 | return "Dynamically resolved"; |
211 | 0 | } |
212 | 0 | if (hasCJSModulesStatic) { |
213 | 0 | return "Statically resolved"; |
214 | 0 | } |
215 | 0 | return "None"; |
216 | 0 | } |
217 | | |
218 | | /// \return an object mapping keys to runtime property values. |
219 | | CallResult<HermesValue> |
220 | 0 | hermesInternalGetRuntimeProperties(void *, Runtime &runtime, NativeArgs args) { |
221 | 0 | GCScope gcScope(runtime); |
222 | 0 | auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); |
223 | 0 | MutableHandle<> tmpHandle{runtime}; |
224 | | |
225 | | /// Add a property \p value keyed under \p key to resultHandle. |
226 | | /// Return an ExecutionStatus. |
227 | 0 | auto addProperty = [&](Handle<> value, const char *key) { |
228 | 0 | auto keySym = symbolForCStr(runtime, key); |
229 | 0 | if (LLVM_UNLIKELY(keySym == ExecutionStatus::EXCEPTION)) { |
230 | 0 | return ExecutionStatus::EXCEPTION; |
231 | 0 | } |
232 | 0 | return JSObject::defineNewOwnProperty( |
233 | 0 | resultHandle, |
234 | 0 | runtime, |
235 | 0 | **keySym, |
236 | 0 | PropertyFlags::defaultNewNamedPropertyFlags(), |
237 | 0 | value); |
238 | 0 | }; |
239 | |
|
240 | | #ifdef HERMES_FACEBOOK_BUILD |
241 | | tmpHandle = |
242 | | HermesValue::encodeBoolValue(std::strstr(__FILE__, "hermes-snapshot")); |
243 | | if (LLVM_UNLIKELY( |
244 | | addProperty(tmpHandle, "Snapshot VM") == |
245 | | ExecutionStatus::EXCEPTION)) { |
246 | | return ExecutionStatus::EXCEPTION; |
247 | | } |
248 | | #endif |
249 | |
|
250 | 0 | tmpHandle = |
251 | 0 | HermesValue::encodeUntrustedNumberValue(::hermes::hbc::BYTECODE_VERSION); |
252 | 0 | if (LLVM_UNLIKELY( |
253 | 0 | addProperty(tmpHandle, "Bytecode Version") == |
254 | 0 | ExecutionStatus::EXCEPTION)) { |
255 | 0 | return ExecutionStatus::EXCEPTION; |
256 | 0 | } |
257 | | |
258 | 0 | tmpHandle = HermesValue::encodeBoolValue(runtime.builtinsAreFrozen()); |
259 | 0 | if (LLVM_UNLIKELY( |
260 | 0 | addProperty(tmpHandle, "Builtins Frozen") == |
261 | 0 | ExecutionStatus::EXCEPTION)) { |
262 | 0 | return ExecutionStatus::EXCEPTION; |
263 | 0 | } |
264 | | |
265 | 0 | tmpHandle = |
266 | 0 | HermesValue::encodeUntrustedNumberValue(runtime.getVMExperimentFlags()); |
267 | 0 | if (LLVM_UNLIKELY( |
268 | 0 | addProperty(tmpHandle, "VM Experiments") == |
269 | 0 | ExecutionStatus::EXCEPTION)) { |
270 | 0 | return ExecutionStatus::EXCEPTION; |
271 | 0 | } |
272 | | |
273 | 0 | const char buildMode[] = |
274 | 0 | #ifdef HERMES_SLOW_DEBUG |
275 | 0 | "SlowDebug" |
276 | | #elif !defined(NDEBUG) |
277 | | "Debug" |
278 | | #else |
279 | | "Release" |
280 | | #endif |
281 | 0 | ; |
282 | 0 | auto buildModeRes = StringPrimitive::create( |
283 | 0 | runtime, ASCIIRef(buildMode, sizeof(buildMode) - 1)); |
284 | 0 | if (LLVM_UNLIKELY(buildModeRes == ExecutionStatus::EXCEPTION)) { |
285 | 0 | return ExecutionStatus::EXCEPTION; |
286 | 0 | } |
287 | 0 | tmpHandle = *buildModeRes; |
288 | 0 | if (LLVM_UNLIKELY( |
289 | 0 | addProperty(tmpHandle, "Build") == ExecutionStatus::EXCEPTION)) { |
290 | 0 | return ExecutionStatus::EXCEPTION; |
291 | 0 | } |
292 | | |
293 | 0 | std::string gcKind = runtime.getHeap().getKindAsStr(); |
294 | 0 | auto gcKindRes = StringPrimitive::create( |
295 | 0 | runtime, ASCIIRef(gcKind.c_str(), gcKind.length())); |
296 | 0 | if (LLVM_UNLIKELY(gcKindRes == ExecutionStatus::EXCEPTION)) { |
297 | 0 | return ExecutionStatus::EXCEPTION; |
298 | 0 | } |
299 | 0 | tmpHandle = *gcKindRes; |
300 | 0 | if (LLVM_UNLIKELY( |
301 | 0 | addProperty(tmpHandle, "GC") == ExecutionStatus::EXCEPTION)) { |
302 | 0 | return ExecutionStatus::EXCEPTION; |
303 | 0 | } |
304 | | |
305 | 0 | #ifdef HERMES_RELEASE_VERSION |
306 | 0 | auto relVerRes = |
307 | 0 | StringPrimitive::create(runtime, createASCIIRef(HERMES_RELEASE_VERSION)); |
308 | 0 | if (LLVM_UNLIKELY(relVerRes == ExecutionStatus::EXCEPTION)) { |
309 | 0 | return ExecutionStatus::EXCEPTION; |
310 | 0 | } |
311 | 0 | tmpHandle = *relVerRes; |
312 | 0 | if (LLVM_UNLIKELY( |
313 | 0 | addProperty(tmpHandle, "OSS Release Version") == |
314 | 0 | ExecutionStatus::EXCEPTION)) { |
315 | 0 | return ExecutionStatus::EXCEPTION; |
316 | 0 | } |
317 | 0 | #endif |
318 | | |
319 | 0 | const bool debuggerEnabled = |
320 | 0 | #ifdef HERMES_ENABLE_DEBUGGER |
321 | 0 | true |
322 | | #else |
323 | | false |
324 | | #endif |
325 | 0 | ; |
326 | |
|
327 | 0 | tmpHandle = HermesValue::encodeBoolValue(debuggerEnabled); |
328 | 0 | if (LLVM_UNLIKELY( |
329 | 0 | addProperty(tmpHandle, "Debugger Enabled") == |
330 | 0 | ExecutionStatus::EXCEPTION)) { |
331 | 0 | return ExecutionStatus::EXCEPTION; |
332 | 0 | } |
333 | | |
334 | 0 | const char *cjsModuleMode = getCJSModuleModeDescription(runtime); |
335 | 0 | auto cjsModuleModeRes = |
336 | 0 | StringPrimitive::create(runtime, createASCIIRef(cjsModuleMode)); |
337 | 0 | if (LLVM_UNLIKELY(cjsModuleModeRes == ExecutionStatus::EXCEPTION)) { |
338 | 0 | return ExecutionStatus::EXCEPTION; |
339 | 0 | } |
340 | 0 | tmpHandle = *cjsModuleModeRes; |
341 | 0 | if (LLVM_UNLIKELY( |
342 | 0 | addProperty(tmpHandle, "CommonJS Modules") == |
343 | 0 | ExecutionStatus::EXCEPTION)) { |
344 | 0 | return ExecutionStatus::EXCEPTION; |
345 | 0 | } |
346 | | |
347 | 0 | return resultHandle.getHermesValue(); |
348 | 0 | } |
349 | | |
350 | | #ifdef HERMESVM_PLATFORM_LOGGING |
351 | | static void logGCStats(Runtime &runtime, const char *msg) { |
352 | | // The GC stats can exceed the android logcat length limit, of |
353 | | // 1024 bytes. Break it up. |
354 | | std::string stats; |
355 | | { |
356 | | llvh::raw_string_ostream os(stats); |
357 | | runtime.printHeapStats(os); |
358 | | } |
359 | | auto copyRegionFrom = [&stats](size_t from) -> size_t { |
360 | | size_t rBrace = stats.find("},", from); |
361 | | if (rBrace == std::string::npos) { |
362 | | std::string portion = stats.substr(from); |
363 | | hermesLog("HermesVM", "%s", portion.c_str()); |
364 | | return stats.size(); |
365 | | } |
366 | | |
367 | | // Add 2 for the length of the search string, to get to the end. |
368 | | const size_t to = rBrace + 2; |
369 | | std::string portion = stats.substr(from, to - from); |
370 | | hermesLog("HermesVM", "%s", portion.c_str()); |
371 | | return to; |
372 | | }; |
373 | | |
374 | | hermesLog("HermesVM", "%s:", msg); |
375 | | for (size_t ind = 0; ind < stats.size(); ind = copyRegionFrom(ind)) |
376 | | ; |
377 | | } |
378 | | #endif |
379 | | |
380 | | CallResult<HermesValue> |
381 | 0 | hermesInternalTTIReached(void *, Runtime &runtime, NativeArgs args) { |
382 | 0 | runtime.ttiReached(); |
383 | | #ifdef HERMESVM_LLVM_PROFILE_DUMP |
384 | | __llvm_profile_dump(); |
385 | | throw jsi::JSINativeException("TTI reached; profiling done"); |
386 | | #endif |
387 | | #ifdef HERMESVM_PLATFORM_LOGGING |
388 | | logGCStats(runtime, "TTI call"); |
389 | | #endif |
390 | 0 | return HermesValue::encodeUndefinedValue(); |
391 | 0 | } |
392 | | |
393 | | CallResult<HermesValue> |
394 | 0 | hermesInternalTTRCReached(void *, Runtime &runtime, NativeArgs args) { |
395 | | // Currently does nothing, but could change in the future. |
396 | 0 | return HermesValue::encodeUndefinedValue(); |
397 | 0 | } |
398 | | |
399 | | CallResult<HermesValue> |
400 | 0 | hermesInternalIsProxy(void *, Runtime &runtime, NativeArgs args) { |
401 | 0 | Handle<JSObject> obj = args.dyncastArg<JSObject>(0); |
402 | 0 | return HermesValue::encodeBoolValue(obj && obj->isProxyObject()); |
403 | 0 | } |
404 | | |
405 | | CallResult<HermesValue> |
406 | 188 | hermesInternalHasPromise(void *, Runtime &runtime, NativeArgs args) { |
407 | 188 | return HermesValue::encodeBoolValue(runtime.hasES6Promise()); |
408 | 188 | } |
409 | | |
410 | | CallResult<HermesValue> |
411 | 94 | hermesInternalHasES6Class(void *, Runtime &runtime, NativeArgs args) { |
412 | 94 | return HermesValue::encodeBoolValue(runtime.hasES6Class()); |
413 | 94 | } |
414 | | |
415 | | CallResult<HermesValue> |
416 | 94 | hermesInternalUseEngineQueue(void *, Runtime &runtime, NativeArgs args) { |
417 | 94 | return HermesValue::encodeBoolValue(runtime.hasMicrotaskQueue()); |
418 | 94 | } |
419 | | |
420 | | /// \code |
421 | | /// HermesInternal.enqueueJob = function (func) {} |
422 | | /// \endcode |
423 | | CallResult<HermesValue> |
424 | 0 | hermesInternalEnqueueJob(void *, Runtime &runtime, NativeArgs args) { |
425 | 0 | auto callable = args.dyncastArg<Callable>(0); |
426 | 0 | if (!callable) { |
427 | 0 | return runtime.raiseTypeError( |
428 | 0 | "Argument to HermesInternal.enqueueJob must be callable"); |
429 | 0 | } |
430 | 0 | runtime.enqueueJob(callable.get()); |
431 | 0 | return HermesValue::encodeUndefinedValue(); |
432 | 0 | } |
433 | | |
434 | | /// \code |
435 | | /// HermesInternal.drainJobs = function () {} |
436 | | /// \endcode |
437 | | /// Throw if the drainJobs throws. |
438 | | CallResult<HermesValue> |
439 | 0 | hermesInternalDrainJobs(void *, Runtime &runtime, NativeArgs args) { |
440 | 0 | auto drainRes = runtime.drainJobs(); |
441 | 0 | if (drainRes == ExecutionStatus::EXCEPTION) { |
442 | | // No need to rethrow since it's already throw. |
443 | 0 | return ExecutionStatus::EXCEPTION; |
444 | 0 | } |
445 | 0 | return HermesValue::encodeUndefinedValue(); |
446 | 0 | } |
447 | | |
448 | | #ifdef HERMESVM_EXCEPTION_ON_OOM |
449 | | /// Gets the current call stack as a JS String value. Intended (only) |
450 | | /// to allow testing of Runtime::callStack() from JS code. |
451 | | CallResult<HermesValue> |
452 | | hermesInternalGetCallStack(void *, Runtime &runtime, NativeArgs args) { |
453 | | std::string stack = runtime.getCallStackNoAlloc(); |
454 | | return StringPrimitive::create(runtime, ASCIIRef(stack.data(), stack.size())); |
455 | | } |
456 | | #endif // HERMESVM_EXCEPTION_ON_OOM |
457 | | |
458 | | /// \return the code block associated with \p callableHandle if it is a |
459 | | /// (possibly bound) JS function, or nullptr otherwise. |
460 | | static const CodeBlock *getLeafCodeBlock( |
461 | | Handle<Callable> callableHandle, |
462 | 0 | Runtime &runtime) { |
463 | 0 | const Callable *callable = callableHandle.get(); |
464 | 0 | while (auto *bound = dyn_vmcast<BoundFunction>(callable)) { |
465 | 0 | callable = bound->getTarget(runtime); |
466 | 0 | } |
467 | 0 | if (auto *asFunction = dyn_vmcast<const JSFunction>(callable)) { |
468 | 0 | return asFunction->getCodeBlock(runtime); |
469 | 0 | } |
470 | 0 | return nullptr; |
471 | 0 | } |
472 | | |
473 | | /// \return the file name associated with \p codeBlock, if any. |
474 | | /// This mirrors the way we print file names for code blocks in JSError. |
475 | | static CallResult<HermesValue> getCodeBlockFileName( |
476 | | Runtime &runtime, |
477 | | const CodeBlock *codeBlock, |
478 | 0 | OptValue<hbc::DebugSourceLocation> location) { |
479 | 0 | RuntimeModule *runtimeModule = codeBlock->getRuntimeModule(); |
480 | 0 | if (!runtimeModule->getBytecode()->isLazy()) { |
481 | | // Lazy code blocks do not have debug information (and will hermes_fatal if |
482 | | // you try to access it), so only touch it for non-lazy blocks. |
483 | 0 | if (location) { |
484 | 0 | auto debugInfo = runtimeModule->getBytecode()->getDebugInfo(); |
485 | 0 | return StringPrimitive::createEfficient( |
486 | 0 | runtime, debugInfo->getFilenameByID(location->filenameId)); |
487 | 0 | } else { |
488 | 0 | llvh::StringRef sourceURL = runtimeModule->getSourceURL(); |
489 | 0 | if (!sourceURL.empty()) { |
490 | 0 | return StringPrimitive::createEfficient(runtime, sourceURL); |
491 | 0 | } |
492 | 0 | } |
493 | 0 | } |
494 | 0 | return HermesValue::encodeUndefinedValue(); |
495 | 0 | } |
496 | | |
497 | | /// \code |
498 | | /// HermesInternal.getFunctionLocation function (func) {} |
499 | | /// \endcode |
500 | | /// Returns an object describing the source location of func. |
501 | | /// The following properties may be present: |
502 | | /// * fileName (string) |
503 | | /// * lineNumber (number) - 1 based |
504 | | /// * columnNumber (number) - 1 based |
505 | | /// * segmentID (number) - 0 based |
506 | | /// * virtualOffset (number) - 0 based |
507 | | /// * isNative (boolean) |
508 | | /// TypeError if func is not a function. |
509 | | CallResult<HermesValue> |
510 | 0 | hermesInternalGetFunctionLocation(void *, Runtime &runtime, NativeArgs args) { |
511 | 0 | GCScope gcScope(runtime); |
512 | |
|
513 | 0 | auto callable = args.dyncastArg<Callable>(0); |
514 | 0 | if (!callable) { |
515 | 0 | return runtime.raiseTypeError( |
516 | 0 | "Argument to HermesInternal.getFunctionLocation must be callable"); |
517 | 0 | } |
518 | 0 | auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); |
519 | 0 | MutableHandle<> tmpHandle{runtime}; |
520 | |
|
521 | 0 | auto codeBlock = getLeafCodeBlock(callable, runtime); |
522 | 0 | bool isNative = !codeBlock; |
523 | 0 | auto res = JSObject::defineOwnProperty( |
524 | 0 | resultHandle, |
525 | 0 | runtime, |
526 | 0 | Predefined::getSymbolID(Predefined::isNative), |
527 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
528 | 0 | runtime.getBoolValue(isNative)); |
529 | 0 | assert(res != ExecutionStatus::EXCEPTION && "Failed to set isNative"); |
530 | 0 | (void)res; |
531 | |
|
532 | 0 | if (codeBlock) { |
533 | 0 | OptValue<hbc::DebugSourceLocation> location = |
534 | 0 | codeBlock->getSourceLocation(); |
535 | 0 | if (location) { |
536 | 0 | tmpHandle = HermesValue::encodeUntrustedNumberValue(location->line); |
537 | 0 | res = JSObject::defineOwnProperty( |
538 | 0 | resultHandle, |
539 | 0 | runtime, |
540 | 0 | Predefined::getSymbolID(Predefined::lineNumber), |
541 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
542 | 0 | tmpHandle); |
543 | 0 | assert(res != ExecutionStatus::EXCEPTION && "Failed to set lineNumber"); |
544 | 0 | (void)res; |
545 | |
|
546 | 0 | tmpHandle = HermesValue::encodeUntrustedNumberValue(location->column); |
547 | 0 | res = JSObject::defineOwnProperty( |
548 | 0 | resultHandle, |
549 | 0 | runtime, |
550 | 0 | Predefined::getSymbolID(Predefined::columnNumber), |
551 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
552 | 0 | tmpHandle); |
553 | 0 | assert(res != ExecutionStatus::EXCEPTION && "Failed to set columnNumber"); |
554 | 0 | (void)res; |
555 | 0 | } else { |
556 | 0 | tmpHandle = HermesValue::encodeUntrustedNumberValue( |
557 | 0 | codeBlock->getRuntimeModule()->getBytecode()->getSegmentID()); |
558 | 0 | res = JSObject::defineOwnProperty( |
559 | 0 | resultHandle, |
560 | 0 | runtime, |
561 | 0 | Predefined::getSymbolID(Predefined::segmentID), |
562 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
563 | 0 | tmpHandle); |
564 | 0 | assert(res != ExecutionStatus::EXCEPTION && "Failed to set segmentID"); |
565 | 0 | (void)res; |
566 | |
|
567 | 0 | tmpHandle = HermesValue::encodeUntrustedNumberValue( |
568 | 0 | codeBlock->getVirtualOffset()); |
569 | 0 | res = JSObject::defineOwnProperty( |
570 | 0 | resultHandle, |
571 | 0 | runtime, |
572 | 0 | Predefined::getSymbolID(Predefined::virtualOffset), |
573 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
574 | 0 | tmpHandle); |
575 | 0 | assert( |
576 | 0 | res != ExecutionStatus::EXCEPTION && "Failed to set virtualOffset"); |
577 | 0 | (void)res; |
578 | 0 | } |
579 | | |
580 | 0 | auto fileNameRes = getCodeBlockFileName(runtime, codeBlock, location); |
581 | 0 | if (LLVM_UNLIKELY(fileNameRes == ExecutionStatus::EXCEPTION)) { |
582 | 0 | return ExecutionStatus::EXCEPTION; |
583 | 0 | } |
584 | 0 | tmpHandle = *fileNameRes; |
585 | 0 | res = JSObject::defineOwnProperty( |
586 | 0 | resultHandle, |
587 | 0 | runtime, |
588 | 0 | Predefined::getSymbolID(Predefined::fileName), |
589 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
590 | 0 | tmpHandle); |
591 | 0 | assert(res != ExecutionStatus::EXCEPTION && "Failed to set fileName"); |
592 | 0 | (void)res; |
593 | 0 | } |
594 | 0 | JSObject::preventExtensions(*resultHandle); |
595 | 0 | return resultHandle.getHermesValue(); |
596 | 0 | } |
597 | | |
598 | | /// \code |
599 | | /// HermesInternal.setPromiseRejectionTrackingHook = function (func) {} |
600 | | /// \endcode |
601 | | /// Register the function which can be used to *enable* Promise rejection |
602 | | /// tracking when the user calls it. |
603 | | /// For example, when using the npm `promise` polyfill: |
604 | | /// \code |
605 | | /// HermesInternal.setPromiseRejectionTrackingHook( |
606 | | /// require('./rejection-tracking.js').enable |
607 | | /// ); |
608 | | /// \endcode |
609 | | CallResult<HermesValue> hermesInternalSetPromiseRejectionTrackingHook( |
610 | | void *, |
611 | | Runtime &runtime, |
612 | 94 | NativeArgs args) { |
613 | 94 | runtime.promiseRejectionTrackingHook_ = args.getArg(0); |
614 | 94 | return HermesValue::encodeUndefinedValue(); |
615 | 94 | } |
616 | | |
617 | | /// \code |
618 | | /// HermesInternal.enablePromiseRejectionTracker = function (opts) {} |
619 | | /// \endcode |
620 | | /// Enable promise rejection tracking with the given opts. |
621 | | CallResult<HermesValue> hermesInternalEnablePromiseRejectionTracker( |
622 | | void *, |
623 | | Runtime &runtime, |
624 | 0 | NativeArgs args) { |
625 | 0 | auto opts = args.getArgHandle(0); |
626 | 0 | auto func = Handle<Callable>::dyn_vmcast( |
627 | 0 | Handle<>(&runtime.promiseRejectionTrackingHook_)); |
628 | 0 | if (!func) { |
629 | 0 | return runtime.raiseTypeError( |
630 | 0 | "Promise rejection tracking hook was not registered"); |
631 | 0 | } |
632 | 0 | return Callable::executeCall1( |
633 | 0 | func, runtime, Runtime::getUndefinedValue(), opts.getHermesValue()) |
634 | 0 | .toCallResultHermesValue(); |
635 | 0 | } |
636 | | |
637 | | #ifdef HERMES_ENABLE_FUZZILLI |
638 | | |
639 | | /// Internal "fuzzilli" function used by the Fuzzilli fuzzer |
640 | | /// (https://github.com/googleprojectzero/fuzzilli) to sanity-check the engine. |
641 | | /// This function is conditionally defined in Hermes internal VM code rather |
642 | | /// than in an external fuzzing module so to catch build misconfigurations, e.g. |
643 | | /// we want to make sure that the VM is compiled with assertions enabled and |
644 | | /// doing this check out of the VM (e.g. in the fuzzing harness) doesn't |
645 | | /// guarantee that the VM has asserts on. |
646 | | /// |
647 | | /// This function is defined as follow: |
648 | | /// \code |
649 | | /// HermesInternal.fuzzilli = function(op, arg) {} |
650 | | /// \endcode |
651 | | /// The first argument "op", is a string specifying the operation to be |
652 | | /// performed. Currently supported values of "op" are "FUZZILLI_CRASH", used to |
653 | | /// simulate a crash, and "FUZZILLI_PRINT", used to send data over Fuzzilli's |
654 | | /// ata write file decriptor (REPRL_DWFD). The secong argument "arg" can be an |
655 | | /// integer specifying the type of crash (if op is "FUZZILLI_CRASH") or a string |
656 | | /// which value will be sent to fuzzilli (if op is "FUZZILLI_PRINT") |
657 | | CallResult<HermesValue> |
658 | | hermesInternalFuzzilli(void *, Runtime &runtime, NativeArgs args) { |
659 | | // REPRL = read-eval-print-reset-loop |
660 | | // This file descriptor is being opened by Fuzzilli |
661 | | constexpr int REPRL_DWFD = 103; // Data write file decriptor |
662 | | |
663 | | auto operationRes = toString_RJS(runtime, args.getArgHandle(0)); |
664 | | if (LLVM_UNLIKELY(operationRes == ExecutionStatus::EXCEPTION)) { |
665 | | return ExecutionStatus::EXCEPTION; |
666 | | } |
667 | | auto operation = StringPrimitive::createStringView( |
668 | | runtime, runtime.makeHandle(std::move(*operationRes))); |
669 | | |
670 | | if (operation.equals(createUTF16Ref(u"FUZZILLI_CRASH"))) { |
671 | | auto crashTypeRes = toIntegerOrInfinity(runtime, args.getArgHandle(1)); |
672 | | if (LLVM_UNLIKELY(crashTypeRes == ExecutionStatus::EXCEPTION)) { |
673 | | return ExecutionStatus::EXCEPTION; |
674 | | } |
675 | | switch (crashTypeRes->getNumberAs<int>()) { |
676 | | case 0: |
677 | | *((int *)0x41414141) = 0x1337; |
678 | | break; |
679 | | case 1: |
680 | | assert(0); |
681 | | break; |
682 | | case 2: |
683 | | std::abort(); |
684 | | break; |
685 | | } |
686 | | } else if (operation.equals(createUTF16Ref(u"FUZZILLI_PRINT"))) { |
687 | | static FILE *fzliout = fdopen(REPRL_DWFD, "w"); |
688 | | if (!fzliout) { |
689 | | fprintf( |
690 | | stderr, |
691 | | "Fuzzer output channel not available, printing to stdout instead\n"); |
692 | | fzliout = stdout; |
693 | | } |
694 | | |
695 | | auto printRes = toString_RJS(runtime, args.getArgHandle(1)); |
696 | | if (LLVM_UNLIKELY(printRes == ExecutionStatus::EXCEPTION)) { |
697 | | return ExecutionStatus::EXCEPTION; |
698 | | } |
699 | | auto print = StringPrimitive::createStringView( |
700 | | runtime, runtime.makeHandle(std::move(*printRes))); |
701 | | |
702 | | vm::SmallU16String<32> allocator; |
703 | | std::string outputString; |
704 | | ::hermes::convertUTF16ToUTF8WithReplacements( |
705 | | outputString, print.getUTF16Ref(allocator)); |
706 | | fprintf(fzliout, "%s\n", outputString.c_str()); |
707 | | fflush(fzliout); |
708 | | } |
709 | | |
710 | | return HermesValue::encodeUndefinedValue(); |
711 | | } |
712 | | #endif // HERMES_ENABLE_FUZZILLI |
713 | | |
714 | | static CallResult<HermesValue> |
715 | 0 | hermesInternalIsLazy(void *, Runtime &runtime, NativeArgs args) { |
716 | 0 | auto callable = args.dyncastArg<Callable>(0); |
717 | 0 | if (!callable) { |
718 | 0 | return HermesValue::encodeBoolValue(false); |
719 | 0 | } |
720 | | |
721 | 0 | auto codeBlock = getLeafCodeBlock(callable, runtime); |
722 | 0 | if (!codeBlock) { |
723 | | // Native function is never lazy. |
724 | 0 | return HermesValue::encodeBoolValue(false); |
725 | 0 | } |
726 | | |
727 | 0 | RuntimeModule *runtimeModule = codeBlock->getRuntimeModule(); |
728 | 0 | return HermesValue::encodeBoolValue( |
729 | 0 | runtimeModule && runtimeModule->getBytecode()->isLazy()); |
730 | 0 | } |
731 | | |
732 | | Handle<JSObject> createHermesInternalObject( |
733 | | Runtime &runtime, |
734 | 94 | const JSLibFlags &flags) { |
735 | 94 | namespace P = Predefined; |
736 | 94 | Handle<JSObject> intern = runtime.makeHandle(JSObject::create(runtime)); |
737 | 94 | GCScope gcScope{runtime}; |
738 | | |
739 | 94 | DefinePropertyFlags constantDPF = |
740 | 94 | DefinePropertyFlags::getDefaultNewPropertyFlags(); |
741 | 94 | constantDPF.enumerable = 0; |
742 | 94 | constantDPF.writable = 0; |
743 | 94 | constantDPF.configurable = 0; |
744 | | |
745 | 94 | auto defineInternMethod = |
746 | 1.12k | [&](Predefined::Str symID, NativeFunctionPtr func, uint8_t count = 0) { |
747 | 1.12k | (void)defineMethod( |
748 | 1.12k | runtime, |
749 | 1.12k | intern, |
750 | 1.12k | Predefined::getSymbolID(symID), |
751 | 1.12k | nullptr /* context */, |
752 | 1.12k | func, |
753 | 1.12k | count, |
754 | 1.12k | constantDPF); |
755 | 1.12k | }; |
756 | | |
757 | 94 | auto defineInternMethodAndSymbol = |
758 | 94 | [&](const char *name, NativeFunctionPtr func, uint8_t count = 0) { |
759 | 0 | ASCIIRef ref = createASCIIRef(name); |
760 | 0 | Handle<SymbolID> symHandle = runtime.ignoreAllocationFailure( |
761 | 0 | runtime.getIdentifierTable().getSymbolHandle(runtime, ref)); |
762 | 0 | (void)defineMethod( |
763 | 0 | runtime, |
764 | 0 | intern, |
765 | 0 | *symHandle, |
766 | 0 | nullptr /* context */, |
767 | 0 | func, |
768 | 0 | count, |
769 | 0 | constantDPF); |
770 | 0 | }; |
771 | | |
772 | | // suppress unused-variable warning |
773 | 94 | (void)defineInternMethodAndSymbol; |
774 | | |
775 | | // Make a copy of the original String.prototype.concat implementation that we |
776 | | // can use internally. |
777 | | // TODO: we can't make HermesInternal.concat a static builtin method now |
778 | | // because this method should be called with a meaningful `this`, but |
779 | | // CallBuiltin instruction does not support it. |
780 | 94 | auto propRes = JSObject::getNamed_RJS( |
781 | 94 | runtime.makeHandle<JSObject>(runtime.stringPrototype), |
782 | 94 | runtime, |
783 | 94 | Predefined::getSymbolID(Predefined::concat)); |
784 | 94 | assert( |
785 | 94 | propRes != ExecutionStatus::EXCEPTION && !(*propRes)->isUndefined() && |
786 | 94 | "Failed to get String.prototype.concat."); |
787 | 94 | auto putRes = JSObject::defineOwnProperty( |
788 | 94 | intern, |
789 | 94 | runtime, |
790 | 94 | Predefined::getSymbolID(Predefined::concat), |
791 | 94 | constantDPF, |
792 | 94 | runtime.makeHandle(std::move(*propRes))); |
793 | 94 | assert( |
794 | 94 | putRes != ExecutionStatus::EXCEPTION && *putRes && |
795 | 94 | "Failed to set HermesInternal.concat."); |
796 | 94 | (void)putRes; |
797 | | |
798 | | // HermesInternal functions that are known to be safe and are required to be |
799 | | // present by the VM internals even under a security-sensitive environment |
800 | | // where HermesInternal might be explicitly disabled. |
801 | 94 | defineInternMethod(P::hasPromise, hermesInternalHasPromise); |
802 | 94 | defineInternMethod(P::hasES6Class, hermesInternalHasES6Class); |
803 | 94 | defineInternMethod(P::enqueueJob, hermesInternalEnqueueJob); |
804 | 94 | defineInternMethod( |
805 | 94 | P::setPromiseRejectionTrackingHook, |
806 | 94 | hermesInternalSetPromiseRejectionTrackingHook); |
807 | 94 | defineInternMethod( |
808 | 94 | P::enablePromiseRejectionTracker, |
809 | 94 | hermesInternalEnablePromiseRejectionTracker); |
810 | 94 | defineInternMethod(P::useEngineQueue, hermesInternalUseEngineQueue); |
811 | | |
812 | | #ifdef HERMES_ENABLE_FUZZILLI |
813 | | defineInternMethod(P::fuzzilli, hermesInternalFuzzilli); |
814 | | #endif |
815 | | |
816 | | // All functions are known to be safe can be defined above this flag check. |
817 | 94 | if (!flags.enableHermesInternal) { |
818 | 0 | JSObject::preventExtensions(*intern); |
819 | 0 | return intern; |
820 | 0 | } |
821 | | |
822 | | // HermesInternal functions that are not necessarily required but are |
823 | | // generally considered harmless to be exposed by default. |
824 | 94 | defineInternMethod(P::getEpilogues, hermesInternalGetEpilogues); |
825 | 94 | defineInternMethod( |
826 | 94 | P::getRuntimeProperties, hermesInternalGetRuntimeProperties); |
827 | 94 | defineInternMethod(P::ttiReached, hermesInternalTTIReached); |
828 | 94 | defineInternMethod(P::ttrcReached, hermesInternalTTRCReached); |
829 | 94 | defineInternMethod(P::getFunctionLocation, hermesInternalGetFunctionLocation); |
830 | | |
831 | 94 | if (LLVM_UNLIKELY(runtime.traceMode != SynthTraceMode::None)) { |
832 | | // Use getNewNonEnumerableFlags() so that getInstrumentedStats can be |
833 | | // overridden for synth trace case. See TracingRuntime.cpp. |
834 | 0 | (void)defineMethod( |
835 | 0 | runtime, |
836 | 0 | intern, |
837 | 0 | Predefined::getSymbolID(P::getInstrumentedStats), |
838 | 0 | nullptr /* context */, |
839 | 0 | hermesInternalGetInstrumentedStats, |
840 | 0 | 0, |
841 | 0 | DefinePropertyFlags::getNewNonEnumerableFlags()); |
842 | 94 | } else { |
843 | 94 | defineInternMethod( |
844 | 94 | P::getInstrumentedStats, hermesInternalGetInstrumentedStats); |
845 | 94 | } |
846 | | |
847 | | // HermesInternal function that are only meant to be used for testing purpose. |
848 | | // They can change language semantics and are security risks. |
849 | 94 | if (flags.enableHermesInternalTestMethods) { |
850 | 0 | defineInternMethod( |
851 | 0 | P::detachArrayBuffer, hermesInternalDetachArrayBuffer, 1); |
852 | 0 | defineInternMethod(P::getWeakSize, hermesInternalGetWeakSize); |
853 | 0 | defineInternMethod( |
854 | 0 | P::copyDataProperties, hermesBuiltinCopyDataProperties, 3); |
855 | 0 | defineInternMethodAndSymbol("isProxy", hermesInternalIsProxy); |
856 | 0 | defineInternMethodAndSymbol("isLazy", hermesInternalIsLazy); |
857 | 0 | defineInternMethod(P::drainJobs, hermesInternalDrainJobs); |
858 | 0 | } |
859 | | |
860 | | #ifdef HERMESVM_EXCEPTION_ON_OOM |
861 | | defineInternMethodAndSymbol("getCallStack", hermesInternalGetCallStack, 0); |
862 | | #endif // HERMESVM_EXCEPTION_ON_OOM |
863 | | |
864 | 94 | JSObject::preventExtensions(*intern); |
865 | | |
866 | 94 | return intern; |
867 | 94 | } |
868 | | |
869 | | } // namespace vm |
870 | | } // namespace hermes |