From 576dca0086b1566eafe9a24cf5946f46f4e857bf Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Thu, 7 May 2009 11:40:40 +0200 Subject: Fix infinite loop when source and target of transition are in different trees The SCXML algorithm depends on the guarantee that there is always an LCA regardless of the state list. The case where the targets are in a different tree than the source (e.g. if you have not given the target state a parent) is a bug. The fix is to set an error when this happens in exitStates() and exit states as if the pending error states were the target states. In enterStates we will detect the error and skip the step of selecting states to enter, and instead just enter the pending error states. This breaks transitions to and from the root state, which is not supported by the SCXML algorithm. --- src/corelib/statemachine/qstatemachine.cpp | 48 ++++++++++++++++++-------- src/corelib/statemachine/qstatemachine.h | 1 + tests/auto/qstatemachine/tst_qstatemachine.cpp | 5 ++- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 41d4c6c..efcf707 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -409,6 +409,15 @@ QList QStateMachinePrivate::exitStates(QEvent *event, const QLi continue; lst.prepend(t->sourceState()); QAbstractState *lca = findLCA(lst); + if (lca == 0) { + setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState()); + lst = pendingErrorStates.toList(); + lst.prepend(t->sourceState()); + + lca = findLCA(lst); + Q_ASSERT(lca != 0); + } + { QSet::const_iterator it; for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { @@ -476,21 +485,23 @@ QList QStateMachinePrivate::enterStates(QEvent *event, const QL QSet statesToEnter; QSet statesForDefaultEntry; - for (int i = 0; i < enabledTransitions.size(); ++i) { - QAbstractTransition *t = enabledTransitions.at(i); - QList lst = t->targetStates(); - if (lst.isEmpty()) - continue; - lst.prepend(t->sourceState()); - QState *lca = findLCA(lst); - for (int j = 1; j < lst.size(); ++j) { - QAbstractState *s = lst.at(j); - addStatesToEnter(s, lca, statesToEnter, statesForDefaultEntry); - if (isParallel(lca)) { - QList lcac = QStatePrivate::get(lca)->childStates(); - foreach (QAbstractState* child,lcac) { - if (!statesToEnter.contains(child)) - addStatesToEnter(child,lca,statesToEnter,statesForDefaultEntry); + if (pendingErrorStates.isEmpty()) { + for (int i = 0; i < enabledTransitions.size(); ++i) { + QAbstractTransition *t = enabledTransitions.at(i); + QList lst = t->targetStates(); + if (lst.isEmpty()) + continue; + lst.prepend(t->sourceState()); + QState *lca = findLCA(lst); + for (int j = 1; j < lst.size(); ++j) { + QAbstractState *s = lst.at(j); + addStatesToEnter(s, lca, statesToEnter, statesForDefaultEntry); + if (isParallel(lca)) { + QList lcac = QStatePrivate::get(lca)->childStates(); + foreach (QAbstractState* child,lcac) { + if (!statesToEnter.contains(child)) + addStatesToEnter(child,lca,statesToEnter,statesForDefaultEntry); + } } } } @@ -976,6 +987,13 @@ void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractSta errorString = QStateMachine::tr("Missing default state in history state '%1'") .arg(currentContext->objectName()); break; + + case QStateMachine::NoCommonAncestorForTransitionError: + Q_ASSERT(currentContext != 0); + + errorString = QStateMachine::tr("No common ancestor for targets and source of transition from state '%1'") + .arg(currentContext->objectName()); + break; default: errorString = QStateMachine::tr("Unknown error"); }; diff --git a/src/corelib/statemachine/qstatemachine.h b/src/corelib/statemachine/qstatemachine.h index 10bd443..d0927a3 100644 --- a/src/corelib/statemachine/qstatemachine.h +++ b/src/corelib/statemachine/qstatemachine.h @@ -87,6 +87,7 @@ public: NoError, NoInitialStateError, NoDefaultStateInHistoryState, + NoCommonAncestorForTransitionError }; QStateMachine(QObject *parent = 0); diff --git a/tests/auto/qstatemachine/tst_qstatemachine.cpp b/tests/auto/qstatemachine/tst_qstatemachine.cpp index 8026d6e..e7ea403 100644 --- a/tests/auto/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/qstatemachine/tst_qstatemachine.cpp @@ -919,8 +919,6 @@ void tst_QStateMachine::brokenStateIsNeverEntered() void tst_QStateMachine::transitionToStateNotInGraph() { - QSKIP("Hangs", SkipAll); - s_countWarnings = false; QStateMachine machine; @@ -930,13 +928,14 @@ void tst_QStateMachine::transitionToStateNotInGraph() machine.setInitialState(initialState); QState *independentState = new QState(); + independentState->setObjectName("independentState"); initialState->addTransition(independentState); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 1); - QVERIFY(machine.configuration().contains(initialState)); + QVERIFY(machine.configuration().contains(qobject_cast(machine.rootState())->errorState())); } void tst_QStateMachine::customErrorStateNotInGraph() -- cgit v0.12