// // Copyright (C) 2006 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.search; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; 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.Path; import gov.nasa.jpf.vm.ThreadList; import gov.nasa.jpf.vm.Transition; import gov.nasa.jpf.vm.VM; /** * 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; } }