[quickjs-devel] Re: SIBGUS when encoding large proto files

  • From: Fabrice Bellard <fabrice@xxxxxxxxxxx>
  • To: quickjs-devel@xxxxxxxxxxxxx
  • Date: Sat, 14 Dec 2019 12:54:29 +0100

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



Other related posts: