Adding location.mode variable conflict tracker.
[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.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32
33 // TODO: Fix for Groovy's model-checking
34 // TODO: This is a listener created to detect device conflicts and global variable conflicts
35 /**
36  * Simple listener tool to track conflicts between 2 apps.
37  * A conflict is defined as one app trying to change the state of a variable
38  * into its opposite value after being set by the other app,
39  * e.g., app1 attempts to change variable A to false after A has been set by app2 to true earlier.
40  */
41 public class VariableConflictTracker extends ListenerAdapter {
42
43   private final HashMap<String, VarChange> writeMap = new HashMap<>();
44   private final HashMap<String, VarChange> readMap = new HashMap<>();
45   private final HashSet<String> conflictSet = new HashSet<>();
46   private final HashSet<String> appSet = new HashSet<>();
47   private boolean trackLocationVar;
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   }
69
70   @Override
71   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
72
73     // CASE #1: Detecting variable write-after-write conflict
74     if (executedInsn instanceof WriteInstruction) {
75       // Check for write-after-write conflict
76       String varId = ((WriteInstruction) executedInsn).getFieldInfo().getFullName();
77       for(String var : conflictSet) {
78
79         if (varId.contains(var)) {
80           // Get variable info
81           byte type  = getType(ti, executedInsn);
82           String value = getValue(ti, executedInsn, type);
83           String writer = getWriter(ti.getStack());
84           // Just return if the writer is not one of the listed apps in the .jpf file
85           if (writer == null)
86             return;
87
88           // Check and throw error if conflict is detected
89           checkWriteMapAndThrowError(var, value, writer, ti);
90         }
91       }
92     }
93
94     // CASE #2: Detecting global variable location.mode write-after-write conflict
95     if (trackLocationVar) {
96       MethodInfo mi = executedInsn.getMethodInfo();
97       // Find the last load before return and get the value here
98       if (mi.getName().equals(SET_LOCATION_METHOD) &&
99               executedInsn instanceof ALOAD && nextInsn instanceof ARETURN) {
100         byte type  = getType(ti, executedInsn);
101         String value = getValue(ti, executedInsn, type);
102
103         // Extract the writer app name
104         ClassInfo ci = mi.getClassInfo();
105         String writer = ci.getName();
106
107         // Check and throw error if conflict is detected
108         checkWriteMapAndThrowError(LOCATION_VAR, value, writer, ti);
109       }
110     }
111   }
112   
113   private void checkWriteMapAndThrowError(String var, String value, String writer, ThreadInfo ti) {
114     
115     if (writeMap.containsKey(var)) {
116       // Subsequent writes to the variable
117       VarChange current = writeMap.get(var);
118       if (current.writer != writer) {
119         // Conflict is declared when:
120         // 1) Current writer != previous writer, e.g., App1 vs. App2
121         // 2) Current value != previous value, e.g., "locked" vs. "unlocked"
122         if (!current.value.equals(value)) {
123
124           StringBuilder sb = new StringBuilder();
125           sb.append("Conflict between apps " + current.writer + " and " + writer + ": ");
126           sb.append(writer + " has attempted to write the value " + value + " into ");
127           sb.append("variable " + var + " that had already had the value " + current.value);
128           sb.append(" previously written by " + current.writer);
129           Instruction nextIns = ti.createAndThrowException("java.lang.RuntimeException", sb.toString());
130           ti.setNextPC(nextIns);
131         }
132       } else {
133         // No conflict is declared if this variable is written subsequently by the same writer
134         current.writer = writer;
135         current.value = value;
136       }
137     } else {
138       // First write to the variable
139       VarChange change = new VarChange(writer, value);
140       writeMap.put(var, change);
141     }
142   }
143
144   class VarChange {
145     String writer;
146     String value;
147     
148     VarChange(String writer, String value) {
149       this.writer = writer;
150       this.value = value;
151     }
152   }
153
154   // Find the variable writer
155   // It should be one of the apps listed in the .jpf file
156   private String getWriter(List<StackFrame> sfList) {
157
158     for(StackFrame sf : sfList) {
159       MethodInfo mi = sf.getMethodInfo();
160       if(!mi.isJPFInternal()) {
161         String method = mi.getStackTraceName();
162         // Check against the apps in the appSet
163         for(String app : appSet) {
164           if (method.contains(app)) {
165             return app;
166           }
167         }
168       }
169     }
170
171     return null;
172   }
173
174   private byte getType(ThreadInfo ti, Instruction inst) {
175     StackFrame frame;
176     FieldInfo fi;
177     String type;
178
179     frame = ti.getTopFrame();
180     if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
181       return (Types.T_REFERENCE);
182     }
183
184     type = null;
185
186     if (inst instanceof JVMLocalVariableInstruction) {
187       type = ((JVMLocalVariableInstruction) inst).getLocalVariableType();
188     } else if (inst instanceof JVMFieldInstruction){
189       fi = ((JVMFieldInstruction) inst).getFieldInfo();
190       type = fi.getType();
191     }
192
193     if (inst instanceof JVMArrayElementInstruction) {
194       return (getTypeFromInstruction(inst));
195     }
196
197     if (type == null) {
198       return (Types.T_VOID);
199     }
200
201     return (decodeType(type));
202   }
203
204   private final static byte getTypeFromInstruction(Instruction inst) {
205     if (inst instanceof JVMArrayElementInstruction)
206       return(getTypeFromInstruction((JVMArrayElementInstruction) inst));
207
208     return(Types.T_VOID);
209   }
210
211   private final static byte getTypeFromInstruction(JVMArrayElementInstruction inst) {
212     String name;
213
214     name = inst.getClass().getName();
215     name = name.substring(name.lastIndexOf('.') + 1);
216
217     switch (name.charAt(0)) {
218       case 'A': return(Types.T_REFERENCE);
219       case 'B': return(Types.T_BYTE);      // Could be a boolean but it is better to assume a byte.
220       case 'C': return(Types.T_CHAR);
221       case 'F': return(Types.T_FLOAT);
222       case 'I': return(Types.T_INT);
223       case 'S': return(Types.T_SHORT);
224       case 'D': return(Types.T_DOUBLE);
225       case 'L': return(Types.T_LONG);
226     }
227
228     return(Types.T_VOID);
229   }
230
231   private final static String encodeType(byte type) {
232     switch (type) {
233       case Types.T_BYTE:    return("B");
234       case Types.T_CHAR:    return("C");
235       case Types.T_DOUBLE:  return("D");
236       case Types.T_FLOAT:   return("F");
237       case Types.T_INT:     return("I");
238       case Types.T_LONG:    return("J");
239       case Types.T_REFERENCE:  return("L");
240       case Types.T_SHORT:   return("S");
241       case Types.T_VOID:    return("V");
242       case Types.T_BOOLEAN: return("Z");
243       case Types.T_ARRAY:   return("[");
244     }
245
246     return("?");
247   }
248
249   private final static byte decodeType(String type) {
250     if (type.charAt(0) == '?'){
251       return(Types.T_REFERENCE);
252     } else {
253       return Types.getBuiltinType(type);
254     }
255   }
256
257   private String getValue(ThreadInfo ti, Instruction inst, byte type) {
258     StackFrame frame;
259     int lo, hi;
260
261     frame = ti.getTopFrame();
262
263     if ((inst instanceof JVMLocalVariableInstruction) ||
264         (inst instanceof JVMFieldInstruction))
265     {
266        if (frame.getTopPos() < 0)
267          return(null);
268
269        lo = frame.peek();
270        hi = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
271
272        return(decodeValue(type, lo, hi));
273     }
274
275     return(null);
276   }
277
278   private final static String decodeValue(byte type, int lo, int hi) {
279     switch (type) {
280       case Types.T_ARRAY:   return(null);
281       case Types.T_VOID:    return(null);
282
283       case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
284       case Types.T_BYTE:    return(String.valueOf(lo));
285       case Types.T_CHAR:    return(String.valueOf((char) lo));
286       case Types.T_DOUBLE:  return(String.valueOf(Types.intsToDouble(lo, hi)));
287       case Types.T_FLOAT:   return(String.valueOf(Types.intToFloat(lo)));
288       case Types.T_INT:     return(String.valueOf(lo));
289       case Types.T_LONG:    return(String.valueOf(Types.intsToLong(lo, hi)));
290       case Types.T_SHORT:   return(String.valueOf(lo));
291
292       case Types.T_REFERENCE:
293         ElementInfo ei = VM.getVM().getHeap().get(lo);
294         if (ei == null)
295           return(null);
296
297         ClassInfo ci = ei.getClassInfo();
298         if (ci == null)
299           return(null);
300
301         if (ci.getName().equals("java.lang.String"))
302           return('"' + ei.asString() + '"');
303
304         return(ei.toString());
305
306       default:
307         System.err.println("Unknown type: " + type);
308         return(null);
309      }
310   }
311 }