//
// 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 cmu.conditional.One;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.util.JPFLogger;
import gov.nasa.jpf.util.LocationSpec;
import gov.nasa.jpf.util.MethodSpec;
import gov.nasa.jpf.vm.ChoiceGenerator;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* listener that removes CGs for specified locations, method calls or method bodies
*
* This is an application specific state space optimizer that should be used
* carefully since it has to be aware of which CGs can be removed, and which
* ones can't (e.g. blocking sync or wait). You also have to be aware of the
* order of listener registration, since subsequently registered listeners can
* still add new CGs after they got removed here. THIS IS ONLY AN OPTIMIZATION
* TOOL THAT SHOULD BE USED IN A WELL KNOWN APPLICATION CONTEXT.
*
* cgrm.thread.cg_class = gov.nasa.jpf.vm.ThreadChoiceGenerator
* cgrm.thread.locations = Foobar.java:42 // either a LocationSpec
* cgrm.thread.method_bodies = a.SomeClass.someMethod() // ..or a MethodSpec for a body
* cgrm.thread.method_calls = b.A.foo(int) // ..or a MethodSpec for a call
*
* NOTE: in its current implementation, this listener has to be registered
* before targeted classes are loaded
*/
public class CGRemover extends ListenerAdapter {
static JPFLogger log = JPF.getLogger("gov.nasa.jpf.CGRemover");
static class Category {
String id;
Class<?> cgClass;
ArrayList<LocationSpec> locations = new ArrayList<LocationSpec>();
ArrayList<MethodSpec> methodBodies = new ArrayList<MethodSpec>();
ArrayList<MethodSpec> methodCalls = new ArrayList<MethodSpec>();
Category (String id){
this.id = id;
}
boolean checkSpecification() {
return cgClass != null &&
(!locations.isEmpty() || !methodBodies.isEmpty() || !methodCalls.isEmpty());
}
}
List<Category> categories;
HashMap<MethodInfo,Category> methodBodies;
HashMap<MethodInfo,Category> methodCalls;
HashMap<Instruction,Category> locations;
public CGRemover (Config conf){
categories = parseCategories(conf);
}
protected List<Category> parseCategories (Config conf){
ArrayList<Category> list = new ArrayList<Category>();
Category category = null;
for (String key : conf.getKeysStartingWith("cgrm.")){
String[] kc = conf.getKeyComponents(key);
if (kc.length == 3){
String k = kc[1];
if (category != null){
if (!category.id.equals(k)){
addCategory(list, category);
category = new Category(k);
}
} else {
category = new Category(k);
}
k = kc[2];
if ("cg_class".equals(k)){
category.cgClass = conf.getClass(key);
} else if ("locations".equals(k)){
parseLocationSpecs(category.locations, conf.getStringArray(key));
} else if ("method_bodies".equals(k)){
parseMethodSpecs(category.methodBodies, conf.getStringArray(key));
} else if ("method_calls".equals(k)){
parseMethodSpecs(category.methodCalls, conf.getStringArray(key));
} else {
// we might have more options in the future
log.warning("illegal CGRemover option: ", key);
}
} else {
log.warning("illegal CGRemover key: ", key);
}
}
addCategory(list, category);
return list;
}
protected void addCategory (List<Category> list, Category cat){
if (cat != null) {
if (cat.checkSpecification()) {
list.add(cat);
log.info("added category: ", cat.id);
} else {
log.warning("incomplete CGRemover category: ", cat.id);
}
}
}
protected void parseLocationSpecs (List<LocationSpec> list, String[] specs){
for (String spec : specs) {
LocationSpec locSpec = LocationSpec.createLocationSpec(spec);
if (locSpec != null) {
if (locSpec.isAnyLine()){
log.warning("whole file location specs not supported by CGRemover (use cgrm...method_bodies)");
} else {
list.add(locSpec);
}
} else {
log.warning("location spec did not parse: ", spec);
}
}
}
protected void parseMethodSpecs (List<MethodSpec> list, String[] specs){
for (String spec : specs) {
MethodSpec mthSpec = MethodSpec.createMethodSpec(spec);
if (mthSpec != null) {
list.add(mthSpec);
} else {
log.warning("methos spec did not parse: ", spec);
}
}
}
protected void processClass (ClassInfo ci, Category cat){
String fname = ci.getSourceFileName();
for (LocationSpec loc : cat.locations){
if (loc.matchesFile(fname)){ // Ok, we have to dig out the corresponding insns (if any)
Instruction[] insns = ci.getMatchingInstructions(loc);
if (insns != null){
if (locations == null){
locations = new HashMap<Instruction,Category>();
}
for (Instruction insn : insns){
locations.put(insn, cat);
}
} else {
log.warning("no insns for location: ", loc, " in class: ", ci.getName());
}
}
}
for (MethodSpec ms : cat.methodBodies){
List<MethodInfo> list = ci.getMatchingMethodInfos(ms);
if (list != null){
for (MethodInfo mi : list){
if (methodBodies == null){
methodBodies = new HashMap<MethodInfo,Category>();
}
methodBodies.put(mi, cat);
}
}
}
for (MethodSpec ms : cat.methodCalls){
List<MethodInfo> list = ci.getMatchingMethodInfos(ms);
if (list != null){
for (MethodInfo mi : list){
if (methodCalls == null){
methodCalls = new HashMap<MethodInfo,Category>();
}
methodCalls.put(mi, cat);
}
}
}
}
protected boolean removeCG (VM vm, Category cat, ChoiceGenerator<?> cg){
if (cat != null){
if (cat.cgClass.isAssignableFrom(cg.getClass())){
vm.getSystemState().removeNextChoiceGenerator();
log.info("removed CG: ", cg);
return true;
}
}
return false;
}
//--- VMListener interface
// this is where we turn Categories into MethodInfos and Instructions to watch out for
@Override
public void classLoaded (VM vm, ClassInfo loadedClass){
for (Category cat : categories){
processClass(loadedClass, cat);
}
}
// this is our main purpose in life
@Override
public void choiceGeneratorRegistered (VM vm, ChoiceGenerator<?> nextCG, ThreadInfo ti, Instruction executedInsn){
ChoiceGenerator<?> cg = vm.getNextChoiceGenerator();
Instruction insn;
if (cg.getInsn() instanceof One)
{
insn = cg.getInsn().getValue();
}
else
{
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: " + this);
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
insn = null;
}
if (locations != null){
if ( removeCG(vm, locations.get(insn), cg)){
return;
}
}
if (insn instanceof InvokeInstruction){
MethodInfo invokedMi = ((InvokeInstruction)insn).getInvokedMethod();
if (methodCalls != null) {
if (removeCG(vm, methodCalls.get(invokedMi), cg)) {
return;
}
}
}
if (methodBodies != null){
if (removeCG(vm, methodBodies.get(insn.getMethodInfo()), cg)) {
return;
}
}
}
}