Change in the Conflict Tracker analysis
[jpf-core.git] / src / main / gov / nasa / jpf / listener / ConflictTracker.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.listener;
19
20 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.JPF;
22 import gov.nasa.jpf.ListenerAdapter;
23 import gov.nasa.jpf.search.Search;
24 import gov.nasa.jpf.jvm.bytecode.*;
25 import gov.nasa.jpf.vm.*;
26 import gov.nasa.jpf.vm.bytecode.LocalVariableInstruction;
27 import gov.nasa.jpf.vm.bytecode.ReadInstruction;
28 import gov.nasa.jpf.vm.bytecode.StoreInstruction;
29 import gov.nasa.jpf.vm.bytecode.WriteInstruction;
30
31 import java.io.PrintWriter;
32
33 import java.util.*;
34
35 /**
36  * Listener using data flow analysis to find conflicts between smartApps.
37  **/
38
39 public class ConflictTracker extends ListenerAdapter {
40   private final PrintWriter out;
41   private final HashSet<String> conflictSet = new HashSet<String>(); // Variables we want to track
42   private final HashSet<String> appSet = new HashSet<String>(); // Apps we want to find their conflicts
43   private final HashSet<String> manualSet = new HashSet<String>(); // Writer classes with manual inputs to detect direct-direct(No Conflict) interactions
44   private final HashMap<Integer, Node> nodes = new HashMap<Integer, Node>(); // Nodes of a graph
45   private ArrayList<NameValuePair> tempSetSet = new ArrayList<NameValuePair>();
46   private long timeout;
47   private long startTime;
48   private Node parentNode = new Node(-2);
49   private String operation;
50   private String detail;
51   private String errorMessage;
52   private int depth;
53   private int id;
54   private boolean conflictFound = false;
55   private boolean manual = false;
56
57   private final String SET_LOCATION_METHOD = "setLocationMode";
58   private final String LOCATION_VAR = "locationMode";
59   
60   public ConflictTracker(Config config, JPF jpf) {
61     out = new PrintWriter(System.out, true);
62
63     String[] conflictVars = config.getStringArray("variables");
64     // We are not tracking anything if it is null
65     if (conflictVars != null) {
66       for (String var : conflictVars) {
67         conflictSet.add(var);
68       }
69     }
70     String[] apps = config.getStringArray("apps");
71     // We are not tracking anything if it is null
72     if (apps != null) {
73       for (String var : apps) {
74         appSet.add(var);
75       }
76     }
77     String[] manualClasses = config.getStringArray("manualClasses");
78     // We are not tracking anything if it is null
79     if (manualClasses != null) {
80       for (String var : manualClasses) {
81         manualSet.add(var);
82       }
83     }
84
85     // Timeout input from config is in minutes, so we need to convert into millis
86     timeout = config.getInt("timeout", 0) * 60 * 1000;
87     startTime = System.currentTimeMillis();
88   }
89
90   boolean propagateTheChange(Node currentNode) {
91         HashSet<Node> changed = new HashSet<Node>(currentNode.getSuccessors());
92         HashMap<Node, HashSet<Node>> parentQueueMap = new HashMap<Node, HashSet<Node>>();
93         HashSet<Node> parents = new HashSet<Node>();
94         parents.add(currentNode);
95
96         for (Node node : currentNode.getSuccessors()) {
97                 parentQueueMap.put(node, parents);
98         }
99
100         while(!changed.isEmpty()) {
101                 // Get the first element of the changed set and remove it
102                 Node nodeToProcess = changed.iterator().next();
103                 changed.remove(nodeToProcess);
104
105                 // Update the changed parents
106                 parents = parentQueueMap.get(nodeToProcess);
107                 boolean isChanged = false;
108
109                 for (Node node : parents) {
110                         // Update the edge
111                         isChanged |= updateTheOutSet(node, nodeToProcess);
112                 }
113
114                 // Check for a conflict if the outSet of nodeToProcess is changed
115                 if (isChanged) {
116                         for (Node node : nodeToProcess.getSuccessors()) {
117                                 HashMap<Transition, ArrayList<NameValuePair>> setSets = nodeToProcess.getOutgoingEdges().get(node).getSetSetMap();
118                                 for (Map.Entry mapElement : setSets.entrySet()) {
119                                         Transition transition = (Transition)mapElement.getKey();
120                                         if (checkForConflict(nodeToProcess, node, transition))
121                                                 return true;
122                                 }
123                         }
124                 }
125
126                 // Update the parents list for the successors of the current node
127                 parents = new HashSet<Node>();
128                 parents.add(nodeToProcess);
129
130                 // Checking if the out set has changed or not(Add its successors to the change list!)
131                 if (isChanged) {
132                         for (Node i : nodeToProcess.getSuccessors()) {
133                                 if (!changed.contains(i))
134                                         changed.add(i);
135
136                                 // Update the list of updated parents for the current node
137                                 if (parentQueueMap.containsKey(i))
138                                         parentQueueMap.get(i).add(nodeToProcess);
139                                 else
140                                         parentQueueMap.put(i, parents);
141                         }
142                 }
143       }
144
145       return false;
146   }
147
148   String createErrorMessage(NameValuePair pair, HashMap<String, String> valueMap, HashMap<String, Integer> writerMap) {
149         String message = "Conflict found between the two apps. App"+pair.getAppNum()+
150                          " has written the value: "+pair.getValue()+
151                          " to the variable: "+pair.getVarName()+" while App"
152                          +writerMap.get(pair.getVarName())+" is overwriting the value: "
153                          +valueMap.get(pair.getVarName())+" to the same variable!";
154         System.out.println(message);    
155         return message;
156   }
157
158   boolean checkForConflict(Node parentNode, Node currentNode, Transition currentTransition) {
159         ArrayList<NameValuePair> setSet = parentNode.getOutgoingEdges().get(currentNode).getSetSetMap().get(currentTransition);
160         HashMap<String, String> valueMap = new HashMap<String, String>(); // HashMap from varName to value
161         HashMap<String, Integer> writerMap = new HashMap<String, Integer>(); //  HashMap from varName to appNum
162
163         // Update the valueMap and writerMap + check for conflict between the elements of setSet
164         for (int i = 0;i < setSet.size();i++) {
165             NameValuePair nameValuePair = setSet.get(i);
166             String varName = nameValuePair.getVarName();
167             String value = nameValuePair.getValue();
168             Integer appNum = nameValuePair.getAppNum();
169
170             if (valueMap.containsKey(varName)) {
171                 // Check if we have a same writer
172                 if (!writerMap.get(varName).equals(appNum)) {
173                         // Check if we have a conflict or not
174                         if (!valueMap.get(varName).equals(value)) {
175                                 errorMessage = createErrorMessage(nameValuePair, valueMap, writerMap);
176                                 return true;
177                         } else { // We have two writers writing the same value
178                                 writerMap.put(varName, 3); // 3 represents both apps
179                         }
180                 } else {
181                         // Check if we have more than one value with the same writer
182                         if (!valueMap.get(varName).equals(value)) {
183                                 // We have one writer writing more than one value in a same event
184                                 valueMap.put(varName, "twoValue");
185                         }
186                 }
187              } else {
188                 valueMap.put(varName, value);
189                 writerMap.put(varName, appNum);
190              }
191         }
192
193         // Check for conflict between outSet and this transition setSet
194         for (NameValuePair i : currentNode.getOutSet()) {
195              if (valueMap.containsKey(i.getVarName())) {
196                  String value = valueMap.get(i.getVarName());
197                  Integer writer = writerMap.get(i.getVarName());
198                      if ((value != null)&&(writer != null)) {
199                          if (!value.equals(i.getValue())&&!writer.equals(i.getAppNum())) { // We have different values and different writers
200                              errorMessage = createErrorMessage(i, valueMap, writerMap);
201                              return true;
202                          }
203                      }
204              }
205        }
206
207        return false;
208   }
209
210   boolean updateTheOutSet(Node parentNode, Node currentNode) {
211         HashMap<Transition, ArrayList<NameValuePair>> setSet = parentNode.getOutgoingEdges().get(currentNode).getSetSetMap();
212         HashSet<String> updatedVarNames = new HashSet<String>();
213         Edge currentEdge = parentNode.getOutgoingEdges().get(currentNode);
214         HashMap<String, Integer> lastWriter = currentEdge.getLastWriter();
215         HashMap<String, String> lastValue = currentEdge.getLastValue();
216         boolean isChanged = false;
217
218         for (Map.Entry mapElement : setSet.entrySet()) {
219             ArrayList<NameValuePair> value = (ArrayList<NameValuePair>)mapElement.getValue();
220   
221             for (int i = 0;i < value.size();i++) {
222                         updatedVarNames.add(value.get(i).getVarName());
223             }
224         }
225
226
227         for (NameValuePair i : parentNode.getOutSet()) {
228                 if (!updatedVarNames.contains(i.getVarName()))
229                         isChanged |= currentNode.getOutSet().add(i);
230         }
231
232
233         for (Map.Entry mapElement : setSet.entrySet()) {
234             ArrayList<NameValuePair> value = (ArrayList<NameValuePair>)mapElement.getValue();
235   
236             for (int i = 0;i < value.size();i++) {
237                         if (value.get(i).getAppNum().equals(lastWriter.get(value.get(i).getVarName())) 
238                             && value.get(i).getValue().equals(lastValue.get(value.get(i).getVarName()))) {
239                                 isChanged |= currentNode.getOutSet().add(value.get(i));
240                         }
241             }
242         }
243
244         return isChanged;
245   }
246
247   void updateTheEdge(Node currentNode, Transition transition) {
248         if (parentNode.getOutgoingEdges().containsKey(currentNode)) {
249                 Edge currentEdge = parentNode.getOutgoingEdges().get(currentNode);
250                 if (currentEdge.getSetSetMap().containsKey(transition)) { // Update the transition
251                         if (manual)
252                                 currentEdge.getSetSetMap().put(transition, tempSetSet);
253                         else
254                                 currentEdge.getSetSetMap().get(transition).addAll(tempSetSet);
255                 } else { // Add a new transition
256                         currentEdge.getSetSetMap().put(transition, tempSetSet);
257                 }
258         } else {
259                 parentNode.getOutgoingEdges().put(currentNode, new Edge(parentNode, currentNode));
260                 Edge currentEdge = parentNode.getOutgoingEdges().get(currentNode);
261                 currentEdge.getSetSetMap().put(transition, tempSetSet);
262         }
263
264         // Update the last writer and last value for this edge for each varName
265         Edge currentEdge = parentNode.getOutgoingEdges().get(currentNode);
266         ArrayList<NameValuePair> setSet = currentEdge.getSetSetMap().get(transition);
267         for (int i = 0;i < setSet.size();i++) {
268                 NameValuePair nameValuePair = setSet.get(i);
269                 currentEdge.getLastWriter().put(nameValuePair.getVarName(), nameValuePair.getAppNum());
270                 currentEdge.getLastValue().put(nameValuePair.getVarName(), nameValuePair.getValue());
271         }
272   }
273
274   
275
276   static class Node {
277         Integer id;
278         HashSet<Node> predecessors = new HashSet<Node>();
279         HashSet<Node> successors = new HashSet<Node>();
280         HashSet<NameValuePair> outSet = new HashSet<NameValuePair>();
281         HashMap<Node, Edge> outgoingEdges = new HashMap<Node, Edge>();
282
283         Node(Integer id) {
284           this.id = id;
285         }
286
287         Integer getId() {
288                 return id;
289         }
290
291         HashSet<Node> getPredecessors() {
292                 return predecessors;
293         }
294
295         HashSet<Node> getSuccessors() {
296                 return successors;
297         }
298
299         HashSet<NameValuePair> getOutSet() {
300                 return outSet;
301         }
302
303         HashMap<Node, Edge> getOutgoingEdges() {
304                 return outgoingEdges;
305         }
306   }
307
308   static class Edge {
309         Node source, destination;
310         HashMap<String, Integer> lastWriter = new HashMap<String, Integer>();
311         HashMap<String, String> lastValue = new HashMap<String, String>();
312         HashMap<Transition, ArrayList<NameValuePair>> setSetMap = new HashMap<Transition, ArrayList<NameValuePair>>();
313
314         Edge(Node source, Node destination) {
315                 this.source = source;
316                 this.destination = destination;
317         }
318
319         Node getSource() {
320                 return source;
321         }
322
323         Node getDestination() {
324                 return destination;
325         }
326
327         HashMap<Transition, ArrayList<NameValuePair>> getSetSetMap() {
328                 return setSetMap;
329         }
330
331         HashMap<String, Integer> getLastWriter() {
332                 return lastWriter;
333         }
334
335         HashMap<String, String> getLastValue() {
336                 return lastValue;
337         }
338   }
339
340   static class NameValuePair {
341         Integer appNum;
342         String value;
343         String varName;
344         boolean isManual;
345
346         NameValuePair(Integer appNum, String value, String varName, boolean isManual) {
347                 this.appNum = appNum;
348                 this.value = value;
349                 this.varName = varName;
350                 this.isManual = isManual;
351         }
352
353         void setAppNum(Integer appNum) {
354                 this.appNum = appNum;
355         }
356
357         void setValue(String value) {
358                 this.value = value;
359         }
360
361         void setVarName(String varName) {
362                 this.varName = varName;
363         }
364
365         void setIsManual(String varName) {
366                 this.isManual = isManual;
367         }
368
369         Integer getAppNum() {
370                 return appNum;
371         }
372
373         String getValue() {
374                 return value;
375         }
376
377         String getVarName() {
378                 return varName;
379         }
380
381         boolean getIsManual() {
382                 return isManual;
383         }
384
385         @Override
386         public boolean equals(Object o) {
387       if (o instanceof NameValuePair) {
388         NameValuePair other = (NameValuePair) o;
389         if (varName.equals(other.getVarName()))
390           return appNum.equals(other.getAppNum());
391       }
392       return false;
393         }
394
395         @Override
396         public int hashCode() {
397                 return appNum.hashCode() * 31 + varName.hashCode();
398         }
399   }
400
401   @Override
402   public void stateRestored(Search search) {
403     id = search.getStateId();
404     depth = search.getDepth();
405     operation = "restored";
406     detail = null;
407
408     out.println("The state is restored to state with id: "+id+", depth: "+depth);
409   
410     // Update the parent node
411     if (nodes.containsKey(id)) {
412           parentNode = nodes.get(id);
413     } else {
414           parentNode = new Node(id);
415     }
416   }
417
418   @Override
419   public void searchStarted(Search search) {
420     out.println("----------------------------------- search started");
421   }
422  
423
424   @Override
425   public void stateAdvanced(Search search) {
426     String theEnd = null;
427     Transition transition = search.getTransition();
428     id = search.getStateId();
429     depth = search.getDepth();
430     operation = "forward";
431
432     // Add the node to the list of nodes
433     if (nodes.get(id) == null)
434         nodes.put(id, new Node(id));
435
436     Node currentNode = nodes.get(id);
437
438     // Update the edge based on the current transition
439     updateTheEdge(currentNode, transition);
440
441     // Reset the temporary variables and flags
442     tempSetSet = new ArrayList<NameValuePair>();
443     manual = false;
444
445     // Check for the conflict in this transition
446     conflictFound = checkForConflict(parentNode, currentNode, transition);
447
448     if (search.isNewState()) {
449       detail = "new";
450     } else {
451       detail = "visited";
452     }
453
454     if (search.isEndState()) {
455       out.println("This is the last state!");
456       theEnd = "end";
457     }
458
459     out.println("The state is forwarded to state with id: "+id+", depth: "+depth+" which is "+detail+" state: "+"% "+theEnd);
460     
461     // Updating the predecessors for this node
462     // Check if parent node is already in successors of the current node or not
463     if (!(currentNode.getPredecessors().contains(parentNode)))
464         currentNode.getPredecessors().add(parentNode);
465
466     // Update the successors for this node
467     // Check if current node is already in successors of the parent node or not
468     if (!(parentNode.getSuccessors().contains(currentNode)))
469         parentNode.getSuccessors().add(currentNode);
470
471     // Update the outset of the current node and check if it is changed or not to propagate the change
472     boolean isChanged = updateTheOutSet(parentNode, currentNode);
473     
474     // Check if the outSet of this state has changed, update all of its successors' sets if any
475     if (isChanged)
476         conflictFound = conflictFound || propagateTheChange(currentNode);
477
478     // Update the parent node
479     if (nodes.containsKey(id)) {
480           parentNode = nodes.get(id);
481     } else {
482           parentNode = new Node(id);
483     }
484   }
485
486   @Override
487   public void stateBacktracked(Search search) {
488     id = search.getStateId();
489     depth = search.getDepth();
490     operation = "backtrack";
491     detail = null;
492
493     out.println("The state is backtracked to state with id: "+id+", depth: "+depth);
494
495     // Update the parent node
496     if (nodes.containsKey(id)) {
497           parentNode = nodes.get(id);
498     } else {
499           parentNode = new Node(id);
500     }
501   }
502
503   @Override
504   public void searchFinished(Search search) {
505     out.println("----------------------------------- search finished");
506   }
507
508   private String getValue(ThreadInfo ti, Instruction inst, byte type) {
509     StackFrame frame;
510     int lo, hi;
511
512     frame = ti.getTopFrame();
513
514     if ((inst instanceof JVMLocalVariableInstruction) ||
515         (inst instanceof JVMFieldInstruction))
516     {
517       if (frame.getTopPos() < 0)
518         return(null);
519
520       lo = frame.peek();
521       hi = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
522
523       return(decodeValue(type, lo, hi));
524     }
525
526     if (inst instanceof JVMArrayElementInstruction)
527       return(getArrayValue(ti, type));
528
529     return(null);
530   }
531
532   private final static String decodeValue(byte type, int lo, int hi) {
533     switch (type) {
534       case Types.T_ARRAY:   return(null);
535       case Types.T_VOID:    return(null);
536
537       case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
538       case Types.T_BYTE:    return(String.valueOf(lo));
539       case Types.T_CHAR:    return(String.valueOf((char) lo));
540       case Types.T_DOUBLE:  return(String.valueOf(Types.intsToDouble(lo, hi)));
541       case Types.T_FLOAT:   return(String.valueOf(Types.intToFloat(lo)));
542       case Types.T_INT:     return(String.valueOf(lo));
543       case Types.T_LONG:    return(String.valueOf(Types.intsToLong(lo, hi)));
544       case Types.T_SHORT:   return(String.valueOf(lo));
545
546       case Types.T_REFERENCE:
547         ElementInfo ei = VM.getVM().getHeap().get(lo);
548         if (ei == null)
549           return(null);
550
551         ClassInfo ci = ei.getClassInfo();
552         if (ci == null)
553           return(null);
554
555         if (ci.getName().equals("java.lang.String"))
556           return('"' + ei.asString() + '"');
557
558         return(ei.toString());
559
560       default:
561         System.err.println("Unknown type: " + type);
562         return(null);
563      }
564   }
565
566   private String getArrayValue(ThreadInfo ti, byte type) {
567     StackFrame frame;
568     int lo, hi;
569
570     frame = ti.getTopFrame();
571     lo    = frame.peek();
572     hi    = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
573
574     return(decodeValue(type, lo, hi));
575   }
576
577   private byte getType(ThreadInfo ti, Instruction inst) {
578     StackFrame frame;
579     FieldInfo fi;
580     String type;
581
582     frame = ti.getTopFrame();
583     if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
584       return (Types.T_REFERENCE);
585     }
586
587     type = null;
588
589     if (inst instanceof JVMLocalVariableInstruction) {
590       type = ((JVMLocalVariableInstruction) inst).getLocalVariableType();
591     } else if (inst instanceof JVMFieldInstruction){
592       fi = ((JVMFieldInstruction) inst).getFieldInfo();
593       type = fi.getType();
594     }
595
596     if (inst instanceof JVMArrayElementInstruction) {
597       return (getTypeFromInstruction(inst));
598     }
599
600     if (type == null) {
601       return (Types.T_VOID);
602     }
603
604     return (decodeType(type));
605   }
606
607   private final static byte getTypeFromInstruction(Instruction inst) {
608     if (inst instanceof JVMArrayElementInstruction)
609       return(getTypeFromInstruction((JVMArrayElementInstruction) inst));
610
611     return(Types.T_VOID);
612   }
613
614   private final static byte decodeType(String type) {
615     if (type.charAt(0) == '?'){
616       return(Types.T_REFERENCE);
617     } else {
618       return Types.getBuiltinType(type);
619     }
620   }
621
622   // Find the variable writer
623   // It should be one of the apps listed in the .jpf file
624   private String getWriter(List<StackFrame> sfList, HashSet<String> writerSet) {
625     // Start looking from the top of the stack backward
626     for(int i=sfList.size()-1; i>=0; i--) {
627       MethodInfo mi = sfList.get(i).getMethodInfo();
628       if(!mi.isJPFInternal()) {
629         String method = mi.getStackTraceName();
630         // Check against the writers in the writerSet
631         for(String writer : writerSet) {
632           if (method.contains(writer)) {
633             return writer;
634           }
635         }
636       }
637     }
638
639     return null;
640   }
641
642   private void writeWriterAndValue(String writer, String value, String var) {
643     // Update the temporary Set set.
644     NameValuePair temp = new NameValuePair(1, value, var, manual);
645     if (writer.equals("App2"))
646         temp = new NameValuePair(2, value, var, manual);
647     
648     tempSetSet.add(temp);
649   }
650
651   @Override
652   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
653     if (timeout > 0) {
654       if (System.currentTimeMillis() - startTime > timeout) {
655         StringBuilder sbTimeOut = new StringBuilder();
656         sbTimeOut.append("Execution timeout: " + (timeout / (60 * 1000)) + " minutes have passed!");
657         Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sbTimeOut.toString());
658         ti.setNextPC(nextIns);
659       }
660     }
661
662     if (conflictFound) {
663       StringBuilder sb = new StringBuilder();
664       sb.append(errorMessage);
665       Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sb.toString());
666       ti.setNextPC(nextIns);
667     } else {
668       if (conflictSet.contains(LOCATION_VAR)) {
669         MethodInfo mi = executedInsn.getMethodInfo();
670         // Find the last load before return and get the value here
671         if (mi.getName().equals(SET_LOCATION_METHOD) &&
672                 executedInsn instanceof ALOAD && nextInsn instanceof ARETURN) {
673           byte type  = getType(ti, executedInsn);
674           String value = getValue(ti, executedInsn, type);
675
676           // Extract the writer app name
677           ClassInfo ci = mi.getClassInfo();
678           String writer = ci.getName();
679
680           // Update the temporary Set set.
681           writeWriterAndValue(writer, value, LOCATION_VAR);
682         }
683       } else {
684         if (executedInsn instanceof WriteInstruction) {
685           String varId = ((WriteInstruction) executedInsn).getFieldInfo().getFullName();
686
687           for (String var : conflictSet) {
688             if (varId.contains(var)) {
689               // Get variable info
690               byte type = getType(ti, executedInsn);
691               String value = getValue(ti, executedInsn, type);
692               String writer = getWriter(ti.getStack(), appSet);
693               // Just return if the writer is not one of the listed apps in the .jpf file
694               if (writer == null)
695                 return;
696
697               if (getWriter(ti.getStack(), manualSet) != null)
698                 manual = true;
699
700               // Update the temporary Set set.
701               writeWriterAndValue(writer, value, var);
702             }
703           }
704         }
705       }
706     }
707   }
708 }