package de.rub.syssec.saaf;
import java.io.File;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import de.rub.syssec.saaf.misc.config.Config;
import de.rub.syssec.saaf.misc.config.ConfigKeys;
import de.rub.syssec.saaf.model.analysis.AnalysisException;
/**
* Own implementation of a ThreadPoolExcetuor which prints out some statistics.
*
* You have to call the SAAFThreadPoolExecutor.shutdown() after all threads
* are submitted to the pool, because the terminate function is called
* so you can react after that.
*
* @author Hanno Lemoine <hanno.lemoine@gdata.de>
* @author Johannes Hoffmann <johannes.hoffmann@rub.de>
*/
class SAAFThreadPoolExecutor extends ThreadPoolExecutor {
private static final Logger LOGGER = Logger.getLogger(SAAFThreadPoolExecutor.class);
private boolean gotUncaughtException = false; // Uncaught RuntimeExceptions
private int criticalExceptionCount = 0; // Caught critical exceptions
private int uncriticalExceptionCount = 0; // Caught uncritical exceptions from, eg, Program Slicing
private int analysisCount = 0;
private final int apkCount;
private boolean aborted = false;
private int skipped = 0;
/**
* A ThreadPoolExecutor which will automatically start all analyzes for all given files.
* @param apks the files to analyze
* @param corePoolSize
* @param maximumPoolSize
* @param keepAliveTime
* @param unit
* @param workQueue
*/
public SAAFThreadPoolExecutor(Collection<File> apks, int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, new ArrayBlockingQueue<Runnable>(apks.size()));
apkCount = apks.size();
for (File apk : apks) { // submit jobs
try {
AnalysisTask aft = new AnalysisTask(apk);
this.submit(aft, aft); // ignore future
}
catch (AnalysisException e) {
LOGGER.error("Could not generate Analysis object, skipping file: "+apk, e);
skipped++;
}
}
}
@Override
protected synchronized void beforeExecute(Thread t, Runnable r) {
analysisCount++;
LOGGER.info("Beginning analysis "+analysisCount+" of "+apkCount);
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
boolean errorOccured = false;
if (t != null) { // never actually happened
LOGGER.error("afterExecute() found a throwable.", t);
gotUncaughtException = true;
}
else if (r instanceof FutureTask<?>) {
try {
@SuppressWarnings("unchecked")
AnalysisTask at = ((FutureTask<AnalysisTask>) r).get();
if (at.hasNonCriticalExceptions()) {
uncriticalExceptionCount ++;
}
if (at.hasCriticalException()) {
errorOccured = true;
criticalExceptionCount++;
LOGGER.error("Analysis of `" + at.getAnalysis().getApp().getApplicationName()
+ " failed!\n\n", at.getCriticalException());
}
} catch (CancellationException e) {
LOGGER.warn("Analysis skipped!");
skipped++;
}
catch (InterruptedException e) {
LOGGER.warn("Analysis interrupted!");
skipped++;
}
catch (ExecutionException e) {
/*
* This is thrown by FutureTask.get() if the task threw an exception.
*/
gotUncaughtException = true;
LOGGER.error("Analysis failed with exception!", e);
}
}
if (errorOccured && Config.getInstance().getBooleanConfigValue(ConfigKeys.ANALYSIS_QUIT_ON_ERROR)) {
LOGGER.error("An error occured and QUIT_ON_ERROR is set. Exiting!");
skipped += shutdownNow().size();
aborted = true;
}
else if (gotUncaughtException) { // We must exit on unforeseen happenings :) Disk full? OOM?
LOGGER.error("Something unexpected occurred, see above. Will now perform an unclean exit (DB connections are not closed etc, but ShutdownHooks are run)!");
System.exit(2);
}
}
/**
* Print some nice statistics.
*/
public void printStatistic() {
String cWhite = "\033[m";
String cGreen = "\033[1;32m";
String cYellow = "\033[1;33m";
String cBlue = "\033[1;34m";
String cRed = "\033[1;31m";
Config conf = Config.getInstance();
if (!conf.getBooleanConfigValue(ConfigKeys.LOGGING_USE_COLOR, false)) { // colors disabled
cWhite = cGreen = cYellow = cBlue = cRed = "";
}
StringBuilder sb = new StringBuilder();
if (aborted) {
sb.append(cRed);
sb.append("\n!!! ANALYSIS PREMATURELY ABORTED DUE TO ERROR AND CONFIG.QUIT_ON_ERROR SET TO TRUE !!!\n");
}
sb.append(cRed);
sb.append("\nAnalysis statistics.\n====================");
sb.append(cBlue);
sb.append("\n#APKs: ");
sb.append(apkCount);
sb.append(cGreen);
sb.append("\n#Analyses: ");
sb.append(analysisCount);
sb.append(cYellow);
if (skipped > 0) {
sb.append("\nSkipped APK analyses due to error: ");
sb.append(skipped);
}
sb.append("\n#Analyses w/ uncritical exceptions: ");
sb.append(uncriticalExceptionCount);
sb.append(cRed);
sb.append("\n#Critical Exceptions: ");
sb.append(criticalExceptionCount);
sb.append(cWhite);
if (conf.getBooleanConfigValue(ConfigKeys.ANALYSIS_SKIP_KNOWN_APP)) {
sb.append(cYellow);
sb.append("\nAlready known APKs were skipped.");
}
if (conf.getBooleanConfigValue(ConfigKeys.DATABASE_DISABLED)) {
sb.append(cYellow);
sb.append("\nNothing was persisted to the DB.");
}
sb.append("\n");
LOGGER.info(sb.toString());
}
/**
* checks if there is any successful analysis
* @return true if each APK has a critical exception or aborted is set.
*/
public boolean hasNoSuccess() {
return (apkCount == criticalExceptionCount || aborted);
}
}