/** * Compute invariants of a graph g by mining invariants from the transitive closure using {@code * extractInvariantsUsingTC}, which returns an over-approximation of the invariants that hold * (i.e. it may return invariants that do not hold, but may not fail to return an invariant that * does not hold) * * @param g the graph of nodes of type LogEvent * @param mineConcurrencyInvariants whether or not to also mine concurrency invariants * @return the set of temporal invariants the graph satisfies */ public TemporalInvariantSet computeTransClosureInvariants( TraceGraph<?> g, boolean mineConcurrencyInvariants) { TimedTask mineInvariants = PerformanceMetrics.createTask("mineInvariants", false); Set<ITemporalInvariant> overapproximatedInvariantsSet; AbstractMain main = AbstractMain.getInstance(); // Compute the over-approximated set of invariants for the input graph. try { TimedTask itc = PerformanceMetrics.createTask("invariants_transitive_closure", false); // Compute the transitive closure. AllRelationsTransitiveClosure transitiveClosure = new AllRelationsTransitiveClosure(g); // Get the over-approximation. itc.stop(); if (main.options.doBenchmarking) { logger.info("BENCHM: " + itc); } TimedTask io = PerformanceMetrics.createTask("invariants_approximation", false); // Extract invariants for all relations, iteratively. Since we are // not considering invariants over multiple relations, this is // sufficient. overapproximatedInvariantsSet = new LinkedHashSet<ITemporalInvariant>(); for (String relation : g.getRelations()) { overapproximatedInvariantsSet.addAll( extractInvariantsFromTC( g, transitiveClosure.get(relation), relation, mineConcurrencyInvariants)); } io.stop(); if (main.options.doBenchmarking) { logger.info("BENCHM: " + io); } // logger.info("Over-approx set: " // + overapproximatedInvariantsSet.toString()); } finally { mineInvariants.stop(); } return new TemporalInvariantSet(overapproximatedInvariantsSet); }
public AllRelationsTransitiveClosure(TraceGraph<?> g) { for (String relation : g.getRelations()) { tcs.put(relation, g.getTransitiveClosure(relation)); } }
/** * Extract an over-approximated set of invariants from the transitive closure {@code tc} of the * graph {@code g}. * * @param g the graph over LogEvent * @param tc the transitive closure (of {@code g}) from which to mine invariants * @param relation the relation to consider for the invariants * @return the over-approximated set of invariants * @throws Exception */ private Set<ITemporalInvariant> extractInvariantsFromTC( TraceGraph<?> g, TransitiveClosure tc, String relation, boolean mineConcurrencyInvariants) { // This maintains the mapping from event type to a map of trace ids -> // list of event instances in the trace. This is used to check // reachability only between event instances that are in the same trace. Map<EventType, Map<Integer, List<EventNode>>> etypeToTraceIdToENode = new LinkedHashMap<EventType, Map<Integer, List<EventNode>>>(); // Initialize the partitions map: each unique label maps to a list of // nodes with that label. for (EventNode node : g.getNodes()) { if (node.getEType().isSpecialEventType()) { /** * The inclusion of INITIAL and TERMINAL states in the graphs generates the following types * of "tautological" invariants (for all event types X): * * <pre> * - x AP TERMINAL * - INITIAL AP x * - x AP TERMINAL * - x AFby TERMINAL * - TERMINAL NFby INITIAL * </pre> * * <p>NOTE: x AP TERMINAL is not actually a tautological invariant, but we do not * mine/include it because we instead use x AFby INITIAL. * * <p>We filter these out by simply ignoring any temporal invariants of the form x INV y * where x or y in {INITIAL, TERMINAL}. This is useful because it relieves us from * checking/reporting invariants which are true for all graphs produced with typical * construction. */ continue; } Map<Integer, List<EventNode>> map; EventType etype = node.getEType(); if (!etypeToTraceIdToENode.containsKey(etype)) { map = new LinkedHashMap<Integer, List<EventNode>>(); etypeToTraceIdToENode.put(etype, map); } else { map = etypeToTraceIdToENode.get(etype); } List<EventNode> list; int tid = node.getTraceID(); if (!map.containsKey(tid)) { list = new LinkedList<EventNode>(); map.put(tid, list); } else { list = map.get(tid); } list.add(node); } Set<ITemporalInvariant> pathInvs = new LinkedHashSet<ITemporalInvariant>(); Set<ITemporalInvariant> neverConcurInvs = new LinkedHashSet<ITemporalInvariant>(); Set<ITemporalInvariant> alwaysConcurInvs = new LinkedHashSet<ITemporalInvariant>(); Set<Pair<EventType, EventType>> observedPairs = new LinkedHashSet<Pair<EventType, EventType>>(); int numTraces = g.getNumTraces(); for (Entry<EventType, Map<Integer, List<EventNode>>> e1Entry : etypeToTraceIdToENode.entrySet()) { EventType e1 = e1Entry.getKey(); // ///////////////// Determine if "INITIAL AFby e1" is true // Check if an e1 node appeared in every trace, if yes then inv // true. if (e1Entry.getValue().keySet().size() == numTraces) { pathInvs.add( new AlwaysFollowedInvariant( StringEventType.newInitialStringEventType(), e1, Event.defTimeRelationStr)); } // ///////////////// for (Entry<EventType, Map<Integer, List<EventNode>>> e2Entry : etypeToTraceIdToENode.entrySet()) { EventType e2 = e2Entry.getKey(); // If we have done (e1,e2) then do not do (e2,e1) because for // pair (e1,e2) we derive orderings and invariants for both // (e1,e2) and (e2,e1) -- this is done so that we can do correct // subsumption of invariants (and concurrency invariants require // symmetrical information). if (observedPairs.contains(new Pair<EventType, EventType>(e2, e1))) { continue; } observedPairs.add(new Pair<EventType, EventType>(e1, e2)); // /////////////////////////////// // Derive the ordering summary between each instance of e1 and // every instance of e2. EventOrderingSummary E1orderE2 = summarizeOrderings(e1Entry.getValue(), e2Entry.getValue(), tc); // Do same for e2,e1. EventOrderingSummary E2orderE1 = summarizeOrderings(e2Entry.getValue(), e1Entry.getValue(), tc); // /////////////////////////////// // Whether or not never ordered invariant was added -- // determines whether or not NFby invariants are added // below. boolean addedNeverOrdered = false; if (mineConcurrencyInvariants) { // Ignore local versions of alwaysOrdered and // neverOrdered since they are trivially true and false // respectively. if (!((DistEventType) e1) .getProcessName() .equals(((DistEventType) e2).getProcessName())) { // Because lack of order is symmetric, it doesn't matter // if we use E1orderE2.neverOrdered or // E2orderE1.neverOrdered. if (E1orderE2.neverOrdered) { alwaysConcurInvs.add( new AlwaysConcurrentInvariant((DistEventType) e2, (DistEventType) e1, relation)); addedNeverOrdered = true; } // Because complete order is symmetric, it doesn't // matter if we use E1orderE2.alwaysOrdered or // E2orderE1.alwaysOrdered. if (E1orderE2.alwaysOrdered && !E1orderE2.alwaysPrecedes && !E1orderE2.alwaysFollowedBy && !E2orderE1.alwaysPrecedes && !E2orderE1.alwaysFollowedBy) { neverConcurInvs.add( new NeverConcurrentInvariant((DistEventType) e2, (DistEventType) e1, relation)); } } } if (!addedNeverOrdered) { // Note that ACWith subsumes NFby, which is why we add NFby // if not(ACWith). if (E1orderE2.neverFollowedBy) { pathInvs.add(new NeverFollowedInvariant(e1, e2, relation)); } if (E2orderE1.neverFollowedBy) { pathInvs.add(new NeverFollowedInvariant(e2, e1, relation)); } } if (E1orderE2.alwaysFollowedBy) { pathInvs.add(new AlwaysFollowedInvariant(e1, e2, relation)); } if (E2orderE1.alwaysFollowedBy) { pathInvs.add(new AlwaysFollowedInvariant(e2, e1, relation)); } if (E1orderE2.alwaysPrecedes) { pathInvs.add(new AlwaysPrecedesInvariant(e2, e1, relation)); } if (E2orderE1.alwaysPrecedes) { pathInvs.add(new AlwaysPrecedesInvariant(e1, e2, relation)); } } } // Merge the concurrency and path invariant sets. pathInvs.addAll(neverConcurInvs); pathInvs.addAll(alwaysConcurInvs); return pathInvs; }