Adding feature to differentiate direct-direct interactions and conflicts
[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
41   private final PrintWriter out;
42   private final HashSet<String> conflictSet = new HashSet<String>(); // Variables we want to track
43   private final HashSet<String> appSet = new HashSet<String>(); // Apps we want to find their conflicts
44   private final HashSet<String> manualSet = new HashSet<String>(); // Writer classes with manual inputs to detect direct-direct(No Conflict) interactions
45   private final HashMap<Integer, Node> nodes = new HashMap<Integer, Node>(); // Nodes of a graph
46   private HashSet<NameValuePair> tempSetSet = new HashSet<NameValuePair>();
47   private long timeout;
48   private long startTime;
49   private Node parentNode = new Node(-2);
50   private String operation;
51   private String detail;
52   private int depth;
53   private int id;
54   private boolean conflictFound = false;
55   private boolean isSet = false;
56  
57   
58   public ConflictTracker(Config config, JPF jpf) {
59     out = new PrintWriter(System.out, true);
60
61     String[] conflictVars = config.getStringArray("variables");
62     // We are not tracking anything if it is null
63     if (conflictVars != null) {
64       for (String var : conflictVars) {
65         conflictSet.add(var);
66       }
67     }
68     String[] apps = config.getStringArray("apps");
69     // We are not tracking anything if it is null
70     if (apps != null) {
71       for (String var : apps) {
72         appSet.add(var);
73       }
74     }
75     String[] manualClasses = config.getStringArray("manualClasses");
76     // We are not tracking anything if it is null
77     if (manualClasses != null) {
78       for (String var : manualClasses) {
79         manualSet.add(var);
80       }
81     }
82
83     // Timeout input from config is in minutes, so we need to convert into millis
84     timeout = config.getInt("timeout", 0) * 60 * 1000;
85     startTime = System.currentTimeMillis();
86   }
87
88   boolean propagateTheChange(Node currentNode) {
89         HashSet<Node> changed = new HashSet<Node>(currentNode.getSuccessors());
90
91         while(!changed.isEmpty()) {
92                 // Get the first element of HashSet and remove it from the changed set
93                 Node nodeToProcess = changed.iterator().next();
94                 changed.remove(nodeToProcess);
95                         
96                 // Update the sets, store the outSet to temp before its changes
97                 boolean isChanged = updateSets(nodeToProcess);
98
99                 // Check for a conflict
100                 if (checkForConflict(nodeToProcess))
101                         return true;
102                         
103                 // Checking if the out set has changed or not(Add its successors to the change list!)
104                 if (isChanged) {
105                         for (Node i : nodeToProcess.getSuccessors()) {
106                                 if (!changed.contains(i))
107                                         changed.add(i);
108                         }
109                 }
110         }
111
112         return false;
113   }
114
115   boolean setOutSet(Node currentNode) {
116         boolean isChanged = false;
117         
118         // Update based on inSet
119         for (NameValuePair i : currentNode.getInSet()) {
120                 if (!currentNode.getOutSet().contains(i)) {
121                         isChanged = true;
122                         currentNode.getOutSet().add(i);
123                 }
124         }
125
126         // Overwrite based on setSet
127         for (NameValuePair i : currentNode.getSetSet()) {
128                 if (!currentNode.getOutSet().contains(i))
129                         isChanged = true;
130                 else
131                         currentNode.getOutSet().remove(i);
132                 currentNode.getOutSet().add(i);
133         }
134
135         return isChanged;
136   }
137
138   //Is it ok to point to the same object? (Not passed by value?)
139   void setInSet(Node currentNode) {
140         for (Node i : currentNode.getPredecessors()) {
141                 currentNode.getInSet().addAll(i.getOutSet());
142         }
143   }
144
145   boolean checkForConflict(Node currentNode) {
146         HashMap<String, String> valueMap = new HashMap<String, String>();
147         HashMap<String, Boolean> isManualMap = new HashMap<String, Boolean>();
148
149         for (NameValuePair i : currentNode.getOutSet()) {
150                 if (valueMap.containsKey(i.getVarName())) {
151                         if (!(isManualMap.get(i.getVarName())&&(i.getIsManual())))
152                                 if (!(valueMap.get(i.getVarName()).equals(i.getValue())))
153                                         return true;
154                 } else {
155                         valueMap.put(i.getVarName(), i.getValue());
156                         isManualMap.put(i.getVarName(), i.getIsManual());
157                 }
158         }
159
160         return false;
161   }
162
163   boolean updateSets(Node currentNode) {
164         // Set input set according to output set of pred states of current state
165         setInSet(currentNode);
166
167         // Set outSet according to inSet, and setSet of current node, check if there is a change
168         return setOutSet(currentNode);
169   }
170
171   static class Node {
172         Integer id;
173         HashSet<Node> predecessors = new HashSet<Node>();
174         HashSet<Node> successors = new HashSet<Node>();
175         HashSet<NameValuePair> inSet = new HashSet<NameValuePair>();
176         HashSet<NameValuePair> setSet = new HashSet<NameValuePair>();
177         HashSet<NameValuePair> outSet = new HashSet<NameValuePair>();
178
179         Node(Integer id) {
180                 this.id = id;
181         }
182
183         void addPredecessor(Node node) {
184                 predecessors.add(node);
185         }
186
187         void addSuccessor(Node node) {
188                 successors.add(node);
189         }
190
191         void setSetSet(HashSet<NameValuePair> setSet) {
192                 for (NameValuePair i : setSet) {
193                         this.setSet.add(new NameValuePair(i.getAppNum(), i.getValue(), i.getVarName(), i.getIsManual()));
194                 }
195         }
196
197         Integer getId() {
198                 return id;
199         }
200
201         HashSet<Node> getPredecessors() {
202                 return predecessors;
203         }
204
205         HashSet<Node> getSuccessors() {
206                 return successors;
207         }
208
209         HashSet<NameValuePair> getInSet() {
210                 return inSet;
211         }
212
213         HashSet<NameValuePair> getSetSet() {
214                 return setSet;
215         }
216
217         HashSet<NameValuePair> getOutSet() {
218                 return outSet;
219         }
220   }
221
222   static class NameValuePair {
223         Integer appNum;
224         String value;
225         String varName;
226         boolean isManual;
227
228         NameValuePair(Integer appNum, String value, String varName, boolean isManual) {
229                 this.appNum = appNum;
230                 this.value = value;
231                 this.varName = varName;
232                 this.isManual = isManual;
233         }
234
235         void setAppNum(Integer appNum) {
236                 this.appNum = appNum;
237         }
238
239         void setValue(String value) {
240                 this.value = value;
241         }
242
243         void setVarName(String varName) {
244                 this.varName = varName;
245         }
246
247         void setIsManual(String varName) {
248                 this.isManual = isManual;
249         }
250
251         Integer getAppNum() {
252                 return appNum;
253         }
254
255         String getValue() {
256                 return value;
257         }
258
259         String getVarName() {
260                 return varName;
261         }
262
263         boolean getIsManual() {
264                 return isManual;
265         }
266
267         @Override
268         public boolean equals(Object o) {
269                 if (o instanceof NameValuePair) {
270                         NameValuePair other = (NameValuePair) o;
271                         if (varName.equals(other.getVarName()))
272                                 return appNum.equals(other.getAppNum());
273                 }
274                 return false;
275         }
276
277         @Override
278         public int hashCode() {
279                 return appNum.hashCode() * 31 + varName.hashCode();
280         }
281   }
282
283   @Override
284   public void stateRestored(Search search) {
285     id = search.getStateId();
286     depth = search.getDepth();
287     operation = "restored";
288     detail = null;
289
290     out.println("The state is restored to state with id: "+id+", depth: "+depth);
291   
292     // Update the parent node
293     if (nodes.containsKey(id)) {
294         parentNode = nodes.get(id);
295     } else {
296         parentNode = new Node(id);
297     }
298   }
299
300   @Override
301   public void searchStarted(Search search) {
302     out.println("----------------------------------- search started");
303   }
304  
305
306   @Override
307   public void stateAdvanced(Search search) {
308     String theEnd = null;
309     id = search.getStateId();
310     depth = search.getDepth();
311     operation = "forward";
312
313     // Add the node to the list of nodes        
314     nodes.put(id, new Node(id));
315     Node currentNode = nodes.get(id);
316
317     // Update the setSet for this new node
318     if (isSet) {
319       currentNode.setSetSet(tempSetSet);
320       tempSetSet = new HashSet<NameValuePair>(); 
321       isSet = false;
322     }
323
324     if (search.isNewState()) {
325       detail = "new";
326     } else {
327       detail = "visited";
328     }
329
330     if (search.isEndState()) {
331       out.println("This is the last state!");
332       theEnd = "end";
333     }
334
335     out.println("The state is forwarded to state with id: "+id+", depth: "+depth+" which is "+detail+" state: "+"% "+theEnd);
336     
337     // Updating the predecessors for this node
338     // Check if parent node is already in successors of the current node or not
339     if (!(currentNode.getPredecessors().contains(parentNode)))
340         currentNode.addPredecessor(parentNode);
341
342     // Update the successors for this node
343     // Check if current node is already in successors of the parent node or not
344     if (!(parentNode.getSuccessors().contains(currentNode)))
345         parentNode.addSuccessor(currentNode);
346
347     // Update the sets, check if the outSet is changed or not
348     boolean isChanged = updateSets(currentNode);
349    
350     // Check for a conflict
351     conflictFound = checkForConflict(currentNode);
352
353     // Check if the outSet of this state has changed, update all of its successors' sets if any
354     if (isChanged)
355         conflictFound = conflictFound || propagateTheChange(currentNode);
356
357     // Update the parent node
358     if (nodes.containsKey(id)) {
359         parentNode = nodes.get(id);
360     } else {
361         parentNode = new Node(id);
362     }
363   }
364
365   @Override
366   public void stateBacktracked(Search search) {
367     id = search.getStateId();
368     depth = search.getDepth();
369     operation = "backtrack";
370     detail = null;
371
372     out.println("The state is backtracked to state with id: "+id+", depth: "+depth);
373
374     // Update the parent node
375     if (nodes.containsKey(id)) {
376         parentNode = nodes.get(id);
377     } else {
378         parentNode = new Node(id);
379     }
380   }
381
382   @Override
383   public void searchFinished(Search search) {
384     out.println("----------------------------------- search finished");
385   }
386
387   private String getValue(ThreadInfo ti, Instruction inst, byte type) {
388     StackFrame frame;
389     int lo, hi;
390
391     frame = ti.getTopFrame();
392
393     if ((inst instanceof JVMLocalVariableInstruction) ||
394         (inst instanceof JVMFieldInstruction))
395     {
396        if (frame.getTopPos() < 0)
397          return(null);
398
399        lo = frame.peek();
400        hi = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
401
402        return(decodeValue(type, lo, hi));
403     }
404
405     if (inst instanceof JVMArrayElementInstruction)
406       return(getArrayValue(ti, type));
407
408     return(null);
409   }
410
411   private final static String decodeValue(byte type, int lo, int hi) {
412     switch (type) {
413       case Types.T_ARRAY:   return(null);
414       case Types.T_VOID:    return(null);
415
416       case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
417       case Types.T_BYTE:    return(String.valueOf(lo));
418       case Types.T_CHAR:    return(String.valueOf((char) lo));
419       case Types.T_DOUBLE:  return(String.valueOf(Types.intsToDouble(lo, hi)));
420       case Types.T_FLOAT:   return(String.valueOf(Types.intToFloat(lo)));
421       case Types.T_INT:     return(String.valueOf(lo));
422       case Types.T_LONG:    return(String.valueOf(Types.intsToLong(lo, hi)));
423       case Types.T_SHORT:   return(String.valueOf(lo));
424
425       case Types.T_REFERENCE:
426         ElementInfo ei = VM.getVM().getHeap().get(lo);
427         if (ei == null)
428           return(null);
429
430         ClassInfo ci = ei.getClassInfo();
431         if (ci == null)
432           return(null);
433
434         if (ci.getName().equals("java.lang.String"))
435           return('"' + ei.asString() + '"');
436
437         return(ei.toString());
438
439       default:
440         System.err.println("Unknown type: " + type);
441         return(null);
442      }
443   }
444
445   private String getArrayValue(ThreadInfo ti, byte type) {
446     StackFrame frame;
447     int lo, hi;
448
449     frame = ti.getTopFrame();
450     lo    = frame.peek();
451     hi    = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
452
453     return(decodeValue(type, lo, hi));
454   }
455
456   private byte getType(ThreadInfo ti, Instruction inst) {
457     StackFrame frame;
458     FieldInfo fi;
459     String type;
460
461     frame = ti.getTopFrame();
462     if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
463       return (Types.T_REFERENCE);
464     }
465
466     type = null;
467
468     if (inst instanceof JVMLocalVariableInstruction) {
469       type = ((JVMLocalVariableInstruction) inst).getLocalVariableType();
470     } else if (inst instanceof JVMFieldInstruction){
471       fi = ((JVMFieldInstruction) inst).getFieldInfo();
472       type = fi.getType();
473     }
474
475     if (inst instanceof JVMArrayElementInstruction) {
476       return (getTypeFromInstruction(inst));
477     }
478
479     if (type == null) {
480       return (Types.T_VOID);
481     }
482
483     return (decodeType(type));
484   }
485
486   private final static byte getTypeFromInstruction(Instruction inst) {
487     if (inst instanceof JVMArrayElementInstruction)
488       return(getTypeFromInstruction((JVMArrayElementInstruction) inst));
489
490     return(Types.T_VOID);
491   }
492
493   private final static byte decodeType(String type) {
494     if (type.charAt(0) == '?'){
495       return(Types.T_REFERENCE);
496     } else {
497       return Types.getBuiltinType(type);
498     }
499   }
500
501   // Find the variable writer
502   // It should be one of the apps listed in the .jpf file
503   private String getWriter(List<StackFrame> sfList, HashSet<String> writerSet) {
504     // Start looking from the top of the stack backward
505     for(int i=sfList.size()-1; i>=0; i--) {
506       MethodInfo mi = sfList.get(i).getMethodInfo();
507       if(!mi.isJPFInternal()) {
508         String method = mi.getStackTraceName();
509         // Check against the writers in the writerSet
510         for(String writer : writerSet) {
511           if (method.contains(writer)) {
512             return writer;
513           }
514         }
515       }
516     }
517
518     return null;
519   }
520
521   @Override
522   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
523     // Instantiate timeoutTimer
524     if (timeout > 0) {
525       if (System.currentTimeMillis() - startTime > timeout) {
526         StringBuilder sbTimeOut = new StringBuilder();
527         sbTimeOut.append("Execution timeout: " + (timeout / (60 * 1000)) + " minutes have passed!");
528         Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sbTimeOut.toString());
529         ti.setNextPC(nextIns);
530       }
531     }
532
533     if (conflictFound) {
534       StringBuilder sb = new StringBuilder();
535       sb.append("Conflict found between two apps!");
536       Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sb.toString());
537       ti.setNextPC(nextIns);
538     } else if (executedInsn instanceof WriteInstruction) {
539       String varId = ((WriteInstruction) executedInsn).getFieldInfo().getFullName();
540       for(String var : conflictSet) {
541
542         if (varId.contains(var)) {
543           // Get variable info
544           byte type  = getType(ti, executedInsn);
545           String value = getValue(ti, executedInsn, type);
546           String writer = getWriter(ti.getStack(), appSet);
547           boolean isManual = false;
548           // Just return if the writer is not one of the listed apps in the .jpf file
549           if (writer == null)
550             return;
551
552           if (getWriter(ti.getStack(), manualSet) != null)
553                 isManual = true;
554
555           // Update the temporary Set set.
556           if (writer.equals("App1"))
557                   tempSetSet.add(new NameValuePair(1, value, var, isManual));
558           else if (writer.equals("App2"))
559                   tempSetSet.add(new NameValuePair(2, value, var, isManual));
560           // Set isSet to true
561           isSet = true;
562                   
563        }
564      }
565
566       
567   }
568   
569  }
570
571 }