2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
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
10 * http://www.apache.org/licenses/LICENSE-2.0.
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.
19 package gov.nasa.jpf.listener;
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;
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.List;
48 * listener that perturbs GETFIELD/GETSTATIC and JVMInvokeInstruction results
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
56 * This means the listener always has to be instantiated BEFORE the respective
57 * target classes get loaded.
59 * configuration example:
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
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
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
82 public class Perturbator extends ListenerAdapter {
84 static JPFLogger log = JPF.getLogger("gov.nasa.jpf.Perturbator");
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;
91 Perturbation (OperandPerturbator perturbator, String loc){
92 this.perturbator = perturbator;
95 sref = new SourceRef(loc);
100 public static class FieldPerturbation extends Perturbation {
103 FieldPerturbation (FieldSpec fieldSpec, OperandPerturbator perturbator, String loc){
104 super(perturbator, loc);
106 this.fieldSpec = fieldSpec;
110 public static class ReturnPerturbation extends Perturbation {
113 ReturnPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc){
114 super(perturbator, loc);
116 this.mthSpec = mthSpec;
120 public static class ParamsPerturbation extends Perturbation {
121 public MethodSpec mthSpec;
123 ParamsPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc) {
124 super(perturbator, loc);
126 this.mthSpec = mthSpec;
130 protected static Class<?>[] argTypes = { Config.class, String.class };
132 protected List<FieldPerturbation> fieldWatchList = new ArrayList<FieldPerturbation>();
133 protected HashMap<FieldInfo,FieldPerturbation> perturbedFields = new HashMap<FieldInfo,FieldPerturbation>();
135 protected List<ReturnPerturbation> returnWatchList = new ArrayList<ReturnPerturbation>();
136 protected HashMap<MethodInfo,ReturnPerturbation> perturbedReturns = new HashMap<MethodInfo,ReturnPerturbation>();
138 protected List<ParamsPerturbation> paramsWatchList = new ArrayList<ParamsPerturbation>();
139 protected HashMap<MethodInfo, ParamsPerturbation> perturbedParams = new HashMap<MethodInfo, ParamsPerturbation>();
141 protected StackFrame savedFrame;
143 public Perturbator (Config conf){
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
149 // get the configured field perturbators
150 String[] fieldIds = conf.getCompactTrimmedStringArray("perturb.fields");
151 for (String id : fieldIds){
152 addToFieldWatchList(conf, id);
155 String[] returnIds = conf.getCompactTrimmedStringArray("perturb.returns");
156 for (String id : returnIds){
157 addToReturnWatchList(conf, id);
160 String[] paramsIds = conf.getCompactTrimmedStringArray("perturb.params");
161 for (String id: paramsIds) {
162 addToParamsWatchList(conf, id);
166 public boolean isMethodWatched(Instruction insn, MethodInfo mi) {
167 ParamsPerturbation e = perturbedParams.get(mi);
168 if (e != null && isRelevantCallLocation(insn, e)){
174 protected void addToFieldWatchList (Config conf, String id){
175 String keyPrefix = "perturb." + id;
177 String fs = conf.getString(keyPrefix + ".field");
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);
188 log.warning("invalid perturbator spec for ", keyPrefix);
191 log.warning("malformed field specification for ", keyPrefix);
195 log.warning("missing field specification for ", keyPrefix);
199 protected void addToReturnWatchList (Config conf, String id){
200 String keyPrefix = "perturb." + id;
202 String ms = conf.getString(keyPrefix + ".method");
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);
213 log.warning("invalid perturbator spec for ", keyPrefix);
217 log.warning("malformed method specification for ", keyPrefix);
221 log.warning("missing method specification for ", keyPrefix);
225 protected void addToParamsWatchList (Config conf, String id){
226 String keyPrefix = "perturb." + id;
228 String ms = conf.getString(keyPrefix + ".method");
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);
239 log.warning("invalid perturbator spec for ", keyPrefix);
243 log.warning("malformed method specification for ", keyPrefix);
246 log.warning("missing method specification for ", keyPrefix);
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
258 String clsName = loadedClass.getName();
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());
268 for (ReturnPerturbation p : returnWatchList){
269 MethodSpec ms = p.mthSpec;
270 if (ms.isMatchingType(loadedClass)){
271 for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
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);
279 log.warning("method " + mi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
286 for (ParamsPerturbation p : paramsWatchList){
287 MethodSpec ms = p.mthSpec;
288 if (ms.isMatchingType(loadedClass)){
289 for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
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);
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);
310 log.warning("field " + fi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
316 protected boolean isRelevantCallLocation (ThreadInfo ti, Perturbation p){
318 // no caller location specified -> all calls relevant
321 StackFrame caller = ti.getCallerStackFrame();
322 if (caller != null) {
323 Instruction invokeInsn = caller.getPC();
324 return p.sref.equals(invokeInsn.getFilePos());
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
338 return p.sref.equals(invokeInsn.getFilePos());
342 public void executeInstruction (VM vm, ThreadInfo ti, Instruction insnToExecute){
344 if (insnToExecute instanceof GETFIELD){
345 FieldInfo fi = ((JVMInstanceFieldInstruction)insnToExecute).getFieldInfo();
346 FieldPerturbation e = perturbedFields.get(fi);
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
353 savedFrame = ti.getTopFrame().clone();
358 } else if (insnToExecute instanceof JVMReturnInstruction){
359 MethodInfo mi = insnToExecute.getMethodInfo();
360 ReturnPerturbation e = perturbedReturns.get(mi);
362 if (e != null && isRelevantCallLocation(ti, e)){
363 SystemState ss = vm.getSystemState();
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);
375 // re-executing, modify the operand stack top and enter
376 ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator("perturbReturn", e.cgType);
378 e.perturbator.perturb(cg, ti.getTopFrame());
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);
389 if (e != null && isRelevantCallLocation(insnToExecute, e)){
390 SystemState ss = vm.getSystemState();
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
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
400 log.info("--- Creating choice generator: " + mi.getFullName() + " for thread: " + ti);
401 if (ss.setNextChoiceGenerator(cg)) {
402 ti.skipInstruction(insnToExecute);
406 // re-executing, modify the operands on stack and enter
407 ChoiceGenerator<?> cg = ss.getChoiceGenerator(mi.getFullName());
409 log.info("--- Using choice generator: " + mi.getFullName() + " in thread: " + ti);
410 e.perturbator.perturb(cg, ti.getTopFrame());
418 public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
420 if (executedInsn instanceof GETFIELD){
421 FieldInfo fi = ((JVMInstanceFieldInstruction)executedInsn).getFieldInfo();
422 FieldPerturbation p = perturbedFields.get(fi);
424 if (isMatchingInstructionLocation(p, executedInsn)) { // none or managed filePos
425 StackFrame frame = ti.getTopFrame();
426 SystemState ss = vm.getSystemState();
428 if (ti.isFirstStepInsn()) { // retrieve value from CG and replace it on operand stack
429 ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator( "perturbGetField", p.cgType);
431 p.perturbator.perturb(cg, frame);
433 log.warning("wrong choice generator type ", cg);
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
454 protected boolean isMatchingInstructionLocation (Perturbation p, Instruction insn){
455 return p.sref == null || p.sref.equals(insn.getFilePos());