// // Copyright (C) 2012 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.vm; import java.util.ArrayList; import de.fosd.typechef.featureexpr.FeatureExpr; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFConfigException; import gov.nasa.jpf.util.Misc; import gov.nasa.jpf.util.Predicate; import gov.nasa.jpf.vm.choice.BreakGenerator; import gov.nasa.jpf.vm.choice.MultiProcessThreadChoice; /** * A VM implementation that simulates running multiple applications within the same * JPF process (centralized model checking of distributed applications). * This is achieved by executing each application in a separate thread group, * using separate SystemClassLoader instances to ensure proper separation of types / static fields. * * * @author Nastaran Shafiei <nastaran.shafiei@gmail.com> * * To use this jpf.properties includes, * vm.class = gov.nasa.jpf.vm.MultiProcessVM */ public class MultiProcessVM extends VM { static final int MAX_APP = 32; ApplicationContext[] appCtxs; MultiProcessPredicate runnablePredicate; MultiProcessPredicate appTimedoutRunnablePredicate; MultiProcessPredicate appDaemonRunnablePredicate; MultiProcessPredicate appPredicate; Predicate<ThreadInfo> systemRunnablePredicate; public MultiProcessVM (JPF jpf, Config conf) { super(jpf, conf); appCtxs = createApplicationContexts(); runnablePredicate = new MultiProcessPredicate() { public boolean isTrue (ThreadInfo t){ return (t.isRunnable() && this.appCtx == t.appCtx); } }; appTimedoutRunnablePredicate = new MultiProcessPredicate() { public boolean isTrue (ThreadInfo t){ return (this.appCtx == t.appCtx && t.isTimeoutRunnable()); } }; appDaemonRunnablePredicate = new MultiProcessPredicate() { public boolean isTrue (ThreadInfo t){ return (this.appCtx == t.appCtx && t.isRunnable() && t.isDaemon()); } }; appPredicate = new MultiProcessPredicate() { public boolean isTrue (ThreadInfo t){ return (this.appCtx == t.appCtx); } }; systemRunnablePredicate = new Predicate<ThreadInfo> () { public boolean isTrue (ThreadInfo t){ return (t.isSystemThread() && t.isRunnable()); } }; } /** * <2do> this should also handle command line specs such as "jpf ... tgt1 tgt1_arg ... -- tgt2 tgt2_arg ... */ ApplicationContext[] createApplicationContexts(){ String[] targets; int replicate = config.getInt("target.replicate", 0); if(replicate>0) { String target = config.getProperty("target"); targets = new String[replicate]; for(int i=0; i<replicate; i++) { targets[i] = target; } } else { targets = config.getStringEnumeration("target", MAX_APP); } if (targets == null){ throw new JPFConfigException("no applications specified, check 'target.N' settings"); } ArrayList<ApplicationContext> list = new ArrayList<ApplicationContext>(targets.length); for (int i=0; i<targets.length; i++){ if (targets[i] != null){ // there might be holes in the array String clsName = targets[i]; if (!isValidClassName(clsName)) { throw new JPFConfigException("main class not a valid class name: " + clsName); } String argsKey; String entryKey; String hostKey; if(replicate>0) { argsKey = "target.args"; entryKey = "target.entry"; hostKey = "target.host"; } else { argsKey = "target.args." + i; entryKey = "target.entry." + i; hostKey = "target.host." + i; } String[] args = config.getCompactStringArray(argsKey); if (args == null){ args = EMPTY_ARGS; } String mainEntry = config.getString(entryKey, "main([Ljava/lang/String;)V"); String host = config.getString(hostKey, "localhost"); SystemClassLoaderInfo sysCli = createSystemClassLoaderInfo(list.size()); ApplicationContext appCtx = new ApplicationContext( i, clsName, mainEntry, args, host, sysCli); list.add( appCtx); } } return list.toArray(new ApplicationContext[list.size()]); } @Override public boolean initialize(FeatureExpr ctx){ try { ThreadInfo tiFirst = null; for (int i=0; i<appCtxs.length; i++){ ThreadInfo tiMain = initializeMainThread(ctx, appCtxs[i], i); initializeFinalizerThread(ctx, appCtxs[i], appCtxs.length+i); if (tiMain == null) { return false; // bail out } if (tiFirst == null){ tiFirst = tiMain; } } initSystemState(tiFirst); initialized = true; notifyVMInitialized(); return true; } catch (JPFConfigException cfe){ log.severe(cfe.getMessage()); return false; } catch (ClassInfoException cie){ log.severe(cie.getMessage()); return false; } // all other exceptions are JPF errors that should cause stack traces } @Override public int getNumberOfApplications(){ return appCtxs.length; } @Override protected ChoiceGenerator<?> getInitialCG () { ThreadInfo[] runnables = getThreadList().getAllMatching(vm.getTimedoutRunnablePredicate()); return new MultiProcessThreadChoice("<root>", runnables, true); } @Override public ApplicationContext getApplicationContext(int objRef) { VM vm = VM.getVM(); ClassInfo ci = vm.getElementInfo(objRef).getClassInfo(); while(!ci.isObjectClassInfo()) { ci = ci.getSuperClass(); } ClassLoaderInfo sysLoader = ci.getClassLoaderInfo(); ApplicationContext[] appContext = vm.getApplicationContexts(); for(int i=0; i<appContext.length; i++) { if(appContext[i].getSystemClassLoader() == sysLoader) { return appContext[i]; } } return null; } @Override public ApplicationContext[] getApplicationContexts(){ return appCtxs; } @Override public ApplicationContext getCurrentApplicationContext(){ ThreadInfo ti = ThreadInfo.getCurrentThread(); if (ti != null){ return ti.getApplicationContext(); } else { // return the last defined one return appCtxs[appCtxs.length-1]; } } @Override public String getSUTName() { StringBuilder sb = new StringBuilder(); for (int i=0; i<appCtxs.length; i++){ if (i>0){ sb.append("+"); } sb.append(appCtxs[i].mainClassName); } return sb.toString(); } @Override public String getSUTDescription(){ StringBuilder sb = new StringBuilder(); for (int i=0; i<appCtxs.length; i++){ if (i>0){ sb.append('+'); // "||" would be more fitting, but would screw up filenames } ApplicationContext appCtx = appCtxs[i]; sb.append(appCtx.mainClassName); sb.append('.'); sb.append(Misc.upToFirst(appCtx.mainEntry, '(')); sb.append('('); String[] args = appCtx.args; for (int j = 0; j < args.length; j++) { if (j > 0) { sb.append(','); } sb.append('"'); sb.append(args[j]); sb.append('"'); } sb.append(')'); } return sb.toString(); } @Override public boolean isSingleProcess() { return false; } @Override public boolean isEndState () { boolean hasNonTerminatedDaemon = getThreadList().hasAnyMatching(getUserLiveNonDaemonPredicate()); boolean hasRunnable = getThreadList().hasAnyMatching(getUserTimedoutRunnablePredicate()); boolean isEndState = !(hasNonTerminatedDaemon && hasRunnable); if(processFinalizers) { if(isEndState) { int n = getThreadList().getMatchingCount(systemRunnablePredicate); if(n>0) { return false; } } } return isEndState; } @Override // Note - for now we just check for global deadlocks not the local ones which occur within a // scope of a single progress public boolean isDeadlocked () { boolean hasNonDaemons = false; boolean hasBlockedThreads = false; if (ss.isBlockedInAtomicSection()) { return true; // blocked in atomic section } ThreadInfo[] threads = getThreadList().getThreads(); int len = threads.length; for (int i=0; i<len; i++){ ThreadInfo ti = threads[i]; if (ti.isAlive()) { hasNonDaemons |= !ti.isDaemon(); // shortcut - if there is at least one runnable, we are not deadlocked if (ti.isTimeoutRunnable()) { // willBeRunnable() ? return false; } // means it is not NEW or TERMINATED, i.e. live & blocked hasBlockedThreads = true; } } return (hasNonDaemons && hasBlockedThreads); } @Override public void terminateProcess (ThreadInfo ti) { SystemState ss = getSystemState(); ThreadInfo[] appThreads = getThreadList().getAllMatching(getAppPredicate(ti)); ThreadInfo finalizerTi = null; for (int i = 0; i < appThreads.length; i++) { ThreadInfo t = appThreads[i]; // if finalizers have to be processed, FinalizerThread is not killed at this // point. We need to keep it around in case fianlizable objects are GCed after // System.exit() returns. if(processFinalizers && t.isSystemThread()) { finalizerTi = t; } else { // keep the stack frames around, so that we can inspect the snapshot t.setTerminated(); } } ThreadList tl = getThreadList(); ChoiceGenerator<ThreadInfo> cg; if (tl.hasAnyMatching(getAlivePredicate())) { ThreadInfo[] runnables = getThreadList().getAllMatching(getTimedoutRunnablePredicate()); cg = new MultiProcessThreadChoice( "PROCESS_TERMINATE", runnables, true); } else { cg = new BreakGenerator("exit", ti, true); } ss.setMandatoryNextChoiceGenerator(cg, "exit without break CG"); // if there is a finalizer thread, we have to run the last GC, to queue finalizable objects, if any if(finalizerTi != null) { assert finalizerTi.isAlive(); activateGC(); } } //---------- Predicates used to query threads from ThreadList ----------// abstract class MultiProcessPredicate implements Predicate<ThreadInfo> { ApplicationContext appCtx; public void setAppCtx (ApplicationContext appCtx) { this.appCtx = appCtx; } } @Override public Predicate<ThreadInfo> getRunnablePredicate() { runnablePredicate.setAppCtx(getCurrentApplicationContext()); return runnablePredicate; } @Override public Predicate<ThreadInfo> getAppTimedoutRunnablePredicate() { appTimedoutRunnablePredicate.setAppCtx(getCurrentApplicationContext()); return appTimedoutRunnablePredicate; } @Override public Predicate<ThreadInfo> getDaemonRunnablePredicate() { appDaemonRunnablePredicate.setAppCtx(getCurrentApplicationContext()); return appDaemonRunnablePredicate; } /** * Returns a predicate used to obtain all the threads that belong to the same application as ti */ Predicate<ThreadInfo> getAppPredicate (final ThreadInfo ti){ appPredicate.setAppCtx(ti.getApplicationContext()); return appPredicate; } // ---------- Methods for handling finalizers ---------- // @Override void updateFinalizerQueues () { for(ApplicationContext appCtx: appCtxs) { appCtx.getFinalizerThread().processNewFinalizables(); } } }