statemachine: Fix state entry bug for parallel state groups

The SCXML spec had a bug that would cause the initial state of a
compound state within a parallel state group to be entered even if
the transition specified another (non-initial) state of the compound
state as its target. This only happened if the transition had
multiple target states.

The bug has been fixed in recent revisions of the SCXML spec. This
commit implements the fix, which is to walk the ancestors of the
transition's target states only after all the target states
themselves have been added, so that the default initial states are
correctly overridden/ignored.

Task-number: QTBUG-25958
Change-Id: Iac532047678c483a4a3996e24dacf30e00f6bbe0
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@nokia.com>
bb10
Kent Hansen 2012-07-12 03:12:06 +02:00 committed by Qt by Nokia
parent b76014b8ef
commit c4cef6fae9
3 changed files with 76 additions and 23 deletions

View File

@ -518,6 +518,10 @@ QList<QAbstractState*> QStateMachinePrivate::computeStatesToEnter(const QList<QA
QAbstractState *s = lst.at(j);
addStatesToEnter(s, lca, statesToEnter, statesForDefaultEntry);
}
for (int j = src ? 1 : 0; j < lst.size(); ++j) {
QAbstractState *s = lst.at(j);
addAncestorStatesToEnter(s, lca, statesToEnter, statesForDefaultEntry);
}
if (isParallel(lca)) {
QList<QAbstractState*> lcac = QStatePrivate::get(lca)->childStates();
foreach (QAbstractState* child,lcac) {
@ -715,32 +719,38 @@ void QStateMachinePrivate::addStatesToEnter(QAbstractState *s, QState *root,
return;
}
}
QList<QState*> ancs = properAncestors(s, root);
for (int i = 0; i < ancs.size(); ++i) {
QState *anc = ancs.at(i);
if (!anc->parentState())
continue;
statesToEnter.insert(anc);
if (isParallel(anc)) {
QList<QAbstractState*> lst = QStatePrivate::get(anc)->childStates();
for (int j = 0; j < lst.size(); ++j) {
QAbstractState *child = lst.at(j);
bool hasDescendantInList = false;
QSet<QAbstractState*>::const_iterator it;
for (it = statesToEnter.constBegin(); it != statesToEnter.constEnd(); ++it) {
if (isDescendantOf(*it, child)) {
hasDescendantInList = true;
break;
}
}
if (!hasDescendantInList)
addStatesToEnter(child, anc, statesToEnter, statesForDefaultEntry);
}
}
}
}
}
void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QState *root,
QSet<QAbstractState*> &statesToEnter,
QSet<QAbstractState*> &statesForDefaultEntry)
{
QList<QState*> ancs = properAncestors(s, root);
for (int i = 0; i < ancs.size(); ++i) {
QState *anc = ancs.at(i);
if (!anc->parentState())
continue;
statesToEnter.insert(anc);
if (isParallel(anc)) {
QList<QAbstractState*> lst = QStatePrivate::get(anc)->childStates();
for (int j = 0; j < lst.size(); ++j) {
QAbstractState *child = lst.at(j);
bool hasDescendantInList = false;
QSet<QAbstractState*>::const_iterator it;
for (it = statesToEnter.constBegin(); it != statesToEnter.constEnd(); ++it) {
if (isDescendantOf(*it, child)) {
hasDescendantInList = true;
break;
}
}
if (!hasDescendantInList)
addStatesToEnter(child, anc, statesToEnter, statesForDefaultEntry);
}
}
}
}
bool QStateMachinePrivate::isFinal(const QAbstractState *s)
{
return s && (QAbstractStatePrivate::get(s)->stateType == QAbstractStatePrivate::FinalState);
@ -1077,6 +1087,7 @@ void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractSta
if (currentErrorState != 0) {
QState *lca = findLCA(QList<QAbstractState*>() << currentErrorState << currentContext);
addStatesToEnter(currentErrorState, lca, pendingErrorStates, pendingErrorStatesForDefaultEntry);
addAncestorStatesToEnter(currentErrorState, lca, pendingErrorStates, pendingErrorStatesForDefaultEntry);
} else {
qWarning("Unrecoverable error detected in running state machine: %s",
qPrintable(errorString));

View File

@ -150,6 +150,9 @@ public:
void addStatesToEnter(QAbstractState *s, QState *root,
QSet<QAbstractState*> &statesToEnter,
QSet<QAbstractState*> &statesForDefaultEntry);
void addAncestorStatesToEnter(QAbstractState *s, QState *root,
QSet<QAbstractState*> &statesToEnter,
QSet<QAbstractState*> &statesForDefaultEntry);
static QState *toStandardState(QAbstractState *state);
static const QState *toStandardState(const QAbstractState *state);

View File

@ -203,6 +203,8 @@ private slots:
void changeStateWhileAnimatingProperty();
void propertiesAreAssignedBeforeEntryCallbacks_data();
void propertiesAreAssignedBeforeEntryCallbacks();
void multiTargetTransitionInsideParallelStateGroup();
};
class TestState : public QState
@ -246,6 +248,9 @@ public:
EventTransition(QEvent::Type type, QAbstractState *target, QState *parent = 0)
: QAbstractTransition(parent), m_type(type)
{ setTargetState(target); }
EventTransition(QEvent::Type type, const QList<QAbstractState *> &targets, QState *parent = 0)
: QAbstractTransition(parent), m_type(type)
{ setTargetStates(targets); }
protected:
virtual bool eventTest(QEvent *e) {
return (e->type() == m_type);
@ -4647,5 +4652,39 @@ void tst_QStateMachine::propertiesAreAssignedBeforeEntryCallbacks()
QVERIFY(s2->enteredPassed);
}
// QTBUG-25958
void tst_QStateMachine::multiTargetTransitionInsideParallelStateGroup()
{
QStateMachine machine;
QState *s1 = new QState(&machine);
machine.setInitialState(s1);
QState *s2 = new QState(QState::ParallelStates, &machine);
QState *s21 = new QState(s2);
QState *s211 = new QState(s21);
QState *s212 = new QState(s21);
s21->setInitialState(s212);
QState *s22 = new QState(s2);
QState *s221 = new QState(s22);
QState *s222 = new QState(s22);
s22->setInitialState(s222);
QAbstractTransition *t1 = new EventTransition(QEvent::User, QList<QAbstractState *>() << s211 << s221);
s1->addTransition(t1);
machine.start();
QTRY_VERIFY(machine.configuration().contains(s1));
machine.postEvent(new QEvent(QEvent::User));
QTRY_VERIFY(machine.configuration().contains(s2));
QCOMPARE(machine.configuration().size(), 5);
QVERIFY(machine.configuration().contains(s21));
QVERIFY(machine.configuration().contains(s211));
QVERIFY(machine.configuration().contains(s22));
QVERIFY(machine.configuration().contains(s221));
}
QTEST_MAIN(tst_QStateMachine)
#include "tst_qstatemachine.moc"