Fixing a few bugs in the statistics printout.
[jpf-core.git] / src / main / gov / nasa / jpf / listener / Perturbator.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
19 package gov.nasa.jpf.listener;
20
21 import gov.nasa.jpf.Config;
22 import gov.nasa.jpf.JPF;
23 import gov.nasa.jpf.ListenerAdapter;
24 import gov.nasa.jpf.jvm.bytecode.GETFIELD;
25 import gov.nasa.jpf.jvm.bytecode.JVMInstanceFieldInstruction;
26 import gov.nasa.jpf.jvm.bytecode.JVMReturnInstruction;
27 import gov.nasa.jpf.jvm.bytecode.JVMInvokeInstruction;
28 import gov.nasa.jpf.perturb.OperandPerturbator;
29 import gov.nasa.jpf.util.FieldSpec;
30 import gov.nasa.jpf.util.JPFLogger;
31 import gov.nasa.jpf.util.MethodSpec;
32 import gov.nasa.jpf.util.SourceRef;
33 import gov.nasa.jpf.vm.ChoiceGenerator;
34 import gov.nasa.jpf.vm.ClassInfo;
35 import gov.nasa.jpf.vm.FieldInfo;
36 import gov.nasa.jpf.vm.Instruction;
37 import gov.nasa.jpf.vm.VM;
38 import gov.nasa.jpf.vm.MethodInfo;
39 import gov.nasa.jpf.vm.StackFrame;
40 import gov.nasa.jpf.vm.SystemState;
41 import gov.nasa.jpf.vm.ThreadInfo;
42
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.List;
46
47 /**
48  * listener that perturbs GETFIELD/GETSTATIC and JVMInvokeInstruction results
49  *
50  * NOTE - this listener initializes in two steps: (1) during listener construction
51  * it builds a list of classes it has to monitor, and (2) during class load
52  * time it further analyzes classes from this list to get the actual target
53  * objects (FieldInfos and MethodInfos) so that instruction monitoring is
54  * efficient enough.
55  *
56  * This means the listener always has to be instantiated BEFORE the respective
57  * target classes get loaded.
58  *
59  * configuration example:
60  *
61  *   # field getter example
62  *   perturb.fields = altitude,...
63  *   perturb.altitude.field = x.y.MyClass.alt
64  *   perturb.altitude.class = .perturb.IntOverUnder
65  *   perturb.altitude.location = MyClass.java:42
66  *   perturb.altitude.delta = 1
67  *
68  *   # method return value example
69  *   perturb.returns = velocity,...
70  *   perturb.velocity.method = x.y.MyClass.computeVelocity()
71  *   perturb.velocity.class = .perturb.IntOverUnder
72  *   perturb.velocity.delta = 50
73  *   
74  *   # method parameter perturbation example
75  *   perturb.params = foo, ...
76  *   perturb.foo.method = x.y.MyClass.send(int, float, boolean)
77  *   perturb.foo.location = MyClass.java:42
78  *   perturb.class = .perturb.dataAbstractor
79  *   
80  */
81
82 public class Perturbator extends ListenerAdapter {
83
84   static JPFLogger log = JPF.getLogger("gov.nasa.jpf.Perturbator");
85
86   public static class Perturbation {
87     SourceRef sref;    // location where field access should be perturbed
88     Class<? extends ChoiceGenerator<?>> cgType; // needs to be compatible with field type
89     OperandPerturbator perturbator;
90
91     Perturbation (OperandPerturbator perturbator, String loc){
92       this.perturbator = perturbator;
93
94       if (loc != null){
95         sref = new SourceRef(loc);
96       }
97     }
98   }
99
100   public static class FieldPerturbation extends Perturbation {
101     FieldSpec fieldSpec;
102
103     FieldPerturbation (FieldSpec fieldSpec, OperandPerturbator perturbator, String loc){
104       super(perturbator, loc);
105
106       this.fieldSpec = fieldSpec;
107     }
108   }
109
110   public static class ReturnPerturbation extends Perturbation {
111     MethodSpec mthSpec;
112
113     ReturnPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc){
114       super(perturbator, loc);
115
116       this.mthSpec = mthSpec;
117     }
118   }
119   
120   public static class ParamsPerturbation extends Perturbation {
121         public MethodSpec mthSpec;
122         
123         ParamsPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc) {
124                 super(perturbator, loc);
125                 
126                 this.mthSpec = mthSpec;
127         }
128   }
129   
130   protected static Class<?>[] argTypes = { Config.class, String.class };
131
132   protected List<FieldPerturbation> fieldWatchList = new ArrayList<FieldPerturbation>();
133   protected HashMap<FieldInfo,FieldPerturbation> perturbedFields = new HashMap<FieldInfo,FieldPerturbation>();
134
135   protected List<ReturnPerturbation> returnWatchList = new ArrayList<ReturnPerturbation>();
136   protected HashMap<MethodInfo,ReturnPerturbation> perturbedReturns = new HashMap<MethodInfo,ReturnPerturbation>();
137   
138   protected List<ParamsPerturbation> paramsWatchList = new ArrayList<ParamsPerturbation>();
139   protected HashMap<MethodInfo, ParamsPerturbation> perturbedParams = new HashMap<MethodInfo, ParamsPerturbation>();
140
141   protected StackFrame savedFrame;
142   
143   public Perturbator (Config conf){
144
145     // in the ctor we only find out which classname patterns we have to watch
146     // for, and store them in a list (together with their partially initialized
147     // Perturbation instances) that is to be checked upon classLoaded notifications
148
149     // get the configured field perturbators
150     String[] fieldIds = conf.getCompactTrimmedStringArray("perturb.fields");
151     for (String id : fieldIds){
152       addToFieldWatchList(conf, id);
153     }
154
155     String[] returnIds = conf.getCompactTrimmedStringArray("perturb.returns");
156     for (String id : returnIds){
157       addToReturnWatchList(conf, id);
158     }
159     
160     String[] paramsIds = conf.getCompactTrimmedStringArray("perturb.params");
161     for (String id: paramsIds) {
162         addToParamsWatchList(conf, id);
163     }
164   }
165   
166   public boolean isMethodWatched(Instruction insn, MethodInfo mi) {
167     ParamsPerturbation e = perturbedParams.get(mi);
168     if (e != null && isRelevantCallLocation(insn, e)){
169       return true;
170     }
171     return false;
172   }
173   
174   protected void addToFieldWatchList (Config conf, String id){
175     String keyPrefix = "perturb." + id;
176
177     String fs = conf.getString(keyPrefix + ".field");
178     if (fs != null) {
179       FieldSpec fieldSpec = FieldSpec.createFieldSpec(fs);
180       if (fieldSpec != null){
181         Object[] args = {conf, keyPrefix};
182         OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);
183         if (perturbator != null) {
184           String loc = conf.getString(keyPrefix + ".location");
185           FieldPerturbation p = new FieldPerturbation(fieldSpec, perturbator, loc);
186           fieldWatchList.add(p);
187         } else {
188           log.warning("invalid perturbator spec for ", keyPrefix);
189         }
190       } else {
191         log.warning("malformed field specification for ", keyPrefix);
192       }
193
194     } else {
195       log.warning("missing field specification for ", keyPrefix);
196     }
197   }
198
199   protected void addToReturnWatchList (Config conf, String id){
200     String keyPrefix = "perturb." + id;
201
202     String ms = conf.getString(keyPrefix + ".method");
203     if (ms != null) {
204       MethodSpec mthSpec = MethodSpec.createMethodSpec(ms);
205       if (mthSpec != null) {
206         Object[] args = {conf, keyPrefix};
207         OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);
208         if (perturbator != null) {
209           String loc = conf.getString(keyPrefix + ".location");
210           ReturnPerturbation p = new ReturnPerturbation(mthSpec, perturbator, loc);
211           returnWatchList.add(p);
212         } else {
213           log.warning("invalid perturbator spec for ", keyPrefix);
214         }
215
216       } else {
217         log.warning("malformed method specification for ", keyPrefix);
218       }
219
220     } else {
221       log.warning("missing method specification for ", keyPrefix);
222     }
223   }
224
225   protected void addToParamsWatchList (Config conf, String id){
226     String keyPrefix = "perturb." + id;
227
228     String ms = conf.getString(keyPrefix + ".method");
229     if (ms != null) {
230       MethodSpec mthSpec = MethodSpec.createMethodSpec(ms);
231       if (mthSpec != null) {
232         Object[] args = {conf, keyPrefix};
233         OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);        
234         if (perturbator != null) {
235           String loc = conf.getString(keyPrefix + ".location");
236           ParamsPerturbation p = new ParamsPerturbation(mthSpec, perturbator, loc);
237           paramsWatchList.add(p);
238         } else {
239           log.warning("invalid perturbator spec for ", keyPrefix);
240         }
241
242       } else {
243         log.warning("malformed method specification for ", keyPrefix);
244       }
245     } else {
246       log.warning("missing method specification for ", keyPrefix);
247     }
248   }
249
250   @Override
251   public void classLoaded (VM vm, ClassInfo loadedClass){
252     // this one takes the watchlists, finds out if the loaded class matches
253     // any of the watch entries, and in case it does fully initializes
254     // the corresponding Perturbation object with the target construct
255     // (MethodInfo, FieldInfo) we use to identify relevant ops during
256     // instruction execution notifications
257
258     String clsName = loadedClass.getName();
259
260     for (FieldPerturbation p : fieldWatchList){
261       FieldSpec fs = p.fieldSpec;
262       if (fs.isMatchingType(loadedClass)){
263         addFieldPerturbations( p, loadedClass, loadedClass.getDeclaredInstanceFields());
264         addFieldPerturbations( p, loadedClass, loadedClass.getDeclaredStaticFields());
265       }
266     }
267
268     for (ReturnPerturbation p : returnWatchList){
269       MethodSpec ms = p.mthSpec;
270       if (ms.isMatchingType(loadedClass)){
271         for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
272           if (ms.matches(mi)){
273             Class<? extends ChoiceGenerator<?>> returnCGType = mi.getReturnChoiceGeneratorType();
274             Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
275             if (returnCGType.isAssignableFrom(perturbatorCGType)){
276               p.cgType = returnCGType;
277               perturbedReturns.put(mi, p);
278             } else {
279               log.warning("method " + mi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
280             }
281           }
282         }
283       }
284     }
285
286     for (ParamsPerturbation p : paramsWatchList){
287       MethodSpec ms = p.mthSpec;
288       if (ms.isMatchingType(loadedClass)){
289         for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
290           if (ms.matches(mi)){
291                 // We simply associate the method with the parameters perturbator
292             Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
293             p.cgType = perturbatorCGType;
294                 perturbedParams.put(mi, p);
295           }
296         }
297       }
298     }
299   }
300
301   protected void addFieldPerturbations (FieldPerturbation p, ClassInfo ci, FieldInfo[] fieldInfos){
302     for (FieldInfo fi : ci.getDeclaredInstanceFields()) {
303       if (p.fieldSpec.matches(fi)) {
304         Class<? extends ChoiceGenerator<?>> fieldCGType = fi.getChoiceGeneratorType();
305         Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
306         if (fieldCGType.isAssignableFrom(perturbatorCGType)) {
307           p.cgType = fieldCGType;
308           perturbedFields.put(fi, p);
309         } else {
310           log.warning("field " + fi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
311         }
312       }
313     }
314   }
315
316   protected boolean isRelevantCallLocation (ThreadInfo ti, Perturbation p){
317     if (p.sref == null){
318       // no caller location specified -> all calls relevant
319       return true;
320     } else {
321       StackFrame caller = ti.getCallerStackFrame();
322       if (caller != null) {
323         Instruction invokeInsn = caller.getPC();
324         return p.sref.equals(invokeInsn.getFilePos());
325       } else {
326         return false;
327       }
328     }
329   }
330   
331   protected boolean isRelevantCallLocation (Instruction invokeInsn, Perturbation p) {
332         // For parameter perturbation, we are about to enter a method
333         // and hence can directly use the invoke instruction to get the file
334         // location of the call
335         if (p.sref == null)
336                 return true;
337         else
338                 return p.sref.equals(invokeInsn.getFilePos());
339   }
340
341   @Override
342   public void executeInstruction (VM vm, ThreadInfo ti, Instruction insnToExecute){
343     
344     if (insnToExecute instanceof GETFIELD){
345       FieldInfo fi = ((JVMInstanceFieldInstruction)insnToExecute).getFieldInfo();
346       FieldPerturbation e = perturbedFields.get(fi);
347
348       if (e != null) {  // managed field
349         if (isMatchingInstructionLocation(e,insnToExecute)) {
350           if (!ti.isFirstStepInsn()){
351             // save the current stackframe so that we can restore it before
352             // we re-enter
353             savedFrame = ti.getTopFrame().clone();
354           }
355         }
356       }
357
358     } else if (insnToExecute instanceof JVMReturnInstruction){
359       MethodInfo mi = insnToExecute.getMethodInfo();
360       ReturnPerturbation e = perturbedReturns.get(mi);
361
362       if (e != null && isRelevantCallLocation(ti, e)){
363         SystemState ss = vm.getSystemState();
364
365         if (!ti.isFirstStepInsn()){
366           // first time, create & set CG but DO NOT enter the insn since it would
367           // pop the callee stackframe and modify the caller stackframe
368           // note that we don't need to enter in order to get the perturbation base
369           // value because its already on the operand stack
370           ChoiceGenerator<?> cg = e.perturbator.createChoiceGenerator("perturbReturn", ti.getTopFrame(), new Integer(0));
371           if (ss.setNextChoiceGenerator(cg)){
372             ti.skipInstruction(insnToExecute);
373           }
374         } else {
375           // re-executing, modify the operand stack top and enter
376           ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator("perturbReturn", e.cgType);
377           if (cg != null) {
378             e.perturbator.perturb(cg, ti.getTopFrame());
379           }
380         }
381       }
382     } else if (insnToExecute instanceof JVMInvokeInstruction) {
383         // first get the method info object corresponding to the invoked method
384         // We can't use getMethodInfo as the method returned may not be the actual
385         // method invoked, but rather its caller
386         MethodInfo mi = ((JVMInvokeInstruction) insnToExecute).getInvokedMethod();
387         ParamsPerturbation e = perturbedParams.get(mi);
388         
389       if (e != null && isRelevantCallLocation(insnToExecute, e)){
390         SystemState ss = vm.getSystemState();
391
392         if (!ti.isFirstStepInsn()) {
393                 // first time, create and set CG and skip instruction as we want the instruction
394                 // to be executed with the parameter choices we like instead of the ones that
395                 // were passed in
396           ChoiceGenerator<?> cg = e.perturbator.createChoiceGenerator(mi.getFullName(), ti.getTopFrame(), mi);
397           // check if the cg returned is null. If it is then we don't want to enter this
398           // method as we are done exploring it
399           if (cg != null) {
400             log.info("--- Creating choice generator: " + mi.getFullName() + " for thread: " + ti);
401             if (ss.setNextChoiceGenerator(cg)) {
402               ti.skipInstruction(insnToExecute);
403             }
404           }
405         } else {
406           // re-executing, modify the operands on stack and enter
407           ChoiceGenerator<?> cg = ss.getChoiceGenerator(mi.getFullName());
408           if (cg != null) {
409             log.info("--- Using choice generator: " + mi.getFullName() + " in thread: " + ti);
410             e.perturbator.perturb(cg, ti.getTopFrame());
411           }
412         }
413       }
414     }
415   }
416
417   @Override
418   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
419     
420     if (executedInsn instanceof GETFIELD){
421       FieldInfo fi = ((JVMInstanceFieldInstruction)executedInsn).getFieldInfo();
422       FieldPerturbation p = perturbedFields.get(fi);
423       if (p != null){
424         if (isMatchingInstructionLocation(p, executedInsn)) {  // none or managed filePos
425           StackFrame frame = ti.getTopFrame();
426           SystemState ss = vm.getSystemState();
427
428           if (ti.isFirstStepInsn()) { // retrieve value from CG and replace it on operand stack
429             ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator( "perturbGetField", p.cgType);
430             if (cg != null) {
431               p.perturbator.perturb(cg, frame);
432             } else {
433               log.warning("wrong choice generator type ", cg);
434             }
435
436           } else { // first time around, create&set the CG and reexecute
437             ChoiceGenerator<?> cg = p.perturbator.createChoiceGenerator( "perturbGetField", frame, new Integer(0));
438             if (ss.setNextChoiceGenerator(cg)){
439               assert savedFrame != null;
440               // we could more efficiently restore the stackframe
441               // to pre-exec state from last 'this' or classobject ref, but then
442               // we have to deal with different field value sizes
443               ti.setTopFrame(savedFrame);
444               ti.setNextPC(executedInsn); // reexecute
445
446               savedFrame = null;
447             }
448           }
449         }
450       }
451     } 
452   }
453   
454   protected boolean isMatchingInstructionLocation (Perturbation p, Instruction insn){
455     return p.sref == null || p.sref.equals(insn.getFilePos());
456   }
457 }