Thank you for the report. It is unfortunately a known issue. You are
right, the fix to avoid all recursion in the GC code. I plan to do it
someday.
Best regards,
Fabrice.
On 11/27/19 9:42 PM, Simon CORSIN wrote:
Made a simpler test case which shows the crash. If you just evaluate
that script it will crash while releasing the root object. If you pass
true to "/keepReferenceOnParent/" and false to "/useArray/" in the
/buildBigObject() /function, it will crash in the GC phase while doing
the mark and sweep.
On my machine, QuickJS crashes when it reaches an object depth of 39
487. In Duktape I'm reaching 5 000 000 and I haven't been able to make
it crash yet. Node.js aborts at around 13 millions.
I'm assuming to fix this, we'd have to make JS_FreeValueRT and the GC
mark functions non recursive.
function buildBigObject(maxDepth, props, useArray, keepReferenceOnParent) {
let root;
if (useArray) {
root = [];
} else {
root = {};
}
const stack = [];
stack.push({element: root, depth: 0});
while (stack.length > 0) {
const current = stack.pop();
const element = current.element;
const currentDepth = current.depth;
for (const prop of props) {
let childElement;
if (useArray) {
childElement = [];
element.push(childElement);
} else {
childElement = {};
element[prop] = childElement;
if (keepReferenceOnParent) {
childElement['parent'] = element;
}
}
if (currentDepth + 1 < maxDepth) {
stack.push({element: childElement, depth: currentDepth + 1});
}
}
}
return root;
}
function makeProps(propsCount) {
const out = [];
for (let i = 0; i < propsCount; i++) {
out.push('prop_' + parseInt(i));
}
return out;
}
function countObjects(depth, propsCount) {
let totalObjects = 1;
while (depth > 0) {
totalObjects += Math.pow(propsCount, depth);
depth--;
}
return totalObjects;
}
function buildObjectsUntilCrash() {
let depth = 1;
const props = makeProps(1);
while (true) {
console.log('Building large object with', props.length, 'props and',
depth, 'depth (total', countObjects(depth, props.length), 'objects)');
let object = buildBigObject(depth, props, false, false);
console.log('Releasing large object');
object = undefined;
depth = Math.max(Math.floor(depth * 1.25), depth + 1);
}
}
buildObjectsUntilCrash();
On Tue, Nov 26, 2019 at 3:24 PM Simon CORSIN <simon@xxxxxxxxx
<mailto:simon@xxxxxxxxx>> wrote:
Hey there,
We are using QuickJS alongside ProtobufJS for encoding/decoding
protobuf objects. I've been investigating a crash where it seems
like the stack gets corrupted in some cases after decoding and/or
encoding a protobuf object. The attached git contains some code
which will deserialize/serialize a proto message, increase its size
progressively until it crashes. The crash is native, it doesn't
gracefully fail with a JS stackoverflow error. This sample code also
starts up the main qjs shim from qjs.c in a background thread, since
it triggers much quicker in that case (probably related to the fact
that the background thread will have a smaller stack size).
When running this sample code on my main machine, the crash is
usually just a stack overflow when recursively calling
/__JS_FreeValueRT/. In our Android production app, the behavior is a
bit more random and it doesn't always crash exactly when handling
the protobuf message, it can be a little bit after.
https://github.com/rFlex/QuickJS-debug
Just execute "/run.sh/". You can also just take the "/js/" directory
and run "/qjs --std index.js/" directly.
It's still unclear whether the problem is solely that JS_FreeValueRT
itself can stack overflow or if there is something else going.
Let me know if you need more information or help.
Thanks!
Simon