Adding a timeout feature.
[jpf-core.git] / src / main / gov / nasa / jpf / listener / VariableConflictTracker.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.ListenerAdapter;
22 import gov.nasa.jpf.jvm.bytecode.*;
23 import gov.nasa.jpf.vm.*;
24 import gov.nasa.jpf.vm.bytecode.LocalVariableInstruction;
25 import gov.nasa.jpf.vm.bytecode.ReadInstruction;
26 import gov.nasa.jpf.vm.bytecode.StoreInstruction;
27 import gov.nasa.jpf.vm.bytecode.WriteInstruction;
28
29 import java.util.*;
30
31 // TODO: Fix for Groovy's model-checking
32 // TODO: This is a listener created to detect device conflicts and global variable conflicts
33 /**
34  * Simple listener tool to track conflicts between 2 apps.
35  * A conflict is defined as one app trying to change the state of a variable
36  * into its opposite value after being set by the other app,
37  * e.g., app1 attempts to change variable A to false after A has been set by app2 to true earlier.
38  */
39 public class VariableConflictTracker extends ListenerAdapter {
40
41   private final HashMap<String, VarChange> writeMap = new HashMap<>();
42   private final HashMap<String, VarChange> readMap = new HashMap<>();
43   private final HashSet<String> conflictSet = new HashSet<>();
44   private final HashSet<String> appSet = new HashSet<>();
45   private boolean trackLocationVar;
46   private int timeout;
47   private Timer timeoutTimer;
48
49   private final String SET_LOCATION_METHOD = "setLocationMode";
50   private final String LOCATION_VAR = "location.mode";
51
52   public VariableConflictTracker(Config config) {
53     String[] conflictVars = config.getStringArray("variables");
54     // We are not tracking anything if it is null
55     if (conflictVars != null) {
56       for (String var : conflictVars) {
57         conflictSet.add(var);
58       }
59     }
60     String[] apps = config.getStringArray("apps");
61     // We are not tracking anything if it is null
62     if (apps != null) {
63       for (String var : apps) {
64         appSet.add(var);
65       }
66     }
67     trackLocationVar = config.getBoolean("track_location_var_conflict", false);
68     // Timeout is in minutes
69     timeout = config.getInt("timeout", 0);
70     timeoutTimer = null;
71   }
72
73   // Create a task for timer to do a timeout
74   private class TimeoutTask extends TimerTask {
75
76     ThreadInfo ti;
77
78     public TimeoutTask(ThreadInfo ti) {
79       this.ti = ti;
80     }
81
82     @Override
83     public void run() {
84       StringBuilder sb = new StringBuilder();
85       sb.append("Execution timeout!\n");
86       Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sb.toString());
87       ti.setNextPC(nextIns);
88     }
89   }
90
91   @Override
92   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
93     // Instantiate timeoutTimer
94     if (timeoutTimer == null && timeout > 0) {
95       ThreadList threads = vm.getThreadList();
96       ThreadInfo mainThread = threads.getThreadInfoForId(0);
97       timeoutTimer = new Timer();
98       timeoutTimer.schedule(new TimeoutTask(mainThread), timeout * 60 * 1000);
99     }
100
101     // CASE #1: Detecting variable write-after-write conflict
102     if (executedInsn instanceof WriteInstruction) {
103       // Check for write-after-write conflict
104       String varId = ((WriteInstruction) executedInsn).getFieldInfo().getFullName();
105       for(String var : conflictSet) {
106
107         if (varId.contains(var)) {
108           // Get variable info
109           byte type  = getType(ti, executedInsn);
110           String value = getValue(ti, executedInsn, type);
111           //System.out.println("\n\n" + ti.getStackTrace() + "\n\n");
112           String writer = getWriter(ti.getStack());
113           // Just return if the writer is not one of the listed apps in the .jpf file
114           if (writer == null)
115             return;
116
117           // Check and throw error if conflict is detected
118           checkWriteMapAndThrowError(var, value, writer, ti);
119         }
120       }
121     }
122
123     // CASE #2: Detecting global variable location.mode write-after-write conflict
124     if (trackLocationVar) {
125       MethodInfo mi = executedInsn.getMethodInfo();
126       // Find the last load before return and get the value here
127       if (mi.getName().equals(SET_LOCATION_METHOD) &&
128               executedInsn instanceof ALOAD && nextInsn instanceof ARETURN) {
129         byte type  = getType(ti, executedInsn);
130         String value = getValue(ti, executedInsn, type);
131
132         // Extract the writer app name
133         ClassInfo ci = mi.getClassInfo();
134         String writer = ci.getName();
135
136         // Check and throw error if conflict is detected
137         checkWriteMapAndThrowError(LOCATION_VAR, value, writer, ti);
138       }
139     }
140   }
141
142   private void checkWriteMapAndThrowError(String var, String value, String writer, ThreadInfo ti) {
143
144     if (writeMap.containsKey(var)) {
145       // Subsequent writes to the variable
146       VarChange current = writeMap.get(var);
147       if (current.writer != writer) {
148         // Conflict is declared when:
149         // 1) Current writer != previous writer, e.g., App1 vs. App2
150         // 2) Current value != previous value, e.g., "locked" vs. "unlocked"
151         if (!current.value.equals(value)) {
152
153           StringBuilder sb = new StringBuilder();
154           sb.append("Conflict between apps " + current.writer + " and " + writer + ": ");
155           sb.append(writer + " has attempted to write the value " + value + " into ");
156           sb.append("variable " + var + " that had already had the value " + current.value);
157           sb.append(" previously written by " + current.writer);
158           Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sb.toString());
159           ti.setNextPC(nextIns);
160         }
161       } else {
162         // No conflict is declared if this variable is written subsequently by the same writer
163         current.writer = writer;
164         current.value = value;
165       }
166     } else {
167       // First write to the variable
168       VarChange change = new VarChange(writer, value);
169       writeMap.put(var, change);
170     }
171   }
172
173   class VarChange {
174     String writer;
175     String value;
176     
177     VarChange(String writer, String value) {
178       this.writer = writer;
179       this.value = value;
180     }
181   }
182
183   // Find the variable writer
184   // It should be one of the apps listed in the .jpf file
185   private String getWriter(List<StackFrame> sfList) {
186     // Start looking from the top of the stack backward
187     for(int i=sfList.size()-1; i>=0; i--) {
188       MethodInfo mi = sfList.get(i).getMethodInfo();
189       if(!mi.isJPFInternal()) {
190         String method = mi.getStackTraceName();
191         // Check against the apps in the appSet
192         for(String app : appSet) {
193           // There is only one writer at a time but we need to always
194           // check all the potential writers in the list
195           if (method.contains(app)) {
196             return app;
197           }
198         }
199       }
200     }
201
202     return null;
203   }
204
205   private byte getType(ThreadInfo ti, Instruction inst) {
206     StackFrame frame;
207     FieldInfo fi;
208     String type;
209
210     frame = ti.getTopFrame();
211     if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
212       return (Types.T_REFERENCE);
213     }
214
215     type = null;
216
217     if (inst instanceof JVMLocalVariableInstruction) {
218       type = ((JVMLocalVariableInstruction) inst).getLocalVariableType();
219     } else if (inst instanceof JVMFieldInstruction){
220       fi = ((JVMFieldInstruction) inst).getFieldInfo();
221       type = fi.getType();
222     }
223
224     if (inst instanceof JVMArrayElementInstruction) {
225       return (getTypeFromInstruction(inst));
226     }
227
228     if (type == null) {
229       return (Types.T_VOID);
230     }
231
232     return (decodeType(type));
233   }
234
235   private final static byte getTypeFromInstruction(Instruction inst) {
236     if (inst instanceof JVMArrayElementInstruction)
237       return(getTypeFromInstruction((JVMArrayElementInstruction) inst));
238
239     return(Types.T_VOID);
240   }
241
242   private final static byte getTypeFromInstruction(JVMArrayElementInstruction inst) {
243     String name;
244
245     name = inst.getClass().getName();
246     name = name.substring(name.lastIndexOf('.') + 1);
247
248     switch (name.charAt(0)) {
249       case 'A': return(Types.T_REFERENCE);
250       case 'B': return(Types.T_BYTE);      // Could be a boolean but it is better to assume a byte.
251       case 'C': return(Types.T_CHAR);
252       case 'F': return(Types.T_FLOAT);
253       case 'I': return(Types.T_INT);
254       case 'S': return(Types.T_SHORT);
255       case 'D': return(Types.T_DOUBLE);
256       case 'L': return(Types.T_LONG);
257     }
258
259     return(Types.T_VOID);
260   }
261
262   private final static String encodeType(byte type) {
263     switch (type) {
264       case Types.T_BYTE:    return("B");
265       case Types.T_CHAR:    return("C");
266       case Types.T_DOUBLE:  return("D");
267       case Types.T_FLOAT:   return("F");
268       case Types.T_INT:     return("I");
269       case Types.T_LONG:    return("J");
270       case Types.T_REFERENCE:  return("L");
271       case Types.T_SHORT:   return("S");
272       case Types.T_VOID:    return("V");
273       case Types.T_BOOLEAN: return("Z");
274       case Types.T_ARRAY:   return("[");
275     }
276
277     return("?");
278   }
279
280   private final static byte decodeType(String type) {
281     if (type.charAt(0) == '?'){
282       return(Types.T_REFERENCE);
283     } else {
284       return Types.getBuiltinType(type);
285     }
286   }
287
288   private String getName(ThreadInfo ti, Instruction inst, byte type) {
289     String name;
290     int index;
291     boolean store;
292
293     if ((inst instanceof JVMLocalVariableInstruction) ||
294             (inst instanceof JVMFieldInstruction)) {
295       name = ((LocalVariableInstruction) inst).getVariableId();
296       name = name.substring(name.lastIndexOf('.') + 1);
297
298       return(name);
299     }
300
301     if (inst instanceof JVMArrayElementInstruction) {
302       store  = inst instanceof StoreInstruction;
303       name   = getArrayName(ti, type, store);
304       index  = getArrayIndex(ti, type, store);
305       return(name + '[' + index + ']');
306     }
307
308     return(null);
309   }
310
311   private String getValue(ThreadInfo ti, Instruction inst, byte type) {
312     StackFrame frame;
313     int lo, hi;
314
315     frame = ti.getTopFrame();
316
317     if ((inst instanceof JVMLocalVariableInstruction) ||
318         (inst instanceof JVMFieldInstruction))
319     {
320        if (frame.getTopPos() < 0)
321          return(null);
322
323        lo = frame.peek();
324        hi = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
325
326        return(decodeValue(type, lo, hi));
327     }
328
329     if (inst instanceof JVMArrayElementInstruction)
330       return(getArrayValue(ti, type));
331
332     return(null);
333   }
334
335   private String getArrayName(ThreadInfo ti, byte type, boolean store) {
336     String attr;
337     int offset;
338
339     offset = calcOffset(type, store) + 1;
340     // <2do> String is really not a good attribute type to retrieve!
341     StackFrame frame = ti.getTopFrame();
342     attr   = frame.getOperandAttr( offset, String.class);
343
344     if (attr != null) {
345       return(attr);
346     }
347
348     return("?");
349   }
350
351   private int getArrayIndex(ThreadInfo ti, byte type, boolean store) {
352     int offset;
353
354     offset = calcOffset(type, store);
355
356     return(ti.getTopFrame().peek(offset));
357   }
358
359   private final static int calcOffset(byte type, boolean store) {
360     if (!store)
361       return(0);
362
363     return(Types.getTypeSize(type));
364   }
365
366   private String getArrayValue(ThreadInfo ti, byte type) {
367     StackFrame frame;
368     int lo, hi;
369
370     frame = ti.getTopFrame();
371     lo    = frame.peek();
372     hi    = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
373
374     return(decodeValue(type, lo, hi));
375   }
376
377   private final static String decodeValue(byte type, int lo, int hi) {
378     switch (type) {
379       case Types.T_ARRAY:   return(null);
380       case Types.T_VOID:    return(null);
381
382       case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
383       case Types.T_BYTE:    return(String.valueOf(lo));
384       case Types.T_CHAR:    return(String.valueOf((char) lo));
385       case Types.T_DOUBLE:  return(String.valueOf(Types.intsToDouble(lo, hi)));
386       case Types.T_FLOAT:   return(String.valueOf(Types.intToFloat(lo)));
387       case Types.T_INT:     return(String.valueOf(lo));
388       case Types.T_LONG:    return(String.valueOf(Types.intsToLong(lo, hi)));
389       case Types.T_SHORT:   return(String.valueOf(lo));
390
391       case Types.T_REFERENCE:
392         ElementInfo ei = VM.getVM().getHeap().get(lo);
393         if (ei == null)
394           return(null);
395
396         ClassInfo ci = ei.getClassInfo();
397         if (ci == null)
398           return(null);
399
400         if (ci.getName().equals("java.lang.String"))
401           return('"' + ei.asString() + '"');
402
403         return(ei.toString());
404
405       default:
406         System.err.println("Unknown type: " + type);
407         return(null);
408      }
409   }
410 }