// // 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; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; import cmu.conditional.ChoiceFactory; import cmu.conditional.ChoiceFactory.Factory; import cmu.conditional.Conditional; import coverage.Coverage; import de.fosd.typechef.featureexpr.FeatureExprFactory; import gov.nasa.jpf.report.Publisher; import gov.nasa.jpf.report.PublisherExtension; import gov.nasa.jpf.report.Reporter; import gov.nasa.jpf.search.Search; import gov.nasa.jpf.search.SearchListener; import gov.nasa.jpf.tool.RunJPF; import gov.nasa.jpf.util.JPFLogger; import gov.nasa.jpf.util.LogManager; import gov.nasa.jpf.util.Misc; import gov.nasa.jpf.util.RunRegistry; import gov.nasa.jpf.vm.NoOutOfMemoryErrorProperty; import gov.nasa.jpf.vm.VM; import gov.nasa.jpf.vm.VMListener; import gov.nasa.jpf.vm.va.BufferedStackHandler; import gov.nasa.jpf.vm.va.HybridStackHandler; import gov.nasa.jpf.vm.va.HybridStackHandler.LiftedStack; import gov.nasa.jpf.vm.va.HybridStackHandler.NormalStack; import gov.nasa.jpf.vm.va.OneStackHandler; import gov.nasa.jpf.vm.va.StackHandler; import gov.nasa.jpf.vm.va.StackHandlerFactory; /** * main class of the JPF verification framework. This reads the configuration, * instantiates the Search and VM objects, and kicks off the Search */ public class JPF implements Runnable { public static List<Resetable> resetable = new ArrayList<>(); public static Coverage COVERAGE; public static String VERSION = "7.0"; // the major version number static Logger logger = null; // initially /** * Defines the method from which the trace strats. */ public static String traceMethod = null; public enum Status { NEW, RUNNING, DONE }; class ConfigListener implements ConfigChangeListener { /** * check for new listeners that are dynamically configured */ @Override public void propertyChanged(Config config, String key, String oldValue, String newValue) { if ("listener".equals(key)) { if (oldValue == null) { oldValue = ""; } String[] nv = config.asStringArray(newValue); String[] ov = config.asStringArray(oldValue); String[] newListeners = Misc.getAddedElements(ov, nv); Class<?>[] argTypes = { Config.class, JPF.class }; // Many listeners have 2 parameter constructors Object[] args = {config, JPF.this }; if (newListeners != null) { for (String clsName : newListeners) { try { JPFListener newListener = config.getInstance("listener", clsName, JPFListener.class, argTypes, args); addListener(newListener); logger.info("config changed, added listener " + clsName); } catch (JPFConfigException cfx) { logger.warning("listener change failed: " + cfx.getMessage()); } } } } } /** * clean up to avoid a sublte but serious memory leak when using the * same config for multiple JPF objects/runs - this listener is an inner * class object that keeps its encapsulating JPF instance alive */ @Override public void jpfRunTerminated(Config config){ config.removeChangeListener(this); } } /** this is the backbone of all JPF configuration */ Config config; /** The search policy used to explore the state space */ Search search; /** Reference to the virtual machine used by the search */ VM vm; /** the report generator */ Reporter reporter; Status status = Status.NEW; /** a list of listeners that get automatically added from VM, Search or Reporter initialization */ List<VMListener> pendingVMListeners; List<SearchListener> pendingSearchListeners; /** we use this as safety margin, to be released upon OutOfMemoryErrors */ byte[] memoryReserve; public static boolean SHARE_INVOCATIONS = false; private static Logger initLogging(Config conf) { LogManager.init(conf); return getLogger("gov.nasa.jpf"); } /** * use this one to get a Logger that is initialized via our Config mechanism. Note that * our own Loggers do NOT pass */ public static JPFLogger getLogger (String name) { return LogManager.getLogger( name); } public static void main(String[] args){ int options = RunJPF.getOptions(args); if (args.length == 0 || RunJPF.isOptionEnabled( RunJPF.HELP,options)) { RunJPF.showUsage(); return; } if (RunJPF.isOptionEnabled( RunJPF.ADD_PROJECT,options)){ RunJPF.addProject(args); return; } if (RunJPF.isOptionEnabled( RunJPF.BUILD_INFO,options)){ RunJPF.showBuild(RunJPF.class.getClassLoader()); } if (RunJPF.isOptionEnabled( RunJPF.LOG,options)){ Config.enableLogging(true); } Config conf = createConfig(args); if (RunJPF.isOptionEnabled( RunJPF.SHOW, options)) { conf.printEntries(); } start(conf, args); } public static void start(Config conf, String[] args){ // this is redundant to jpf.report.<publisher>.start=..config.. // but nobody can remember this (it's only used to produce complete reports) if (logger == null) { logger = initLogging(conf); } if (!checkArgs(args)){ return; } setNativeClassPath(conf); // in case we have to find a shell // check if there is a shell class specification, in which case we just delegate JPFShell shell = conf.getInstance("shell", JPFShell.class); if (shell != null) { shell.start(args); // responsible for exception handling itself } else { // no shell, we start JPF directly LogManager.printStatus(logger); conf.printStatus(logger); // this has to be done after we checked&consumed all "-.." arguments // this is NOT about checking properties! checkUnknownArgs(args); try { JPF jpf = new JPF(conf); jpf.run(); } catch (ExitException x) { logger.severe( "JPF terminated"); // this is how we get most runtime config exceptions if (x.shouldReport()) { x.printStackTrace(); } /** Throwable cause = x.getCause(); if ((cause != null) && conf.getBoolean("pass_exceptions", false)) { cause.fillInStackTrace(); throw cause; } **/ } catch (JPFException jx) { logger.severe( "JPF exception, terminating: " + jx.getMessage()); if (conf.getBoolean("jpf.print_exception_stack")) { Throwable cause = jx.getCause(); if (cause != null && cause != jx){ cause.printStackTrace(); } else { jx.printStackTrace(); } } // pass this on, caller has to handle throw jx; } } } static void setNativeClassPath(Config config) { if (!config.hasSetClassLoader()) { config.initClassLoader( JPF.class.getClassLoader()); } } protected JPF (){ // just to support unit test mockups } /** * create new JPF object. Note this is always guaranteed to return, but the * Search and/or VM object instantiation might have failed (i.e. the JPF * object might not be really usable). If you directly use getSearch() / getVM(), * check for null */ public JPF(Config conf) { config = conf; setNativeClassPath(config); // before we do anything else if (logger == null) { // maybe somebody created a JPF object explicitly logger = initLogging(config); } initialize(); } /** * convenience method if caller doesn't care about Config */ public JPF (String ... args) { this( createConfig(args)); } private void initialize() { VERSION = config.getString("jpf.version", VERSION); memoryReserve = new byte[config.getInt("jpf.memory_reserve", 64 * 1024)]; // in bytes try { Class<?>[] vmArgTypes = { JPF.class, Config.class }; Object[] vmArgs = { this, config }; vm = config.getEssentialInstance("vm.class", VM.class, vmArgTypes, vmArgs); Class<?>[] searchArgTypes = { Config.class, VM.class }; Object[] searchArgs = { config, vm }; search = config.getEssentialInstance("search.class", Search.class, searchArgTypes, searchArgs); // although the Reporter will always be notified last, this has to be set // first so that it can register utility listeners like Statistics that // can be used by configured listeners Class<?>[] reporterArgTypes = { Config.class, JPF.class }; Object[] reporterArgs = { config, this }; reporter = config.getInstance("report.class", Reporter.class, reporterArgTypes, reporterArgs); if (reporter != null){ search.setReporter(reporter); } // Set featureExprFactory String factory = config.getString("factory", "BDD"); if (factory.equals("BDD")) { FeatureExprFactory.setDefault(FeatureExprFactory.bdd()); } else { FeatureExprFactory.setDefault(FeatureExprFactory.sat()); } SHARE_INVOCATIONS = config.getBoolean("invocation"); processInteractionCommand(); // set the trace method traceMethod = config.getString("traceMethod", null); // Set StackHandlerFactory String stackHandlerFactory = config.getString("stack", ""); if (stackHandlerFactory.equals(BufferedStackHandler.class.getSimpleName())) { StackHandlerFactory.activateBufferedStackHandler(); } else if (stackHandlerFactory.equals(OneStackHandler.class.getSimpleName())) { StackHandlerFactory.activateOneStackHandler(); } else if (stackHandlerFactory.startsWith(HybridStackHandler.class.getSimpleName())) { StackHandlerFactory.activateHybridStackHandler(); String[] split = stackHandlerFactory.split("[|]"); System.out.println(Arrays.toString(split)); if (split.length == 3) { if (split[1].equals("JPF")) { HybridStackHandler.normalStack = NormalStack.JPFStack; } if (split[2].equals("Buffered")) { HybridStackHandler.liftedStack = LiftedStack.Buffered; } } } else if (stackHandlerFactory.equals(StackHandler.class.getSimpleName())) { StackHandlerFactory.activateDefaultStackHandler(); } else { StackHandlerFactory.activateHybridStackHandler(); } // Set the ChoiceFactory String choice = config.getString("choice", Factory.TreeChoice.toString()); ChoiceFactory.setDefault(Factory.valueOf(choice)); // Set the feature model Conditional.setFM(config.getString("featuremodel", "")); addListeners(); config.addChangeListener(new ConfigListener()); } catch (JPFConfigException cx) { logger.severe(cx.toString()); //cx.getCause().printStackTrace(); throw new ExitException(false, cx); } } public enum COVERAGE_TYPE { feature, stack, local, context, composedContext, time, interaction, frame } public static COVERAGE_TYPE SELECTED_COVERAGE_TYPE = null; public static Map<Integer, Object> JVMheap = Collections.emptyMap(); private void processInteractionCommand() { String logInteractions = config.getString("interaction", null); if (logInteractions != null) { if (COVERAGE == null) { // do not override COVERAGE = new Coverage(); COVERAGE.setMinInteraction(config.getInt("minInteraction", -1)); } for (COVERAGE_TYPE type : COVERAGE_TYPE.values()) { if (type.name().equals(logInteractions)) { SELECTED_COVERAGE_TYPE = type; switch (SELECTED_COVERAGE_TYPE) { case feature: COVERAGE.setType("Max features: "); COVERAGE.setBaseValue(0); break; case local: COVERAGE.setType("Max local: "); COVERAGE.setBaseValue(0); break; case stack: COVERAGE.setType("Stack with: "); COVERAGE.setBaseValue(1); break; case context: COVERAGE.setType("Number of different contexts: "); COVERAGE.setBaseValue(1); break; case composedContext: COVERAGE.setType("Size of disjuntion of all contexts: "); COVERAGE.setBaseValue(0); break; case time: COVERAGE.setType("Max time: "); COVERAGE.setBaseValue(0); break; case interaction: COVERAGE.setType("Interaction: "); COVERAGE.setBaseValue(0); break; case frame: COVERAGE.setType("Frame: "); COVERAGE.setBaseValue(0); break; default: break; } break; } } if (SELECTED_COVERAGE_TYPE == null) { StringBuilder message = new StringBuilder(); message.append("Specified interaction type \""); message.append(logInteractions); message.append("\" does not exist. Use one of:"); for (COVERAGE_TYPE type : COVERAGE_TYPE.values()) { message.append(type); message.append(' '); } throw new RuntimeException(message.toString()); } } } public Status getStatus() { return status; } public boolean isRunnable () { return ((vm != null) && (search != null)); } public void addPropertyListener (PropertyListenerAdapter pl) { if (vm != null) { vm.addListener( pl); } if (search != null) { search.addListener( pl); search.addProperty(pl); } } public void addSearchListener (SearchListener l) { if (search != null) { search.addListener(l); } } protected void addPendingVMListener (VMListener l){ if (pendingVMListeners == null){ pendingVMListeners = new ArrayList<VMListener>(); } pendingVMListeners.add(l); } protected void addPendingSearchListener (SearchListener l){ if (pendingSearchListeners == null){ pendingSearchListeners = new ArrayList<SearchListener>(); } pendingSearchListeners.add(l); } public void addListener (JPFListener l) { // the VM is instantiated first if (l instanceof VMListener) { if (vm != null) { vm.addListener( (VMListener) l); } else { // no VM yet, we are in VM initialization addPendingVMListener((VMListener)l); } } if (l instanceof SearchListener) { if (search != null) { search.addListener( (SearchListener) l); } else { // no search yet, we are in Search initialization addPendingSearchListener((SearchListener)l); } } } public <T> T getListenerOfType( Class<T> type){ if (search != null){ T listener = search.getNextListenerOfType(type, null); if (listener != null){ return listener; } } if (vm != null){ T listener = vm.getNextListenerOfType(type, null); if (listener != null){ return listener; } } return null; } public boolean addUniqueTypeListener (JPFListener l) { boolean addedListener = false; Class<?> cls = l.getClass(); if (l instanceof VMListener) { if (vm != null) { if (!vm.hasListenerOfType(cls)) { vm.addListener( (VMListener) l); addedListener = true; } } } if (l instanceof SearchListener) { if (search != null) { if (!search.hasListenerOfType(cls)) { search.addListener( (SearchListener) l); addedListener = true; } } } return addedListener; } public void removeListener (JPFListener l){ if (l instanceof VMListener) { if (vm != null) { vm.removeListener( (VMListener) l); } } if (l instanceof SearchListener) { if (search != null) { search.removeListener( (SearchListener) l); } } } public void addVMListener (VMListener l) { if (vm != null) { vm.addListener(l); } } public void addSearchProperty (Property p) { if (search != null) { search.addProperty(p); } } /** * this is called after vm, search and reporter got instantiated */ void addListeners () { Class<?>[] argTypes = { Config.class, JPF.class }; Object[] args = { config, this }; // first listeners that were automatically added from VM, Search and Reporter initialization if (pendingVMListeners != null){ for (VMListener l : pendingVMListeners) { vm.addListener(l); } pendingVMListeners = null; } if (pendingSearchListeners != null){ for (SearchListener l : pendingSearchListeners) { search.addListener(l); } pendingSearchListeners = null; } // and finally everything that's user configured List<JPFListener> listeners = config.getInstances("listener", JPFListener.class, argTypes, args); if (listeners != null) { for (JPFListener l : listeners) { addListener(l); } } } public Reporter getReporter () { return reporter; } public <T extends Publisher> boolean addPublisherExtension (Class<T> pCls, PublisherExtension e) { if (reporter != null) { return reporter.addPublisherExtension(pCls, e); } return false; } public <T extends Publisher> void setPublisherItems (Class<T> pCls, int category, String[] topics) { if (reporter != null) { reporter.setPublisherItems(pCls, category, topics); } } public Config getConfig() { return config; } /** * return the search object. This can be null if the initialization has failed */ public Search getSearch() { return search; } /** * return the VM object. This can be null if the initialization has failed */ public VM getVM() { return vm; } public static void exit() { // Hmm, exception as non local return. But we might be called from a // context we don't want to kill throw new ExitException(); } public boolean foundErrors() { return !(search.getErrors().isEmpty()); } /** * this assumes that we have checked and 'consumed' (nullified) all known * options, so we just have to check for any '-' option prior to the * target class name */ static void checkUnknownArgs (String[] args) { for ( int i=0; i<args.length; i++) { if (args[i] != null) { if (args[i].charAt(0) == '-') { logger.warning("unknown command line option: " + args[i]); } else { // this is supposed to be the target class name - everything that follows // is supposed to be processed by the program under test break; } } } } public static void printBanner (Config config) { System.out.println("Java Pathfinder Model Checker v" + config.getString("jpf.version", VERSION) + " - (C) 1999-2008 RIACS/NASA Ames Research Center"); } /** * find the value of an arg that is either specific as * "-key=value" or as "-key value". If not found, the supplied * defValue is returned */ static String getArg(String[] args, String pattern, String defValue, boolean consume) { String s = defValue; if (args != null){ for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg != null) { if (arg.matches(pattern)) { int idx=arg.indexOf('='); if (idx > 0) { s = arg.substring(idx+1); if (consume) { args[i]=null; } } else if (i < args.length-1) { s = args[i+1]; if (consume) { args[i] = null; args[i+1] = null; } } break; } } } } return s; } /** * what property file to look for */ static String getConfigFileName (String[] args) { if (args.length > 0) { // check if the last arg is a mode property file // if yes, it has to include a 'target' spec int idx = args.length-1; String lastArg = args[idx]; if (lastArg.endsWith(".jpf") || lastArg.endsWith(".properties")) { if (lastArg.startsWith("-c")) { int i = lastArg.indexOf('='); if (i > 0) { lastArg = lastArg.substring(i+1); } } args[idx] = null; return lastArg; } } return getArg(args, "-c(onfig)?(=.+)?", "jpf.properties", true); } /** * return a Config object that holds the JPF options. This first * loads the properties from a (potentially configured) properties file, and * then overlays all command line arguments that are key/value pairs */ public static Config createConfig (String[] args) { return new Config(args); } /** * runs the verification. */ @Override public void run() { for (Resetable m : resetable) { m.reset(); } Runtime rt = Runtime.getRuntime(); // this might be executed consecutively, so notify everybody RunRegistry.getDefaultRegistry().reset(); if (isRunnable()) { try { if (vm.initialize(FeatureExprFactory.True())) { status = Status.RUNNING; search.search(); } } catch (OutOfMemoryError oom) { // try to get memory back before we do anything that makes it worse // (note that we even try to avoid calls here, we are on thin ice) // NOTE - we don't try to recover at this point (that is what we do // if we fall below search.min_free within search()), we only want to // terminate gracefully (incl. report) memoryReserve = null; // release something long m0 = rt.freeMemory(); long d = 10000; // see if we can reclaim some memory before logging or printing statistics for (int i=0; i<10; i++) { rt.gc(); long m1 = rt.freeMemory(); if ((m1 <= m0) || ((m1 - m0) < d)) { break; } m0 = m1; } logger.severe("JPF out of memory"); // that's questionable, but we might want to see statistics / coverage // to know how far we got. We don't inform any other listeners though // if it throws an exception we bail - we can't handle it w/o memory try { search.notifySearchConstraintHit("JPF out of memory"); search.error(new NoOutOfMemoryErrorProperty()); // JUnit tests will succeed if OOM isn't flagged. reporter.searchFinished(search); } catch (Throwable t){ throw new JPFListenerException("exception during out-of-memory termination", t); } // NOTE - this is not an exception firewall anymore } finally { status = Status.DONE; config.jpfRunTerminated(); cleanUp(); } } if (traceMethod != null) { // TraceComparator.compare(); } } protected void cleanUp(){ search.cleanUp(); vm.cleanUp(); reporter.cleanUp(); } public List<Error> getSearchErrors () { if (search != null) { return search.getErrors(); } return null; } public Error getLastError () { if (search != null) { return search.getLastError(); } return null; } // some minimal sanity checks static boolean checkArgs (String[] args){ String lastArg = args[args.length-1]; if (lastArg != null && lastArg.endsWith(".jpf")){ if (!new File(lastArg).isFile()){ logger.severe("application property file not found: " + lastArg); return false; } } return true; } public static void handleException(JPFException e) { logger.severe(e.getMessage()); exit(); } /** * private helper class for local termination of JPF (without killing the * whole Java process via System.exit). * While this is basically a bad non-local goto exception, it seems to be the * least of evils given the current JPF structure, and the need to terminate * w/o exiting the whole Java process. If we just do a System.exit(), we couldn't * use JPF in an embedded context */ public static class ExitException extends RuntimeException { boolean report = true; ExitException() {} ExitException (boolean report, Throwable cause){ super(cause); this.report = report; } ExitException(String msg) { super(msg); } public boolean shouldReport() { return report; } } }