diff src/main/gov/nasa/jpf/search/Search.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/gov/nasa/jpf/search/Search.java	Fri Jan 23 10:14:01 2015 -0800
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2014, United States Government, as represented by the
+ * Administrator of the National Aeronautics and Space Administration.
+ * All rights reserved.
+ *
+ * The Java Pathfinder core (jpf-core) platform is licensed under the
+ * Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0. 
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+package gov.nasa.jpf.search;
+
+import gov.nasa.jpf.Config;
+import gov.nasa.jpf.ConfigChangeListener;
+import gov.nasa.jpf.Error;
+import gov.nasa.jpf.JPF;
+import gov.nasa.jpf.JPFException;
+import gov.nasa.jpf.JPFListenerException;
+import gov.nasa.jpf.Property;
+import gov.nasa.jpf.State;
+import gov.nasa.jpf.report.Reporter;
+import gov.nasa.jpf.util.IntVector;
+import gov.nasa.jpf.util.JPFLogger;
+import gov.nasa.jpf.util.Misc;
+import gov.nasa.jpf.vm.VM;
+import gov.nasa.jpf.vm.Path;
+import gov.nasa.jpf.vm.ThreadList;
+import gov.nasa.jpf.vm.Transition;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * the mother of all search classes. Mostly takes care of listeners, keeping
+ * track of state attributes and errors. This class mainly keeps the
+ * general search info like depth, configured properties etc.
+ */
+public abstract class Search {
+  
+  protected static JPFLogger log = JPF.getLogger("gov.nasa.jpf.search");
+  
+  /** error encountered during last transition, null otherwise */
+  protected Error currentError = null;
+  protected ArrayList<Error> errors = new ArrayList<Error>();
+
+  protected int       depth = 0;
+  protected VM       vm;
+
+  protected ArrayList<Property> properties;
+
+  protected boolean matchDepth;
+  protected long    minFreeMemory;
+  protected int     depthLimit;
+  protected boolean getAllErrors;
+
+  // message explaining the last search constraint hit
+  protected String lastSearchConstraint;
+
+  // these states control the search loop
+  protected boolean done = false;
+  protected boolean doBacktrack = false;
+
+  // do we have a probe request
+  protected AtomicBoolean notifyProbeListeners = new AtomicBoolean(false);
+
+  /** search listeners. We keep them in a simple array to avoid
+   creating objects on each notification */
+  protected SearchListener[] listeners = new SearchListener[0];
+
+  /** this is a special SearchListener that is always notified last, so that
+   * PublisherExtensions can be sure the notification has been processed by all listeners */
+  protected Reporter reporter;
+
+  protected final Config config; // to later-on access settings that are only used once (not ideal)
+
+  // don't forget to unregister or we have a HUGE memory leak if the same Config object is
+  // reused for several JPF runs
+  class ConfigListener implements ConfigChangeListener {
+
+    @Override
+    public void propertyChanged(Config config, String key, String oldValue, String newValue) {
+      // Different Config instance
+      if (!config.equals(Search.this.config)) {
+        return;
+      }
+
+      // Check if Search configuration changed
+      if (key.startsWith("search.")){
+        String k = key.substring(7);
+        if ("match_depth".equals(k) ||
+            "min_free".equals(k) ||
+            "multiple_errors".equals(k)){
+          initialize(config);
+        }
+      }
+    }
+    
+    @Override
+    public void jpfRunTerminated (Config config){
+      config.removeChangeListener(this);
+    }
+  }
+  
+  /** storage to keep track of state depths */
+  protected final IntVector stateDepth = new IntVector();
+
+  protected Search (Config config, VM vm) {
+    this.vm = vm;
+    this.config = config;
+
+    initialize( config);
+
+    properties = getProperties(config);
+    if (properties.isEmpty()) {
+      log.severe("no property");
+    }
+    
+    config.addChangeListener( new ConfigListener());
+  }
+
+  protected void initialize( Config conf){
+    depthLimit = conf.getInt("search.depth_limit", Integer.MAX_VALUE);
+    matchDepth = conf.getBoolean("search.match_depth");
+    minFreeMemory = conf.getMemorySize("search.min_free", 1024<<10);    
+    getAllErrors = conf.getBoolean("search.multiple_errors");
+  }
+  
+  /**
+   * called after the JPF run is finished. Shouldn't be public, but is called by JPF
+   */
+  public void cleanUp(){
+    // nothing here, the ConfigListener removes itself
+  }
+  
+  public Config getConfig() {
+    return config;
+  }
+  
+  public abstract void search ();
+
+  public void setReporter(Reporter reporter){
+    this.reporter = reporter;
+  }
+
+  public void addListener (SearchListener newListener) {
+    log.info("SearchListener added: ", newListener);
+    listeners = Misc.appendElement(listeners, newListener);
+  }
+
+  public boolean hasListenerOfType (Class<?> listenerCls) {
+    return Misc.hasElementOfType(listeners, listenerCls);
+  }
+  
+  public <T> T getNextListenerOfType(Class<T> type, T prev){
+    return Misc.getNextElementOfType(listeners, type, prev);
+  }
+
+
+  public void removeListener (SearchListener removeListener) {
+    listeners = Misc.removeElement(listeners, removeListener);
+  }
+
+
+  public void addProperty (Property newProperty) {
+    properties.add(newProperty);
+  }
+
+  public void removeProperty (Property oldProperty) {
+     properties.remove(oldProperty);
+  }
+
+  /**
+   * return set of configured properties
+   * note there is a name clash here - JPF 'properties' have nothing to do with
+   * Java properties (java.util.Properties)
+   */
+  protected ArrayList<Property> getProperties (Config config) {
+    Class<?>[] argTypes = { Config.class, Search.class };
+    Object[] args = { config, this };
+
+    ArrayList<Property> list = config.getInstances("search.properties", Property.class,
+                                                   argTypes, args);
+
+    return list;
+  }
+
+  // check for property violation, return true if not done
+  protected boolean hasPropertyTermination () {
+    if (currentError != null){
+      if (done){
+        return true;
+      } else { // we search for multiple errors, so we ignore and go on
+        doBacktrack = true;
+      }
+    }
+
+    return false;
+  }
+
+  // this should only be called once per transition, otherwise it keeps adding the same error
+  protected boolean checkPropertyViolation () {
+    for (Property p : properties) {
+      if (!p.check(this, vm)) {
+        error(p, vm.getClonedPath(), vm.getThreadList());
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public List<Error> getErrors () {
+    return errors;
+  }
+
+  public int getNumberOfErrors(){
+    return errors.size();
+  }
+
+  public String getLastSearchConstraint() {
+    return lastSearchConstraint;
+  }
+
+  /**
+   * request a probe
+   * 
+   * This does not do the actual listener notification, it only stores
+   * the request, which is then processed from within JPFs inner execution loop.
+   * As a consequence, probeSearch() can be called async, and searchProbed() listeners
+   * don't have to bother with synchronization or inconsistent JPF states (notification 
+   * happens from within JPFs main thread after a completed Instruction execution)
+   */
+  public void probeSearch(){
+    notifyProbeListeners.set(true);
+  }
+  
+  /**
+   * this does the actual notification and resets the request, hence this call
+   * should only happen from within JPFs main thread
+   */
+  public void checkAndResetProbeRequest(){
+    if (notifyProbeListeners.compareAndSet(true, false)){
+      notifySearchProbed();
+    }
+  }
+  
+  /**
+   * @return error encountered during *last* transition (null otherwise)
+   */
+  public Error getCurrentError(){
+    return currentError;
+  }
+
+  public Error getLastError() {
+    int i=errors.size()-1;
+    if (i >=0) {
+      return errors.get(i);
+    } else {
+      return null;
+    }
+  }
+
+  public boolean hasErrors(){
+    return !errors.isEmpty();
+  }
+
+  public VM getVM() {
+    return vm;
+  }
+
+  public boolean isEndState () {
+    return vm.isEndState();
+  }
+
+  public boolean isErrorState(){
+    return (currentError != null);
+  }
+
+  public boolean hasNextState () {
+    return !isEndState();
+  }
+
+  public boolean transitionOccurred(){
+    return vm.transitionOccurred();
+  }
+
+  public boolean isNewState () {
+    boolean isNew = vm.isNewState();
+
+    if (matchDepth) {
+      int id = vm.getStateId();
+
+      if (isNew) {
+        setStateDepth(id, depth);
+      } else {
+        return depth < getStateDepth(id);
+      }
+    }
+
+    return isNew;
+  }
+
+  public boolean isVisitedState () {
+    return !isNewState();
+  }
+
+  public boolean isIgnoredState(){
+    return vm.isIgnoredState();
+  }
+
+  public boolean isProcessedState(){
+    return vm.getChoiceGenerator().isProcessed();
+  }
+
+  public boolean isDone(){
+    return done;
+  }
+
+  public int getDepth () {
+    return depth;
+  }
+
+  public String getSearchConstraint () {
+    return lastSearchConstraint;
+  }
+
+  public Transition getTransition () {
+    return vm.getLastTransition();
+  }
+
+  public int getStateId () {
+    return vm.getStateId();
+  }
+
+  public int getPurgedStateId () {
+    return -1; // a lot of Searches don't purge any states
+  }
+
+
+  /**
+   * this is somewhat redundant to SystemState.setIgnored(), but we don't
+   * want to mix the case of overriding state matching with backtracking when
+   * searching for multiple errors
+   */
+  public boolean requestBacktrack () {
+    return doBacktrack = true;
+  }
+
+
+  protected boolean checkAndResetBacktrackRequest() {
+    if (doBacktrack){
+      doBacktrack = false;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public boolean supportsBacktrack () {
+    return true;
+  }
+
+  public boolean supportsRestoreState () {
+    // not supported by default
+    return false;
+  }
+
+  public int getDepthLimit () {
+    return depthLimit;
+  }
+  
+  public void setDepthLimit(int limit){
+    depthLimit = limit;
+  }
+
+  protected SearchState getSearchState () {
+    return new SearchState(this);
+  }
+
+  // can be used by SearchListeners to create path-less errors (liveness)
+  public void error (Property property) {
+    error(property, null, null);
+  }
+
+  public void error (Property property, Path path, ThreadList threadList) {
+
+    if (getAllErrors) {
+       // otherwise we are going to overwrite it if we go on
+      try {
+        property = property.clone();
+        path = path.clone();
+        threadList = (ThreadList) threadList.clone(); // this makes it a snapshot (deep) clone
+      } catch (CloneNotSupportedException cnsx){
+        throw new JPFException("failed to clone error information: " + cnsx);
+      }
+      done = false;
+      
+    } else {
+      done = true;
+    }
+
+    currentError = new Error(errors.size()+1, property, path, threadList);
+
+    errors.add(currentError);
+
+    // we should not reset the property until listeners have been notified
+    // (the listener might be the property itself, in which case it could get
+    // confused if propertyViolated() notifications happen after a reset)
+  }
+
+  public void resetProperties(){
+    for (Property p : properties) {
+      p.reset();
+    }
+  }
+
+  protected void notifyStateAdvanced () {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].stateAdvanced(this);
+      }
+      if (reporter != null){
+        // reporter always comes last to ensure all listeners have been notified
+        reporter.stateAdvanced(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during stateAdvanced() notification", t);
+    }
+  }
+
+  protected void notifyStateProcessed() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].stateProcessed(this);
+      }
+      if (reporter != null){
+        reporter.stateProcessed(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during stateProcessed() notification", t);
+    }
+  }
+
+  protected void notifyStateStored() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].stateStored(this);
+      }
+      if (reporter != null){
+        reporter.stateStored(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during stateStored() notification", t);
+    }
+  }
+
+  protected void notifyStateRestored() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].stateRestored(this);
+      }
+      if (reporter != null){
+        reporter.stateRestored(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during stateRestored() notification", t);
+    }
+  }
+
+  protected void notifyStateBacktracked() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].stateBacktracked(this);
+      }
+      if (reporter != null){
+        reporter.stateBacktracked(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during stateBacktracked() notification", t);
+    }
+  }
+
+  protected void notifyStatePurged() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].statePurged(this);
+      }
+      if (reporter != null){
+        reporter.statePurged(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during statePurged() notification", t);
+    }
+  }
+
+  public void notifySearchProbed() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].searchProbed(this);
+      }
+      if (reporter != null){
+        reporter.searchProbed(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during searchProbed() notification", t);
+    }
+  }
+
+  
+  protected void notifyPropertyViolated() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].propertyViolated(this);
+      }
+      if (reporter != null){
+        reporter.propertyViolated(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during propertyViolated() notification", t);
+    }
+
+    // reset properties if getAllErrors is set
+    if (getAllErrors){
+      resetProperties();
+    }
+  }
+
+  protected void notifySearchStarted() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].searchStarted(this);
+      }
+      if (reporter != null){
+        reporter.searchStarted(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during searchStarted() notification", t);
+    }
+  }
+
+  public void notifySearchConstraintHit(String details) {
+    try {
+      lastSearchConstraint = details;
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].searchConstraintHit(this);
+      }
+      if (reporter != null){
+        reporter.searchConstraintHit(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during searchConstraintHit() notification", t);
+    }
+  }
+
+  protected void notifySearchFinished() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].searchFinished(this);
+      }
+      if (reporter != null){
+        reporter.searchFinished(this);
+      }
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during searchFinished() notification", t);
+    }
+  }
+
+  protected boolean forward () {
+    currentError = null;
+
+    boolean ret = vm.forward();
+
+    checkPropertyViolation();
+    return ret;
+  }
+
+  protected boolean backtrack () {
+    return vm.backtrack();
+  }
+
+  public void setIgnoredState (boolean cond) {
+    vm.ignoreState(cond);
+  }
+
+  protected void restoreState (State state) {
+    // not supported by default
+  }
+
+  /** this can be used by listeners to terminate the search */
+  public void terminate () {
+    done = true;
+  }
+
+  protected void setStateDepth (int stateId, int depth) {
+    stateDepth.set(stateId, depth + 1);
+  }
+
+  public int getStateDepth (int stateId) {
+    int depthPlusOne = stateDepth.get(stateId);
+    if (depthPlusOne <= 0) {
+      throw new JPFException("Asked for depth of unvisited state");
+    } else {
+      return depthPlusOne - 1;
+    }
+  }
+
+  /**
+   * check if we have a minimum amount of free memory left. If not, we rather want to stop in time
+   * (with a threshold amount left) so that we can report something useful, and not just die silently
+   * with a OutOfMemoryError (which isn't handled too gracefully by most VMs)
+   */
+  public boolean checkStateSpaceLimit () {
+    Runtime rt = Runtime.getRuntime();
+
+    long avail = rt.freeMemory();
+
+    // we could also just check for a max number of states, but what really
+    // limits us is the memory required to store states
+
+    if (avail < minFreeMemory) {
+      // try to collect first
+      rt.gc();
+      avail = rt.freeMemory();
+
+      if (avail < minFreeMemory) {
+        // Ok, we give up, threshold reached
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
+