|
|
|
|
@@ -7,6 +7,7 @@
|
|
|
|
|
#include <JavaScriptCore/ErrorInstance.h>
|
|
|
|
|
#include <JavaScriptCore/ExceptionScope.h>
|
|
|
|
|
#include <JavaScriptCore/FunctionConstructor.h>
|
|
|
|
|
#include <JavaScriptCore/HashMapImplInlines.h>
|
|
|
|
|
#include <JavaScriptCore/Identifier.h>
|
|
|
|
|
#include <JavaScriptCore/IteratorOperations.h>
|
|
|
|
|
#include <JavaScriptCore/JSArray.h>
|
|
|
|
|
@@ -15,7 +16,9 @@
|
|
|
|
|
#include <JavaScriptCore/JSClassRef.h>
|
|
|
|
|
#include <JavaScriptCore/JSInternalPromise.h>
|
|
|
|
|
#include <JavaScriptCore/JSMap.h>
|
|
|
|
|
#include <JavaScriptCore/JSModuleEnvironment.h>
|
|
|
|
|
#include <JavaScriptCore/JSModuleLoader.h>
|
|
|
|
|
#include <JavaScriptCore/JSModuleNamespaceObject.h>
|
|
|
|
|
#include <JavaScriptCore/JSModuleRecord.h>
|
|
|
|
|
#include <JavaScriptCore/JSNativeStdFunction.h>
|
|
|
|
|
#include <JavaScriptCore/JSObject.h>
|
|
|
|
|
@@ -27,6 +30,7 @@
|
|
|
|
|
#include <JavaScriptCore/StackFrame.h>
|
|
|
|
|
#include <JavaScriptCore/StackVisitor.h>
|
|
|
|
|
#include <JavaScriptCore/VM.h>
|
|
|
|
|
#include <JavaScriptCore/VMEntryScope.h>
|
|
|
|
|
#include <JavaScriptCore/WasmFaultSignalHandler.h>
|
|
|
|
|
#include <wtf/text/ExternalStringImpl.h>
|
|
|
|
|
#include <wtf/text/StringCommon.h>
|
|
|
|
|
@@ -92,6 +96,243 @@ void JSC__JSObject__putRecord(JSC__JSObject *object, JSC__JSGlobalObject *global
|
|
|
|
|
object->putDirect(global->vm(), ident, descriptor.value());
|
|
|
|
|
scope.release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void populateStackFrameMetadata(const JSC::StackFrame *stackFrame, ZigStackFrame *frame) {
|
|
|
|
|
frame->source_url = Zig::toZigString(stackFrame->sourceURL());
|
|
|
|
|
|
|
|
|
|
if (stackFrame->isWasmFrame()) {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeWasm;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto m_codeBlock = stackFrame->codeBlock();
|
|
|
|
|
if (m_codeBlock) {
|
|
|
|
|
switch (m_codeBlock->codeType()) {
|
|
|
|
|
case JSC::EvalCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeEval;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::ModuleCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeModule;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::GlobalCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeGlobal;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::FunctionCode: {
|
|
|
|
|
frame->code_type =
|
|
|
|
|
!m_codeBlock->isConstructor() ? ZigStackFrameCodeFunction : ZigStackFrameCodeConstructor;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: ASSERT_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto calleeCell = stackFrame->callee();
|
|
|
|
|
if (!calleeCell || !calleeCell->isObject()) return;
|
|
|
|
|
|
|
|
|
|
JSC::JSObject *callee = JSC::jsCast<JSC::JSObject *>(calleeCell);
|
|
|
|
|
// Does the code block have a user-defined name property?
|
|
|
|
|
JSC::JSValue name = callee->getDirect(m_codeBlock->vm(), m_codeBlock->vm().propertyNames->name);
|
|
|
|
|
if (name && name.isString()) {
|
|
|
|
|
auto str = name.toWTFString(m_codeBlock->globalObject());
|
|
|
|
|
frame->function_name = Zig::toZigString(str);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* For functions (either JSFunction or InternalFunction), fallback to their "native" name
|
|
|
|
|
* property. Based on JSC::getCalculatedDisplayName, "inlining" the
|
|
|
|
|
* JSFunction::calculatedDisplayName\InternalFunction::calculatedDisplayName calls */
|
|
|
|
|
if (JSC::JSFunction *function =
|
|
|
|
|
JSC::jsDynamicCast<JSC::JSFunction *>(m_codeBlock->vm(), callee)) {
|
|
|
|
|
|
|
|
|
|
WTF::String actualName = function->name(m_codeBlock->vm());
|
|
|
|
|
if (!actualName.isEmpty() || function->isHostOrBuiltinFunction()) {
|
|
|
|
|
frame->function_name = Zig::toZigString(actualName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto inferred_name = function->jsExecutable()->name();
|
|
|
|
|
frame->function_name = Zig::toZigString(inferred_name.string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JSC::InternalFunction *function =
|
|
|
|
|
JSC::jsDynamicCast<JSC::InternalFunction *>(m_codeBlock->vm(), callee)) {
|
|
|
|
|
// Based on JSC::InternalFunction::calculatedDisplayName, skipping the "displayName" property
|
|
|
|
|
frame->function_name = Zig::toZigString(function->name());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Based on
|
|
|
|
|
// https://github.com/mceSystems/node-jsc/blob/master/deps/jscshim/src/shim/JSCStackTrace.cpp#L298
|
|
|
|
|
static void populateStackFramePosition(const JSC::StackFrame *stackFrame, ZigString *source_lines,
|
|
|
|
|
int32_t *source_line_numbers, uint8_t source_lines_count,
|
|
|
|
|
ZigStackFramePosition *position) {
|
|
|
|
|
auto m_codeBlock = stackFrame->codeBlock();
|
|
|
|
|
if (!m_codeBlock) return;
|
|
|
|
|
|
|
|
|
|
JSC::BytecodeIndex bytecodeOffset =
|
|
|
|
|
stackFrame->hasBytecodeIndex() ? stackFrame->bytecodeIndex() : JSC::BytecodeIndex();
|
|
|
|
|
|
|
|
|
|
/* Get the "raw" position info.
|
|
|
|
|
* Note that we're using m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeOffset
|
|
|
|
|
* rather than m_codeBlock->expressionRangeForBytecodeOffset in order get the "raw" offsets and
|
|
|
|
|
* avoid the CodeBlock's expressionRangeForBytecodeOffset modifications to the line and column
|
|
|
|
|
* numbers, (we don't need the column number from it, and we'll calculate the line "fixes"
|
|
|
|
|
* ourselves). */
|
|
|
|
|
int startOffset = 0;
|
|
|
|
|
int endOffset = 0;
|
|
|
|
|
int divotPoint = 0;
|
|
|
|
|
unsigned line = 0;
|
|
|
|
|
unsigned unusedColumn = 0;
|
|
|
|
|
m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeIndex(
|
|
|
|
|
bytecodeOffset, divotPoint, startOffset, endOffset, line, unusedColumn);
|
|
|
|
|
divotPoint += m_codeBlock->sourceOffset();
|
|
|
|
|
|
|
|
|
|
// TODO: evaluate if using the API from UnlinkedCodeBlock can be used instead of iterating
|
|
|
|
|
// through source text.
|
|
|
|
|
|
|
|
|
|
/* On the first line of the source code, it seems that we need to "fix" the column with the
|
|
|
|
|
* starting offset. We currently use codeBlock->source()->startPosition().m_column.oneBasedInt()
|
|
|
|
|
* as the offset in the first line rather than codeBlock->firstLineColumnOffset(), which seems
|
|
|
|
|
* simpler (and what CodeBlock::expressionRangeForBytecodeOffset does). This is because
|
|
|
|
|
* firstLineColumnOffset values seems different from what we expect (according to v8's tests)
|
|
|
|
|
* and I haven't dove into the relevant parts in JSC (yet) to figure out why. */
|
|
|
|
|
unsigned columnOffset = line ? 0 : m_codeBlock->source().startColumn().zeroBasedInt();
|
|
|
|
|
|
|
|
|
|
// "Fix" the line number
|
|
|
|
|
JSC::ScriptExecutable *executable = m_codeBlock->ownerExecutable();
|
|
|
|
|
if (std::optional<int> overrideLine = executable->overrideLineNumber(m_codeBlock->vm())) {
|
|
|
|
|
line = overrideLine.value();
|
|
|
|
|
} else {
|
|
|
|
|
line += executable->firstLine();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the staring\ending offsets of the entire expression
|
|
|
|
|
int expressionStart = divotPoint - startOffset;
|
|
|
|
|
int expressionStop = divotPoint + endOffset;
|
|
|
|
|
|
|
|
|
|
// Make sure the range is valid
|
|
|
|
|
WTF::StringView sourceString = m_codeBlock->source().provider()->source();
|
|
|
|
|
if (!expressionStop || expressionStart > static_cast<int>(sourceString.length())) { return; }
|
|
|
|
|
|
|
|
|
|
// Search for the beginning of the line
|
|
|
|
|
unsigned int lineStart = expressionStart;
|
|
|
|
|
while ((lineStart > 0) && ('\n' != sourceString[lineStart - 1])) { lineStart--; }
|
|
|
|
|
// Search for the end of the line
|
|
|
|
|
unsigned int lineStop = expressionStop;
|
|
|
|
|
unsigned int sourceLength = sourceString.length();
|
|
|
|
|
while ((lineStop < sourceLength) && ('\n' != sourceString[lineStop])) { lineStop++; }
|
|
|
|
|
if (source_lines_count > 1 && source_lines != nullptr) {
|
|
|
|
|
auto chars = sourceString.characters8();
|
|
|
|
|
|
|
|
|
|
// Most of the time, when you look at a stack trace, you want a couple lines above
|
|
|
|
|
|
|
|
|
|
source_lines[0] = {&chars[lineStart], lineStop - lineStart};
|
|
|
|
|
source_line_numbers[0] = line;
|
|
|
|
|
|
|
|
|
|
if (lineStart > 0) {
|
|
|
|
|
auto byte_offset_in_source_string = lineStart - 1;
|
|
|
|
|
uint8_t source_line_i = 1;
|
|
|
|
|
auto remaining_lines_to_grab = source_lines_count - 1;
|
|
|
|
|
|
|
|
|
|
while (byte_offset_in_source_string > 0 && remaining_lines_to_grab > 0) {
|
|
|
|
|
unsigned int end_of_line_offset = byte_offset_in_source_string;
|
|
|
|
|
|
|
|
|
|
// This should probably be code points instead of newlines
|
|
|
|
|
while (byte_offset_in_source_string > 0 && chars[byte_offset_in_source_string] != '\n') {
|
|
|
|
|
byte_offset_in_source_string--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We are at the beginning of the line
|
|
|
|
|
source_lines[source_line_i] = {&chars[byte_offset_in_source_string],
|
|
|
|
|
end_of_line_offset - byte_offset_in_source_string + 1};
|
|
|
|
|
|
|
|
|
|
source_line_numbers[source_line_i] = line - source_line_i;
|
|
|
|
|
source_line_i++;
|
|
|
|
|
|
|
|
|
|
remaining_lines_to_grab--;
|
|
|
|
|
|
|
|
|
|
byte_offset_in_source_string -= byte_offset_in_source_string > 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Finally, store the source "positions" info.
|
|
|
|
|
* Notes:
|
|
|
|
|
* - The retrieved column seem to point the "end column". To make sure we're current, we'll
|
|
|
|
|
*calculate the columns ourselves, since we've already found where the line starts. Note that in
|
|
|
|
|
*v8 it should be 0-based here (in contrast the 1-based column number in v8::StackFrame).
|
|
|
|
|
* - The static_casts are ugly, but comes from differences between JSC and v8's api, and should
|
|
|
|
|
*be OK since no source should be longer than "max int" chars.
|
|
|
|
|
* TODO: If expressionStart == expressionStop, then m_endColumn will be equal to m_startColumn.
|
|
|
|
|
*Should we handle this case?
|
|
|
|
|
*/
|
|
|
|
|
position->expression_start = expressionStart;
|
|
|
|
|
position->expression_stop = expressionStop;
|
|
|
|
|
position->line = WTF::OrdinalNumber::fromOneBasedInt(static_cast<int>(line)).zeroBasedInt();
|
|
|
|
|
position->column_start = (expressionStart - lineStart) + columnOffset;
|
|
|
|
|
position->column_stop = position->column_start + (expressionStop - expressionStart);
|
|
|
|
|
position->line_start = lineStart;
|
|
|
|
|
position->line_stop = lineStop;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
static void populateStackFrame(ZigStackTrace *trace, const JSC::StackFrame *stackFrame,
|
|
|
|
|
ZigStackFrame *frame, bool is_top) {
|
|
|
|
|
populateStackFrameMetadata(stackFrame, frame);
|
|
|
|
|
populateStackFramePosition(stackFrame, is_top ? trace->source_lines_ptr : nullptr,
|
|
|
|
|
is_top ? trace->source_lines_numbers : nullptr,
|
|
|
|
|
is_top ? trace->source_lines_to_collect : 0, &frame->position);
|
|
|
|
|
}
|
|
|
|
|
static void populateStackTrace(const WTF::Vector<JSC::StackFrame> &frames, ZigStackTrace *trace) {
|
|
|
|
|
uint8_t frame_i = 0;
|
|
|
|
|
size_t stack_frame_i = 0;
|
|
|
|
|
const size_t total_frame_count = frames.size();
|
|
|
|
|
const uint8_t frame_count =
|
|
|
|
|
total_frame_count < trace->frames_len ? total_frame_count : trace->frames_len;
|
|
|
|
|
|
|
|
|
|
while (frame_i < frame_count && stack_frame_i < total_frame_count) {
|
|
|
|
|
// Skip native frames
|
|
|
|
|
while (stack_frame_i < total_frame_count && !(&frames.at(stack_frame_i))->codeBlock() &&
|
|
|
|
|
!(&frames.at(stack_frame_i))->isWasmFrame()) {
|
|
|
|
|
stack_frame_i++;
|
|
|
|
|
}
|
|
|
|
|
if (stack_frame_i >= total_frame_count) break;
|
|
|
|
|
|
|
|
|
|
ZigStackFrame *frame = &trace->frames_ptr[frame_i];
|
|
|
|
|
populateStackFrame(trace, &frames[stack_frame_i], frame, frame_i == 0);
|
|
|
|
|
stack_frame_i++;
|
|
|
|
|
frame_i++;
|
|
|
|
|
}
|
|
|
|
|
trace->frames_len = frame_i;
|
|
|
|
|
}
|
|
|
|
|
static void fromErrorInstance(ZigException *except, JSC::JSGlobalObject *global,
|
|
|
|
|
JSC::ErrorInstance *err, const Vector<JSC::StackFrame> *stackTrace,
|
|
|
|
|
JSC::JSValue val) {
|
|
|
|
|
JSC::JSObject *obj = JSC::jsDynamicCast<JSC::JSObject *>(global->vm(), val);
|
|
|
|
|
if (stackTrace != nullptr && stackTrace->size() > 0) {
|
|
|
|
|
populateStackTrace(*stackTrace, &except->stack);
|
|
|
|
|
} else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) {
|
|
|
|
|
populateStackTrace(*err->stackTrace(), &except->stack);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except->code = (unsigned char)err->errorType();
|
|
|
|
|
if (err->isStackOverflowError()) { except->code = 253; }
|
|
|
|
|
if (err->isOutOfMemoryError()) { except->code = 8; }
|
|
|
|
|
|
|
|
|
|
if (obj->hasProperty(global, global->vm().propertyNames->message)) {
|
|
|
|
|
except->message = Zig::toZigString(
|
|
|
|
|
obj->getDirect(global->vm(), global->vm().propertyNames->message).toWTFString(global));
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
except->message = Zig::toZigString(err->sanitizedMessageString(global));
|
|
|
|
|
}
|
|
|
|
|
except->name = Zig::toZigString(err->sanitizedNameString(global));
|
|
|
|
|
except->runtime_type = err->runtimeTypeForCause();
|
|
|
|
|
|
|
|
|
|
except->exception = err;
|
|
|
|
|
}
|
|
|
|
|
void JSC__JSValue__putRecord(JSC__JSValue objectValue, JSC__JSGlobalObject *global, ZigString *key,
|
|
|
|
|
ZigString *values, size_t valuesLen) {
|
|
|
|
|
JSC::JSValue objValue = JSC::JSValue::decode(objectValue);
|
|
|
|
|
@@ -268,6 +509,65 @@ bWTF__String JSC__JSString__value(JSC__JSString *arg0, JSC__JSGlobalObject *arg1
|
|
|
|
|
|
|
|
|
|
#pragma mark - JSC::JSModuleLoader
|
|
|
|
|
|
|
|
|
|
JSC__JSValue JSC__JSModuleLoader__callExportedFunction(JSC__JSGlobalObject *globalObject,
|
|
|
|
|
ZigString specifier, ZigString functionName,
|
|
|
|
|
JSC__JSValue *arguments,
|
|
|
|
|
unsigned char args_len,
|
|
|
|
|
ZigException *zig_exception) {
|
|
|
|
|
JSC::VM &vm = globalObject->vm();
|
|
|
|
|
JSC::JSLockHolder lock(vm);
|
|
|
|
|
|
|
|
|
|
JSC::JSObject *loader = JSC::jsDynamicCast<JSC::JSObject *>(vm, globalObject->moduleLoader());
|
|
|
|
|
JSC::JSMap *registry = JSC::jsDynamicCast<JSC::JSMap *>(
|
|
|
|
|
vm, loader->getDirect(vm, JSC::Identifier::fromString(vm, "registry")));
|
|
|
|
|
auto specifier_impl = WTF::ExternalStringImpl::createStatic(specifier.ptr, specifier.len);
|
|
|
|
|
auto specifier_ident =
|
|
|
|
|
JSC::jsOwnedString(vm, reinterpret_cast<WTF::UniquedStringImpl *>(specifier_impl.ptr()));
|
|
|
|
|
auto entry_cell = registry->get(globalObject, specifier_ident);
|
|
|
|
|
|
|
|
|
|
if (JSC::JSObject *entry = JSC::jsDynamicCast<JSC::JSObject *>(vm, entry_cell)) {
|
|
|
|
|
auto recordIdentifier = JSC::Identifier::fromString(vm, "module");
|
|
|
|
|
|
|
|
|
|
if (JSC::JSModuleRecord *record =
|
|
|
|
|
JSC::jsDynamicCast<JSC::JSModuleRecord *>(vm, entry->getDirect(vm, recordIdentifier))) {
|
|
|
|
|
auto fn_impl = WTF::ExternalStringImpl::createStatic(functionName.ptr, functionName.len);
|
|
|
|
|
auto fn_ident = reinterpret_cast<WTF::UniquedStringImpl *>(specifier_impl.ptr());
|
|
|
|
|
auto env = record->getModuleNamespace(globalObject);
|
|
|
|
|
|
|
|
|
|
if (JSC::JSValue macroFunctionExport =
|
|
|
|
|
env->getIfPropertyExists(globalObject, JSC::PropertyName(fn_ident))) {
|
|
|
|
|
|
|
|
|
|
if (JSC::JSObject *macroFunction = JSC::asObject(macroFunctionExport.asCell())) {
|
|
|
|
|
// auto functionNameImpl =
|
|
|
|
|
// WTF::ExternalStringImpl::createStatic(functionName.ptr, functionName.len);
|
|
|
|
|
JSC::VMEntryScope entryScope(vm, globalObject);
|
|
|
|
|
|
|
|
|
|
auto callData = JSC::getCallData(vm, macroFunction);
|
|
|
|
|
if (callData.type == JSC::CallData::Type::None) return JSC::JSValue::encode({});
|
|
|
|
|
|
|
|
|
|
JSC::MarkedArgumentBuffer argList;
|
|
|
|
|
for (size_t i = 0; i < args_len; i++) argList.append(JSC::JSValue::decode(arguments[i]));
|
|
|
|
|
|
|
|
|
|
NakedPtr<JSC::Exception> uncaughtException;
|
|
|
|
|
JSC::JSValue reval = JSC::call(globalObject, macroFunction, callData,
|
|
|
|
|
globalObject->globalThis(), argList, uncaughtException);
|
|
|
|
|
if (uncaughtException) {
|
|
|
|
|
if (JSC::ErrorInstance *error =
|
|
|
|
|
JSC::jsDynamicCast<JSC::ErrorInstance *>(vm, uncaughtException->value())) {
|
|
|
|
|
fromErrorInstance(zig_exception, globalObject, error, &uncaughtException->stack(),
|
|
|
|
|
JSC::JSValue(uncaughtException));
|
|
|
|
|
return JSC::JSValue::encode({});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return JSC::JSValue::encode(reval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JSC::JSValue::encode({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JSC__JSValue
|
|
|
|
|
// JSC__JSModuleLoader__dependencyKeysIfEvaluated(JSC__JSModuleLoader* arg0,
|
|
|
|
|
// JSC__JSGlobalObject* arg1, JSC__JSModuleRecord* arg2) {
|
|
|
|
|
@@ -1032,243 +1332,6 @@ bWTF__String JSC__JSValue__toWTFString(JSC__JSValue JSValue0, JSC__JSGlobalObjec
|
|
|
|
|
return Wrap<WTF::String, bWTF__String>::wrap(value.toWTFString(arg1));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void populateStackFrameMetadata(const JSC::StackFrame *stackFrame, ZigStackFrame *frame) {
|
|
|
|
|
frame->source_url = Zig::toZigString(stackFrame->sourceURL());
|
|
|
|
|
|
|
|
|
|
if (stackFrame->isWasmFrame()) {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeWasm;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto m_codeBlock = stackFrame->codeBlock();
|
|
|
|
|
if (m_codeBlock) {
|
|
|
|
|
switch (m_codeBlock->codeType()) {
|
|
|
|
|
case JSC::EvalCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeEval;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::ModuleCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeModule;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::GlobalCode: {
|
|
|
|
|
frame->code_type = ZigStackFrameCodeGlobal;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case JSC::FunctionCode: {
|
|
|
|
|
frame->code_type =
|
|
|
|
|
!m_codeBlock->isConstructor() ? ZigStackFrameCodeFunction : ZigStackFrameCodeConstructor;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: ASSERT_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto calleeCell = stackFrame->callee();
|
|
|
|
|
if (!calleeCell || !calleeCell->isObject()) return;
|
|
|
|
|
|
|
|
|
|
JSC::JSObject *callee = JSC::jsCast<JSC::JSObject *>(calleeCell);
|
|
|
|
|
// Does the code block have a user-defined name property?
|
|
|
|
|
JSC::JSValue name = callee->getDirect(m_codeBlock->vm(), m_codeBlock->vm().propertyNames->name);
|
|
|
|
|
if (name && name.isString()) {
|
|
|
|
|
auto str = name.toWTFString(m_codeBlock->globalObject());
|
|
|
|
|
frame->function_name = Zig::toZigString(str);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* For functions (either JSFunction or InternalFunction), fallback to their "native" name
|
|
|
|
|
* property. Based on JSC::getCalculatedDisplayName, "inlining" the
|
|
|
|
|
* JSFunction::calculatedDisplayName\InternalFunction::calculatedDisplayName calls */
|
|
|
|
|
if (JSC::JSFunction *function =
|
|
|
|
|
JSC::jsDynamicCast<JSC::JSFunction *>(m_codeBlock->vm(), callee)) {
|
|
|
|
|
|
|
|
|
|
WTF::String actualName = function->name(m_codeBlock->vm());
|
|
|
|
|
if (!actualName.isEmpty() || function->isHostOrBuiltinFunction()) {
|
|
|
|
|
frame->function_name = Zig::toZigString(actualName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto inferred_name = function->jsExecutable()->name();
|
|
|
|
|
frame->function_name = Zig::toZigString(inferred_name.string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JSC::InternalFunction *function =
|
|
|
|
|
JSC::jsDynamicCast<JSC::InternalFunction *>(m_codeBlock->vm(), callee)) {
|
|
|
|
|
// Based on JSC::InternalFunction::calculatedDisplayName, skipping the "displayName" property
|
|
|
|
|
frame->function_name = Zig::toZigString(function->name());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Based on
|
|
|
|
|
// https://github.com/mceSystems/node-jsc/blob/master/deps/jscshim/src/shim/JSCStackTrace.cpp#L298
|
|
|
|
|
static void populateStackFramePosition(const JSC::StackFrame *stackFrame, ZigString *source_lines,
|
|
|
|
|
int32_t *source_line_numbers, uint8_t source_lines_count,
|
|
|
|
|
ZigStackFramePosition *position) {
|
|
|
|
|
auto m_codeBlock = stackFrame->codeBlock();
|
|
|
|
|
if (!m_codeBlock) return;
|
|
|
|
|
|
|
|
|
|
JSC::BytecodeIndex bytecodeOffset =
|
|
|
|
|
stackFrame->hasBytecodeIndex() ? stackFrame->bytecodeIndex() : JSC::BytecodeIndex();
|
|
|
|
|
|
|
|
|
|
/* Get the "raw" position info.
|
|
|
|
|
* Note that we're using m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeOffset
|
|
|
|
|
* rather than m_codeBlock->expressionRangeForBytecodeOffset in order get the "raw" offsets and
|
|
|
|
|
* avoid the CodeBlock's expressionRangeForBytecodeOffset modifications to the line and column
|
|
|
|
|
* numbers, (we don't need the column number from it, and we'll calculate the line "fixes"
|
|
|
|
|
* ourselves). */
|
|
|
|
|
int startOffset = 0;
|
|
|
|
|
int endOffset = 0;
|
|
|
|
|
int divotPoint = 0;
|
|
|
|
|
unsigned line = 0;
|
|
|
|
|
unsigned unusedColumn = 0;
|
|
|
|
|
m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeIndex(
|
|
|
|
|
bytecodeOffset, divotPoint, startOffset, endOffset, line, unusedColumn);
|
|
|
|
|
divotPoint += m_codeBlock->sourceOffset();
|
|
|
|
|
|
|
|
|
|
// TODO: evaluate if using the API from UnlinkedCodeBlock can be used instead of iterating
|
|
|
|
|
// through source text.
|
|
|
|
|
|
|
|
|
|
/* On the first line of the source code, it seems that we need to "fix" the column with the
|
|
|
|
|
* starting offset. We currently use codeBlock->source()->startPosition().m_column.oneBasedInt()
|
|
|
|
|
* as the offset in the first line rather than codeBlock->firstLineColumnOffset(), which seems
|
|
|
|
|
* simpler (and what CodeBlock::expressionRangeForBytecodeOffset does). This is because
|
|
|
|
|
* firstLineColumnOffset values seems different from what we expect (according to v8's tests)
|
|
|
|
|
* and I haven't dove into the relevant parts in JSC (yet) to figure out why. */
|
|
|
|
|
unsigned columnOffset = line ? 0 : m_codeBlock->source().startColumn().zeroBasedInt();
|
|
|
|
|
|
|
|
|
|
// "Fix" the line number
|
|
|
|
|
JSC::ScriptExecutable *executable = m_codeBlock->ownerExecutable();
|
|
|
|
|
if (std::optional<int> overrideLine = executable->overrideLineNumber(m_codeBlock->vm())) {
|
|
|
|
|
line = overrideLine.value();
|
|
|
|
|
} else {
|
|
|
|
|
line += executable->firstLine();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the staring\ending offsets of the entire expression
|
|
|
|
|
int expressionStart = divotPoint - startOffset;
|
|
|
|
|
int expressionStop = divotPoint + endOffset;
|
|
|
|
|
|
|
|
|
|
// Make sure the range is valid
|
|
|
|
|
WTF::StringView sourceString = m_codeBlock->source().provider()->source();
|
|
|
|
|
if (!expressionStop || expressionStart > static_cast<int>(sourceString.length())) { return; }
|
|
|
|
|
|
|
|
|
|
// Search for the beginning of the line
|
|
|
|
|
unsigned int lineStart = expressionStart;
|
|
|
|
|
while ((lineStart > 0) && ('\n' != sourceString[lineStart - 1])) { lineStart--; }
|
|
|
|
|
// Search for the end of the line
|
|
|
|
|
unsigned int lineStop = expressionStop;
|
|
|
|
|
unsigned int sourceLength = sourceString.length();
|
|
|
|
|
while ((lineStop < sourceLength) && ('\n' != sourceString[lineStop])) { lineStop++; }
|
|
|
|
|
if (source_lines_count > 1 && source_lines != nullptr) {
|
|
|
|
|
auto chars = sourceString.characters8();
|
|
|
|
|
|
|
|
|
|
// Most of the time, when you look at a stack trace, you want a couple lines above
|
|
|
|
|
|
|
|
|
|
source_lines[0] = {&chars[lineStart], lineStop - lineStart};
|
|
|
|
|
source_line_numbers[0] = line;
|
|
|
|
|
|
|
|
|
|
if (lineStart > 0) {
|
|
|
|
|
auto byte_offset_in_source_string = lineStart - 1;
|
|
|
|
|
uint8_t source_line_i = 1;
|
|
|
|
|
auto remaining_lines_to_grab = source_lines_count - 1;
|
|
|
|
|
|
|
|
|
|
while (byte_offset_in_source_string > 0 && remaining_lines_to_grab > 0) {
|
|
|
|
|
unsigned int end_of_line_offset = byte_offset_in_source_string;
|
|
|
|
|
|
|
|
|
|
// This should probably be code points instead of newlines
|
|
|
|
|
while (byte_offset_in_source_string > 0 && chars[byte_offset_in_source_string] != '\n') {
|
|
|
|
|
byte_offset_in_source_string--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We are at the beginning of the line
|
|
|
|
|
source_lines[source_line_i] = {&chars[byte_offset_in_source_string],
|
|
|
|
|
end_of_line_offset - byte_offset_in_source_string + 1};
|
|
|
|
|
|
|
|
|
|
source_line_numbers[source_line_i] = line - source_line_i;
|
|
|
|
|
source_line_i++;
|
|
|
|
|
|
|
|
|
|
remaining_lines_to_grab--;
|
|
|
|
|
|
|
|
|
|
byte_offset_in_source_string -= byte_offset_in_source_string > 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Finally, store the source "positions" info.
|
|
|
|
|
* Notes:
|
|
|
|
|
* - The retrieved column seem to point the "end column". To make sure we're current, we'll
|
|
|
|
|
*calculate the columns ourselves, since we've already found where the line starts. Note that in
|
|
|
|
|
*v8 it should be 0-based here (in contrast the 1-based column number in v8::StackFrame).
|
|
|
|
|
* - The static_casts are ugly, but comes from differences between JSC and v8's api, and should
|
|
|
|
|
*be OK since no source should be longer than "max int" chars.
|
|
|
|
|
* TODO: If expressionStart == expressionStop, then m_endColumn will be equal to m_startColumn.
|
|
|
|
|
*Should we handle this case?
|
|
|
|
|
*/
|
|
|
|
|
position->expression_start = expressionStart;
|
|
|
|
|
position->expression_stop = expressionStop;
|
|
|
|
|
position->line = WTF::OrdinalNumber::fromOneBasedInt(static_cast<int>(line)).zeroBasedInt();
|
|
|
|
|
position->column_start = (expressionStart - lineStart) + columnOffset;
|
|
|
|
|
position->column_stop = position->column_start + (expressionStop - expressionStart);
|
|
|
|
|
position->line_start = lineStart;
|
|
|
|
|
position->line_stop = lineStop;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
static void populateStackFrame(ZigStackTrace *trace, const JSC::StackFrame *stackFrame,
|
|
|
|
|
ZigStackFrame *frame, bool is_top) {
|
|
|
|
|
populateStackFrameMetadata(stackFrame, frame);
|
|
|
|
|
populateStackFramePosition(stackFrame, is_top ? trace->source_lines_ptr : nullptr,
|
|
|
|
|
is_top ? trace->source_lines_numbers : nullptr,
|
|
|
|
|
is_top ? trace->source_lines_to_collect : 0, &frame->position);
|
|
|
|
|
}
|
|
|
|
|
static void populateStackTrace(const WTF::Vector<JSC::StackFrame> &frames, ZigStackTrace *trace) {
|
|
|
|
|
uint8_t frame_i = 0;
|
|
|
|
|
size_t stack_frame_i = 0;
|
|
|
|
|
const size_t total_frame_count = frames.size();
|
|
|
|
|
const uint8_t frame_count =
|
|
|
|
|
total_frame_count < trace->frames_len ? total_frame_count : trace->frames_len;
|
|
|
|
|
|
|
|
|
|
while (frame_i < frame_count && stack_frame_i < total_frame_count) {
|
|
|
|
|
// Skip native frames
|
|
|
|
|
while (stack_frame_i < total_frame_count && !(&frames.at(stack_frame_i))->codeBlock() &&
|
|
|
|
|
!(&frames.at(stack_frame_i))->isWasmFrame()) {
|
|
|
|
|
stack_frame_i++;
|
|
|
|
|
}
|
|
|
|
|
if (stack_frame_i >= total_frame_count) break;
|
|
|
|
|
|
|
|
|
|
ZigStackFrame *frame = &trace->frames_ptr[frame_i];
|
|
|
|
|
populateStackFrame(trace, &frames[stack_frame_i], frame, frame_i == 0);
|
|
|
|
|
stack_frame_i++;
|
|
|
|
|
frame_i++;
|
|
|
|
|
}
|
|
|
|
|
trace->frames_len = frame_i;
|
|
|
|
|
}
|
|
|
|
|
static void fromErrorInstance(ZigException *except, JSC::JSGlobalObject *global,
|
|
|
|
|
JSC::ErrorInstance *err, const Vector<JSC::StackFrame> *stackTrace,
|
|
|
|
|
JSC::JSValue val) {
|
|
|
|
|
JSC::JSObject *obj = JSC::jsDynamicCast<JSC::JSObject *>(global->vm(), val);
|
|
|
|
|
if (stackTrace != nullptr && stackTrace->size() > 0) {
|
|
|
|
|
populateStackTrace(*stackTrace, &except->stack);
|
|
|
|
|
} else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) {
|
|
|
|
|
populateStackTrace(*err->stackTrace(), &except->stack);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except->code = (unsigned char)err->errorType();
|
|
|
|
|
if (err->isStackOverflowError()) { except->code = 253; }
|
|
|
|
|
if (err->isOutOfMemoryError()) { except->code = 8; }
|
|
|
|
|
|
|
|
|
|
if (obj->hasProperty(global, global->vm().propertyNames->message)) {
|
|
|
|
|
except->message = Zig::toZigString(
|
|
|
|
|
obj->getDirect(global->vm(), global->vm().propertyNames->message).toWTFString(global));
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
except->message = Zig::toZigString(err->sanitizedMessageString(global));
|
|
|
|
|
}
|
|
|
|
|
except->name = Zig::toZigString(err->sanitizedNameString(global));
|
|
|
|
|
except->runtime_type = err->runtimeTypeForCause();
|
|
|
|
|
|
|
|
|
|
except->exception = err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void exceptionFromString(ZigException *except, JSC::JSValue value, JSC::JSGlobalObject *global) {
|
|
|
|
|
// Fallback case for when it's a user-defined ErrorLike-object that doesn't inherit from
|
|
|
|
|
// ErrorInstance
|
|
|
|
|
|