Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-89844

QProperty's eager evaluation leads to inconsistent states

    XMLWordPrintable

Details

    • Bug
    • Resolution: Done
    • P2: Important
    • 6.2.0 Alpha
    • 6.0
    • Core: Object Model
    • None
    • bb44c18b673e2955592ee86774eb7cc25d390c17 (qt/qtbase/dev)

    Description

      QProperties, when having a binding, have a dirty flag. When their dependencies change, they become dirty. On reevaluation, they become non-dirty again. That's the theory. Practice is, there are three possible states. In addition to "property is dirty and dirty flag is set" ("dirty" hereafter) and "property is non-dirty and dirty flag is not set" ("nondirty" hereafter), there is a third possible state which is "property is dirty and dirty flag is not yet set" ("transition" hereafter).

      This transition state only occurs while the dirty states are propagated through the dependency graph. There would not be any problem if nothing else happened in this step. However, the eager evaluation feature leads to a problem. When any QProperty in the dependency graph does eager evaluation, it re-evaluates as soon as it is marked dirty. At the time it is marked dirty, other QProperties in the dependency graph might be in the transition state. These are then not re-evaluated because they don't know yet they are dirty, even though their content is dirty.

      The problem can easily be reproduced, see the following code:

      int main(){
          QProperty<int> a(0);
          QProperty<int> b;
          b.setBinding([&](){ return a.value(); });
          QProperty<int> c;
          c.setBinding([&](){ return a.value(); });
          QProperty<int> d;
          d.setBinding([&](){ return b.value() + c.value(); });
          auto connection = d.subscribe([&](){ qDebug() << d.value(); });
          a = 1;
          a = 2;
      }
      

      Expected output:

      0
      2
      4
      

      Actual output:

      0
      1
      2
      3
      4
      

      If properties hold pointers to objects, this might lead to use-after-free errors, as illustrated in the following example

      int main(){
          int* myint = new int(0);
          QProperty<int*> a(myint);
          QProperty<int*> b;
          b.setBinding([&](){ return a.value(); });
          QProperty<int*> c;
          c.setBinding([&](){ return a.value(); });
          QProperty<int> d;
          d.setBinding([&](){ return *b.value() + *c.value(); });
          auto connection = d.subscribe([&](){ qDebug() << d.value(); });
      
          int* myint2 = new int(1);
          delete myint;
      
          a = myint2; // in this assignment, the old myint pointer will be read
      }
      

      Expected output:

      0
      2
      

      Actual output:

      0
      1908350417
      2
      

      A possible solution to this problem would be to not do any evaluation while propagating dirty states. In order to allow eager evaluation, we would need to introduce an additional step for potential eager evaluation. So, in pseudocode, where we have

      foreach(dependent property p)
          p.setDirty()
      

      we would need

      foreach(dependent property p)
          p.setDirty()
      foreach(dependent property p)
          p.potentialEagerEvaluation()
      

      That way, we could avoid having QProperties in transision state when performing eager evaluation.

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            fabiankosmale Fabian Kosmale
            andreasbuhr Andreas Buhr
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes