lib/VM/JSError.cpp (588 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/VM/JSError.h" #include "hermes/BCGen/HBC/DebugInfo.h" #include "hermes/Support/OptValue.h" #include "hermes/VM/BuildMetadata.h" #include "hermes/VM/Callable.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSCallSite.h" #include "hermes/VM/PropertyAccessor.h" #include "hermes/VM/RuntimeModule-inline.h" #include "hermes/VM/StackFrame-inline.h" #include "hermes/VM/StringView.h" #include "llvh/ADT/ScopeExit.h" namespace hermes { namespace vm { //===----------------------------------------------------------------------===// // class JSError const ObjectVTable JSError::vt{ VTable( CellKind::JSErrorKind, cellSize<JSError>(), JSError::_finalizeImpl, nullptr, JSError::_mallocSizeImpl), JSError::_getOwnIndexedRangeImpl, JSError::_haveOwnIndexedImpl, JSError::_getOwnIndexedPropertyFlagsImpl, JSError::_getOwnIndexedImpl, JSError::_setOwnIndexedImpl, JSError::_deleteOwnIndexedImpl, JSError::_checkAllOwnIndexedImpl, }; void JSErrorBuildMeta(const GCCell *cell, Metadata::Builder &mb) { mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSError>()); JSObjectBuildMeta(cell, mb); const auto *self = static_cast<const JSError *>(cell); mb.setVTable(&JSError::vt); mb.addField("funcNames", &self->funcNames_); mb.addField("domains", &self->domains_); } CallResult<Handle<JSError>> JSError::getErrorFromStackTarget_RJS( Runtime &runtime, Handle<JSObject> targetHandle) { if (targetHandle) { auto capturedErrorRes = JSObject::getNamed_RJS( targetHandle, runtime, Predefined::getSymbolID(Predefined::InternalPropertyCapturedError)); if (capturedErrorRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto capturedErrorHandle = runtime.makeHandle(std::move(*capturedErrorRes)); auto errorHandle = Handle<JSError>::dyn_vmcast( capturedErrorHandle ? capturedErrorHandle : targetHandle); if (errorHandle) { return errorHandle; } } return runtime.raiseTypeError( "Error.stack getter called with an invalid receiver"); } CallResult<HermesValue> errorStackGetter(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); auto targetHandle = args.dyncastThis<JSObject>(); auto errorHandleRes = JSError::getErrorFromStackTarget_RJS(runtime, targetHandle); if (errorHandleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto errorHandle = *errorHandleRes; if (!errorHandle->stacktrace_) { // Stacktrace has not been set, we simply return empty string. // This is different from other VMs where stacktrace is created when // the error object is created. We only set it when the error // is raised. return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } // It's possible we're getting the stack for a stack overflow // RangeError. Allow ourselves a little extra room to do this. vm::ScopedNativeDepthReducer reducer(runtime); SmallU16String<32> stack; auto errorCtor = Handle<JSObject>::vmcast(&runtime.errorConstructor); auto prepareStackTraceRes = JSObject::getNamed_RJS( errorCtor, runtime, Predefined::getSymbolID(Predefined::prepareStackTrace), PropOpFlags().plusThrowOnError()); if (prepareStackTraceRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } MutableHandle<> stackTraceFormatted{runtime}; auto prepareStackTrace = Handle<Callable>::dyn_vmcast( runtime.makeHandle(std::move(*prepareStackTraceRes))); if (LLVM_UNLIKELY(prepareStackTrace && !runtime.formattingStackTrace())) { const auto &recursionGuard = llvh::make_scope_exit( [&runtime]() { runtime.setFormattingStackTrace(false); }); (void)recursionGuard; runtime.setFormattingStackTrace(true); auto callSitesRes = JSError::constructCallSitesArray(runtime, errorHandle); if (LLVM_UNLIKELY(callSitesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto prepareRes = Callable::executeCall2( prepareStackTrace, runtime, runtime.getNullValue(), targetHandle.getHermesValue(), *callSitesRes); if (LLVM_UNLIKELY(prepareRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } stackTraceFormatted = std::move(*prepareRes); } else { if (JSError::constructStackTraceString( runtime, errorHandle, targetHandle, stack) == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto strRes = StringPrimitive::create(runtime, stack); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { // StringPrimitive creation can throw if the stacktrace string is too // long. In that case, we replace it with a predefined string. stackTraceFormatted = HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::stacktraceTooLong)); runtime.clearThrownValue(); } else { stackTraceFormatted = std::move(*strRes); } } // We no longer need the accessor. Redefine the stack property to a regular // property. DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); if (JSObject::defineOwnProperty( targetHandle, runtime, Predefined::getSymbolID(Predefined::stack), dpf, stackTraceFormatted) == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return *stackTraceFormatted; } CallResult<HermesValue> errorStackSetter(void *, Runtime &runtime, NativeArgs args) { auto res = toObject(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto selfHandle = runtime.makeHandle<JSObject>(res.getValue()); // Redefines the stack property to a regular property. DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); if (JSObject::defineOwnProperty( selfHandle, runtime, Predefined::getSymbolID(Predefined::stack), dpf, args.getArgHandle(0)) == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return HermesValue::encodeUndefinedValue(); } PseudoHandle<JSError> JSError::create( Runtime &runtime, Handle<JSObject> parentHandle) { return create(runtime, parentHandle, /*catchable*/ true); } PseudoHandle<JSError> JSError::createUncatchable( Runtime &runtime, Handle<JSObject> parentHandle) { return create(runtime, parentHandle, /*catchable*/ false); } PseudoHandle<JSError> JSError::create( Runtime &runtime, Handle<JSObject> parentHandle, bool catchable) { auto *cell = runtime.makeAFixed<JSError, HasFinalizer::Yes>( runtime, parentHandle, runtime.getHiddenClassForPrototype( *parentHandle, numOverlapSlots<JSError>()), catchable); return JSObjectInit::initToPseudoHandle(runtime, cell); } ExecutionStatus JSError::setupStack( Handle<JSObject> selfHandle, Runtime &runtime) { // Lazily allocate the accessor. if (runtime.jsErrorStackAccessor.isUndefined()) { // This code path allocates quite a few handles, so make sure we // don't disturb the parent GCScope and free them. GCScope gcScope{runtime}; auto getter = NativeFunction::create( runtime, Handle<JSObject>::vmcast(&runtime.functionPrototype), nullptr, errorStackGetter, Predefined::getSymbolID(Predefined::emptyString), 0, Runtime::makeNullHandle<JSObject>()); auto setter = NativeFunction::create( runtime, Handle<JSObject>::vmcast(&runtime.functionPrototype), nullptr, errorStackSetter, Predefined::getSymbolID(Predefined::emptyString), 1, Runtime::makeNullHandle<JSObject>()); auto crtRes = PropertyAccessor::create(runtime, getter, setter); if (crtRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } runtime.jsErrorStackAccessor = *crtRes; } auto accessor = Handle<PropertyAccessor>::vmcast(&runtime.jsErrorStackAccessor); DefinePropertyFlags dpf{}; dpf.setEnumerable = 1; dpf.setConfigurable = 1; dpf.setGetter = 1; dpf.setSetter = 1; dpf.enumerable = 0; dpf.configurable = 1; auto res = JSObject::defineOwnProperty( selfHandle, runtime, Predefined::getSymbolID(Predefined::stack), dpf, accessor); // Ignore failures to set the "stack" property as other engines do. if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { runtime.clearThrownValue(); } return ExecutionStatus::RETURNED; } ExecutionStatus JSError::setMessage( Handle<JSError> selfHandle, Runtime &runtime, Handle<> message) { auto stringMessage = Handle<StringPrimitive>::dyn_vmcast(message); if (LLVM_UNLIKELY(!stringMessage)) { auto strRes = toString_RJS(runtime, message); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } stringMessage = runtime.makeHandle(std::move(*strRes)); } DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); return JSObject::defineOwnProperty( selfHandle, runtime, Predefined::getSymbolID(Predefined::message), dpf, stringMessage) .getStatus(); } /// \return a list of function names associated with the call stack \p frames. /// Function names are first read out of 'displayName', followed by the 'name' /// property of each Callable on the stack. Accessors are skipped. If a /// Callable does not have a name, or if the name is an accessor, undefined is /// set. Names are returned in reverse order (topmost frame is first). /// In case of error returns a nullptr handle. /// \param skipTopFrame if true, skip the top frame. static Handle<PropStorage> getCallStackFunctionNames( Runtime &runtime, bool skipTopFrame, size_t sizeHint) { auto arrRes = PropStorage::create(runtime, sizeHint); if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { runtime.clearThrownValue(); return Runtime::makeNullHandle<PropStorage>(); } MutableHandle<PropStorage> names{runtime, vmcast<PropStorage>(*arrRes)}; GCScope gcScope(runtime); MutableHandle<> name{runtime}; auto marker = gcScope.createMarker(); uint32_t frameIndex = 0; uint32_t namesIndex = 0; for (StackFramePtr cf : runtime.getStackFrames()) { if (frameIndex++ == 0 && skipTopFrame) continue; name = HermesValue::encodeUndefinedValue(); if (auto callableHandle = Handle<Callable>::dyn_vmcast( Handle<>(&cf.getCalleeClosureOrCBRef()))) { NamedPropertyDescriptor desc; JSObject *propObj = JSObject::getNamedDescriptorPredefined( callableHandle, runtime, Predefined::displayName, desc); if (!propObj) { propObj = JSObject::getNamedDescriptorPredefined( callableHandle, runtime, Predefined::name, desc); } if (propObj && !desc.flags.accessor && LLVM_LIKELY(!desc.flags.proxyObject) && LLVM_LIKELY(!desc.flags.hostObject)) { name = JSObject::getNamedSlotValueUnsafe(propObj, runtime, desc) .unboxToHV(runtime); } else if (desc.flags.proxyObject) { name = HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::proxyTrap)); } } else if (!cf.getCalleeClosureOrCBRef().isObject()) { // If CalleeClosureOrCB is not an object pointer, then it must be a native // pointer to a CodeBlock. auto *cb = cf.getCalleeClosureOrCBRef().getNativePointer<const CodeBlock>(); if (cb->getNameMayAllocate().isValid()) name = HermesValue::encodeStringValue( runtime.getStringPrimFromSymbolID(cb->getNameMayAllocate())); } if (PropStorage::resize(names, runtime, namesIndex + 1) == ExecutionStatus::EXCEPTION) { runtime.clearThrownValue(); return Runtime::makeNullHandle<PropStorage>(); } auto shv = SmallHermesValue::encodeHermesValue(name.getHermesValue(), runtime); names->set(namesIndex, shv, &runtime.getHeap()); ++namesIndex; gcScope.flushToMarker(marker); } return std::move(names); } ExecutionStatus JSError::recordStackTrace( Handle<JSError> selfHandle, Runtime &runtime, bool skipTopFrame, CodeBlock *codeBlock, const Inst *ip) { if (selfHandle->stacktrace_) return ExecutionStatus::RETURNED; auto frames = runtime.getStackFrames(); // Check if the top frame is a JSFunction and we don't have the current // CodeBlock, do nothing. if (!skipTopFrame && !codeBlock && frames.begin() != frames.end() && frames.begin()->getCalleeCodeBlock()) { return ExecutionStatus::RETURNED; } StackTracePtr stack{new StackTrace()}; auto domainsRes = ArrayStorageSmall::create(runtime, 1); if (LLVM_UNLIKELY(domainsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto domains = runtime.makeMutableHandle<ArrayStorageSmall>( vmcast<ArrayStorageSmall>(*domainsRes)); // Add the domain to the domains list, provided that it's not the same as the // last domain in the list. This allows us to save storage with a constant // time check, but we don't have to loop through and check every domain to // deduplicate. auto addDomain = [&domains, &runtime](CodeBlock *codeBlock) -> ExecutionStatus { GCScopeMarkerRAII marker{runtime}; Handle<Domain> domain = codeBlock->getRuntimeModule()->getDomain(runtime); if (domains->size() > 0 && vmcast<Domain>(domains->at(domains->size() - 1).getObject(runtime)) == domain.get()) { return ExecutionStatus::RETURNED; } return ArrayStorageSmall::push_back(domains, runtime, domain); }; if (!skipTopFrame) { if (codeBlock) { stack->emplace_back(codeBlock, codeBlock->getOffsetOf(ip)); if (LLVM_UNLIKELY(addDomain(codeBlock) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } } else { stack->emplace_back(nullptr, 0); } } const StackFramePtr framesEnd = *runtime.getStackFrames().end(); // Fill in the call stack. // Each stack frame tracks information about the caller. for (StackFramePtr cf : runtime.getStackFrames()) { CodeBlock *savedCodeBlock = cf.getSavedCodeBlock(); const Inst *const savedIP = cf.getSavedIP(); // Go up one frame and get the callee code block but use the current // frame's saved IP. This also allows us to account for bound functions, // which have savedCodeBlock == nullptr in order to allow proper returns in // the interpreter. StackFramePtr prev = cf->getPreviousFrame(); if (prev != framesEnd) { if (CodeBlock *parentCB = prev->getCalleeCodeBlock()) { savedCodeBlock = parentCB; } } if (savedCodeBlock && savedIP) { stack->emplace_back(savedCodeBlock, savedCodeBlock->getOffsetOf(savedIP)); if (LLVM_UNLIKELY( addDomain(savedCodeBlock) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } } else { stack->emplace_back(nullptr, 0); } } selfHandle->domains_.set(runtime, domains.get(), &runtime.getHeap()); // Remove the last entry. stack->pop_back(); auto funcNames = getCallStackFunctionNames(runtime, skipTopFrame, stack->size()); // Either the function names is empty, or they have the same count. assert( (!funcNames || funcNames->size() == stack->size()) && "Function names and stack trace must have same size."); selfHandle->stacktrace_ = std::move(stack); selfHandle->funcNames_.set(runtime, *funcNames, &runtime.getHeap()); return ExecutionStatus::RETURNED; } /// Given a codeblock and opcode offset, \returns the debug information. OptValue<hbc::DebugSourceLocation> JSError::getDebugInfo( CodeBlock *codeBlock, uint32_t bytecodeOffset) { auto offset = codeBlock->getDebugSourceLocationsOffset(); if (!offset.hasValue()) { return llvh::None; } return codeBlock->getRuntimeModule() ->getBytecode() ->getDebugInfo() ->getLocationForAddress(offset.getValue(), bytecodeOffset); } Handle<StringPrimitive> JSError::getFunctionNameAtIndex( Runtime &runtime, Handle<JSError> selfHandle, size_t index) { IdentifierTable &idt = runtime.getIdentifierTable(); MutableHandle<StringPrimitive> name{ runtime, runtime.getPredefinedString(Predefined::emptyString)}; // If funcNames_ is set and contains a string primitive, use that. if (selfHandle->funcNames_) { assert( index < selfHandle->funcNames_.getNonNull(runtime)->size() && "Index out of bounds"); name = dyn_vmcast<StringPrimitive>( selfHandle->funcNames_.getNonNull(runtime)->at(index).unboxToHV( runtime)); } if (!name || name->getStringLength() == 0) { // We did not have an explicit function name, or it was not a nonempty // string. If we have a code block, try its debug info. if (const CodeBlock *codeBlock = selfHandle->stacktrace_->at(index).codeBlock) { name = idt.getStringPrim(runtime, codeBlock->getNameMayAllocate()); } } if (!name || name->getStringLength() == 0) { return runtime.makeNullHandle<StringPrimitive>(); } return std::move(name); } bool JSError::appendFunctionNameAtIndex( Runtime &runtime, Handle<JSError> selfHandle, size_t index, llvh::SmallVectorImpl<char16_t> &str) { auto name = getFunctionNameAtIndex(runtime, selfHandle, index); if (!name) return false; name->appendUTF16String(str); return true; } ExecutionStatus JSError::constructStackTraceString( Runtime &runtime, Handle<JSError> selfHandle, Handle<JSObject> targetHandle, SmallU16String<32> &stack) { GCScope gcScope(runtime); // First of all, the stacktrace string starts with target.toString. auto res = toString_RJS(runtime, targetHandle); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { if (isUncatchableError(runtime.getThrownValue())) { // If toString throws an uncatchable exception, propagate it up. return ExecutionStatus::EXCEPTION; } // If toString throws an exception, we just use <error>. stack.append(u"<error>"); // There is not much we can do if exception thrown when trying to // get the stacktrace. We just name it <error>, and it should be // sufficient to tell what happened here. runtime.clearThrownValue(); } else { res->get()->appendUTF16String(stack); } // Virtual offsets are computed by walking the list of bytecode functions. If // we have an extremely deep stack, this could get expensive. Assume that very // deep stacks are most likely due to runaway recursion and so use a local // cache of virtual offsets. llvh::DenseMap<const CodeBlock *, uint32_t> virtualOffsetCache; // Append each function location in the call stack to stack trace. auto marker = gcScope.createMarker(); for (size_t index = 0, max = selfHandle->stacktrace_->size() - selfHandle->firstExposedFrameIndex_; index < max; index++) { char buf[NUMBER_TO_STRING_BUF_SIZE]; // If the trace contains more than 100 entries, limit the string to the // first 50 and the last 50 entries and include a line about the truncation. static constexpr unsigned PRINT_HEAD = 50; static constexpr unsigned PRINT_TAIL = 50; if (LLVM_UNLIKELY(max > PRINT_HEAD + PRINT_TAIL)) { if (index == PRINT_HEAD) { stack.append("\n ... skipping "); numberToString(max - PRINT_HEAD - PRINT_TAIL, buf, sizeof(buf)); stack.append(buf); stack.append(" frames"); continue; } // Skip the middle frames. if (index > PRINT_HEAD && index < max - PRINT_TAIL) { index = max - PRINT_TAIL; } } const size_t absIndex = index + selfHandle->firstExposedFrameIndex_; const StackTraceInfo &sti = selfHandle->stacktrace_->at(absIndex); gcScope.flushToMarker(marker); // For each stacktrace entry, we add a line with the following format: // at <functionName> (<fileName>:<lineNo>:<columnNo>) stack.append(u"\n at "); if (!appendFunctionNameAtIndex(runtime, selfHandle, absIndex, stack)) stack.append(u"anonymous"); // If we have a null codeBlock, it's a native function, which do not have // lines and columns. if (!sti.codeBlock) { stack.append(u" (native)"); continue; } // We are not a native function. int32_t lineNo; int32_t columnNo; OptValue<SymbolID> fileName; bool isAddress = false; OptValue<hbc::DebugSourceLocation> location = getDebugInfo(sti.codeBlock, sti.bytecodeOffset); if (location) { // Use the line and column from the debug info. lineNo = location->line; columnNo = location->column; } else { // Use a "line" and "column" synthesized from the bytecode. // In our synthesized stack trace, a line corresponds to a bytecode // module. This matches the interpretation in DebugInfo.cpp. Currently we // can only have one bytecode module without debug information, namely the // one loaded from disk, which is always at index 1. // TODO: find a way to track the bytecode modules explicitly. // TODO: we do not yet have a way of getting the file name separate from // the debug info. For now we end up leaving it as "unknown". auto pair = virtualOffsetCache.insert({sti.codeBlock, 0}); uint32_t &virtualOffset = pair.first->second; if (pair.second) { // Code block was not in the cache, update the cache. virtualOffset = sti.codeBlock->getVirtualOffset(); } // Add 1 to the SegmentID to account for 1-based indexing of // symbolication tools. lineNo = sti.codeBlock->getRuntimeModule()->getBytecode()->getSegmentID() + 1; columnNo = sti.bytecodeOffset + virtualOffset; isAddress = true; } stack.append(u" ("); if (isAddress) stack.append(u"address at "); // Append the filename. If we have a source location, use the filename from // that location; otherwise use the RuntimeModule's sourceURL; otherwise // report unknown. RuntimeModule *runtimeModule = sti.codeBlock->getRuntimeModule(); if (location) { stack.append( runtimeModule->getBytecode()->getDebugInfo()->getFilenameByID( location->filenameId)); } else { auto sourceURL = runtimeModule->getSourceURL(); stack.append(sourceURL.empty() ? "unknown" : sourceURL); } stack.push_back(u':'); numberToString(lineNo, buf, NUMBER_TO_STRING_BUF_SIZE); stack.append(buf); stack.push_back(u':'); numberToString(columnNo, buf, NUMBER_TO_STRING_BUF_SIZE); stack.append(buf); stack.push_back(u')'); } return ExecutionStatus::RETURNED; } CallResult<HermesValue> JSError::constructCallSitesArray( Runtime &runtime, Handle<JSError> selfHandle) { auto max = selfHandle->stacktrace_ ? selfHandle->stacktrace_->size() - selfHandle->firstExposedFrameIndex_ : 0; auto arrayRes = JSArray::create(runtime, max, 0); if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto array = std::move(*arrayRes); if (!selfHandle->stacktrace_) { return array.getHermesValue(); } size_t callSiteIndex = 0; GCScope gcScope(runtime); auto marker = gcScope.createMarker(); for (size_t index = 0; index < max; index++) { // TODO: truncate traces? Support Error.stackTraceLimit? // Problem: The CallSite API doesn't provide a way to denote skipped frames. // V8 truncates bottom frames (and adds no marker) while we truncate middle // frames (and in string traces, add a marker with a count). auto absIndex = index + selfHandle->firstExposedFrameIndex_; // Each CallSite stores a reference to this JSError and a particular frame // index, and provides methods for querying information about that frame. auto callSiteRes = JSCallSite::create(runtime, selfHandle, absIndex); if (callSiteRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto callSite = runtime.makeHandle(*callSiteRes); JSArray::setElementAt(array, runtime, callSiteIndex++, callSite); gcScope.flushToMarker(marker); } auto cr = JSArray::setLengthProperty(array, runtime, callSiteIndex, PropOpFlags{}); (void)cr; assert( cr != ExecutionStatus::EXCEPTION && *cr && "JSArray::setLength() failed"); return array.getHermesValue(); } /// \return the code block associated with \p callableHandle if it is a /// (possibly bound) function, or nullptr otherwise. static const CodeBlock *getLeafCodeBlock( Handle<Callable> callableHandle, Runtime &runtime) { const Callable *callable = callableHandle.get(); while (callable) { if (auto *asFunction = dyn_vmcast<const JSFunction>(callable)) { return asFunction->getCodeBlock(); } if (auto *asBoundFunction = dyn_vmcast<const BoundFunction>(callable)) { callable = asBoundFunction->getTarget(runtime); } } return nullptr; } void JSError::popFramesUntilInclusive( Runtime &runtime, Handle<JSError> selfHandle, Handle<Callable> callableHandle) { assert( selfHandle->stacktrace_ && "Cannot pop frames when stacktrace_ is null"); auto codeBlock = getLeafCodeBlock(callableHandle, runtime); if (!codeBlock) { return; } // By default, assume we won't encounter the sentinel function and skip the // entire stack. selfHandle->firstExposedFrameIndex_ = selfHandle->stacktrace_->size(); for (size_t index = 0, max = selfHandle->stacktrace_->size(); index < max; index++) { const StackTraceInfo &sti = selfHandle->stacktrace_->at(index); if (sti.codeBlock == codeBlock) { selfHandle->firstExposedFrameIndex_ = index + 1; break; } } } void JSError::_finalizeImpl(GCCell *cell, GC *) { JSError *self = vmcast<JSError>(cell); self->~JSError(); } size_t JSError::_mallocSizeImpl(GCCell *cell) { JSError *self = vmcast<JSError>(cell); auto stacktrace = self->stacktrace_.get(); return stacktrace ? sizeof(StackTrace) + stacktrace->capacity() * sizeof(StackTrace::value_type) : 0; } } // namespace vm } // namespace hermes