Coverage Report

Created: 2025-09-04 06:34

/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