Implementing the Callable extension in a QScriptClass allows scripts to call objects as either functions or constructors. When called as a constructor (i.e. with the "new" keyword), this leads to an application crash after several more lines of script code are executed. For instance:
var a = new A(); // where object "A" has a custom QScriptClass that implements the constructor.
print( "1" );
print( "2" ); // this line crashes.
There is no problem if you implement A's Callable extension to work as a constructor even when not called as a constructor:
var a2 = A(); // this constructor does not cause a crash later on.
The attached code demonstrates this bug. when running the three-line script given above.
Looking at the Qt source code, this bug appears to be due to incorrect handling of the scripting call stack in ClassObjectDelege::construct() (in src/script/bridge/qscriptclassobject.cpp), which pushes context, but never pops it back off after the QScriptClass::extension() call. Here's is a patched version which causes the attached example to work correctly:
JSC::JSObject* ClassObjectDelegate::construct(JSC::ExecState *exec, JSC::JSObject *callee,
const JSC::ArgList &args)
{
Q_ASSERT(callee->inherits(&QScriptObject::info));
QScriptObject obj = static_cast<QScriptObject>(callee);
QScriptObjectDelegate *delegate = obj->delegate();
QScriptClass scriptClass = static_cast<ClassObjectDelegate>(delegate)->scriptClass();
QScriptEnginePrivate *eng_p = scriptEngineFromExec(exec);
// =============================================================
// Change #1 begins here. This line is commented out in 4.6.1 source code
// =============================================================
JSC::ExecState *oldFrame = eng_p->currentFrame;
// =============================================================
// End change 1
// =============================================================
eng_p->pushContext(exec, JSC::JSValue(), args, callee, true);
QScriptContext *ctx = eng_p->contextForFrame(eng_p->currentFrame);
QScriptValue defaultObject = ctx->thisObject();
QScriptValue result = qvariant_cast<QScriptValue>(scriptClass->extension(QScriptClass::Callable, qVariantFromValue(ctx)));
// =============================================================
// Begin change 2: pop context and restore the frame pointer.
// =============================================================
eng_p->popContext();
eng_p->currentFrame = oldFrame;
// =============================================================
// End change 2
// =============================================================
if (!result.isObject())
result = defaultObject;
return JSC::asObject(eng_p->scriptValueToJSCValue(result));
}