itemisCREATE / statecharts

YAKINDU Statechart Tools (http://www.statecharts.org)
Eclipse Public License 1.0
174 stars 84 forks source link

ModelSequencer does not generate all needed exit sequences in all cases #2380

Closed BeckmaR closed 5 years ago

BeckmaR commented 5 years ago

WIP: #2382

The model sequencer contains a Bug which leads to the omission of exit sequences.

Model

Consider the following statechart: image We'd expect that e is always executed when any of the states are left.

It is contained in the exit sequence of main_region.StateA:

/* Exit action for state 'StateA'. */
static void exact_main_region_StateA(Default* handle)
{
    /* Exit action for state 'StateA'. */
    defaultIface_e(handle);
}

Expectation

We'd expect the operation e to be called - always - when the statechart is exited.

Generated code

However, this is not the case. This is the exit sequence of main_region:

/* Default exit sequence for region main region */
static void exseq_main_region(Default* handle)
{
    /* Default exit sequence for region main region */
    /* Handle exit of all possible states (of default.main_region) at position 0... */
    switch(handle->stateConfVector[ 0 ])
    {
        case Default_main_region_StateA_inner1_StateA1 :
        {
            exseq_main_region_StateA_inner1_StateA1(handle);
            break;
        }
        case Default_main_region_StateA_inner1_State2_inner2a_State1 :
        {
            exseq_main_region_StateA_inner1_State2_inner2a_State1(handle);
            break;
        }
        default: break;
    }
    /* Handle exit of all possible states (of default.main_region) at position 1... */
    switch(handle->stateConfVector[ 1 ])
    {
        case Default_main_region_StateA_inner1_State2_inner2b_State1 :
        {
            exseq_main_region_StateA_inner1_State2_inner2b_State1(handle);
            break;
        }
        default: break;
    }
    /* Handle exit of all possible states (of default.main_region) at position 2... */
    switch(handle->stateConfVector[ 2 ])
    {
        case Default_main_region_StateA_inner1_State2_inner3b_State1 :
        {
            exseq_main_region_StateA_inner1_State2_inner3b_State1(handle);
            exact_main_region_StateA(handle);
            break;
        }
        default: break;
    }
}

The function exact_main_region_StateA is only called from switch(handle->stateConfVector[ 2 ]). This seems like a good idea to avoid calling the exit sequence multiple times from each parallel state within, but: the whole state must not be active, because the execution can stop in Default_main_region_StateA_inner1_StateA1, and this leads to the exit sequence not being executed at all.

Possible reason

The reason is most likely this part of the new SequenceBuilder in the method defineExitSwitch:

exitScopes.fold(caseSeq,
                [cs, exitScope|
                    {
                        println(s.name + "/" + exitScope.name)
                        println(s.stateVector.size + "/" + s.stateVector.offset)
                        println(exitScope.stateVector.size + "/" + exitScope.stateVector.offset)
                        if (exitScope instanceof ExecutionState && s.stateVector.last == exitScope.stateVector.last) {
                            val execState = exitScope as ExecutionState
                            if(execState.exitAction !== null) cs.steps.add(execState.exitAction.newCall)
                            if(_addTraceSteps) cs.steps.add(execState.newTraceStateExited)
                        }
                        cs
                    }])

So the exit sequence is only generated when the sum of size and offset of both statevectors are the same. println added for debugging purposes, producing the following output with the given statechart:

default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA.inner1.State2.inner2a
1/0
1/0
default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA.inner1.State2
1/0
3/0
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA.inner1.State2.inner2b
1/1
1/1
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA.inner1.State2
1/1
3/0
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA.inner1.State2.inner3b
1/2
1/2
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA.inner1.State2
1/2
3/0
default.main_region.StateA.inner1.StateA1/default.main_region.StateA.inner1
1/0
3/0
default.main_region.StateA.inner1.StateA1/default.main_region.StateA
1/0
3/0
default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA.inner1.State2.inner2a
1/0
1/0
default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA.inner1.State2
1/0
3/0
default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA.inner1
1/0
3/0
default.main_region.StateA.inner1.State2.inner2a.State1/default.main_region.StateA
1/0
3/0
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA.inner1.State2.inner2b
1/1
1/1
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA.inner1.State2
1/1
3/0
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA.inner1
1/1
3/0
default.main_region.StateA.inner1.State2.inner2b.State1/default.main_region.StateA
1/1
3/0
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA.inner1.State2.inner3b
1/2
1/2
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA.inner1.State2
1/2
3/0
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA.inner1
1/2
3/0
default.main_region.StateA.inner1.State2.inner3b.State1/default.main_region.StateA
1/2
3/0

One can see that the only match where default.main_region.StateA is part is the last one - exactly the place where the exit sequence ends up in the code.

BeckmaR commented 5 years ago

I created tests for this. Can be found in #2382 / on branch issue_2380.