Search code examples
c++qtqtscript

Deep copy of a QScriptValue as Global Object


I have a program using QtScript for some automation. I have added a bunch of C++ functions and classes to the global scope of the script engine so that scripts can access them, like so:

QScriptValue fun = engine->newFunction( systemFunc );
engine->globalObject().setProperty( "system", fun );

I would like to be able to run multiple scripts in succession, each with a fresh global state. So if one script sets a global variable, like

myGlobalVar = "stuff";

I want that variable to be erased before the next script runs. My method for doing this is to make a deep copy of the script engine's Global Object, and then restore it when a script finishes running. But the deep copies aren't working, since my system function suddenly breaks with the error:

TypeError: Result of expression 'system' [[object Object]] is not a function.

Here is my deep copy function, adapted from:
http://qt.gitorious.org/qt-labs/scxml/blobs/master/src/qscxml.cpp

QScriptValue copyObject( const QScriptValue& obj, QString level = "" )
{
    if( obj.isObject() || obj.isArray() ) {
        QScriptValue copy = obj.isArray() ? obj.engine()->newArray() : obj.engine()->newObject();
        copy.setData( obj.data() );
        QScriptValueIterator it(obj);
        while(it.hasNext()) {
            it.next();
            qDebug() << "copying" + level + "." + it.name();
            if( it.flags() & QScriptValue::SkipInEnumeration )
                 continue;
            copy.setProperty( it.name(), copyObject(it.value(), level + "." + it.name()) );
        }
        return copy;
    }

    return obj;
}

(the SkipInEnumeration was put in to avoid an infinite loop)

EDIT: Part of the problem, I think, is that in the debugger (QScriptEngineDebugger), the functions and constructors I've added are supposed to appear as type Function, but after copying, they appear as type Object. I haven't yet found a good way of creating a new Function that duplicates an existing one (QScriptEngine::newFunction takes an actual function pointer).


Solution

  • I got it working. Here's the solution in case it's useful for anyone else:

    QScriptValue copyObject( const QScriptValue& obj)
    {
        if( (obj.isObject() || obj.isArray()) && !obj.isFunction() ) {
            QScriptValue copy = obj.isArray() ? obj.engine()->newArray() : obj.engine()->newObject();
            copy.setData( obj.data() );
            QScriptValueIterator it(obj);
            while(it.hasNext()) {
                it.next();
                copy.setProperty( it.name(), copyObject(it.value()) );
            }
            return copy;
        }
    
        return obj;
    }
    

    The important part is the addition of the !obj.isFunction() check, which will just copy Functions as they are, and not do a deep copy. The subtlety here is that isObject() will return true if the item is a Function, which we don't want. This is documented in the Qt docs and I stumbled upon it a few moments ago.

    Also, this check removed the need to avoid copying items marked SkipInEnumeration. The infinite loop is fixed by checking for functions and copying them as-is. Leaving in the SkipInEnumeration actually broke some other stuff, like the eval function and a bunch of other built-ins.