Commit f7a077b1 authored by Christian Kamm's avatar Christian Kamm

QmlJS: Avoid infinite loop with recursive prototypes.

parent 443be8ee
......@@ -1675,7 +1675,9 @@ void Context::setProperty(const ObjectValue *object, const QString &name, const
QString Context::defaultPropertyName(const ObjectValue *object) const
{
for (const ObjectValue *o = object; o; o = o->prototype(this)) {
PrototypeIterator iter(object, this);
while (iter.hasNext()) {
const ObjectValue *o = iter.next();
if (const ASTObjectValue *astObjValue = dynamic_cast<const ASTObjectValue *>(o)) {
QString defaultProperty = astObjValue->defaultPropertyName();
if (!defaultProperty.isEmpty())
......@@ -1881,9 +1883,11 @@ const Value *ObjectValue::lookupMember(const QString &name, const Context *conte
}
if (examinePrototypes) {
const ObjectValue *prototypeObject = prototype(context);
if (prototypeObject) {
if (const Value *m = prototypeObject->lookupMember(name, context))
PrototypeIterator iter(this, context);
iter.next(); // skip this
while (iter.hasNext()) {
const ObjectValue *prototypeObject = iter.next();
if (const Value *m = prototypeObject->lookupMember(name, context, false))
return m;
}
}
......@@ -1891,6 +1895,55 @@ const Value *ObjectValue::lookupMember(const QString &name, const Context *conte
return 0;
}
PrototypeIterator::PrototypeIterator(const ObjectValue *start, const Context *context)
: m_current(0)
, m_next(start)
, m_context(context)
{
if (start)
m_prototypes.reserve(10);
}
bool PrototypeIterator::hasNext()
{
if (m_next)
return true;
if (!m_current)
return false;
m_next = m_current->prototype(m_context);
if (!m_next || m_prototypes.contains(m_next)) {
m_next = 0;
return false;
}
return true;
}
const ObjectValue *PrototypeIterator::next()
{
if (hasNext()) {
m_current = m_next;
m_prototypes += m_next;
m_next = 0;
return m_current;
}
return 0;
}
const ObjectValue *PrototypeIterator::peekNext()
{
if (hasNext()) {
return m_next;
}
return 0;
}
QList<const ObjectValue *> PrototypeIterator::all()
{
while (hasNext())
next();
return m_prototypes;
}
Activation::Activation(Context *parentContext)
: _thisObject(0),
_calledAsFunction(true),
......
......@@ -397,6 +397,7 @@ public:
QString className() const;
void setClassName(const QString &className);
// not guaranteed to not recurse, use PrototypeIterator!
const ObjectValue *prototype(const Context *context) const;
void setPrototype(const Value *prototype);
......@@ -422,6 +423,24 @@ private:
QString _className;
};
class QMLJS_EXPORT PrototypeIterator
{
public:
PrototypeIterator(const ObjectValue *start, const Context *context);
bool hasNext();
const ObjectValue *peekNext();
const ObjectValue *next();
QList<const ObjectValue *> all();
private:
const ObjectValue *m_current;
const ObjectValue *m_next;
QList<const ObjectValue *> m_prototypes;
const Context *m_context;
};
class QMLJS_EXPORT QmlObjectValue: public ObjectValue
{
public:
......
......@@ -202,8 +202,10 @@ void ScopeBuilder::setQmlScopeObject(Node *node)
// check if the object has a Qt.ListElement or Qt.Connections ancestor
// ### allow only signal bindings for Connections
const ObjectValue *prototype = scopeObject->prototype(_context);
while (prototype) {
PrototypeIterator iter(scopeObject, _context);
iter.next();
while (iter.hasNext()) {
const ObjectValue *prototype = iter.next();
if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) {
if ((qmlMetaObject->className() == QLatin1String("ListElement")
|| qmlMetaObject->className() == QLatin1String("Connections")
......@@ -213,11 +215,10 @@ void ScopeBuilder::setQmlScopeObject(Node *node)
break;
}
}
prototype = prototype->prototype(_context);
}
// check if the object has a Qt.PropertyChanges ancestor
prototype = scopeObject->prototype(_context);
const ObjectValue *prototype = scopeObject->prototype(_context);
prototype = isPropertyChangesObject(_context, prototype);
// find the target script binding
if (prototype) {
......@@ -281,15 +282,15 @@ const Value *ScopeBuilder::scopeObjectLookup(AST::UiQualifiedId *id)
const ObjectValue *ScopeBuilder::isPropertyChangesObject(const Context *context,
const ObjectValue *object)
{
const ObjectValue *prototype = object;
while (prototype) {
PrototypeIterator iter(object, context);
while (iter.hasNext()) {
const ObjectValue *prototype = iter.next();
if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) {
if (qmlMetaObject->className() == QLatin1String("PropertyChanges")
&& (qmlMetaObject->packageName() == QLatin1String("Qt")
|| qmlMetaObject->packageName() == QLatin1String("QtQuick")))
return prototype;
}
prototype = prototype->prototype(context);
}
return 0;
}
......@@ -372,10 +372,12 @@ public:
if (objectValue && objectValue->prototype(m_context) == m_context->engine()->arrayPrototype())
return true;
for (const Interpreter::ObjectValue *iter = containingObject; iter; iter = iter->prototype(m_context)) {
if (iter->property(name, m_context) == m_context->engine()->arrayPrototype())
Interpreter::PrototypeIterator iter(containingObject, m_context);
while (iter.hasNext()) {
const Interpreter::ObjectValue *proto = iter.next();
if (proto->property(name, m_context) == m_context->engine()->arrayPrototype())
return true;
if (const Interpreter::QmlObjectValue *qmlIter = dynamic_cast<const Interpreter::QmlObjectValue *>(iter)) {
if (const Interpreter::QmlObjectValue *qmlIter = dynamic_cast<const Interpreter::QmlObjectValue *>(proto)) {
if (qmlIter->isListProperty(name))
return true;
}
......@@ -397,9 +399,11 @@ public:
return hasQuotes ? QVariant(cleanedValue) : cleverConvert(cleanedValue);
}
for (const Interpreter::ObjectValue *iter = containingObject; iter; iter = iter->prototype(m_context)) {
if (iter->lookupMember(name, m_context, false)) {
containingObject = iter;
Interpreter::PrototypeIterator iter(containingObject, m_context);
while (iter.hasNext()) {
const Interpreter::ObjectValue *proto = iter.next();
if (proto->lookupMember(name, m_context, false)) {
containingObject = proto;
break;
}
}
......@@ -446,9 +450,11 @@ public:
return QVariant();
}
for (const Interpreter::ObjectValue *iter = containingObject; iter; iter = iter->prototype(m_context)) {
if (iter->lookupMember(name, m_context, false)) {
containingObject = iter;
Interpreter::PrototypeIterator iter(containingObject, m_context);
while (iter.hasNext()) {
const Interpreter::ObjectValue *proto = iter.next();
if (proto->lookupMember(name, m_context, false)) {
containingObject = proto;
break;
}
}
......@@ -473,12 +479,11 @@ public:
rhsValueName = memberExp->name->asString();
}
if (!rhsValueObject)
return QVariant();
for (const Interpreter::ObjectValue *iter = rhsValueObject; iter; iter = iter->prototype(m_context)) {
if (iter->lookupMember(rhsValueName, m_context, false)) {
rhsValueObject = iter;
iter = Interpreter::PrototypeIterator(rhsValueObject, m_context);
while (iter.hasNext()) {
const Interpreter::ObjectValue *proto = iter.next();
if (proto->lookupMember(rhsValueName, m_context, false)) {
rhsValueObject = proto;
break;
}
}
......
......@@ -1055,11 +1055,13 @@ protected:
{
Bind *bind = m_lookupContext->document()->bind();
const Interpreter::ObjectValue *objValue = bind->findQmlObject(ast);
QStringList prototypes;
if (!objValue)
return false;
while (objValue) {
prototypes.append(objValue->className());
objValue = objValue->prototype(m_lookupContext->context());
QStringList prototypes;
foreach (const Interpreter::ObjectValue *value,
Interpreter::PrototypeIterator(objValue, m_lookupContext->context()).all()) {
prototypes.append(value->className());
}
return prototypes.contains(QString("QGraphicsObject"));
......
......@@ -71,12 +71,16 @@ static const ObjectValue *prototypeWithMember(const Context *context, const Obje
const Value *value = object->property(name, context);
if (!value)
return 0;
forever {
const ObjectValue *prototype = object->prototype(context);
if (!prototype || prototype->property(name, context) != value)
return object;
object = prototype;
}
const ObjectValue *prev = object;
PrototypeIterator iter(object, context);
iter.next();
while (iter.hasNext()) {
const ObjectValue *prototype = iter.next();
if (prototype->property(name, context) != value)
return prev;
prev = prototype;
}
return prev;
}
namespace {
......
......@@ -237,17 +237,19 @@ void HoverHandler::prettyPrintTooltip(const QmlJS::Interpreter::Value *value,
if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) {
bool found = false;
do {
const QString className = objectValue->className();
Interpreter::PrototypeIterator iter(objectValue, context);
while (iter.hasNext()) {
const Interpreter::ObjectValue *prototype = iter.next();
const QString className = prototype->className();
if (! className.isEmpty()) {
found = !qmlHelpId(className).isEmpty();
if (toolTip().isEmpty() || found)
setToolTip(className);
}
objectValue = objectValue->prototype(context);
} while (objectValue && !found);
if (found)
break;
}
} else if (const Interpreter::QmlEnumValue *enumValue =
dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
setToolTip(enumValue->name());
......
......@@ -115,19 +115,21 @@ void QuickToolBar::apply(TextEditor::BaseTextEditorEditable *editor, Document::P
const Interpreter::ObjectValue *scopeObject = document->bind()->findQmlObject(node);
if (!lookupContext.isNull()) {
if (!lookupContext.isNull() && scopeObject) {
m_prototypes.clear();
while (scopeObject) {
m_prototypes.append(scopeObject->className());
scopeObject = scopeObject->prototype(lookupContext->context());
foreach (const Interpreter::ObjectValue *object,
Interpreter::PrototypeIterator(scopeObject, lookupContext->context()).all()) {
m_prototypes.append(object->className());
}
if (m_prototypes.contains("PropertyChanges")) {
const Interpreter::ObjectValue *targetObject = getPropertyChangesTarget(node, lookupContext);
m_prototypes.clear();
while (targetObject) {
m_prototypes.append(targetObject->className());
targetObject = targetObject->prototype(lookupContext->context());
if (targetObject) {
foreach (const Interpreter::ObjectValue *object,
Interpreter::PrototypeIterator(targetObject, lookupContext->context()).all()) {
m_prototypes.append(object->className());
}
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment