//
// Copyright (C) 2010 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.listener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import de.fosd.typechef.featureexpr.FeatureExpr;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.GETFIELD;
import gov.nasa.jpf.jvm.bytecode.InstanceFieldInstruction;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.jvm.bytecode.ReturnInstruction;
import gov.nasa.jpf.perturb.OperandPerturbator;
import gov.nasa.jpf.util.FieldSpec;
import gov.nasa.jpf.util.JPFLogger;
import gov.nasa.jpf.util.MethodSpec;
import gov.nasa.jpf.util.SourceRef;
import gov.nasa.jpf.vm.ChoiceGenerator;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.FieldInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.StackFrame;
import gov.nasa.jpf.vm.SystemState;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* listener that perturbs GETFIELD/GETSTATIC and InvokeInstruction results
*
* NOTE - this listener initializes in two steps: (1) during listener construction
* it builds a list of classes it has to monitor, and (2) during class load
* time it further analyzes classes from this list to get the actual target
* objects (FieldInfos and MethodInfos) so that instruction monitoring is
* efficient enough.
*
* This means the listener always has to be instantiated BEFORE the respective
* target classes get loaded.
*
* configuration example:
*
* # field getter example
* perturb.fields = altitude,...
* perturb.altitude.field = x.y.MyClass.alt
* perturb.altitude.class = .perturb.IntOverUnder
* perturb.altitude.location = MyClass.java:42
* perturb.altitude.delta = 1
*
* # method return value example
* perturb.returns = velocity,...
* perturb.velocity.method = x.y.MyClass.computeVelocity()
* perturb.velocity.class = .perturb.IntOverUnder
* perturb.velocity.delta = 50
*
* # method parameter perturbation example
* perturb.params = foo, ...
* perturb.foo.method = x.y.MyClass.send(int, float, boolean)
* perturb.foo.location = MyClass.java:42
* perturb.class = .perturb.dataAbstractor
*
*/
public class Perturbator extends ListenerAdapter {
static JPFLogger log = JPF.getLogger("gov.nasa.jpf.Perturbator");
public static class Perturbation {
SourceRef sref; // location where field access should be perturbed
Class<? extends ChoiceGenerator<?>> cgType; // needs to be compatible with field type
OperandPerturbator perturbator;
Perturbation (OperandPerturbator perturbator, String loc){
this.perturbator = perturbator;
if (loc != null){
sref = new SourceRef(loc);
}
}
}
public static class FieldPerturbation extends Perturbation {
FieldSpec fieldSpec;
FieldPerturbation (FieldSpec fieldSpec, OperandPerturbator perturbator, String loc){
super(perturbator, loc);
this.fieldSpec = fieldSpec;
}
}
public static class ReturnPerturbation extends Perturbation {
MethodSpec mthSpec;
ReturnPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc){
super(perturbator, loc);
this.mthSpec = mthSpec;
}
}
public static class ParamsPerturbation extends Perturbation {
public MethodSpec mthSpec;
ParamsPerturbation (MethodSpec mthSpec, OperandPerturbator perturbator, String loc) {
super(perturbator, loc);
this.mthSpec = mthSpec;
}
}
protected static Class<?>[] argTypes = { Config.class, String.class };
protected List<FieldPerturbation> fieldWatchList = new ArrayList<FieldPerturbation>();
protected HashMap<FieldInfo,FieldPerturbation> perturbedFields = new HashMap<FieldInfo,FieldPerturbation>();
protected List<ReturnPerturbation> returnWatchList = new ArrayList<ReturnPerturbation>();
protected HashMap<MethodInfo,ReturnPerturbation> perturbedReturns = new HashMap<MethodInfo,ReturnPerturbation>();
protected List<ParamsPerturbation> paramsWatchList = new ArrayList<ParamsPerturbation>();
protected HashMap<MethodInfo, ParamsPerturbation> perturbedParams = new HashMap<MethodInfo, ParamsPerturbation>();
protected StackFrame savedFrame;
public Perturbator (Config conf){
// in the ctor we only find out which classname patterns we have to watch
// for, and store them in a list (together with their partially initialized
// Perturbation instances) that is to be checked upon classLoaded notifications
// get the configured field perturbators
String[] fieldIds = conf.getCompactTrimmedStringArray("perturb.fields");
for (String id : fieldIds){
addToFieldWatchList(conf, id);
}
String[] returnIds = conf.getCompactTrimmedStringArray("perturb.returns");
for (String id : returnIds){
addToReturnWatchList(conf, id);
}
String[] paramsIds = conf.getCompactTrimmedStringArray("perturb.params");
for (String id: paramsIds) {
addToParamsWatchList(conf, id);
}
}
public boolean isMethodWatched(Instruction insn, MethodInfo mi) {
ParamsPerturbation e = perturbedParams.get(mi);
if (e != null && isRelevantCallLocation(insn, e)){
return true;
}
return false;
}
protected void addToFieldWatchList (Config conf, String id){
String keyPrefix = "perturb." + id;
String fs = conf.getString(keyPrefix + ".field");
if (fs != null) {
FieldSpec fieldSpec = FieldSpec.createFieldSpec(fs);
if (fieldSpec != null){
Object[] args = {conf, keyPrefix};
OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);
if (perturbator != null) {
String loc = conf.getString(keyPrefix + ".location");
FieldPerturbation p = new FieldPerturbation(fieldSpec, perturbator, loc);
fieldWatchList.add(p);
} else {
log.warning("invalid perturbator spec for ", keyPrefix);
}
} else {
log.warning("malformed field specification for ", keyPrefix);
}
} else {
log.warning("missing field specification for ", keyPrefix);
}
}
protected void addToReturnWatchList (Config conf, String id){
String keyPrefix = "perturb." + id;
String ms = conf.getString(keyPrefix + ".method");
if (ms != null) {
MethodSpec mthSpec = MethodSpec.createMethodSpec(ms);
if (mthSpec != null) {
Object[] args = {conf, keyPrefix};
OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);
if (perturbator != null) {
String loc = conf.getString(keyPrefix + ".location");
ReturnPerturbation p = new ReturnPerturbation(mthSpec, perturbator, loc);
returnWatchList.add(p);
} else {
log.warning("invalid perturbator spec for ", keyPrefix);
}
} else {
log.warning("malformed method specification for ", keyPrefix);
}
} else {
log.warning("missing method specification for ", keyPrefix);
}
}
protected void addToParamsWatchList (Config conf, String id){
String keyPrefix = "perturb." + id;
String ms = conf.getString(keyPrefix + ".method");
if (ms != null) {
MethodSpec mthSpec = MethodSpec.createMethodSpec(ms);
if (mthSpec != null) {
Object[] args = {conf, keyPrefix};
OperandPerturbator perturbator = conf.getInstance(keyPrefix + ".class", OperandPerturbator.class, argTypes, args);
if (perturbator != null) {
String loc = conf.getString(keyPrefix + ".location");
ParamsPerturbation p = new ParamsPerturbation(mthSpec, perturbator, loc);
paramsWatchList.add(p);
} else {
log.warning("invalid perturbator spec for ", keyPrefix);
}
} else {
log.warning("malformed method specification for ", keyPrefix);
}
} else {
log.warning("missing method specification for ", keyPrefix);
}
}
@Override
public void classLoaded (VM vm, ClassInfo loadedClass){
// this one takes the watchlists, finds out if the loaded class matches
// any of the watch entries, and in case it does fully initializes
// the corresponding Perturbation object with the target construct
// (MethodInfo, FieldInfo) we use to identify relevant ops during
// instruction execution notifications
// String clsName = loadedClass.getName();
for (FieldPerturbation p : fieldWatchList){
FieldSpec fs = p.fieldSpec;
if (fs.isMatchingType(loadedClass)){
addFieldPerturbations( p, loadedClass, loadedClass.getDeclaredInstanceFields());
addFieldPerturbations( p, loadedClass, loadedClass.getDeclaredStaticFields());
}
}
for (ReturnPerturbation p : returnWatchList){
MethodSpec ms = p.mthSpec;
if (ms.isMatchingType(loadedClass)){
for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
if (ms.matches(mi)){
Class<? extends ChoiceGenerator<?>> returnCGType = mi.getReturnChoiceGeneratorType();
Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
if (returnCGType.isAssignableFrom(perturbatorCGType)){
p.cgType = returnCGType;
perturbedReturns.put(mi, p);
} else {
log.warning("method " + mi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
}
}
}
}
}
for (ParamsPerturbation p : paramsWatchList){
MethodSpec ms = p.mthSpec;
if (ms.isMatchingType(loadedClass)){
for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
if (ms.matches(mi)){
// We simply associate the method with the parameters perturbator
Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
p.cgType = perturbatorCGType;
perturbedParams.put(mi, p);
}
}
}
}
}
protected void addFieldPerturbations (FieldPerturbation p, ClassInfo ci, FieldInfo[] fieldInfos){
for (FieldInfo fi : ci.getDeclaredInstanceFields()) {
if (p.fieldSpec.matches(fi)) {
Class<? extends ChoiceGenerator<?>> fieldCGType = fi.getChoiceGeneratorType();
Class<? extends ChoiceGenerator<?>> perturbatorCGType = p.perturbator.getChoiceGeneratorType();
if (fieldCGType.isAssignableFrom(perturbatorCGType)) {
p.cgType = fieldCGType;
perturbedFields.put(fi, p);
} else {
log.warning("field " + fi + " not compatible with perturbator choice type " + perturbatorCGType.getName());
}
}
}
}
protected boolean isRelevantCallLocation (ThreadInfo ti, Perturbation p){
if (p.sref == null){
// no caller location specified -> all calls relevant
return true;
} else {
StackFrame caller = ti.getCallerStackFrame();
if (caller != null) {
Instruction invokeInsn = caller.getPC().getValue();
return p.sref.equals(invokeInsn.getFilePos());
} else {
return false;
}
}
}
protected boolean isRelevantCallLocation (Instruction invokeInsn, Perturbation p) {
// For parameter perturbation, we are about to enter a method
// and hence can directly use the invoke instruction to get the file
// location of the call
if (p.sref == null)
return true;
else
return p.sref.equals(invokeInsn.getFilePos());
}
@Override
public void executeInstruction (FeatureExpr ctx, VM vm, ThreadInfo ti, Instruction insnToExecute){
if (insnToExecute instanceof GETFIELD){
FieldInfo fi = ((InstanceFieldInstruction)insnToExecute).getFieldInfo(null);
FieldPerturbation e = perturbedFields.get(fi);
if (e != null) { // managed field
if (isMatchingInstructionLocation(e,insnToExecute)) {
if (!ti.isFirstStepInsn()){
// save the current stackframe so that we can restore it before
// we re-enter
savedFrame = ti.getTopFrame().clone();
}
}
}
} else if (insnToExecute instanceof ReturnInstruction){
MethodInfo mi = insnToExecute.getMethodInfo();
ReturnPerturbation e = perturbedReturns.get(mi);
if (e != null && isRelevantCallLocation(ti, e)){
SystemState ss = vm.getSystemState();
if (!ti.isFirstStepInsn()){
// first time, create & set CG but DO NOT enter the insn since it would
// pop the callee stackframe and modify the caller stackframe
// note that we don't need to enter in order to get the perturbation base
// value because its already on the operand stack
ChoiceGenerator<?> cg = e.perturbator.createChoiceGenerator("perturbReturn", ti.getTopFrame(), new Integer(0));
if (ss.setNextChoiceGenerator(cg)){
ti.skipInstruction(insnToExecute);
}
} else {
// re-executing, modify the operand stack top and enter
ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator("perturbReturn", e.cgType);
if (cg != null) {
e.perturbator.perturb(cg, ti.getTopFrame());
}
}
}
} else if (insnToExecute instanceof InvokeInstruction) {
// first get the method info object corresponding to the invoked method
// We can't use getMethodInfo as the method returned may not be the actual
// method invoked, but rather its caller
MethodInfo mi = ((InvokeInstruction) insnToExecute).getInvokedMethod();
ParamsPerturbation e = perturbedParams.get(mi);
if (e != null && isRelevantCallLocation(insnToExecute, e)){
SystemState ss = vm.getSystemState();
if (!ti.isFirstStepInsn()) {
// first time, create and set CG and skip instruction as we want the instruction
// to be executed with the parameter choices we like instead of the ones that
// were passed in
ChoiceGenerator<?> cg = e.perturbator.createChoiceGenerator(mi.getFullName(), ti.getTopFrame(), mi);
// check if the cg returned is null. If it is then we don't want to enter this
// method as we are done exploring it
if (cg != null) {
log.info("--- Creating choice generator: " + mi.getFullName() + " for thread: " + ti);
if (ss.setNextChoiceGenerator(cg)) {
ti.skipInstruction(insnToExecute);
}
}
} else {
// re-executing, modify the operands on stack and enter
ChoiceGenerator<?> cg = ss.getChoiceGenerator(mi.getFullName());
if (cg != null) {
log.info("--- Using choice generator: " + mi.getFullName() + " in thread: " + ti);
e.perturbator.perturb(cg, ti.getTopFrame());
}
}
}
}
}
@Override
public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
if (executedInsn instanceof GETFIELD){
FieldInfo fi = ((InstanceFieldInstruction)executedInsn).getFieldInfo(null);
FieldPerturbation p = perturbedFields.get(fi);
if (p != null){
if (isMatchingInstructionLocation(p, executedInsn)) { // none or managed filePos
StackFrame frame = ti.getTopFrame();
SystemState ss = vm.getSystemState();
if (ti.isFirstStepInsn()) { // retrieve value from CG and replace it on operand stack
ChoiceGenerator<?> cg = ss.getCurrentChoiceGenerator( "perturbGetField", p.cgType);
if (cg != null) {
p.perturbator.perturb(cg, frame);
} else {
log.warning("wrong choice generator type ", cg);
}
} else { // first time around, create&set the CG and reexecute
ChoiceGenerator<?> cg = p.perturbator.createChoiceGenerator( "perturbGetField", frame, new Integer(0));
if (ss.setNextChoiceGenerator(cg)){
assert savedFrame != null;
// we could more efficiently restore the stackframe
// to pre-exec state from last 'this' or classobject ref, but then
// we have to deal with different field value sizes
ti.setTopFrame(savedFrame);
ti.setNextPC(executedInsn); // reexecute
savedFrame = null;
}
}
}
}
}
}
protected boolean isMatchingInstructionLocation (Perturbation p, Instruction insn){
return p.sref == null || p.sref.equals(insn.getFilePos());
}
}