/** * Augments the CFSM with a tracking channel and send synthetic messages on this channel to keep * track of the two events that are part of the binary invariant inv. Adds the synthetic events to * the CFSM alphabet. */ private void augmentWithBinInvTracing(BinaryInvariant inv) { DistEventType e1 = inv.getFirst(); DistEventType e2 = inv.getSecond(); assert alphabet.contains(e1); assert alphabet.contains(e2); assert e1.getPid() < fsms.size(); assert e2.getPid() < fsms.size(); assert !invs.contains(invs); invs.add(inv); int scmId = this.channelIds.size(); if (firstSyntheticChIndex > scmId) { firstSyntheticChIndex = scmId; } // Create and add a new invariant-specific channel. ChannelId invCid = new InvChannelId(inv, scmId); this.channelIds.add(invCid); // Update the FSM corresponding to e1. Set<FSMState> visited = Util.newSet(); FSM f1 = this.fsms.get(e1.getPid()); DistEventType e1Tracer1 = DistEventType.SynthSendEvent(e1, invCid, true); DistEventType e1Tracer2 = DistEventType.SynthSendEvent(e1, invCid, false); addSendToEventTx(f1, e1, e1Tracer1, e1Tracer2, visited); this.alphabet.add(e1Tracer1); this.alphabet.add(e1Tracer2); // Update the FSM corresponding to e2. visited.clear(); FSM f2 = this.fsms.get(e2.getPid()); DistEventType e2Tracer1 = DistEventType.SynthSendEvent(e2, invCid, true); DistEventType e2Tracer2 = DistEventType.SynthSendEvent(e2, invCid, false); addSendToEventTx(f2, e2, e2Tracer1, e2Tracer2, visited); this.alphabet.add(e2Tracer1); this.alphabet.add(e2Tracer2); inv.setFirstSynthTracers(e1Tracer1, e1Tracer2); inv.setSecondSynthTracers(e2Tracer1, e2Tracer2); }
/** * Augments the CFSM with synthetic events for model checking binv. * * <p>The basic strategy, regardless of invariant, is to create a separate FIFO queue that will be * used to record the sequence of executed events that are relevant to the invariant. * * <p>For instance, for a AFby b invariant, create a queue Q_ab. Modify any state p that has an * outgoing "a" transition, add a synthetic state p_synth, and redirect the "a" transition from p * to p_synth. Then, add just one outgoing transition on "Q_ab ! a" from p_synth to the original * state target of "a" in state p. That is, whenever "a" occurs, we will add "a" to Q_ab. Do the * same for event "b". * * <p>For a AFby b bad state pairs within the modified GFSM (per above procedure) are all initial * state and all states where all queues except Q_ab are empty, and where Q_ab = [*a], and where * the process states are terminal. In a sense, we've added Q_ab to track "a" and "b" executions, * and have not interfered with the normal execution of the FIFO system. * * <p>For a AP b, the procedure is identical, but the second bad state in every pair would have * Q_ab = [b*]. For a NFby b, Q_ab = [*a*b*]. In a sense, we've expressed LTL properties as * regular expressions of Q_ab queue contents. */ public void augmentWithInvTracing(BinaryInvariant binv) throws Exception { if (binv instanceof EventuallyHappens) { augmentWithInvTracing((EventuallyHappens) binv); } else if (binv instanceof AlwaysPrecedes || binv instanceof AlwaysFollowedBy || binv instanceof NeverFollowedBy) { augmentWithBinInvTracing(binv); } else { throw new Exception("Unrecognized binary invarianr type: " + binv.toString()); } }
/** * 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; }
/** * Returns a set of bad states that correspond to a specific invariant that is augmenting this * CFSM. Each invariant corresponds to (possibly multiple) bad states. A bad states is a * combination of FSM states and a sequence of regular expressions that describe the contents of * each of the queues in the CFSM. For an invariant I, a bad state B has the property that if B is * reachable in the CFSM then I is falsified. That is, the path to reach B is the counter-example * for I. */ public List<BadState> getBadStates(BinaryInvariant inv) { assert invs.contains(inv); // Without invariants there are no bad states. if (invs.isEmpty()) { return Collections.emptyList(); } List<BadState> badStates = Util.newList(); Set<CFSMState> accepts = this.getAcceptStates(); if (accepts.isEmpty()) { assert !accepts.isEmpty(); } List<String> qReList = Util.newList(channelIds.size()); // Set non-synthetic queues reg-exps to accept the empty string. for (int i = 0; i < firstSyntheticChIndex; i++) { qReList.add("_"); } int invIndex = invs.indexOf(inv); // Set the synthetic queue reg-exps. for (int i = firstSyntheticChIndex; i < channelIds.size(); i++) { if (i == firstSyntheticChIndex + invIndex) { // The invariant we care about checking. qReList.add(inv.scmBadStateQRe()); } else if (i == localEventsChIndex) { // Add an RE for the local events queue. Set<String> localEvents = this.alphabet.getLocalEventScmStrings(); if (!localEvents.isEmpty()) { String localEventsQueueRe = "("; for (String eLocal : localEvents) { localEventsQueueRe += eLocal + " | "; } // Remove the last occurrence of the "|" character. localEventsQueueRe = localEventsQueueRe.substring(0, localEventsQueueRe.length() - 3); localEventsQueueRe += ")^*"; qReList.add(localEventsQueueRe); } else { // If there are no local events then the queue RE is the // empty string, since no corresponding local event messages // will be generated. qReList.add("_"); } } else { // Initialize non-inv invariant synthetic queues to accept // everything that their alphabet permits. qReList.add(inv.someSynthEventsQRe()); } } // For each accept, generate a bad state <accept, qReList>. for (CFSMState accept : accepts) { badStates.add(new BadState(accept, qReList)); } return badStates; }