// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include #include "qscriptengine_p.h" #include "qscriptvalueimpl_p.h" #include "qscriptcontext_p.h" #include "qscriptmember_p.h" #include "qscriptobject_p.h" #include "qscriptable.h" #include "qscriptable_p.h" #include "qscriptextqobject_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE // we use bits 15..12 of property flags enum { PROPERTY_ID = 0 << 12, DYNAPROPERTY_ID = 1 << 12, METHOD_ID = 2 << 12, CHILD_ID = 3 << 12, ID_MASK = 7 << 12, MAYBE_OVERLOADED = 8 << 12 }; static const bool GeneratePropertyFunctions = true; int QScriptMetaType::typeId() const { if (isVariant()) return QMetaType::type("QVariant"); return isMetaEnum() ? 2/*int*/ : m_typeId; } QByteArray QScriptMetaType::name() const { if (!m_name.isEmpty()) return m_name; else if (m_kind == Variant) return "QVariant"; return QMetaType::typeName(typeId()); } namespace QScript { class QObjectNotifyCaller : public QObject { public: void callConnectNotify(const char *signal) { connectNotify(signal); } void callDisconnectNotify(const char *signal) { disconnectNotify(signal); } }; class QtPropertyFunction: public QScriptFunction { public: QtPropertyFunction(const QMetaObject *meta, int index) : m_meta(meta), m_index(index) { } ~QtPropertyFunction() { } virtual void execute(QScriptContextPrivate *context); virtual Type type() const { return QScriptFunction::QtProperty; } virtual QString functionName() const; private: const QMetaObject *m_meta; int m_index; }; class QObjectPrototype : public QObject { Q_OBJECT public: QObjectPrototype(QObject *parent = 0) : QObject(parent) { } ~QObjectPrototype() { } }; static inline QByteArray methodName(const QMetaMethod &method) { QByteArray signature = method.signature(); return signature.left(signature.indexOf('(')); } static inline QVariant variantFromValue(QScriptEnginePrivate *eng, int targetType, const QScriptValueImpl &value) { QVariant v(targetType, (void *)0); Q_ASSERT(eng); if (QScriptEnginePrivate::convert(value, targetType, v.data(), eng)) return v; if (uint(targetType) == QVariant::LastType) return value.toVariant(); if (value.isVariant()) { v = value.toVariant(); if (v.canConvert(QVariant::Type(targetType))) { v.convert(QVariant::Type(targetType)); return v; } QByteArray typeName = v.typeName(); if (typeName.endsWith('*') && (QMetaType::type(typeName.left(typeName.size()-1)) == targetType)) { return QVariant(targetType, *reinterpret_cast(v.data())); } } return QVariant(); } void ExtQObject::Instance::finalize(QScriptEnginePrivate *eng) { switch (ownership) { case QScriptEngine::QtOwnership: break; case QScriptEngine::ScriptOwnership: if (value) eng->disposeQObject(value); break; case QScriptEngine::AutoOwnership: if (value && !value->parent()) eng->disposeQObject(value); break; } } ExtQObject::Instance *ExtQObject::Instance::get(const QScriptValueImpl &object, QScriptClassInfo *klass) { if (! klass || klass == object.classInfo()) return static_cast (object.objectData()); return 0; } static inline QScriptable *scriptableFromQObject(QObject *qobj) { void *ptr = qobj->qt_metacast("QScriptable"); return reinterpret_cast(ptr); } static bool isObjectProperty(const QScriptValueImpl &object, const char *name) { QScriptEnginePrivate *eng = object.engine(); QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); QScript::Member member; QScriptValueImpl base; return object.resolve(nameId, &member, &base, QScriptValue::ResolveLocal, QScript::Read) && member.testFlags(QScript::Member::ObjectProperty); } static bool hasMethodAccess(const QMetaMethod &method, int index, const QScriptEngine::QObjectWrapOptions &opt) { return (method.access() != QMetaMethod::Private) && ((index != 2) || !(opt & QScriptEngine::ExcludeDeleteLater)); } static bool isEnumerableMetaProperty(const QMetaProperty &prop, const QMetaObject *mo, int index) { return prop.isScriptable() && prop.isValid() // the following lookup is to ensure that we have the // "most derived" occurrence of the property with this name && (mo->indexOfProperty(prop.name()) == index); } static uint flagsForMetaProperty(const QMetaProperty &prop) { return (QScriptValue::Undeletable | (!prop.isWritable() ? QScriptValue::ReadOnly : QScriptValue::PropertyFlag(0)) | (GeneratePropertyFunctions ? (QScriptValue::PropertyGetter | QScriptValue::PropertySetter) : QScriptValue::PropertyFlag(0)) | QScriptValue::QObjectMember | PROPERTY_ID); } static int indexOfMetaEnum(const QMetaObject *meta, const QByteArray &str) { QByteArray scope; QByteArray name; int scopeIdx = str.lastIndexOf("::"); if (scopeIdx != -1) { scope = str.left(scopeIdx); name = str.mid(scopeIdx + 2); } else { name = str; } for (int i = meta->enumeratorCount() - 1; i >= 0; --i) { QMetaEnum m = meta->enumerator(i); if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) return i; } return -1; } static QMetaMethod metaMethod(const QMetaObject *meta, QMetaMethod::MethodType type, int index) { if (type != QMetaMethod::Constructor) return meta->method(index); else return meta->constructor(index); } static void callQtMethod(QScriptContextPrivate *context, QMetaMethod::MethodType callType, QObject *thisQObject, const QMetaObject *meta, int initialIndex, bool maybeOverloaded) { QScriptValueImpl result; QScriptEnginePrivate *engine = context->engine(); int limit; #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE int lastFoundIndex = initialIndex; QScriptMetaObject *metaCache = engine->cachedMetaObject(meta); if (callType != QMetaMethod::Constructor) limit = metaCache->methodLowerBound(initialIndex); else limit = 0; #else limit = 0; #endif QByteArray funName; QScriptMetaMethod chosenMethod; int chosenIndex = -1; QVarLengthArray args; QVector candidates; QVector unresolved; QVector tooFewArgs; QVector conversionFailed; int index; for (index = initialIndex; index >= limit; --index) { QScriptMetaMethod mtd; #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE if (callType != QMetaMethod::Constructor) mtd = metaCache->findMethod(index); if (!mtd.isValid()) #endif { QMetaMethod method = metaMethod(meta, callType, index); QVector types; // resolve return type QByteArray returnTypeName = method.typeName(); int rtype = QMetaType::type(returnTypeName); if ((rtype == 0) && !returnTypeName.isEmpty()) { if (returnTypeName == "QVariant") { types.append(QScriptMetaType::variant()); } else { int enumIndex = indexOfMetaEnum(meta, returnTypeName); if (enumIndex != -1) types.append(QScriptMetaType::metaEnum(enumIndex, returnTypeName)); else types.append(QScriptMetaType::unresolved(returnTypeName)); } } else { if (callType == QMetaMethod::Constructor) types.append(QScriptMetaType::metaType(QMetaType::QObjectStar, "QObject*")); else if (returnTypeName == "QVariant") types.append(QScriptMetaType::variant()); else types.append(QScriptMetaType::metaType(rtype, returnTypeName)); } // resolve argument types QList parameterTypeNames = method.parameterTypes(); for (int i = 0; i < parameterTypeNames.count(); ++i) { QByteArray argTypeName = parameterTypeNames.at(i); int atype = QMetaType::type(argTypeName); if (atype == 0) { if (argTypeName == "QVariant") { types.append(QScriptMetaType::variant()); } else { int enumIndex = indexOfMetaEnum(meta, argTypeName); if (enumIndex != -1) types.append(QScriptMetaType::metaEnum(enumIndex, argTypeName)); else types.append(QScriptMetaType::unresolved(argTypeName)); } } else { if (argTypeName == "QVariant") types.append(QScriptMetaType::variant()); else types.append(QScriptMetaType::metaType(atype, argTypeName)); } } mtd = QScriptMetaMethod(methodName(method), types); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE if (mtd.fullyResolved() && (callType != QMetaMethod::Constructor)) metaCache->registerMethod(index, mtd); #endif } if (index == initialIndex) funName = mtd.name(); else { if (mtd.name() != funName) continue; #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE lastFoundIndex = index; #endif } if (context->argumentCount() < mtd.argumentCount()) { tooFewArgs.append(index); continue; } if (!mtd.fullyResolved()) { // remember it so we can give an error message later, if necessary unresolved.append(QScriptMetaArguments(/*matchDistance=*/INT_MAX, index, mtd, QVarLengthArray())); if (mtd.hasUnresolvedReturnType()) continue; } if (args.count() != mtd.count()) args.resize(mtd.count()); QScriptMetaType retType = mtd.returnType(); args[0] = QVariant(retType.typeId(), (void *)0); // the result // try to convert arguments bool converted = true; int matchDistance = 0; for (int i = 0; converted && i < mtd.argumentCount(); ++i) { QScriptValueImpl actual = context->argument(i); QScriptMetaType argType = mtd.argumentType(i); int tid = -1; QVariant v; if (argType.isUnresolved()) { v = QVariant(QMetaType::QObjectStar, (void *)0); converted = engine->convertToNativeQObject( actual, argType.name(), reinterpret_cast(v.data())); } else if (argType.isVariant()) { if (actual.isVariant()) { v = actual.variantValue(); } else { v = actual.toVariant(); converted = v.isValid() || actual.isUndefined() || actual.isNull(); } } else { tid = argType.typeId(); v = QVariant(tid, (void *)0); converted = QScriptEnginePrivate::convert(actual, tid, v.data(), engine); if (engine->hasUncaughtException()) return; } if (!converted) { if (actual.isVariant()) { if (tid == -1) tid = argType.typeId(); QVariant &vv = actual.variantValue(); if (vv.canConvert(QVariant::Type(tid))) { v = vv; converted = v.convert(QVariant::Type(tid)); if (converted && (vv.userType() != tid)) matchDistance += 10; } else { QByteArray vvTypeName = vv.typeName(); if (vvTypeName.endsWith('*') && (vvTypeName.left(vvTypeName.size()-1) == argType.name())) { v = QVariant(tid, *reinterpret_cast(vv.data())); converted = true; matchDistance += 10; } } } else if (actual.isNumber() || actual.isString()) { // see if it's an enum value QMetaEnum m; if (argType.isMetaEnum()) { m = meta->enumerator(argType.enumeratorIndex()); } else { int mi = indexOfMetaEnum(meta, argType.name()); if (mi != -1) m = meta->enumerator(mi); } if (m.isValid()) { if (actual.isNumber()) { int ival = actual.toInt32(); if (m.valueToKey(ival) != 0) { qVariantSetValue(v, ival); converted = true; matchDistance += 10; } } else { QString sval = actual.toString(); int ival = m.keyToValue(sval.toLatin1()); if (ival != -1) { qVariantSetValue(v, ival); converted = true; matchDistance += 10; } } } } } else { // determine how well the conversion matched if (actual.isNumber()) { switch (tid) { case QMetaType::Double: // perfect break; case QMetaType::Float: matchDistance += 1; break; case QMetaType::LongLong: case QMetaType::ULongLong: matchDistance += 2; break; case QMetaType::Long: case QMetaType::ULong: matchDistance += 3; break; case QMetaType::Int: case QMetaType::UInt: matchDistance += 4; break; case QMetaType::Short: case QMetaType::UShort: matchDistance += 5; break; case QMetaType::Char: case QMetaType::UChar: matchDistance += 6; break; default: matchDistance += 10; break; } } else if (actual.isString()) { switch (tid) { case QMetaType::QString: // perfect break; default: matchDistance += 10; break; } } else if (actual.isBoolean()) { switch (tid) { case QMetaType::Bool: // perfect break; default: matchDistance += 10; break; } } else if (actual.isDate()) { switch (tid) { case QMetaType::QDateTime: // perfect break; case QMetaType::QDate: matchDistance += 1; break; case QMetaType::QTime: matchDistance += 2; break; default: matchDistance += 10; break; } } else if (actual.isRegExp()) { switch (tid) { case QMetaType::QRegExp: // perfect break; default: matchDistance += 10; break; } } else if (actual.isVariant()) { if (argType.isVariant() || (actual.variantValue().userType() == tid)) { // perfect } else { matchDistance += 10; } } else if (actual.isArray()) { switch (tid) { case QMetaType::QStringList: case QMetaType::QVariantList: matchDistance += 5; break; default: matchDistance += 10; break; } } else if (actual.isQObject()) { switch (tid) { case QMetaType::QObjectStar: case QMetaType::QWidgetStar: // perfect break; default: matchDistance += 10; break; } } else if (actual.isNull()) { switch (tid) { case QMetaType::VoidStar: case QMetaType::QObjectStar: case QMetaType::QWidgetStar: // perfect break; default: if (!argType.name().endsWith('*')) matchDistance += 10; break; } } else { matchDistance += 10; } } if (converted) args[i+1] = v; } if (converted) { if ((context->argumentCount() == mtd.argumentCount()) && (matchDistance == 0)) { // perfect match, use this one chosenMethod = mtd; chosenIndex = index; break; } else { bool redundant = false; if ((callType != QMetaMethod::Constructor) && (index < meta->methodOffset())) { // it is possible that a virtual method is redeclared in a subclass, // in which case we want to ignore the superclass declaration for (int i = 0; i < candidates.size(); ++i) { const QScriptMetaArguments &other = candidates.at(i); if (mtd.types() == other.method.types()) { redundant = true; break; } } } if (!redundant) { QScriptMetaArguments metaArgs(matchDistance, index, mtd, args); if (candidates.isEmpty()) { candidates.append(metaArgs); } else { const QScriptMetaArguments &otherArgs = candidates.at(0); if ((args.count() > otherArgs.args.count()) || ((args.count() == otherArgs.args.count()) && (matchDistance <= otherArgs.matchDistance))) { candidates.prepend(metaArgs); } else { candidates.append(metaArgs); } } } } } else if (mtd.fullyResolved()) { conversionFailed.append(index); } if (!maybeOverloaded) break; } #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE if ((index == -1) && (lastFoundIndex != limit) && maybeOverloaded && (callType != QMetaMethod::Constructor)) { metaCache->setMethodLowerBound(initialIndex, lastFoundIndex); } #endif if ((chosenIndex == -1) && candidates.isEmpty()) { context->calleeMetaIndex = initialIndex; #ifndef Q_SCRIPT_NO_EVENT_NOTIFY engine->notifyFunctionEntry(context); #endif if (!conversionFailed.isEmpty()) { QString message = QString::fromLatin1("incompatible type of argument(s) in call to %0(); candidates were\n") .arg(QLatin1String(funName)); for (int i = 0; i < conversionFailed.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = metaMethod(meta, callType, conversionFailed.at(i)); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } result = context->throwError(QScriptContext::TypeError, message); } else if (!unresolved.isEmpty()) { QScriptMetaArguments argsInstance = unresolved.first(); int unresolvedIndex = argsInstance.method.firstUnresolvedIndex(); Q_ASSERT(unresolvedIndex != -1); QScriptMetaType unresolvedType = argsInstance.method.type(unresolvedIndex); QString unresolvedTypeName = QString::fromLatin1(unresolvedType.name()); QString message = QString::fromLatin1("cannot call %0(): ") .arg(QString::fromLatin1(funName)); if (unresolvedIndex > 0) { message.append(QString::fromLatin1("argument %0 has unknown type `%1'"). arg(unresolvedIndex).arg(unresolvedTypeName)); } else { message.append(QString::fromLatin1("unknown return type `%0'") .arg(unresolvedTypeName)); } message.append(QString::fromLatin1(" (register the type with qScriptRegisterMetaType())")); result = context->throwError(QScriptContext::TypeError, message); } else { QString message = QString::fromLatin1("too few arguments in call to %0(); candidates are\n") .arg(QLatin1String(funName)); for (int i = 0; i < tooFewArgs.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = metaMethod(meta, callType, tooFewArgs.at(i)); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } result = context->throwError(QScriptContext::SyntaxError, message); } } else { if (chosenIndex == -1) { QScriptMetaArguments metaArgs = candidates.at(0); if ((candidates.size() > 1) && (metaArgs.args.count() == candidates.at(1).args.count()) && (metaArgs.matchDistance == candidates.at(1).matchDistance)) { // ambiguous call QString message = QString::fromLatin1("ambiguous call of overloaded function %0(); candidates were\n") .arg(QLatin1String(funName)); for (int i = 0; i < candidates.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = metaMethod(meta, callType, candidates.at(i).index); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } result = context->throwError(QScriptContext::TypeError, message); } else { chosenMethod = metaArgs.method; chosenIndex = metaArgs.index; args = metaArgs.args; } } if (chosenIndex != -1) { // call it context->calleeMetaIndex = chosenIndex; QVarLengthArray array(args.count()); void **params = array.data(); for (int i = 0; i < args.count(); ++i) { const QVariant &v = args[i]; switch (chosenMethod.type(i).kind()) { case QScriptMetaType::Variant: params[i] = const_cast(&v); break; case QScriptMetaType::MetaType: case QScriptMetaType::MetaEnum: case QScriptMetaType::Unresolved: params[i] = const_cast(v.constData()); break; default: Q_ASSERT(0); } } QScriptable *scriptable = 0; if (thisQObject) scriptable = scriptableFromQObject(thisQObject); QScriptEngine *oldEngine = 0; if (scriptable) { oldEngine = QScriptablePrivate::get(scriptable)->engine; QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(engine); } #ifndef Q_SCRIPT_NO_EVENT_NOTIFY engine->notifyFunctionEntry(context); #endif if (callType == QMetaMethod::Constructor) { Q_ASSERT(meta != 0); meta->static_metacall(QMetaObject::CreateInstance, chosenIndex, params); } else { Q_ASSERT(thisQObject != 0); QMetaObject::metacall(thisQObject, QMetaObject::InvokeMetaMethod, chosenIndex, params); } if (scriptable) QScriptablePrivate::get(scriptable)->engine = oldEngine; if (context->state() == QScriptContext::ExceptionState) { result = context->returnValue(); // propagate } else { QScriptMetaType retType = chosenMethod.returnType(); if (retType.isVariant()) { result = engine->valueFromVariant(*(QVariant *)params[0]); } else if (retType.typeId() != 0) { result = engine->create(retType.typeId(), params[0]); if (!result.isValid()) engine->newVariant(&result, QVariant(retType.typeId(), params[0])); } else { result = engine->undefinedValue(); } } } } context->m_result = result; #ifndef Q_SCRIPT_NO_EVENT_NOTIFY engine->notifyFunctionExit(context); #endif } class ExtQObjectDataIterator: public QScriptClassDataIterator { public: ExtQObjectDataIterator(const QScriptValueImpl &object); virtual ~ExtQObjectDataIterator(); virtual bool hasNext() const; virtual void next(QScript::Member *member); virtual bool hasPrevious() const; virtual void previous(QScript::Member *member); virtual void toFront(); virtual void toBack(); private: enum State { MetaProperties, DynamicProperties, MetaMethods }; QScriptValueImpl m_object; int m_index; State m_state; }; ExtQObjectDataIterator::ExtQObjectDataIterator(const QScriptValueImpl &object) { m_object = object; toFront(); } ExtQObjectDataIterator::~ExtQObjectDataIterator() { } bool ExtQObjectDataIterator::hasNext() const { ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return false; const QMetaObject *meta = inst->value->metaObject(); int i = m_index; switch (m_state) { case MetaProperties: { for ( ; i < meta->propertyCount(); ++i) { QMetaProperty prop = meta->property(i); if (isEnumerableMetaProperty(prop, meta, i) && !isObjectProperty(m_object, prop.name())) { return true; } } i = 0; // fall-through } case DynamicProperties: { QList dpNames = inst->value->dynamicPropertyNames(); for ( ; i < dpNames.count(); ++i) { if (!isObjectProperty(m_object, dpNames.at(i))) { return true; } } if (inst->options & QScriptEngine::SkipMethodsInEnumeration) return false; i = (inst->options & QScriptEngine::ExcludeSuperClassMethods) ? meta->methodOffset() : 0; // fall-through } case MetaMethods: { for ( ; i < meta->methodCount(); ++i) { QMetaMethod method = meta->method(i); if (hasMethodAccess(method, i, inst->options) && !isObjectProperty(m_object, method.signature())) { return true; } } } } // switch return false; } void ExtQObjectDataIterator::next(QScript::Member *member) { QScriptEnginePrivate *eng = m_object.engine(); ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return; const QMetaObject *meta = inst->value->metaObject(); int i = m_index; switch (m_state) { case MetaProperties: { for ( ; i < meta->propertyCount(); ++i) { QMetaProperty prop = meta->property(i); if (isEnumerableMetaProperty(prop, meta, i) && !isObjectProperty(m_object, prop.name())) { QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(prop.name())); member->native(nameId, i, flagsForMetaProperty(prop)); m_index = i + 1; return; } } m_state = DynamicProperties; m_index = 0; i = m_index; // fall-through } case DynamicProperties: { QList dpNames = inst->value->dynamicPropertyNames(); for ( ; i < dpNames.count(); ++i) { if (!isObjectProperty(m_object, dpNames.at(i))) { QByteArray name = dpNames.at(i); QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); member->native(nameId, i, QScriptValue::QObjectMember | DYNAPROPERTY_ID); m_index = i + 1; return; } } m_state = MetaMethods; m_index = (inst->options & QScriptEngine::ExcludeSuperClassMethods) ? meta->methodOffset() : 0; i = m_index; // fall-through } case MetaMethods: { for ( ; i < meta->methodCount(); ++i) { QMetaMethod method = meta->method(i); if (hasMethodAccess(method, i, inst->options) && !isObjectProperty(m_object, method.signature())) { QMetaMethod method = meta->method(i); QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(method.signature())); member->native(nameId, i, QScriptValue::QObjectMember | METHOD_ID); m_index = i + 1; return; } } } } // switch member->invalidate(); } bool ExtQObjectDataIterator::hasPrevious() const { ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return false; const QMetaObject *meta = inst->value->metaObject(); int i = m_index - 1; switch (m_state) { case MetaMethods: { int limit = (inst->options & QScriptEngine::ExcludeSuperClassMethods) ? meta->methodOffset() : 0; for ( ; i >= limit; --i) { QMetaMethod method = meta->method(i); if (hasMethodAccess(method, i, inst->options) && !isObjectProperty(m_object, method.signature())) { return true; } } i = inst->value->dynamicPropertyNames().count() - 1; // fall-through } case DynamicProperties: { QList dpNames = inst->value->dynamicPropertyNames(); for ( ; i >= 0; --i) { if (!isObjectProperty(m_object, dpNames.at(i))) { return true; } } i = meta->propertyCount() - 1; // fall-through } case MetaProperties: { int limit = (inst->options & QScriptEngine::ExcludeSuperClassProperties) ? meta->propertyOffset() : 0; for ( ; i >= limit; --i) { QMetaProperty prop = meta->property(i); if (isEnumerableMetaProperty(prop, meta, i) && !isObjectProperty(m_object, prop.name())) { return true; } } } } // switch return false; } void ExtQObjectDataIterator::previous(QScript::Member *member) { QScriptEnginePrivate *eng = m_object.engine(); ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return; const QMetaObject *meta = inst->value->metaObject(); int i = m_index - 1; switch (m_state) { case MetaMethods: { int limit = (inst->options & QScriptEngine::ExcludeSuperClassMethods) ? meta->methodOffset() : 0; for ( ; i >= limit; --i) { QMetaMethod method = meta->method(i); if (hasMethodAccess(method, i, inst->options) && !isObjectProperty(m_object, method.signature())) { QMetaMethod method = meta->method(i); QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(method.signature())); member->native(nameId, i, QScriptValue::QObjectMember | METHOD_ID); m_index = i; return; } } m_state = DynamicProperties; m_index = inst->value->dynamicPropertyNames().count() - 1; i = m_index; // fall-through } case DynamicProperties: { QList dpNames = inst->value->dynamicPropertyNames(); for ( ; i >= 0; --i) { if (!isObjectProperty(m_object, dpNames.at(i))) { QByteArray name = dpNames.at(i); QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); member->native(nameId, i, QScriptValue::QObjectMember | DYNAPROPERTY_ID); m_index = i; return; } } m_state = MetaProperties; m_index = meta->propertyCount() - 1; i = m_index; // fall-through } case MetaProperties: { int limit = (inst->options & QScriptEngine::ExcludeSuperClassProperties) ? meta->propertyOffset() : 0; for ( ; i >= limit; --i) { QMetaProperty prop = meta->property(i); if (isEnumerableMetaProperty(prop, meta, i) && !isObjectProperty(m_object, prop.name())) { QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(prop.name())); member->native(nameId, i, flagsForMetaProperty(prop)); m_index = i; return; } } } } // switch member->invalidate(); } void ExtQObjectDataIterator::toFront() { ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return; m_state = MetaProperties; const QMetaObject *meta = inst->value->metaObject(); m_index = (inst->options & QScriptEngine::ExcludeSuperClassProperties) ? meta->propertyOffset() : 0; } void ExtQObjectDataIterator::toBack() { ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); if (!inst->value) return; if (inst->options & QScriptEngine::SkipMethodsInEnumeration) { m_state = DynamicProperties; m_index = inst->value->dynamicPropertyNames().count(); } else { m_state = MetaMethods; const QMetaObject *meta = inst->value->metaObject(); m_index = meta->methodCount(); } } class ExtQObjectData: public QScriptClassData { public: ExtQObjectData(QScriptClassInfo *classInfo) : m_classInfo(classInfo) { } virtual bool resolve(const QScriptValueImpl &object, QScriptNameIdImpl *nameId, QScript::Member *member, QScriptValueImpl *, QScript::AccessMode access) { ExtQObject::Instance *inst = ExtQObject::Instance::get(object, m_classInfo); QObject *qobject = inst->value; if (! qobject) { // the object was deleted. We return true so we can // throw an error in get()/put() member->native(nameId, /*id=*/-1, /*flags=*/0); return true; } const QScriptEngine::QObjectWrapOptions &opt = inst->options; const QMetaObject *meta = qobject->metaObject(); QScriptEnginePrivate *eng = object.engine(); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE QScriptMetaObject *metaCache = eng->cachedMetaObject(meta); if (metaCache->findMember(nameId, member)) { bool ignore = false; switch (member->flags() & ID_MASK) { case PROPERTY_ID: ignore = (opt & QScriptEngine::ExcludeSuperClassProperties) && (member->id() < meta->propertyOffset()); break; case METHOD_ID: ignore = ((opt & QScriptEngine::ExcludeSuperClassMethods) && (member->id() < meta->methodOffset())) || ((opt & QScriptEngine::ExcludeDeleteLater) && (member->id() == 2)); break; // we don't cache dynamic properties nor children, // so no need to handle DYNAPROPERTY_ID and CHILD_ID default: break; } if (!ignore) return true; } #endif QString memberName = eng->toString(nameId); QByteArray name = memberName.toLatin1(); int index = -1; if (name.contains('(')) { QByteArray normalized = QMetaObject::normalizedSignature(name); if (-1 != (index = meta->indexOfMethod(normalized))) { QMetaMethod method = meta->method(index); if (hasMethodAccess(method, index, opt)) { member->native(nameId, index, QScriptValue::QObjectMember | METHOD_ID); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE metaCache->registerMember(nameId, *member); #endif if (!(opt & QScriptEngine::ExcludeSuperClassMethods) || (index >= meta->methodOffset())) { return true; } } } } index = meta->indexOfProperty(name); if (index != -1) { QMetaProperty prop = meta->property(index); if (prop.isScriptable()) { member->native(nameId, index, flagsForMetaProperty(prop)); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE metaCache->registerMember(nameId, *member); #endif if (!(opt & QScriptEngine::ExcludeSuperClassProperties) || (index >= meta->propertyOffset())) { return true; } } } index = qobject->dynamicPropertyNames().indexOf(name); if (index != -1) { member->native(nameId, index, QScriptValue::QObjectMember | DYNAPROPERTY_ID); // not cached because it can be removed return true; } const int offset = (opt & QScriptEngine::ExcludeSuperClassMethods) ? meta->methodOffset() : 0; for (index = meta->methodCount() - 1; index >= offset; --index) { QMetaMethod method = meta->method(index); if (hasMethodAccess(method, index, opt) && (methodName(method) == name)) { member->native(nameId, index, QScriptValue::QObjectMember | METHOD_ID | MAYBE_OVERLOADED); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE metaCache->registerMember(nameId, *member); #endif return true; } } if (!(opt & QScriptEngine::ExcludeChildObjects)) { QList children = qobject->children(); for (index = 0; index < children.count(); ++index) { QObject *child = children.at(index); if (child->objectName() == memberName) { member->native(nameId, index, QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::SkipInEnumeration | CHILD_ID); // not cached because it can be removed or change name return true; } } } if ((access & QScript::Write) && (opt & QScriptEngine::AutoCreateDynamicProperties)) { member->native(nameId, -1, DYNAPROPERTY_ID); return true; } return false; } virtual bool get(const QScriptValueImpl &obj, const QScript::Member &member, QScriptValueImpl *result) { if (! member.isNativeProperty()) return false; QScriptEnginePrivate *eng = obj.engine(); ExtQObject::Instance *inst = ExtQObject::Instance::get(obj, m_classInfo); QObject *qobject = inst->value; if (!qobject) { QScriptContextPrivate *ctx = eng->currentContext(); *result = ctx->throwError( QString::fromLatin1("cannot access member `%0' of deleted QObject") .arg(member.nameId()->s)); return true; } switch (member.flags() & ID_MASK) { case PROPERTY_ID: { const QMetaObject *meta = qobject->metaObject(); const int propertyIndex = member.id(); QMetaProperty prop = meta->property(propertyIndex); Q_ASSERT(prop.isScriptable()); if (GeneratePropertyFunctions) { QScriptValueImpl accessor; #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE QScriptMetaObject *metaCache = eng->cachedMetaObject(meta); accessor = metaCache->findPropertyAccessor(propertyIndex); if (!accessor.isValid()) { #endif accessor = eng->createFunction(new QtPropertyFunction(meta, propertyIndex)); #ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE metaCache->registerPropertyAccessor(propertyIndex, accessor); } #endif *result = accessor; } else { QVariant v = prop.read(qobject); *result = eng->valueFromVariant(v); } } break; case DYNAPROPERTY_ID: { if (member.id() != -1) { QVariant v = qobject->property(member.nameId()->s.toLatin1()); *result = eng->valueFromVariant(v); } else { *result = eng->undefinedValue(); } } break; case METHOD_ID: { QScript::Member m; bool maybeOverloaded = (member.flags() & MAYBE_OVERLOADED) != 0; *result = eng->createFunction(new QtFunction(obj, member.id(), maybeOverloaded)); // make it persist (otherwise Function.prototype.disconnect() would fail) uint flags = QScriptValue::QObjectMember; if (inst->options & QScriptEngine::SkipMethodsInEnumeration) flags |= QScriptValue::SkipInEnumeration; QScriptObject *instance = obj.objectValue(); if (!instance->findMember(member.nameId(), &m)) instance->createMember(member.nameId(), &m, flags); instance->put(m, *result); } break; case CHILD_ID: { QObject *child = qobject->children().at(member.id()); result->invalidate(); QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; eng->newQObject(result, child, QScriptEngine::QtOwnership, opt); } break; } // switch return true; } virtual bool put(QScriptValueImpl *object, const QScript::Member &member, const QScriptValueImpl &value) { if (! member.isNativeProperty() || ! member.isWritable()) return false; ExtQObject::Instance *inst = ExtQObject::Instance::get(*object, m_classInfo); QObject *qobject = inst->value; if (!qobject) { QScriptEnginePrivate *eng = object->engine(); QScriptContextPrivate *ctx = eng->currentContext(); ctx->throwError(QString::fromLatin1("cannot access member `%0' of deleted QObject") .arg(member.nameId()->s)); return true; } switch (member.flags() & ID_MASK) { case CHILD_ID: return false; case METHOD_ID: { QScript::Member m; QScriptObject *instance = object->objectValue(); if (!instance->findMember(member.nameId(), &m)) { instance->createMember(member.nameId(), &m, /*flags=*/0); } instance->put(m, value); return true; } case PROPERTY_ID: if (GeneratePropertyFunctions) { // we shouldn't get here, QScriptValueImpl::setProperty() messed up Q_ASSERT_X(0, "put", "Q_PROPERTY access cannot be overridden"); return false; } else { const QMetaObject *meta = qobject->metaObject(); QMetaProperty prop = meta->property(member.id()); Q_ASSERT(prop.isScriptable()); QVariant v = variantFromValue(object->engine(), prop.userType(), value); bool ok = prop.write(qobject, v); return ok; } case DYNAPROPERTY_ID: { QVariant v = value.toVariant(); return ! qobject->setProperty(member.nameId()->s.toLatin1(), v); } } // switch return false; } virtual bool removeMember(const QScriptValueImpl &object, const QScript::Member &member) { QObject *qobject = object.toQObject(); if (!qobject || !member.isNativeProperty() || !member.isDeletable()) return false; if ((member.flags() & ID_MASK) == DYNAPROPERTY_ID) { qobject->setProperty(member.nameId()->s.toLatin1(), QVariant()); return true; } return false; } virtual void mark(const QScriptValueImpl &, int) { } virtual QScriptClassDataIterator *newIterator(const QScriptValueImpl &object) { return new ExtQObjectDataIterator(object); } private: QScriptClassInfo *m_classInfo; }; struct QObjectConnection { int slotIndex; QScriptValueImpl receiver; QScriptValueImpl slot; QScriptValueImpl senderWrapper; QObjectConnection(int i, const QScriptValueImpl &r, const QScriptValueImpl &s, const QScriptValueImpl &sw) : slotIndex(i), receiver(r), slot(s), senderWrapper(sw) {} QObjectConnection() : slotIndex(-1) {} bool hasTarget(const QScriptValueImpl &r, const QScriptValueImpl &s) const { if (r.isObject() != receiver.isObject()) return false; if ((r.isObject() && receiver.isObject()) && (r.objectValue() != receiver.objectValue())) { return false; } return (s.objectValue() == slot.objectValue()); } void mark(int generation) { if (senderWrapper.isValid() && !senderWrapper.isMarked(generation)) { // see if the sender should be marked or not ExtQObject::Instance *inst = ExtQObject::Instance::get(senderWrapper); if ((inst->ownership == QScriptEngine::ScriptOwnership) || ((inst->ownership == QScriptEngine::AutoOwnership) && inst->value && !inst->value->parent())) { senderWrapper.invalidate(); } else { senderWrapper.mark(generation); } } if (receiver.isValid()) receiver.mark(generation); if (slot.isValid()) slot.mark(generation); } }; class QObjectConnectionManager: public QObject { public: QObjectConnectionManager(); ~QObjectConnectionManager(); bool addSignalHandler(QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &slot, const QScriptValueImpl &senderWrapper, Qt::ConnectionType type); bool removeSignalHandler( QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &slot); static const QMetaObject staticMetaObject; virtual const QMetaObject *metaObject() const; virtual void *qt_metacast(const char *); virtual int qt_metacall(QMetaObject::Call, int, void **argv); void execute(int slotIndex, void **argv); void mark(int generation); private: int m_slotCounter; QVector > connections; }; } // ::QScript QScript::ExtQObject::ExtQObject(QScriptEnginePrivate *eng): Ecma::Core(eng, QLatin1String("QObject"), QScriptClassInfo::QObjectType) { newQObject(&publicPrototype, new QScript::QObjectPrototype(), QScriptEngine::AutoOwnership, QScriptEngine::ExcludeSuperClassMethods | QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeChildObjects); eng->newConstructor(&ctor, this, publicPrototype); addPrototypeFunction(QLatin1String("toString"), method_toString, 0); addPrototypeFunction(QLatin1String("findChild"), method_findChild, 1); addPrototypeFunction(QLatin1String("findChildren"), method_findChildren, 1); classInfo()->setData(new QScript::ExtQObjectData(classInfo())); } QScript::ExtQObject::~ExtQObject() { } void QScript::ExtQObject::execute(QScriptContextPrivate *context) { QScriptValueImpl tmp; newQObject(&tmp, 0); context->setReturnValue(tmp); } void QScript::ExtQObject::newQObject(QScriptValueImpl *result, QObject *value, QScriptEngine::ValueOwnership ownership, const QScriptEngine::QObjectWrapOptions &options) { Instance *instance; if (!result->isValid()) { engine()->newObject(result, publicPrototype, classInfo()); instance = new Instance(); result->setObjectData(instance); } else { Q_ASSERT(result->isObject()); if (result->classInfo() != classInfo()) { result->destroyObjectData(); result->setClassInfo(classInfo()); instance = new Instance(); result->setObjectData(instance); } else { instance = Instance::get(*result); } } instance->value = value; instance->ownership = ownership; instance->options = options; } QScriptValueImpl QScript::ExtQObject::method_findChild(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) { if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { QObject *obj = instance->value; QString name = context->argument(0).toString(); QObject *child = qFindChild(obj, name); QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; QScriptValueImpl result; eng->newQObject(&result, child, QScriptEngine::QtOwnership, opt); return result; } return eng->undefinedValue(); } QScriptValueImpl QScript::ExtQObject::method_findChildren(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) { if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { QObject *obj = instance->value; QList found; QScriptValueImpl arg = context->argument(0); #ifndef QT_NO_REGEXP if (arg.isRegExp()) { QRegExp re = arg.toRegExp(); found = qFindChildren(obj, re); } else #endif { QString name = arg.isUndefined() ? QString() : arg.toString(); found = qFindChildren(obj, name); } QScriptValueImpl result = eng->newArray(found.size()); QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; for (int i = 0; i < found.size(); ++i) { QScriptValueImpl value; eng->newQObject(&value, found.at(i), QScriptEngine::QtOwnership, opt); result.setProperty(i, value); } return result; } return eng->undefinedValue(); } QScriptValueImpl QScript::ExtQObject::method_toString(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) { if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { QObject *obj = instance->value; const QMetaObject *meta = obj ? obj->metaObject() : &QObject::staticMetaObject; QString name = obj ? obj->objectName() : QString::fromUtf8("unnamed"); QString str = QString::fromUtf8("%0(name = \"%1\")") .arg(QLatin1String(meta->className())).arg(name); return QScriptValueImpl(eng, str); } return eng->undefinedValue(); } static const uint qt_meta_data_QObjectConnectionManager[] = { // content: 1, // revision 0, // classname 0, 0, // classinfo 1, 10, // methods 0, 0, // properties 0, 0, // enums/sets // slots: signature, parameters, type, tag, flags 35, 34, 34, 34, 0x0a, 0 // eod }; static const char qt_meta_stringdata_QObjectConnectionManager[] = { "QScript::QObjectConnectionManager\0\0execute()\0" }; const QMetaObject QScript::QObjectConnectionManager::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_QObjectConnectionManager, qt_meta_data_QObjectConnectionManager, 0 } }; const QMetaObject *QScript::QObjectConnectionManager::metaObject() const { return &staticMetaObject; } void *QScript::QObjectConnectionManager::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_QObjectConnectionManager)) return static_cast(const_cast(this)); return QObject::qt_metacast(_clname); } int QScript::QObjectConnectionManager::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { execute(_id, _a); _id -= m_slotCounter; } return _id; } void QScript::QObjectConnectionManager::execute(int slotIndex, void **argv) { QScriptValueImpl receiver; QScriptValueImpl slot; QScriptValueImpl senderWrapper; int signalIndex = -1; for (int i = 0; i < connections.size(); ++i) { const QVector &cs = connections.at(i); for (int j = 0; j < cs.size(); ++j) { const QObjectConnection &c = cs.at(j); if (c.slotIndex == slotIndex) { receiver = c.receiver; slot = c.slot; senderWrapper = c.senderWrapper; signalIndex = i; break; } } } Q_ASSERT(slot.isValid()); QScriptEnginePrivate *eng = slot.engine(); if (eng->isCollecting()) { // we can't do a script function call during GC, // so we're forced to ignore this signal return; } QScriptFunction *fun = eng->convertToNativeFunction(slot); if (fun == 0) { // the signal handler has been GC'ed. This can only happen when // a QObject is owned by the engine, the engine is destroyed, and // there is a script function connected to the destroyed() signal Q_ASSERT(signalIndex <= 1); // destroyed(QObject*) return; } const QMetaObject *meta = sender()->metaObject(); const QMetaMethod method = meta->method(signalIndex); QList parameterTypes = method.parameterTypes(); int argc = parameterTypes.count(); QScriptValueImpl activation; eng->newActivation(&activation); QScriptObject *activation_data = activation.objectValue(); activation_data->m_scope = slot.scope(); int formalCount = fun->formals.count(); int mx = qMax(formalCount, argc); activation_data->m_members.resize(mx + 1); activation_data->m_values.resize(mx + 1); for (int i = 0; i < mx; ++i) { QScriptNameIdImpl *nameId; if (i < formalCount) nameId = fun->formals.at(i); else nameId = 0; activation_data->m_members[i].object(nameId, i, QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); QScriptValueImpl actual; if (i < argc) { void *arg = argv[i + 1]; QByteArray typeName = parameterTypes.at(i); int argType = QMetaType::type(typeName); if (!argType) { if (typeName == "QVariant") { actual = eng->valueFromVariant(*reinterpret_cast(arg)); } else { qWarning("QScriptEngine: Unable to handle unregistered datatype '%s' " "when invoking handler of signal %s::%s", typeName.constData(), meta->className(), method.signature()); actual = eng->undefinedValue(); } } else { actual = eng->create(argType, arg); } } else { actual = eng->undefinedValue(); } activation_data->m_values[i] = actual; } QScriptValueImpl senderObject; if (senderWrapper.isQObject()) { senderObject = senderWrapper; } else { QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; eng->newQObject(&senderObject, sender(), QScriptEngine::QtOwnership, opt); } activation_data->m_members[mx].object(eng->idTable()->id___qt_sender__, mx, QScriptValue::SkipInEnumeration); activation_data->m_values[mx] = senderObject; QScriptValueImpl thisObject; if (receiver.isObject()) thisObject = receiver; else thisObject = eng->globalObject(); QScriptContextPrivate *context_data = eng->pushContext(); context_data->m_activation = activation; context_data->m_callee = slot; context_data->m_thisObject = thisObject; context_data->argc = argc; context_data->args = const_cast (activation_data->m_values.constData()); fun->execute(context_data); eng->popContext(); if (eng->hasUncaughtException()) eng->emitSignalHandlerException(); } QScript::QObjectConnectionManager::QObjectConnectionManager() : m_slotCounter(0) { } QScript::QObjectConnectionManager::~QObjectConnectionManager() { } void QScript::QObjectConnectionManager::mark(int generation) { for (int i = 0; i < connections.size(); ++i) { QVector &cs = connections[i]; for (int j = 0; j < cs.size(); ++j) cs[j].mark(generation); } } bool QScript::QObjectConnectionManager::addSignalHandler( QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &function, const QScriptValueImpl &senderWrapper, Qt::ConnectionType type) { if (connections.size() <= signalIndex) connections.resize(signalIndex+1); QVector &cs = connections[signalIndex]; int absSlotIndex = m_slotCounter + metaObject()->methodOffset(); bool ok = QMetaObject::connect(sender, signalIndex, this, absSlotIndex, type); if (ok) { cs.append(QScript::QObjectConnection(m_slotCounter++, receiver, function, senderWrapper)); QMetaMethod signal = sender->metaObject()->method(signalIndex); QByteArray signalString; signalString.append('2'); // signal code signalString.append(signal.signature()); static_cast(sender)->callConnectNotify(signalString); } return ok; } bool QScript::QObjectConnectionManager::removeSignalHandler( QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &slot) { if (connections.size() <= signalIndex) return false; QVector &cs = connections[signalIndex]; for (int i = 0; i < cs.size(); ++i) { const QObjectConnection &c = cs.at(i); if (c.hasTarget(receiver, slot)) { int absSlotIndex = c.slotIndex + metaObject()->methodOffset(); bool ok = QMetaObject::disconnect(sender, signalIndex, this, absSlotIndex); if (ok) { cs.remove(i); QMetaMethod signal = sender->metaObject()->method(signalIndex); QByteArray signalString; signalString.append('2'); // signal code signalString.append(signal.signature()); static_cast(sender)->callDisconnectNotify(signalString); } return ok; } } return false; } QString QScript::QtPropertyFunction::functionName() const { QMetaProperty prop = m_meta->property(m_index); return QLatin1String(prop.name()); } void QScript::QtPropertyFunction::execute(QScriptContextPrivate *context) { context->calleeMetaIndex = m_index; QScriptEnginePrivate *eng_p = context->engine(); #ifndef Q_SCRIPT_NO_EVENT_NOTIFY eng_p->notifyFunctionEntry(context); #endif QScriptValueImpl result = eng_p->undefinedValue(); QScriptValueImpl object = context->thisObject(); QObject *qobject = object.toQObject(); while ((!qobject || (qobject->metaObject() != m_meta)) && object.prototype().isObject()) { object = object.prototype(); qobject = object.toQObject(); } Q_ASSERT(qobject); QMetaProperty prop = m_meta->property(m_index); Q_ASSERT(prop.isScriptable()); if (context->argumentCount() == 0) { // get if (prop.isValid()) { QScriptable *scriptable = scriptableFromQObject(qobject); QScriptEngine *oldEngine = 0; if (scriptable) { oldEngine = QScriptablePrivate::get(scriptable)->engine; QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(eng_p); } QVariant v = prop.read(qobject); if (scriptable) QScriptablePrivate::get(scriptable)->engine = oldEngine; result = eng_p->valueFromVariant(v); } } else { // set QScriptValueImpl arg = context->argument(0); QVariant v; if (prop.isEnumType() && arg.isString() && !eng_p->demarshalFunction(prop.userType())) { // give QMetaProperty::write() a chance to convert from // string to enum value v = arg.toString(); } else { v = variantFromValue(eng_p, prop.userType(), arg); } QScriptable *scriptable = scriptableFromQObject(qobject); QScriptEngine *oldEngine = 0; if (scriptable) { oldEngine = QScriptablePrivate::get(scriptable)->engine; QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(eng_p); } prop.write(qobject, v); if (scriptable) QScriptablePrivate::get(scriptable)->engine = oldEngine; result = context->argument(0); } if (!eng_p->hasUncaughtException()) context->m_result = result; #ifndef Q_SCRIPT_NO_EVENT_NOTIFY eng_p->notifyFunctionExit(context); #endif } QString QScript::QtFunction::functionName() const { const QMetaObject *meta = metaObject(); if (!meta) return QString(); QMetaMethod method = meta->method(m_initialIndex); return QLatin1String(methodName(method)); } void QScript::QtFunction::mark(QScriptEnginePrivate *engine, int generation) { if (m_object.isValid()) engine->markObject(m_object, generation); QScriptFunction::mark(engine, generation); } void QScript::QtFunction::execute(QScriptContextPrivate *context) { QScriptEnginePrivate *eng_p = context->engine(); QObject *qobj = qobject(); if (!qobj) { context->calleeMetaIndex = m_initialIndex; #ifndef Q_SCRIPT_NO_EVENT_NOTIFY eng_p->notifyFunctionEntry(context); #endif context->throwError(QLatin1String("cannot call function of deleted QObject")); #ifndef Q_SCRIPT_NO_EVENT_NOTIFY eng_p->notifyFunctionExit(context); #endif return; } const QMetaObject *meta = qobj->metaObject(); QObject *thisQObject = context->thisObject().toQObject(); if (!thisQObject) // ### TypeError thisQObject = qobj; if (!meta->cast(thisQObject)) { #if 0 // ### find common superclass, see if initialIndex is // in that class (or a superclass of that class), // then it's still safe to execute it funName = methodName(meta->method(m_initialIndex)); context->throwError( QString::fromUtf8("cannot execute %0: %1 does not inherit %2") .arg(QLatin1String(funName)) .arg(QLatin1String(thisQObject->metaObject()->className())) .arg(QLatin1String(meta->className()))); return; #endif // invoking a function in the prototype thisQObject = qobj; } callQtMethod(context, QMetaMethod::Method, thisQObject, meta, m_initialIndex, m_maybeOverloaded); } int QScript::QtFunction::mostGeneralMethod(QMetaMethod *out) const { const QMetaObject *meta = metaObject(); if (!meta) return -1; int index = m_initialIndex; QMetaMethod method = meta->method(index); if (maybeOverloaded() && (method.attributes() & QMetaMethod::Cloned)) { // find the most general method do { method = meta->method(--index); } while (method.attributes() & QMetaMethod::Cloned); } if (out) *out = method; return index; } QList QScript::QtFunction::overloadedIndexes() const { if (!maybeOverloaded()) return QList(); QList result; QString name = functionName(); const QMetaObject *meta = metaObject(); for (int index = mostGeneralMethod() - 1; index >= 0; --index) { QString otherName = QString::fromLatin1(methodName(meta->method(index))); if (otherName == name) result.append(index); } return result; } ///////////////////////////////////////////////////////// namespace QScript { ExtQMetaObject::Instance *ExtQMetaObject::Instance::get(const QScriptValueImpl &object, QScriptClassInfo *klass) { if (! klass || klass == object.classInfo()) return static_cast (object.objectData()); return 0; } void ExtQMetaObject::Instance::execute(QScriptContextPrivate *context) { if (ctor.isFunction()) { QScriptValueImplList args; for (int i = 0; i < context->argumentCount(); ++i) args << context->argument(i); QScriptEnginePrivate *eng = context->engine(); context->m_result = eng->call(ctor, context->thisObject(), args, context->isCalledAsConstructor()); } else { if (value->constructorCount() > 0) { callQtMethod(context, QMetaMethod::Constructor, /*thisQObject=*/0, value, value->constructorCount()-1, /*maybeOverloaded=*/true); if (context->state() == QScriptContext::NormalState) { ExtQObject::Instance *inst = ExtQObject::Instance::get(context->m_result); Q_ASSERT(inst != 0); inst->ownership = QScriptEngine::AutoOwnership; context->m_result.setPrototype(prototype); } } else { context->m_result = context->throwError( QScriptContext::TypeError, QString::fromUtf8("no constructor for %0") .arg(QLatin1String(value->className()))); } } } struct StaticQtMetaObject : public QObject { static const QMetaObject *get() { return &static_cast (0)->staticQtMetaObject; } }; class ExtQMetaObjectData: public QScriptClassData { public: ExtQMetaObjectData(QScriptEnginePrivate *, QScriptClassInfo *classInfo); virtual bool resolve(const QScriptValueImpl &object, QScriptNameIdImpl *nameId, QScript::Member *member, QScriptValueImpl *base, QScript::AccessMode access); virtual bool get(const QScriptValueImpl &object, const QScript::Member &member, QScriptValueImpl *result); virtual bool put(QScriptValueImpl *object, const QScript::Member &member, const QScriptValueImpl &value); virtual void mark(const QScriptValueImpl &object, int generation); private: QScriptClassInfo *m_classInfo; }; ExtQMetaObjectData::ExtQMetaObjectData(QScriptEnginePrivate *, QScriptClassInfo *classInfo) : m_classInfo(classInfo) { } bool ExtQMetaObjectData::resolve(const QScriptValueImpl &object, QScriptNameIdImpl *nameId, QScript::Member *member, QScriptValueImpl *base, QScript::AccessMode /*access*/) { const QMetaObject *meta = object.toQMetaObject(); if (!meta) return false; QScriptEnginePrivate *eng_p = object.engine(); if (eng_p->idTable()->id_prototype == nameId) { // prototype property is a proxy to constructor's prototype property member->native(nameId, /*id=*/0, QScriptValue::Undeletable); return true; } QByteArray name = eng_p->toString(nameId).toLatin1(); for (int i = 0; i < meta->enumeratorCount(); ++i) { QMetaEnum e = meta->enumerator(i); for (int j = 0; j < e.keyCount(); ++j) { const char *key = e.key(j); if (! qstrcmp (key, name.constData())) { member->native(nameId, e.value(j), QScriptValue::ReadOnly | QScriptValue::Undeletable); *base = object; return true; } } } return false; } bool ExtQMetaObjectData::get(const QScriptValueImpl &object, const QScript::Member &member, QScriptValueImpl *result) { if (! member.isNativeProperty()) return false; QScriptEnginePrivate *eng_p = object.engine(); if (eng_p->idTable()->id_prototype == member.nameId()) { ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(object, m_classInfo); if (inst->ctor.isFunction()) *result = inst->ctor.property(eng_p->idTable()->id_prototype); else *result = inst->prototype; } else { *result = QScriptValueImpl(member.id()); } return true; } bool ExtQMetaObjectData::put(QScriptValueImpl *object, const Member &member, const QScriptValueImpl &value) { if (! member.isNativeProperty()) return false; QScriptEnginePrivate *eng_p = object->engine(); if (eng_p->idTable()->id_prototype == member.nameId()) { ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(*object, m_classInfo); if (inst->ctor.isFunction()) inst->ctor.setProperty(eng_p->idTable()->id_prototype, value); else inst->prototype = value; } return true; } void ExtQMetaObjectData::mark(const QScriptValueImpl &object, int generation) { ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(object, m_classInfo); if (inst->ctor.isObject() || inst->ctor.isString()) inst->ctor.mark(generation); } } // namespace QScript QScript::ExtQMetaObject::ExtQMetaObject(QScriptEnginePrivate *eng) : Ecma::Core(eng, QLatin1String("QMetaObject"), QScriptClassInfo::QMetaObjectType) { newQMetaObject(&publicPrototype, QScript::StaticQtMetaObject::get()); eng->newConstructor(&ctor, this, publicPrototype); addPrototypeFunction(QLatin1String("className"), method_className, 0); classInfo()->setData(new QScript::ExtQMetaObjectData(eng, classInfo())); } QScript::ExtQMetaObject::~ExtQMetaObject() { } void QScript::ExtQMetaObject::execute(QScriptContextPrivate *context) { QScriptValueImpl tmp; newQMetaObject(&tmp, 0); context->setReturnValue(tmp); } void QScript::ExtQMetaObject::newQMetaObject(QScriptValueImpl *result, const QMetaObject *value, const QScriptValueImpl &ctor) { Instance *instance = new Instance(); instance->value = value; if (ctor.isFunction()) { instance->ctor = ctor; } else { instance->prototype = engine()->newObject(); instance->prototype.setPrototype(engine()->qobjectConstructor->publicPrototype); } engine()->newObject(result, publicPrototype, classInfo()); result->setObjectData(instance); } QScriptValueImpl QScript::ExtQMetaObject::method_className(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) { if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { return QScriptValueImpl(eng, QString::fromLatin1(instance->value->className())); } return eng->undefinedValue(); } QScriptQObjectData::QScriptQObjectData() : m_connectionManager(0) { } QScriptQObjectData::~QScriptQObjectData() { if (m_connectionManager) { delete m_connectionManager; m_connectionManager = 0; } } bool QScriptQObjectData::addSignalHandler(QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &slot, const QScriptValueImpl &senderWrapper, Qt::ConnectionType type) { if (!m_connectionManager) m_connectionManager = new QScript::QObjectConnectionManager(); return m_connectionManager->addSignalHandler( sender, signalIndex, receiver, slot, senderWrapper, type); } bool QScriptQObjectData::removeSignalHandler(QObject *sender, int signalIndex, const QScriptValueImpl &receiver, const QScriptValueImpl &slot) { if (!m_connectionManager) return false; return m_connectionManager->removeSignalHandler( sender, signalIndex, receiver, slot); } bool QScriptQObjectData::findWrapper(QScriptEngine::ValueOwnership ownership, const QScriptEngine::QObjectWrapOptions &options, QScriptValueImpl *out) { for (int i = 0; i < wrappers.size(); ++i) { const QScriptQObjectWrapperInfo &info = wrappers.at(i); if ((info.ownership == ownership) && (info.options == options)) { *out = info.object; return true; } } return false; } void QScriptQObjectData::registerWrapper(const QScriptValueImpl &wrapper, QScriptEngine::ValueOwnership ownership, const QScriptEngine::QObjectWrapOptions &options) { wrappers.append(QScriptQObjectWrapperInfo(wrapper, ownership, options)); } void QScriptQObjectData::mark(int generation) { if (m_connectionManager) m_connectionManager->mark(generation); { QList::iterator it; for (it = wrappers.begin(); it != wrappers.end(); ) { const QScriptQObjectWrapperInfo &info = *it; if (info.object.isMarked(generation)) { ++it; } else { it = wrappers.erase(it); } } } } QT_END_NAMESPACE #include "qscriptextqobject.moc"