Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions code/components/citizen-scripting-core/src/ScriptHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,13 +688,7 @@ static InitFunction initFunction([]()
auto topLevelStackBlob = context.GetArgument<char*>(0);
auto topLevelStackSize = context.GetArgument<uint32_t>(1);

if (fx::g_suppressErrors && topLevelStackBlob != nullptr)
{
context.SetResult(nullptr);
return;
}

fx::g_suppressErrors = true;
static std::string stackTraceBuffer;

auto vis = fx::MakeNew<StringifyingStackVisitor>();

Expand All @@ -718,6 +712,20 @@ static InitFunction initFunction([]()
}
}

// If we have a topLevelStackBlob instead of suppressing (which will eat any error that happens in the same tick), we print
// out the current topLevelStackBlob, this is a really hacky fix, and only really fixes errors being eaten for the JS/C# ScRT
//
// This shouldn't break any existing behavior since g_suppressErrors gets re-enabled on a ScRT environment pop, and we can't
// really traverse the stack safely due to thread safety issues with C# possibly not being on the main thread.
if (fx::g_suppressErrors && topLevelStackBlob != nullptr)
{
stackTraceBuffer = vis->stringTrace.str();
context.SetResult(stackTraceBuffer.c_str());
return;
}

fx::g_suppressErrors = true;

for (int i = 0; i < fx::ms_runtimeStack.size(); i++)
{
auto rit = fx::ms_runtimeStack.begin() + i;
Expand All @@ -735,7 +743,6 @@ static InitFunction initFunction([]()
}
}

static std::string stackTraceBuffer;
stackTraceBuffer = vis->stringTrace.str();

context.SetResult(stackTraceBuffer.c_str());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ static InitFunction initFunction([]()
//NOTE: it still depends on preexisting files from old runtime
static const char* g_platformScripts[] =
{
"citizen:/scripting/v8/source-map.js",
"citizen:/scripting/v8/natives_server.js",
"citizen:/scripting/v8/console.js",
"citizen:/scripting/v8/timer.js",
Expand Down
5 changes: 1 addition & 4 deletions code/components/citizen-scripting-v8/src/V8ScriptRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ extern int g_argc;
extern char** g_argv;

static const char* g_platformScripts[] = {
"citizen:/scripting/v8/source-map.js",
"citizen:/scripting/v8/console.js",
"citizen:/scripting/v8/timer.js",
"citizen:/scripting/v8/msgpack.js",
Expand Down Expand Up @@ -2572,7 +2573,6 @@ void V8ScriptGlobals::Initialize()
}
#endif

#ifndef V8_NODE
m_isolate->SetPromiseRejectCallback([](PromiseRejectMessage message)
{
Local<Promise> promise = message.GetPromise();
Expand All @@ -2595,9 +2595,6 @@ void V8ScriptGlobals::Initialize()
scRT->HandlePromiseRejection(message);

});
#else
m_isolate->SetPromiseRejectCallback(node::PromiseRejectCallback);
#endif

Isolate::Initialize(m_isolate, params);

Expand Down
104 changes: 89 additions & 15 deletions data/shared/citizen/scripting/v8/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,64 @@ const EXT_LOCALFUNCREF = 11;
};
}

let currentStackDumpError = null;
const sourceMapCache = new Map();
const RESOURCE_PATH_RE = /@([^/]+)\/(.+)/;

function loadSourceMap(filePath) {
const match = filePath.match(RESOURCE_PATH_RE);
if (!match) {
return null;
}

const [, , resourcePath] = match;

if (sourceMapCache.has(filePath)) {
return sourceMapCache.get(filePath);
}

let consumer = null;

// try to load the map file for our resource
let mapContent = LoadResourceFile(currentResourceName, resourcePath + '.map');

if (!mapContent) {
// if we don't have a '.map' file then we will have to try and load the data from the .js file that errord
const jsContent = LoadResourceFile(currentResourceName, resourcePath);
if (jsContent) {
const inlineMatch = jsContent.match(/\/\/[#@]\s*sourceMappingURL=data:application\/json;(?:charset=utf-8;)?base64,([A-Za-z0-9+/=]+)\s*$/m);
// if we have a match then assign our mapContent
if (inlineMatch) {
try {
mapContent = atob(inlineMatch[1]);
} catch (e) {
// we don't care if atob errors
}
}
}
}

try {
if (mapContent) {
consumer = new sourceMap.SourceMapConsumer(mapContent);
}
} catch (e) {
// if we had any valid mapContent and we error then we should let the script dev know why this error'd so
// they can hopefully fix their build system.
console.error(`Failed to load source map for ${currentResourceName}/${resourcePath} with error: ${e}`)
}

// we only want to delete our map if we actually have it, it doesn't make sense to clear our source map
// if the source map already doesn't exist.
if (consumer) {
// we don't want to keep our source map around forever, so we'll delete it after a minute.
setTimeout(() => {
sourceMapCache.delete(filePath)
}, 60_000)
}

sourceMapCache.set(filePath, consumer);
return consumer;
}

function prepareStackTrace(error, trace) {
const frames = [];
Expand Down Expand Up @@ -377,30 +434,46 @@ const EXT_LOCALFUNCREF = 11;
const fn = frame.file;

if (fn && !fn.startsWith('citizen:/')) {
const isConstruct = false;
const isEval = false;
const isNative = false;
const methodName = functionName;
const type = frame.typeName;

let frameName = '';

if (isNative) {
frameName = 'native';
} else if (isEval) {
frameName = `eval at ${frame.getEvalOrigin()}`;
} else if (isConstruct) {
frameName = `new ${functionName}`;
} else if (methodName && functionName && methodName !== functionName) {
if (methodName && functionName && methodName !== functionName) {
frameName = `${type}${functionName} [as ${methodName}]`;
} else if (methodName || functionName) {
frameName = `${type}${functionName ? functionName : methodName}`;
}

let originalFile = fn;
let originalLine = frame.lineNumber | 0;
let originalColumn = frame.column | 0;
let originalName = frameName;

const consumer= loadSourceMap(fn);
if (consumer) {
const orig = consumer.originalPositionFor({
line: originalLine,
column: originalColumn
});

if (orig && orig.source) {
// we should make sure we attribute the file to our resource, during export calls we can
// also get other stack frames from another resource appended, making it hard to distingush.
originalFile = `@${currentResourceName}/${orig.source}`;
originalLine = orig.line;
originalColumn = orig.column;
if (orig.name) {
originalName = orig.name;
}
}
}

frames.push({
file: fn,
line: frame.lineNumber | 0,
name: frameName
file: originalFile,
line: originalLine,
column: originalColumn,
name: originalName,
});
}
}
Expand All @@ -418,7 +491,8 @@ const EXT_LOCALFUNCREF = 11;
}

function getError(where, e) {
const stackBlob = global.msgpack_pack(prepareStackTrace(e, parseStack(e.stack)));
const stack = prepareStackTrace(e, parseStack(e.stack))
const stackBlob = global.msgpack_pack(stack);
const fst = global.FormatStackTrace(stackBlob, stackBlob.length);

if (fst !== null && fst !== undefined) {
Expand Down
Loading
Loading