From 703f41d5a51aacf4d481f49daea2b68d9ad5a797 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Thu, 4 Mar 2021 14:51:30 +0100 Subject: qv4generatorobject: Fix crash when creating new properties Previously HeapObject::GeneratorObject utilized a ValueArray member to store stack information. As we rely on all HeapObject members to have a constant size in order for QV4Table::inlinePropertyOffset to remain accurate, this lead to a memory conflict when a user defined his own property on the Generator. Please do not use ValueArray for any types that are user accessible or that you intend to add properties to. Now the stack information is stored into ArrayObjects instead which circumvents the issue. Fixes: QTBUG-91491 Change-Id: Id6f638bf36a3ae3c9320ac99e67214c48dc81226 Reviewed-by: Fabian Kosmale Reviewed-by: Andrei Golubev (cherry picked from commit 7ea690c61dabd2485e80e7fae9aed392ba02c846) Reviewed-by: Qt Cherry-pick Bot --- src/qml/jsruntime/qv4generatorobject.cpp | 21 +++++++++------------ src/qml/jsruntime/qv4generatorobject_p.h | 3 ++- .../data/generatorCrashNewProperty.qml | 20 ++++++++++++++++++++ .../auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 16 ++++++++++++++++ 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 tests/auto/qml/qqmlecmascript/data/generatorCrashNewProperty.qml diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp index 4eee6f4338..5077bf1d2b 100644 --- a/src/qml/jsruntime/qv4generatorobject.cpp +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -97,24 +97,21 @@ ReturnedValue GeneratorFunction::virtualCall(const FunctionObject *f, const Valu Function *function = gf->function(); ExecutionEngine *engine = gf->engine(); - // We need to set up a separate stack for the generator, as it's being re-entered - uint stackSize = argc // space for the original arguments - + CppStackFrame::requiredJSStackFrameSize(function); // space for the JS stack frame - - size_t requiredMemory = sizeof(GeneratorObject::Data) - sizeof(Value) + sizeof(Value) * stackSize; - Scope scope(gf); - Scoped g(scope, scope.engine->memoryManager->allocManaged(requiredMemory, scope.engine->classes[EngineBase::Class_GeneratorObject])); + Scoped g(scope, engine->memoryManager->allocManaged(sizeof(GeneratorObject::Data), engine->classes[EngineBase::Class_GeneratorObject])); g->setPrototypeOf(ScopedObject(scope, gf->get(scope.engine->id_prototype()))); + // We need to set up a separate JSFrame for the generator, as it's being re-entered Heap::GeneratorObject *gp = g->d(); - gp->stack.size = stackSize; - gp->stack.alloc = stackSize; + gp->values.set(engine, engine->newArrayObject(argc)); + gp->jsFrame.set(engine, engine->newArrayObject(CppStackFrame::requiredJSStackFrameSize(function))); // copy original arguments - memcpy(gp->stack.values, argv, argc*sizeof(Value)); - gp->cppFrame.init(engine, function, gp->stack.values, argc); - gp->cppFrame.setupJSFrame(&gp->stack.values[argc], *gf, gf->scope(), + for (int i = 0; i < argc; i++) + gp->values->arrayData->setArrayData(engine, i, argv[i]); + + gp->cppFrame.init(engine, function, gp->values->arrayData->values.values, argc); + gp->cppFrame.setupJSFrame(gp->jsFrame->arrayData->values.values, *gf, gf->scope(), thisObject ? *thisObject : Value::undefinedValue(), Value::undefinedValue()); diff --git a/src/qml/jsruntime/qv4generatorobject_p.h b/src/qml/jsruntime/qv4generatorobject_p.h index 366319723d..10eea5e46b 100644 --- a/src/qml/jsruntime/qv4generatorobject_p.h +++ b/src/qml/jsruntime/qv4generatorobject_p.h @@ -90,7 +90,8 @@ struct GeneratorPrototype : FunctionObject { Member(class, Pointer, GeneratorFunction *, function) \ Member(class, NoMark, GeneratorState, state) \ Member(class, NoMark, CppStackFrame, cppFrame) \ - Member(class, ValueArray, ValueArray, stack) + Member(class, Pointer, ArrayObject *, values) \ + Member(class, Pointer, ArrayObject *, jsFrame) DECLARE_HEAP_OBJECT(GeneratorObject, Object) { DECLARE_MARKOBJECTS(GeneratorObject); diff --git a/tests/auto/qml/qqmlecmascript/data/generatorCrashNewProperty.qml b/tests/auto/qml/qqmlecmascript/data/generatorCrashNewProperty.qml new file mode 100644 index 0000000000..f775b4c613 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/generatorCrashNewProperty.qml @@ -0,0 +1,20 @@ +// QTBUG-91491 +import QtQml 2.15 + +QtObject { + property int a: 42 + property int b: 0 + property int c: 0 + + function f(myfunc) { + let gen = myfunc(); + gen["u"] = 0 // Adding members to the generator used to cause crashes when calling next() + c = gen.next().value + } + + function refreshA() { + f(function*() { b = 12; return a }); + } + + Component.onCompleted: refreshA(); +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 775026de2e..1ac1c04fd4 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -243,6 +243,7 @@ private slots: void eval(); void function(); void topLevelGeneratorFunction(); + void generatorCrashNewProperty(); void qtbug_10696(); void qtbug_11606(); void qtbug_11600(); @@ -6494,6 +6495,21 @@ void tst_qqmlecmascript::topLevelGeneratorFunction() QCOMPARE(it.property("next").callWithInstance(it).property("value").toInt(), 1); } +// QTBUG-91491 +void tst_qqmlecmascript::generatorCrashNewProperty() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("generatorCrashNewProperty.qml")); + + QScopedPointer o(component.create()); + + QVERIFY2(o != nullptr, qPrintable(component.errorString())); + + QCOMPARE(o->property("a").toInt(), 42); + QCOMPARE(o->property("b").toInt(), 12); + QCOMPARE(o->property("c").toInt(), 42); +} + // Test the "Qt.include" method void tst_qqmlecmascript::include() { -- cgit v1.2.3