/** * Adds a new FSM instance to the CFSM. Once all the FSMs have been added, the CFSM is considered * initialized. * * @param fsm */ public void addFSM(FSM fsm) { assert unSpecifiedPids > 0; assert fsm != null; int pid = fsm.getPid(); if (CSightMain.assertsOn) { // Must be a valid pid (in the right range). assert (pid >= 0 && pid < numProcesses); // Only allow to set the FSM for a pid once. assert (fsms.get(pid) == null); // Check that the FSM alphabet conforms to the expected number of // processes. int ppid; for (DistEventType e : fsm.getAlphabet()) { if (e.isCommEvent()) { ppid = e.getChannelId().getDstPid(); assert ppid >= 0 && ppid < numProcesses; ppid = e.getChannelId().getSrcPid(); assert ppid >= 0 && ppid < numProcesses; } else { ppid = e.getPid(); assert ppid >= 0 && ppid < numProcesses; } } } fsms.set(pid, fsm); alphabet.addAll(fsm.getAlphabet()); unSpecifiedPids -= 1; }
@Override public String toString() { String ret = "CFSM: \n"; for (FSM f : fsms) { ret += "\t" + f.toString() + "\n\n"; } return ret.substring(0, ret.length() - 1); }
/** * Generate SCM representation of this CFSM, with bad states if this CFSM was augmented with any * invariants. */ public String toScmString(String cfsmName) { assert unSpecifiedPids == 0; String ret = "scm " + cfsmName + ":\n\n"; // Channels: // Add a special channel for handling local events, represented as // messages on this channel. LocalEventsChannelId localChId; if (localEventsChIndex == Integer.MAX_VALUE) { localChId = new LocalEventsChannelId(this.channelIds.size()); localEventsChIndex = localChId.getScmId(); this.channelIds.add(localChId); } else { localChId = (LocalEventsChannelId) this.channelIds.get(localEventsChIndex); } ret += "nb_channels = " + channelIds.size() + " ;\n"; ret += "/*\n"; for (int i = 0; i < channelIds.size(); i++) { ret += "channel " + Integer.toString(i) + " : " + channelIds.get(i).toString() + "\n"; } ret += "*/\n\n"; // Whether or not any channels are lossy: // TODO: add lossy field to ChannelId and list all channel ids that // are lossy here. // Parameters/Alphabet: ret += "parameters :\n"; ret += alphabet.toScmParametersString(); ret += "\n"; // FSMS: for (int pid = 0; pid < numProcesses; pid++) { FSM f = fsms.get(pid); ret += "automaton p" + Integer.toString(pid) + " :\n"; ret += f.toScmString(localChId); ret += "\n"; } // Bad states: if (!invs.isEmpty()) { ret += "\nbad_states:\n"; for (BadState b : getBadStates()) { ret += b.toScmString() + "\n"; } } return ret; }
/** * Traverse the graph of FSM f and replace all transitions on e to some state s to transition to a * new state X, and add a transition from X to s that enqueues event e on channel identified by * invCid. * * @param visited * @param f1 * @param e1 * @param invCid */ private void addSendToEventTx( FSM f, DistEventType eToTrace, DistEventType eTracer1, DistEventType eTracer2, Set<FSMState> visited) { for (FSMState init : f.getInitStates()) { recurseAddSendToEventTx(f, init, eToTrace, eTracer1, eTracer2, visited); } }
/** * Recursive call to perform DFA exploration of FSM f. Helper to addSendToEventTx * * @param visited */ private void recurseAddSendToEventTx( FSM f, FSMState parent, DistEventType eToTrace, DistEventType eTracer1, DistEventType eTracer2, Set<FSMState> visited) { if (visited.contains(parent)) { return; } visited.add(parent); // If there is a transition on to-trace event, then perform the // re-writing. if (parent.getTransitioningEvents().contains(eToTrace)) { for (FSMState child : parent.getNextStates(eToTrace)) { f.addSyntheticState(parent, child, eToTrace, eTracer1, eTracer2); if (!visited.contains(child)) { // If we haven't visited the child yet, then recurse to it. recurseAddSendToEventTx(f, child, eToTrace, eTracer1, eTracer2, visited); } } } for (DistEventType e : parent.getTransitioningEvents()) { // Now handle all the non-to-trace events. Note, however, that these // have been re-written above with eTracer1 events, so this is what // we check for. if (!e.equals(eTracer1)) { for (FSMState nextF : parent.getNextStates(e)) { if (visited.contains(nextF)) { continue; } recurseAddSendToEventTx(f, nextF, eToTrace, eTracer1, eTracer2, visited); } } } }
/** * Generates a Promela representation of this CFSM, to be used with SPIN. The never claim is not * specified here and it is appended to the CFSM elsewhere. */ public String toPromelaString(List<BinaryInvariant> invariants, int chanCapacity) { assert unSpecifiedPids == 0; String ret = "/* Spin-promela Multiple invariants */\n\n"; // Message types: // // mtype is global and can only be declared once. // There is also limit of 255 for the size of mtype. // This outputs a set of event types for the CFSM. ret += "/* Message types: */\n"; ret += "mtype = { "; Set<String> eventTypes = Util.newSet(); for (DistEventType e : alphabet) { eventTypes.add(e.getPromelaEType()); } ret += StringUtils.join(eventTypes, ", "); ret += " };\n"; // End mtype declaration. ret += "\n\n"; // Define the channels: ret += "/* Channels: */\n\n"; // Specifying channels as an array to work with inlines. ret += String.format("chan channel[%d] = [%d] of { mtype };\n", channelIds.size(), chanCapacity); // The following block defines EMPTYCHANNELCHECK as a conditional that // checks if all the channels are empty. This is used in the never claim // to make sure our channels are empty before terminating. String emptyChannelCheck = ""; for (int i = 0; i < channelIds.size(); i++) { ret += "/* Channel " + channelIds.get(i).toString() + " */\n"; if (i != 0) { emptyChannelCheck += " && "; } emptyChannelCheck += "empty(channel[" + i + "])"; } ret += String.format("#define EMPTYCHANNELCHECK (%s)\n", emptyChannelCheck); ret += "\n\n"; // Tracks if the current states of each of the FSM are terminal. ret += "bit terminal[" + numProcesses + "];\n"; // ENDSTATECHECK is the conditional used by the never claim to // check the terminal states in all CFSMs. The never claim has this to // ensure that the processes are in a proper terminal state when the // never claim is done. String endStateCheck = ""; for (int pid = 0; pid < numProcesses; pid++) { // Set up the terminal check conditional. if (pid != 0) { endStateCheck += " && "; } endStateCheck += "terminal[" + pid + "]"; } ret += String.format("#define ENDSTATECHECK (%s)\n", endStateCheck); // Event type definitions for type tracking // OTHEREVENTs are used for other transitions so we do not accidentally // trigger an "a NFby b". This can happen when a == b. This invariant // can be accepted if a transition happens that does not call // setRecentEvent. OTHEREVENT does not match any event we are interested // in tracking so it is safe to use during these transitions. ret += "#define OTHEREVENT (0)\n"; // Event types we're actively tracking. ret += "#define LOCAL (1)\n"; ret += "#define SEND (2)\n"; ret += "#define RECV (3)\n"; // Custom datatype to assist in tracking recent event. ret += "typedef myEvent {\n"; // The type of event: LOCAL, SEND or RECV ret += " byte type;\n"; // id is the process id if the type is LOCAL and the channel id if the // type is SEND or RECV. ret += " byte id;\n"; // The event itself. These are the previously defined mtypes. ret += " mtype event;\n"; ret += "};\n"; // Declaration of event tracker. ret += "myEvent recentEvent;\n"; // Custom inline function to update most recent event. ret += "inline setRecentEvent(event_type, owner_id, event_message) {\n"; ret += " d_step{\n"; ret += " recentEvent.type = event_type;\n"; ret += " recentEvent.id = owner_id;\n"; ret += " recentEvent.event = event_message;\n"; ret += " };\n"; ret += "}\n"; // Each of the FSMs in the CFSM: for (int pid = 0; pid < numProcesses; pid++) { String labelPrefix = "state" + Integer.toString(pid); FSM f = fsms.get(pid); ret += "active proctype p" + Integer.toString(pid) + "(){\n"; ret += f.toPromelaString(invariants, labelPrefix); ret += "}\n\n"; } ret += "\n\n"; for (BinaryInvariant inv : invariants) { ret += "/* " + inv.toString() + "*/\n"; ret += inv.promelaNeverClaim(); ret += "\n\n"; } return ret; }