/src/hermes/lib/VM/JSError.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 "hermes/VM/JSError.h" |
9 | | |
10 | | #include "hermes/BCGen/HBC/DebugInfo.h" |
11 | | #include "hermes/Support/OptValue.h" |
12 | | #include "hermes/VM/BuildMetadata.h" |
13 | | #include "hermes/VM/Callable.h" |
14 | | #include "hermes/VM/JSArray.h" |
15 | | #include "hermes/VM/JSCallSite.h" |
16 | | #include "hermes/VM/PropertyAccessor.h" |
17 | | #include "hermes/VM/RuntimeModule-inline.h" |
18 | | #include "hermes/VM/StackFrame-inline.h" |
19 | | #include "hermes/VM/StringBuilder.h" |
20 | | #include "hermes/VM/StringView.h" |
21 | | |
22 | | #include "llvh/ADT/ScopeExit.h" |
23 | | #pragma GCC diagnostic push |
24 | | |
25 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
26 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
27 | | #endif |
28 | | namespace hermes { |
29 | | namespace vm { |
30 | | //===----------------------------------------------------------------------===// |
31 | | // class JSError |
32 | | |
33 | | const ObjectVTable JSError::vt{ |
34 | | VTable( |
35 | | CellKind::JSErrorKind, |
36 | | cellSize<JSError>(), |
37 | | JSError::_finalizeImpl, |
38 | | JSError::_mallocSizeImpl), |
39 | | JSError::_getOwnIndexedRangeImpl, |
40 | | JSError::_haveOwnIndexedImpl, |
41 | | JSError::_getOwnIndexedPropertyFlagsImpl, |
42 | | JSError::_getOwnIndexedImpl, |
43 | | JSError::_setOwnIndexedImpl, |
44 | | JSError::_deleteOwnIndexedImpl, |
45 | | JSError::_checkAllOwnIndexedImpl, |
46 | | }; |
47 | | |
48 | 1 | void JSErrorBuildMeta(const GCCell *cell, Metadata::Builder &mb) { |
49 | 1 | mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSError>()); |
50 | 1 | JSObjectBuildMeta(cell, mb); |
51 | 1 | const auto *self = static_cast<const JSError *>(cell); |
52 | 1 | mb.setVTable(&JSError::vt); |
53 | 1 | mb.addField("funcNames", &self->funcNames_); |
54 | 1 | mb.addField("domains", &self->domains_); |
55 | 1 | } |
56 | | |
57 | | /// Given an object \p targetHandle which may be nullptr: |
58 | | /// 1. Look for [[CapturedError]] in the object or its prototype chain and |
59 | | /// return it a JSError. |
60 | | /// 2. Otherwise, return llvh::None. |
61 | | static llvh::Optional<Handle<JSError>> getErrorFromStackTarget( |
62 | | Runtime &runtime, |
63 | 23 | Handle<JSObject> targetHandle) { |
64 | 23 | MutableHandle<JSObject> mutHnd = |
65 | 23 | runtime.makeMutableHandle<JSObject>(targetHandle.get()); |
66 | 23 | targetHandle = mutHnd; |
67 | | |
68 | 23 | while (targetHandle) { |
69 | 23 | NamedPropertyDescriptor desc; |
70 | 23 | bool exists = JSObject::getOwnNamedDescriptor( |
71 | 23 | targetHandle, |
72 | 23 | runtime, |
73 | 23 | Predefined::getSymbolID(Predefined::InternalPropertyCapturedError), |
74 | 23 | desc); |
75 | 23 | if (exists) { |
76 | 0 | auto sv = JSObject::getNamedSlotValueUnsafe(*targetHandle, runtime, desc); |
77 | 0 | return runtime.makeHandle(vmcast<JSError>(sv.getObject(runtime))); |
78 | 0 | } |
79 | 23 | if (vmisa<JSError>(*targetHandle)) { |
80 | 23 | return Handle<JSError>::vmcast(targetHandle); |
81 | 23 | } |
82 | | |
83 | 0 | mutHnd.set(targetHandle->getParent(runtime)); |
84 | 0 | } |
85 | 0 | return llvh::None; |
86 | 23 | } |
87 | | |
88 | | CallResult<HermesValue> |
89 | 23 | errorStackGetter(void *, Runtime &runtime, NativeArgs args) { |
90 | 23 | GCScope gcScope(runtime); |
91 | | |
92 | 23 | auto targetHandle = args.dyncastThis<JSObject>(); |
93 | 23 | auto errorHandleOpt = getErrorFromStackTarget(runtime, targetHandle); |
94 | 23 | if (!errorHandleOpt.hasValue()) { |
95 | 0 | return HermesValue::encodeUndefinedValue(); |
96 | 0 | } |
97 | 23 | auto errorHandle = *errorHandleOpt; |
98 | 23 | if (!errorHandle->stacktrace_) { |
99 | | // Stacktrace has not been set, we simply return empty string. |
100 | | // This is different from other VMs where stacktrace is created when |
101 | | // the error object is created. We only set it when the error |
102 | | // is raised. |
103 | 0 | return HermesValue::encodeStringValue( |
104 | 0 | runtime.getPredefinedString(Predefined::emptyString)); |
105 | 0 | } |
106 | | // It's possible we're getting the stack for a stack overflow |
107 | | // RangeError. Allow ourselves a little extra room to do this. |
108 | 23 | vm::ScopedNativeDepthReducer reducer(runtime); |
109 | 23 | SmallU16String<32> stack; |
110 | | |
111 | 23 | auto errorCtor = Handle<JSObject>::vmcast(&runtime.errorConstructor); |
112 | | |
113 | 23 | auto prepareStackTraceRes = JSObject::getNamed_RJS( |
114 | 23 | errorCtor, |
115 | 23 | runtime, |
116 | 23 | Predefined::getSymbolID(Predefined::prepareStackTrace), |
117 | 23 | PropOpFlags().plusThrowOnError()); |
118 | | |
119 | 23 | if (prepareStackTraceRes == ExecutionStatus::EXCEPTION) { |
120 | 0 | return ExecutionStatus::EXCEPTION; |
121 | 0 | } |
122 | | |
123 | 23 | MutableHandle<> stackTraceFormatted{runtime}; |
124 | | |
125 | 23 | auto prepareStackTrace = Handle<Callable>::dyn_vmcast( |
126 | 23 | runtime.makeHandle(std::move(*prepareStackTraceRes))); |
127 | 23 | if (LLVM_UNLIKELY(prepareStackTrace && !runtime.formattingStackTrace())) { |
128 | 0 | const auto &recursionGuard = llvh::make_scope_exit( |
129 | 0 | [&runtime]() { runtime.setFormattingStackTrace(false); }); |
130 | 0 | (void)recursionGuard; |
131 | |
|
132 | 0 | runtime.setFormattingStackTrace(true); |
133 | |
|
134 | 0 | auto callSitesRes = JSError::constructCallSitesArray(runtime, errorHandle); |
135 | |
|
136 | 0 | if (LLVM_UNLIKELY(callSitesRes == ExecutionStatus::EXCEPTION)) { |
137 | 0 | return ExecutionStatus::EXCEPTION; |
138 | 0 | } |
139 | 0 | auto prepareRes = Callable::executeCall2( |
140 | 0 | prepareStackTrace, |
141 | 0 | runtime, |
142 | 0 | runtime.getNullValue(), |
143 | 0 | targetHandle.getHermesValue(), |
144 | 0 | *callSitesRes); |
145 | 0 | if (LLVM_UNLIKELY(prepareRes == ExecutionStatus::EXCEPTION)) { |
146 | 0 | return ExecutionStatus::EXCEPTION; |
147 | 0 | } |
148 | 0 | stackTraceFormatted = std::move(*prepareRes); |
149 | 23 | } else { |
150 | 23 | if (JSError::constructStackTraceString_RJS( |
151 | 23 | runtime, errorHandle, targetHandle, stack) == |
152 | 23 | ExecutionStatus::EXCEPTION) { |
153 | 0 | return ExecutionStatus::EXCEPTION; |
154 | 0 | } |
155 | | |
156 | 23 | auto strRes = StringPrimitive::create(runtime, stack); |
157 | 23 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
158 | | // StringPrimitive creation can throw if the stacktrace string is too |
159 | | // long. In that case, we replace it with a predefined string. |
160 | 0 | stackTraceFormatted = HermesValue::encodeStringValue( |
161 | 0 | runtime.getPredefinedString(Predefined::stacktraceTooLong)); |
162 | 0 | runtime.clearThrownValue(); |
163 | 23 | } else { |
164 | 23 | stackTraceFormatted = std::move(*strRes); |
165 | 23 | } |
166 | 23 | } |
167 | | |
168 | | // We no longer need the accessor. Redefine the stack property to a regular |
169 | | // property. |
170 | 23 | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
171 | 23 | if (JSObject::defineOwnProperty( |
172 | 23 | targetHandle, |
173 | 23 | runtime, |
174 | 23 | Predefined::getSymbolID(Predefined::stack), |
175 | 23 | dpf, |
176 | 23 | stackTraceFormatted) == ExecutionStatus::EXCEPTION) { |
177 | 0 | return ExecutionStatus::EXCEPTION; |
178 | 0 | } |
179 | 23 | return *stackTraceFormatted; |
180 | 23 | } |
181 | | |
182 | | CallResult<HermesValue> |
183 | 0 | errorStackSetter(void *, Runtime &runtime, NativeArgs args) { |
184 | 0 | auto res = toObject(runtime, args.getThisHandle()); |
185 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
186 | 0 | return ExecutionStatus::EXCEPTION; |
187 | 0 | } |
188 | 0 | auto selfHandle = runtime.makeHandle<JSObject>(res.getValue()); |
189 | | |
190 | | // Redefines the stack property to a regular property. |
191 | 0 | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
192 | 0 | if (JSObject::defineOwnProperty( |
193 | 0 | selfHandle, |
194 | 0 | runtime, |
195 | 0 | Predefined::getSymbolID(Predefined::stack), |
196 | 0 | dpf, |
197 | 0 | args.getArgHandle(0)) == ExecutionStatus::EXCEPTION) { |
198 | 0 | return ExecutionStatus::EXCEPTION; |
199 | 0 | } |
200 | | |
201 | 0 | return HermesValue::encodeUndefinedValue(); |
202 | 0 | } |
203 | | |
204 | | PseudoHandle<JSError> JSError::create( |
205 | | Runtime &runtime, |
206 | 23 | Handle<JSObject> parentHandle) { |
207 | 23 | return create(runtime, parentHandle, /*catchable*/ true); |
208 | 23 | } |
209 | | |
210 | | PseudoHandle<JSError> JSError::createUncatchable( |
211 | | Runtime &runtime, |
212 | 0 | Handle<JSObject> parentHandle) { |
213 | 0 | return create(runtime, parentHandle, /*catchable*/ false); |
214 | 0 | } |
215 | | |
216 | | PseudoHandle<JSError> JSError::create( |
217 | | Runtime &runtime, |
218 | | Handle<JSObject> parentHandle, |
219 | 23 | bool catchable) { |
220 | 23 | auto *cell = runtime.makeAFixed<JSError, HasFinalizer::Yes>( |
221 | 23 | runtime, |
222 | 23 | parentHandle, |
223 | 23 | runtime.getHiddenClassForPrototype( |
224 | 23 | *parentHandle, numOverlapSlots<JSError>()), |
225 | 23 | catchable); |
226 | 23 | return JSObjectInit::initToPseudoHandle(runtime, cell); |
227 | 23 | } |
228 | | |
229 | | CallResult<Handle<StringPrimitive>> JSError::toString( |
230 | | Handle<JSObject> O, |
231 | 23 | Runtime &runtime) { |
232 | | // 20.5.3.4 Error.prototype.toString ( ) |
233 | | |
234 | | // 1. and 2. don't apply -- O is already an Object. |
235 | | |
236 | | // 3. Let name be ? Get(O, "name"). |
237 | 23 | auto propRes = JSObject::getNamed_RJS( |
238 | 23 | O, runtime, Predefined::getSymbolID(Predefined::name), PropOpFlags()); |
239 | 23 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
240 | 0 | return ExecutionStatus::EXCEPTION; |
241 | 0 | } |
242 | 23 | Handle<> name = runtime.makeHandle(std::move(*propRes)); |
243 | | |
244 | | // 4. If name is undefined, set name to "Error"; otherwise set name to ? |
245 | | // ToString(name). |
246 | 23 | MutableHandle<StringPrimitive> nameStr{runtime}; |
247 | 23 | if (name->isUndefined()) { |
248 | 0 | nameStr = runtime.getPredefinedString(Predefined::Error); |
249 | 23 | } else { |
250 | 23 | auto strRes = toString_RJS(runtime, name); |
251 | 23 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
252 | 0 | return ExecutionStatus::EXCEPTION; |
253 | 0 | } |
254 | 23 | nameStr = strRes->get(); |
255 | 23 | } |
256 | | |
257 | | // 5. Let msg be ? Get(O, "message"). |
258 | 23 | if (LLVM_UNLIKELY( |
259 | 23 | (propRes = JSObject::getNamed_RJS( |
260 | 23 | O, |
261 | 23 | runtime, |
262 | 23 | Predefined::getSymbolID(Predefined::message), |
263 | 23 | PropOpFlags())) == ExecutionStatus::EXCEPTION)) { |
264 | 0 | return ExecutionStatus::EXCEPTION; |
265 | 0 | } |
266 | 23 | Handle<> msg = runtime.makeHandle(std::move(*propRes)); |
267 | | |
268 | | // 6. If msg is undefined, set msg to the empty String; |
269 | | // otherwise set msg to ? ToString(msg). |
270 | 23 | MutableHandle<StringPrimitive> msgStr{runtime}; |
271 | 23 | if (msg->isUndefined()) { |
272 | | // If msg is undefined, then let msg be the empty String. |
273 | 0 | msgStr = runtime.getPredefinedString(Predefined::emptyString); |
274 | 23 | } else { |
275 | 23 | auto strRes = toString_RJS(runtime, msg); |
276 | 23 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
277 | 0 | return ExecutionStatus::EXCEPTION; |
278 | 0 | } |
279 | 23 | msgStr = strRes->get(); |
280 | 23 | } |
281 | | |
282 | | // 7. If name is the empty String, return msg. |
283 | 23 | if (nameStr->getStringLength() == 0) { |
284 | 0 | return msgStr; |
285 | 0 | } |
286 | | |
287 | | // 8. If msg is the empty String, return name. |
288 | 23 | if (msgStr->getStringLength() == 0) { |
289 | 0 | return nameStr; |
290 | 0 | } |
291 | | |
292 | | // 9. Return the string-concatenation of name, the code unit 0x003A (COLON), |
293 | | // the code unit 0x0020 (SPACE), and msg. |
294 | 23 | SafeUInt32 length{nameStr->getStringLength()}; |
295 | 23 | length.add(2); |
296 | 23 | length.add(msgStr->getStringLength()); |
297 | 23 | CallResult<StringBuilder> builderRes = |
298 | 23 | StringBuilder::createStringBuilder(runtime, length); |
299 | 23 | if (LLVM_UNLIKELY(builderRes == ExecutionStatus::EXCEPTION)) { |
300 | 0 | return ExecutionStatus::EXCEPTION; |
301 | 0 | } |
302 | 23 | auto builder = std::move(*builderRes); |
303 | 23 | builder.appendStringPrim(nameStr); |
304 | 23 | builder.appendASCIIRef({": ", 2}); |
305 | 23 | builder.appendStringPrim(msgStr); |
306 | 23 | return builder.getStringPrimitive(); |
307 | 23 | } |
308 | | |
309 | | ExecutionStatus JSError::setMessage( |
310 | | Handle<JSError> selfHandle, |
311 | | Runtime &runtime, |
312 | 23 | Handle<> message) { |
313 | 23 | auto stringMessage = Handle<StringPrimitive>::dyn_vmcast(message); |
314 | 23 | if (LLVM_UNLIKELY(!stringMessage)) { |
315 | 0 | auto strRes = toString_RJS(runtime, message); |
316 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
317 | 0 | return ExecutionStatus::EXCEPTION; |
318 | 0 | } |
319 | 0 | stringMessage = runtime.makeHandle(std::move(*strRes)); |
320 | 0 | } |
321 | | |
322 | 23 | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
323 | 23 | return JSObject::defineOwnProperty( |
324 | 23 | selfHandle, |
325 | 23 | runtime, |
326 | 23 | Predefined::getSymbolID(Predefined::message), |
327 | 23 | dpf, |
328 | 23 | stringMessage) |
329 | 23 | .getStatus(); |
330 | 23 | } |
331 | | |
332 | | /// \return a list of function names associated with the call stack \p frames. |
333 | | /// Function names are first read out of 'displayName', followed by the 'name' |
334 | | /// property of each Callable on the stack. Accessors are skipped. If a |
335 | | /// Callable does not have a name, or if the name is an accessor, undefined is |
336 | | /// set. Names are returned in reverse order (topmost frame is first). |
337 | | /// In case of error returns a nullptr handle. |
338 | | /// \param skipTopFrame if true, skip the top frame. |
339 | | static Handle<PropStorage> getCallStackFunctionNames( |
340 | | Runtime &runtime, |
341 | | bool skipTopFrame, |
342 | 23 | size_t sizeHint) { |
343 | 23 | auto arrRes = PropStorage::create(runtime, sizeHint); |
344 | 23 | if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { |
345 | 0 | runtime.clearThrownValue(); |
346 | 0 | return Runtime::makeNullHandle<PropStorage>(); |
347 | 0 | } |
348 | 23 | MutableHandle<PropStorage> names{runtime, vmcast<PropStorage>(*arrRes)}; |
349 | | |
350 | 23 | GCScope gcScope(runtime); |
351 | 23 | MutableHandle<> name{runtime}; |
352 | 23 | auto marker = gcScope.createMarker(); |
353 | | |
354 | 23 | uint32_t frameIndex = 0; |
355 | 23 | uint32_t namesIndex = 0; |
356 | 24 | for (StackFramePtr cf : runtime.getStackFrames()) { |
357 | 24 | if (frameIndex++ == 0 && skipTopFrame) |
358 | 0 | continue; |
359 | | |
360 | 24 | name = HermesValue::encodeUndefinedValue(); |
361 | 24 | if (auto callableHandle = Handle<Callable>::dyn_vmcast( |
362 | 24 | Handle<>(&cf.getCalleeClosureOrCBRef()))) { |
363 | 24 | NamedPropertyDescriptor desc; |
364 | 24 | JSObject *propObj = JSObject::getNamedDescriptorPredefined( |
365 | 24 | callableHandle, runtime, Predefined::displayName, desc); |
366 | | |
367 | 24 | if (!propObj) { |
368 | 24 | propObj = JSObject::getNamedDescriptorPredefined( |
369 | 24 | callableHandle, runtime, Predefined::name, desc); |
370 | 24 | } |
371 | | |
372 | 24 | if (propObj && !desc.flags.accessor && |
373 | 24 | LLVM_LIKELY(!desc.flags.proxyObject) && |
374 | 24 | LLVM_LIKELY(!desc.flags.hostObject)) { |
375 | 24 | name = JSObject::getNamedSlotValueUnsafe(propObj, runtime, desc) |
376 | 24 | .unboxToHV(runtime); |
377 | 24 | } else if (desc.flags.proxyObject) { |
378 | 0 | name = HermesValue::encodeStringValue( |
379 | 0 | runtime.getPredefinedString(Predefined::proxyTrap)); |
380 | 0 | } |
381 | 24 | } else if (!cf.getCalleeClosureOrCBRef().isObject()) { |
382 | | // If CalleeClosureOrCB is not an object pointer, then it must be a native |
383 | | // pointer to a CodeBlock. |
384 | 0 | auto *cb = |
385 | 0 | cf.getCalleeClosureOrCBRef().getNativePointer<const CodeBlock>(); |
386 | 0 | if (cb->getNameMayAllocate().isValid()) |
387 | 0 | name = HermesValue::encodeStringValue( |
388 | 0 | runtime.getStringPrimFromSymbolID(cb->getNameMayAllocate())); |
389 | 0 | } |
390 | 24 | if (PropStorage::resize(names, runtime, namesIndex + 1) == |
391 | 24 | ExecutionStatus::EXCEPTION) { |
392 | 0 | runtime.clearThrownValue(); |
393 | 0 | return Runtime::makeNullHandle<PropStorage>(); |
394 | 0 | } |
395 | 24 | auto shv = |
396 | 24 | SmallHermesValue::encodeHermesValue(name.getHermesValue(), runtime); |
397 | 24 | names->set(namesIndex, shv, runtime.getHeap()); |
398 | 24 | ++namesIndex; |
399 | 24 | gcScope.flushToMarker(marker); |
400 | 24 | } |
401 | | |
402 | 23 | return std::move(names); |
403 | 23 | } |
404 | | |
405 | | ExecutionStatus JSError::recordStackTrace( |
406 | | Handle<JSError> selfHandle, |
407 | | Runtime &runtime, |
408 | | bool skipTopFrame, |
409 | | CodeBlock *codeBlock, |
410 | 45 | const Inst *ip) { |
411 | 45 | if (selfHandle->stacktrace_) |
412 | 0 | return ExecutionStatus::RETURNED; |
413 | | |
414 | 45 | auto frames = runtime.getStackFrames(); |
415 | | |
416 | | // Check if the top frame is a JSFunction and we don't have the current |
417 | | // CodeBlock, do nothing. |
418 | 45 | if (!skipTopFrame && !codeBlock && frames.begin() != frames.end() && |
419 | 45 | frames.begin()->getCalleeCodeBlock(runtime)) { |
420 | 22 | return ExecutionStatus::RETURNED; |
421 | 22 | } |
422 | | |
423 | 23 | StackTracePtr stack{new StackTrace()}; |
424 | 23 | auto domainsRes = ArrayStorageSmall::create(runtime, 1); |
425 | 23 | if (LLVM_UNLIKELY(domainsRes == ExecutionStatus::EXCEPTION)) { |
426 | 0 | return ExecutionStatus::EXCEPTION; |
427 | 0 | } |
428 | 23 | auto domains = runtime.makeMutableHandle<ArrayStorageSmall>( |
429 | 23 | vmcast<ArrayStorageSmall>(*domainsRes)); |
430 | | |
431 | | // Add the domain to the domains list, provided that it's not the same as the |
432 | | // last domain in the list. This allows us to save storage with a constant |
433 | | // time check, but we don't have to loop through and check every domain to |
434 | | // deduplicate. |
435 | 23 | auto addDomain = [&domains, |
436 | 23 | &runtime](CodeBlock *codeBlock) -> ExecutionStatus { |
437 | 23 | GCScopeMarkerRAII marker{runtime}; |
438 | 23 | Handle<Domain> domain = codeBlock->getRuntimeModule()->getDomain(runtime); |
439 | 23 | if (domains->size() > 0 && |
440 | 23 | vmcast<Domain>(domains->at(domains->size() - 1).getObject(runtime)) == |
441 | 0 | domain.get()) { |
442 | 0 | return ExecutionStatus::RETURNED; |
443 | 0 | } |
444 | 23 | return ArrayStorageSmall::push_back(domains, runtime, domain); |
445 | 23 | }; |
446 | | |
447 | 23 | if (!skipTopFrame) { |
448 | 23 | if (codeBlock) { |
449 | 22 | stack->emplace_back(codeBlock, codeBlock->getOffsetOf(ip)); |
450 | 22 | if (LLVM_UNLIKELY(addDomain(codeBlock) == ExecutionStatus::EXCEPTION)) { |
451 | 0 | return ExecutionStatus::EXCEPTION; |
452 | 0 | } |
453 | 22 | } else { |
454 | 1 | stack->emplace_back(nullptr, 0); |
455 | 1 | } |
456 | 23 | } |
457 | | |
458 | 23 | const StackFramePtr framesEnd = *runtime.getStackFrames().end(); |
459 | | |
460 | | // Fill in the call stack. |
461 | | // Each stack frame tracks information about the caller. |
462 | 24 | for (StackFramePtr cf : runtime.getStackFrames()) { |
463 | 24 | CodeBlock *savedCodeBlock = cf.getSavedCodeBlock(); |
464 | 24 | const Inst *const savedIP = cf.getSavedIP(); |
465 | | // Go up one frame and get the callee code block but use the current |
466 | | // frame's saved IP. This also allows us to account for bound functions, |
467 | | // which have savedCodeBlock == nullptr in order to allow proper returns in |
468 | | // the interpreter. |
469 | 24 | StackFramePtr prev = cf->getPreviousFrame(); |
470 | 24 | if (prev != framesEnd) { |
471 | 1 | if (CodeBlock *parentCB = prev->getCalleeCodeBlock(runtime)) { |
472 | 1 | savedCodeBlock = parentCB; |
473 | 1 | } |
474 | 1 | } |
475 | 24 | if (savedCodeBlock && savedIP) { |
476 | 1 | stack->emplace_back(savedCodeBlock, savedCodeBlock->getOffsetOf(savedIP)); |
477 | 1 | if (LLVM_UNLIKELY( |
478 | 1 | addDomain(savedCodeBlock) == ExecutionStatus::EXCEPTION)) { |
479 | 0 | return ExecutionStatus::EXCEPTION; |
480 | 0 | } |
481 | 23 | } else { |
482 | 23 | stack->emplace_back(nullptr, 0); |
483 | 23 | } |
484 | 24 | } |
485 | 23 | selfHandle->domains_.set(runtime, domains.get(), runtime.getHeap()); |
486 | | |
487 | | // Remove the last entry. |
488 | 23 | stack->pop_back(); |
489 | | |
490 | 23 | auto funcNames = |
491 | 23 | getCallStackFunctionNames(runtime, skipTopFrame, stack->size()); |
492 | | |
493 | | // Either the function names is empty, or they have the same count. |
494 | 23 | assert( |
495 | 23 | (!funcNames || funcNames->size() == stack->size()) && |
496 | 23 | "Function names and stack trace must have same size."); |
497 | | |
498 | 23 | selfHandle->stacktrace_ = std::move(stack); |
499 | 23 | selfHandle->funcNames_.set(runtime, *funcNames, runtime.getHeap()); |
500 | 23 | return ExecutionStatus::RETURNED; |
501 | 23 | } |
502 | | |
503 | | /// Given a codeblock and opcode offset, \returns the debug information. |
504 | | OptValue<hbc::DebugSourceLocation> JSError::getDebugInfo( |
505 | | CodeBlock *codeBlock, |
506 | 23 | uint32_t bytecodeOffset) { |
507 | 23 | auto offset = codeBlock->getDebugSourceLocationsOffset(); |
508 | 23 | if (!offset.hasValue()) { |
509 | 0 | return llvh::None; |
510 | 0 | } |
511 | | |
512 | 23 | return codeBlock->getRuntimeModule() |
513 | 23 | ->getBytecode() |
514 | 23 | ->getDebugInfo() |
515 | 23 | ->getLocationForAddress(offset.getValue(), bytecodeOffset); |
516 | 23 | } |
517 | | |
518 | | Handle<StringPrimitive> JSError::getFunctionNameAtIndex( |
519 | | Runtime &runtime, |
520 | | Handle<JSError> selfHandle, |
521 | 24 | size_t index) { |
522 | 24 | IdentifierTable &idt = runtime.getIdentifierTable(); |
523 | 24 | MutableHandle<StringPrimitive> name{ |
524 | 24 | runtime, runtime.getPredefinedString(Predefined::emptyString)}; |
525 | | |
526 | | // If funcNames_ is set and contains a string primitive, use that. |
527 | 24 | if (selfHandle->funcNames_) { |
528 | 24 | assert( |
529 | 24 | index < selfHandle->funcNames_.getNonNull(runtime)->size() && |
530 | 24 | "Index out of bounds"); |
531 | 24 | name = dyn_vmcast<StringPrimitive>( |
532 | 24 | selfHandle->funcNames_.getNonNull(runtime)->at(index).unboxToHV( |
533 | 24 | runtime)); |
534 | 24 | } |
535 | | |
536 | 24 | if (!name || name->getStringLength() == 0) { |
537 | | // We did not have an explicit function name, or it was not a nonempty |
538 | | // string. If we have a code block, try its debug info. |
539 | 0 | if (const CodeBlock *codeBlock = |
540 | 0 | selfHandle->stacktrace_->at(index).codeBlock) { |
541 | 0 | name = idt.getStringPrim(runtime, codeBlock->getNameMayAllocate()); |
542 | 0 | } |
543 | 0 | } |
544 | | |
545 | 24 | if (!name || name->getStringLength() == 0) { |
546 | 0 | return runtime.makeNullHandle<StringPrimitive>(); |
547 | 0 | } |
548 | | |
549 | 24 | return std::move(name); |
550 | 24 | } |
551 | | |
552 | | bool JSError::appendFunctionNameAtIndex( |
553 | | Runtime &runtime, |
554 | | Handle<JSError> selfHandle, |
555 | | size_t index, |
556 | 24 | llvh::SmallVectorImpl<char16_t> &str) { |
557 | 24 | auto name = getFunctionNameAtIndex(runtime, selfHandle, index); |
558 | | |
559 | 24 | if (!name) |
560 | 0 | return false; |
561 | | |
562 | 24 | name->appendUTF16String(str); |
563 | 24 | return true; |
564 | 24 | } |
565 | | |
566 | | ExecutionStatus JSError::constructStackTraceString_RJS( |
567 | | Runtime &runtime, |
568 | | Handle<JSError> selfHandle, |
569 | | Handle<JSObject> targetHandle, |
570 | 23 | SmallU16String<32> &stack) { |
571 | | // This method potentially runs javascript, so we need to protect it agains |
572 | | // stack overflow. |
573 | 23 | ScopedNativeDepthTracker depthTracker(runtime); |
574 | 23 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
575 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
576 | 0 | } |
577 | | |
578 | 23 | GCScope gcScope(runtime); |
579 | | // First of all, the stacktrace string starts with |
580 | | // %Error.prototype.toString%(target). |
581 | 23 | CallResult<Handle<StringPrimitive>> res = |
582 | 23 | JSError::toString(targetHandle, runtime); |
583 | | // Keep track whether targetHandle.toString() threw. If it did, the error |
584 | | // message will contain a string letting the user know that something went |
585 | | // awry. |
586 | 23 | const bool targetHandleToStringThrew = res == ExecutionStatus::EXCEPTION; |
587 | | |
588 | 23 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
589 | | // target.toString() threw an exception; if it is a catchable error try to |
590 | | // toString() it so the user has some indication of what went wrong. |
591 | 0 | if (!isUncatchableError(runtime.getThrownValue())) { |
592 | 0 | HermesValue thrownValue = runtime.getThrownValue(); |
593 | 0 | if (thrownValue.isObject()) { |
594 | | // Clear the pending exception, and try to convert thrownValue to string |
595 | | // with %Error.prototype.toString%(thrownValue). |
596 | 0 | runtime.clearThrownValue(); |
597 | 0 | res = JSError::toString( |
598 | 0 | runtime.makeHandle<JSObject>(thrownValue), runtime); |
599 | 0 | } |
600 | 0 | } |
601 | 0 | } |
602 | | |
603 | 23 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
604 | | // An exception happened while trying to get the description for the error. |
605 | 0 | if (isUncatchableError(runtime.getThrownValue())) { |
606 | | // If JSError::toString throws an uncatchable exception, bubble it up. |
607 | 0 | return ExecutionStatus::EXCEPTION; |
608 | 0 | } |
609 | | // Clear the pending exception so the caller doesn't observe this side |
610 | | // effect. |
611 | 0 | runtime.clearThrownValue(); |
612 | | // Append a generic <error> string and move on. |
613 | 0 | stack.append(u"<error>"); |
614 | 23 | } else { |
615 | 23 | if (targetHandleToStringThrew) { |
616 | 0 | stack.append(u"<while converting error to string: "); |
617 | 0 | } |
618 | 23 | res->get()->appendUTF16String(stack); |
619 | 23 | if (targetHandleToStringThrew) { |
620 | 0 | stack.append(u">"); |
621 | 0 | } |
622 | 23 | } |
623 | | |
624 | | // Virtual offsets are computed by walking the list of bytecode functions. If |
625 | | // we have an extremely deep stack, this could get expensive. Assume that very |
626 | | // deep stacks are most likely due to runaway recursion and so use a local |
627 | | // cache of virtual offsets. |
628 | 23 | llvh::DenseMap<const CodeBlock *, uint32_t> virtualOffsetCache; |
629 | | |
630 | | // Append each function location in the call stack to stack trace. |
631 | 23 | auto marker = gcScope.createMarker(); |
632 | 23 | for (size_t index = 0, |
633 | 23 | max = selfHandle->stacktrace_->size() - |
634 | 23 | selfHandle->firstExposedFrameIndex_; |
635 | 47 | index < max; |
636 | 24 | index++) { |
637 | 24 | char buf[NUMBER_TO_STRING_BUF_SIZE]; |
638 | | |
639 | | // If the trace contains more than 100 entries, limit the string to the |
640 | | // first 50 and the last 50 entries and include a line about the truncation. |
641 | 24 | static constexpr unsigned PRINT_HEAD = 50; |
642 | 24 | static constexpr unsigned PRINT_TAIL = 50; |
643 | 24 | if (LLVM_UNLIKELY(max > PRINT_HEAD + PRINT_TAIL)) { |
644 | 0 | if (index == PRINT_HEAD) { |
645 | 0 | stack.append("\n ... skipping "); |
646 | 0 | numberToString(max - PRINT_HEAD - PRINT_TAIL, buf, sizeof(buf)); |
647 | 0 | stack.append(buf); |
648 | 0 | stack.append(" frames"); |
649 | 0 | continue; |
650 | 0 | } |
651 | | |
652 | | // Skip the middle frames. |
653 | 0 | if (index > PRINT_HEAD && index < max - PRINT_TAIL) { |
654 | 0 | index = max - PRINT_TAIL; |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | 24 | const size_t absIndex = index + selfHandle->firstExposedFrameIndex_; |
659 | 24 | const StackTraceInfo &sti = selfHandle->stacktrace_->at(absIndex); |
660 | 24 | gcScope.flushToMarker(marker); |
661 | | // For each stacktrace entry, we add a line with the following format: |
662 | | // at <functionName> (<fileName>:<lineNo>:<columnNo>) |
663 | | |
664 | 24 | stack.append(u"\n at "); |
665 | | |
666 | 24 | if (!appendFunctionNameAtIndex(runtime, selfHandle, absIndex, stack)) |
667 | 0 | stack.append(u"anonymous"); |
668 | | |
669 | | // If we have a null codeBlock, it's a native function, which do not have |
670 | | // lines and columns. |
671 | 24 | if (!sti.codeBlock) { |
672 | 1 | stack.append(u" (native)"); |
673 | 1 | continue; |
674 | 1 | } |
675 | | |
676 | | // We are not a native function. |
677 | 23 | int32_t lineNo; |
678 | 23 | int32_t columnNo; |
679 | 23 | OptValue<SymbolID> fileName; |
680 | 23 | bool isAddress = false; |
681 | 23 | OptValue<hbc::DebugSourceLocation> location = |
682 | 23 | getDebugInfo(sti.codeBlock, sti.bytecodeOffset); |
683 | 23 | if (location) { |
684 | | // Use the line and column from the debug info. |
685 | 23 | lineNo = location->line; |
686 | 23 | columnNo = location->column; |
687 | 23 | } else { |
688 | | // Use a "line" and "column" synthesized from the bytecode. |
689 | | // In our synthesized stack trace, a line corresponds to a bytecode |
690 | | // module. This matches the interpretation in DebugInfo.cpp. Currently we |
691 | | // can only have one bytecode module without debug information, namely the |
692 | | // one loaded from disk, which is always at index 1. |
693 | | // TODO: find a way to track the bytecode modules explicitly. |
694 | | // TODO: we do not yet have a way of getting the file name separate from |
695 | | // the debug info. For now we end up leaving it as "unknown". |
696 | 0 | auto pair = virtualOffsetCache.insert({sti.codeBlock, 0}); |
697 | 0 | uint32_t &virtualOffset = pair.first->second; |
698 | 0 | if (pair.second) { |
699 | | // Code block was not in the cache, update the cache. |
700 | 0 | virtualOffset = sti.codeBlock->getVirtualOffset(); |
701 | 0 | } |
702 | | // Add 1 to the SegmentID to account for 1-based indexing of |
703 | | // symbolication tools. |
704 | 0 | lineNo = |
705 | 0 | sti.codeBlock->getRuntimeModule()->getBytecode()->getSegmentID() + 1; |
706 | 0 | columnNo = sti.bytecodeOffset + virtualOffset; |
707 | 0 | isAddress = true; |
708 | 0 | } |
709 | | |
710 | 23 | stack.append(u" ("); |
711 | 23 | if (isAddress) |
712 | 0 | stack.append(u"address at "); |
713 | | |
714 | | // Append the filename. If we have a source location, use the filename from |
715 | | // that location; otherwise use the RuntimeModule's sourceURL; otherwise |
716 | | // report unknown. |
717 | 23 | RuntimeModule *runtimeModule = sti.codeBlock->getRuntimeModule(); |
718 | 23 | if (location) { |
719 | 23 | stack.append( |
720 | 23 | runtimeModule->getBytecode()->getDebugInfo()->getFilenameByID( |
721 | 23 | location->filenameId)); |
722 | 23 | } else { |
723 | 0 | auto sourceURL = runtimeModule->getSourceURL(); |
724 | 0 | stack.append(sourceURL.empty() ? "unknown" : sourceURL); |
725 | 0 | } |
726 | 23 | stack.push_back(u':'); |
727 | | |
728 | 23 | numberToString(lineNo, buf, NUMBER_TO_STRING_BUF_SIZE); |
729 | 23 | stack.append(buf); |
730 | | |
731 | 23 | stack.push_back(u':'); |
732 | | |
733 | 23 | numberToString(columnNo, buf, NUMBER_TO_STRING_BUF_SIZE); |
734 | 23 | stack.append(buf); |
735 | | |
736 | 23 | stack.push_back(u')'); |
737 | 23 | } |
738 | 23 | return ExecutionStatus::RETURNED; |
739 | 23 | } |
740 | | |
741 | | CallResult<HermesValue> JSError::constructCallSitesArray( |
742 | | Runtime &runtime, |
743 | 0 | Handle<JSError> selfHandle) { |
744 | 0 | auto max = selfHandle->stacktrace_ |
745 | 0 | ? selfHandle->stacktrace_->size() - selfHandle->firstExposedFrameIndex_ |
746 | 0 | : 0; |
747 | 0 | auto arrayRes = JSArray::create(runtime, max, 0); |
748 | 0 | if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) { |
749 | 0 | return ExecutionStatus::EXCEPTION; |
750 | 0 | } |
751 | | |
752 | 0 | auto array = std::move(*arrayRes); |
753 | 0 | if (!selfHandle->stacktrace_) { |
754 | 0 | return array.getHermesValue(); |
755 | 0 | } |
756 | | |
757 | 0 | size_t callSiteIndex = 0; |
758 | |
|
759 | 0 | GCScope gcScope(runtime); |
760 | 0 | auto marker = gcScope.createMarker(); |
761 | |
|
762 | 0 | for (size_t index = 0; index < max; index++) { |
763 | | // TODO: truncate traces? Support Error.stackTraceLimit? |
764 | | // Problem: The CallSite API doesn't provide a way to denote skipped frames. |
765 | | // V8 truncates bottom frames (and adds no marker) while we truncate middle |
766 | | // frames (and in string traces, add a marker with a count). |
767 | |
|
768 | 0 | auto absIndex = index + selfHandle->firstExposedFrameIndex_; |
769 | | |
770 | | // Each CallSite stores a reference to this JSError and a particular frame |
771 | | // index, and provides methods for querying information about that frame. |
772 | 0 | auto callSiteRes = JSCallSite::create(runtime, selfHandle, absIndex); |
773 | 0 | if (callSiteRes == ExecutionStatus::EXCEPTION) { |
774 | 0 | return ExecutionStatus::EXCEPTION; |
775 | 0 | } |
776 | 0 | auto callSite = runtime.makeHandle(*callSiteRes); |
777 | |
|
778 | 0 | JSArray::setElementAt(array, runtime, callSiteIndex++, callSite); |
779 | |
|
780 | 0 | gcScope.flushToMarker(marker); |
781 | 0 | } |
782 | | |
783 | 0 | auto cr = |
784 | 0 | JSArray::setLengthProperty(array, runtime, callSiteIndex, PropOpFlags{}); |
785 | 0 | (void)cr; |
786 | 0 | assert( |
787 | 0 | cr != ExecutionStatus::EXCEPTION && *cr && "JSArray::setLength() failed"); |
788 | | |
789 | 0 | return array.getHermesValue(); |
790 | 0 | } |
791 | | |
792 | | /// \return the code block associated with \p callableHandle if it is a |
793 | | /// (possibly bound) function, or nullptr otherwise. |
794 | | static const CodeBlock *getLeafCodeBlock( |
795 | | Handle<Callable> callableHandle, |
796 | 0 | Runtime &runtime) { |
797 | 0 | const Callable *callable = callableHandle.get(); |
798 | 0 | while (callable) { |
799 | 0 | if (auto *asFunction = dyn_vmcast<const JSFunction>(callable)) { |
800 | 0 | return asFunction->getCodeBlock(runtime); |
801 | 0 | } |
802 | 0 | if (auto *asBoundFunction = dyn_vmcast<const BoundFunction>(callable)) { |
803 | 0 | callable = asBoundFunction->getTarget(runtime); |
804 | 0 | } else { |
805 | 0 | break; |
806 | 0 | } |
807 | 0 | } |
808 | | |
809 | 0 | return nullptr; |
810 | 0 | } |
811 | | |
812 | | void JSError::popFramesUntilInclusive( |
813 | | Runtime &runtime, |
814 | | Handle<JSError> selfHandle, |
815 | 0 | Handle<Callable> callableHandle) { |
816 | 0 | assert( |
817 | 0 | selfHandle->stacktrace_ && "Cannot pop frames when stacktrace_ is null"); |
818 | | // By default, assume we won't encounter the sentinel function and skip the |
819 | | // entire stack. |
820 | 0 | selfHandle->firstExposedFrameIndex_ = selfHandle->stacktrace_->size(); |
821 | 0 | auto codeBlock = getLeafCodeBlock(callableHandle, runtime); |
822 | 0 | if (!codeBlock) { |
823 | 0 | return; |
824 | 0 | } |
825 | 0 | for (size_t index = 0, max = selfHandle->stacktrace_->size(); index < max; |
826 | 0 | index++) { |
827 | 0 | const StackTraceInfo &sti = selfHandle->stacktrace_->at(index); |
828 | 0 | if (sti.codeBlock == codeBlock) { |
829 | 0 | selfHandle->firstExposedFrameIndex_ = index + 1; |
830 | 0 | break; |
831 | 0 | } |
832 | 0 | } |
833 | 0 | } |
834 | | |
835 | 23 | void JSError::_finalizeImpl(GCCell *cell, GC &) { |
836 | 23 | JSError *self = vmcast<JSError>(cell); |
837 | 23 | self->~JSError(); |
838 | 23 | } |
839 | | |
840 | 0 | size_t JSError::_mallocSizeImpl(GCCell *cell) { |
841 | 0 | JSError *self = vmcast<JSError>(cell); |
842 | 0 | auto stacktrace = self->stacktrace_.get(); |
843 | 0 | return stacktrace ? sizeof(StackTrace) + |
844 | 0 | stacktrace->capacity() * sizeof(StackTrace::value_type) |
845 | 0 | : 0; |
846 | 0 | } |
847 | | |
848 | | } // namespace vm |
849 | | } // namespace hermes |