qtqml

How to analyse a "Binding loop"


I have a Qt/QML application with a C++ model and a QML visualisation.

At run-time (start-up), I get a warning

QML Item: Binding loop detected for property "xyz"

I see no obvious loop in my QML. Can I enable more debugging to understand where this loop comes from? Other suggestions?


Solution

  • I usually do this by placing a breakpoint in the Qt code that prints the warning. For that, you need to have a Qt with debug symbols.

    Searching for "Binding loop detected" in the Qt sources gives me QQmlAbstractBinding::printBindingLoopError(). Placing a breakpoint there will usually lead to a backtrace that gives a clear picture of the situation.

    Update: David Edmundson has developed a little tool that displays a QML-only backtrace on binding loops, see his blog here. Under the hood is does exactly what is described here, only that it is nicely automated and wrapped in a Python script.

    Example:

    Rectangle {
        id: parent
        width: child.width + 1
        height: child.height + 1
        Rectangle {
            id: child
            anchors.fill: parent
        }
    }
    

    Backtrace:

    1   QQmlAbstractBinding::printBindingLoopError  qqmlabstractbinding.cpp 178 0x7ffff6eb36da  
    2   QQmlBinding::update qqmlbinding.cpp 221 0x7ffff6eb9abe  
    3   QQmlBinding::update qqmlbinding_p.h 97  0x7ffff6eba354  
    4   QQmlBinding::expressionChanged  qqmlbinding.cpp 260 0x7ffff6eb9e68  
    5   QQmlJavaScriptExpressionGuard_callback  qqmljavascriptexpression.cpp    361 0x7ffff6eb223e  
    6   QQmlNotifier::emitNotify    qqmlnotifier.cpp    94  0x7ffff6e9087a  
    7   QQmlData::signalEmitted qqmlengine.cpp  763 0x7ffff6e19a45  
    8   QMetaObject::activate   qobject.cpp 3599    0x7ffff683655e  
    9   QMetaObject::activate   qobject.cpp 3578    0x7ffff6836364  
    10  QQuickItem::widthChanged    moc_qquickitem.cpp  1104    0x7ffff7a7ba49  
    11  QQuickItem::geometryChanged qquickitem.cpp  3533    0x7ffff7a6e9cd  
    12  QQuickItem::setSize qquickitem.cpp  6389    0x7ffff7a75f35  
    13  QQuickAnchorsPrivate::setItemSize   qquickanchors.cpp   400 0x7ffff7a60d94  
    14  QQuickAnchorsPrivate::fillChanged   qquickanchors.cpp   177 0x7ffff7a5fe0e  
    15  QQuickAnchorsPrivate::itemGeometryChanged   qquickanchors.cpp   441 0x7ffff7a6106f  
    16  QQuickItem::geometryChanged qquickitem.cpp  3523    0x7ffff7a6e96c  
    17  QQuickItem::setWidth    qquickitem.cpp  6091    0x7ffff7a74c1d  
    18  QQuickItem::qt_static_metacall  moc_qquickitem.cpp  874 0x7ffff7a7b0dc  
    19  QQuickItem::qt_metacall moc_qquickitem.cpp  946 0x7ffff7a7b4d8  
    20  QQuickRectangle::qt_metacall    moc_qquickrectangle_p.cpp   610 0x7ffff7c189c2  
    21  QMetaObject::metacall   qmetaobject.cpp 296 0x7ffff680118b  
    22  QQmlPropertyPrivate::writeBinding   qqmlproperty.cpp    1512    0x7ffff6e33ec3  
    23  QQmlBinding::update qqmlbinding.cpp 199 0x7ffff6eb992a  
    24  QQmlBinding::update qqmlbinding_p.h 97  0x7ffff6eba354  
    25  QQmlBinding::expressionChanged  qqmlbinding.cpp 260 0x7ffff6eb9e68  
    26  QQmlJavaScriptExpressionGuard_callback  qqmljavascriptexpression.cpp    361 0x7ffff6eb223e  
    27  QQmlNotifier::emitNotify    qqmlnotifier.cpp    94  0x7ffff6e9087a  
    28  QQmlData::signalEmitted qqmlengine.cpp  763 0x7ffff6e19a45  
    29  QMetaObject::activate   qobject.cpp 3599    0x7ffff683655e  
    30  QMetaObject::activate   qobject.cpp 3578    0x7ffff6836364  
    31  QQuickItem::widthChanged    moc_qquickitem.cpp  1104    0x7ffff7a7ba49  
    32  QQuickItem::geometryChanged qquickitem.cpp  3533    0x7ffff7a6e9cd  
    33  QQuickItem::setSize qquickitem.cpp  6389    0x7ffff7a75f35  
    34  QQuickAnchorsPrivate::setItemSize   qquickanchors.cpp   400 0x7ffff7a60d94  
    35  QQuickAnchorsPrivate::fillChanged   qquickanchors.cpp   177 0x7ffff7a5fe0e  
    36  QQuickAnchorsPrivate::update    qquickanchors.cpp   431 0x7ffff7a60fc6  
    37  QQuickAnchorsPrivate::updateOnComplete  qquickanchors.cpp   425 0x7ffff7a60f93  
    38  QQuickItem::componentComplete   qquickitem.cpp  4593    0x7ffff7a70944  
    39  QQmlObjectCreator::finalize qqmlobjectcreator.cpp   1207    0x7ffff6ecab66  
    40  QQmlComponentPrivate::complete  qqmlcomponent.cpp   928 0x7ffff6e38609  
    41  QQmlComponentPrivate::completeCreate    qqmlcomponent.cpp   964 0x7ffff6e386ee  
    42  QQmlComponent::completeCreate   qqmlcomponent.cpp   957 0x7ffff6e386a0  
    43  QQmlComponent::create   qqmlcomponent.cpp   791 0x7ffff6e37edd  
    44  QQuickView::continueExecute qquickview.cpp  476 0x7ffff7b720d4  
    45  QQuickViewPrivate::execute  qquickview.cpp  124 0x7ffff7b7101f  
    46  QQuickView::setSource   qquickview.cpp  253 0x7ffff7b71426  
    47  main    main.cpp    24  0x4033e4    
    

    In the backtrace, one can see that the anchors.fill anchor for the child item is calculated when loading the file (frame 35, 36). That causes the child item's width to change (frame 31), which causes a binding update (frame 25) for a binding on the "width" property (frame 17) on the parent item. That in turn forces a recalculation of the child anchors (frame 14), which changes the child's width (frame 10), which updates a binding (frame 4). That is the same binding that was already being updated in frame 25, hence a binding loop exists. One can see that the this pointer in frame 25 and frame 4 are the same, i.e. the same binding is updated recursively.