summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKent Hansen <kent.hansen@nokia.com>2010-04-09 13:43:35 (GMT)
committerKent Hansen <kent.hansen@nokia.com>2010-04-09 14:31:12 (GMT)
commit06add85eb8a9bd8f53acd162ce665d46e7ebc137 (patch)
treebb9b6d404e1a3e6bedac121096d9a1076eb80655
parent8f75ee78746a311434db3fe5a3793c6f725fa210 (diff)
downloadQt-06add85eb8a9bd8f53acd162ce665d46e7ebc137.zip
Qt-06add85eb8a9bd8f53acd162ce665d46e7ebc137.tar.gz
Qt-06add85eb8a9bd8f53acd162ce665d46e7ebc137.tar.bz2
Regressions in Global Object prototype access
In 4.5, changing the prototype of the (custom) global object used to "Just Work"(tm). In the JSC-based back-end, the built-in global object acts as a proxy if a custom global object is set, because JSC doesn't (yet, anyway) provide a way to replace the global object. To complicate this further, we also have a proxy to the original global object (that bypasses the custom global object proxying (!)). This is so that properties of the original global object can still be accessed with the QtScript C++ API when a custom global object has been set. Unfortunately, JSObject::prototype()/setPrototype() are not virtual, meaning that a change of prototype in the source object is not reflected in the proxy or vice versa. Work around this for now by syncing the prototype at the appropriate places (QScriptEngine::setGlobalObject(), QScriptValue::setPrototype()). This fixes all except the case when a prototype is set from JS, since such a write doesn't go through our public C++ API. But this case can be detected and handled by the global object's JSObject::put() reimplementation. Created a separate report for that issue: QTBUG-9737. Task-number: QTBUG-7066 Reviewed-by: Jedrzej Nowacki
-rw-r--r--src/script/api/qscriptengine.cpp8
-rw-r--r--src/script/api/qscriptvalue.cpp13
-rw-r--r--tests/auto/qscriptengine/tst_qscriptengine.cpp127
3 files changed, 144 insertions, 4 deletions
diff --git a/src/script/api/qscriptengine.cpp b/src/script/api/qscriptengine.cpp
index d6d1367..2422108 100644
--- a/src/script/api/qscriptengine.cpp
+++ b/src/script/api/qscriptengine.cpp
@@ -1007,11 +1007,15 @@ void QScriptEnginePrivate::setGlobalObject(JSC::JSObject *object)
if (object == globalObject())
return;
QScript::GlobalObject *glob = static_cast<QScript::GlobalObject*>(originalGlobalObject());
- if (object == originalGlobalObjectProxy)
+ if (object == originalGlobalObjectProxy) {
glob->customGlobalObject = 0;
- else {
+ // Sync the internal prototype, since JSObject::prototype() is not virtual.
+ glob->setPrototype(originalGlobalObjectProxy->prototype());
+ } else {
Q_ASSERT(object != originalGlobalObject());
glob->customGlobalObject = object;
+ // Sync the internal prototype, since JSObject::prototype() is not virtual.
+ glob->setPrototype(object->prototype());
}
}
diff --git a/src/script/api/qscriptvalue.cpp b/src/script/api/qscriptvalue.cpp
index 8cf01e7..79d5dcb 100644
--- a/src/script/api/qscriptvalue.cpp
+++ b/src/script/api/qscriptvalue.cpp
@@ -792,19 +792,28 @@ void QScriptValue::setPrototype(const QScriptValue &prototype)
"a different engine");
return;
}
+ JSC::JSObject *thisObject = JSC::asObject(d->jscValue);
JSC::JSValue other = d->engine->scriptValueToJSCValue(prototype);
// check for cycle
JSC::JSValue nextPrototypeValue = other;
while (nextPrototypeValue && nextPrototypeValue.isObject()) {
JSC::JSObject *nextPrototype = JSC::asObject(nextPrototypeValue);
- if (nextPrototype == JSC::asObject(d->jscValue)) {
+ if (nextPrototype == thisObject) {
qWarning("QScriptValue::setPrototype() failed: cyclic prototype value");
return;
}
nextPrototypeValue = nextPrototype->prototype();
}
- JSC::asObject(d->jscValue)->setPrototype(other);
+
+ thisObject->setPrototype(other);
+
+ // Sync the internal Global Object prototype if appropriate.
+ if (((thisObject == d->engine->originalGlobalObjectProxy)
+ && !d->engine->customGlobalObject())
+ || (thisObject == d->engine->customGlobalObject())) {
+ d->engine->originalGlobalObject()->setPrototype(other);
+ }
}
/*!
diff --git a/tests/auto/qscriptengine/tst_qscriptengine.cpp b/tests/auto/qscriptengine/tst_qscriptengine.cpp
index cbc36a7..f556fa1 100644
--- a/tests/auto/qscriptengine/tst_qscriptengine.cpp
+++ b/tests/auto/qscriptengine/tst_qscriptengine.cpp
@@ -104,6 +104,8 @@ private slots:
void getSetGlobalObject();
void globalObjectProperties();
void globalObjectGetterSetterProperty();
+ void customGlobalObjectWithPrototype();
+ void globalObjectWithCustomPrototype();
void builtinFunctionNames_data();
void builtinFunctionNames();
void checkSyntax_data();
@@ -1174,6 +1176,131 @@ void tst_QScriptEngine::globalObjectGetterSetterProperty()
QVERIFY(global.property("baz").equals(789));
}
+void tst_QScriptEngine::customGlobalObjectWithPrototype()
+{
+ for (int x = 0; x < 2; ++x) {
+ QScriptEngine engine;
+ QScriptValue wrap = engine.newObject();
+ QScriptValue global = engine.globalObject();
+ QScriptValue originalGlobalProto = global.prototype();
+ if (!x) {
+ // Set prototype before setting global object
+ wrap.setPrototype(global);
+ QVERIFY(wrap.prototype().strictlyEquals(global));
+ engine.setGlobalObject(wrap);
+ } else {
+ // Set prototype after setting global object
+ engine.setGlobalObject(wrap);
+ wrap.setPrototype(global);
+ QVERIFY(wrap.prototype().strictlyEquals(global));
+ }
+ {
+ QScriptValue ret = engine.evaluate("print");
+ QVERIFY(ret.isFunction());
+ QVERIFY(ret.strictlyEquals(wrap.property("print")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("this.print");
+ QVERIFY(ret.isFunction());
+ QVERIFY(ret.strictlyEquals(wrap.property("print")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("hasOwnProperty('print')");
+ QVERIFY(ret.isBool());
+ QVERIFY(!ret.toBool());
+ }
+ {
+ QScriptValue ret = engine.evaluate("this.hasOwnProperty('print')");
+ QVERIFY(ret.isBool());
+ QVERIFY(!ret.toBool());
+ }
+
+ QScriptValue anotherProto = engine.newObject();
+ anotherProto.setProperty("anotherProtoProperty", 123);
+ global.setPrototype(anotherProto);
+ {
+ QScriptValue ret = engine.evaluate("print");
+ QVERIFY(ret.isFunction());
+ QVERIFY(ret.strictlyEquals(wrap.property("print")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("anotherProtoProperty");
+ QVERIFY(ret.isNumber());
+ QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("this.anotherProtoProperty");
+ QVERIFY(ret.isNumber());
+ QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
+ }
+
+ wrap.setPrototype(anotherProto);
+ {
+ QScriptValue ret = engine.evaluate("print");
+ QVERIFY(ret.isError());
+ QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: print"));
+ }
+ {
+ QScriptValue ret = engine.evaluate("anotherProtoProperty");
+ QVERIFY(ret.isNumber());
+ QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
+ }
+ QVERIFY(global.prototype().strictlyEquals(anotherProto));
+
+ global.setPrototype(originalGlobalProto);
+ engine.setGlobalObject(global);
+ {
+ QScriptValue ret = engine.evaluate("anotherProtoProperty");
+ QVERIFY(ret.isError());
+ QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: anotherProtoProperty"));
+ }
+ {
+ QScriptValue ret = engine.evaluate("print");
+ QVERIFY(ret.isFunction());
+ QVERIFY(ret.strictlyEquals(global.property("print")));
+ }
+ QVERIFY(!anotherProto.property("print").isValid());
+ }
+}
+
+void tst_QScriptEngine::globalObjectWithCustomPrototype()
+{
+ QScriptEngine engine;
+ QScriptValue proto = engine.newObject();
+ proto.setProperty("protoProperty", 123);
+ QScriptValue global = engine.globalObject();
+ QScriptValue originalProto = global.prototype();
+ global.setPrototype(proto);
+ {
+ QScriptValue ret = engine.evaluate("protoProperty");
+ QVERIFY(ret.isNumber());
+ QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("this.protoProperty");
+ QVERIFY(ret.isNumber());
+ QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
+ }
+ {
+ QScriptValue ret = engine.evaluate("hasOwnProperty('protoProperty')");
+ QVERIFY(ret.isBool());
+ QVERIFY(!ret.toBool());
+ }
+ {
+ QScriptValue ret = engine.evaluate("this.hasOwnProperty('protoProperty')");
+ QVERIFY(ret.isBool());
+ QVERIFY(!ret.toBool());
+ }
+
+ // Custom prototype set from JS
+ {
+ QScriptValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a");
+ QVERIFY(ret.isNumber());
+ QEXPECT_FAIL("", "QTBUG-9737", Continue);
+ QVERIFY(ret.strictlyEquals(global.property("a")));
+ }
+}
+
void tst_QScriptEngine::builtinFunctionNames_data()
{
QTest::addColumn<QString>("expression");