Updating main.jpf; Cleaning up the StateReducer.
[jpf-core.git] / src / main / gov / nasa / jpf / listener / VarRecorder.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.JVMArrayElementInstruction;
23 import gov.nasa.jpf.jvm.bytecode.ArrayLoadInstruction;
24 import gov.nasa.jpf.jvm.bytecode.JVMFieldInstruction;
25 import gov.nasa.jpf.jvm.bytecode.JVMLocalVariableInstruction;
26 import gov.nasa.jpf.vm.bytecode.StoreInstruction;
27 import gov.nasa.jpf.vm.bytecode.LocalVariableInstruction;
28 import gov.nasa.jpf.util.StringSetMatcher;
29 import gov.nasa.jpf.vm.ClassInfo;
30 import gov.nasa.jpf.vm.ElementInfo;
31 import gov.nasa.jpf.vm.FieldInfo;
32 import gov.nasa.jpf.vm.Instruction;
33 import gov.nasa.jpf.vm.MJIEnv;
34 import gov.nasa.jpf.vm.VM;
35 import gov.nasa.jpf.vm.MethodInfo;
36 import gov.nasa.jpf.vm.StackFrame;
37 import gov.nasa.jpf.vm.Step;
38 import gov.nasa.jpf.vm.ThreadInfo;
39 import gov.nasa.jpf.vm.Types;
40
41 import java.util.HashMap;
42
43 /**
44  * Simple listener tool to record the values of variables as they are accessed.
45  * Records the information in Step.setComment() so that when the trace is
46  * written the values can be written too.
47  */
48 public class VarRecorder extends ListenerAdapter {
49
50   private final HashMap<ThreadInfo, String> pendingComment = new HashMap<ThreadInfo, String>();
51
52   private final StringSetMatcher includes;
53   private final StringSetMatcher excludes;
54   private final boolean recordFields;
55   private final boolean recordLocals;
56   private final boolean recordArrays;
57
58   private ClassInfo lastClass;
59   private boolean recordClass;
60
61   public VarRecorder(Config config) {
62     includes = StringSetMatcher.getNonEmpty(config.getStringArray("var_recorder.include"));
63     excludes = StringSetMatcher.getNonEmpty(config.getStringArray("var_recorder.exclude"));
64     recordFields = config.getBoolean("var_recorder.fields", true);
65     recordLocals = config.getBoolean("var_recorder.locals", true);
66     recordArrays = config.getBoolean("var_recorder.arrays", true);
67   }
68
69   @Override
70   public void executeInstruction(VM vm, ThreadInfo ti, Instruction insnToExecute) {
71     String name, value;
72     byte type;
73
74     if (!canRecord(vm, insnToExecute))
75       return;
76
77     if (!(insnToExecute instanceof StoreInstruction))
78       if (!(insnToExecute instanceof ArrayLoadInstruction))
79         return;
80
81     type = getType(ti, insnToExecute);
82     name = getName(ti, insnToExecute, type);
83
84     if (insnToExecute instanceof ArrayLoadInstruction) {
85       setComment(vm, ti, name, "", "", true);
86       saveVariableType(ti, type);
87     } else {
88       value = getValue(ti, insnToExecute, type);
89       setComment(vm, ti, name, " <- ", value, true);
90     }
91   }
92
93   @Override
94   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
95     String name, value;
96     byte type;
97
98     if (!canRecord(vm, executedInsn))
99       return;
100
101     if (executedInsn instanceof StoreInstruction) {
102       name = pendingComment.remove(ti);
103       setComment(vm, ti, name, "", "", false);
104       return;
105     }
106
107     type  = getType(ti, executedInsn);
108     value = getValue(ti, executedInsn, type);
109
110     if (executedInsn instanceof ArrayLoadInstruction)
111       name = pendingComment.remove(ti);
112     else
113       name = getName(ti, executedInsn, type);
114
115     if (isArrayReference(vm, ti))
116       saveVariableName(ti, name);
117     else
118       saveVariableType(ti, type);
119
120     setComment(vm, ti, name, " -> ", value, false);
121   }
122
123   private final void setComment(VM vm, ThreadInfo ti, String name, String operator, String value, boolean pending) {
124     Step step;
125     String comment;
126
127     if (name == null)
128       return;
129
130     if (value == null)
131       return;
132
133     comment = name + operator + value;
134
135     if (pending) {
136       pendingComment.put(ti, comment);
137     } else {
138       step = vm.getLastStep();
139       step.setComment(name + operator + value);
140     }
141   }
142
143   private final boolean canRecord(VM vm, Instruction inst) {
144     ClassInfo ci;
145     MethodInfo mi;
146
147     if (vm.getLastStep() == null)
148       return(false);
149
150     if (!(inst instanceof LocalVariableInstruction))
151       if (!(inst instanceof JVMArrayElementInstruction))
152         return(false);
153
154     mi   = inst.getMethodInfo();
155     if (mi == null)
156       return(false);
157
158     ci = mi.getClassInfo();
159     if (ci == null)
160       return(false);
161
162     if (lastClass != ci) {
163       lastClass   = ci;
164       recordClass = StringSetMatcher.isMatch(ci.getName(), includes, excludes);
165     }
166
167     return(recordClass);
168   }
169
170   // <2do> general purpose listeners should not use anonymous attribute types such as String
171   
172   private final void saveVariableName(ThreadInfo ti, String name) {
173     StackFrame frame = ti.getModifiableTopFrame();
174     frame.addOperandAttr(name);
175   }
176
177   private final void saveVariableType(ThreadInfo ti, byte type) {
178     StackFrame frame;
179     String str;
180
181     frame = ti.getModifiableTopFrame();
182     if (frame.getTopPos() < 0) {
183       return;
184     }
185
186     str = encodeType(type);
187     frame.addOperandAttr(str);
188   }
189
190   private final boolean isArrayReference(VM vm, ThreadInfo ti) {
191     StackFrame frame = ti.getTopFrame();
192
193     if (frame.getTopPos() < 0) {
194       return(false);
195     }
196
197     if (!frame.isOperandRef()) {
198       return(false);
199     }
200
201     int objRef = frame.peek();
202     if (objRef == MJIEnv.NULL) {
203       return(false);
204     }
205
206     ElementInfo ei = ti.getElementInfo(objRef);
207     if (ei == null) {
208       return(false);
209     }
210
211     return(ei.isArray());
212   }
213
214   private byte getType(ThreadInfo ti, Instruction inst) {
215     StackFrame frame;
216     FieldInfo fi;
217     String type;
218
219     frame = ti.getTopFrame();
220     if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
221       return (Types.T_REFERENCE);
222     }
223
224     type = null;
225
226     if (((recordLocals) && (inst instanceof JVMLocalVariableInstruction))
227             || ((recordFields) && (inst instanceof JVMFieldInstruction))) {
228       if (inst instanceof JVMLocalVariableInstruction) {
229         type = ((JVMLocalVariableInstruction) inst).getLocalVariableType();
230       } else {
231         fi = ((JVMFieldInstruction) inst).getFieldInfo();
232         type = fi.getType();
233       }
234     }
235
236     if ((recordArrays) && (inst instanceof JVMArrayElementInstruction)) {
237       return (getTypeFromInstruction(inst));
238     }
239
240     if (type == null) {
241       return (Types.T_VOID);
242     }
243
244     return (decodeType(type));
245   }
246
247   private final static byte getTypeFromInstruction(Instruction inst) {
248     if (inst instanceof JVMArrayElementInstruction)
249       return(getTypeFromInstruction((JVMArrayElementInstruction) inst));
250
251     return(Types.T_VOID);
252   }
253
254   private final static byte getTypeFromInstruction(JVMArrayElementInstruction inst) {
255     String name;
256
257     name = inst.getClass().getName();
258     name = name.substring(name.lastIndexOf('.') + 1);
259
260     switch (name.charAt(0)) {
261       case 'A': return(Types.T_REFERENCE);
262       case 'B': return(Types.T_BYTE);      // Could be a boolean but it is better to assume a byte.
263       case 'C': return(Types.T_CHAR);
264       case 'F': return(Types.T_FLOAT);
265       case 'I': return(Types.T_INT);
266       case 'S': return(Types.T_SHORT);
267       case 'D': return(Types.T_DOUBLE);
268       case 'L': return(Types.T_LONG);
269     }
270
271     return(Types.T_VOID);
272   }
273
274   private final static String encodeType(byte type) {
275     switch (type) {
276       case Types.T_BYTE:    return("B");
277       case Types.T_CHAR:    return("C");
278       case Types.T_DOUBLE:  return("D");
279       case Types.T_FLOAT:   return("F");
280       case Types.T_INT:     return("I");
281       case Types.T_LONG:    return("J");
282       case Types.T_REFERENCE:  return("L");
283       case Types.T_SHORT:   return("S");
284       case Types.T_VOID:    return("V");
285       case Types.T_BOOLEAN: return("Z");
286       case Types.T_ARRAY:   return("[");
287     }
288
289     return("?");
290   }
291
292   private final static byte decodeType(String type) {
293     if (type.charAt(0) == '?'){
294       return(Types.T_REFERENCE);
295     } else {
296       return Types.getBuiltinType(type);
297     }
298   }
299
300   private String getName(ThreadInfo ti, Instruction inst, byte type) {
301     String name;
302     int index;
303     boolean store;
304
305     if (((recordLocals) && (inst instanceof JVMLocalVariableInstruction)) ||
306         ((recordFields) && (inst instanceof JVMFieldInstruction))) {
307       name = ((LocalVariableInstruction) inst).getVariableId();
308       name = name.substring(name.lastIndexOf('.') + 1);
309
310       return(name);
311     }
312
313     if ((recordArrays) && (inst instanceof JVMArrayElementInstruction)) {
314       store  = inst instanceof StoreInstruction;
315       name   = getArrayName(ti, type, store);
316       index  = getArrayIndex(ti, type, store);
317       return(name + '[' + index + ']');
318     }
319
320     return(null);
321   }
322
323   private String getValue(ThreadInfo ti, Instruction inst, byte type) {
324     StackFrame frame;
325     int lo, hi;
326
327     frame = ti.getTopFrame();
328
329     if (((recordLocals) && (inst instanceof JVMLocalVariableInstruction)) ||
330         ((recordFields) && (inst instanceof JVMFieldInstruction)))
331     {
332        if (frame.getTopPos() < 0)
333          return(null);
334
335        lo = frame.peek();
336        hi = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
337
338        return(decodeValue(type, lo, hi));
339     }
340
341     if ((recordArrays) && (inst instanceof JVMArrayElementInstruction))
342       return(getArrayValue(ti, type));
343
344     return(null);
345   }
346
347   private String getArrayName(ThreadInfo ti, byte type, boolean store) {
348     String attr;
349     int offset;
350
351     offset = calcOffset(type, store) + 1;
352     // <2do> String is really not a good attribute type to retrieve!
353     StackFrame frame = ti.getTopFrame();
354     attr   = frame.getOperandAttr( offset, String.class); 
355
356     if (attr != null) {
357       return(attr);
358     }
359
360     return("?");
361   }
362
363   private int getArrayIndex(ThreadInfo ti, byte type, boolean store) {
364     int offset;
365
366     offset = calcOffset(type, store);
367
368     return(ti.getTopFrame().peek(offset));
369   }
370
371   private final static int calcOffset(byte type, boolean store) {
372     if (!store)
373       return(0);
374
375     return(Types.getTypeSize(type));
376   }
377
378   private String getArrayValue(ThreadInfo ti, byte type) {
379     StackFrame frame;
380     int lo, hi;
381
382     frame = ti.getTopFrame();
383     lo    = frame.peek();
384     hi    = frame.getTopPos() >= 1 ? frame.peek(1) : 0;
385
386     return(decodeValue(type, lo, hi));
387   }
388
389   private final static String decodeValue(byte type, int lo, int hi) {
390     switch (type) {
391       case Types.T_ARRAY:   return(null);
392       case Types.T_VOID:    return(null);
393
394       case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
395       case Types.T_BYTE:    return(String.valueOf(lo));
396       case Types.T_CHAR:    return(String.valueOf((char) lo));
397       case Types.T_DOUBLE:  return(String.valueOf(Types.intsToDouble(lo, hi)));
398       case Types.T_FLOAT:   return(String.valueOf(Types.intToFloat(lo)));
399       case Types.T_INT:     return(String.valueOf(lo));
400       case Types.T_LONG:    return(String.valueOf(Types.intsToLong(lo, hi)));
401       case Types.T_SHORT:   return(String.valueOf(lo));
402
403       case Types.T_REFERENCE:
404         ElementInfo ei = VM.getVM().getHeap().get(lo);
405         if (ei == null)
406           return(null);
407
408         ClassInfo ci = ei.getClassInfo();
409         if (ci == null)
410           return(null);
411
412         if (ci.getName().equals("java.lang.String"))
413           return('"' + ei.asString() + '"');
414
415         return(ei.toString());
416
417       default:
418         System.err.println("Unknown type: " + type);
419         return(null);
420      }
421   }
422 }