Description:
Order of method invocations for some object is not matched with valid state transition rule defined for this class.
To detect this error Sapient use state final automaton which is defined
in fsa.xml file. Below is DTD of XML elements used to define automaton:
<!ELEMENT class (state*)> <!ELEMENT state (method*)> <!ELEMENT method (iftrue?, iffalse?, step?)> <!ELEMENT iftrue EMPTY> <!ELEMENT iffalse EMPTY> <!ELEMENT step EMPTY> <!ATTLIST class name NMTOKEN #REQUIRED> <!ATTLIST state name NMTOKEN #REQUIRED> <!ATTLIST method name NMTOKEN #REQUIRED> <!ATTLIST iftrue to NMTOKEN #REQUIRED> <!ATTLIST iffalse to NMTOKEN #REQUIRED> <!ATTLIST step to NMTOKEN #REQUIRED> <!ATTLIST state final BOOLEAN "false"> <!ATTLIST state init BOOLEAN "false">
Root element defines name of Java class for which automaton is defined and contains list of states. Each state should have name element which identifies the state. First state is considered to be the unknown state. Each variable of the specified type is first assigned this unknown state. Also state can contain final and init boolean attributes. Final attributes is used to mark final states of automaton (i.e. valid states for end of variable live area). If variable is not in final state at the end of its live area, then message is produced. Init attribute marks initial state (it should be only one initial state in automaton). Initial state is state of variable after assignment to it newly constructed object.
State element should contain list of method elements. Method element specifies conditional or unconditional rule for
state transition. Method element should have name attribute which specifies name of Java method (overloading
is considered, so method with any signature and with specified name will match this rule).
It is possible to specify * to match all methods. Method elements are inspected in
the order of their definition, so you can specify transfer rule for some particular methods, and then use *
to specify rule for all other methods.
Method element should contain iftrue and iffalse or step elements (or all those three elements) which to attribute specifies name of the target state. Elements iftrue and iffalse are used for boolean method checked in if statement. Inside then branch of if variable is considered to have state specified by iftrue element, inside else branch - specified by iffalse element. If no step element is defined for method and its result is not used, then message is produced. Step element is used in all other cases.
Below is an example of definition automaton for java.io.RandomAccessFile class:
<?xml version="1.0"?>
<class name="java.io.RandomAccessFile">
<state name="UNKNOWN" final="true">
<method name="close">
<step to="CLOSED"/>
</method>
<method name="*">
<step to="OPENED"/>
</method>
</state>
<state name="OPENED" init="true">
<method name="close">
<step to="CLOSED"/>
</method>
<method name="*">
<step to="OPENED"/>
</method>
</state>
<state name="CLOSED" final="true">
</state>
</class>
How Sapient proceed such automaton?
It first detects all local variables in the method which type matches
the class in automaton definition. Then it associate state with each such variable
(unknown state). After it Sapient performs cyclic iteration for method body.
When variable is used as target of some method, Sapient tries to match this method with one of the rules specified for this state. If no such rule is found, the message is produced. Otherwise the variable is assigned new state defined in the rule.
When variable is assigned the value of some other variable, then Sapient copies state of this variable.
When r-value expression is new operator, then variable is assigned initial
state. Also in case of assignment Sapient checks that variable was in final state before assignment,
otherwise message will be produced.
At the end of variable life, Sapient checks that variable is in the final state. Currently the analysis is performed only locally (within method body), but may be it will extended to the global analysis in future.
Example:
RandomAccessFile f = new RabdomAccessFile("file.dat", "rw");
f.write(someRecord);
if (noMoreData) {
f.close();
}
f.write(terminatorRecord); // write method is invoked after close