diff --git a/microbat/META-INF/MANIFEST.MF b/microbat/META-INF/MANIFEST.MF index ddd0547fd..a6ffc57f2 100644 --- a/microbat/META-INF/MANIFEST.MF +++ b/microbat/META-INF/MANIFEST.MF @@ -132,6 +132,8 @@ Export-Package: microbat;uses:="org.osgi.framework,org.eclipse.jface.resource,or org.eclipse.core.commands, microbat.model.trace", microbat.handler.xml, + microbat.instrumentation.instr.aggreplay.shared, + microbat.instrumentation.model.id, microbat.instrumentation.output, microbat.model;uses:="microbat.model.variable,microbat.model.value,microbat.model.trace", microbat.model.trace; diff --git a/microbat/lib/instrumentator.jar b/microbat/lib/instrumentator.jar index c3063d0f1..84fb16e3a 100644 Binary files a/microbat/lib/instrumentator.jar and b/microbat/lib/instrumentator.jar differ diff --git a/microbat/plugin.xml b/microbat/plugin.xml index 4d0b462af..806cb6e8a 100644 --- a/microbat/plugin.xml +++ b/microbat/plugin.xml @@ -132,6 +132,22 @@ id="microbat.command.RestoreTrace" name="Restore Trace"> + + + + + + @@ -194,6 +210,19 @@ label="Restore Trace" style="push"> + + + + + + builder, + VMConfiguration config) { + builder.appendIf("-Xmx30g", enableSettingHeapSize); + // builder.appendIf("-Xmn10g", enableSettingHeapSize); + builder.appendIf("-XX:+UseG1GC", enableSettingHeapSize); + super.buildVmOption(builder, config); + } + + private void runSharedVarDetector() throws SavException { + super.addAgentParam(AgentParams.OPT_SHARED_DETECTION, true); + super.addAgentParam(AgentParams.OPT_DUMP_FILE, VARIABLE_DUMP_FILEPATH); + super.startAndWaitUntilStop(getProgramArgs()); + } + + /** + * Method used for running the individual concurrent modes. + * @param mode + * @param concDumpFile The path to the conc dump + * @param dumpFile The dump file + * @throws SavException + */ + public void concReplay(String mode, String concDumpFile, String dumpFile) throws SavException { + setUp(); + addAgentParam(mode, true); + addAgentParam(AgentParams.OPT_CONC_RECORD_DUMP, concDumpFile); + addAgentParam(AgentParams.OPT_DUMP_FILE, dumpFile); + super.startAndWaitUntilStop(this.configuration); + removeAgentParam(mode); + } + + /** + * Run's the three stages of the record and replay + */ + @Override + public void run() { + SingleTimer.start("Aggr"); + try { + runSharedVarDetector(); + } catch (SavException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + } + +} diff --git a/microbat/src/main/microbat/agent/ExecTraceFileReader.java b/microbat/src/main/microbat/agent/ExecTraceFileReader.java index 366380248..f8843f212 100644 --- a/microbat/src/main/microbat/agent/ExecTraceFileReader.java +++ b/microbat/src/main/microbat/agent/ExecTraceFileReader.java @@ -1,6 +1,7 @@ package microbat.agent; import java.util.ArrayList; +import java.util.List; import java.util.Optional; import microbat.codeanalysis.runtime.PreCheckInformation; @@ -11,14 +12,20 @@ public class ExecTraceFileReader { private String msg; private Trace trace; + private List allTraces; public Trace read(String execTraceFile) { RunningInfo info = RunningInfo.readFromFile(execTraceFile); this.trace = info.getMainTrace(); this.msg = info.getProgramMsg(); + this.allTraces = info.getTraceList(); return trace; } + public List getAllTraces() { + return this.allTraces; + } + public PreCheckInformation readPrecheck(String precheckFile) { PrecheckInfo info = PrecheckInfo.readFromFile(precheckFile); PreCheckInformation result = new PreCheckInformation(info.getThreadNum(), info.getStepTotal(), info.isOverLong(), diff --git a/microbat/src/main/microbat/agent/TraceAgentRunner.java b/microbat/src/main/microbat/agent/TraceAgentRunner.java index ffaec254d..a1b426264 100644 --- a/microbat/src/main/microbat/agent/TraceAgentRunner.java +++ b/microbat/src/main/microbat/agent/TraceAgentRunner.java @@ -17,6 +17,7 @@ import microbat.model.trace.Trace; import microbat.preference.DatabasePreference; import microbat.trace.Reader; +import microbat.util.MicroBatUtil; import sav.common.core.SavException; import sav.common.core.SavRtException; import sav.common.core.utils.CollectionBuilder; @@ -42,6 +43,10 @@ public class TraceAgentRunner extends AgentVmRunner { private List traces; + public void stopRunning() { + super.stop(); + } + public TraceAgentRunner(String agentJar, VMConfiguration vmConfig) { super(agentJar, AgentConstants.AGENT_OPTION_SEPARATOR, AgentConstants.AGENT_PARAMS_SEPARATOR); this.setConfig(vmConfig); @@ -54,6 +59,31 @@ protected void buildVmOption(CollectionBuilder builder, VMConfigurati builder.appendIf("-XX:+UseG1GC", enableSettingHeapSize); super.buildVmOption(builder, config); } + + public boolean sharedDetection() throws SavException { + addAgentParam(AgentParams.OPT_SHARED_DETECTION, true); + super.startAndWaitUntilStop(getConfig()); + removeAgentParam(AgentParams.OPT_SHARED_DETECTION); + return true; + } + + /** + * Method used for running the individual concurrent modes. + * @param mode + * @param concDumpFile The path to the conc dump + * @param dumpFile The dump file + * @throws SavException + */ + public void concReplay(String mode, String concDumpFile, String dumpFile) throws SavException { + addAgentParam(mode, true); + addAgentParam(AgentParams.OPT_CONC_RECORD_DUMP, concDumpFile); + addAgentParam(AgentParams.OPT_DUMP_FILE, dumpFile); + super.startAndWaitUntilStop(getConfig()); + removeAgentParam(mode); + removeAgentParam(AgentParams.OPT_DUMP_FILE); + removeAgentParam(AgentParams.OPT_CONC_RECORD_DUMP); + } + public boolean precheck(String filePath) throws SavException { isPrecheckMode = true; @@ -279,8 +309,12 @@ private void updateTestResult(String msg) { unknownTestResult = true; return; } + isTestSuccessful = MicroBatUtil.checkTestResult(msg); int sIdx = msg.indexOf(";"); - isTestSuccessful = Boolean.valueOf(msg.substring(0, sIdx)); + if (sIdx < 0 || msg.length() < sIdx) { + unknownTestResult = true; + return; + } testFailureMessage = msg.substring(sIdx + 1, msg.length()); } diff --git a/microbat/src/main/microbat/codeanalysis/runtime/InstrumentationExecutor.java b/microbat/src/main/microbat/codeanalysis/runtime/InstrumentationExecutor.java index a1e19e322..0f821b772 100644 --- a/microbat/src/main/microbat/codeanalysis/runtime/InstrumentationExecutor.java +++ b/microbat/src/main/microbat/codeanalysis/runtime/InstrumentationExecutor.java @@ -1,6 +1,10 @@ package microbat.codeanalysis.runtime; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -9,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Scanner; import org.apache.commons.io.FileUtils; import org.eclipse.jdt.core.dom.ASTNode; @@ -33,6 +38,7 @@ import microbat.preference.MicrobatPreference; import microbat.sql.DBSettings; import microbat.sql.DbService; +import microbat.trace.FileTraceReader; import microbat.util.JavaUtil; import microbat.util.MinimumASTNodeFinder; import microbat.util.Settings; @@ -49,6 +55,7 @@ public class InstrumentationExecutor { private String traceExecFilePath; private TraceAgentRunner agentRunner; private long timeout = VMRunner.NO_TIME_OUT; + private boolean isForceJunit3Or4 = false; private List includeLibs = Collections.emptyList(); private List excludeLibs = Collections.emptyList(); @@ -69,6 +76,11 @@ public InstrumentationExecutor(AppJavaClassPath appPath, String traceExecFilePat agentRunner = createTraceAgentRunner(); } + public void setIsForceJunit3Or4(boolean isForceJunit3Or4) { + this.isForceJunit3Or4 = isForceJunit3Or4; + agentRunner = createTraceAgentRunner(); + } + private TraceAgentRunner createTraceAgentRunner() { if(DBSettings.USE_DB.equals("true")) { @@ -92,6 +104,11 @@ private TraceAgentRunner createTraceAgentRunner() { if (appPath.getOptionalTestClass() != null) { config.addProgramArgs(appPath.getOptionalTestClass()); config.addProgramArgs(appPath.getOptionalTestMethod()); + // force the test runner to run only 3 or 4 -> skip the check + if (this.isForceJunit3Or4) { + config.addProgramArgs("forceJunit3Or4"); + } + agentRunner.addAgentParam(AgentParams.OPT_LAUNCH_CLASS, appPath.getOptionalTestClass()); } else { agentRunner.addAgentParam(AgentParams.OPT_ENTRY_POINT, @@ -117,7 +134,8 @@ private TraceAgentRunner createTraceAgentRunner() { agentRunner.setTimeout(timeout); // FIXME Xuezhi [2] List entries = ExecutionRangePreference.getCodeRangeEntrys(); - agentRunner.addAgentParams(AgentParams.OPT_CODE_RANGE, entries); + agentRunner.addAgentParams(AgentParams.OPT_CODE_RANGE, entries); + agentRunner.addAgentParam(AgentParams.TIMEOUT, Settings.timeLimit + ""); return agentRunner; } @@ -164,6 +182,123 @@ public RunningInfo run() throws StepLimitException { return null; } + public RunningInfo runCounter() throws StepLimitException { + try { + agentRunner.getConfig().setDebug(Settings.isRunWtihDebugMode); + agentRunner.getConfig().setPort(9000); + + System.out.println("precheck.."); + agentRunner.precheck(null); + PrecheckInfo info = agentRunner.getPrecheckInfo(); + System.out.println(info); + PreCheckInformation precheckInfomation = new PreCheckInformation(info.getThreadNum(), info.getStepTotal(), + info.isOverLong(), new ArrayList<>(info.getVisitedLocs()), info.getExceedingLimitMethods(), info.getLoadedClasses()); + precheckInfomation.setPassTest(agentRunner.isTestSuccessful()); + this.setPrecheckInfo(precheckInfomation); + } catch (SavException e1) { + e1.printStackTrace(); + } + + return null; + } + + public String runMemoryMeasureMent(String dumpFile) { + + agentRunner.getConfig().setDebug(Settings.isRunWtihDebugMode); + agentRunner.addAgentParam(AgentParams.OPT_DUMP_FILE, dumpFile); + agentRunner.getConfig().setPort(9000); + agentRunner.addAgentParam(AgentParams.MEASURE_MEM, true); + try { + agentRunner.startAndWaitUntilStop(agentRunner.getConfig()); + } catch (SavException e) { + e.printStackTrace(); + } + agentRunner.removeAgentParam(AgentParams.OPT_DUMP_FILE); + agentRunner.removeAgentParam(AgentParams.MEASURE_MEM); + + return agentRunner.getProccessError(); + } + + + public String runSharedVariable(String dumpFile, int stepLimit) { + agentRunner.getConfig().setDebug(Settings.isRunWtihDebugMode); + agentRunner.getConfig().setPort(9000); + agentRunner.addAgentParam(AgentParams.OPT_DUMP_FILE, dumpFile); + agentRunner.addAgentParam(AgentParams.OPT_STEP_LIMIT, stepLimit); + agentRunner.addAgentParam(AgentParams.OPT_SHARED_DETECTION, true); + String result = ""; + try { + agentRunner.sharedDetection(); + result = agentRunner.getProccessError(); + } catch (SavException e) { + // TODO Auto-generated catch block + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + result = pw.toString(); + } finally { + + } + agentRunner.removeAgentParam(AgentParams.OPT_SHARED_DETECTION); + return result; + } + + /** + * + * @param dumpFile + * @param concDumpFile + * @param stepLimit + */ + public String runRecordConc(String dumpFile, String concDumpFile, int stepLimit) { + agentRunner.getConfig().setDebug(Settings.isRunWtihDebugMode); + agentRunner.getConfig().setPort(9000); + agentRunner.addAgentParam(AgentParams.OPT_STEP_LIMIT, stepLimit); + String result; + try { + agentRunner.concReplay(AgentParams.OPT_CONC_RECORD, concDumpFile, dumpFile); + String processError = agentRunner.getProccessError(); + result = processError; + } catch (SavException e) { + StringWriter s = new StringWriter(); + PrintWriter p = new PrintWriter(s); + e.printStackTrace(p); + result = s.toString(); + } + return result; + } + + public String getProcessError() { + return this.agentRunner.getProccessError(); + } + + + public RunningInfo runReplayTracer(String concFile, String outputFile, int stepLimit) { + agentRunner.getConfig().setDebug(Settings.isRunWtihDebugMode); + System.out.println("Debug " + agentRunner.getConfig().isDebug()); + agentRunner.getConfig().setPort(9000); + agentRunner.addAgentParam(AgentParams.OPT_STEP_LIMIT, stepLimit); + agentRunner.addAgentParam(AgentParams.OPT_TRACE_RECORDER, "FILE"); + agentRunner.addAgentParam(AgentParams.REPLAY_MODE, Settings.replayMode.toString()); + try { + agentRunner.concReplay(AgentParams.OPT_CONC_REPLAY, concFile, outputFile); + } catch (SavException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + agentRunner.removeAgentParam(AgentParams.REPLAY_MODE); + FileTraceReader fileTraceReader = new FileTraceReader(); + RunningInfo result = fileTraceReader.read(null, outputFile); + for (Trace trace : result.getTraceList()) { + trace.setAppJavaClassPath(appPath); + appendMissingInfo(trace, appPath); + } + return result; + } + + public void interrupt() { + agentRunner.stopRunning(); + } + public PreCheckInformation runPrecheck(String dumpFile, int stepLimit) { try { /* test stepLimit */ @@ -198,7 +333,8 @@ public RunningInfo execute(PreCheckInformation info) { RunningInfo result = agentRunner.getRunningInfo(); // System.out.println(result); System.out.println("isExpectedStepsMet? " + result.isExpectedStepsMet()); - System.out.println("trace length: " + result.getMainTrace() == null ? "0" : result.getMainTrace().size()); + int size = result.getMainTrace() == null ? 0 : result.getMainTrace().size(); + System.out.println("trace length: " + size); System.out.println("isTestSuccessful? " + agentRunner.isTestSuccessful()); System.out.println("testFailureMessage: " + agentRunner.getTestFailureMessage()); System.out.println("finish!"); @@ -208,7 +344,10 @@ public RunningInfo execute(PreCheckInformation info) { trace.setAppJavaClassPath(appPath); // trace.setMultiThread(info.getThreadNum()!=1); - appendMissingInfo(trace, appPath); +// appendMissingInfo(trace, appPath); + for (Trace trace1 : result.getTraceList()) { + appendMissingInfo(trace1, appPath); + } trace.setConstructTime((int) (System.currentTimeMillis() - start)); return result; diff --git a/microbat/src/main/microbat/handler/CancelThread.java b/microbat/src/main/microbat/handler/CancelThread.java new file mode 100644 index 000000000..7539b0b5a --- /dev/null +++ b/microbat/src/main/microbat/handler/CancelThread.java @@ -0,0 +1,45 @@ +package microbat.handler; + +import org.eclipse.core.runtime.IProgressMonitor; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; + +/** + * Used to check if the job is canceled + * @author Gabau + * + */ +public class CancelThread extends Thread { + public boolean stopped = false; + IProgressMonitor monitor; + InstrumentationExecutor executor; + public CancelThread(IProgressMonitor monitor, + InstrumentationExecutor executor) { + this.setName("Cancel thread"); + this.monitor = monitor; + this.executor = executor; + this.setDaemon(true); + } + + @Override + public void run() { + while (!stopped) { + if (monitor.isCanceled()) { + if (executor != null) executor.interrupt(); + stopped = true; + break; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public void stopMonitoring() { + this.stopped = true; + } + +} \ No newline at end of file diff --git a/microbat/src/main/microbat/handler/ConcurrentRecordHandler.java b/microbat/src/main/microbat/handler/ConcurrentRecordHandler.java new file mode 100644 index 000000000..d5b2e8925 --- /dev/null +++ b/microbat/src/main/microbat/handler/ConcurrentRecordHandler.java @@ -0,0 +1,120 @@ +package microbat.handler; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.common.AbstractHandleObjectEvent; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.widgets.Display; + + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.evaluation.junit.TestCaseAnalyzer; +import microbat.instrumentation.output.RunningInfo; +import microbat.model.trace.Trace; +import microbat.preference.AnalysisScopePreference; +import microbat.util.MicroBatUtil; +import microbat.util.Settings; +import microbat.views.DebugFeedbackView; +import microbat.views.MicroBatViews; +import microbat.views.TraceView; +import sav.common.core.utils.FileUtils; +import sav.strategies.dto.AppJavaClassPath; + +/** + * + * @author Gabau + * + */ +public class ConcurrentRecordHandler extends AbstractHandler { + + + protected String generateTraceDir(AppJavaClassPath appPath) { + String traceFolder; + if (appPath.getOptionalTestClass() != null) { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getOptionalTestClass(), + appPath.getOptionalTestMethod()); + } else { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getLaunchClass()); + } + FileUtils.createFolder(traceFolder); + return traceFolder; + } + + public Object execute(ExecutionEvent event) throws ExecutionException { + + final AppJavaClassPath appClassPath = MicroBatUtil.constructClassPaths(); + if (Settings.isRunTest) { + appClassPath.setOptionalTestClass(Settings.launchClass); + appClassPath.setOptionalTestMethod(Settings.testMethod); + appClassPath.setLaunchClass(TestCaseAnalyzer.TEST_RUNNER); + appClassPath.setTestCodePath(MicroBatUtil.getSourceFolder(Settings.launchClass, Settings.projectName)); + } + + List srcFolders = MicroBatUtil.getSourceFolders(Settings.projectName); + appClassPath.setSourceCodePath(appClassPath.getTestCodePath()); + for (String srcFolder : srcFolders) { + if (!srcFolder.equals(appClassPath.getTestCodePath())) { + appClassPath.getAdditionalSourceFolders().add(srcFolder); + } + } + return executeAggrRecord(event, appClassPath); + + } + + + + private Object executeAggrRecord(ExecutionEvent event, final AppJavaClassPath appJavaClassPath) { + List includedClassNames = AnalysisScopePreference.getIncludedLibList(); + List excludedClassNames = AnalysisScopePreference.getExcludedLibList(); + InstrumentationExecutor executor = new InstrumentationExecutor(appJavaClassPath, + generateTraceDir(appJavaClassPath), "trace", includedClassNames, excludedClassNames); + Job runningJob = new Job("Run aggr") { + @Override + public IStatus run(IProgressMonitor monitor) { + String fileName = null; + File dumpFile = null; + File concDumpFile = null; + // the absolute path to the dump file. + String concFileNameString = null; + if (Settings.concurrentDumpFile.isPresent()) { + concFileNameString = Settings.concurrentDumpFile.get(); + concDumpFile = new File(concFileNameString); + } + try { + dumpFile = File.createTempFile("temp", ".txt"); + if (concDumpFile == null) { + concDumpFile = File.createTempFile("concTemp", ".txt"); + Settings.concurrentDumpFile = Optional.of(concDumpFile.getPath()); + } + fileName = dumpFile.getPath(); + concFileNameString = concDumpFile.getPath(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + CancelThread ctThread = new CancelThread(monitor, executor); + ctThread.start(); + executor.runSharedVariable(fileName, Settings.stepLimit); + executor.runRecordConc(fileName, concFileNameString, Settings.stepLimit); + ctThread.stopMonitoring(); + return Status.OK_STATUS; + } + }; + runningJob.schedule(); + return null; + } + +} diff --git a/microbat/src/main/microbat/handler/ConcurrentReplayHandler.java b/microbat/src/main/microbat/handler/ConcurrentReplayHandler.java new file mode 100644 index 000000000..aa04fb066 --- /dev/null +++ b/microbat/src/main/microbat/handler/ConcurrentReplayHandler.java @@ -0,0 +1,57 @@ +package microbat.handler; + +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputFilter.Status; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.widgets.Display; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.handler.replayexp.ConcurrentReplayJob; +import microbat.instrumentation.output.RunningInfo; +import microbat.model.trace.Trace; +import microbat.util.MicroBatUtil; +import microbat.util.Settings; +import microbat.views.MicroBatViews; +import microbat.views.TraceView; +import sav.common.core.utils.FileUtils; +import sav.strategies.dto.AppJavaClassPath; + +public class ConcurrentReplayHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + Job job = createReplayJob(); + job.schedule(); + return null; + } + + public ConcurrentReplayJob createReplayJob() { + return new ConcurrentReplayJob(); + } + + + protected String generateTraceDir(AppJavaClassPath appPath) { + String traceFolder; + if (appPath.getOptionalTestClass() != null) { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getOptionalTestClass(), + appPath.getOptionalTestMethod()); + } else { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getLaunchClass()); + } + FileUtils.createFolder(traceFolder); + return traceFolder; + } + +} diff --git a/microbat/src/main/microbat/handler/InstrumentExecutorSupplierImpl.java b/microbat/src/main/microbat/handler/InstrumentExecutorSupplierImpl.java new file mode 100644 index 000000000..a155ebe04 --- /dev/null +++ b/microbat/src/main/microbat/handler/InstrumentExecutorSupplierImpl.java @@ -0,0 +1,66 @@ +package microbat.handler; + +import java.util.List; +import java.util.function.Supplier; + +import org.eclipse.core.commands.AbstractHandler; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.evaluation.junit.TestCaseAnalyzer; +import microbat.preference.AnalysisScopePreference; +import microbat.util.MicroBatUtil; +import microbat.util.Settings; +import sav.common.core.utils.FileUtils; +import sav.strategies.dto.AppJavaClassPath; + +/** + * Represents a handler which initialises the app java class + * @author Gabau + * + */ +public class InstrumentExecutorSupplierImpl implements Supplier { + + @Override + public InstrumentationExecutor get() { + final AppJavaClassPath appClassPath = MicroBatUtil.constructClassPaths(); + if (Settings.isRunTest) { + appClassPath.setOptionalTestClass(Settings.launchClass); + appClassPath.setOptionalTestMethod(Settings.testMethod); + appClassPath.setLaunchClass(TestCaseAnalyzer.TEST_RUNNER); + appClassPath.setTestCodePath(MicroBatUtil.getSourceFolder(Settings.launchClass, Settings.projectName)); + } + + List srcFolders = MicroBatUtil.getSourceFolders(Settings.projectName); + appClassPath.setSourceCodePath(appClassPath.getTestCodePath()); + for (String srcFolder : srcFolders) { + if (!srcFolder.equals(appClassPath.getTestCodePath())) { + appClassPath.getAdditionalSourceFolders().add(srcFolder); + } + } + + List includedClassNames = AnalysisScopePreference.getIncludedLibList(); + List excludedClassNames = AnalysisScopePreference.getExcludedLibList(); + return new InstrumentationExecutor(appClassPath, + generateTraceDir(appClassPath), "trace", includedClassNames, excludedClassNames); + + } + + + protected String generateTraceDir(AppJavaClassPath appPath) { + String traceFolder; + if (appPath.getOptionalTestClass() != null) { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getOptionalTestClass(), + appPath.getOptionalTestMethod()); + } else { + traceFolder = FileUtils.getFilePath(MicroBatUtil.getTraceFolder(), + Settings.projectName, + appPath.getLaunchClass()); + } + FileUtils.createFolder(traceFolder); + return traceFolder; + } + + +} diff --git a/microbat/src/main/microbat/handler/ReplayExperimentHandler.java b/microbat/src/main/microbat/handler/ReplayExperimentHandler.java new file mode 100644 index 000000000..4db9b975b --- /dev/null +++ b/microbat/src/main/microbat/handler/ReplayExperimentHandler.java @@ -0,0 +1,201 @@ +package microbat.handler; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Scanner; +import java.util.function.Function; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.handler.replayexp.ConcurrentReplayJob; +import microbat.handler.replayexp.NormalTraceJob; +import microbat.handler.replayexp.ReplayJob; +import microbat.instrumentation.instr.aggreplay.ReplayMode; +import microbat.instrumentation.utils.MicrobatUtils; +import microbat.util.Settings; +import sav.common.core.utils.SingleTimer; + + +public class ReplayExperimentHandler extends AbstractHandler { + private int numberOfRuns = 200; + private static String resultLocation = "D:\\replayExperiment.xlsx"; + private static boolean skipStrict = false; + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ExperimentJob experimentJob = new ExperimentJob(numberOfRuns); + experimentJob.schedule(); + return null; + } + + private static class MemoryJob extends ReplayJob { + private String dumpFile; + public MemoryJob(String dumpFile) { + super("normal memory run"); + // TODO Auto-generated constructor stub + this.dumpFile = dumpFile; + + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + SingleTimer timer = SingleTimer.start("no trace"); + InstrumentationExecutor executor = new InstrumentExecutorSupplierImpl().get(); + String processError = executor.runMemoryMeasureMent(dumpFile); + long runTime = timer.getExecutionTime(); + stats.setRunTime(runTime); + stats.setStdError(processError); + stats.memoryUsed = getMemory(); + return Status.OK_STATUS; + } + + + protected long getMemory() { + Scanner dumpFileScanner; + long origMemSize = -1; + try { + dumpFileScanner = new Scanner(new File(dumpFile)); + if (dumpFileScanner.hasNext()) origMemSize = dumpFileScanner.nextLong(); + dumpFileScanner.close(); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return origMemSize; + } + + + } + + private static class ExperimentJob extends Job { + + Class statClass = ReplayStats.class; + private XSSFWorkbook workbook = new XSSFWorkbook(); + private int numberOfRuns = 100; + public ExperimentJob(int numberOfRuns) { + super("Run replay experiment"); + this.numberOfRuns = numberOfRuns; + } + + private void writeWorkBook() { + try { + FileOutputStream fileOutputStream = new FileOutputStream(new File(resultLocation)); + this.workbook.write(fileOutputStream); + fileOutputStream.flush(); + fileOutputStream.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + private void runJobProducer(Function jobProducer, String workSheetName) { + LinkedList stats = new LinkedList<>(); + for (int i = 0; i < numberOfRuns; ++i) { + ReplayJob job = jobProducer.apply(""); + job.schedule(); + try { + job.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + stats.add(job.getReplayStats()); + } + XSSFSheet sheet = workbook.createSheet(workSheetName); + writeToWorkSheet(sheet, stats); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + ConcurrentReplayHandler replayHandler = new ConcurrentReplayHandler(); + ReplayMode[] replayMode = ReplayMode.values(); + + ReplayMode temp = Settings.replayMode; + // run the experiment over the replay modes + for (int j = 0; j < replayMode.length; ++j) { + if (skipStrict && replayMode[j] == ReplayMode.STRICT_RW) continue; + Settings.replayMode = replayMode[j]; + runJobProducer(v -> replayHandler.createReplayJob(), "experiment" + Settings.replayMode.toString()); + } + runJobProducer(v -> new NormalTraceJob(), "normal runs"); + + File tempFile = null; + try { + tempFile = File.createTempFile("memory", "txt"); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + final File tFile = tempFile; + runJobProducer(v -> new MemoryJob(tFile.getAbsolutePath()), "memory run"); + writeWorkBook(); + + return Status.OK_STATUS; + } + + protected void writeToWorkSheet(XSSFSheet workSheet, Collection stats) { + int rowNum = 0; + initHeaders(workSheet.createRow(rowNum)); + rowNum++; + for (ReplayStats stat : stats) { + writeToRow(workSheet.createRow(rowNum), stat); + rowNum++; + } + writeWorkBook(); + + } + + protected void initHeaders(XSSFRow row) { + Field[] fields = statClass.getFields(); + int v = 0; + for (Field field : fields) { + XSSFCell cell = row.createCell(v++); + cell.setCellValue(field.getName()); + } + } + + private void writeToRow(XSSFRow row, ReplayStats replayStats) { + Field[] fields = statClass.getFields(); + int cellIdx = 0; + for (Field field : fields) { + try { + Object resultObject = field.get(replayStats); + XSSFCell cell = row.createCell(cellIdx++); + if (resultObject == null) { + cell.setCellValue("null"); + } else { + cell.setCellValue(resultObject.toString()); + } + + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + + +} diff --git a/microbat/src/main/microbat/handler/ReplayStats.java b/microbat/src/main/microbat/handler/ReplayStats.java new file mode 100644 index 000000000..5ffafabff --- /dev/null +++ b/microbat/src/main/microbat/handler/ReplayStats.java @@ -0,0 +1,99 @@ +package microbat.handler; + +import org.eclipse.core.runtime.jobs.Job; + +import microbat.instrumentation.output.RunningInfo; + +public class ReplayStats { + + /** + * The program msg sent to Agent._exitProgram + */ + public String programMsgString; + /** + * False if not junit test. + */ + public boolean hasPassedTest = false; + /** + * The stdout data + */ + public String stderr; + /** + * The size of the recording. + */ + public long dumpFileSize = -1; + /** + * The size of the logs. + */ + public long traceFileSize = -1; + /** + * The runtime in miliseconds + */ + public long runTime = -1; + + /** + * The memory used during execution + */ + public long memoryUsed = -1; + + + public ReplayStats() { + + } + + public String getProgramMsgString() { + return programMsgString; + } + + public void setProgramMsgString(String programMsgString) { + this.programMsgString = programMsgString; + } + + public boolean isHasPassedTest() { + return hasPassedTest; + } + + public void setHasPassedTest(boolean hasPassedTest) { + this.hasPassedTest = hasPassedTest; + } + + public String getStdout() { + if (stderr == null) return "null"; + return stderr; + } + + public void setStdError(String stderrString) { + if (stderrString == null) this.stderr = "null"; + this.stderr = stderrString; + } + + public long getDumpFileSize() { + return dumpFileSize; + } + + public void setDumpFileSize(long dumpFileSize) { + this.dumpFileSize = dumpFileSize; + } + + public long getTraceFileSize() { + return traceFileSize; + } + + public void setTraceFileSize(long traceFileSize) { + this.traceFileSize = traceFileSize; + } + + public double getRunTime() { + return runTime; + } + + public void setRunTime(long runTime) { + this.runTime = runTime; + } + + public void updateFromRunningInfo(RunningInfo info) { + this.setHasPassedTest(info.hasPassedTest()); + this.setProgramMsgString(info.getProgramMsg()); + } + +} \ No newline at end of file diff --git a/microbat/src/main/microbat/handler/StartDebugHandler.java b/microbat/src/main/microbat/handler/StartDebugHandler.java index b3f6625dc..b276bb44f 100644 --- a/microbat/src/main/microbat/handler/StartDebugHandler.java +++ b/microbat/src/main/microbat/handler/StartDebugHandler.java @@ -1,5 +1,6 @@ package microbat.handler; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -17,6 +18,7 @@ import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.swt.widgets.Display; +import microbat.agent.AggrePlaySharedVarRunner; import microbat.behavior.Behavior; import microbat.behavior.BehaviorData; import microbat.behavior.BehaviorReader; @@ -60,7 +62,9 @@ public void run() { }); } + public Object execute(ExecutionEvent event) throws ExecutionException { + final AppJavaClassPath appClassPath = MicroBatUtil.constructClassPaths(); if (Settings.isRunTest) { appClassPath.setOptionalTestClass(Settings.launchClass); @@ -68,6 +72,7 @@ public Object execute(ExecutionEvent event) throws ExecutionException { appClassPath.setLaunchClass(TestCaseAnalyzer.TEST_RUNNER); appClassPath.setTestCodePath(MicroBatUtil.getSourceFolder(Settings.launchClass, Settings.projectName)); } + List srcFolders = MicroBatUtil.getSourceFolders(Settings.projectName); appClassPath.setSourceCodePath(appClassPath.getTestCodePath()); for (String srcFolder : srcFolders) { diff --git a/microbat/src/main/microbat/handler/replayexp/ConcurrentReplayJob.java b/microbat/src/main/microbat/handler/replayexp/ConcurrentReplayJob.java new file mode 100644 index 000000000..68efcfb71 --- /dev/null +++ b/microbat/src/main/microbat/handler/replayexp/ConcurrentReplayJob.java @@ -0,0 +1,102 @@ +package microbat.handler.replayexp; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.widgets.Display; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.handler.CancelThread; +import microbat.handler.InstrumentExecutorSupplierImpl; +import microbat.handler.ReplayStats; +import microbat.instrumentation.output.RunningInfo; +import microbat.model.trace.Trace; +import microbat.util.Settings; +import microbat.views.MicroBatViews; +import microbat.views.TraceView; +import sav.common.core.utils.SingleTimer; + +public class ConcurrentReplayJob extends ReplayJob { + + boolean finishedExecution = false; + boolean requireVisualisation = true; + public ConcurrentReplayJob() { + super("Run replay"); + } + + public static ConcurrentReplayJob withoutVis() { + ConcurrentReplayJob job = new ConcurrentReplayJob(); + job.requireVisualisation = false; + return job; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + SingleTimer timer = SingleTimer.start("Run replay"); + InstrumentationExecutor executor = new InstrumentExecutorSupplierImpl().get(); + CancelThread ct = new CancelThread(monitor, executor); + File concDumpFile = null; + File outputFile = null; + // the absolute path to the dump file. + String concFileNameString = null; + if (Settings.concurrentDumpFile.isPresent()) { + concFileNameString = Settings.concurrentDumpFile.get(); + concDumpFile = new File(concFileNameString); + System.out.println("Used recording in " + concFileNameString); + } + try { + if (concDumpFile == null) { + concDumpFile = File.createTempFile("concTemp", ".txt"); + } + outputFile = File.createTempFile("outputFile", ".txt"); + concFileNameString = concDumpFile.getPath(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ct.start(); + RunningInfo result = executor.runReplayTracer(concFileNameString, outputFile.getPath(), Settings.stepLimit); + Trace mainTrace = result.getMainTrace(); + if (mainTrace != null) { + this.stats.memoryUsed = mainTrace.getMemoryUsed(); + } + ct.stopMonitoring(); + long executionTime = timer.getExecutionTime(); + this.stats.setRunTime(executionTime); + this.stats.setDumpFileSize(concDumpFile.length()); + this.stats.setTraceFileSize(outputFile.length()); + this.stats.setStdError(executor.getProcessError()); + if (requireVisualisation) { + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + TraceView traceView = MicroBatViews.getTraceView(); + if (result == null) { + traceView.setMainTrace(null); + traceView.setTraceList(null); + return; + } + Trace trace = result.getMainTrace(); + trace.setAppJavaClassPath(executor.getAppPath()); + List traces = result.getTraceList(); + + traceView.setMainTrace(trace); + traceView.setTraceList(traces); + traceView.updateData(); + } + }); + } + + // TODO Auto-generated method stub + return org.eclipse.core.runtime.Status.OK_STATUS; + } + + public ReplayStats getReplayStats() { + return this.stats; + } +} diff --git a/microbat/src/main/microbat/handler/replayexp/NormalTraceJob.java b/microbat/src/main/microbat/handler/replayexp/NormalTraceJob.java new file mode 100644 index 000000000..d540cdc9e --- /dev/null +++ b/microbat/src/main/microbat/handler/replayexp/NormalTraceJob.java @@ -0,0 +1,82 @@ +package microbat.handler.replayexp; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.widgets.Display; + +import microbat.codeanalysis.runtime.InstrumentationExecutor; +import microbat.codeanalysis.runtime.StepLimitException; +import microbat.handler.CancelThread; +import microbat.handler.InstrumentExecutorSupplierImpl; +import microbat.instrumentation.output.RunningInfo; +import microbat.model.trace.Trace; +import microbat.util.Settings; +import microbat.views.MicroBatViews; +import microbat.views.TraceView; +import sav.common.core.utils.SingleTimer; + +public class NormalTraceJob extends ReplayJob { + + + boolean finishedExecution = false; + + public NormalTraceJob() { + super("Run normal trace"); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + SingleTimer timer = SingleTimer.start("Run replay"); + InstrumentationExecutor executor = new InstrumentExecutorSupplierImpl().get(); + CancelThread ct = new CancelThread(monitor, executor); + File concDumpFile = null; + File outputFile = null; + // the absolute path to the dump file. + String concFileNameString = null; + if (Settings.concurrentDumpFile.isPresent()) { + concFileNameString = Settings.concurrentDumpFile.get(); + concDumpFile = new File(concFileNameString); + System.out.println("Used recording in " + concFileNameString); + } + try { + if (concDumpFile == null) { + concDumpFile = File.createTempFile("concTemp", ".txt"); + } + outputFile = File.createTempFile("outputFile", ".txt"); + concFileNameString = concDumpFile.getPath(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ct.start(); + RunningInfo result = null; + try { + result = executor.run(); + this.stats.updateFromRunningInfo(result); + } catch (StepLimitException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ct.stopMonitoring(); + long executionTime = timer.getExecutionTime(); + this.stats.setRunTime(executionTime); + // when there is a trace avail + if (result != null && result.getMainTrace() != null) { + Trace mainTrace = result.getMainTrace(); + this.stats.memoryUsed = mainTrace.getMemoryUsed(); + this.stats.setRunTime(mainTrace.getConstructTime()); + } + this.stats.setDumpFileSize(-1); + this.stats.setTraceFileSize(outputFile.length()); + this.stats.setStdError(executor.getProcessError()); + // TODO Auto-generated method stub + return org.eclipse.core.runtime.Status.OK_STATUS; + } + + +} diff --git a/microbat/src/main/microbat/handler/replayexp/ReplayJob.java b/microbat/src/main/microbat/handler/replayexp/ReplayJob.java new file mode 100644 index 000000000..fe229f833 --- /dev/null +++ b/microbat/src/main/microbat/handler/replayexp/ReplayJob.java @@ -0,0 +1,16 @@ +package microbat.handler.replayexp; + +import org.eclipse.core.runtime.jobs.Job; + +import microbat.handler.ReplayStats; + +public abstract class ReplayJob extends Job { + public ReplayJob(String name) { + super(name); + // TODO Auto-generated constructor stub + } + protected ReplayStats stats = new ReplayStats(); + public ReplayStats getReplayStats() { + return this.stats; + } +} diff --git a/microbat/src/main/microbat/model/ConcNode.java b/microbat/src/main/microbat/model/ConcNode.java new file mode 100644 index 000000000..044cef2a2 --- /dev/null +++ b/microbat/src/main/microbat/model/ConcNode.java @@ -0,0 +1,61 @@ +package microbat.model; + +import microbat.model.trace.ConcurrentTrace; +import microbat.model.trace.TraceNode; +import microbat.model.value.VarValue; +import sav.common.core.Pair; + +public class ConcNode { + private int node1; + // after + private int node2; + // node1 is before + private boolean isBefore1; + // node2 isbefore + private boolean isBefore2; + + private int changeType; + + private VarValue linkedVarValue = null; + + public VarValue getLinkedValue() { + return linkedVarValue; + } + + public int getChangeType() { + return this.changeType; + } + public ConcNode(int node1, int node2, boolean isBefore1, boolean isBefore2, int changeType) { + this.node1 = node1; + this.node2 = node2; + this.isBefore1 = isBefore1; + this.isBefore2 = isBefore2; + this.changeType = changeType; + } + public int getNode1() { + return node1; + } + public int getNode2() { + return node2; + } + public boolean isBefore1() { + return isBefore1; + } + public boolean isBefore2() { + return isBefore2; + } + + public Pair getFirst() { + return Pair.of(node1, isBefore1); + } + + public Pair getSecond() { + return Pair.of(node2, isBefore2); + } + + + public static ConcNode fromTraceNodes(TraceNode node1, TraceNode node2, boolean isBefore1, boolean isBefore2, + int changeType) { + return new ConcNode(node1.getBound().getOrder(), node2.getBound().getOrder(), isBefore1, isBefore2, changeType); + } +} diff --git a/microbat/src/main/microbat/model/trace/ConcurrentTrace.java b/microbat/src/main/microbat/model/trace/ConcurrentTrace.java new file mode 100644 index 000000000..6c1c793df --- /dev/null +++ b/microbat/src/main/microbat/model/trace/ConcurrentTrace.java @@ -0,0 +1,459 @@ +package microbat.model.trace; + +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import microbat.instrumentation.model.id.ThreadId; +import microbat.model.value.VarValue; +import microbat.model.variable.Variable; +import sav.strategies.dto.AppJavaClassPath; + +public class ConcurrentTrace extends Trace { + + private List originalTraces; + // temporary storage for processing + private List generatedTraceNodes; + // the non sequential concurrent trace nodes + private ArrayList> traces + = new ArrayList<>(); + + private ConcurrentTraceNode linkedTraceNode; + + public List getTraceList() { + return originalTraces; + } + + public Trace getMainTrace() { + return originalTraces.get(0); + } + + public List getSequentialTrace() { + return generatedTraceNodes; + } + + public ConcurrentTrace(AppJavaClassPath appJavaClassPath) { + super(appJavaClassPath); + // TODO Auto-generated constructor stub + } + + public Trace getCorrespondingTrace(ThreadId threadId) { + for (Trace trace : originalTraces) { + if (threadId.equals(trace.getInnerThreadId())) { + return trace; + } + } + return null; + } + + /** + * Technique to update trace nodes values + */ + private void postProcess() { + for (int i = 0; i < generatedTraceNodes.size(); ++i) { + generatedTraceNodes.get(i).setOrder(i + 1); + generatedTraceNodes.get(i).setConcurrentTrace(this); + this.addTraceNode(generatedTraceNodes.get(i)); + } + } + + /** + * Function used to generate concurrent read write nodes. + * Need to split the trace nodes into read and write. + */ + private void generateTraces() { + if (traces.size() != 0) throw new RuntimeException("Should not generate more than once"); + if (originalTraces == null) throw new RuntimeException("Original traces not set"); + traces.ensureCapacity(originalTraces.size()); + int ctr = 0; + for (Trace trace: originalTraces) { + ArrayList result = new ArrayList<>(); + // reserve double the size than expected + result.ensureCapacity(trace.getExecutionList().size() * 2); + for (int i = 0; i < trace.getExecutionList().size(); ++i) { + TraceNode traceNode = trace.getExecutionList().get(i); + result.addAll(ConcurrentTraceNode.splitTraceNode(traceNode, ctr, trace.getThreadId())); + } + ctr += 1; + traces.add(result); + } + // update the step in previous and step in next + for (ArrayList trace: traces) { + ConcurrentTraceNode previous = null; + for (int i = 0; i < trace.size(); ++i) { + if (previous != null) { + trace.get(i).setStepInPrevious(trace.get(i-1)); + } + if (i != trace.size() - 1) { + trace.get(i).setStepInNext(trace.get(i+1)); + } + previous = trace.get(i); + } + } + } + + @Override + public TraceNode findProducer(VarValue varValue, TraceNode startNode) { + + // check if it is on heap + String s = varValue.getAliasVarID(); + boolean isLocalVar = varValue.isLocalVariable(); + boolean isOnHeap = !isLocalVar; + if (!(startNode instanceof ConcurrentTraceNode)) { + startNode = startNode.getBound(); + } + ConcurrentTraceNode concNode = (ConcurrentTraceNode) startNode; + int j = concNode.getOrder(); +// if (!concNode.isAtomic() && concNode.getWrittenVariables().size() > 0) { +// return concNode.getLinkedTraceNode().initialTraceNode; +// } +// + // find the individual trace to use the find data dependenccy + String varID = Variable.truncateSimpleID(varValue.getVarID()); + String headID = Variable.truncateSimpleID(varValue.getAliasVarID()); + + + for(int i=j-2; i>=1; i--) { + ConcurrentTraceNode node = this.getSequentialTrace().get(i); + for(VarValue writtenValue: node.getWrittenVariables()) { + String k = writtenValue.getAliasVarID(); + if (isOnHeap && writtenValue.getAliasVarID() != null + && writtenValue.getAliasVarID().equals(varValue.getAliasVarID())) { + return node.initialTraceNode; + } + if (!isOnHeap && concNode.getCurrentThread() != node.getCurrentThread()) { + // when the value is on a different thread + continue; + } + + String wVarID = Variable.truncateSimpleID(writtenValue.getVarID()); + String wHeadID = Variable.truncateSimpleID(writtenValue.getAliasVarID()); +// if (wVarID != null && wVarID.equals(varID) && concNode.getCurrentThread() == node.getCurrentThread()) { + if(wVarID != null && wVarID.equals(varID)) { + return node.initialTraceNode; + } + + if(wHeadID != null && wHeadID.equals(headID)) { + return node.initialTraceNode; + } + + VarValue childValue = writtenValue.findVarValue(varID, headID); + if(childValue != null) { + return node.initialTraceNode; + } + + } + } + return null; + } + + /** + * Generates a concurrent trace from the given trace by timestamp order. + * End goal is to remove timestamp synchronisation - aka deprecate this. + * @param traces The trace to generate the trace from + * @return + */ + public static ConcurrentTrace fromTimeStampOrder(List inputTraces) { + ConcurrentTrace resultTrace = new ConcurrentTrace(""); + resultTrace.originalTraces = inputTraces; + + AppJavaClassPath cPath = null; + for (Trace trace : inputTraces) { + cPath = trace.getAppJavaClassPath(); + if (cPath != null) break; + } + resultTrace.setAppJavaClassPath(cPath); + resultTrace.generateTraces(); + ArrayList> traces = resultTrace.traces; + + ArrayList result = new ArrayList<>(); + resultTrace.generatedTraceNodes = result; + List sharedVarIds = getSharedVarIDs(inputTraces); + HashSet sharedVarAliasIds = new HashSet<>(); + for (VarValue val: sharedVarIds) { + sharedVarAliasIds.add(val.getAliasVarID()); + } + HashMap sharedVarValues = new HashMap<>(); + ArrayList tracesPtr = new ArrayList<>(); + tracesPtr.ensureCapacity(traces.size()); + for (int i = 0; i < traces.size(); ++i) { + tracesPtr.add(0); // point to the first trace node + } + while (true) { + // select the first trace based off of time stamp order + // if there is a tie, break tie based off of current shared variables status + ArrayList possibleNodes = new ArrayList<>(); + for (int i = 0; i < traces.size(); ++i) { + ArrayList trace = traces.get(i); + int currentPoint = tracesPtr.get(i); + if (currentPoint >= trace.size()) { + // deal with case when all the pointers are at the end of the trace + continue; + } + ConcurrentTraceNode m = trace.get(currentPoint); + if (possibleNodes.size() == 0) { + possibleNodes.add(m); + } else { + if (possibleNodes.get(0).getTimestamp() < m.getTimestamp()) { + continue; + } + if (possibleNodes.get(0).getTimestamp() == m.getTimestamp()) { + possibleNodes.add(m); + continue; + } + // when all possible nodes are larger + possibleNodes.clear(); + possibleNodes.add(m); + } + } + if (possibleNodes.size() == 0) { + break; + } + + // greedy resolution -> pick the first + result.add(possibleNodes.get(0)); + int id = possibleNodes.get(0).getCurrentTraceId(); + tracesPtr.set(id, tracesPtr.get(id) + 1); + // todo: proper resolution + // deal with possible nodes. + // allocate the first possible node + // will be the bulk of the logic without timestamps + //ConcurrentTraceNode selected = extractPossibleTraceNode(result, sharedVarAliasIds, sharedVarValues, possibleNodes); + //tracesPtr.set(selected.getCurrentTraceId(), tracesPtr.get(selected.getCurrentTraceId()) + 1); + } + + resultTrace.postProcess(); + + return resultTrace; + } + + /** + * Extracts a tracenode + * @param result + * @param sharedVarAliasIds + * @param sharedVarValues + * @param possibleNodes + * @return + */ + private static ConcurrentTraceNode extractPossibleTraceNode(ArrayList result, + HashSet sharedVarAliasIds, HashMap sharedVarValues, + ArrayList possibleNodes) { + ConcurrentTraceNode selected = null; + for (int i = 0; i < possibleNodes.size(); ++i) { + ConcurrentTraceNode current = possibleNodes.get(i); + if (current.getWrittenVariables().size() > 0) { + continue; + } + List readValues = current.getReadVariables(); + // check the string value to if satis + boolean satis = true; + for (VarValue val: readValues) { + assert(val.getAliasVarID() == null || (!sharedVarAliasIds.contains(val.getAliasVarID()) + || sharedVarValues.containsKey(val.getAliasVarID()))); + // when there isn't a match + if (val.getAliasVarID() != null && sharedVarAliasIds.contains(val.getAliasVarID()) + && !val.getStringValue().equals(sharedVarValues.get(val.getAliasVarID()))) { + satis = false; + break; + } + } + if (satis) { + selected = current; + } + } + if (selected != null ) { + result.add(selected); + return selected; + } + // todo: this resolution is too greedy + // it doesn't work as we do not preserve W -> R + // may need to use some kind of constraint solving + // may not be possible until we indicate in the instrumentation + // to record RC vector (To determine what reads must occur before this write) + for (int i = 0; i < possibleNodes.size(); ++i) { + ConcurrentTraceNode current = possibleNodes.get(i); + if (current.getWrittenVariables().size() > 0) { + // take the first write + selected = current; + break; + } + } + if (selected == null) { + throw new RuntimeException("Failed to allocate trace node to concurrent trace"); + } + return selected; + } + + + + + + @Override + public TraceNode getTraceNode(int order) { + // TODO Auto-generated method stub + return this.getSequentialTrace().get(order - 1); + } + + + + /** + * Finds data dependency relative to the concurrent trace + */ + @Override + public TraceNode findDataDependency(TraceNode checkingNode, VarValue readVar) { + return this.findProducer(readVar, checkingNode); + } + + + + @Override + public List findDataDependentee(TraceNode traceNode, VarValue writtenVar) { + List consumers = new ArrayList(); + if (!(traceNode instanceof ConcurrentTraceNode)) { + traceNode = traceNode.getBound(); + } + + ConcurrentTraceNode cnode = (ConcurrentTraceNode) traceNode; + String varID = Variable.truncateSimpleID(writtenVar.getVarID()); + String headID = Variable.truncateSimpleID(writtenVar.getAliasVarID()); + + for(int i=cnode.getOrder() + 1; i <= this.getSequentialTrace().size(); i++) { + ConcurrentTraceNode node = this.getSequentialTrace().get(i); + for(VarValue readVar: node.getReadVariables()) { + + String rVarID = Variable.truncateSimpleID(readVar.getVarID()); + String rHeadID = Variable.truncateSimpleID(readVar.getAliasVarID()); + + if(rVarID != null && rVarID.equals(varID)) { + consumers.add(node.getInitialTraceNode()); + } + + if(rHeadID != null && rHeadID.equals(headID)) { + consumers.add(node.getInitialTraceNode()); + } + + VarValue childValue = readVar.findVarValue(varID, headID); + if(childValue != null) { + consumers.add(node.getInitialTraceNode()); + } + + } + } + return consumers; + + } + + + + public ConcurrentTrace(String id) { + super(id); + } + + public static ConcurrentTrace fromTraces(List traces) { + return null; + } + + private List generateSequential(List traces) { + return null; + } + + // convert the trace nodes to have read only and write only + private static List convertTraceNodes(Trace trace) { + ArrayList result = new ArrayList<>(); + for (int i = 0; i < trace.size(); ++i) { + TraceNode current = trace.getTraceNode(i); + } + return null; + } + + private static List getSharedVarIDs(List traces) { + HashSet sharedVariables = new HashSet<>(); + List result = new ArrayList<>(); + for (Trace trace: traces) { + for (TraceNode tnode: trace.getExecutionList()) { + List writtenValues = tnode.getWrittenVariables(); + List readValues = tnode.getReadVariables(); + List combined = new ArrayList<>(); + for (VarValue wVal: writtenValues) { + combined.add(wVal); + } + for (VarValue rVal: readValues) { + combined.add(rVal); + } + for (VarValue value: combined) { + if (value.getAliasVarID() == null) continue; + if (sharedVariables.contains(value.getAliasVarID())) { + result.add(value); + continue; + } + sharedVariables.add(value.getAliasVarID()); + } + } + } + return result; + } + + /** + * Creates a virtual trace given a concurrent trace + * @return + */ + private static List constructSequentialTrace(List traces) { + Set sharedVariables = new HashSet<>(); + HashMap valueMap = new HashMap<>(); + + sharedVariables.addAll(getSharedVarIDs(traces)); + for (VarValue sharedVar: sharedVariables) { + valueMap.put(sharedVar.getAliasVarID(), ""); + } + + ArrayList sequentialTrace = new ArrayList<>(); + // split all the traces into read + write trace nodes + + int[] startingPtr = new int[traces.size()]; + for (int i = 0; i < traces.size(); ++i) { + startingPtr[i] = 0; + } + + while (true) { + // keep track of all traces -> if none of them have a matching node -> we can make no progress + boolean allNotStatis = true; + for (int i = 0; i < traces.size(); ++i) { + TraceNode current = traces.get(i).getTraceNode(startingPtr[i]); + List readValues = current.getReadVariables(); + List writeValues = current.getWrittenVariables(); + boolean satis = true; + // check if the read value matches the current variables value + for (VarValue val: readValues) { + if (sharedVariables.contains(val) + && valueMap.get(val) != val.getStringValue()) { + satis = false; + break; + } + } + if (!satis) continue; + for (int k = 0; k < traces.size(); ++k) { + if (k == i) continue; + + } + // todo: check if this is blocking the other points + startingPtr[i] += 1; + sequentialTrace.add(current); + break; + } + boolean completed = true; + for (int i = 0; i < traces.size(); ++i) { + if (startingPtr[i] < traces.get(i).size()) { + completed = false; + break; + } + } + if (completed) break; + } + return sequentialTrace; + + } +} \ No newline at end of file diff --git a/microbat/src/main/microbat/model/trace/ConcurrentTraceNode.java b/microbat/src/main/microbat/model/trace/ConcurrentTraceNode.java new file mode 100644 index 000000000..d8be87321 --- /dev/null +++ b/microbat/src/main/microbat/model/trace/ConcurrentTraceNode.java @@ -0,0 +1,376 @@ +package microbat.model.trace; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +import microbat.instrumentation.model.id.ThreadId; +import microbat.model.BreakPoint; +import microbat.model.BreakPointValue; +import microbat.model.UserInterestedVariables; +import microbat.model.value.VarValue; +import microbat.model.variable.Variable; +import microbat.util.JavaUtil; +import microbat.views.ReferenceAnnotation; + +/** + * Represents a concurrent trace node + * + * Adds additional logic for determining dependencies + * @author Gabau + * + */ +public class ConcurrentTraceNode extends TraceNode { + + public static TraceNode getIniTraceNode(TraceNode traceNode) { + if (traceNode instanceof ConcurrentTraceNode) { + return ((ConcurrentTraceNode) traceNode).getInitialTraceNode(); + } + return traceNode; + } + + /** + * Obtains the original trace nodes given a list + * of a mix of concurrent trace nodes and trace nodes + * @param traceNodes + * @return + */ + public static List convert(List traceNodes) { + List result = new LinkedList<>(); + for (TraceNode traceNode: traceNodes) { + if (result instanceof ConcurrentTraceNode) { + result.add(((ConcurrentTraceNode) traceNode).getInitialTraceNode()); + } else { + result.add(traceNode); + } + } + return result; + } + + + /** + * The index of the trace that this trace node is in + */ + protected int currentTraceId = 0; + protected boolean isAtomic = false; + protected TraceNode initialTraceNode; + // the sequential order it is in + protected int concurrentOrder; + /** + * The thread id + */ + protected long threadId = 0; + private ConcurrentTrace concurrentTrace; + + public void setConcurrentTrace(ConcurrentTrace concurrentTrace) { + this.concurrentTrace = concurrentTrace; + } + + public ConcurrentTrace getConcurrentTrace() { + return this.concurrentTrace; + } + + public long getCurrentThread() { + return initialTraceNode.getTrace().getThreadId(); + } + + + + + @Override + public ConcurrentTraceNode getBound() { + return this; + } + + + + + @Override + public TraceNode getStepInNext() { + return initialTraceNode.getStepInNext(); + } + + + + + @Override + public TraceNode getStepInPrevious() { + return initialTraceNode.getStepInPrevious(); + } + + + + + @Override + public TraceNode getStepOverNext() { + return initialTraceNode.getStepOverNext(); + } + + + + + @Override + public TraceNode getStepOverPrevious() { + return initialTraceNode.getStepOverPrevious(); + } + + + + /** + * Returns the order this node is in in the global order. + */ + @Override + public int getOrder() { + return concurrentOrder; + } + + /** + * Returns the order of this trace node within the trace + * @return + */ + public int getInitialOrder() { + return initialTraceNode.getOrder(); + } + + @Override + public void setOrder(int concurrentOrder) { + this.concurrentOrder = concurrentOrder; + } + + + /** + * Pointer to the trace node which writes to this trace node / reads to this trace node + * nullable + */ + private ConcurrentTraceNode linkedTraceNode; + public int getCurrentTraceId() { + return currentTraceId; + } + + /** + * Link the read and write trace nodes + * @return + */ + public ConcurrentTraceNode getLinkedTraceNode() { + return linkedTraceNode; + } + + + public ConcurrentTraceNode(BreakPoint breakPoint, BreakPointValue programState, int order, Trace trace, + String bytecode, long threadId) { + super(breakPoint, programState, order, trace, bytecode); + this.threadId = threadId; + // TODO Auto-generated constructor stub + } + + public ConcurrentTraceNode(TraceNode node, int currentTraceId, long threadId) { + super(node.getBreakPoint(), node.getProgramState(), + node.getOrder(), node.getTrace(), node.getBytecode()); + this.setTimestamp(node.getTimestamp()); + this.currentTraceId = currentTraceId; + this.threadId = threadId; + } + + /** + * Due to the way concurrency works, we separate the read and the + * writes, unless there is a synchronisation step. + * @return + */ + public boolean isWrite() { + return this.writtenVariables.size() > 0; + } + + public boolean isAtomic() { + return isAtomic; + } + + + /** + * This implementation currently means that if this + * node only has write, we get the written VarValues that match the selection. + * + */ + @Override + public List getWrongReadVars(UserInterestedVariables interestedVariables) { + if (this.isAtomic()) { + return super.getWrongReadVars(interestedVariables); + } + if (this.isWrite()) { + + List vars = new ArrayList<>(); + for(VarValue var: getWrittenVariables()){ + if(interestedVariables.contains(var)){ + vars.add(var); + } + List children = var.getAllDescedentChildren(); + for(VarValue child: children){ + if(interestedVariables.contains(child)){ + if(!vars.contains(child)){ + vars.add(child); + } + } + } + } + + return vars; + } + return super.getWrongReadVars(interestedVariables); + } + + public TraceNode getInitialTraceNode() { + return this.initialTraceNode; + } + +// +// @Override +// public TraceNode findProducer(VarValue varValue, TraceNode startNode) { +// +// // check if it is on heap +// String s = varValue.getAliasVarID(); +// boolean isLocalVar = varValue.isLocalVariable(); +// boolean isOnHeap = !isLocalVar; +// if (!(startNode instanceof ConcurrentTraceNode)) { +// throw new RuntimeException("Wrong checking node type"); +// } +// ConcurrentTraceNode concNode = (ConcurrentTraceNode) startNode; +// int j = concNode.getOrder(); +// if (!concNode.isAtomic() && concNode.getWrittenVariables().size() > 0) { +// return concNode.getLinkedTraceNode(); +// } +// +// // find the individual trace to use the find data dependenccy +// String varID = Variable.truncateSimpleID(varValue.getVarID()); +// String headID = Variable.truncateSimpleID(varValue.getAliasVarID()); +// +// +// for(int i=j-2; i>=1; i--) { +// ConcurrentTraceNode node = this.getSequentialTrace().get(i); +// for(VarValue writtenValue: node.getWrittenVariables()) { +// +// if (isOnHeap && writtenValue.getAliasVarID() != null +// && writtenValue.getAliasVarID().equals(varValue.getAliasVarID())) { +// return node; +// } +// if (!isOnHeap && concNode.getCurrentThread() != node.getCurrentThread()) { +// // when the value is on a different thread +// continue; +// } +// +// String wVarID = Variable.truncateSimpleID(writtenValue.getVarID()); +// String wHeadID = Variable.truncateSimpleID(writtenValue.getAliasVarID()); +// +// if(wVarID != null && wVarID.equals(varID) && node.getCurrentTraceId() +// == concNode.getCurrentTraceId()) { +// return node; +// } +// +// if(wHeadID != null && wHeadID.equals(headID)) { +// return node; +// } +// +// VarValue childValue = writtenValue.findVarValue(varID, headID); +// if(childValue != null) { +// return node; +// } +// +// } +// } +// return null; +// } +// +// + + + + /** + * Split a given trace node into read and write concurrent + * @param node + * @return + */ + public static List splitTraceNode(TraceNode node, int traceId, long threadId) { + ArrayList result = new ArrayList<>(); + ConcurrentTraceNode r = new ConcurrentTraceNode(node, traceId, threadId); + node.setBoundTraceNode(r); + r.setReadVariables(node.getReadVariables()); + r.setWrittenVariables(node.getWrittenVariables()); + r.initialTraceNode = node; + r.isAtomic = true; + return List.of(r); + +// if (node.getReadVariables().size() == 0 && node.getWrittenVariables().size() == 0) { +// ConcurrentTraceNode r = new ConcurrentTraceNode(node, threadId); +// node.setConcurrentTraceNode(r); +// return List.of(r); +// } +// if (node.getReadVariables().size() > 0) { +// ConcurrentTraceNode readVariables = new ConcurrentTraceNode(node, threadId); +// readVariables.setReadVariables(node.getReadVariables()); +// node.setConcurrentTraceNode(readVariables); +// result.add(readVariables); +// } +// if (node.getWrittenVariables().size() > 0) { +// ConcurrentTraceNode writeVariables = new ConcurrentTraceNode(node, threadId); +// writeVariables.setWrittenVariables(node.getWrittenVariables()); +// result.add(writeVariables); +// if (result.size() == 2) { +// result.get(1).linkedTraceNode = result.get(0); +// result.get(0).linkedTraceNode = result.get(1); +// } +// } +// return result; + } + + @Override + public TraceNode getInvocationMethodOrDominator() { + TraceNode invcationParent = super.getInvocationMethodOrDominator(); + if (invcationParent != null) return invcationParent; + ConcurrentTrace concTrace = this.getConcurrentTrace(); + Trace currTrace = this.initialTraceNode.getTrace(); + ThreadId threadId = currTrace.getInnerThreadId(); + if (threadId == null) return null; + if (threadId.getSpawnOrder() != -1) { + Trace parentTrace = concTrace.getCorrespondingTrace(threadId.getParent()); + if (parentTrace != null) return parentTrace.getTraceNode(threadId.getSpawnOrder()); + } + + // find the node which invoked this thread + return null; + + } + + @Override + public TraceNode getDataDominator(VarValue readVar) { + return this.getConcurrentTrace().findDataDependency(this, readVar); + } + + @Override + public String toString() { + // TODO Auto-generated method stub + return "Current threadId: " + this.currentTraceId + " " + super.toString(); + } + + + + + +} \ No newline at end of file diff --git a/microbat/src/main/microbat/model/trace/Trace.java b/microbat/src/main/microbat/model/trace/Trace.java index 81e699686..5ee9a292c 100644 --- a/microbat/src/main/microbat/model/trace/Trace.java +++ b/microbat/src/main/microbat/model/trace/Trace.java @@ -1,8 +1,10 @@ package microbat.model.trace; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; @@ -12,6 +14,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import microbat.codeanalysis.ast.LocalVariableScopes; +import microbat.instrumentation.model.id.ThreadId; import microbat.model.AttributionVar; import microbat.model.BreakPoint; import microbat.model.Scope; @@ -36,6 +39,51 @@ public class Trace { private boolean isMain; private String threadName; private String id; + private ThreadId innerThreadId; + private long memoryUsed = -1; + + public void setMemoryUsed(long memoryUsed) { + this.memoryUsed = memoryUsed; + } + + public long getMemoryUsed() { + return this.memoryUsed; + } + + /** + * Used to detect deadlocks + */ + private List acquiredLocks = null; + private Long acquiringLock = null; + + public Long getAcquiringLock() { + return this.acquiringLock; + } + + public List getAcquiredLocks() { + return this.acquiredLocks; + } + + public void setAcquiredLocks(Collection values) { + this.acquiredLocks = new LinkedList(values); + } + + public void setAcquiringLock(Long value) { + this.acquiringLock = value; + } + + /** + * Getter for inner thread id. Representation of a + * thread id using parent counter. + * @return + */ + public ThreadId getInnerThreadId() { + return innerThreadId; + } + + public void setInnerThreadId(ThreadId threadId) { + this.innerThreadId = threadId; + } /** * This variable is to trace whether the variables in different lines are the same @@ -254,6 +302,9 @@ public void conductStateDiff() { * @return */ public TraceNode findDataDependency(TraceNode checkingNode, VarValue readVar) { + if (checkingNode.getBound() != null) { + return checkingNode.getBound().getDataDominator(readVar); + } return findProducer(readVar, checkingNode); } diff --git a/microbat/src/main/microbat/model/trace/TraceNode.java b/microbat/src/main/microbat/model/trace/TraceNode.java index 39bdd60ab..1cc4356d1 100644 --- a/microbat/src/main/microbat/model/trace/TraceNode.java +++ b/microbat/src/main/microbat/model/trace/TraceNode.java @@ -57,7 +57,6 @@ public class TraceNode{ // private Map> dataDominators = new HashMap<>(); // private Map> dataDominatees = new HashMap<>(); - private TraceNode controlDominator; private List controlDominatees = new ArrayList<>(); @@ -96,6 +95,17 @@ public class TraceNode{ private transient double sliceBreakerProbability = 0; + // Utility binding + private ConcurrentTraceNode boundTraceNode; + + public void setBoundTraceNode(ConcurrentTraceNode boundNode) { + this.boundTraceNode = boundNode; + } + + public ConcurrentTraceNode getBound() { + return this.boundTraceNode; + } + /** * the first element of the pair is the read variable list, the second element is the * written variable list. @@ -169,6 +179,9 @@ public TraceNode getDataDominator(VarValue readVar) { // } // // return dataDominator; + if (this.boundTraceNode != null) { + return this.boundTraceNode.getDataDominator(readVar); + } return this.trace.findDataDependency(this, readVar); } @@ -270,6 +283,8 @@ public boolean equals(Object obj) { TraceNode other = (TraceNode) obj; if (order != other.order) return false; + if (trace.getThreadId() != other.getTrace().getThreadId()) + return false; return true; } @@ -492,7 +507,7 @@ public String getShortMethodSignature() { public Map getDataDominators() { Map dataDominators = new HashMap<>(); for(VarValue readVar: this.getReadVariables()){ - TraceNode dominator = this.trace.findDataDependency(this, readVar); + TraceNode dominator = this.getDataDominator(readVar); if(dominator != null){ dataDominators.put(dominator, readVar); } @@ -503,8 +518,12 @@ public Map getDataDominators() { public Map getDataDominatee() { Map dataDominatees = new HashMap<>(); + Trace trace = this.trace; + if (this.getBound() != null) { + trace = this.getBound().getConcurrentTrace(); + } for(VarValue writtenVar: this.getWrittenVariables()){ - List dominatees = this.trace.findDataDependentee(this, writtenVar); + List dominatees = trace.findDataDependentee(this, writtenVar); for(TraceNode dominatee: dominatees){ dataDominatees.put(dominatee, writtenVar); } diff --git a/microbat/src/main/microbat/preference/MicrobatPreference.java b/microbat/src/main/microbat/preference/MicrobatPreference.java index a60347065..9da70a8b2 100644 --- a/microbat/src/main/microbat/preference/MicrobatPreference.java +++ b/microbat/src/main/microbat/preference/MicrobatPreference.java @@ -21,7 +21,9 @@ import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; +import java_cup.internal_error; import microbat.Activator; +import microbat.instrumentation.instr.aggreplay.ReplayMode; import microbat.util.SWTFactory; import microbat.util.Settings; @@ -50,6 +52,7 @@ public void init(IWorkbench workbench) { this.defaultRecordSnapshot = Activator.getDefault().getPreferenceStore().getString(RECORD_SNAPSHORT); this.defaultRunWithDebugMode = Activator.getDefault().getPreferenceStore().getString(RUN_WITH_DEBUG_MODE); this.defaultAdvancedDetailInspector = Activator.getDefault().getPreferenceStore().getString(APPLY_ADVANCE_INSPECTOR); + this.defaultTimeoutLong = Long.parseLong(Activator.getDefault().getPreferenceStore().getString(TIMEOUT)); this.defaultStepLimit = getStepLimit(); this.defaultRunTest = Activator.getDefault().getPreferenceStore().getString(RUN_TEST); this.defaultVariableLayer = getVariableValue(); @@ -57,6 +60,7 @@ public void init(IWorkbench workbench) { this.defaultApplyRecodingOptimization = Activator.getDefault().getPreferenceStore().getString(RECORDING_OPTIMIZATION); this.defaultEnableMethodSplitting = Activator.getDefault().getPreferenceStore().getBoolean(REQUIRE_METHOD_SPLITTING); this.defaultRunWithDebugMode = Activator.getDefault().getPreferenceStore().getString(RUN_WITH_DEBUG_MODE); + this.defaultReplayMode = ReplayMode.parse(Activator.getDefault().getPreferenceStore().getString(REPLAY_MODE)); } public static String getStepLimit() { @@ -85,7 +89,10 @@ public static String getValue(String key) { public static final String RECORDING_OPTIMIZATION = "recording_optimization"; public static final String REQUIRE_METHOD_SPLITTING = "enableMethodSplitting"; public static final String SUPPORT_CONCURRENT_TRACE = "supportConcurrentTrace"; + public static final String AGGREPLAY_CONCURRENT_RECORDING = "aggrePlayConcurrentRecording"; public static final String RUN_WITH_DEBUG_MODE = "runWithDebugMode"; + public static final String TIMEOUT = "timeOut"; + public static final String REPLAY_MODE = "replay_mode"; private Combo projectCombo; private Text lanuchClassText; @@ -94,6 +101,7 @@ public static String getValue(String key) { private Text lineNumberText; private Text stepLimitText; private Text variableLayerText; + private Text timeOutText; private Button recordSnapshotButton; private Button supportConcurrentTraceButton; private Button recordingOptimizationButton; @@ -102,6 +110,7 @@ public static String getValue(String key) { private Button runWithDebugModeButton; private Button enableMethodSplittingButton; private Text java7HomePathText; + private Combo replayModeSelectorCombo; private String defaultTargetProject = ""; private String defaultLanuchClass = ""; @@ -117,6 +126,8 @@ public static String getValue(String key) { private String defaultJava7HomePath; private String defaultApplyRecodingOptimization; private String defaultRunWithDebugMode = "false"; + private Long defaultTimeoutLong = 10000L; + private ReplayMode defaultReplayMode = ReplayMode.AGGR; private boolean defaultEnableMethodSplitting; @Override @@ -181,6 +192,19 @@ private void createSettingGroup(Composite parent){ variableLayerText.setLayoutData(variableLayerTextData); variableLayerText.setToolTipText("how many layers of variable children does the debugger need to retrieve, -1 means infinite."); + Label timeOutLabel = new Label(settingGroup, SWT.NONE); + timeOutLabel.setText("Timeout: "); + + + GridData timeOutLayerTextData = new GridData(SWT.FILL, SWT.FILL, true, false); + timeOutLayerTextData.horizontalSpan = 2; + timeOutText = new Text(settingGroup, SWT.BORDER); + timeOutText.setText(this.defaultTimeoutLong + ""); + timeOutText.setLayoutData(variableLayerTextData); + timeOutText.setToolTipText("How long do you want the timeout, -1 means infinite."); + + + supportConcurrentTraceButton = new Button(settingGroup, SWT.CHECK); supportConcurrentTraceButton.setText("Support concurrent trace"); GridData supportConcurrentTraceButtonData = new GridData(SWT.FILL, SWT.FILL, true, false); @@ -223,6 +247,26 @@ private void createSettingGroup(Composite parent){ enableMethodSplittingButton = SWTFactory.createCheckbox(settingGroup, "Enable method splitting function", 2); enableMethodSplittingButton.setSelection(this.defaultEnableMethodSplitting); + + createReplaySelector(parent); + } + + + private void createReplaySelector(Composite parent) { + Label replaySelectorLabel = new Label(parent, SWT.BORDER); + replaySelectorLabel.setText("Select replay mode"); + replaySelectorLabel.setToolTipText("Strict - Chance of deadlock when sync methods used"); + this.replayModeSelectorCombo = new Combo(parent, SWT.BORDER); + ReplayMode[] replayModes = ReplayMode.values(); + String[] dataStrings = new String[replayModes.length]; + for (int i = 0; i < dataStrings.length; ++i) { + dataStrings[i] = replayModes[i].toString(); + } + replayModeSelectorCombo.setItems(dataStrings); + replayModeSelectorCombo.setText(defaultReplayMode.toString()); + GridData comboData = new GridData(SWT.FILL, SWT.FILL, true, false); + comboData.horizontalSpan = 2; + this.replayModeSelectorCombo.setLayoutData(comboData); } private void createSeedStatementGroup(Composite parent){ @@ -284,6 +328,7 @@ public boolean performOk(){ preferences.put(TARGET_PORJECT, this.projectCombo.getText()); preferences.put(LANUCH_CLASS, this.lanuchClassText.getText()); preferences.put(TEST_METHOD, this.testMethodText.getText()); + preferences.put(TIMEOUT, this.timeOutText.getText()); // preferences.put(CLASS_NAME, this.classNameText.getText()); // preferences.put(LINE_NUMBER, this.lineNumberText.getText()); preferences.put(RECORD_SNAPSHORT, String.valueOf(this.recordSnapshotButton.getSelection())); @@ -296,10 +341,12 @@ public boolean performOk(){ preferences.putBoolean(REQUIRE_METHOD_SPLITTING, this.enableMethodSplittingButton.getSelection()); preferences.put(SUPPORT_CONCURRENT_TRACE, String.valueOf(this.supportConcurrentTraceButton.getSelection())); preferences.put(RUN_WITH_DEBUG_MODE, String.valueOf(this.runWithDebugModeButton.getSelection())); + preferences.put(REPLAY_MODE, this.replayModeSelectorCombo.getText()); Activator.getDefault().getPreferenceStore().putValue(TARGET_PORJECT, this.projectCombo.getText()); Activator.getDefault().getPreferenceStore().putValue(LANUCH_CLASS, this.lanuchClassText.getText()); Activator.getDefault().getPreferenceStore().putValue(TEST_METHOD, this.testMethodText.getText()); + Activator.getDefault().getPreferenceStore().putValue(TIMEOUT, timeOutText.getText()); // Activator.getDefault().getPreferenceStore().putValue(CLASS_NAME, this.classNameText.getText()); // Activator.getDefault().getPreferenceStore().putValue(LINE_NUMBER, this.lineNumberText.getText()); Activator.getDefault().getPreferenceStore().putValue(RECORD_SNAPSHORT, String.valueOf(this.recordSnapshotButton.getSelection())); @@ -312,7 +359,7 @@ public boolean performOk(){ Activator.getDefault().getPreferenceStore().putValue(REQUIRE_METHOD_SPLITTING, String.valueOf(this.enableMethodSplittingButton.getSelection())); Activator.getDefault().getPreferenceStore().putValue(SUPPORT_CONCURRENT_TRACE, String.valueOf(this.supportConcurrentTraceButton.getSelection())); Activator.getDefault().getPreferenceStore().putValue(RUN_WITH_DEBUG_MODE, String.valueOf(this.runWithDebugModeButton.getSelection())); - + Activator.getDefault().getPreferenceStore().putValue(REPLAY_MODE, this.replayModeSelectorCombo.getText()); confirmChanges(); return true; @@ -323,6 +370,7 @@ private void confirmChanges(){ Settings.projectName = this.projectCombo.getText(); Settings.launchClass = this.lanuchClassText.getText(); Settings.testMethod = this.testMethodText.getText(); + Settings.timeLimit = Long.parseLong(this.timeOutText.getText()); // Settings.buggyClassName = this.classNameText.getText(); // Settings.buggyLineNumber = this.lineNumberText.getText(); Settings.isRecordSnapshot = this.recordSnapshotButton.getSelection(); @@ -333,6 +381,7 @@ private void confirmChanges(){ Settings.applyLibraryOptimization = this.recordingOptimizationButton.getSelection(); Settings.supportConcurrentTrace = this.supportConcurrentTraceButton.getSelection(); Settings.isRunWtihDebugMode = this.runWithDebugModeButton.getSelection(); + Settings.replayMode = ReplayMode.parse(this.replayModeSelectorCombo.getText()); } private String[] getProjectsInWorkspace(){ diff --git a/microbat/src/main/microbat/util/MicroBatUtil.java b/microbat/src/main/microbat/util/MicroBatUtil.java index 5b9616601..30d4032e8 100644 --- a/microbat/src/main/microbat/util/MicroBatUtil.java +++ b/microbat/src/main/microbat/util/MicroBatUtil.java @@ -37,6 +37,14 @@ public class MicroBatUtil { private static Logger log = LoggerFactory.getLogger(MicroBatUtil.class); private MicroBatUtil(){} + public static boolean checkTestResult(String msg) { + int sIdx = msg.indexOf(";"); + if (sIdx < 0 || msg.length() < sIdx) { + return false; + } + return Boolean.valueOf(msg.substring(0, sIdx)); + } + public static String getProjectPath(String projectName){ IWorkspaceRoot myWorkspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IProject iProject = myWorkspaceRoot.getProject(projectName); diff --git a/microbat/src/main/microbat/util/Settings.java b/microbat/src/main/microbat/util/Settings.java index 8d6edee12..b0473f0ac 100644 --- a/microbat/src/main/microbat/util/Settings.java +++ b/microbat/src/main/microbat/util/Settings.java @@ -2,13 +2,16 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.Optional; import java.util.Stack; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.dom.CompilationUnit; +import fj.P; import microbat.Activator; import microbat.handler.CheckingState; +import microbat.instrumentation.instr.aggreplay.ReplayMode; import microbat.model.UserInterestedVariables; import microbat.model.trace.PotentialCorrectPatternList; import microbat.preference.MicrobatPreference; @@ -19,11 +22,20 @@ public class Settings { public static String launchClass; public static String testMethod; + /** + * Used to indicate the dump file used for record and replay. + * If the dump file is not provided, a temp file is used. + */ + public static Optional concurrentDumpFile = Optional.empty(); + public static boolean isRecordSnapshot; public static boolean isApplyAdvancedInspector; public static boolean isRunTest; public static boolean isRunWtihDebugMode; public static int stepLimit; + public static long timeLimit; + public static ReplayMode replayMode; + public static boolean isForce3Or4; private static Integer variableLayer; @@ -54,6 +66,7 @@ public class Settings { static{ if(Activator.getDefault() != null){ try{ + replayMode = ReplayMode.parse(Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.REPLAY_MODE)); projectName = Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.TARGET_PORJECT); launchClass = Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.LANUCH_CLASS); testMethod = Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.TEST_METHOD); @@ -68,6 +81,16 @@ public class Settings { if(stepLimit == 0){ stepLimit = 5000; } + String timeLimitString = Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.TIMEOUT); + try { + timeLimit = Long.valueOf(timeLimitString); + } catch (NumberFormatException except) { + + } + + if (timeLimit == 0) { + timeLimit = 10000L; + } String varLayerString = Activator.getDefault().getPreferenceStore().getString(MicrobatPreference.VARIABLE_LAYER); variableLayer = Integer.valueOf(varLayerString); @@ -120,5 +143,6 @@ public static void setVariableLayer(Integer level){ public static HashMap iCompilationUnitMap = new HashMap<>(); public static boolean enableLoopInference = true; public static boolean supportConcurrentTrace; + public static boolean supportAggrePlayTrace; } diff --git a/microbat/src/main/microbat/views/ConcurrentTraceView.java b/microbat/src/main/microbat/views/ConcurrentTraceView.java index a437307db..8a434d1bf 100644 --- a/microbat/src/main/microbat/views/ConcurrentTraceView.java +++ b/microbat/src/main/microbat/views/ConcurrentTraceView.java @@ -72,6 +72,7 @@ public class ConcurrentTraceView extends TraceView { protected Map traceMap; + protected Map treeViewMap = new HashMap<>(); protected Trace curTrace; public Trace getCurrentTrace() { @@ -198,6 +199,8 @@ public void jumpToNode(Trace trace, int order, boolean refreshProgramState) { } /** keep the original expanded list */ + curTreeViewer = this.treeViewMap.get(trace.getThreadId()); + Object[] expandedElements = curTreeViewer.getExpandedElements(); for (Object obj : expandedElements) { TraceNode tn = (TraceNode) obj; @@ -290,6 +293,7 @@ public void creatPart(Composite parent, Trace trace) { String threadName = trace.getThreadName(); group.setText(threadName != null ? threadName : "NA"); TreeViewer viewer = new TreeViewer(group, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER); + treeViewMap.put(trace.getThreadId(), viewer); viewer.setContentProvider(new TraceContentProvider()); viewer.setLabelProvider(new TraceLabelProvider()); viewerList.add(viewer); @@ -487,7 +491,7 @@ public void menuAboutToShow(IMenuManager manager) { viewer.getTree().setMenu(menuMgr.createContextMenu(viewer.getTree())); } - protected void otherViewsBehavior(TraceNode node) { + public void otherViewsBehavior(TraceNode node) { DebugFeedbackView feedbackView = MicroBatViews.getDebugFeedbackView(); if (this.refreshProgramState) { @@ -502,7 +506,7 @@ protected void otherViewsBehavior(TraceNode node) { } public void updateData() { - + treeViewMap.clear(); Control[] childs = tracePanel.getChildren(); for (int k = 0; k < childs.length; k++) { childs[k].dispose(); diff --git a/microbat_examples/resources/instrumentator.jar b/microbat_examples/resources/instrumentator.jar index 4e7b7bf2f..a1046e781 100644 Binary files a/microbat_examples/resources/instrumentator.jar and b/microbat_examples/resources/instrumentator.jar differ diff --git a/microbat_instrumentator/.classpath b/microbat_instrumentator/.classpath index 75ac85ffd..e461609b5 100644 --- a/microbat_instrumentator/.classpath +++ b/microbat_instrumentator/.classpath @@ -11,7 +11,7 @@ - + diff --git a/microbat_instrumentator/.settings/org.eclipse.jdt.core.prefs b/microbat_instrumentator/.settings/org.eclipse.jdt.core.prefs index 980b98c1d..ace45ceef 100644 --- a/microbat_instrumentator/.settings/org.eclipse.jdt.core.prefs +++ b/microbat_instrumentator/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,12 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/microbat_instrumentator/lib/instrumentator_agent_v02.jar b/microbat_instrumentator/lib/instrumentator_agent_v02.jar index 2e2b0e5af..8b55a5568 100644 Binary files a/microbat_instrumentator/lib/instrumentator_agent_v02.jar and b/microbat_instrumentator/lib/instrumentator_agent_v02.jar differ diff --git a/microbat_instrumentator/src/core/microbat/model/trace/Trace.java b/microbat_instrumentator/src/core/microbat/model/trace/Trace.java index 6447d70d5..4d45c9478 100644 --- a/microbat_instrumentator/src/core/microbat/model/trace/Trace.java +++ b/microbat_instrumentator/src/core/microbat/model/trace/Trace.java @@ -1,7 +1,9 @@ package microbat.model.trace; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -15,6 +17,7 @@ import microbat.codeanalysis.bytecode.CFGConstructor; import microbat.codeanalysis.bytecode.CFGNode; import microbat.codeanalysis.bytecode.MethodFinderByLine; +import microbat.instrumentation.model.id.ThreadId; import microbat.model.BreakPoint; import microbat.model.ClassLocation; import microbat.model.ControlScope; @@ -37,7 +40,50 @@ public class Trace { private long threadId; private boolean isMain; private String threadName; - + private ThreadId innerThreadId; + private long memoryUsed = -1; + /** + * Used to detect deadlocks + */ + private List acquiredLocks = null; + private Long acquiringLock = null; + + public void setMemoryUsed(long memoryUsed) { + this.memoryUsed = memoryUsed; + } + + public long getMemoryUsed() { + return this.memoryUsed; + } + + public Long getAcquiringLock() { + if (this.acquiringLock == null) { + return -1L; + } + return this.acquiringLock; + } + + public List getAcquiredLocks() { + return this.acquiredLocks; + } + + public void setAcquiredLocks(Collection values) { + this.acquiredLocks = new LinkedList(values); + } + + public void setAcquiringLock(Long value) { + this.acquiringLock = value; + } + + + public ThreadId getInnerThreadId() { + return innerThreadId; + } + + public void setInnerThreadId(ThreadId threadId) { + this.innerThreadId = threadId; + } + /** * This variable is to trace whether the variables in different lines are the same * local variable. @@ -414,8 +460,9 @@ public void setIncludedLibraryClasses(List includedLibraryClasses) { this.includedLibraryClasses = includedLibraryClasses; } + // TODO(Gab): Remove the botch Linked list (used cause of an unknown concurrent access bug) public List getExcludedLibraryClasses() { - return CollectionUtils.nullToEmpty(excludedLibraryClasses); + return new LinkedList<>(CollectionUtils.nullToEmpty(excludedLibraryClasses)); } public void setExcludedLibraryClasses(List excludedLibraryClasses) { diff --git a/microbat_instrumentator/src/core/microbat/model/trace/TraceNode.java b/microbat_instrumentator/src/core/microbat/model/trace/TraceNode.java index e32a6bbfa..3c67eb563 100644 --- a/microbat_instrumentator/src/core/microbat/model/trace/TraceNode.java +++ b/microbat_instrumentator/src/core/microbat/model/trace/TraceNode.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -93,6 +94,7 @@ public class TraceNode{ private long timestamp; private String bytecode; + public TraceNode(BreakPoint breakPoint, BreakPointValue programState, int order, Trace trace, String bytecode) { this(breakPoint, programState, order, trace, -1, -1, System.currentTimeMillis(), bytecode); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/Agent.java b/microbat_instrumentator/src/main/microbat/instrumentation/Agent.java index bd4ecf173..67d025fb5 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/Agent.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/Agent.java @@ -6,6 +6,7 @@ import java.util.List; import microbat.instrumentation.filter.GlobalFilterChecker; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; import microbat.instrumentation.runtime.ExecutionTracer; import microbat.instrumentation.runtime.IExecutionTracer; @@ -76,12 +77,37 @@ public void run() { }); } + public static void _forceProgramStop(String programMsg) { + String currentThreadName = Thread.currentThread().getName(); + assert(currentThreadName.equals(TimeoutThread.ID)); + Agent.programMsg = programMsg; + for (IExecutionTracer tracer: ExecutionTracer.getAllThreadStore()) { + if (tracer instanceof ExecutionTracer) { + ExecutionTracer executionTracer = (ExecutionTracer) tracer; + executionTracer.setLock(); + } + } + ExecutionTracer.getMainThreadStore().lock(); + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + stop(); + ExecutionTracer.getMainThreadStore().unLock(); + Runtime.getRuntime().exit(1); // force program to exit to avoid getting stuck by background running threads. + } + /** * This method will be instrumented at the end of main() method. * @param programMsg */ public static void _exitProgram(String programMsg) { - if(Thread.currentThread().getName().equals("main")) { + // this is necessary to handle exitProgram called in test case + if (!ExecutionTracer.isRecordingOrStarted() && !ExecutionTracer.isShutdown()) return; + String currentThreadName = Thread.currentThread().getName(); + if(currentThreadName.equals("main") || currentThreadName.equals(TimeoutThread.ID)) { ExecutionTracer.getMainThreadStore().lock(); Agent.programMsg = programMsg; diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/AgentFactory.java b/microbat_instrumentator/src/main/microbat/instrumentation/AgentFactory.java index fe1a74007..b447c4849 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/AgentFactory.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/AgentFactory.java @@ -5,6 +5,10 @@ import microbat.instrumentation.AgentParams.LogType; import microbat.instrumentation.cfgcoverage.CoverageAgent; import microbat.instrumentation.cfgcoverage.CoverageAgentParams; +import microbat.instrumentation.instr.aggreplay.agents.AggrePlayReplayAgent; +import microbat.instrumentation.instr.aggreplay.agents.SharedVariableAgent; +import microbat.instrumentation.instr.aggreplay.agents.RNRRecordingAgent; +import microbat.instrumentation.precheck.MemoryMeasurementAgent; import microbat.instrumentation.precheck.PrecheckAgent; /** @@ -27,6 +31,18 @@ public static Agent createAgent(CommandLine cmd, Instrumentation inst) { } else if (cmd.getBoolean(AgentParams.OPT_PRECHECK, false)) { agent = new PrecheckAgent(cmd, inst); agent.setInstrumentation(inst); + } else if (cmd.getBoolean(AgentParams.OPT_SHARED_DETECTION, false)) { + agent = SharedVariableAgent.getAgent(cmd); + agent.setInstrumentation(inst); + } else if (cmd.getBoolean(AgentParams.OPT_CONC_RECORD, false)) { + agent = RNRRecordingAgent.getAttached(cmd); + agent.setInstrumentation(inst); + } else if (cmd.getBoolean(AgentParams.OPT_CONC_REPLAY, false)) { + agent = AggrePlayReplayAgent.getAttached(cmd); + agent.setInstrumentation(inst); + } else if (cmd.getBoolean(AgentParams.MEASURE_MEM, false)) { + agent = MemoryMeasurementAgent.getMeasurementAgent(cmd); + agent.setInstrumentation(inst); } else { agent = new TraceAgent(cmd); agent.setInstrumentation(inst); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/AgentParams.java b/microbat_instrumentator/src/main/microbat/instrumentation/AgentParams.java index dd720793b..af8fad0c7 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/AgentParams.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/AgentParams.java @@ -7,6 +7,7 @@ import java.util.Set; import microbat.instrumentation.filter.CodeRangeEntry; +import microbat.instrumentation.instr.aggreplay.ReplayMode; import microbat.instrumentation.instr.instruction.info.EntryPoint; import microbat.instrumentation.utils.FileUtils; import microbat.sql.TraceRecorder; @@ -24,11 +25,15 @@ public class AgentParams extends CommonParams { public static final String OPT_WORKING_DIR = CommonParams.OPT_WORKING_DIR; public static final String OPT_LOG = CommonParams.OPT_LOG; + public static final String OPT_SHARED_DETECTION = "detect_share"; + public static final String OPT_CONC_RECORD = "record_conc"; + public static final String OPT_CONC_REPLAY = "replay_conc"; public static final String OPT_PRECHECK = "precheck"; public static final String OPT_ENTRY_POINT = "entry_point"; public static final String OPT_LAUNCH_CLASS = "launch_class"; public static final String OPT_JAVA_HOME = "java_home"; public static final String OPT_DUMP_FILE = "dump_file_path"; + public static final String OPT_CONC_RECORD_DUMP = "conc_dump_file_path"; public static final String OPT_TCP_PORT = "tcp_port"; public static final String OPT_INCLUDES = "includes"; public static final String OPT_EXCLUDES = "excludes"; @@ -43,10 +48,15 @@ public class AgentParams extends CommonParams { public static final String OPT_CODE_RANGE = "code_range"; public static final String OPT_TRACE_RECORDER = "trace_recorder"; public static final String OPT_RUN_ID = "run_id"; + public static final String MEASURE_MEM = "measure_mem"; + public static final String TIMEOUT = "time_out"; // the timeout of instrumentation. + public static final String REPLAY_MODE = "replay_mode"; + private long timeOut = 100000L; + private ReplayMode replayMode = ReplayMode.AGGR; private boolean precheck; private EntryPoint entryPoint; - + private String concDumpFile; private String javaHome; private String launchClass; private int tcpPort = -1; @@ -68,6 +78,7 @@ public AgentParams(CommandLine cmd) { super(cmd); precheck = cmd.getBoolean(OPT_PRECHECK, false); String entryPointStr = cmd.getString(OPT_ENTRY_POINT); + replayMode = cmd.getReplayMode(REPLAY_MODE); if (entryPointStr != null) { int idx = entryPointStr.lastIndexOf("."); String mainClass = entryPointStr.substring(0, idx); @@ -75,7 +86,9 @@ public AgentParams(CommandLine cmd) { EntryPoint entryPoint = new EntryPoint(mainClass, mainMethod); this.entryPoint = entryPoint; } - + if (cmd.getString(TIMEOUT) != null) { + this.timeOut = Long.parseLong(cmd.getString(TIMEOUT)); + } setJavaHome(cmd.getString(OPT_JAVA_HOME)); setWorkingDirectory(cmd.getString(OPT_WORKING_DIR)); @@ -87,6 +100,7 @@ public AgentParams(CommandLine cmd) { tcpPort = cmd.getInt(OPT_TCP_PORT, -1); dumpFile = cmd.getString(OPT_DUMP_FILE); + concDumpFile = cmd.getString(OPT_CONC_RECORD_DUMP); includesExpression = getFilterExpression(cmd, OPT_INCLUDES_FILE, OPT_INCLUDES); excludesExpression = getFilterExpression(cmd, OPT_EXCLUDES_FILE, OPT_EXCLUDES); variableLayer = cmd.getInt(OPT_VARIABLE_LAYER, 2); @@ -127,6 +141,10 @@ private static String getFilterExpression(CommandLine cmd, String fileOpt, Strin return expression; } + public long getTimeOut() { + return this.timeOut; + } + public EntryPoint getEntryPoint() { return entryPoint; } @@ -151,6 +169,10 @@ public void setLaunchClass(String launchClass) { this.launchClass = launchClass; } + public ReplayMode getReplayMode() { + return replayMode; + } + public int getTcpPort() { return tcpPort; } @@ -158,6 +180,10 @@ public int getTcpPort() { public String getDumpFile() { return dumpFile; } + + public String getConcDumpFile() { + return concDumpFile; + } public String getExcludesExpression() { return excludesExpression; diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/CommandLine.java b/microbat_instrumentator/src/main/microbat/instrumentation/CommandLine.java index 50665dbe3..1d81cc65d 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/CommandLine.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/CommandLine.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; +import microbat.instrumentation.instr.aggreplay.ReplayMode; import sav.common.core.utils.CollectionUtils; /** @@ -64,4 +65,16 @@ public Set getStringSet(String option) { public String getString(String option) { return argMap.get(option); } + + public ReplayMode getReplayMode(String option) { + String valueString = argMap.get(option); + if (valueString == null) { + return ReplayMode.AGGR; + } + ReplayMode resultMode = ReplayMode.parse(valueString); + if (resultMode == null) { + return ReplayMode.AGGR; + } + return resultMode; + } } diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/TraceAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/TraceAgent.java index 6e051e66d..196ed9913 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/TraceAgent.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/TraceAgent.java @@ -7,7 +7,10 @@ import microbat.instrumentation.filter.CodeRangeUserFilter; import microbat.instrumentation.filter.GlobalFilterChecker; import microbat.instrumentation.filter.OverLongMethodFilter; +import microbat.instrumentation.instr.SystemClassTransformer; import microbat.instrumentation.instr.TraceTransformer; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; +import microbat.instrumentation.model.generator.ThreadIdGenerator; import microbat.instrumentation.runtime.ExecutionTracer; import microbat.instrumentation.runtime.IExecutionTracer; import microbat.model.trace.Trace; @@ -18,19 +21,32 @@ public class TraceAgent extends Agent { private AgentParams agentParams; + protected static ThreadIdGenerator threadIdGenerator = new ThreadIdGenerator(); + private TimeoutThread timeoutThread = new TimeoutThread(); // private StopTimer timer; public TraceAgent(CommandLine cmd) { this.agentParams = AgentParams.initFrom(cmd); } + + public static void _onThreadStart(Thread thread) { + if (ExecutionTracer.isRecordingOrStarted()) { + int order = ExecutionTracer.getCurrentThreadStore().getLatestOrder(); + threadIdGenerator.createId(thread, order); + } + } public void startup0(long vmStartupTime, long agentPreStartup) { + timeoutThread.setTimeout(agentParams.getTimeOut()); + if (!timeoutThread.isAlive()) timeoutThread.start(); + SystemClassTransformer.attachThreadId(getInstrumentation(), TraceAgent.class); // timer = new StopTimer("Trace Construction"); // timer.newPoint("Execution"); /* init filter */ AppJavaClassPath appPath = agentParams.initAppClassPath(); GlobalFilterChecker.setup(appPath, agentParams.getIncludesExpression(), agentParams.getExcludesExpression()); ExecutionTracer.appJavaClassPath = appPath; + System.out.println("Exection class paths" + ExecutionTracer.appJavaClassPath.getClasspaths()); ExecutionTracer.variableLayer = agentParams.getVariableLayer(); ExecutionTracer.setStepLimit(agentParams.getStepLimit()); if (!agentParams.isRequireMethodSplit()) { @@ -62,13 +78,16 @@ public void shutdown() throws Exception { Trace trace = tracer.getTrace(); trace.setThreadId(tracer.getThreadId()); + trace.setInnerThreadId(threadIdGenerator.getId(tracer.getThreadId())); trace.setThreadName(tracer.getThreadName()); trace.setMain(ExecutionTracer.getMainThreadStore().equals(tracer)); - + trace.setAppJavaClassPath(ExecutionTracer.appJavaClassPath); + trace.setAcquiredLocks(tracer.getLockAcquired()); + trace.setAcquiringLock(tracer.getAcquiringLock()); constructTrace(trace); traceList.add(trace); } - + // timer.newPoint("Saving trace"); Recorder.create(agentParams).store(traceList); // AgentLogger.debug(timer.getResultString()); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/cfgcoverage/instr/CoverageInstrumenter.java b/microbat_instrumentator/src/main/microbat/instrumentation/cfgcoverage/instr/CoverageInstrumenter.java index 652136422..1df33e1bf 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/cfgcoverage/instr/CoverageInstrumenter.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/cfgcoverage/instr/CoverageInstrumenter.java @@ -337,7 +337,6 @@ public void setEntryPoint(String entryPoint) { @Override protected boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, boolean isAppClass, boolean isMainMethod, boolean isEntry) { - // TODO Auto-generated method stub return false; } } diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/AbstractInstrumenter.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/AbstractInstrumenter.java index 7f2709ff7..51bdf1d62 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/AbstractInstrumenter.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/AbstractInstrumenter.java @@ -36,6 +36,9 @@ public abstract class AbstractInstrumenter { public byte[] instrument(String classFName, byte[] classfileBuffer) throws Exception { String className = classFName.replace("/", "."); + if (!shouldInstrument(className)) { + return null; + } ClassParser cp = new ClassParser(new java.io.ByteArrayInputStream(classfileBuffer), classFName); JavaClass jc = cp.parse(); // First, make sure we have to instrument this class: @@ -47,6 +50,10 @@ public byte[] instrument(String classFName, byte[] classfileBuffer) throws Excep return instrument(classFName, className, jc); } + protected boolean shouldInstrument(String className) { + return true; + } + protected abstract boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, boolean isAppClass, boolean isMainMethod, boolean isEntry); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/SystemClassTransformer.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/SystemClassTransformer.java index 37a659626..a9466ff25 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/SystemClassTransformer.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/SystemClassTransformer.java @@ -7,6 +7,7 @@ import java.security.ProtectionDomain; import microbat.instrumentation.AgentLogger; +import microbat.instrumentation.instr.aggreplay.ThreadIdInstrumenter; public class SystemClassTransformer { @@ -18,6 +19,19 @@ public static void transformThread(Instrumentation inst) { transform(inst, Thread.class, new ThreadInstrumenter()); } + /** + * Used to generate persistent thread id during runtime. + * Needed so that thread id is consistent across runs. + * @param inst + */ + public static void attachThreadId(Instrumentation inst) { + transform(inst, Thread.class, new ThreadIdInstrumenter()); + } + + public static void attachThreadId(Instrumentation inst, Class clazz) { + transform(inst, Thread.class, new ThreadIdInstrumenter(clazz)); + } + public static void transform(Instrumentation inst, final Class clazz, final AbstractInstrumenter instrumenter) { final String clazzFName = clazz.getName().replace(".", "/"); ClassFileTransformer transformer = new AbstractTransformer() { diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceInstrumenter.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceInstrumenter.java index eb79b0b40..1c37fb4b3 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceInstrumenter.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceInstrumenter.java @@ -61,6 +61,8 @@ import microbat.instrumentation.instr.instruction.info.LineInstructionInfo; import microbat.instrumentation.instr.instruction.info.LocalVarInstructionInfo; import microbat.instrumentation.instr.instruction.info.RWInstructionInfo; +import microbat.instrumentation.model.id.AggrePlayMethods; +import microbat.instrumentation.runtime.ExecutionTracer; import microbat.instrumentation.runtime.IExecutionTracer; import microbat.instrumentation.runtime.TraceUtils; import microbat.instrumentation.utils.MicrobatUtils; @@ -70,7 +72,7 @@ public class TraceInstrumenter extends AbstractInstrumenter { private static final String TEMP_VAR_NAME = "$tempVar"; // local var private int tempVarIdx = 0; - private EntryPoint entryPoint; + protected EntryPoint entryPoint; private Set requireSplittingMethods = Collections.emptySet(); private UserFilters userFilters; @@ -152,7 +154,7 @@ protected byte[] instrument(String classFName, String className, JavaClass jc) { return null; } - private boolean isThread(JavaClass jc) { + protected boolean isThread(JavaClass jc) { try { for(JavaClass interf: jc.getAllInterfaces()) { if(interf.getClassName().equals("java.lang.Runnable")) { @@ -175,7 +177,7 @@ private boolean isThread(JavaClass jc) { return false; } - private boolean doesBytecodeExceedLimit(GeneratedMethods generatedMethods) { + protected boolean doesBytecodeExceedLimit(GeneratedMethods generatedMethods) { boolean excessive = doesBytecodeExceedLimit(generatedMethods.getRootMethod()); for (MethodGen addedMethod : generatedMethods.getExtractedMethods()) { excessive |= doesBytecodeExceedLimit(addedMethod); @@ -183,7 +185,7 @@ private boolean doesBytecodeExceedLimit(GeneratedMethods generatedMethods) { return excessive; } - private GeneratedMethods runMethodInstrumentation(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, + protected GeneratedMethods runMethodInstrumentation(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, boolean isAppClass, boolean isMainMethod, boolean isEntry) { String methodFullName = MicrobatUtils.getMicrobatMethodFullName(classGen.getClassName(), method); boolean changed = instrumentMethod(classGen, constPool, methodGen, method, isAppClass, isMainMethod, isEntry); @@ -300,14 +302,39 @@ protected boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, injectCodeTracerExit(exitInsHandle, insnList, constPool, tracerVar, line, classNameVar, methodSigVar, isMainMethod, isEntry); } + for (InstructionHandle monitorEnterHandle : lineInfo.getMonitorEnterInstructionHandles()) { + injectMonitorEnterInstruction(insnList, monitorEnterHandle, constPool); + } + + for (InstructionHandle monitorExitHandle : lineInfo.getMonitorExitInstructionHandles()) { + injectMonitorExitInstruction(insnList, monitorExitHandle, constPool); + } + lineInfo.dispose(); } injectCodeInitTracer(methodGen, constPool, startLine, endLine, isAppClass, classNameVar, methodSigVar, isMainMethod, tracerVar); return true; } + + protected void injectMonitorEnterInstruction(InstructionList il, InstructionHandle ih, ConstantPoolGen cpg) { + InstructionList beforeMonitorEnter = new InstructionList(); + beforeMonitorEnter.append(new DUP()); + beforeMonitorEnter.append(AggrePlayMethods.ON_LOCK_ACQUIRE.toInvokeStatic(cpg, ExecutionTracer.class)); + + InstructionList afterMonitorEnter = new InstructionList(); + afterMonitorEnter.append(AggrePlayMethods.ON_LOCK_ACQUIRE2.toInvokeStatic(cpg, ExecutionTracer.class)); + insertInsnHandler(il, beforeMonitorEnter, ih); + appendInstruction(il, afterMonitorEnter, ih); + } + + protected void injectMonitorExitInstruction(InstructionList il, InstructionHandle ih, ConstantPoolGen cp) { + InstructionList afterMonitorExitInstructionList = new InstructionList(); + afterMonitorExitInstructionList.append(AggrePlayMethods.AFTER_LOCK_ACQUIRE.toInvokeStatic(cp, ExecutionTracer.class)); + appendInstruction(il, afterMonitorExitInstructionList, ih); + } - private void injectCodeTracerExit(InstructionHandle exitInsHandle, InstructionList insnList, + protected void injectCodeTracerExit(InstructionHandle exitInsHandle, InstructionList insnList, ConstantPoolGen constPool, LocalVariableGen tracerVar, int line, LocalVariableGen classNameVar, LocalVariableGen methodSigVar, boolean isMainMethod, boolean isEntry) { InstructionList newInsns = new InstructionList(); @@ -330,7 +357,7 @@ private void injectCodeTracerExit(InstructionHandle exitInsHandle, InstructionLi newInsns.dispose(); } - private void injectCodeTracerReturn(InstructionList insnList, ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected void injectCodeTracerReturn(InstructionList insnList, ConstantPoolGen constPool, LocalVariableGen tracerVar, InstructionHandle insnHandler, int line, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionList newInsns = new InstructionList(); ReturnInstruction insn = (ReturnInstruction) insnHandler.getInstruction(); @@ -378,7 +405,7 @@ private void injectCodeTracerReturn(InstructionList insnList, ConstantPoolGen co newInsns.dispose(); } - private void injectCodeTracerInvokeMethod(MethodGen methodGen, InstructionList insnList, ConstantPoolGen constPool, + protected void injectCodeTracerInvokeMethod(MethodGen methodGen, InstructionList insnList, ConstantPoolGen constPool, InstructionFactory instructionFactory, LocalVariableGen tracerVar, InstructionHandle insnHandler, int line, LocalVariableGen classNameVar, LocalVariableGen methodSigVar, boolean isAppClass) { InvokeInstruction insn = (InvokeInstruction) insnHandler.getInstruction(); @@ -566,7 +593,7 @@ private void injectCodeAfterInvoke(InstructionList insnList, ConstantPoolGen con newInsns.dispose(); } - private InstructionList getInjectCodeTracerRWriteField(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodeTracerRWriteField(ConstantPoolGen constPool, LocalVariableGen tracerVar, FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { FieldInstruction insn = (FieldInstruction) info.getInstruction(); if (insn instanceof PUTFIELD) { @@ -581,7 +608,7 @@ private InstructionList getInjectCodeTracerRWriteField(ConstantPoolGen constPool return null; } - private InstructionList getInjectCodePutField(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodePutField(ConstantPoolGen constPool, LocalVariableGen tracerVar, FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionList newInsns = new InstructionList(); /* @@ -662,7 +689,7 @@ private InstructionList getInjectCodePutField(ConstantPoolGen constPool, LocalVa * @param methodSigVar * @param classNameVar */ - private InstructionList getInjectCodePutStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodePutStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionList newInsns = new InstructionList(); if (info.isNextToAconstNull()) { @@ -708,7 +735,7 @@ protected void appendTracerMethodInvoke(InstructionList newInsns, TracerMethods } } - private InstructionList getInjectCodeGetField(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodeGetField(ConstantPoolGen constPool, LocalVariableGen tracerVar, FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionList newInsns = new InstructionList(); @@ -757,7 +784,7 @@ private InstructionList getInjectCodeGetField(ConstantPoolGen constPool, LocalVa return newInsns; } - private InstructionList getInjectCodeGetStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodeGetStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionList newInsns = new InstructionList(); GETSTATIC insn = (GETSTATIC) info.getInstruction(); @@ -793,7 +820,7 @@ private InstructionList getInjectCodeGetStatic(ConstantPoolGen constPool, LocalV return newInsns; } - private InstructionList getInjectCodeTracerRWLocalVar(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodeTracerRWLocalVar(ConstantPoolGen constPool, LocalVariableGen tracerVar, LocalVarInstructionInfo insnInfo, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { LocalVariableInstruction insn = insnInfo.getInstruction(); // ignore reference to self @@ -856,7 +883,7 @@ private InstructionList getInjectCodeTracerRWLocalVar(ConstantPoolGen constPool, return newInsns; } - private InstructionList getInjectCodeTracerIINC(ConstantPoolGen constPool, LocalVariableGen tracerVar, + protected InstructionList getInjectCodeTracerIINC(ConstantPoolGen constPool, LocalVariableGen tracerVar, LocalVarInstructionInfo insnInfo, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { IINC insn = (IINC) insnInfo.getInstruction(); // ignore reference to self @@ -904,7 +931,7 @@ private InstructionList getInjectCodeTracerIINC(ConstantPoolGen constPool, Local return newInsns; } - private InstructionList getInjectCodeTracerRWriteArray(MethodGen methodGen, ConstantPoolGen constPool, + protected InstructionList getInjectCodeTracerRWriteArray(MethodGen methodGen, ConstantPoolGen constPool, LocalVariableGen tracerVar, ArrayInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { InstructionHandle insnHandler = info.getInstructionHandler(); ArrayInstruction insn = info.getInstruction(); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceTransformer.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceTransformer.java index e8a4474a7..685e3a4b7 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceTransformer.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TraceTransformer.java @@ -19,6 +19,11 @@ public TraceTransformer(AgentParams params) { instrumenter = new TraceInstrumenter(params); } + + public TraceTransformer(TraceInstrumenter instr) { + this.instrumenter = instr; + } + @Override protected byte[] doTransform(ClassLoader loader, String classFName, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TracerMethods.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TracerMethods.java index a323fece2..7f85cc1a0 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/TracerMethods.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/TracerMethods.java @@ -25,7 +25,8 @@ public enum TracerMethods { WRITE_FIELD(true, "microbat/instrumentation/runtime/IExecutionTracer", "_writeField", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", 8), WRITE_LOCAL_VAR(true, "microbat/instrumentation/runtime/IExecutionTracer", "_writeLocalVar", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIIILjava/lang/String;Ljava/lang/String;)V", 10), WRITE_STATIC_FIELD(true, "microbat/instrumentation/runtime/IExecutionTracer", "_writeStaticField", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", 8), - + MONITER_ENTER(false, "microbat/instrumentation/runtime/ExecutionTracer", "_onLockAcquire", "(Ljava/lang/Object;)V", 1), + MONITOR_EXIT(false, "microbat/instrumentation/runtime/ExecutionTracer", "_afterLockAcquire", "()V", 0) ; private boolean interfaceMethod; private String declareClass; diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/AggrePlayTraceInstrumenter.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/AggrePlayTraceInstrumenter.java new file mode 100644 index 000000000..9ee1a727f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/AggrePlayTraceInstrumenter.java @@ -0,0 +1,525 @@ +package microbat.instrumentation.instr.aggreplay; + +import java.util.List; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.DUP; +import org.apache.bcel.generic.DUP2; +import org.apache.bcel.generic.DUP2_X1; +import org.apache.bcel.generic.DUP2_X2; +import org.apache.bcel.generic.DUP_X2; +import org.apache.bcel.generic.FieldInstruction; +import org.apache.bcel.generic.GETSTATIC; +import org.apache.bcel.generic.IINC; +import org.apache.bcel.generic.INVOKESTATIC; +import org.apache.bcel.generic.Instruction; +import org.apache.bcel.generic.InstructionFactory; +import org.apache.bcel.generic.InstructionHandle; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.InvokeInstruction; +import org.apache.bcel.generic.LDC; +import org.apache.bcel.generic.LocalVariableGen; +import org.apache.bcel.generic.MethodGen; +import org.apache.bcel.generic.POP; +import org.apache.bcel.generic.POP2; +import org.apache.bcel.generic.PUTFIELD; +import org.apache.bcel.generic.PUTSTATIC; +import org.apache.bcel.generic.SWAP; +import org.apache.bcel.generic.TargetLostException; +import org.apache.bcel.generic.Type; + +import microbat.instrumentation.AgentConstants; +import microbat.instrumentation.AgentLogger; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.filter.GlobalFilterChecker; +import microbat.instrumentation.filter.UserFilters; +import microbat.instrumentation.instr.GeneratedMethods; +import microbat.instrumentation.instr.LocalVariableSupporter; +import microbat.instrumentation.instr.TraceInstrumenter; +import microbat.instrumentation.instr.aggreplay.agents.AggrePlayReplayAgent; +import microbat.instrumentation.instr.instruction.info.ArrayInstructionInfo; +import microbat.instrumentation.instr.instruction.info.FieldInstructionInfo; +import microbat.instrumentation.instr.instruction.info.LineInstructionInfo; +import microbat.instrumentation.instr.instruction.info.LocalVarInstructionInfo; +import microbat.instrumentation.instr.instruction.info.RWInstructionInfo; +import microbat.instrumentation.model.id.AggrePlayMethods; +import microbat.instrumentation.runtime.IExecutionTracer; +import microbat.instrumentation.utils.MicrobatUtils; + +public class AggrePlayTraceInstrumenter extends TraceInstrumenter { + + private UserFilters userFilters; + private int tempVarIdx; + private Class instrumentationClass = AggrePlayReplayAgent.class; + + public AggrePlayTraceInstrumenter(AgentParams params) { + super(params); + } + +// @Override +// protected byte[] instrument(String classFName, String className, JavaClass jc) { +// ClassGen cg = new ClassGen(jc); +// ConstantPoolGen cpg = cg.getConstantPool(); +// for (Method method : cg.getMethods()) { +// if (method.isNative() || method.isAbstract() || method.getCode() == null) { +// continue; +// } +// MethodGen mg = new MethodGen(method, classFName, cpg); +// mg.getInstructionList(); +// /* fill up missing variables in localVariableTable */ +// LocalVariableSupporter.fillUpVariableTable(mg, method, cpg); +// +// List lineInsnInfos = LineInstructionInfo.buildLineInstructionInfos(cg, cpg, +// mg, method, true, mg.getInstructionList()); +// +// } +// return cg.getJavaClass().getBytes(); +// } + + + + + + @Override + protected byte[] instrument(String classFName, String className, JavaClass jc) { + ClassGen classGen = new ClassGen(jc); + ConstantPoolGen constPool = classGen.getConstantPool(); + JavaClass newJC = null; + boolean entry = entryPoint == null ? false : className.equals(entryPoint.getClassName()); +// boolean isAppClass = GlobalFilterChecker.isAppClass(classFName) || entry; + boolean isAppClass = true; + if (userFilters != null && !userFilters.isInstrumentable(className)) { + return null; + } + for (Method method : jc.getMethods()) { + if (method.isNative() || method.isAbstract() || method.getCode() == null) { + continue; // Only instrument methods with code in them! + } + try { + MethodGen methodGen = new MethodGen(method, classFName, constPool); + boolean isMainMethod = false; + if (entry && entryPoint.matchMethod(method.getName(), method.getSignature())) { + isMainMethod = true; + } + + boolean isEntry = false; + if(method.getName().equals("run") && isThread(jc)) { + isEntry = true; + } + + + GeneratedMethods generatedMethods = runMethodInstrumentation(classGen, constPool, methodGen, + method, isAppClass, isMainMethod, isEntry); + if (generatedMethods != null) { + if (doesBytecodeExceedLimit(generatedMethods)) { + AgentLogger.info(String.format("Warning: %s exceeds bytecode limit!", + MicrobatUtils.getMicrobatMethodFullName(classGen.getClassName(), method))); + } else { + for (MethodGen newMethod : generatedMethods.getExtractedMethods()) { + newMethod.setMaxStack(); + newMethod.setMaxLocals(); + classGen.addMethod(newMethod.getMethod()); + } + methodGen = generatedMethods.getRootMethod(); + // All changes made, so finish off the method: + InstructionList instructionList = methodGen.getInstructionList(); + instructionList.setPositions(); + methodGen.setMaxStack(); + methodGen.setMaxLocals(); + classGen.replaceMethod(method, methodGen.getMethod()); + } + } + newJC = classGen.getJavaClass(); + newJC.setConstantPool(constPool.getFinalConstantPool()); + } catch (Exception e) { + String message = e.getMessage(); + if (e.getMessage() != null && e.getMessage().contains("offset too large")) { + message = "offset too large"; + } + AgentLogger.info(String.format("Warning: %s [%s]", + MicrobatUtils.getMicrobatMethodFullName(classGen.getClassName(), method), message)); + AgentLogger.error(e); + e.printStackTrace(); + } + } + if (newJC != null) { + byte[] data = newJC.getBytes(); + return data; + } + + return null; + } + + + + + @Override + protected InstructionList getInjectCodePutField(ConstantPoolGen constPool, LocalVariableGen tracerVar, + FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { + InstructionList result = new InstructionList(); + if (info.isComputationalType2()) { + // o, d -> d, o, o + result.append(new DUP2_X1()); + result.append(new POP2()); + result.append(new DUP()); + } else { + result.append(new SWAP()); // val, obj + result.append(new DUP()); // val, obj, obj + } + + + result.append(new LDC(constPool.addString(info.getFieldName()))); // v, o, o, s + result.append(AggrePlayMethods.BEFORE_OBJECT_WRITE.toInvokeStatic(constPool, instrumentationClass)); // v, o + + if (info.isComputationalType2()) { + result.append(new DUP_X2()); + result.append(new POP()); + } else { + result.append(new SWAP()); + } + result.append(super.getInjectCodePutField(constPool, tracerVar, info, classNameVar, methodSigVar)); + return result; + } + + @Override + protected InstructionList getInjectCodeGetField(ConstantPoolGen constPool, LocalVariableGen tracerVar, + FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { + InstructionList beforeGetFieldInstructionList = new InstructionList(); + InstructionList afterGetFieldInstructionList = new InstructionList(); + // obj + beforeGetFieldInstructionList.append(new DUP()); + // obj, obj + beforeGetFieldInstructionList.append(new LDC(constPool.addString(info.getFieldName()))); + beforeGetFieldInstructionList.append(AggrePlayMethods.BEFORE_OBJECT_READ.toInvokeStatic(constPool, instrumentationClass)); + afterGetFieldInstructionList.append(AggrePlayMethods.AFTER_OBJECT_READ.toInvokeStatic(constPool, instrumentationClass)); + InstructionList getFieldInstructions = + super.getInjectCodeGetField(constPool, tracerVar, info, classNameVar, methodSigVar); + beforeGetFieldInstructionList.append(getFieldInstructions); + beforeGetFieldInstructionList.append(afterGetFieldInstructionList); + return beforeGetFieldInstructionList; + // return super.getInjectCodeGetField(constPool, tracerVar, info, classNameVar, methodSigVar); + } + + + + + + + private InstructionList afterWrite(ConstantPoolGen cpg) { + InstructionList result = new InstructionList(); + INVOKESTATIC afterPutFieldInvokestatic = + AggrePlayMethods.AFTER_OBJECT_WRITE.toInvokeStatic(cpg, instrumentationClass); + result.append(afterPutFieldInvokestatic); + return result; + } + + + + protected InstructionList appendFieldInstructions(FieldInstructionInfo info, ConstantPoolGen cpg) { + // only need to deal with putfield and putstatic + Instruction instruction = info.getInstruction(); + if (instruction.getOpcode() == Const.PUTFIELD) { + return afterWrite(cpg); + } + if (instruction.getOpcode() == Const.PUTSTATIC) { + return afterWrite(cpg); + } + return null; + } + + // TODO: + /** + * Currently store instruction, i.e. write instructions, we only instrument the before stage, + * We do not instrument the after stage -> should modify to instrument the after stage + * with the minimum amount of change. + * TODO: reduce the amount of change in this code + */ + @Override + protected boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, + boolean isAppClass, boolean isMainMethod, boolean isEntry) { + // TODO Auto-generated method stub + if (userFilters != null && !userFilters.isInstrumentable(classGen.getClassName(), method, methodGen.getLineNumbers())) { + return false; + } + tempVarIdx = 0; + InstructionList insnList = methodGen.getInstructionList(); + InstructionHandle startInsn = insnList.getStart(); + if (startInsn == null) { + // empty method + return false; + } + + /* fill up missing variables in localVariableTable */ + LocalVariableSupporter.fillUpVariableTable(methodGen, method, constPool); + + List lineInsnInfos = LineInstructionInfo.buildLineInstructionInfos(classGen, constPool, + methodGen, method, isAppClass, insnList); + int startLine = Integer.MAX_VALUE; + int endLine = AgentConstants.UNKNOWN_LINE; + for (LineInstructionInfo lineInfo : lineInsnInfos) { + int line = lineInfo.getLine(); + if (line < startLine) { + startLine = line; + } + if (line > endLine) { + endLine = line; + } + } + if (startLine == Integer.MAX_VALUE) { + startLine = AgentConstants.UNKNOWN_LINE; + } + LocalVariableGen classNameVar = createLocalVariable(CLASS_NAME, methodGen, constPool); + LocalVariableGen methodSigVar = createLocalVariable(METHOD_SIGNATURE, methodGen, constPool); + LocalVariableGen tracerVar = methodGen.addLocalVariable(TRACER_VAR_NAME, Type.getType(IExecutionTracer.class), + insnList.getStart(), insnList.getEnd()); + + if (userFilters != null) userFilters.filter(lineInsnInfos, classGen.getClassName(), method); + for (LineInstructionInfo lineInfo : lineInsnInfos) { + /* instrument RW instructions */ + List rwInsns = lineInfo.getRWInstructions(); +// if (lineInfo.hasNoInstrumentation()) { + injectCodeTracerHitLine(insnList, constPool, tracerVar, lineInfo.getLine(), lineInfo.getLineNumberInsn(), + classNameVar, methodSigVar, lineInfo.hasExceptionTarget(), lineInfo.getReadWriteInsnTotal(false), + lineInfo.getReadWriteInsnTotal(true), lineInfo); +// } + for (RWInstructionInfo rwInsnInfo : rwInsns) { + InstructionList newInsns = null; + // instructions to append --> only on write instructions + InstructionList appendInsnsInstructionList = null; + if (rwInsnInfo instanceof FieldInstructionInfo) { + newInsns = getInjectCodeTracerRWriteField(constPool, tracerVar, (FieldInstructionInfo) rwInsnInfo, classNameVar, methodSigVar); + appendInsnsInstructionList = appendFieldInstructions((FieldInstructionInfo) rwInsnInfo, constPool); + } else if (rwInsnInfo instanceof ArrayInstructionInfo) { + newInsns = getInjectCodeTracerRWriteArray(methodGen, constPool, tracerVar, + (ArrayInstructionInfo) rwInsnInfo, classNameVar, methodSigVar); + appendInsnsInstructionList = appendInstructionListRWArray((ArrayInstructionInfo) rwInsnInfo, constPool); + } else if (rwInsnInfo instanceof LocalVarInstructionInfo) { + if (rwInsnInfo.getInstruction() instanceof IINC) { + newInsns = getInjectCodeTracerIINC(constPool, tracerVar, + (LocalVarInstructionInfo) rwInsnInfo, classNameVar, methodSigVar); + } else { + newInsns = getInjectCodeTracerRWLocalVar(constPool, tracerVar, + (LocalVarInstructionInfo) rwInsnInfo, classNameVar, methodSigVar); + } + } + if ((newInsns != null) && (newInsns.getLength() > 0)) { + InstructionHandle insnHandler = rwInsnInfo.getInstructionHandler(); + if (rwInsnInfo.isStoreInstruction()) { + insertInsnHandler(insnList, newInsns, insnHandler); + if (appendInsnsInstructionList != null) appendInstruction(insnList, appendInsnsInstructionList, insnHandler); + newInsns.dispose(); + } else { + insertInsnHandler(insnList, newInsns, insnHandler); + try { + updateTarget(insnHandler, insnHandler.getPrev(), insnHandler.getNext()); + insnList.delete(insnHandler); + } catch (TargetLostException e) { + e.printStackTrace(); + } + newInsns.dispose(); + } + } + } + int line = lineInfo.getLine(); + /* instrument Invocation instructions */ + InstructionFactory instructionFactory = new InstructionFactory(classGen, constPool); + for (InstructionHandle insn : lineInfo.getInvokeInstructions()) { + injectCodeTracerInvokeMethod(methodGen, insnList, constPool, instructionFactory, tracerVar, insn, line, + classNameVar, methodSigVar, isAppClass); + } + /* instrument Return instructions */ + for (InstructionHandle insn : lineInfo.getReturnInsns()) { + injectCodeTracerReturn(insnList, constPool, tracerVar, insn, line, classNameVar, methodSigVar); + } + + /** + * instrument exit instructions + */ + for (InstructionHandle exitInsHandle : lineInfo.getExitInsns()) { + injectCodeTracerExit(exitInsHandle, insnList, constPool, tracerVar, line, classNameVar, methodSigVar, isMainMethod, isEntry); + } + + /** + * Instrument new instructions + */ + for (InstructionHandle newInsnHandle: lineInfo.getNewInstructions()) { + injectOnNewInstructions(insnList, newInsnHandle, constPool); + } + + /** + * Instrument new array instructions + */ + for (InstructionHandle newArrayInsnHandle: lineInfo.getNewArrayInstructions()) { + injectOnNewArrayInsns(insnList, newArrayInsnHandle, constPool); + } + + for (InstructionHandle ih : lineInfo.getMonitorEnterInstructionHandles()) { + injectMonitorEnterInstruction(insnList, ih, constPool); + } + + for (InstructionHandle ih : lineInfo.getMonitorExitInstructionHandles()) { + injectMonitorExitInstruction(insnList, ih, constPool); + } + + + lineInfo.dispose(); + } + injectCodeInitTracer(methodGen, constPool, startLine, endLine, isAppClass, classNameVar, + methodSigVar, isMainMethod, tracerVar); + return true; + } + + @Override + protected void injectMonitorEnterInstruction(InstructionList il, InstructionHandle ih, ConstantPoolGen cpg) { + super.injectMonitorEnterInstruction(il, ih, cpg); + InstructionList beforeMonitorEnter = new InstructionList(); + beforeMonitorEnter.append(new DUP()); + beforeMonitorEnter.append(AggrePlayMethods.ON_LOCK_ACQUIRE.toInvokeStatic(cpg, instrumentationClass)); + insertInsnHandler(il, beforeMonitorEnter, ih); + } + @Override + protected void injectMonitorExitInstruction(InstructionList il, InstructionHandle ih, ConstantPoolGen cp) { + super.injectMonitorExitInstruction(il, ih, cp); + InstructionList afterMonitorExitInstructionList = new InstructionList(); + afterMonitorExitInstructionList.append(AggrePlayMethods.AFTER_LOCK_ACQUIRE.toInvokeStatic(cp, instrumentationClass)); + appendInstruction(il, afterMonitorExitInstructionList, ih); + } + + private void injectOnNewArrayInsns(InstructionList instructionList, InstructionHandle ih, ConstantPoolGen cpg) { + InstructionList toAppend = new InstructionList(); + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ON_NEW_ARRAY.toInvokeStatic(cpg, instrumentationClass)); + appendInstruction(instructionList, toAppend, ih); + } + + + + @Override + protected void injectCodeTracerInvokeMethod(MethodGen methodGen, InstructionList insnList, + ConstantPoolGen constPool, InstructionFactory instructionFactory, LocalVariableGen tracerVar, + InstructionHandle insnHandler, int line, LocalVariableGen classNameVar, LocalVariableGen methodSigVar, + boolean isAppClass) { + // TODO Auto-generated method stub + super.injectCodeTracerInvokeMethod(methodGen, insnList, constPool, instructionFactory, tracerVar, insnHandler, line, + classNameVar, methodSigVar, isAppClass); + InstructionList toAppend = new InstructionList(); + InvokeInstruction invokeInstruction = (InvokeInstruction) insnHandler.getInstruction(); + String signature = invokeInstruction.getReturnType(constPool).getSignature(); + if (signature.startsWith("L")) { + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ASSERT_OBJECT_EXISTS.toInvokeStatic(constPool, instrumentationClass)); + appendInstruction(insnList, toAppend, insnHandler); + } else if (signature.startsWith("[")) { + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ASSERT_ARRAY_EXISTS.toInvokeStatic(constPool, instrumentationClass)); + appendInstruction(insnList, toAppend, insnHandler); + } + } + + /** + * + * @param info + * @return + */ + private InstructionList appendInstructionListRWArray(ArrayInstructionInfo info, ConstantPoolGen cpg) { + + InstructionList result = new InstructionList(); + if (info.isStoreInstruction()) { + result.append(AggrePlayMethods.AFTER_OBJECT_WRITE.toInvokeStatic(cpg, instrumentationClass)); + } + return result; + } + + @Override + protected InstructionList getInjectCodeTracerRWriteArray(MethodGen methodGen, ConstantPoolGen constPool, + LocalVariableGen tracerVar, ArrayInstructionInfo info, LocalVariableGen classNameVar, + LocalVariableGen methodSigVar) { + InstructionList before = new InstructionList(); + if (info.isStoreInstruction()) { + // a, i, d -> a, i, d, a, i + if (info.isComputationalType2()) { + before.append(new DUP2_X2()); // d a i d + before.append(new POP2()); // d a i + before.append(new DUP2_X2());// a i d a i + } else { + // a i d - d a i d + before.append(new DUP_X2()); + before.append(new POP()); + before.append(new DUP2_X1()); + } + before.append(AggrePlayMethods.BEFORE_ARRAY_WRITE.toInvokeStatic(constPool, instrumentationClass)); + } else { + before.append(new DUP2()); + before.append(AggrePlayMethods.BEFORE_ARRAY_READ.toInvokeStatic(constPool, instrumentationClass)); + } + InstructionList intermediate = super.getInjectCodeTracerRWriteArray(methodGen, constPool, tracerVar, info, classNameVar, methodSigVar); + + before.append(intermediate); + if (!info.isStoreInstruction()) { + before.append(AggrePlayMethods.AFTER_OBJECT_READ.toInvokeStatic(constPool, instrumentationClass)); + } + return before; + } + + @Override + protected InstructionList getInjectCodePutStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, + FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { + InstructionList before = new InstructionList(); + PUTSTATIC insPutstatic = (PUTSTATIC) info.getInstruction(); + String classNameString = insPutstatic.getReferenceType(constPool).getSignature(); + + before.append(new GETSTATIC(insPutstatic.getIndex())); + if (info.isComputationalType2()) { + before.append(new POP2()); + } else { + before.append(new POP()); + } + before.append(new LDC(constPool.addString(classNameString))); + before.append(new LDC(constPool.addString(info.getFieldName()))); + before.append(AggrePlayMethods.BEFORE_STATIC_WRITE.toInvokeStatic(constPool, instrumentationClass)); + InstructionList inBetween = super.getInjectCodePutStatic(constPool, tracerVar, info, classNameVar, methodSigVar); + before.append(inBetween); + + return before; + } + + @Override + protected InstructionList getInjectCodeGetStatic(ConstantPoolGen constPool, LocalVariableGen tracerVar, + FieldInstructionInfo info, LocalVariableGen classNameVar, LocalVariableGen methodSigVar) { + InstructionList before = new InstructionList(); + GETSTATIC insPutstatic = (GETSTATIC) info.getInstruction(); + String classNameString = insPutstatic.getReferenceType(constPool).getSignature(); + + before.append(insPutstatic); + if (info.isComputationalType2()) { + before.append(new POP2()); + } else { + before.append(new POP()); + } + + before.append(new LDC(constPool.addString(classNameString))); + before.append(new LDC(constPool.addString(info.getFieldName()))); + before.append(AggrePlayMethods.BEFORE_STATIC_READ.toInvokeStatic(constPool, instrumentationClass)); + + + InstructionList tracerCode = super.getInjectCodeGetStatic(constPool, tracerVar, info, classNameVar, methodSigVar); + before.append(tracerCode); + before.append(AggrePlayMethods.AFTER_OBJECT_READ.toInvokeStatic(constPool, instrumentationClass)); + return before; + } + + protected void injectOnNewInstructions(InstructionList instructionList, + InstructionHandle instructionHandle, + ConstantPoolGen cpg) { + InstructionList toAppend = new InstructionList(); + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ON_NEW_OBJECT.toInvokeStatic(cpg, instrumentationClass)); + appendInstruction(instructionList, toAppend, instructionHandle); + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ObjectAccessInstrumentator.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ObjectAccessInstrumentator.java new file mode 100644 index 000000000..c476178d6 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ObjectAccessInstrumentator.java @@ -0,0 +1,371 @@ +package microbat.instrumentation.instr.aggreplay; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.ArrayInstruction; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.DUP; +import org.apache.bcel.generic.DUP2; +import org.apache.bcel.generic.DUP2_X1; +import org.apache.bcel.generic.DUP2_X2; +import org.apache.bcel.generic.DUP_X2; +import org.apache.bcel.generic.FieldInstruction; +import org.apache.bcel.generic.GETFIELD; +import org.apache.bcel.generic.GETSTATIC; +import org.apache.bcel.generic.INVOKESTATIC; +import org.apache.bcel.generic.InstructionHandle; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.InvokeInstruction; +import org.apache.bcel.generic.LASTORE; +import org.apache.bcel.generic.LDC; +import org.apache.bcel.generic.MONITORENTER; +import org.apache.bcel.generic.MethodGen; +import org.apache.bcel.generic.NEWARRAY; +import org.apache.bcel.generic.POP; +import org.apache.bcel.generic.POP2; +import org.apache.bcel.generic.PUTFIELD; +import org.apache.bcel.generic.PUTSTATIC; +import org.apache.bcel.generic.SWAP; +import org.apache.bcel.generic.Type; + +import javassist.bytecode.Opcode; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.filter.UserFilters; +import microbat.instrumentation.instr.AbstractInstrumenter; +import microbat.instrumentation.instr.GeneratedMethods; +import microbat.instrumentation.instr.MethodSplitter; +import microbat.instrumentation.instr.TracerMethods; +import microbat.instrumentation.instr.instruction.info.EntryPoint; +import microbat.instrumentation.model.id.AggrePlayMethods; +import microbat.instrumentation.runtime.ExecutionTracer; +import microbat.instrumentation.utils.MicrobatUtils; + +public abstract class ObjectAccessInstrumentator extends AbstractInstrumenter { + + protected Class agentClass; + + private EntryPoint entryPoint; + private UserFilters userFilters; + private Set requireSplittingMethods = Collections.emptySet(); + + /** + * Fired after a new object is created + */ + public static final String ON_NEW_OBJECT = "_onNewObject"; + public static final String ON_NEW_OBJECT_SIG = "(Ljava/lang/Object;)V"; + + // for getfield and putfield instructions + /** + * Before object write + */ + public static final String ON_OBJECT_WRITE = "_onObjectWrite"; + public static final String ON_OBJECT_WRITE_SIG = "(Ljava/lang/Object;Ljava/lang/String;)V"; + /** + * Before object read + */ + public static final String ON_OBJECT_READ = "_onObjectRead"; + public static final String ON_OBJECT_READ_SIG = "(Ljava/lang/Object;Ljava/lang/String;)V"; + + public ObjectAccessInstrumentator(Class agentClass, AgentParams agentParams) { + this.agentClass = agentClass; + this.entryPoint = agentParams.getEntryPoint(); + if (agentParams.isRequireMethodSplit()) { + this.requireSplittingMethods = agentParams.getOverlongMethods(); + } + } + + @Override + protected boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, + boolean isAppClass, boolean isMainMethod, boolean isEntry) { + + // TODO Auto-generated method stub + return false; + } + + private int addFieldName(ConstantPoolGen constPool, FieldInstruction getfield) { + String fieldName = getfield.getName(constPool); + return constPool.addString(fieldName); + } + + + @Override + protected byte[] instrument(String classFName, String className, JavaClass jc) { + ClassGen classGen = new ClassGen(jc); + ConstantPoolGen constPool = classGen.getConstantPool(); + String agentClassString = agentClass.getName().replace(".", "/"); + final int onNewObjectMethod = + constPool.addMethodref(agentClassString, ON_NEW_OBJECT, ON_NEW_OBJECT_SIG); + final int onObjectWrite = + constPool.addMethodref(agentClassString, ON_OBJECT_WRITE, ON_OBJECT_WRITE_SIG); + final int onObjectRead = + constPool.addMethodref(agentClassString, ON_OBJECT_READ, ON_OBJECT_READ_SIG); + + final INVOKESTATIC onNewObjectInvoke = new INVOKESTATIC(onNewObjectMethod); + final INVOKESTATIC onObjectWriteInvoke = new INVOKESTATIC(onObjectWrite); + final INVOKESTATIC onObjectReadInvoke = new INVOKESTATIC(onObjectRead); + final DUP dup = new DUP(); + boolean entry = entryPoint == null ? false : className.equals(entryPoint.getClassName()); + for (Method method : classGen.getMethods()) { + if (method.isNative() || method.isAbstract() || method.getCode() == null) { + continue; // Only instrument methods with code in them! + } + MethodGen mGen = new MethodGen(method, className, constPool); + InstructionList iList = mGen.getInstructionList(); + InstructionHandle[] ihs = iList.getInstructionHandles(); + boolean isMainMethod = entry && entryPoint.matchMethod(method.getName(), method.getSignature()); + if (isMainMethod) { + iList.insert(AggrePlayMethods.START.toInvokeStatic(constPool, ExecutionTracer.class)); + } + instrumentMethod(constPool, onNewObjectInvoke, onObjectWriteInvoke, onObjectReadInvoke, dup, mGen, iList, + ihs); + checkSplitMethods(mGen, classGen, method); + } + + return classGen.getJavaClass().getBytes(); + } + + private void checkSplitMethods(MethodGen mg, ClassGen classGen, Method oldMethod) { + String methodFullName = MicrobatUtils.getMicrobatMethodFullName(classGen.getClassName(), mg.getMethod()); + if (requireSplittingMethods.contains(methodFullName)) { + MethodSplitter methodSplitter = new MethodSplitter(classGen, classGen.getConstantPool()); + GeneratedMethods generatedMethods = methodSplitter.splitMethod(mg); + for (MethodGen newMethodGen : generatedMethods.getExtractedMethods()) { + newMethodGen.setMaxStack(); + newMethodGen.setMaxLocals(); + classGen.addMethod(newMethodGen.getMethod()); + } + MethodGen rootMethodGen = generatedMethods.getRootMethod(); + InstructionList iList = rootMethodGen.getInstructionList(); + iList.setPositions(); + rootMethodGen.setMaxStack(); + rootMethodGen.setMaxLocals(); + classGen.replaceMethod(oldMethod, rootMethodGen.getMethod());; + } else { + InstructionList instructionList = mg.getInstructionList(); + instructionList.setPositions(); + mg.setMaxStack(); + mg.setMaxLocals(); + classGen.replaceMethod(oldMethod, mg.getMethod()); + } + } + + private void instrumentMethod(ConstantPoolGen constPool, final INVOKESTATIC onNewObjectInvoke, + final INVOKESTATIC onObjectWriteInvoke, final INVOKESTATIC onObjectReadInvoke, final DUP dup, + MethodGen mGen, InstructionList iList, InstructionHandle[] ihs) { + for (InstructionHandle handle: ihs) { + InstructionList newList = new InstructionList(); + if (handle.getInstruction().getOpcode() == Opcode.NEW) { + newList.append(dup); + newList.append(onNewObjectInvoke); + if(handle.getNext() == null) { + iList.append(newList); + } else { + insertInsnHandler(iList, newList, handle.getNext()); + } + newList.dispose(); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.GETFIELD) { + GETFIELD getfield = (GETFIELD) handle.getInstruction(); + instrumentGetField(constPool, onObjectReadInvoke, iList, handle, getfield); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.PUTFIELD) { + PUTFIELD putField = (PUTFIELD) handle.getInstruction(); + instrumentPutField(constPool, onObjectWriteInvoke, iList, handle, putField); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.MONITORENTER) { + instrumentMonitorEnter(constPool, iList, handle); + continue; + } + if (handle.getInstruction() instanceof ArrayInstruction) { + instrumentArrayAccess(constPool, iList, handle); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.GETSTATIC) { + instrumentGetStaticInstruction(constPool, iList, handle); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.PUTSTATIC) { + instrumentPutStatic(constPool, iList, handle); + continue; + } + if (handle.getInstruction().getOpcode() == Opcode.MULTIANEWARRAY + || handle.getInstruction().getOpcode() == Opcode.NEWARRAY + || handle.getInstruction().getOpcode() == Opcode.ANEWARRAY) { + instrumentNewArray(constPool, iList, handle); + continue; + } + + if (handle.getInstruction() instanceof InvokeInstruction) { + instrumentMethodReturn(constPool, iList, handle); + continue; + } + } + } + + protected void instrumentMethodReturn(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + InstructionList toAppend = new InstructionList(); + String signature = ((InvokeInstruction) ih.getInstruction()).getReturnType(cpg).getSignature(); + if (signature.startsWith("L")) { + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ASSERT_OBJECT_EXISTS.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, toAppend, ih); + } else if (signature.startsWith("[")) { + toAppend.append(new DUP()); + toAppend.append(AggrePlayMethods.ASSERT_ARRAY_EXISTS.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, toAppend, ih); + } + } + + /** + * Instruments NEWARRAY or MULTIANEWARRAY + * @param cpg + * @param il + * @param il + */ + protected void instrumentNewArray(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + InstructionList toAppend = new InstructionList(); + toAppend.append(new DUP()); // arrayRef, arrayRef + toAppend.append(AggrePlayMethods.ON_NEW_ARRAY.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, toAppend, ih); + } + + + protected void instrumentPutStatic(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + InstructionList beforeInstructionList = new InstructionList(); + InstructionList afterInstructionList = new InstructionList(); + + PUTSTATIC ps = (PUTSTATIC) ih.getInstruction(); + + // ldc class + field + beforeInstructionList.append(new LDC(cpg.addString(ps.getReferenceType(cpg).getSignature()))); + beforeInstructionList.append(new LDC(cpg.addString(ps.getFieldName(cpg)))); + beforeInstructionList.append(AggrePlayMethods.BEFORE_STATIC_WRITE.toInvokeStatic(cpg, agentClass)); + insertInsnHandler(il, beforeInstructionList, ih); + } + + protected void instrumentGetStaticInstruction(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + InstructionList beforeInstructionList = new InstructionList(); + GETSTATIC ps = (GETSTATIC) ih.getInstruction(); + // ldc class + field + beforeInstructionList.append(new LDC(cpg.addString(ps.getReferenceType(cpg).getSignature()))); + beforeInstructionList.append(new LDC(cpg.addString(ps.getFieldName(cpg)))); + beforeInstructionList.append(AggrePlayMethods.BEFORE_STATIC_READ.toInvokeStatic(cpg, agentClass)); + insertInsnHandler(il, beforeInstructionList, ih); + } + + protected void instrumentArrayAccess(ConstantPoolGen cpg, + InstructionList instructionList, InstructionHandle ih) { + switch (ih.getInstruction().getOpcode()) { + case Const.AALOAD: + case Const.BALOAD: + case Const.CALOAD: + case Const.DALOAD: + case Const.FALOAD: + case Const.IALOAD: + case Const.LALOAD: + case Const.SALOAD: + instrumentArrayRead(cpg, instructionList, ih); + break; + // deal with store + default: + instrumentArrayWrite(cpg, instructionList, ih); + break; + } + } + + protected void instrumentArrayRead(ConstantPoolGen cpg, + InstructionList il, InstructionHandle ih) { + InstructionList befInstructionList = new InstructionList(); + befInstructionList.append(new DUP2()); + befInstructionList.append(AggrePlayMethods.BEFORE_ARRAY_READ.toInvokeStatic(cpg, agentClass)); + insertInsnHandler(il, befInstructionList, ih); + } + + protected void instrumentArrayWrite(ConstantPoolGen cpg, + InstructionList il, InstructionHandle ih) { + + InstructionList befInstructionList = new InstructionList(); + boolean c2 = (ih.getInstruction().getOpcode() == Const.LASTORE + || ih.getInstruction().getOpcode() == Const.DASTORE); + if (c2) { + // arra, index, value1, value2 + befInstructionList.append(new DUP2_X2()); + befInstructionList.append(new POP2()); + befInstructionList.append(new DUP2_X2()); + } else { + befInstructionList.append(new DUP_X2()); + befInstructionList.append(new POP()); + befInstructionList.append(new DUP2_X1()); + } + befInstructionList.append(AggrePlayMethods.BEFORE_ARRAY_WRITE.toInvokeStatic(cpg, agentClass)); + insertInsnHandler(il, befInstructionList, ih); + } + + + protected void instrumentMonitorEnter(ConstantPoolGen constPool, InstructionList instructionList, + InstructionHandle handle) { + InstructionList beforeInstructionList = new InstructionList(); + InstructionList afterInstructionList = new InstructionList(); + beforeInstructionList.append(new DUP()); + afterInstructionList.append(createInvokeStatic(constPool, agentClass, AggrePlayMethods.ON_LOCK_ACQUIRE)); + insertInsnHandler(instructionList, beforeInstructionList, handle); + appendInstruction(instructionList, afterInstructionList, handle); + } + + protected void instrumentPutField(ConstantPoolGen constPool, final INVOKESTATIC onObjectWriteInvoke, InstructionList iList, + InstructionHandle handle, PUTFIELD putField) { + InstructionList newList = new InstructionList(); + int fieldRef = addFieldName(constPool, putField); + Type fieldType = putField.getType(constPool); + if (fieldType.equals(Type.LONG) + || fieldType.equals(Type.DOUBLE)) { + newList.append(new DUP2_X1()); + newList.append(new POP2()); + newList.append(new DUP()); + } else { + newList.append(new SWAP()); + newList.append(new DUP()); // value, object, object + } + + newList.append(new LDC(fieldRef)); // object, object, fieldName + newList.append(onObjectWriteInvoke); // object + if (fieldType.equals(Type.LONG) || fieldType.equals(Type.DOUBLE)) { + newList.append(new DUP_X2()); + newList.append(new POP()); + } else { + newList.append(new SWAP()); + } + insertInsnHandler(iList, newList, handle); + newList.dispose(); + } + + protected void instrumentGetField( + ConstantPoolGen constPool, final INVOKESTATIC onObjectReadInvoke, + InstructionList iList, InstructionHandle handle, GETFIELD getfield) { + InstructionList newList = new InstructionList(); + int fieldRef = addFieldName(constPool, getfield); + newList.append(new DUP()); // object, object + newList.append(new LDC(fieldRef)); // object, object, fieldName + newList.append(onObjectReadInvoke); // object + insertInsnHandler(iList, newList, handle); + newList.dispose(); + } + + protected INVOKESTATIC createInvokeStatic(ConstantPoolGen cpg, Class clazz, AggrePlayMethods method) { + return createInvokeStatic(cpg, clazz, method.methodName, method.methodSig); + } + + protected INVOKESTATIC createInvokeStatic(ConstantPoolGen cpg, Class clazz, String methodName, String signature) { + return new INVOKESTATIC(cpg.addMethodref(clazz.getName().replace(".", "/"), methodName, signature)); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ReplayMode.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ReplayMode.java new file mode 100644 index 000000000..156694362 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ReplayMode.java @@ -0,0 +1,25 @@ +package microbat.instrumentation.instr.aggreplay; + +public enum ReplayMode { + STRICT_RW("strict_rw"), + RELAX_RW("relax_rw"), + AGGR("aggr_rw"); + + private String idString; + private ReplayMode(String name) { + idString = name; + } + + public String getIdString() { + return this.idString; + } + + public static ReplayMode parse(String value) { + try { + return ReplayMode.valueOf(value); + } catch (IllegalArgumentException arg) { + return AGGR; + } + + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ThreadIdInstrumenter.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ThreadIdInstrumenter.java new file mode 100644 index 000000000..c7d7405ed --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/ThreadIdInstrumenter.java @@ -0,0 +1,86 @@ +package microbat.instrumentation.instr.aggreplay; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.ALOAD; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.INVOKESTATIC; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.MethodGen; + +import microbat.instrumentation.instr.AbstractInstrumenter; +import microbat.instrumentation.instr.aggreplay.agents.SharedVariableAgent; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.runtime.ExecutionTracer; + +/** + * Instrumenter solely for generating thread id + * @author Gabau + * + */ +public class ThreadIdInstrumenter extends AbstractInstrumenter { + + // the class containing _onThreadStart (Ljava/lang/Thread;)V, IMPT!! should not be interface + private Class instrumentedClass = getClass(); + + public ThreadIdInstrumenter() {} + + public ThreadIdInstrumenter(Class instrumentedClass) { + this.instrumentedClass = instrumentedClass; + } + + public static void _onThreadStart(Thread thread) { + if (ExecutionTracer.isRecordingOrStarted()) { + ThreadIdGenerator.threadGenerator.createId(thread); + } + } + + + @Override + protected boolean shouldInstrument(String className) { + return className.equals(Thread.class.getName()); + } + + @Override + protected byte[] instrument(String classFName, String className, JavaClass jc) { + System.out.println("Instrumeted thread " + classFName); + ClassGen classGen = new ClassGen(jc); + Method startMethod = null; + for (Method method: classGen.getMethods()) { + if ("start".equals(method.getName())) { + startMethod = method; + } + } + instrumentStartMethod(classGen, startMethod); + return classGen.getJavaClass().getBytes(); + } + + protected void instrumentStartMethod(ClassGen cg, Method startMethod) { + MethodGen mGen = new MethodGen(startMethod, cg.getClassName(), cg.getConstantPool()); + InstructionList iList = mGen.getInstructionList(); + ConstantPoolGen constantPoolGen = cg.getConstantPool(); + String threadStartClass = instrumentedClass.getName().replace(".", "/"); + ALOAD aload = new ALOAD(0); + INVOKESTATIC invokestatic = new INVOKESTATIC(constantPoolGen.addMethodref( + threadStartClass, "_onThreadStart", "(Ljava/lang/Thread;)V")); + iList.insert(invokestatic); + iList.insert(aload); + iList.setPositions(); + mGen.setMaxLocals(); + mGen.setMaxStack(); + cg.replaceMethod(startMethod, mGen.getMethod()); + } + + + @Override + protected boolean instrumentMethod(ClassGen classGen, ConstantPoolGen constPool, MethodGen methodGen, Method method, + boolean isAppClass, boolean isMainMethod, boolean isEntry) { + return false; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/TimeoutThread.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/TimeoutThread.java new file mode 100644 index 000000000..22758c894 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/TimeoutThread.java @@ -0,0 +1,51 @@ +package microbat.instrumentation.instr.aggreplay; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.AgentLogger; +import microbat.instrumentation.runtime.ExecutionTracer; + +public class TimeoutThread extends Thread { + + private Agent attachedAgent = null; + public static final String ID = "Timeout-thread$$"; + public static final String TIMEOUT_MSG = "false;Timeout"; + + private long timeOut = 10000L; + + public TimeoutThread(Agent attachedAgent) { + this.attachedAgent = attachedAgent; + super.setName(ID); + super.setDaemon(true); + } + + public void setTimeout(long timeOut) { + this.timeOut = timeOut; + } + + public TimeoutThread() { + super.setName(ID); + super.setDaemon(true); + } + @Override + public void run() { + // the timeout is forever + if (timeOut < 0) return; + try { + Thread.sleep(timeOut); + } catch (InterruptedException e) { + } + AgentLogger.debug("Interrupted program due to timeout"); + ExecutionTracer.shutdown(); + Agent._forceProgramStop(TIMEOUT_MSG); +// if (attachedAgent == null) { +// Agent._exitProgram("false;Timeout"); +// } else { +//// try { +//// attachedAgent.shutdown(); +//// } catch (Exception e) { +//// e.printStackTrace(); +//// } +// } +// System.exit(1); + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayRecordingRWAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayRecordingRWAgent.java new file mode 100644 index 000000000..ab01eb106 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayRecordingRWAgent.java @@ -0,0 +1,139 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.stream.Collectors; + +import javassist.compiler.ast.Pair; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.instr.aggreplay.ObjectAccessInstrumentator; +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.record.RecordingInstrumentor; +import microbat.instrumentation.instr.aggreplay.shared.BasicTransformer; +import microbat.instrumentation.instr.aggreplay.shared.RecordingOutput; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.runtime.ExecutionTracer; + +/** + * Agent used for recording data. + * Only records read and write. + * @author Gabau + * + */ +public class AggrePlayRecordingRWAgent extends RNRRecordingAgent { + + private Semaphore smp = new Semaphore(1); + private Map> lockAcquisitionListMap = new HashMap<>(); + + + + protected AggrePlayRecordingRWAgent(CommandLine cml) { + super(cml); + } + + @Override + protected void onLockAcquire(Object object) { + ObjectId oId = objectIdGenerator.getId(object); + if (!lockAcquisitionListMap.containsKey(oId)) { + lockAcquisitionListMap.put(oId, new LinkedList()); + } + lockAcquisitionListMap.get(oId).add(new Event(null)); + } + @Override + protected RecordingOutput getRecordingOutput() { + RecordingOutput result = new RecordingOutput(new ReadWriteAccessList(), ThreadIdGenerator.threadGenerator.getThreadIds(), + sharedGenerator.getAllLocations(), + lockAcquisitionListMap); + return result; + } + + @Override + protected void parseSharedOutput(SharedVariableOutput svo) { + + } + + @Override + public void startTest(String junitClass, String junitMethod) { + // TODO Auto-generated method stub + + } + + @Override + public void finishTest(String junitClass, String junitMethod) { + // TODO Auto-generated method stub + + } + + @Override + public ClassFileTransformer getTransformer0() { + return new BasicTransformer(new RecordingInstrumentor(RNRRecordingAgent.class, agentParams)); + } + + @Override + public void retransformBootstrapClasses(Instrumentation instrumentation, Class[] retransformableClasses) + throws Exception { + instrumentation.retransformClasses(retransformableClasses); + } + + @Override + public void exitTest(String testResultMsg, String junitClass, String junitMethod, long threadId) { + + } + + @Override + public boolean isInstrumentationActive0() { + return !ExecutionTracer.isShutdown(); + } + + @Override + public void acquireLock() { + try { + smp.acquire(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public void releaseLock() { + smp.release(); + } + + @Override + protected void onRead(SharedMemoryLocation sml) { + Event readEvent = new Event(sml); + lastEvent.set(readEvent); + acquireLock(); + sml.appendExList(sml.getLastWrite(), readEvent); + } + @Override + protected void onWrite(SharedMemoryLocation sml) { + Event writeEvent = new Event(sml); + lastEvent.set(writeEvent); + acquireLock(); + sml.write(writeEvent); + } + + @Override + protected void afterWrite() { + releaseLock(); + } + + @Override + protected void afterRead() { + releaseLock(); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayReplayAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayReplayAgent.java new file mode 100644 index 000000000..f6159280f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/AggrePlayReplayAgent.java @@ -0,0 +1,465 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.security.KeyStore.Entry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.function.Supplier; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.AgentLogger; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.TraceAgent; +import microbat.instrumentation.instr.SystemClassTransformer; +import microbat.instrumentation.instr.ThreadInstrumenter; +import microbat.instrumentation.instr.TraceInstrumenter; +import microbat.instrumentation.instr.TraceTransformer; +import microbat.instrumentation.instr.aggreplay.AggrePlayTraceInstrumenter; +import microbat.instrumentation.instr.aggreplay.ThreadIdInstrumenter; +import microbat.instrumentation.instr.aggreplay.shared.BasicTransformer; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.RecordingOutput; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.ReadWriteAccessListReplay; +import microbat.instrumentation.model.SharedMemGeneratorInitialiser; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedMemoryGenerator; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ReadCountVector; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.output.StorableReader; +import microbat.instrumentation.runtime.ExecutionTracer; +import microbat.instrumentation.runtime.IExecutionTracer; +import microbat.model.trace.Trace; +import microbat.sql.Recorder; + +public class AggrePlayReplayAgent extends TraceAgent { + + private AgentParams agentParams; + + /** + * Current replay values + */ + protected ObjectIdGenerator objectIdGenerator = new ObjectIdGenerator(); + protected SharedMemoryGenerator sharedMemGenerator = new SharedMemoryGenerator(objectIdGenerator); + private ClassFileTransformer transformer; + private static AggrePlayReplayAgent attachedAgent; + protected ReadCountVector rcVector = new ReadCountVector(); + // null if the last event not was shared + ThreadLocal lastEventLocal = ThreadLocal.withInitial(new Supplier() { + @Override + public Event get() { + return null; + } + }); + + ThreadLocal> lastObjStackLocal = ThreadLocal.withInitial(() -> new Stack<>()); + + // maps from recording thread to replay + private HashMap threadIdMap = new HashMap<>(); + + /** + * Recorded output + */ + private ReadWriteAccessList rwal; + protected RecordingOutput recordingOutput; + private HashMap recordedThreadIdMap = new HashMap<>(); + protected Map> lockAcquisitionMap; + private ReadWriteAccessListReplay rwalGeneratedAccessListReplay; + + + protected static boolean cannotRecord() { + return !ExecutionTracer.isRecordingOrStarted(); + } + + + public static void _assertObjectExists(Object obj) { + if (cannotRecord()) return; + if (attachedAgent.objectIdGenerator.getId(obj) != null) { + return; + } + _onNewObject(obj); + } + + public static void _assertArrayExists(Object object) { + if (cannotRecord()) return; + attachedAgent.assertArrayExists(object); + } + + protected void assertArrayExists(Object object) { + sharedMemGenerator.assertArray(object); + } + + public static void _onNewObject(Object object) { + if (cannotRecord()) return; + attachedAgent.onObjectCreate(object); + } + + private void onObjectCreate(Object object) { + objectIdGenerator.createId(object); + } + + public static void _onThreadStart(Thread thread) { + if (cannotRecord()) return; + if (ExecutionTracer.isRecordingOrStarted()) { + attachedAgent.onThreadStart(thread); + } + + } + + /** + * Instruemented before the lock acquisition + * @param object + */ + public static void _onLockAcquire(Object object) { + if (cannotRecord()) return; + attachedAgent.onLockAcquire(object); + } + + protected void onLockAcquire(Object obj) { + ObjectId oid = this.sharedMemGenerator.ofObjectOrArray(obj); + + Stack eventStack = this.lockAcquisitionMap.get(oid); + Event currEvent = new Event(null, getPreviousThreadId()); + if (eventStack == null || eventStack.empty() || !currEvent.equals(eventStack.peek())) { + Thread.yield(); + } + lastObjStackLocal.set(eventStack); + } + + public static void _afterLockAcquire() { + if (cannotRecord()) return; + attachedAgent.afterLockAcquire(); + } + + protected void afterLockAcquire() { + if (lastObjStackLocal.get() == null) return; + if (lastObjStackLocal.get().empty()) return; + lastObjStackLocal.get().pop(); + } + + private void onThreadStart(Thread thread) { + threadIdGenerator.createId(thread); + ThreadId tId = threadIdGenerator.getId(thread); + threadIdMap.put(thread.getId(), recordedThreadIdMap.get(tId)); + } + + public AggrePlayReplayAgent(CommandLine cmd) { + super(cmd); + agentParams = AgentParams.initFrom(cmd); + this.transformer = new BasicTransformer(new AggrePlayTraceInstrumenter(agentParams)); + } + + + + public static AggrePlayReplayAgent getAttached(CommandLine cmd) { + AgentParams parameAgentParams = AgentParams.initFrom(cmd); + if (attachedAgent == null) { + switch (parameAgentParams.getReplayMode()) { + case RELAX_RW: + attachedAgent = new LaxRWReplayAgent(cmd); + break; + case STRICT_RW: + attachedAgent = new StrictRWReplay(cmd); + break; + case AGGR: + default: + attachedAgent = new AggrePlayReplayAgent(cmd); + } + } + attachedAgent.agentParams = parameAgentParams; + return attachedAgent; + } + + + /** + * Used to get the thread id from the previous thread which the current thread maps to. + * @return + */ + protected long getPreviousThreadId() { + Long previousThreadID = threadIdMap.get(Thread.currentThread().getId()); + if (previousThreadID == null) { + ThreadId currentThreadId = threadIdGenerator.getId(Thread.currentThread()); + if (currentThreadId == null) { + return -1; + } + return recordedThreadIdMap.get(currentThreadId); + } + return previousThreadID; + } + + private void onObjectRead(Object object, String field) { + if (!attachedAgent.sharedMemGenerator.isSharedObject(object, field)) { + lastEventLocal.set(null); + return; + } + + long previousThreadId = getPreviousThreadId(); + SharedMemoryLocation sharedMemLocation = attachedAgent.sharedMemGenerator.ofField(object, field); + // consider a better alternative + onRead(previousThreadId, sharedMemLocation); + } + + protected void onRead(long previousThreadId, SharedMemoryLocation sharedMemLocation) { + Event readEvent = new Event(sharedMemLocation, previousThreadId); + lastEventLocal.set(readEvent); + if (!sharedMemLocation.isSameAsLastWrite(readEvent)) { + Thread.yield(); + lastEventLocal.set(null); + } + } + public static void _onStaticRead(String className, String fieldName) { + if (cannotRecord()) return; + attachedAgent.onStaticRead(className, fieldName); + } + + public static void _onStaticWrite(String className, String fieldName) { + if (cannotRecord()) return; + attachedAgent.onStaticWrite(className, fieldName); + } + + public static void _onNewArray(Object object) { + if (cannotRecord()) return; + attachedAgent.onNewArray(object); + } + + protected void onNewArray(Object object) { + this.sharedMemGenerator.newArray(object); + } + + public static void _onArrayRead(Object arrayRef, int index) { + if (cannotRecord()) return; + _assertArrayExists(arrayRef); + attachedAgent.onArrayRead(arrayRef, index); + } + + public static void _onArrayWrite(Object arrayRef, int index) { + if (cannotRecord()) return; + _assertArrayExists(arrayRef); + attachedAgent.onArrayWrite(arrayRef, index); + } + + public static void _onObjectRead(Object object, String field) { + if (cannotRecord()) return; + _assertObjectExists(object); + attachedAgent.onObjectRead(object, field); + } + + public static void _onObjectWrite(Object object, String field) { + if (cannotRecord()) return; + _assertObjectExists(object); + attachedAgent.onObjectWrite(object, field); + } + + protected void onStaticWrite(String className, String fieldName) { + if (!sharedMemGenerator.isSharedStaticField(className, fieldName)) { + lastEventLocal.set(null); + return; + } + SharedMemoryLocation sml = sharedMemGenerator.ofStaticField(className, fieldName); + this.onWrite(getPreviousThreadId(), sml); + } + + protected void onStaticRead(String className, String fieldName) { + if (!sharedMemGenerator.isSharedStaticField(className, fieldName)) { + lastEventLocal.set(null); + return; + } + SharedMemoryLocation sml = sharedMemGenerator.ofStaticField(className, fieldName); + this.onRead(getPreviousThreadId(), sml); + } + + protected void onArrayWrite(Object arrayRef, int index) { + if (!sharedMemGenerator.isSharedArray(arrayRef, index)) { + lastEventLocal.set(null); + return; + } + SharedMemoryLocation sml = sharedMemGenerator.ofArray(arrayRef, index); + this.onWrite(getPreviousThreadId(), sml); + } + + protected void onArrayRead(Object arrayRef, int index) { + if (!sharedMemGenerator.isSharedArray(arrayRef, index)) { + lastEventLocal.set(null); + return; + } + SharedMemoryLocation sml = sharedMemGenerator.ofArray(arrayRef, index); + this.onRead(getPreviousThreadId(), sml); + } + + private void onObjectWrite(Object object, String field) { + if (!sharedMemGenerator.isSharedObject(object, field)) { + lastEventLocal.set(null); + return; + } + long p_tid = getPreviousThreadId(); + SharedMemoryLocation shm = sharedMemGenerator.ofField(object, field); + onWrite(p_tid, shm); + + } + + protected void onWrite(long p_tid, SharedMemoryLocation shm) { + Event writeEvent = new Event(shm, p_tid); + synchronized (rcVector) { + rcVector.updateReadVectors(shm.getLocation(), p_tid); + } + + if (!shm.isSameAsPrevRunWrite(writeEvent) || !checkReads(p_tid)) { + Thread.yield(); + lastEventLocal.set(null); + return; + } + lastEventLocal.set(writeEvent); + } + + public static void _afterObjectWrite() { + if (cannotRecord()) return; + attachedAgent.afterObjectWrite(); + } + + public boolean checkReads(long threadId) { + return rwalGeneratedAccessListReplay.checkRead(rcVector, threadId); + } + + protected void afterObjectWrite() { + if (lastEventLocal.get() == null) { + return; + } + SharedMemoryLocation mlcation = lastEventLocal.get().getLocation(); + mlcation.addRepWriteEvent(lastEventLocal.get()); + mlcation.popEvent(); + + } + public static void _afterObjectRead() { + if (cannotRecord()) return; + attachedAgent.afterObjectRead(); + } + + protected boolean wasShared() { + return lastEventLocal.get() != null; + } + + protected void afterObjectRead() { + if (!wasShared()) return; + long t = getPreviousThreadId(); + SharedMemoryLocation mLocation = lastEventLocal.get().getLocation(); + // TODO(Gab): can this run in parallel? + synchronized (mLocation) { + rcVector.increment(t, lastEventLocal.get().getLocation().getLocation()); + mLocation.popRecordedLastWR(); + } + } + + private void initialiseLockAcquisitionMap(Map> lockAcquisitionMap) { + HashMap> result = new HashMap<>(); + for (Map.Entry> entry : lockAcquisitionMap.entrySet()) { + Stack toAddEvents = new Stack<>(); + LinkedList eLLinkedList = new LinkedList<>(entry.getValue()); + eLLinkedList.descendingIterator().forEachRemaining(v -> toAddEvents.push(v)); + result.put(entry.getKey(), toAddEvents); + } + this.lockAcquisitionMap = result; + } + + @Override + public void startup0(long vmStartupTime, long agentPreStartup) { + File concDumpFile = new File(agentParams.getConcDumpFile()); + try { + StorableReader reader = new StorableReader(concDumpFile); + RecordingOutput input = new RecordingOutput(); + List result = reader.read(); + RecordingOutput output = input.parse(result.get(0)); + this.rwal = output.rwAccessList; + this.recordingOutput = output; + this.rwalGeneratedAccessListReplay = new ReadWriteAccessListReplay(rwal); + initialiseLockAcquisitionMap(output.lockAcquisitionMap); + for (ThreadId threadId: recordingOutput.threadIds) { + this.recordedThreadIdMap.put(threadId, threadId.getId()); + } + this.sharedMemGenerator.init(this.recordingOutput); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + System.exit(-1); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + System.exit(-1); + } + super.startup0(vmStartupTime, agentPreStartup); + } + + + @Override + public void shutdown() throws Exception { + // Agent._exitProgram(getProgramMsg()); + System.out.println("Shutting down"); + try { + + shutdownInner(); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("Shutdown"); + } + + private void shutdownInner() { + ExecutionTracer.shutdown(); + /* collect trace & store */ + AgentLogger.debug("Building trace dependencies ..."); +// timer.newPoint("Building trace dependencies"); + // FIXME -mutithread LINYUN [3] + // LLT: only trace of main thread is recorded. + List tracers = ExecutionTracer.getAllThreadStore(); + + int size = tracers.size(); + List traceList = new ArrayList<>(size); + for (IExecutionTracer tracerInner: tracers) { + + ExecutionTracer tracer = (ExecutionTracer) tracerInner; + + Trace trace = tracer.getTrace(); + trace.setThreadId(tracer.getThreadId()); + trace.setInnerThreadId(threadIdGenerator.getId(tracer.getThreadId())); + trace.setThreadName(tracer.getThreadName()); + trace.setMain(ExecutionTracer.getMainThreadStore().equals(tracer)); + trace.setInnerThreadId(threadIdGenerator.getId(trace.getThreadId())); + // TODO(Gab): Botch needed because tracer can be initialised + // in thread id instrumenter + if (trace.getAppJavaClassPath() == null) { + trace.setAppJavaClassPath(ExecutionTracer.appJavaClassPath); + } + constructTrace(trace); + traceList.add(trace); + } + +// timer.newPoint("Saving trace"); + Recorder.create(agentParams).store(traceList); +// AgentLogger.debug(timer.getResultString()); + } + + + @Override + public ClassFileTransformer getTransformer() { + // TODO Auto-generated method stub + return this.transformer; + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRWReplayAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRWReplayAgent.java new file mode 100644 index 000000000..104cbfc49 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRWReplayAgent.java @@ -0,0 +1,93 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.util.Stack; +import java.util.concurrent.Semaphore; + +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; + +public class LaxRWReplayAgent extends AggrePlayReplayAgent { + + /** + * RW replay with non blocking yield + * @param cmd + */ + public LaxRWReplayAgent(CommandLine cmd) { + super(cmd); + } + + private Semaphore smp = new Semaphore(1); + + + @Override + protected void onLockAcquire(Object obj) { + + ObjectId oid = sharedMemGenerator.ofObjectOrArray(obj); + Stack eventStack = this.lockAcquisitionMap.get(oid); + Event currEvent = new Event(null, getPreviousThreadId()); + if (eventStack == null || eventStack.empty() || !currEvent.equals(eventStack.peek())) { + Thread.yield(); + } + lastObjStackLocal.set(eventStack); + } + + @Override + protected void onRead(long previousThreadId, SharedMemoryLocation sharedMemLocation) { + // TODO Auto-generated method stub + Event readEvent = new Event(sharedMemLocation, previousThreadId); + lastEventLocal.set(readEvent); + if (!sharedMemLocation.canRead(readEvent)) { + Thread.yield(); + } + } + + @Override + protected void onWrite(long p_tid, SharedMemoryLocation shm) { + Event writeEvent = new Event(shm, p_tid); + if (!shm.canWrite(writeEvent)) { + Thread.yield(); + } + lastEventLocal.set(writeEvent); + } + + private void acquireLock() { + try { + this.smp.acquire(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void releaseLock() { + this.smp.release(); + } + + + @Override + protected void afterObjectWrite() { + if (!wasShared()) { + return; + } + SharedMemoryLocation lastLocation = lastEventLocal.get().getLocation(); + lastLocation.setLastWrite(lastEventLocal.get()); + lastLocation.popEvent(); + } + + @Override + protected void afterObjectRead() { + if (!wasShared()) { + return; + } + SharedMemoryLocation lastLocation = lastEventLocal.get().getLocation(); + lastLocation.read(lastEventLocal.get()); + } + + @Override + public void startup0(long vmStartupTime, long agentPreStartup) { + super.startup0(vmStartupTime, agentPreStartup); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRecordingAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRecordingAgent.java new file mode 100644 index 000000000..a2e64de7f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/LaxRecordingAgent.java @@ -0,0 +1,172 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.function.Supplier; + +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.record.RecordingInstrumentor; +import microbat.instrumentation.instr.aggreplay.shared.BasicTransformer; +import microbat.instrumentation.instr.aggreplay.shared.RecordingOutput; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedMemoryGenerator; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ReadCountVector; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.runtime.ExecutionTracer; + +/** + * Implementration of AggrePlay for non-blocking wait. + * @author Gabau + * + */ +public class LaxRecordingAgent extends RNRRecordingAgent { + private ReadCountVector rcVector = new ReadCountVector(); + private ReadWriteAccessList rwal = new ReadWriteAccessList(); + private Map> lockAcquisitionListMap = new HashMap<>(); + + + public static final Semaphore LOCK_OBJECT = new Semaphore(1); + + // last write, these two variables are used for computation, + // not for the actual last write, last read + private ThreadLocal lw = ThreadLocal.withInitial(new Supplier() { + @Override + public Event get() { + return null; + } + }); + // last read + private ThreadLocal lr = ThreadLocal.withInitial(new Supplier() { + @Override + public Event get() { + return null; + } + }); + private boolean wasShared = false; + + protected LaxRecordingAgent(CommandLine cml) { + super(cml); + } + + @Override + protected void onRead(SharedMemoryLocation smLocation) { + Event readEvent = new Event(smLocation); + lr.set(readEvent); + lastEvent.set(readEvent); + rcVector.increment(Thread.currentThread().getId(), smLocation.getLocation()); + _acquireLock(); + Event lastWrite = smLocation.getLastWrite(); + lw.set(lastWrite); + wasShared = true; + } + + + private void updateReadVectors(Event event) { + rcVector.updateReadVectors( + event.getLocation().getLocation(), Thread.currentThread().getId()); + rwal.add(event.getLocation().getLocation(), event, rcVector); + } + + @Override + protected void onWrite(SharedMemoryLocation sml) { + wasShared = true; + Event writeEvent = new Event(sml); + this.updateReadVectors(writeEvent); + sml.write(writeEvent); + } + + @Override + protected void acquireLock() { + try { + LOCK_OBJECT.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + protected void releaseLock() { + + LOCK_OBJECT.release(); + } + + @Override + protected void afterWrite() { + // TODO Auto-generated method stub + + } + + @Override + protected void afterRead() { + if (!this.wasShared) return; + _releaseLock(); + Event lw = this.lw.get(); + Event lr = this.lr.get(); + lr.getLocation().appendExList(lw, lr); + } + + @Override + protected void onLockAcquire(Object lockObject) { + ObjectId oId = sharedGenerator.ofObjectOrArray(lockObject); + if (!lockAcquisitionListMap.containsKey(oId)) { + lockAcquisitionListMap.put(oId, new LinkedList()); + } + lockAcquisitionListMap.get(oId).add(new Event(null)); + } + + @Override + protected RecordingOutput getRecordingOutput() { + List threadIds = ThreadIdGenerator.threadGenerator.getThreadIds(); + RecordingOutput output = new RecordingOutput(rwal, threadIds, + sharedGenerator.getAllLocations(), + lockAcquisitionListMap); + return output; + } + + @Override + protected void parseSharedOutput(SharedVariableOutput svo) { + + } + + + + @Override + public ClassFileTransformer getTransformer0() { + // TODO Auto-generated method stub + return new BasicTransformer(new RecordingInstrumentor(RNRRecordingAgent.class, agentParams)); + } + + @Override + public void retransformBootstrapClasses(Instrumentation instrumentation, Class[] retransformableClasses) + throws Exception { + // TODO Auto-generated method stub + + } + + @Override + public void exitTest(String testResultMsg, String junitClass, String junitMethod, long threadId) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isInstrumentationActive0() { + return !ExecutionTracer.isShutdown(); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/RNRRecordingAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/RNRRecordingAgent.java new file mode 100644 index 000000000..5003ca57f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/RNRRecordingAgent.java @@ -0,0 +1,341 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.filter.GlobalFilterChecker; +import microbat.instrumentation.instr.SystemClassTransformer; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.RecordingOutput; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedMemoryGenerator; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.storage.FileStorage; +import microbat.instrumentation.model.storage.Storable; +import microbat.instrumentation.output.StorableReader; +import microbat.instrumentation.output.StorableWriter; +import microbat.instrumentation.runtime.ExecutionTracer; +import sav.strategies.dto.AppJavaClassPath; + +/** + * Abstract class representing a recording class + * @author Gabau + * + */ +public abstract class RNRRecordingAgent extends Agent { + private static RNRRecordingAgent recordingAgent; + protected ObjectIdGenerator objectIdGenerator = new ObjectIdGenerator(); + protected SharedMemoryGenerator sharedGenerator = new SharedMemoryGenerator(objectIdGenerator); + protected ThreadLocal lastEvent = ThreadLocal.withInitial(new Supplier() { + @Override + public Event get() { + return null; + } + }); + protected AgentParams agentParams; + protected TimeoutThread timeoutThread; + + + protected static boolean cannotRecord() { + return !ExecutionTracer.isRecordingOrStarted(); + } + + protected void assertObjectExists(Object object) { + if (objectIdGenerator.getId(object) != null) { + return; + } + objectIdGenerator.createId(object); + } + + public static void _assertObjectExists(Object object) { + if (cannotRecord()) return; + recordingAgent.assertObjectExists(object); + } + + public static void _assertArrayExists(Object object) { + if (cannotRecord()) return; + recordingAgent.assertArrayExists(object); + } + + protected void assertArrayExists(Object object) { + sharedGenerator.assertArray(object); + } + + + public static void _acquireLock() { + if (cannotRecord()) return; + recordingAgent.acquireLock(); + } + + public static void _releaseLock() { + if (cannotRecord()) return; + recordingAgent.releaseLock(); + } + + public static RNRRecordingAgent getAttached(CommandLine cml) { + if (recordingAgent != null) { + recordingAgent.agentParams = AgentParams.initFrom(cml); + return recordingAgent; + } + recordingAgent = new LaxRecordingAgent(cml); + return recordingAgent; + } + + + protected RNRRecordingAgent(CommandLine cml) { + this.agentParams = AgentParams.initFrom(cml); + } + + public static void _onNewObject(Object object) { + if (cannotRecord()) return; + recordingAgent.onObjectCreate(object); + } + + public static void _onNewArray(Object obj) { + if (cannotRecord()) return; + recordingAgent.onNewArray(obj); + } + + private void onNewArray(Object obj) { + sharedGenerator.newArray(obj); + } + + public static void _onObjectRead(Object object, String field) { + if (cannotRecord()) return; + _assertObjectExists(object); + recordingAgent.onObjectRead(object, field); + } + + public static void _onObjectWrite(Object object, String field) { + if (cannotRecord()) return; + _assertObjectExists(object); + recordingAgent.onObjectWrite(object, field); + } + + + public static void _afterObjectWrite() { + if (cannotRecord()) return; + recordingAgent._afterObjectWriteInner(); + } + + + + + private void _afterObjectWriteInner() { + if (this.lastEvent.get() == null) { + return; + } + this.afterWrite(); + } + + private void _afterObjectReadInner() { + if (this.lastEvent.get() == null) { + return; + } + this.afterRead(); + } + + public static void _afterObjectRead() { + if (cannotRecord()) return; + recordingAgent._afterObjectReadInner(); + } + + public static void _onStaticRead(String className, String fieldName) { + if (cannotRecord()) return; + recordingAgent.onStaticRead(className, fieldName); + } + + public static void _onStaticWrite(String className, String fieldName) { + if (cannotRecord()) return; + recordingAgent.onStaticWrite(className, fieldName); + } + + public static void _onArrayRead(Object arrayRef, int index) { + if (cannotRecord()) return; + _assertArrayExists(arrayRef); + recordingAgent.onArrayRead(arrayRef, index); + } + + public static void _onArrayWrite(Object arrayRef, int index) { + if (cannotRecord()) return; + _assertArrayExists(arrayRef); + recordingAgent.onArrayWrite(arrayRef, index); + } + + /** + * Instrumented after the lock has been acquired + * @param object + */ + public static void _onLockAcquire(Object object) { + if (cannotRecord()) return; + recordingAgent.onLockAcquire(object); + } + + + protected abstract void onRead(SharedMemoryLocation sml); + + protected abstract void onWrite(SharedMemoryLocation sml); + + protected abstract void acquireLock(); + protected abstract void releaseLock(); + + /** + * After object creation + * @param object + */ + protected final void onObjectCreate(Object object) { + objectIdGenerator.createId(object); + } + + /** + * Before object read. + * @param object + * @param field + */ + protected final void onObjectRead(Object object, String field) { + if (!sharedGenerator.isSharedObject(object, field)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofField(object, field); + this.onRead(sml); + } + + /** + * Called before object write + * @param object + * @param field + */ + protected final void onObjectWrite(Object object, String field) { + if (!sharedGenerator.isSharedObject(object, field)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofField(object, field); + this.onWrite(sml); + } + + /** + * Called after object write + */ + protected abstract void afterWrite(); + + /** + * Called after object read + */ + protected abstract void afterRead(); + + protected abstract void onLockAcquire(Object object); + + protected final void onStaticRead(String className, String fieldName) { + if (!sharedGenerator.isSharedStaticField(className, fieldName)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofStaticField(className, fieldName); + this.onRead(sml); + } + + protected final void onStaticWrite(String className, String fieldName) { + if (!sharedGenerator.isSharedStaticField(className, fieldName)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofStaticField(className, fieldName); + this.onWrite(sml); + } + + protected final void onArrayRead(Object arrayRef, int arrayIndex) { + if (!sharedGenerator.isSharedArray(arrayRef, arrayIndex)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofArray(arrayRef, arrayIndex); + this.onRead(sml); + } + + protected final void onArrayWrite(Object arrayRef, int arrayIndex) { + if (!sharedGenerator.isSharedArray(arrayRef, arrayIndex)) { + lastEvent.set(null); + return; + } + SharedMemoryLocation sml = sharedGenerator.ofArray(arrayRef, arrayIndex); + this.onWrite(sml); + } + + @Override + public void startup0(long vmStartupTime, long agentStartupTime) { + timeoutThread = new TimeoutThread(this); + timeoutThread.setTimeout(agentParams.getTimeOut()); + timeoutThread.start(); + AppJavaClassPath appPath = agentParams.initAppClassPath(); + GlobalFilterChecker.setup(appPath, agentParams.getIncludesExpression(), agentParams.getExcludesExpression()); + recordingAgent = this; + SystemClassTransformer.attachThreadId(getInstrumentation()); + String dumpFileStr = agentParams.getDumpFile(); + if (dumpFileStr == null) dumpFileStr = "temp.txt"; + File dumpFile = new File(dumpFileStr); + StorableReader fileReader = null; + try { + fileReader = new StorableReader(dumpFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Failed to find dump file"); + } + + try { + List data = fileReader.read(); + SharedVariableOutput svOutput = new SharedVariableOutput(data.get(0)); + sharedGenerator.init(svOutput); + parseSharedOutput(svOutput); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void shutdown() { + File file = new File(this.agentParams.getConcDumpFile()); + try { + StorableWriter writer = new StorableWriter(file); + RecordingOutput output = getRecordingOutput(); + writer.writeStorable(output); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + protected abstract RecordingOutput getRecordingOutput(); + + protected abstract void parseSharedOutput(SharedVariableOutput svo); + + @Override + public void startTest(String junitClass, String junitMethod) { + ExecutionTracer._start(); + ExecutionTracer.appJavaClassPath.setOptionalTestClass(junitClass); + ExecutionTracer.appJavaClassPath.setOptionalTestMethod(junitMethod); + } + + @Override + public void finishTest(String junitClass, String junitMethod) { + ExecutionTracer.shutdown(); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/SharedVariableAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/SharedVariableAgent.java new file mode 100644 index 000000000..113c00643 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/SharedVariableAgent.java @@ -0,0 +1,179 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.io.File; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import javax.print.CancelablePrintJob; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.AgentLogger; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.filter.GlobalFilterChecker; +import microbat.instrumentation.instr.SystemClassTransformer; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.shared.BasicTransformer; +import microbat.instrumentation.instr.aggreplay.shared.SharedObjectAccessInstrumentator; +import microbat.instrumentation.model.SharedVariableObjectId; +import microbat.instrumentation.model.generator.IdGenerator; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedVariableArrayRef; +import microbat.instrumentation.model.generator.SharedVariableObjectGenerator; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.StaticFieldLocation; +import microbat.instrumentation.model.storage.FileStorage; +import microbat.instrumentation.model.storage.Storable; +import microbat.instrumentation.output.StorableWriter; +import microbat.instrumentation.runtime.ExecutionTracer; +import sav.common.core.Pair; +import sav.strategies.dto.AppJavaClassPath; + +/** + * Dynamically determines whether a memory location is shared or not + * @author Gabau + * + */ +public class SharedVariableAgent extends Agent { + + private ClassFileTransformer transformer = null; + private SharedVariableObjectGenerator shObjectIdGenerator = new SharedVariableObjectGenerator(); + private static SharedVariableAgent agent = new SharedVariableAgent(); + private AgentParams agentParams = null; + private TimeoutThread timeoutThread; + + + public static SharedVariableAgent getAgent(CommandLine cmd) { + agent.agentParams = AgentParams.initFrom(cmd); + return agent; + } + + protected static boolean cannotRecord() { + return !ExecutionTracer.isRecordingOrStarted(); + } + + // TODO(Gab) + public static void _onNewArray(Object arrayRef) { + if (cannotRecord()) return; + agent.shObjectIdGenerator.createArrayId(arrayRef); + } + + public static void _onLockAcquire(Object object) { + + } + + public static void _onArrayAccess(Object arrayRef, int index) { + if (cannotRecord()) return; + _assertArrayExists(arrayRef); + agent.onArrayAccess(arrayRef, index); + } + + private void onArrayAccess(Object arrayRef, int index) { + SharedVariableArrayRef arrayVal = shObjectIdGenerator.getArrayId(arrayRef); + arrayVal.addAccess(index, Thread.currentThread().getId()); + } + + public static void _assertObjectExists(Object object) { + if (cannotRecord()) return; + agent.shObjectIdGenerator.assertId(object); + } + + public static void _assertArrayExists(Object object) { + if (cannotRecord()) return; + agent.shObjectIdGenerator.assertArrayId(object); + } + + /** + * Generate object id on object creation + * @param object + */ + public static void _onObjectCreation(Object object) { + if (cannotRecord()) return; + agent.shObjectIdGenerator.createId(object); + } + + + public static void _onObjectAccess(Object object, String field) { + if (cannotRecord()) return; + _assertObjectExists(object); + agent.shObjectIdGenerator.getId(object).addAccess(Thread.currentThread().getId(), field); + } + + public static void _onStaticAccess(String className, String fieldName) { + if (cannotRecord()) return; + agent.onStaticAccess(className, fieldName); + } + + private void onStaticAccess(String className, String fieldName) { + this.shObjectIdGenerator.addAccessStaticField(new StaticFieldLocation(className, fieldName), + Thread.currentThread().getId()); + } + + + + @Override + public void startup0(long vmStartupTime, long agentPreStartup) { + this.timeoutThread = new TimeoutThread(this); + this.timeoutThread.setTimeout(agentParams.getTimeOut()); + timeoutThread.start(); + AppJavaClassPath appPath = agentParams.initAppClassPath(); + GlobalFilterChecker.setup(appPath, agentParams.getIncludesExpression(), agentParams.getExcludesExpression()); + SystemClassTransformer.attachThreadId(getInstrumentation()); + } + + @Override + public void shutdown() throws Exception { + write(); + AgentLogger.debug("Ended program"); + } + + private void write() throws Exception { + StorableWriter writer = new StorableWriter(new File(agentParams.getDumpFile())); + SharedVariableOutput output = new SharedVariableOutput(shObjectIdGenerator); + writer.writeStorable(output); + } + + @Override + public void startTest(String junitClass, String junitMethod) { + ExecutionTracer._start(); + ExecutionTracer.appJavaClassPath.setOptionalTestClass(junitClass); + ExecutionTracer.appJavaClassPath.setOptionalTestMethod(junitMethod); + } + + @Override + public void finishTest(String junitClass, String junitMethod) { + ExecutionTracer.shutdown(); + } + + @Override + public ClassFileTransformer getTransformer0() { + SharedObjectAccessInstrumentator instrumentator = new SharedObjectAccessInstrumentator(agentParams); + return new BasicTransformer(instrumentator); + } + + @Override + public void retransformBootstrapClasses(Instrumentation instrumentation, Class[] retransformableClasses) + throws Exception { + // TODO Auto-generated method stub + + } + + @Override + public void exitTest(String testResultMsg, String junitClass, String junitMethod, long threadId) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isInstrumentationActive0() { + return !ExecutionTracer.isShutdown(); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/StrictRWReplay.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/StrictRWReplay.java new file mode 100644 index 000000000..d974ea1d7 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/agents/StrictRWReplay.java @@ -0,0 +1,104 @@ +package microbat.instrumentation.instr.aggreplay.agents; + +import java.lang.instrument.ClassFileTransformer; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.Semaphore; + +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; + +/** + * Different mode of replay, used to test stricter replay. + * Use blocking yield instead. + * For every shm, we have a list of writes, and the reads associated to each write. + * So each write, we can simply wait for the read to occur + * before we allow another write. + * + * @author Gabau + * + */ +public class StrictRWReplay extends AggrePlayReplayAgent { + + private Semaphore smp = new Semaphore(1); + + public StrictRWReplay(CommandLine cmd) { + super(cmd); + } + + + @Override + protected void onLockAcquire(Object obj) { + + ObjectId oid = this.sharedMemGenerator.ofObjectOrArray(obj); + + Stack eventStack = this.lockAcquisitionMap.get(oid); + Event currEvent = new Event(null, getPreviousThreadId()); + while (eventStack == null || !currEvent.equals(eventStack.peek())) { + Thread.yield(); + } + lastObjStackLocal.set(eventStack); + } + + + @Override + protected void onRead(long previousThreadId, SharedMemoryLocation sharedMemLocation) { + // TODO Auto-generated method stub + Event readEvent = new Event(sharedMemLocation, previousThreadId); + lastEventLocal.set(readEvent); + while (!sharedMemLocation.canRead(readEvent)) { + Thread.yield(); + } + } + + @Override + protected void onWrite(long p_tid, SharedMemoryLocation shm) { + Event writeEvent = new Event(shm, p_tid); + while (!shm.canWrite(writeEvent)) { + Thread.yield(); + } + lastEventLocal.set(writeEvent); + } + + private void acquireLock() { + try { + this.smp.acquire(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void releaseLock() { + this.smp.release(); + } + + + @Override + protected void afterObjectWrite() { + if (!wasShared()) { + return; + } + SharedMemoryLocation lastLocation = lastEventLocal.get().getLocation(); + lastLocation.setLastWrite(lastEventLocal.get()); + lastLocation.popEvent(); + } + + @Override + protected void afterObjectRead() { + if (!wasShared()) { + return; + } + SharedMemoryLocation lastLocation = lastEventLocal.get().getLocation(); + lastLocation.read(lastEventLocal.get()); + } + + @Override + public void startup0(long vmStartupTime, long agentPreStartup) { + super.startup0(vmStartupTime, agentPreStartup); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/output/SharedVariableOutput.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/output/SharedVariableOutput.java new file mode 100644 index 000000000..d6d3c2e7d --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/output/SharedVariableOutput.java @@ -0,0 +1,115 @@ +package microbat.instrumentation.instr.aggreplay.output; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.SharedMemGeneratorInitialiser; +import microbat.instrumentation.model.SharedVariableObjectId; +import microbat.instrumentation.model.generator.ArrayIndexMemLocation; +import microbat.instrumentation.model.generator.SharedVariableArrayRef; +import microbat.instrumentation.model.generator.SharedVariableObjectGenerator; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.StaticFieldLocation; +import microbat.instrumentation.model.storage.Storable; + +/** + * Class representing the Shared variable output. + * Data needed for shared variable analysis + * + * Memory locations which are shared + * + * @author Gabau + * + */ +public class SharedVariableOutput extends Storable implements Parser, SharedMemGeneratorInitialiser { + public Set sharedObjects; + public Set sharedArrays; + public Set sharedStaticFields; + public SharedVariableOutput(SharedVariableObjectGenerator objectGen) { + Collection sharedObjectsIds = objectGen.getSharedVariables(); + sharedObjects = objectGen.getSharedVariables() + .stream().collect(Collectors.toSet()); + sharedArrays = objectGen.getSharedArrays(); + sharedStaticFields = objectGen.getSharedStaticFields(); + } + public SharedVariableOutput(ParseData data) { + parse(data); + } + + + public Map getObjects() { + Map result = new HashMap<>(); + for (SharedVariableObjectId svoId: sharedObjects) { + RecorderObjectId fromSharedVar = new RecorderObjectId(svoId.getObjectId()); + fromSharedVar.updateSharedFieldSet(svoId.getFieldAccessList()); + result.put(fromSharedVar.getObjectId(), fromSharedVar); + } + return result; + + } + + @Override + public SharedVariableOutput parse(ParseData data) { + List objectIds = data.getField("sharedObjects").toList(new Function() { + @Override + public SharedVariableObjectId apply(ParseData data) { + SharedVariableObjectId object = new SharedVariableObjectId(); + object.parse(data); + return object; + } + }); + this.sharedObjects = objectIds.stream().collect(Collectors.toSet()); + this.sharedArrays = data.getField("sharedArrays").toList() + .stream() + .map(v -> new SharedVariableArrayRef(null).parse(v)) + .collect(Collectors.toSet()); + this.sharedStaticFields = data.getField("sharedStaticFields") + .toList() + .stream() + .map(v -> new StaticFieldLocation(v)) + .collect(Collectors.toSet()); + return this; + } + @Override + public int hashCode() { + return Objects.hash(sharedObjects); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SharedVariableOutput other = (SharedVariableOutput) obj; + return Objects.equals(sharedObjects, other.sharedObjects); + } + @Override + public Set getArrayRefs() { + return this.sharedArrays.stream() + .flatMap(v -> v.getSharedMemLocations().stream()) + .map(v -> new SharedMemoryLocation(v)) + .collect(Collectors.toSet()); + } + @Override + public Set getStaticFields() { + return this.sharedStaticFields.stream() + .map(v -> new SharedMemoryLocation(v)).collect(Collectors.toSet()); + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/record/RecordingInstrumentor.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/record/RecordingInstrumentor.java new file mode 100644 index 000000000..4968bbe13 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/record/RecordingInstrumentor.java @@ -0,0 +1,173 @@ +package microbat.instrumentation.instr.aggreplay.record; + +import java.util.concurrent.Semaphore; + +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.DUP; +import org.apache.bcel.generic.DUP2; +import org.apache.bcel.generic.DUP2_X1; +import org.apache.bcel.generic.DUP_X2; +import org.apache.bcel.generic.FieldInstruction; +import org.apache.bcel.generic.GETFIELD; +import org.apache.bcel.generic.GETSTATIC; +import org.apache.bcel.generic.INVOKESTATIC; +import org.apache.bcel.generic.Instruction; +import org.apache.bcel.generic.InstructionHandle; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.LDC; +import org.apache.bcel.generic.NEW; +import org.apache.bcel.generic.POP; +import org.apache.bcel.generic.POP2; +import org.apache.bcel.generic.PUTFIELD; +import org.apache.bcel.generic.PUTSTATIC; +import org.apache.bcel.generic.SWAP; +import org.apache.bcel.generic.Type; + +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.instr.aggreplay.ObjectAccessInstrumentator; +import microbat.instrumentation.instr.aggreplay.agents.SharedVariableAgent; +import microbat.instrumentation.model.id.AggrePlayMethods; + +public class RecordingInstrumentor extends ObjectAccessInstrumentator { + + public static final String ACQUIRE_LOCK_STRING = "_acquireLock"; + public static final String RELEASE_LOCK_STRING = "_releaseLock"; + public static final String LOCK_SIG_STRING = "()V"; + + public RecordingInstrumentor(Class clazz, AgentParams agentParams) { + super(clazz, agentParams); + } + + + + + + @Override + protected void instrumentPutStatic(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + /** + * Need to call GETSTATIC to force the class loader to run first + * so that there is no interleaving between static calls. + */ + InstructionList beforePutStatic = new InstructionList(); + PUTSTATIC ps = (PUTSTATIC) ih.getInstruction(); + GETSTATIC gs = new GETSTATIC(ps.getIndex()); + beforePutStatic.append(gs); + if (ps.getType(cpg).equals(Type.LONG) || ps.getType(cpg).equals(Type.DOUBLE)) { + beforePutStatic.append(new POP2()); + } else { + beforePutStatic.append(new POP()); + } + insertInsnHandler(il, beforePutStatic, ih); + + super.instrumentPutStatic(cpg, il, ih); + InstructionList afterInstructionList = new InstructionList(); + afterInstructionList.append(AggrePlayMethods.AFTER_OBJECT_WRITE.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, afterInstructionList, ih); + } + + protected boolean isComputationType2(FieldInstruction ih, ConstantPoolGen cpg) { + return ih.getType(cpg).equals(Type.LONG) + || ih.getType(cpg).equals(Type.DOUBLE); + } + + @Override + protected void instrumentGetStaticInstruction(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + InstructionList beforeGetStaticInstructionList = new InstructionList(); + beforeGetStaticInstructionList.append(ih.getInstruction()); + if (isComputationType2((FieldInstruction) ih.getInstruction(), cpg)) { + beforeGetStaticInstructionList.append(new POP2()); + } else { + beforeGetStaticInstructionList.append(new POP()); + } + insertInsnHandler(il, beforeGetStaticInstructionList, ih); + super.instrumentGetStaticInstruction(cpg, il, ih); + InstructionList afterInstructionList = new InstructionList(); + afterInstructionList.append(AggrePlayMethods.AFTER_OBJECT_READ.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, afterInstructionList, ih); + } + + + + + + @Override + protected void instrumentArrayRead(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + // TODO Auto-generated method stub + super.instrumentArrayRead(cpg, il, ih); + InstructionList toAppend = new InstructionList(); + toAppend.append(AggrePlayMethods.AFTER_OBJECT_READ.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, toAppend, ih); + } + + @Override + protected void instrumentArrayWrite(ConstantPoolGen cpg, InstructionList il, InstructionHandle ih) { + // TODO Auto-generated method stub + super.instrumentArrayWrite(cpg, il, ih); + InstructionList toAppend = new InstructionList(); + toAppend.append(AggrePlayMethods.AFTER_OBJECT_WRITE.toInvokeStatic(cpg, agentClass)); + appendInstruction(il, toAppend, ih); + } + + @Override + protected void instrumentPutField(ConstantPoolGen constPool, INVOKESTATIC onObjectWriteInvoke, + InstructionList iList, InstructionHandle handle, PUTFIELD putField) { + InstructionList beforeInstructionList = new InstructionList(); + InstructionList afterInstructionList = new InstructionList(); + + if (isComputationType2(putField, constPool)) { + beforeInstructionList.append(new DUP2_X1()); + beforeInstructionList.append(new POP2()); + beforeInstructionList.append(new DUP()); + } else { + // objectRef, value + beforeInstructionList.append(new SWAP()); + // v, o, o + beforeInstructionList.append(new DUP()); + } + beforeInstructionList.append(new LDC(constPool.addString(putField.getFieldName(constPool)))); + // v, o, o, c + beforeInstructionList + .append(createInvokeStatic(constPool, agentClass, + AggrePlayMethods.BEFORE_OBJECT_WRITE)); + // v, o + if (isComputationType2(putField, constPool)) { + beforeInstructionList.append(new DUP_X2()); + beforeInstructionList.append(new POP()); + } else { + beforeInstructionList.append(new SWAP()); + } + // o, v + afterInstructionList.append(AggrePlayMethods.AFTER_OBJECT_WRITE.toInvokeStatic(constPool, agentClass)); + + insertInsnHandler(iList, beforeInstructionList, handle); + appendInstruction(iList, afterInstructionList, handle); + } + + @Override + protected void instrumentGetField(ConstantPoolGen constPool, INVOKESTATIC onObjectReadInvoke, InstructionList iList, + InstructionHandle handle, GETFIELD getfield) { + + // TODO Auto-generated method stub + InstructionList beforeInstructionList = new InstructionList(); + InstructionList afterInstructionList = new InstructionList(); + + beforeInstructionList.append(new DUP()); + // objectRef, objectRef + beforeInstructionList + .append(new LDC(constPool.addString(getfield.getFieldName(constPool)))); + // obj, obj, field + beforeInstructionList.append(createInvokeStatic(constPool, + agentClass, AggrePlayMethods.BEFORE_OBJECT_READ)); + + afterInstructionList.append(createInvokeStatic(constPool, agentClass, + AggrePlayMethods.AFTER_OBJECT_READ)); + insertInsnHandler(iList, beforeInstructionList, handle); + appendInstruction(iList, afterInstructionList, handle); + } + + + + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/replay/ReplayingInstrumentator.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/replay/ReplayingInstrumentator.java new file mode 100644 index 000000000..48c4358d0 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/replay/ReplayingInstrumentator.java @@ -0,0 +1,5 @@ +package microbat.instrumentation.instr.aggreplay.replay; + +public class ReplayingInstrumentator { + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/BasicTransformer.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/BasicTransformer.java new file mode 100644 index 000000000..23ba4a3fe --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/BasicTransformer.java @@ -0,0 +1,73 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.time.DayOfWeek; + +import microbat.instrumentation.filter.GlobalFilterChecker; +import microbat.instrumentation.instr.AbstractInstrumenter; +import microbat.instrumentation.instr.AbstractTransformer; + +public class BasicTransformer extends AbstractTransformer implements ClassFileTransformer { + private AbstractInstrumenter instrumenter; + + public BasicTransformer(AbstractInstrumenter inst) { + this.instrumenter = inst; + } + + @Override + public byte[] doTransform(ClassLoader loader, String classFName, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + // TODO Auto-generated method stub + /* bootstrap classes */ + + if ((loader == null) || (protectionDomain == null)) { + if (!GlobalFilterChecker.isTransformable(classFName, null, true)) { + return null; + } + } + + if (protectionDomain != null) { + String path = protectionDomain.getCodeSource().getLocation().getFile(); + if (!GlobalFilterChecker.isTransformable(classFName, path, false)) { + return null; + } + } + + /* do instrumentation */ + try { + byte[] result = instrumenter.instrument(classFName, classfileBuffer); + + if (classFName.equals("simplebug1/simplebug/TestObject")) { + File otherFile = new File("K:\\OldOutput.class"); + File tocreate = new File("K:\\Output.class"); + try { + FileOutputStream fileOutputStream = new FileOutputStream(otherFile); + fileOutputStream.write(classfileBuffer); + + FileOutputStream fw = new FileOutputStream(tocreate); + fw.write(result); + fw.flush(); + fileOutputStream.flush(); + fileOutputStream.close(); + fw.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ObjectIdParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ObjectIdParser.java new file mode 100644 index 000000000..6eded9ac2 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ObjectIdParser.java @@ -0,0 +1,20 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import java.util.HashSet; + +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ThreadId; + +public class ObjectIdParser implements Parser { + + @Override + public ObjectId parse(ParseData data) { + ParseData threadId = data.innerDataMap.get("threadId"); + ThreadId actualThreadId = ThreadId.createThreadId(threadId); + int objectCounter = Integer.parseInt(data.innerDataMap.get("objectCounter").getValue()); + ObjectId objectId = new ObjectId(actualThreadId, objectCounter); + return objectId; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ParseData.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ParseData.java new file mode 100644 index 000000000..b8f3002de --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/ParseData.java @@ -0,0 +1,102 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import sav.common.core.Pair; + +public class ParseData { + Map innerDataMap = new HashMap<>(); + List listData; + String actualData = null; + + public String getObjectType() { + return innerDataMap.get("ObjectType").getValue(); + } + + public ParseData getField(String field) { + return innerDataMap.get(field); + } + public String getValue() { + return actualData; + } + + public Long getLongValue() { + return Long.parseLong(actualData); + } + + public List toList(Function function) { + if (this.listData == null) { + return Collections.emptyList(); + } + return this.listData.stream().map(function).collect(Collectors.toList()); + } + + public Pair toPair(Function keyGen, Function valueGen) { + T firstValue = null; + V secondValue = null; + int counter = 0; + for (ParseData data : this.listData) { + if (counter == 2) { + break; + } + if (counter == 0) { + firstValue = keyGen.apply(data); + } else { + secondValue = valueGen.apply(data); + } + counter++; + } + return Pair.of(firstValue, secondValue); + + } + + public Map toMap(Function keyGen, Function valueGen) { + Map result = new HashMap<>(); + for (Pair dataPair : toPairList()) { + T key = keyGen.apply(dataPair.first()); + V value = valueGen.apply(dataPair.second()); + result.put(key, value); + } + return result; + } + + public List> toPairList() { + if (this.listData == null) { + return Collections.emptyList(); + } + LinkedList> result = new LinkedList<>(); + ParseData data1 = null; + ParseData data2 = null; + int ctr = 0; + for (ParseData data : listData) { + if (ctr%2 == 0) { + data1 = data; + } else { + data2 = data; + result.add(Pair.of(data1, data2)); + } + ctr++; + } + + return result; + } + + public List toList() { + return listData; + } + + public int getIntValue() { + return Integer.parseInt(actualData); + } + + + public boolean isClass(Class clazz) { + return getObjectType().equals(clazz.getName()); + } +} \ No newline at end of file diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/Parser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/Parser.java new file mode 100644 index 000000000..09fba8809 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/Parser.java @@ -0,0 +1,5 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +public interface Parser { + public T parse(ParseData data); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/RecordingOutput.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/RecordingOutput.java new file mode 100644 index 000000000..0e9cf2f38 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/RecordingOutput.java @@ -0,0 +1,176 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.SharedMemGeneratorInitialiser; +import microbat.instrumentation.model.generator.ArrayIndexMemLocation; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedVariableArrayRef; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectFieldMemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ReadCountVector; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.StaticFieldLocation; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.storage.Storable; + +/** + * Class that represents the recording output for aggreplay recording. + * Stages involved -> Shared var detection -> recording -> replay + * + * @author Gabau + * + */ +public class RecordingOutput extends Storable implements Parser, SharedMemGeneratorInitialiser { + public ReadWriteAccessList rwAccessList; // WR_var(e) + // used to get the object acquisition + public List threadIds; + public List sharedMemoryLocations; // W_var(e) + public Map> lockAcquisitionMap; + public long memoryUsed; + public RecordingOutput(ReadWriteAccessList rwAccessList, + List threadIds, + List sharedMemoryLocations, + Map> lockAcquisitionMap) { + super(); + this.rwAccessList = rwAccessList; + // TODO(Gab): filter out the objects that aren't used. + this.sharedMemoryLocations = sharedMemoryLocations; + this.threadIds = threadIds; + this.lockAcquisitionMap = lockAcquisitionMap; + this.memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + } + + public RecordingOutput() { + + } + public Map> getLockAcquisitionMap() { + return lockAcquisitionMap; + } + + // used for shared mem locations + @Override + public Map getObjects() { + Map result = new HashMap<>(); + for (SharedMemoryLocation shMemoryLocation : this.sharedMemoryLocations) { + if (shMemoryLocation.isSharedObjectMem()) { + ObjectId objectId = shMemoryLocation.getLocation().getObjectId(); + if (!result.containsKey(objectId)) { + result.put(objectId, new RecorderObjectId(objectId)); + } + RecorderObjectId toObtainRObjectId = result.get(objectId); + ObjectFieldMemoryLocation ofml = (ObjectFieldMemoryLocation) shMemoryLocation.getLocation(); + toObtainRObjectId.setField(ofml.getField(), shMemoryLocation); + } + } + + return result; + } + + + public static List parseThreadIds(ParseData parseData) { + List values = parseData.toList(); + return values.stream().map(new Function() { + @Override + public ThreadId apply(ParseData pData) { + return ThreadId.createThreadId(pData); + } + }).collect(Collectors.toList()); + } + + // TODO: + public RecordingOutput parse(ParseData parseData) { + rwAccessList = new ReadWriteAccessList().parse(parseData.getField("rwAccessList")); + threadIds = parseThreadIds(parseData.getField("threadIds")); + this.memoryUsed = parseData.getField("memoryUsed").getLongValue(); + this.lockAcquisitionMap = parseData.getField("lockAcquisitionMap") + .toMap(new Function() { + @Override + public ObjectId apply(ParseData data) { + ObjectId objectId = new ObjectId(false); + objectId.parse(data); + return objectId; + } + }, new Function>() { + @Override + public List apply(ParseData data) { + return data.toList(new Function() { + @Override + public Event apply(ParseData parseData) { + Event result = Event.parseEvent(parseData); + return result; + } + }); + } + }); + + this.sharedMemoryLocations = + parseData.getField("sharedMemoryLocations").toList(new Function() { + + @Override + public SharedMemoryLocation apply(ParseData t) { + return new SharedMemoryLocation().parse(t); + } + + }); + + for (SharedMemoryLocation sml : this.sharedMemoryLocations) { + sml.generateWRMap(); + } + return this; + } + + @Override + public int hashCode() { + return Objects.hash(this.lockAcquisitionMap, rwAccessList, sharedMemoryLocations, threadIds); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RecordingOutput other = (RecordingOutput) obj; + return Objects.equals(this.lockAcquisitionMap, other.lockAcquisitionMap) + && Objects.equals(rwAccessList, other.rwAccessList) + && Objects.equals(sharedMemoryLocations, other.sharedMemoryLocations) + && Objects.equals(threadIds, other.threadIds); + } + + @Override + public Set getArrayRefs() { + return this.sharedMemoryLocations + .stream() + .filter((v) -> v.getLocation() instanceof ArrayIndexMemLocation).collect(Collectors.toSet()); + } + + @Override + public Set getStaticFields() { + Set locations = new HashSet<>(); + for (SharedMemoryLocation sml: this.sharedMemoryLocations) { + if (sml.getLocation() instanceof StaticFieldLocation) { + locations.add(sml); + } + } + return locations; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedDataParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedDataParser.java new file mode 100644 index 000000000..e9ddc3b48 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedDataParser.java @@ -0,0 +1,148 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.storage.Storable; +import microbat.instrumentation.model.storage.Storage; +import microbat.instrumentation.output.OutputReader; + +/** + * Performs parsing of data generated during instrumentation. + * Generic object parser + * @author Gabau + * + */ +public class SharedDataParser { + + + + private HashSet parseSet(String setString) { + HashSet result = new HashSet<>(); + // TODO: add the values in the string to the set. + String[] fieldNames = setString.split(Storage.STORE_DELIM_STRING); + for (String fieldName : fieldNames) { + result.add(fieldName); + } + return result; + } + + public RecorderObjectId parseRecordObject(ParseData data) { + ParseData threadId = data.innerDataMap.get("threadId"); + ThreadId actualThreadId = ThreadId.createThreadId(threadId); + int objectCounter = Integer.parseInt(data.innerDataMap.get("objectCounter").getValue()); + ObjectId objectId = new ObjectId(actualThreadId, objectCounter); + HashSet fieldNames = parseSet(data.innerDataMap.get("fieldAccessMap").getValue()); + RecorderObjectId result = new RecorderObjectId(objectId); + result.updateSharedFieldSet(fieldNames); + return result; + } + + public Map generateObjectIds(List data) { + HashMap resultObjectIdsHashMap = new HashMap<>(); + for (ParseData value : data) { + if (value.isClass(ObjectId.class)) { + RecorderObjectId roiObjectId = parseRecordObject(value); + resultObjectIdsHashMap.put(roiObjectId.getObjectId(), roiObjectId); + } + } + return resultObjectIdsHashMap; + } + + public List parse(Reader data) throws IOException { + LinkedList result = new LinkedList<>(); + int k = data.read(); + while (k != -1) { + while (k != Storage.START_OBJECT_STRING.charAt(0)) { + if (k == -1) break; + k = data.read(); + } + if (k == -1) break; + result.add(parseInner(data)); + k = data.read(); + } + return result; + } + + + private ParseData parseList(Reader reader) throws IOException { + // grab the contents of the list + int c = reader.read(); + List data = new LinkedList<>(); + ParseData result = new ParseData(); + while (c != Storage.LIST_END) { + if (c == Storage.START_OBJECT_STRING.charAt(0)) { + data.add(parseInner(reader)); + } else if (c == Storage.LIST_SEP) { + c = reader.read(); + continue; + } else if (c == Storage.LIST_START) { + ParseData listData = parseList(reader); + data.add(listData); + } else { + StringBuilder sb = new StringBuilder(); + while (c != Storage.LIST_SEP && c != Storage.LIST_END) { + sb.append((char) c); + c = reader.read(); + } + ParseData toAdd = new ParseData(); + toAdd.actualData = sb.toString(); + data.add(toAdd); + continue; + } + c = reader.read(); + } + result.listData = data; + return result; + } + + private ParseData parseInner(Reader reader) throws IOException { + ParseData parseData = new ParseData(); + StringBuilder sb = new StringBuilder(); + while (true) { + char c = (char) reader.read(); + if (c == Storage.CLOSE_OBJECT_STRING.charAt(0)) { + break; + } + if (c == Storage.OBJECT_SEPARATOR.charAt(0) + || c == '\n' || c == '\r') { + continue; + } + if (c == Storage.STORE_DELIM_STRING.charAt(0)) { + String field = sb.toString().trim(); + char k = (char) reader.read(); + ParseData value = new ParseData(); + if (k == Storage.LIST_START) { + value = parseList(reader); + } else if (k == Storage.START_OBJECT_STRING.charAt(0)) { + value = parseInner(reader); + } else { + StringBuilder innerValue = new StringBuilder(); + while (k != Storage.OBJECT_SEPARATOR.charAt(0)) { + innerValue.append(k); + k = (char) reader.read(); + } + value.actualData = innerValue.toString(); + } + parseData.innerDataMap.put(field, value); + sb.setLength(0); + continue; + } + sb.append(c); + } + return parseData; + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedObjectAccessInstrumentator.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedObjectAccessInstrumentator.java new file mode 100644 index 000000000..70b404276 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/SharedObjectAccessInstrumentator.java @@ -0,0 +1,56 @@ +package microbat.instrumentation.instr.aggreplay.shared; + +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.instr.aggreplay.ObjectAccessInstrumentator; +import microbat.instrumentation.instr.aggreplay.agents.SharedVariableAgent; + +public class SharedObjectAccessInstrumentator extends ObjectAccessInstrumentator { + + public SharedObjectAccessInstrumentator(AgentParams agentParams) { + super(SharedObjectAccessInstrumentator.class, agentParams); + } + + public static void _onNewObject(Object object) { + SharedVariableAgent._onObjectCreation(object); + } + + public static void _onObjectWrite(Object object, String field) { + SharedVariableAgent._onObjectAccess(object, field); + } + + public static void _onObjectRead(Object object, String field) { + SharedVariableAgent._onObjectAccess(object, field); + } + + public static void _onStaticRead(String className, String fieldName) { + SharedVariableAgent._onStaticAccess(className, fieldName); + } + + public static void _onStaticWrite(String className, String fieldName) { + SharedVariableAgent._onStaticAccess(className, fieldName); + } + + public static void _onArrayRead(Object arrayRef, int index) { + SharedVariableAgent._onArrayAccess(arrayRef, index); + } + + public static void _assertObjectExists(Object object) { + SharedVariableAgent._assertObjectExists(object); + } + + public static void _assertArrayExists(Object object) { + SharedVariableAgent._assertArrayExists(object); + } + + public static void _onArrayWrite(Object arrayRef, int index) { + SharedVariableAgent._onArrayAccess(arrayRef, index); + } + + public static void _onNewArray(Object arrayRef) { + SharedVariableAgent._onNewArray(arrayRef); + } + + public static void _onLockAcquire(Object object) { + SharedVariableAgent._onLockAcquire(object); + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/MemoryLocationParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/MemoryLocationParser.java new file mode 100644 index 000000000..d334d0442 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/MemoryLocationParser.java @@ -0,0 +1,39 @@ +package microbat.instrumentation.instr.aggreplay.shared.parser; + +import microbat.instrumentation.instr.aggreplay.shared.ObjectIdParser; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.generator.ArrayIndexMemLocation; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectFieldMemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.StaticFieldLocation; + +public class MemoryLocationParser implements Parser { + private static class ObjectFieldMemLocationParser implements Parser { + @Override + public ObjectFieldMemoryLocation parse(ParseData data) { + return new ObjectFieldMemoryLocation(data.getField("fieldName").getValue(), + new ObjectIdParser().parse(data.getField("objectId"))); + } + + } + + @Override + public MemoryLocation parse(ParseData data) { + if (data.isClass(ObjectFieldMemoryLocation.class)) { + ObjectFieldMemLocationParser ofmParser = new ObjectFieldMemLocationParser(); + return ofmParser.parse(data); + } + if (data.isClass(ArrayIndexMemLocation.class)) { + return new ArrayIndexMemLocation(data); + } + + if (data.isClass(StaticFieldLocation.class)) { + return new StaticFieldLocation(data); + } + return null; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/RCVectorParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/RCVectorParser.java new file mode 100644 index 000000000..127ba5ea2 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/RCVectorParser.java @@ -0,0 +1,28 @@ +package microbat.instrumentation.instr.aggreplay.shared.parser; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import sav.common.core.Pair; + +/** + * Parser for parsing a map from long to int + * @author Gabau + * + */ +public class RCVectorParser implements Parser> { + + public Map parse(ParseData data) { + List> objectMap = data.toPairList(); + Map values = new HashMap<>(); + for (Pair innerData : objectMap) { + Long keyLong = innerData.first().getLongValue(); + Integer keyInteger = innerData.second().getIntValue(); + values.put(keyLong, keyInteger); + } + return values; + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/ReadWriteAccessListParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/ReadWriteAccessListParser.java new file mode 100644 index 000000000..563480a4d --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/aggreplay/shared/parser/ReadWriteAccessListParser.java @@ -0,0 +1,15 @@ +package microbat.instrumentation.instr.aggreplay.shared.parser; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.model.id.ReadWriteAccessList; + +public class ReadWriteAccessListParser implements Parser { + + @Override + public ReadWriteAccessList parse(ParseData data) { + ReadWriteAccessList accessList = new ReadWriteAccessList(); + return null; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/instr/instruction/info/LineInstructionInfo.java b/microbat_instrumentator/src/main/microbat/instrumentation/instr/instruction/info/LineInstructionInfo.java index 854748712..da93ee45f 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/instr/instruction/info/LineInstructionInfo.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/instr/instruction/info/LineInstructionInfo.java @@ -3,8 +3,10 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.bcel.Const; import org.apache.bcel.classfile.LineNumberTable; @@ -43,6 +45,10 @@ public class LineInstructionInfo { protected List rwInsructionInfo; protected List invokeInsns; protected List returnInsns; + protected List newInsns; + protected List newArrayInsns; + protected List monitorEnterInsnsHandles; + protected List monitorExitInsnsHandles; private List exitInsns; private boolean hasExceptionTarget; @@ -50,6 +56,21 @@ public LineInstructionInfo() { // } + private void initialiseMonitorInstructions(List lineInsns) { + this.monitorEnterInsnsHandles = new LinkedList<>(); + this.monitorExitInsnsHandles = new LinkedList<>(); + for (InstructionHandle handle : lineInsns) { + if (handle.getInstruction().getOpcode() == Const.MONITORENTER) { + monitorEnterInsnsHandles.add(handle); + } + if (handle.getInstruction().getOpcode() == Const.MONITOREXIT) { + monitorExitInsnsHandles.add(handle); + } + } + + + } + public LineInstructionInfo(String locId, ConstantPoolGen constPool, Method method, MethodGen methodGen, Set exceptionTargets, LineNumberGen lineGen, CFG cfg, boolean isAppClass) { this.line = lineGen.getSourceLine(); @@ -62,7 +83,10 @@ public LineInstructionInfo(String locId, ConstantPoolGen constPool, Method metho rwInsructionInfo = extractRWInstructions(locId, isAppClass); invokeInsns = extractInvokeInstructions(lineInsns); returnInsns = extractReturnInstructions(lineInsns); + newInsns = extractNewInstructions(lineInsns); exitInsns = extractExitInsns(cfg, lineInsns); + newArrayInsns = extractNewArrayInstruction(lineInsns); + initialiseMonitorInstructions(lineInsns); for (InstructionHandle insn : lineInsns) { if (exceptionTargets.remove(insn)) { hasExceptionTarget = true; @@ -70,6 +94,19 @@ public LineInstructionInfo(String locId, ConstantPoolGen constPool, Method metho } } + private boolean isNewArrayInsn(InstructionHandle insnHandle) { + int opcode = insnHandle.getInstruction().getOpcode(); + return opcode == Const.ANEWARRAY + || opcode == Const.MULTIANEWARRAY + || opcode == Const.NEWARRAY; + } + + private List extractNewArrayInstruction(List insns) { + return insns.stream() + .filter(v -> isNewArrayInsn(v)) + .collect(Collectors.toList()); + } + public List getInstructionsOnLine(){ return lineInsns; } @@ -86,6 +123,14 @@ private List extractExitInsns(CFG cfg, List getMonitorEnterInstructionHandles() { + return this.monitorEnterInsnsHandles; + } + + public List getMonitorExitInstructionHandles() { + return this.monitorExitInsnsHandles; + } public List getRWInstructions() { return rwInsructionInfo; @@ -209,6 +254,14 @@ public List getInvokeInstructions() { return invokeInsns; } + public List getNewInstructions() { + return newInsns; + } + + public List getNewArrayInstructions() { + return newArrayInsns; + } + public static List findCorrespondingInstructions(InstructionList list, LineNumberTable lineTable, int lineNumber) { List correspondingInstructions = new ArrayList<>(); @@ -224,7 +277,7 @@ public static List findCorrespondingInstructions(InstructionL } protected static List extractInvokeInstructions(List insns) { - List invokeInsns = new ArrayList<>(3); + List invokeInsns = new ArrayList<>(); for (InstructionHandle insnHandler : insns) { Instruction insn = insnHandler.getInstruction(); if (insn instanceof InvokeInstruction) { @@ -238,6 +291,17 @@ protected static List extractInvokeInstructions(List extractNewInstructions(List lineInsns) { + List newInsns = new LinkedList<>(); + for (InstructionHandle insnHandle : lineInsns) { + Instruction insn = insnHandle.getInstruction(); + if (insn.getOpcode() == Const.NEW) { + newInsns.add(insnHandle); + } + } + return newInsns; + } + protected List extractReturnInstructions(List lineInsns) { List returnInsns = new ArrayList<>(1); for (InstructionHandle insnHandler : lineInsns) { diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/Observable.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/Observable.java new file mode 100644 index 000000000..6d50c717f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/Observable.java @@ -0,0 +1,11 @@ +package microbat.instrumentation.model; + +/** + * + * @author Gabau + * + * @param The object containing the change information + */ +public interface Observable { + public void onChange(T data); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/Observer.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/Observer.java new file mode 100644 index 000000000..cfb0a94f6 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/Observer.java @@ -0,0 +1,5 @@ +package microbat.instrumentation.model; + +public interface Observer { + public void onChange(Data data); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/ReadWriteAccessListReplay.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/ReadWriteAccessListReplay.java new file mode 100644 index 000000000..1aac93ebe --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/ReadWriteAccessListReplay.java @@ -0,0 +1,128 @@ +package microbat.instrumentation.model; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ReadCountVector; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.ReadWriteAccessList.Node; +import microbat.instrumentation.model.id.SharedMemoryLocation; + +/** + * Wrapper class used for generating the RC vector + * for each thread accounting for the reduced recording + * due to optimisations + * + * Represents EX_t in aggreplay. + * @author Gabau + * + */ +public class ReadWriteAccessListReplay { + + // map from recorded threadId to RC vector for thread id + // with the current state + private Map>> + generatedMap = new HashMap<>(); + private Map> changeStack = new HashMap<>(); + public ReadWriteAccessListReplay(ReadWriteAccessList rwal) { + Map> values = rwal.getList(); + for (Map.Entry> value: values.entrySet()) { + changeStack.put(value.getKey(), fromList(value.getValue())); + } + } + /** + * Returns true if the RC vector for each memory location is greater or equal + * to the reference + * @param threadMap The current rc vector map + * @param threadId The thread id in which this access list was built on + * @param threadIdMap The thread id map from current thread id to previous + * @return + */ + public synchronized boolean checkRead(ReadCountVector currentVector, + long threadId) { + + Map> threadMap = currentVector.getRCVector(threadId); + for (Map.Entry> entry : threadMap.entrySet()) { + Map currentRc = entry.getValue(); + Map referenceMap = generatedMap.getOrDefault(threadId, + Collections.>emptyMap()) + .getOrDefault(entry.getKey(), new HashMap()); + if (!compareMaps(currentRc, referenceMap)) { + return false; + } + } + pop(threadId); + return true; + } + + + /** + * Checks that none of the current RC vectors is less than the reference RC vector + * + * @param reference The reference RC vector + * @param current The current RC vector + * @param threadIdMap The mapping from current thread id's to previous id's + * @return + */ + private boolean compareMaps(Map current, + Map reference) { + // all the threads in reference must be in current, + // the vice versa may not be true. + if (!current.keySet().containsAll(reference.keySet())) { + return false; + } + for (Map.Entry entry : current.entrySet()) { + if (entry.getValue() < reference.getOrDefault(entry.getKey(), 0)) { + return false; + } + } + return true; + } + + private Stack fromList(LinkedList values) { + final Stack result = new Stack<>(); + values.descendingIterator().forEachRemaining(new Consumer() { + @Override + public void accept(Node val) { + result.push(val); + } + }); + return result; + } + + private void assertThreadId(long threadId) { + if (!generatedMap.containsKey(threadId)) { + synchronized (generatedMap) { + if (!generatedMap.containsKey(threadId)) generatedMap.put(threadId, + new HashMap>()); + } + } + } + + /** + * Obtain the top + * + * @param threadId The recorded thread id. + * @return + */ + public int top(long threadId, long index, SharedMemoryLocation shm) { + assertThreadId(threadId); + return generatedMap.get(threadId).get(shm.getLocation()).get(index); + } + + public void pop(long threadId) { + assertThreadId(threadId); + Node change = changeStack.get(threadId).peek(); + changeStack.get(threadId).pop(); + generatedMap.get(threadId).put(change.getLocation(), change.getRcMap()); + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/RecorderObjectId.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/RecorderObjectId.java new file mode 100644 index 000000000..438c953b0 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/RecorderObjectId.java @@ -0,0 +1,115 @@ +package microbat.instrumentation.model; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import microbat.instrumentation.instr.aggreplay.ThreadIdInstrumenter; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectFieldMemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.storage.Storable; + +/** + * Class used for keeping track of the shared memory locations during record and replay. + * Serves a different purpose compared to ObjectId. + * @author Gabau + * + */ +public class RecorderObjectId extends Storable { + + // the fields of this object that are shared. + private final ObjectId objectId; + // the memory locations of the shared fields. + private ConcurrentHashMap fieldMemoryLocations = new ConcurrentHashMap<>(); + private LinkedList lockAcquisitionList = new LinkedList<>(); + public RecorderObjectId(ObjectId objectId) { + this.objectId = objectId; + } + + public void setField(String field, SharedMemoryLocation sml) { + this.fieldMemoryLocations.put(field, sml); + } + + /** + * Get the set of shared memory locations that represents this object's fields. + * @return + */ + public Collection getFieldLocations() { + return fieldMemoryLocations.values(); + } + + public List getLockAcquisition() { + return this.lockAcquisitionList; + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + result.put("fieldMemoryLocations", fromObject(fieldMemoryLocations)); + result.put("lockAcquisitionList", fromObject(lockAcquisitionList)); + result.put("objectId", fromObject(objectId)); + return result; + } + + public void acquireLock(Event event) { + lockAcquisitionList.add(event); + } + + public void updateSharedFieldSet(Collection fieldSet) { + for (String fieldName : fieldSet) { + ObjectFieldMemoryLocation location = new ObjectFieldMemoryLocation(fieldName, + this.objectId); + fieldMemoryLocations.put(fieldName, new SharedMemoryLocation(location)); + } + } + + public Set getField() { + return fieldMemoryLocations.keySet(); + } + + public ObjectId getObjectId() { + return this.objectId; + } + + /** + * Gets the field from the object. + * Is null if the field is not shared exist. + * + * @param field + * @return + */ + public SharedMemoryLocation getField(String field) { + return fieldMemoryLocations.get(field); + } + + + @Override + public int hashCode() { + return Objects.hash(objectId); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RecorderObjectId other = (RecorderObjectId) obj; + return Objects.equals(objectId, other.objectId); + } + + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedMemGeneratorInitialiser.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedMemGeneratorInitialiser.java new file mode 100644 index 000000000..0d6df82dc --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedMemGeneratorInitialiser.java @@ -0,0 +1,28 @@ +package microbat.instrumentation.model; + +import java.util.Map; +import java.util.Set; + +import microbat.instrumentation.model.generator.SharedVariableArrayRef; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.StaticFieldLocation; + +/** + * Class represents object used to initialise + * the shared memory generator + * @author Gabau + * + */ +public interface SharedMemGeneratorInitialiser { + + /** + * Get the map from object id to recorder object ids + * @return + */ + public Map getObjects(); + + public Set getArrayRefs(); + + public Set getStaticFields(); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedVariableObjectId.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedVariableObjectId.java new file mode 100644 index 000000000..4483eb687 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/SharedVariableObjectId.java @@ -0,0 +1,122 @@ +package microbat.instrumentation.model; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.storage.Storable; +import microbat.instrumentation.model.storage.Storage; + +/** + * Represents an object during the shared variable detection + * stage + * @author Gabau + * + */ +public class SharedVariableObjectId extends Storable implements Parser { + private ObjectId objectId; + private Map> fieldAccessMap = new HashMap<>(); + private List fieldAccessList = new LinkedList<>(); + + public SharedVariableObjectId(ObjectId objectId) { + this.objectId = objectId; + } + + public SharedVariableObjectId() { + + } + + public List getFieldAccessList() { + return this.fieldAccessList; + } + + public ObjectId getObjectId() { + return this.objectId; + } + + /** + * The stored data only includes the field access list, no need for the map. + */ + @Override + public SharedVariableObjectId parse(ParseData data) { + this.objectId = new ObjectId(false); + this.objectId = objectId.parse(data.getField("objectId")); + this.fieldAccessList = data.getField("fieldAccessList").toList(new Function() { + @Override + public String apply(ParseData data) { + return data.getValue(); + } + }); + return this; + } + + + private void assertHashSet(String field) { + if (!fieldAccessMap.containsKey(field)) { + synchronized (fieldAccessMap) { + if (!fieldAccessMap.containsKey(field)) { + fieldAccessMap.put(field, new HashSet()); + } + } + } + } + + public void addAccess(long threadId, String field) { + assertHashSet(field); + Set hSet = fieldAccessMap.get(field); + // no need to address this field again + if (hSet.size() == 2) return; + synchronized (hSet) { + hSet.add(threadId); + // only do this on the second access + if (hSet.size() == 2) { + synchronized(fieldAccessList) { + fieldAccessList.add(field); + } + } + } + } + + public List getMultiThreadFields() { + return fieldAccessList; + } + + + @Override + protected Map store() { + Map fieldMap = new HashMap<>(); + fieldMap.put("fieldAccessList", Storable.fromList(getMultiThreadFields())); + fieldMap.put("objectId", fromObject(objectId)); + return fieldMap; + } + + @Override + public int hashCode() { + return Objects.hash(objectId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SharedVariableObjectId other = (SharedVariableObjectId) obj; + return Objects.equals(fieldAccessList, other.fieldAccessList) && Objects.equals(objectId, other.objectId); + } + + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ArrayIndexMemLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ArrayIndexMemLocation.java new file mode 100644 index 000000000..9c143b217 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ArrayIndexMemLocation.java @@ -0,0 +1,54 @@ +package microbat.instrumentation.model.generator; + +import java.util.Objects; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectId; + +public class ArrayIndexMemLocation extends MemoryLocation { + + public ObjectId objectId; + public int index; + + public ArrayIndexMemLocation(ObjectId objectId, int index) { + this.objectId = objectId; + this.index = index; + } + + public int getIndex() { + return index; + } + + + + @Override + public ObjectId getObjectId() { + return this.objectId; + } + + public ArrayIndexMemLocation(ParseData parseData) { + this.objectId = new ObjectId(false).parse(parseData.getField("objectId")); + this.index = parseData.getField("index").getIntValue(); + } + + @Override + public int hashCode() { + return Objects.hash(index, objectId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ArrayIndexMemLocation other = (ArrayIndexMemLocation) obj; + return index == other.index && Objects.equals(objectId, other.objectId); + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/IdGenerator.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/IdGenerator.java new file mode 100644 index 000000000..0ac5c8cc9 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/IdGenerator.java @@ -0,0 +1,13 @@ +package microbat.instrumentation.model.generator; + +/** + * + * @author Gabau + * + * @param The type of the object to create id for. + * @param The type of id created. + */ +public interface IdGenerator { + public ID createId(T object); + public ID getId(T object); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdGenerator.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdGenerator.java new file mode 100644 index 000000000..0a88855b5 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdGenerator.java @@ -0,0 +1,52 @@ +package microbat.instrumentation.model.generator; + +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.storage.Storable; + +public class ObjectIdGenerator implements IdGenerator { + private ConcurrentHashMap objectIdMap = new ConcurrentHashMap<>(); + // indicates whether this generator needs to store the map + private final boolean storeMap; + + public ObjectIdGenerator() { + this.storeMap = true; + } + + /** + * Indicates whether this object id generator should use the object map + * @param storeMap + */ + public ObjectIdGenerator(boolean storeMap) { + this.storeMap = storeMap; + } + + public Collection getObjects() { + return objectIdMap.values(); + } + + @Override + public ObjectId createId(Object object) { + int hashCode = System.identityHashCode(object); + if (storeMap && objectIdMap.containsKey(hashCode)) { + return objectIdMap.get(object); + } + + ObjectId objectId = new ObjectId(); + if (storeMap) objectIdMap.put(hashCode,objectId); + return objectId; + } + + /** + * Only works if + */ + @Override + public ObjectId getId(Object object) { + int hashCode = System.identityHashCode(object); + return objectIdMap.get(hashCode); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdParser.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdParser.java new file mode 100644 index 000000000..7b62086fc --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ObjectIdParser.java @@ -0,0 +1,17 @@ +package microbat.instrumentation.model.generator; + +import microbat.instrumentation.model.RecorderObjectId; + +public class ObjectIdParser implements IdGenerator{ + + @Override + public RecorderObjectId createId(String object) { + return null; + } + + @Override + public RecorderObjectId getId(String object) { + return createId(object); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedMemoryGenerator.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedMemoryGenerator.java new file mode 100644 index 000000000..ac1d8322f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedMemoryGenerator.java @@ -0,0 +1,176 @@ +package microbat.instrumentation.model.generator; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.NotImplementedException; + +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.SharedMemGeneratorInitialiser; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.StaticFieldLocation; + +/** + * Used to generate shared memory id's + * Object fields, Object + * + * @author Gabau + * + */ +public class SharedMemoryGenerator { + + /** + * TODO: + * ObjectId generator currently stores data on the + * object id's lock acquisition, + * while object id recorder map stores data on + * the field's shared memory locations. Would + * be better to have the two data in one object, + * but it isn't straightforward to implement. + * + */ + private ObjectIdGenerator objectIdGenerator = new ObjectIdGenerator(); + private Map arrayObjectIdMap = new HashMap<>(); + + private Map objectIdRecorderMap = new HashMap<>(); + private Map> arrayMemLocationsMap = new HashMap<>(); + private Map staticMemLocationsMap = new HashMap<>(); + + + public Set getSharedObjects() { + return objectIdRecorderMap.keySet(); + } + + + public List getAllLocations() { + List result = new LinkedList<>(); + for (RecorderObjectId recObjectId : objectIdRecorderMap.values()) { + result.addAll(recObjectId.getFieldLocations()); + } + + result.addAll(staticMemLocationsMap.values()); + for (Map location : arrayMemLocationsMap.values()) { + result.addAll(location.values()); + } + return result; + } + + public ObjectId ofObjectOrArray(Object object) { + ObjectId result = objectIdGenerator.getId(object); + if (result == null) return arrayObjectIdMap.get(System.identityHashCode(object)); + return result; + + } + + public SharedMemoryGenerator(ObjectIdGenerator objIdGenerator) { + this.objectIdGenerator = objIdGenerator; + } + + public void init(SharedMemGeneratorInitialiser sharedVar) { + setObjectIdRecorderMap(sharedVar.getObjects()); + setStaticFields(sharedVar.getStaticFields()); + setArrayIndexes(sharedVar); + } + + private void setArrayIndexes(SharedMemGeneratorInitialiser sharedMGI) { + Set res = sharedMGI.getArrayRefs(); + arrayMemLocationsMap = new HashMap<>(); + res.forEach(field -> { + Map shmMap = null; + ArrayIndexMemLocation iml = (ArrayIndexMemLocation) field.getLocation(); + if (!arrayMemLocationsMap.containsKey(iml.getObjectId())) { + arrayMemLocationsMap.put(iml.getObjectId(), new HashMap()); + } + shmMap = arrayMemLocationsMap.get(iml.getObjectId()); + shmMap.put(iml.getIndex(), field); + }); + } + + + private void setStaticFields(Set sfl) { + sfl.forEach(field -> this.staticMemLocationsMap.put((StaticFieldLocation) field.getLocation(), field)); + } + + private void setObjectIdRecorderMap(Map map) { + this.objectIdRecorderMap = map; + } + + public RecorderObjectId ofObject(Object object) { + ObjectId objectId = objectIdGenerator.getId(object); + RecorderObjectId result = null; + synchronized (this.objectIdRecorderMap) { + if (!this.objectIdRecorderMap.containsKey(objectId)) { + result = new RecorderObjectId(objectId); + this.objectIdRecorderMap.put(objectId, result); + } else { + result = this.objectIdRecorderMap.get(objectId); + } + } + return result; + } + + public boolean isSharedObject(Object object, String field) { + if (object == null) return false; + return ofField(object, field) != null; + } + + public boolean isSharedObject(ObjectId object) { + return this.objectIdRecorderMap.containsKey(object); + } + + public SharedMemoryLocation ofField(Object object, String fieldName) { + ObjectId objectId = objectIdGenerator.getId(object); + RecorderObjectId value = objectIdRecorderMap.get(objectId); + if (value == null) return null; + return value.getField(fieldName); + } + + public Map> getLockAcquisitionMap() { + Map> resultList = new HashMap<>(); + for (RecorderObjectId objectId : this.objectIdRecorderMap.values()) { + resultList.put(objectId.getObjectId(), objectId.getLockAcquisition()); + } + return resultList; + } + + public SharedMemoryLocation ofStaticField(String className, String fieldName) { + return staticMemLocationsMap.get(new StaticFieldLocation(className, fieldName)); + } + + public boolean isSharedStaticField(String className, String fieldName) { + return staticMemLocationsMap.containsKey(new StaticFieldLocation(className, fieldName)); + } + + public boolean isSharedArray(Object array, int access) { + return ofArray(array, access) != null; + } + + public void assertArray(Object obj) { + if (this.arrayObjectIdMap.containsKey(System.identityHashCode(obj))) { + return; + } + newArray(obj); + } + + public void newArray(Object obj) { + this.arrayObjectIdMap.put(System.identityHashCode(obj), new ObjectId()); + } + + public SharedMemoryLocation ofArray(Object array, int index) { + if (!arrayObjectIdMap.containsKey(System.identityHashCode(array))) { + return null; + } + ObjectId oId = arrayObjectIdMap.get(System.identityHashCode(array)); + if (!arrayMemLocationsMap.containsKey(oId)) { + return null; + } + return arrayMemLocationsMap.get(oId).get(index); + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableArrayRef.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableArrayRef.java new file mode 100644 index 000000000..9571067ca --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableArrayRef.java @@ -0,0 +1,86 @@ +package microbat.instrumentation.model.generator; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.storage.Storable; +import sav.common.core.Pair; + +public class SharedVariableArrayRef extends Storable implements Parser { + public ObjectId objectId; + public HashMap> indexAccessMap = new HashMap<>(); + private HashSet sharedIndexSet = new HashSet<>(); + protected Set sharedIndexes() { + if (sharedIndexSet.size() > 0) { + return sharedIndexSet; + } + return indexAccessMap.entrySet().stream().filter(new Predicate>>() { + @Override + public boolean test(Entry> v) { + return v.getValue().size() > 1; + } + }).map(v -> v.getKey()).collect(Collectors.toSet()); + } + + public ObjectId getObjectId() { + return this.objectId; + } + + public Set getSharedMemLocations() { + return this.sharedIndexSet + .stream().map(v -> new ArrayIndexMemLocation(objectId, v)).collect(Collectors.toSet()); + } + + public HashSet getSharedIndexes() { + return sharedIndexSet; + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + result.put("objectId", fromObject(objectId)); + result.put("indexAccessSet", fromObject(sharedIndexes())); + return result; + } + + public SharedVariableArrayRef(ObjectId objectId) { + this.objectId = objectId; + } + + public boolean isShared() { + for (Map.Entry> entry : indexAccessMap.entrySet()) { + if (entry.getValue().size() > 1) { + return true; + } + } + return false; + } + + public synchronized void addAccess(int index, long threadId) { + HashSet indexAccess = this.indexAccessMap.getOrDefault(index, new HashSet()); + indexAccess.add(threadId); + this.indexAccessMap.put(index, indexAccess); + } + + @Override + public SharedVariableArrayRef parse(ParseData data) { + this.objectId = new ObjectId(false); + objectId.parse(data.getField("objectId")); + data.getField("indexAccessSet").toList().forEach(new Consumer() { + @Override + public void accept(ParseData v) { + sharedIndexSet.add(v.getIntValue()); + } + }); + return this; + } +} \ No newline at end of file diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableObjectGenerator.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableObjectGenerator.java new file mode 100644 index 000000000..743a2f082 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/SharedVariableObjectGenerator.java @@ -0,0 +1,112 @@ +package microbat.instrumentation.model.generator; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import microbat.instrumentation.model.SharedVariableObjectId; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.StaticFieldLocation; +import sav.common.core.Pair; + +public class SharedVariableObjectGenerator implements IdGenerator { + // the object id generator does not need to store the map + private ObjectIdGenerator objectIdGenerator = new ObjectIdGenerator(false); + private ObjectIdGenerator arrayIdGenerator = new ObjectIdGenerator(false); + private ConcurrentHashMap sharedVariableMap = new ConcurrentHashMap<>(); + private ConcurrentHashMap arrayRefMap = new ConcurrentHashMap<>(); + private ConcurrentHashMap> staticFieldMap = new ConcurrentHashMap<>(); + + public void assertId(Object object) { + if (sharedVariableMap.containsKey(System.identityHashCode(object))) return; + createId(object); + } + + public void assertArrayId(Object object) { + if (arrayRefMap.containsKey(System.identityHashCode(object))) return; + createArrayId(object); + } + + @Override + public SharedVariableObjectId createId(Object object) { + Integer currentId = System.identityHashCode(object); + ObjectId objectId = objectIdGenerator.createId(object); + SharedVariableObjectId result = new SharedVariableObjectId(objectId); + sharedVariableMap.put(currentId, result); + return result; + } + + public Set getSharedArrays() { + return arrayRefMap.values().stream() + .filter(new Predicate() { + @Override + public boolean test(SharedVariableArrayRef v) { + return v.isShared(); + } + }) + .collect(Collectors.toSet()); + } + + public Set getSharedStaticFields() { + return staticFieldMap.entrySet().stream() + .filter(new Predicate>>() { + @Override + public boolean test(Entry> entry) { + return entry.getValue().size() > 1; + } + }) + .map(new Function>, StaticFieldLocation>() { + @Override + public StaticFieldLocation apply(Entry> v) { + return v.getKey(); + } + }) + .collect(Collectors.toSet()); + } + + public void createArrayId(Object arrayId) { + ObjectId arrayObjectId = arrayIdGenerator.createId(arrayId); + arrayRefMap.put(System.identityHashCode(arrayId), + new SharedVariableArrayRef(arrayObjectId)); + } + + public SharedVariableArrayRef getArrayId(Object arrayId) { + return arrayRefMap.get(System.identityHashCode(arrayId)); + } + + public void addAccessStaticField(StaticFieldLocation sfl, long threadId) { + if (!this.staticFieldMap.containsKey(sfl)) { + synchronized (staticFieldMap) { + if (!this.staticFieldMap.containsKey(sfl)) staticFieldMap.put(sfl, new HashSet<>()); + } + } + HashSet values = staticFieldMap.get(sfl); + synchronized (values) { + values.add(threadId); + } + } + + public Collection getSharedVariables() { + return sharedVariableMap.values().stream().filter(new Predicate() { + @Override + public boolean test(SharedVariableObjectId value) { + return value.getMultiThreadFields().size() > 0; + } + }).collect(Collectors.toList()); + } + + @Override + public SharedVariableObjectId getId(Object object) { + Integer currentId = System.identityHashCode(object); + return sharedVariableMap.get(currentId); + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ThreadIdGenerator.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ThreadIdGenerator.java new file mode 100644 index 000000000..a8cd5831f --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/generator/ThreadIdGenerator.java @@ -0,0 +1,95 @@ +package microbat.instrumentation.model.generator; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.storage.Storable; + +public class ThreadIdGenerator extends Storable implements IdGenerator { + private ConcurrentHashMap idMap = new ConcurrentHashMap<>(); + private ThreadId rootId = null; + public static final ThreadIdGenerator threadGenerator = new ThreadIdGenerator(); + + public ThreadId getRoot() { + if (rootId == null) { + createRootId(); + } + return rootId; + } + + public ThreadIdGenerator() { + + } + + public List getThreadIds() { + return idMap.values().stream().collect(Collectors.toList()); + } + + private synchronized void createRootId() { + if (this.rootId != null) return; + this.rootId = new ThreadId(Thread.currentThread().getId()); + idMap.put(Thread.currentThread().getId(), rootId); + } + + public void createId(Thread thread, int spawnOrder) { + ThreadId id = createId(thread); + id.setSpawnOrder(spawnOrder); + } + + @Override + public ThreadId createId(Thread thread) { + if (idMap.containsKey(thread.getId())) { + return idMap.get(thread.getId()); + } + // Only create root when creating child thread. + ThreadId currentId = idMap.get(Thread.currentThread().getId()); + if (currentId == null && rootId == null) { + createRootId(); + currentId = rootId; + } + if (currentId == null) { + // when the current thread is outside of the application context + // handle via rootId -> TODO(Gab): return null instead, no reason to keep track + // of the shutdown thread. + currentId = rootId; + } + + ThreadId valueId = currentId.createChildWithThread(thread.getId()); + idMap.put(thread.getId(), valueId); + return valueId; + } + + @Override + public ThreadId getId(Thread object) { + return getId(object.getId()); + } + + public ThreadId getId(long threadId) { + if (idMap.get(threadId) == null) { + if (rootId == null) { + createRootId(); + return rootId; + } + // Not supposed to happen + // Happen's when Thread.start() isn't used to initialise the thread. + return null; + } + return idMap.get(threadId); + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + for (Map.Entry entry : this.idMap.entrySet()) { + result.put(entry.getKey().toString(), entry.getValue().getFromStore()); + } + return result; + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/AggrePlayMethods.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/AggrePlayMethods.java new file mode 100644 index 000000000..01a89a473 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/AggrePlayMethods.java @@ -0,0 +1,54 @@ +package microbat.instrumentation.model.id; + +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.INVOKESTATIC; + +/** + * Events that are used in this instrumentation + * @author Gabau + * + */ +public enum AggrePlayMethods { + ACQUIRE_LOCK("_acquireLock", "()V"), + ON_LOCK_ACQUIRE("_onLockAcquire", "(Ljava/lang/Object;)V"), + ON_LOCK_ACQUIRE2("_onLockAcquire2", "()V"), + RELEASE_LOCK("_releaseLock", "()V"), + START("_start", "()V"), + /** + * After NEW instruction, before object initialisation + */ + ON_NEW_OBJECT("_onNewObject", "(Ljava/lang/Object;)V"), + BEFORE_OBJECT_READ("_onObjectRead", "(Ljava/lang/Object;Ljava/lang/String;)V"), + AFTER_OBJECT_READ("_afterObjectRead", "()V"), + AFTER_OBJECT_WRITE("_afterObjectWrite", "()V"), + BEFORE_OBJECT_WRITE("_onObjectWrite", "(Ljava/lang/Object;Ljava/lang/String;)V"), + BEFORE_ARRAY_WRITE("_onArrayWrite", "(Ljava/lang/Object;I)V"), + BEFORE_ARRAY_READ("_onArrayRead", "(Ljava/lang/Object;I)V"), + ON_NEW_ARRAY("_onNewArray", "(Ljava/lang/Object;)V"), + AFTER_LOCK_ACQUIRE("_afterLockAcquire", "()V"), + BEFORE_STATIC_READ("_onStaticRead", "(Ljava/lang/String;Ljava/lang/String;)V"), + BEFORE_STATIC_WRITE("_onStaticWrite", "(Ljava/lang/String;Ljava/lang/String;)V"), + ASSERT_OBJECT_EXISTS("_assertObjectExists", "(Ljava/lang/Object;)V"), + ASSERT_ARRAY_EXISTS("_assertArrayExists", "(Ljava/lang/Object;)V"); + + public final String methodName; + public final String methodSig; + + private AggrePlayMethods(String methodName, String methodSig) { + this.methodName = methodName; + this.methodSig = methodSig; + } + + public static INVOKESTATIC createInvokeStatic(ConstantPoolGen cpg, Class clazz, AggrePlayMethods method) { + return createInvokeStatic(cpg, clazz, method.methodName, method.methodSig); + } + + public static INVOKESTATIC createInvokeStatic(ConstantPoolGen cpg, Class clazz, String methodName, String signature) { + return new INVOKESTATIC(cpg.addMethodref(clazz.getName().replace(".", "/"), methodName, signature)); + } + + + public INVOKESTATIC toInvokeStatic(ConstantPoolGen cpg, Class clazz) { + return createInvokeStatic(cpg, clazz, this); + } +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ArrayIndexLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ArrayIndexLocation.java new file mode 100644 index 000000000..fd00e5525 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ArrayIndexLocation.java @@ -0,0 +1,11 @@ +package microbat.instrumentation.model.id; + +public class ArrayIndexLocation extends MemoryLocation { + private Object arrayRef; + private final int index; + public ArrayIndexLocation(Object arrayRef, int index) { + this.arrayRef = arrayRef; + this.index = index; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/Event.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/Event.java new file mode 100644 index 000000000..e699a6e55 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/Event.java @@ -0,0 +1,113 @@ +package microbat.instrumentation.model.id; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.instr.aggreplay.shared.parser.MemoryLocationParser; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.storage.Storable; + +public class Event extends Storable implements Parser { + private static ThreadLocal eventCounterLocal = ThreadLocal.withInitial(new Supplier() { + @Override + public Integer get() { + return 0; + } + }); + private long threadId; + private int eventId; + // on write, the location written to + // on read, the location read from. + private SharedMemoryLocation relevantLocation; + + /** + * Needed to update thread Id which is obtained from + * serialized data. + * @param threadId + */ + public void updateThreadId(long threadId) { + this.threadId = threadId; + } + + public static Event getFirstEvent(SharedMemoryLocation sml) { + Event result = new Event(); + result.eventId = -1; + result.threadId = -1; + result.relevantLocation = sml; + return result; + } + + public Event(SharedMemoryLocation location) { + threadId = Thread.currentThread().getId(); + eventId = eventCounterLocal.get(); + eventCounterLocal.set(eventId + 1); + this.relevantLocation = location; + } + + public Event(SharedMemoryLocation location, final long threadId) { + this.threadId = threadId; + eventId = eventCounterLocal.get(); + eventCounterLocal.set(eventId + 1); + this.relevantLocation = location; + } + + public SharedMemoryLocation getLocation() { + return relevantLocation; + } + + public long getThreadId() { + return threadId; + } + + private Event() { + + } + + public static Event parseEvent(ParseData data) { + return new Event().parse(data); + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + result.put("threadId", fromObject(this.threadId)); + result.put("eventId", eventId + ""); + // removed -> won't be stored as can lead to recursive problems + // solution -> the role of populating the relevant location falls + // to the parent shared memory location object. + // result.put("relevantLocation", fromObject(this.relevantLocation)); + return result; + } + + @Override + public Event parse(ParseData data) { + this.threadId = data.getField("threadId").getLongValue(); + this.eventId = data.getField("eventId").getIntValue(); + return this; + } + + @Override + public int hashCode() { + return Objects.hash(eventId, threadId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Event other = (Event) obj; + return eventId == other.eventId + && threadId == other.threadId; + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/FieldId.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/FieldId.java new file mode 100644 index 000000000..6eb6e3976 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/FieldId.java @@ -0,0 +1,32 @@ +package microbat.instrumentation.model.id; + +import java.util.Objects; + +public class FieldId { + private ObjectId objectId; + private String name; + + public FieldId(ObjectId objectId, String name) { + this.objectId = objectId; + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(name, objectId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FieldId other = (FieldId) obj; + return Objects.equals(name, other.name) && Objects.equals(objectId, other.objectId); + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/MemoryLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/MemoryLocation.java new file mode 100644 index 000000000..d8d23fb3a --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/MemoryLocation.java @@ -0,0 +1,22 @@ +package microbat.instrumentation.model.id; + +import microbat.instrumentation.model.storage.Storable; + +/** + * A unique identifier for an objects field, + * array index or static field + * @author Gabau + * + */ +public abstract class MemoryLocation extends Storable { + + /** + * Returns the object that this memory location belongs to + * and null if this memory location does not belong to an object. + * @return + */ + public ObjectId getObjectId() { + return null; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectFieldMemoryLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectFieldMemoryLocation.java new file mode 100644 index 000000000..19cbcbe7a --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectFieldMemoryLocation.java @@ -0,0 +1,46 @@ +package microbat.instrumentation.model.id; + +import java.util.Objects; + +public class ObjectFieldMemoryLocation extends MemoryLocation { + /** + * Public fields for storage. + */ + public final String fieldName; + public final ObjectId objectId; + + public ObjectFieldMemoryLocation(String fieldName, ObjectId objectId) { + super(); + this.fieldName = fieldName; + this.objectId = objectId; + } + + public final String getField() { + return this.fieldName; + } + + @Override + public ObjectId getObjectId() { + return this.objectId; + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, objectId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ObjectFieldMemoryLocation other = (ObjectFieldMemoryLocation) obj; + return Objects.equals(fieldName, other.fieldName) && Objects.equals(objectId, other.objectId); + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectId.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectId.java new file mode 100644 index 000000000..aca62357b --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ObjectId.java @@ -0,0 +1,106 @@ +package microbat.instrumentation.model.id; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.model.generator.ThreadIdGenerator; +import microbat.instrumentation.model.storage.Storable; +import microbat.instrumentation.model.storage.Storage; + +/** + * Uniquely identifies an ojbect + * @author Gabau + * + */ +public class ObjectId extends Storable implements Parser { + public ThreadId threadId; + public long objectCounter; + + private static ThreadLocal objectCounterThraedLocal = ThreadLocal.withInitial(new Supplier() { + @Override + public Long get() { + return 0L; + } + }); + + public ObjectId() { + this(true); + } + + public ObjectId(ThreadId threadId, long objectCounter) { + this.threadId = threadId; + this.objectCounter = objectCounter; + } + + /** + * + * @param incrementLocalCounter false iff this is a reference object + */ + public ObjectId(boolean incrementLocalCounter) { + this.threadId = ThreadIdGenerator.threadGenerator.getId(Thread.currentThread()); + if (incrementLocalCounter) { + this.objectCounter = objectCounterThraedLocal.get(); + objectCounterThraedLocal.set(objectCounter + 1); + } + } + + + @Override + public String toString() { + // TODO Auto-generated method stub + return getFromStore(); + } + + + + public Map store() { + HashMap fieldMap = new HashMap(); + Field[] fields = getClass().getFields(); + for (Field f : fields) { + try { + Object value = f.get(this); + fieldMap.put(f.getName(), fromObject(value)); + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return fieldMap; + } + + @Override + public int hashCode() { + return Objects.hash(objectCounter, threadId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ObjectId other = (ObjectId) obj; + return objectCounter == other.objectCounter && threadId.equals(other.threadId); + } + + @Override + public ObjectId parse(ParseData data) { + this.threadId = ThreadId.createThreadId(data.getField("threadId")); + this.objectCounter = data.getField("objectCounter").getLongValue(); + return this; + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadCountVector.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadCountVector.java new file mode 100644 index 000000000..eb27a087c --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadCountVector.java @@ -0,0 +1,143 @@ +package microbat.instrumentation.model.id; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.instr.aggreplay.shared.parser.MemoryLocationParser; +import microbat.instrumentation.instr.aggreplay.shared.parser.RCVectorParser; +import microbat.instrumentation.model.ReadWriteAccessListReplay; +import microbat.instrumentation.model.storage.Storable; +import sav.common.core.Pair; + + +public class ReadCountVector extends Storable implements Parser { + + private ConcurrentHashMap>> + rcVectorClockConcurrentHashMap = new ConcurrentHashMap<>(); + + private static Map parseInnerMap(ParseData data) { + return new RCVectorParser().parse(data); + } + + public Map> getRCVector(long threadID) { + return rcVectorClockConcurrentHashMap.getOrDefault(threadID, new HashMap>()); + } + + private static Map> parseMap(ParseData data) { + List> objectMap = data.toPairList(); + Map rcMap; + Map> rcVector = new HashMap<>(); + MemoryLocationParser parser = new MemoryLocationParser(); + for (Pair innerData : objectMap) { + MemoryLocation mlocation = parser.parse(innerData.first()); + rcMap = parseInnerMap(innerData.second()); + rcVector.put(mlocation, rcMap); + } + return rcVector; + } + @Override + public ReadCountVector parse(ParseData data) { + ParseData mapData = data.getField("rcVectorClockConcurrentHashMap"); + List> objectMap = mapData.toPairList(); + Long keyValueLong = 0L; + Map> rcVector = null; + for (Pair innerData : objectMap) { + keyValueLong = Long.parseLong(innerData.first().getValue()); + rcVector = parseMap(innerData.second()); + rcVectorClockConcurrentHashMap.put(keyValueLong, rcVector); + } + return this; + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + String resultString = fromMap(rcVectorClockConcurrentHashMap); + result.put("rcVectorClockConcurrentHashMap", resultString); + return result; + } + + public ReadCountVector() { + + } + + public Map get(MemoryLocation memoryLocation, long threadId) { + assertThreadId(threadId); + Map otherMap = rcVectorClockConcurrentHashMap.get(threadId).get(memoryLocation); + if (otherMap == null) return new HashMap<>(); + return new HashMap<>(otherMap); + } + + private void assertThreadId(long threadId) { + if (!rcVectorClockConcurrentHashMap.containsKey(threadId)) { + synchronized (rcVectorClockConcurrentHashMap) { + // additional check in case another thread reaches this line + if (!rcVectorClockConcurrentHashMap.containsKey(threadId)) { + rcVectorClockConcurrentHashMap.put(threadId, + new ConcurrentHashMap>()); + } + } + } + } + + public void updateReadVectors(MemoryLocation variable, long eventThreadID) { + assertThreadId(eventThreadID); + Map eventThreadMap = rcVectorClockConcurrentHashMap.get(eventThreadID) + .getOrDefault(variable, new HashMap()); + for (Map.Entry>> entry + : rcVectorClockConcurrentHashMap.entrySet()) { + int value = entry.getValue().getOrDefault(variable, new HashMap()) + .getOrDefault(entry.getKey(), 0); + if (value == 0) { + continue; + } + long tPrime = entry.getKey(); + eventThreadMap.put(tPrime, value); + } + rcVectorClockConcurrentHashMap.get(eventThreadID).put(variable, eventThreadMap); + } + + public void increment(long t1, MemoryLocation variable) { + assertThreadId(t1); + Map> threadRCVector = + rcVectorClockConcurrentHashMap.get(t1); + Map innerConcurrentHashMap = threadRCVector.get(variable); + if (innerConcurrentHashMap == null) { + innerConcurrentHashMap = new HashMap<>(); + threadRCVector.put(variable, innerConcurrentHashMap); + } + int count = innerConcurrentHashMap.getOrDefault(t1, 0); + innerConcurrentHashMap.put(t1, count + 1); + } + + public void update(Event event) { + long threadId = Thread.currentThread().getId(); + assertThreadId(threadId); + + } + + @Override + public int hashCode() { + return Objects.hash(rcVectorClockConcurrentHashMap); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ReadCountVector other = (ReadCountVector) obj; + return Objects.equals(rcVectorClockConcurrentHashMap, other.rcVectorClockConcurrentHashMap); + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadWriteAccessList.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadWriteAccessList.java new file mode 100644 index 000000000..1e9c3e8ca --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ReadWriteAccessList.java @@ -0,0 +1,138 @@ +package microbat.instrumentation.model.id; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.instr.aggreplay.shared.parser.MemoryLocationParser; +import microbat.instrumentation.instr.aggreplay.shared.parser.RCVectorParser; +import microbat.instrumentation.model.storage.Storable; +import sav.common.core.Pair; + + +public class ReadWriteAccessList extends Storable implements Parser { + private HashMap> exList = new HashMap<>(); + + public Node top(long threadId) { + return exList.get(threadId).getFirst(); + } + + public Map> getList() { + return exList; + } + + public static class Node extends Storable implements Parser { + private Map rcMap; + private MemoryLocation memoryLocation; + private Event event; + public Node(Map rcMap, MemoryLocation memoryLocation, Event event) { + this.rcMap = rcMap; + this.memoryLocation = memoryLocation; + this.event = event; + } + + public Node() { + + } + + public Map getRcMap() { + return rcMap; + } + + public MemoryLocation getLocation() { + return memoryLocation; + } + + @Override + protected Map store() { + Map fields = new HashMap<>(); + fields.put("rcMap", fromObject(rcMap)); + fields.put("memoryLocation", fromObject(memoryLocation)); + fields.put("event", fromObject(event)); + return fields; + } + @Override + public Node parse(ParseData data) { + this.event = Event.parseEvent(data.getField("event")); + this.memoryLocation = new MemoryLocationParser().parse(data.getField("memoryLocation")); + data.getField("event"); + this.rcMap = new RCVectorParser().parse(data.getField("rcMap")); + // TODO Auto-generated method stub + return this; + } + } + + + + @Override + protected Map store() { + Map result = new HashMap<>(); + result.put("exList", fromMap(exList)); + return result; + } + + + protected void assertExListThread(long threadId) { + if (!exList.containsKey(threadId)) { + synchronized (exList) { + if (!exList.containsKey(threadId)) { + exList.put(threadId, new LinkedList()); + } + } + } + } + + public void add(MemoryLocation memoryLocation, Event event, ReadCountVector readVector) { + long tid = event.getThreadId(); + assertExListThread(tid); + Map rcMap = readVector.get(memoryLocation, tid); + if (rcMap == null) { + rcMap = new HashMap<>(); + } + exList.get(tid).add(new Node(new HashMap<>(rcMap), memoryLocation, event)); + } + + @Override + public ReadWriteAccessList parse(ParseData data) { + ParseData exListData = data.getField("exList"); + List> mapData = exListData.toPairList(); + for (Pair innerData : mapData) { + Long value = innerData.first().getLongValue(); + List nodeDatas = innerData.second().toList(); + LinkedList nodes = new LinkedList<>(); + for (ParseData nodeData: nodeDatas) { + Node node = new Node(); + node.parse(nodeData); + nodes.add(node); + } + this.exList.put(value, nodes); + } + return this; + } + + + @Override + public int hashCode() { + return Objects.hash(exList); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ReadWriteAccessList other = (ReadWriteAccessList) obj; + return Objects.equals(exList, other.exList); + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/SharedMemoryLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/SharedMemoryLocation.java new file mode 100644 index 000000000..f327826d2 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/SharedMemoryLocation.java @@ -0,0 +1,288 @@ +package microbat.instrumentation.model.id; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Stack; +import java.util.function.Consumer; +import java.util.function.Function; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.Parser; +import microbat.instrumentation.instr.aggreplay.shared.parser.MemoryLocationParser; +import microbat.instrumentation.model.storage.Storable; +import sav.common.core.Pair; + +/** + * Represents shared memory -> can be a field in an object, + * an index in an array or a static field + * + * @author Gabau + * + */ +public class SharedMemoryLocation extends Storable implements Parser { + private Event lastWrite = Event.getFirstEvent(this); + /** + * The location this object is at. + */ + private MemoryLocation location; + + + + /** + * The event write for this object. + * Uses a map to prevent need for synchronisation + */ + private final Map>> threadExListMap = new HashMap<>(); + private LinkedList> wrList = new LinkedList<>(); + + // write event list used during recording + private List writeEventList = new LinkedList<>(); + // TODO: get this stack from the input + put in a different class + // the bottom two are data used for replay + // Need to separate these two cause can get quite confusing -> ideally in a separate class + // W_var(e) + // stack of write events, top is the earliest write + private Stack writeEventStack; + private List repWriteEvent = new LinkedList<>(); + // stack of lw -> read event + private Stack> repWrStack = new Stack<>(); + // mapping from write event to set of reads for that write + private Map> repWrMapSetMap = new HashMap<>(); + + + protected Stack> generateWriteEventStack(LinkedList> writeEventList) { + final Stack> result = new Stack<>(); + writeEventList.descendingIterator().forEachRemaining(new Consumer>() { + @Override + public void accept(Pair value) { + result.push(value); + } + }); + return result; + } + + public boolean canWrite(Event e) { + if (this.writeEventStack.empty()) return false; + return this.writeEventStack.peek().equals(e) && + this.repWrMapSetMap.getOrDefault(lastWrite, Collections.emptySet()).isEmpty(); + } + + public boolean canRead(Event event) { + return this.repWrMapSetMap.getOrDefault(lastWrite, Collections.emptySet()).contains(event); + } + + public void read(Event event) { + this.repWrMapSetMap.getOrDefault(lastWrite, Collections.emptySet()).remove(event); + } + + @Override + protected Map store() { + Map result = new HashMap(); + result.put("writeEventList", fromObject(writeEventList)); + result.put("location", fromObject(location)); + result.put("threadExListMap", fromObject(threadExListMap)); + result.put("wrList", fromObject(wrList)); + return result; + } + + public boolean checkLastWrite(Event e) { + return getRecordedLastWrite().equals(lastWrite); + } + + // TODO (Gab): make the recorded lastwrite not a map + private Event getRecordedLastWrite() { + return repWrStack.peek().first(); + } + + public void popRecordedLastWR() { + if (repWrStack.empty()) return; + repWrStack.pop(); + } + + /** + * Checks if this shared memory location is a location + * in an object. + * @return + */ + public boolean isSharedObjectMem() { + return this.location instanceof ObjectFieldMemoryLocation; + } + + + /** + * Checks if the given event is the same as the event in the previous run + * on the top of the stack + * @param e + * @return + */ + public boolean isSameAsPrevRunWrite(Event e) { + if (writeEventStack.empty()) return false; + return writeEventStack.peek().equals(e); + } + + /** + * Separate function for replay. + * @param e + */ + public void addRepWriteEvent(Event e) { + repWriteEvent.add(e); + setLastWrite(e); + } + + public void popEvent() { + if (writeEventStack.empty()) return; + writeEventStack.pop(); + } + + public SharedMemoryLocation() { + this.location = null; + this.writeEventList = null; + } + + public SharedMemoryLocation(MemoryLocation location) { + this.location = location; + } + + public MemoryLocation getLocation() { + return this.location; + } + + public void write(Event event) { + writeEventList.add(event); + setLastWrite(event); + } + + + /** + * Checks if the current shared mem location write + * is the same as the previous + * @return + */ + public boolean isSameAsLastWrite(Event readEvent) { + if (this.repWrStack.empty()) return false; + return this.lastWrite.equals(repWrStack.peek().first()) + && repWrStack.peek().second().equals(readEvent); + } + + public boolean isSameEvent(Event event) { + if (this.writeEventStack.peek() == null) return false; + return this.writeEventStack.peek().equals(event); + } + + public void setLastWrite(Event event) { + this.lastWrite = event; + } + + public Event getLastWrite() { + return this.lastWrite; + } + + private void assertThreadExists(long threadId) { + if (!threadExListMap.containsKey(threadId)) { + synchronized (threadExListMap) { + if (!threadExListMap.containsKey(threadId)) { + threadExListMap.put(threadId, new LinkedList>()); + } + } + } + } + + public void appendExList(Event lw, Event readEvent) { + long threadId = Thread.currentThread().getId(); + assertThreadExists(threadId); + // in theory only need to maintain per thread tho + // - > then can do the popping per threaad + // but need to decide which write occurs first +// threadExListMap.get(threadId).add(Pair.of(lw, readEvent)); + synchronized (wrList) { + wrList.add(Pair.of(lw, readEvent)); + } + } + + @Override + public int hashCode() { + return Objects.hash(location, threadExListMap, writeEventList); + } + + /** + * Generates the mapping from write event to read events + */ + public void generateWRMap() { + + for (Event writeEvent : writeEventList) { + if (!this.repWrMapSetMap.containsKey(writeEvent)) { + this.repWrMapSetMap.put(writeEvent, new HashSet()); + } + } + for (Pair events: wrList) { + if (!this.repWrMapSetMap.containsKey(events.first())) { + this.repWrMapSetMap.put(events.first(), new HashSet()); + } + this.repWrMapSetMap.get(events.first()).add(events.second()); + } + + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SharedMemoryLocation other = (SharedMemoryLocation) obj; + return Objects.equals(location, other.location) && Objects.equals(threadExListMap, other.threadExListMap) + && Objects.equals(writeEventList, other.writeEventList); + } + + @Override + public SharedMemoryLocation parse(ParseData data) { + this.location = new MemoryLocationParser().parse(data.getField("location")); + this.writeEventList = data.getField("writeEventList").toList(new Function() { + @Override + public Event apply(ParseData parseData) { + return Event.parseEvent(parseData); + } + }); + this.writeEventStack = new Stack<>(); + new LinkedList<>(writeEventList).descendingIterator().forEachRemaining(new Consumer() { + @Override + public void accept(Event event) { + writeEventStack.add(event); + } + }); + + + List> wrListInner = data.getField("wrList").>toList(new Function>() { + @Override + public Pair apply(ParseData parseData) { + return parseData.toPair(new Function() { + @Override + public Event apply(ParseData data) { + return Event.parseEvent(data); + } + }, + new Function() { + @Override + public Event apply(ParseData data) { + return Event.parseEvent(data); + } + }); + } + }); + + this.wrList = new LinkedList<>(wrListInner); + this.repWrStack = this.generateWriteEventStack(this.wrList); + return this; + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/StaticFieldLocation.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/StaticFieldLocation.java new file mode 100644 index 000000000..77a640e87 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/StaticFieldLocation.java @@ -0,0 +1,55 @@ +package microbat.instrumentation.model.id; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; + +/** + * Represents a static field location. + * @author Gabau + * + */ +public class StaticFieldLocation extends MemoryLocation { + private final String className; + private final String fieldName; + + public StaticFieldLocation(ParseData data) { + className = data.getField("className").getValue(); + fieldName = data.getField("fieldName").getValue(); + } + + public StaticFieldLocation(String className, String fieldName) { + this.className = className; + this.fieldName = fieldName; + } + + @Override + public int hashCode() { + return Objects.hash(className, fieldName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + StaticFieldLocation other = (StaticFieldLocation) obj; + return Objects.equals(className, other.className) && Objects.equals(fieldName, other.fieldName); + } + + @Override + protected Map store() { + Map result = new HashMap<>(); + result.put("className", this.className); + result.put("fieldName", this.fieldName); + return result; + } + + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ThreadId.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ThreadId.java new file mode 100644 index 000000000..0b11025d8 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/id/ThreadId.java @@ -0,0 +1,169 @@ +package microbat.instrumentation.model.id; + +import java.util.Objects; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.model.storage.Storable; + +public class ThreadId extends Storable { + + + public ListNode rootListNode = null; + public long threadId; + // keeps track of the order of the trace node which spawned this node. + public int spawnOrder = -1; + private long idCounter = 0; + + public void setSpawnOrder(Integer spawnOrder) { + this.spawnOrder = spawnOrder; + } + + public ThreadId getParent() { + if (rootListNode == null) { + return null; + } + return new ThreadId(rootListNode.parent); + } + + public Integer getSpawnOrder() { + return this.spawnOrder; + } + + public int internalHashCode = 100002301; + + public String printRootListNode() { + return rootListNode.getFromStore(); + } + + private static class ListNode extends Storable { + long value; + ListNode parent; + public ListNode(long value, ListNode parent) { + this.value = value; + this.parent = parent; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ListNode other = (ListNode) obj; + return value == other.value && Objects.equals(parent, other.parent); + } + @Override + public String getFromStore() { + StringBuilder result = new StringBuilder(); + ListNode temp = this; + while (temp != null) { + result.append(temp.value); + result.append(";"); + temp = temp.parent; + } + return result.toString(); + } + + + } + + public ThreadId(long threadId) { + rootListNode = new ListNode(0, null); + precomputeHashCode(); + this.threadId = threadId; + } + + private ThreadId(ListNode rootListNode) { + this.rootListNode = rootListNode; + precomputeHashCode(); + } + + private ThreadId(int internalHashCode, String threadId, long threadValue, int spawnOrder) { + this.internalHashCode = internalHashCode; + this.rootListNode = fromString(threadId); + this.threadId = threadValue; + this.spawnOrder = spawnOrder; + } + + public static ListNode fromString(String hashedId) { + String[] splitValues = hashedId.split(";"); + ListNode result = new ListNode(Integer.parseInt(splitValues[splitValues.length - 1]), null); + for (int i = splitValues.length - 2; i >= 0; i--) { + int value = Integer.parseInt(splitValues[i]); + result = new ListNode(value, result); + } + return result; + } + + /** + * Creates a thread with the provided thread + * @param internalHashCode The generated hash code of the thread. + * @param threadId The replicated id of the thread. + * @param threadValue The id {@code long} of the thread. TODO(Gab): Not ideal to store in this class + * @return + */ + public static ThreadId createThread(int internalHashCode, String threadId, long threadValue, + int spawnOrder) { + return new ThreadId(internalHashCode, threadId, threadValue, spawnOrder); + } + + private void precomputeHashCode() { + ListNode temp = rootListNode; + while (temp != null) { + int k = Long.hashCode(temp.value); + k = k & (k >>> 32); + internalHashCode = 37 * internalHashCode + k; + temp = temp.parent; + } + } + + /** + * Not synchronised, because this should be called in the parent thread. + * @param threadId + * @return + */ + public ThreadId createChildWithThread(long threadId) { + long value = idCounter++; + ThreadId childId = createChild(value); + childId.threadId = threadId; + return childId; + } + + + + public ThreadId createChild(long value) { + ListNode rootListNode = new ListNode(value, this.rootListNode); + return new ThreadId(rootListNode); + } + + @Override + public int hashCode() { + return Objects.hash(internalHashCode); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ThreadId other = (ThreadId) obj; + return internalHashCode == other.internalHashCode + && other.rootListNode.equals(this.rootListNode); + } + + public long getId() { + return this.threadId; + } + + public static ThreadId createThreadId(ParseData data) { + int internalHashCode = Integer.parseInt(data.getField("internalHashCode").getValue()); + int spawnOrder = data.getField("spawnOrder").getIntValue(); + return createThread(internalHashCode, data.getField("rootListNode").getValue(), + data.getField("threadId").getLongValue(), spawnOrder); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/FileStorage.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/FileStorage.java new file mode 100644 index 000000000..bbb35870a --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/FileStorage.java @@ -0,0 +1,40 @@ +package microbat.instrumentation.model.storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashSet; + +public class FileStorage implements Storage { + + private String fileName; + + private static final String CLASS_DELIM_STRING = ";"; + public FileStorage(String fileName) { + this.fileName = fileName; + } + + @Override + public void store(Iterable objects) { + File file = new File(fileName); + System.out.println("Writing to file " + fileName); + try { + FileOutputStream fileOutputStream = new FileOutputStream(file); + for (Storable storable: objects) { + fileOutputStream.write(storable.getFromStore().getBytes()); + fileOutputStream.write(Storage.OBJECT_SEPARATOR.getBytes()); + } + fileOutputStream.write('!'); + fileOutputStream.close(); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storable.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storable.java new file mode 100644 index 000000000..77d8175bc --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storable.java @@ -0,0 +1,128 @@ +package microbat.instrumentation.model.storage; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import sav.common.core.Pair; + +public abstract class Storable { + public static String fromList(List list) { + StringBuilder values = new StringBuilder(); + values.append(Storage.LIST_START); + for (T val : list) { + values.append(fromObject(val)); + values.append(Storage.LIST_SEP); + } + + values.append(Storage.LIST_END); + return values.toString(); + } + + // store [value, {}] + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static String fromMap(Map map) { + List values = new LinkedList<>(); + for (Map.Entry entry : map.entrySet()) { + values.add(fromObject(entry.getKey())); + values.add(fromObject(entry.getValue())); + } + return fromList(values); + } + + public static String fromPair(Pair pair) { + LinkedList objectList = new LinkedList<>(); + objectList.add(pair.first()); + objectList.add(pair.second()); + return fromList(objectList); + } + + public static String fromSet(Set set) { + return fromList(set.stream().collect(Collectors.toList())); + } + + @SuppressWarnings("unchecked") + public static String fromObject(Object object) { + if (object == null) { + return "null"; + } + if (object instanceof Storable) { + return ((Storable) object).getFromStore(); + } + if (object instanceof Set) { + return fromSet((Set) object); + } + if (object instanceof List) { + return fromList((List) object); + } + if (object instanceof Map) { + return fromMap((Map) object); + } + if (object instanceof Pair) { + return fromPair((Pair) object); + } + return object.toString(); + } + + /** + * The guarantees of a storable. + * Object is enclosed in { and } + * + * + * @return + */ + protected Map store() { + HashMap fieldMap = new HashMap(); + Field[] fields = getClass().getFields(); + for (Field f : fields) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + try { + Object value = f.get(this); + if (value == null) { + continue; + } + if (value instanceof Storable) { + fieldMap.put(f.getName(), ((Storable) value).getFromStore()); + } else { + fieldMap.put(f.getName(), fromObject(value)); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return fieldMap; + } + + public String getObjectType() { + return this.getClass().getName(); + } + + // guarantees an object type + public String getFromStore() { + StringBuilder builder = new StringBuilder(); + builder.append(Storage.START_OBJECT_STRING); + builder.append('\n'); + builder.append("ObjectType:"); + builder.append(getObjectType()); + builder.append(Storage.OBJECT_SEPARATOR); + builder.append('\n'); + for (Map.Entry entry : store().entrySet()) { + builder.append(entry.getKey()); + builder.append(Storage.STORE_DELIM_STRING); + builder.append(entry.getValue()); + builder.append(Storage.OBJECT_SEPARATOR); + builder.append('\n'); + } + builder.append(Storage.CLOSE_OBJECT_STRING); + return builder.toString(); + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storage.java b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storage.java new file mode 100644 index 000000000..a842b9473 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/model/storage/Storage.java @@ -0,0 +1,18 @@ +package microbat.instrumentation.model.storage; + +import java.util.HashSet; + +import microbat.instrumentation.model.id.MemoryLocation; + +public interface Storage { + public static String itemDelimString = ","; + String STORE_DELIM_STRING = ":"; + public static final String START_OBJECT_STRING = "{"; + public static final String CLOSE_OBJECT_STRING = "}"; + public static final String OBJECT_SEPARATOR = ","; + + public static final char LIST_START = '['; + public static final char LIST_END = ']'; + public static final char LIST_SEP = ','; + public void store(Iterable objects); +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/output/RunningInfo.java b/microbat_instrumentator/src/main/microbat/instrumentation/output/RunningInfo.java index a24f0eada..dace0a1f1 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/output/RunningInfo.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/output/RunningInfo.java @@ -8,8 +8,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; +import microbat.instrumentation.utils.MicrobatUtils; import microbat.model.trace.Trace; import sav.common.core.SavRtException; @@ -25,11 +28,32 @@ public class RunningInfo { private int expectedSteps; private int collectedSteps; + /** + * For use with concurrent bugs, + * the information at + * precheck is not sufficient. + */ + private boolean passTest; + + public boolean hasPassedTest() { + return passTest; + } + public RunningInfo(String programMsg, List traceList, int expectedSteps, int collectedSteps) { this.programMsg = programMsg; - this.traceList = traceList; + if (traceList != null) { + // used to get rid of redundant threads. + this.traceList = traceList.stream().filter(trace -> trace.size() != 0 || trace.getThreadName().equals("main")).collect(Collectors.toList()); + } else { + traceList = new LinkedList(); + } + if (traceList.size() == 0) { + this.traceList = traceList; + } + this.expectedSteps = expectedSteps; this.collectedSteps = collectedSteps; + this.passTest = MicrobatUtils.checkTestResult(programMsg); } public static RunningInfo readFromFile(String execTraceFile) { @@ -70,6 +94,7 @@ public static RunningInfo readFromFile(File execTraceFile) { } public Trace getMainTrace() { + if (traceList == null) return null; for(Trace trace: traceList) { if(trace.isMain()) { return trace; diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableReader.java b/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableReader.java new file mode 100644 index 000000000..ffd298630 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableReader.java @@ -0,0 +1,43 @@ +package microbat.instrumentation.output; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.util.List; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; + +public class StorableReader { + private OutputReader reader; + private String programMsgString; + + public StorableReader(InputStream stream) { + this.reader = new OutputReader(stream); + } + + public StorableReader(File file) throws FileNotFoundException { + InputStream iStream = new FileInputStream(file); + this.reader = new OutputReader(iStream); + } + + public List read() throws IOException { + SharedDataParser parser = new SharedDataParser(); + String dataString = reader.readString(); + StringReader sReader = new StringReader(dataString); + List resultDatas = parser.parse(sReader); + this.programMsgString = reader.readString(); + reader.close(); + return resultDatas; + } + + public String getProgramMsg() { + return programMsgString; + } + +} + diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableWriter.java b/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableWriter.java new file mode 100644 index 000000000..7e4e9e126 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/output/StorableWriter.java @@ -0,0 +1,35 @@ +package microbat.instrumentation.output; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.model.storage.Storable; + +public class StorableWriter { + private OutputWriter outputWriter; + + public StorableWriter(OutputStream outputStream) { + this.outputWriter = new OutputWriter(outputStream); + } + + public StorableWriter(File file) throws FileNotFoundException { + OutputStream oStream = new FileOutputStream(file); + this.outputWriter = new OutputWriter(oStream); + } + + public void writeStorable(Storable storable) throws IOException { + String resultString = storable.getFromStore(); + outputWriter.writeString(resultString); + outputWriter.writeString(Agent.getProgramMsg()); + outputWriter.flush(); + outputWriter.close(); + } + + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputReader.java b/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputReader.java index ba28351f1..dd8467787 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputReader.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputReader.java @@ -2,9 +2,13 @@ import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.id.ThreadId; import microbat.model.BreakPoint; import microbat.model.ClassLocation; import microbat.model.ControlScope; @@ -47,12 +51,29 @@ public List readTrace() throws IOException { List locationList = readLocations(); trace.setExecutionList(readSteps(trace, locationList)); readStepVariableRelation(trace); - + ThreadId threadId = readTraceInnerThreadId(); + trace.setInnerThreadId(threadId); + List lockList = readSerializableList(); + long acquiringLock = readLong(); + trace.setAcquiredLocks(lockList); + trace.setAcquiringLock(acquiringLock); + long memoryUsed = readLong(); + trace.setMemoryUsed(memoryUsed); traceList.add(trace); } return traceList; } + + private ThreadId readTraceInnerThreadId() throws IOException { + String threadIDString = readString(); + if (threadIDString.length() == 0) return null; + StringReader threadIdReader = new StringReader(threadIDString); + SharedDataParser parser = new SharedDataParser(); + ParseData data = parser.parse(threadIdReader).get(0); + ThreadId resulThreadId = ThreadId.createThreadId(data); + return resulThreadId; + } private List readFilterInfo() throws IOException { boolean inFile = readBoolean(); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputWriter.java b/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputWriter.java index 6c52d73e1..d69fa6a0b 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputWriter.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/output/TraceOutputWriter.java @@ -60,6 +60,19 @@ public void writeTrace(Trace trace, String projectName, String projectVersion, S Map locIdIdxMap = writeLocations(trace); writeSteps(trace.getExecutionList(), locIdIdxMap); writeStepVariableRelation(trace); + writeTraceThreadId(trace); + writeSerializableList(trace.getAcquiredLocks()); + writeLong(trace.getAcquiringLock()); + writeLong(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); + } + + private void writeTraceThreadId(Trace trace) throws IOException { + if (trace.getInnerThreadId() == null) { + writeString(null); + return; + } + writeString(trace.getInnerThreadId().getFromStore()); + } private void writeFilterInfo(List libClasses, boolean isInclusive) throws IOException { diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/precheck/MemoryMeasurementAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/precheck/MemoryMeasurementAgent.java new file mode 100644 index 000000000..12e15c665 --- /dev/null +++ b/microbat_instrumentator/src/main/microbat/instrumentation/precheck/MemoryMeasurementAgent.java @@ -0,0 +1,82 @@ +package microbat.instrumentation.precheck; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; + +import microbat.instrumentation.Agent; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; +import microbat.instrumentation.instr.aggreplay.shared.BasicTransformer; + +public class MemoryMeasurementAgent extends Agent { + + private TimeoutThread timeoutThread; + private static MemoryMeasurementAgent attachedAgent = null; + private AgentParams agentParams; + public static MemoryMeasurementAgent getMeasurementAgent(CommandLine cmLine) { + if (attachedAgent == null) { + attachedAgent = new MemoryMeasurementAgent(); + attachedAgent.agentParams = AgentParams.initFrom(cmLine); + } + return attachedAgent; + } + + @Override + public void startup0(long vmStartupTime, long agentPreStartup) { + this.timeoutThread = new TimeoutThread(this); + timeoutThread.start(); + } + + @Override + public void shutdown() throws Exception { + long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + File dumpFile = new File(agentParams.getDumpFile()); + FileWriter fw = new FileWriter(dumpFile); + fw.write(memoryUsed + ""); + fw.flush(); + fw.close(); + } + + @Override + public void startTest(String junitClass, String junitMethod) { + // TODO Auto-generated method stub + + } + + @Override + public void finishTest(String junitClass, String junitMethod) { + // TODO Auto-generated method stub + + } + + @Override + public ClassFileTransformer getTransformer0() { + return new ClassFileTransformer() { + + }; + } + + @Override + public void retransformBootstrapClasses(Instrumentation instrumentation, Class[] retransformableClasses) + throws Exception { + // TODO Auto-generated method stub + + } + + @Override + public void exitTest(String testResultMsg, String junitClass, String junitMethod, long threadId) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isInstrumentationActive0() { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/precheck/PrecheckAgent.java b/microbat_instrumentator/src/main/microbat/instrumentation/precheck/PrecheckAgent.java index 860ad006b..d9cf4da2b 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/precheck/PrecheckAgent.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/precheck/PrecheckAgent.java @@ -9,11 +9,13 @@ import microbat.instrumentation.CommandLine; import microbat.instrumentation.filter.GlobalFilterChecker; import microbat.instrumentation.instr.SystemClassTransformer; +import microbat.instrumentation.instr.aggreplay.TimeoutThread; public class PrecheckAgent extends Agent{ private AgentParams agentParams; private PrecheckTransformer precheckTransformer; private Instrumentation instrumentation; + private TimeoutThread timeoutThread = new TimeoutThread(); public PrecheckAgent(CommandLine cmd, Instrumentation instrumentation) { this.agentParams = AgentParams.initFrom(cmd); @@ -22,6 +24,7 @@ public PrecheckAgent(CommandLine cmd, Instrumentation instrumentation) { } public void startup0(long vmStartupTime, long agentPreStartup) { + timeoutThread.start(); GlobalFilterChecker.setup(agentParams.initAppClassPath(), agentParams.getIncludesExpression(), agentParams.getExcludesExpression()); TraceMeasurement.setStepLimit(agentParams.getStepLimit()); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/EmptyExecutionTracer.java b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/EmptyExecutionTracer.java index 717e20f81..74b413c4d 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/EmptyExecutionTracer.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/EmptyExecutionTracer.java @@ -118,5 +118,10 @@ public void _hitExeptionTarget(int line, String className, String methodSignatur public void setThreadName(String threadName) { } + + @Override + public int getLatestOrder() { + return 0; + } } diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/ExecutionTracer.java b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/ExecutionTracer.java index 5bb8ab71a..c9392be36 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/ExecutionTracer.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/ExecutionTracer.java @@ -5,11 +5,14 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Stack; import org.apache.bcel.Repository; import org.apache.bcel.classfile.LocalVariable; @@ -57,12 +60,19 @@ public class ExecutionTracer implements IExecutionTracer, ITracer { public static int expectedSteps = Integer.MAX_VALUE; // private static int tolerantExpectedSteps = expectedSteps; public static boolean avoidProxyToString = false; + private boolean lock = false; private long threadId; - + /** + * When the current thread is trying to acquire an object. + */ + private Long acquiringLock = null; private Trace trace; private MethodCallStack methodCallStack; + // stack containing the list of locks acquired + private Stack lockStack = new Stack(); + /** * indicate whether the execution of the thread should be recorded */ @@ -74,6 +84,25 @@ public static void setExpectedSteps(int expectedSteps) { // tolerantExpectedSteps = expectedSteps * 2; } } + + // fires before lock is acquire + public static void _onLockAcquire(Object object) { + ExecutionTracer tracer = rtStore.get(Thread.currentThread().getId()); + tracer.acquiringLock = TraceUtils.getUniqueId(object); + } + + // fire immediately after lock is acquired + public static void _onLockAcquire2() { + ExecutionTracer tracer = rtStore.get(Thread.currentThread().getId()); + tracer.lockStack.add(tracer.acquiringLock); + tracer.acquiringLock = null; + } + + public static void _afterLockAcquire() { + ExecutionTracer tracer = rtStore.get(Thread.currentThread().getId()); + tracer.lockStack.pop(); + } + public static void setStepLimit(int stepLimit) { if (stepLimit != AgentConstants.UNSPECIFIED_INT_VALUE) { @@ -292,6 +321,20 @@ private boolean isProxyClass(Class clazz) { } return false; } + + /** + * Lock used to stop modifications to the trace + */ + public void setLock() { + this.lock = true; + } + + /** + * Used to remove lock + */ + public void removeLock() { + this.lock = false; + } /* * Methods with prefix "_" are called in instrument code. @@ -677,12 +720,12 @@ public void _hitLine(int line, String className, String methodSignature, int num // shutdown(); // Agent._exitProgram("fail;Trace size exceeds expected_steps!"); // } - BreakPoint bkp = new BreakPoint(className, methodSignature, line); long timestamp = System.currentTimeMillis(); TraceNode currentNode = new TraceNode(bkp, null, order, trace, numOfReadVars, numOfWrittenVars, timestamp, bytecode); - - trace.addTraceNode(currentNode); + if (!lock) { + trace.addTraceNode(currentNode); + } AgentLogger.printProgress(order); if (!methodCallStack.isEmpty()) { TraceNode caller = methodCallStack.peek(); @@ -1278,6 +1321,11 @@ public static List getAllThreadStore() { return rtStore.getAllThreadTracer(); } + public int getLatestOrder() { + if (trace.getLatestNode() == null) return -1; + return trace.getLatestNode().getOrder(); + } + public static synchronized IExecutionTracer getCurrentThreadStore() { synchronized (rtStore) { long threadId = Thread.currentThread().getId(); @@ -1320,6 +1368,10 @@ public static void dispose() { public static void _start() { state = TracingState.TEST_STARTED; } + + public static boolean isRecordingOrStarted() { + return state == TracingState.TEST_STARTED || state == TracingState.RECORDING; + } public static boolean isShutdown() { return state == TracingState.SHUTDOWN; @@ -1328,6 +1380,17 @@ public static boolean isShutdown() { public Trace getTrace() { return trace; } + + public Collection getLockAcquired() { + return Collections.unmodifiableCollection(this.lockStack); + } + + public long getAcquiringLock() { + if (this.acquiringLock == null) { + return -1; + } + return this.acquiringLock; + } private static volatile LockedThreads lockedThreads = new LockedThreads(); diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/IExecutionTracer.java b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/IExecutionTracer.java index 2bec069a5..293f51759 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/runtime/IExecutionTracer.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/runtime/IExecutionTracer.java @@ -42,6 +42,8 @@ void _afterInvoke(Object returnedValue, Object invokeObj, String invokeMethodSig public void _hitMethodEnd(int line, String className, String methodSignature); + public int getLatestOrder(); + /** * * @return: isLocking diff --git a/microbat_instrumentator/src/main/microbat/instrumentation/utils/MicrobatUtils.java b/microbat_instrumentator/src/main/microbat/instrumentation/utils/MicrobatUtils.java index c29662a05..000fa92d5 100644 --- a/microbat_instrumentator/src/main/microbat/instrumentation/utils/MicrobatUtils.java +++ b/microbat_instrumentator/src/main/microbat/instrumentation/utils/MicrobatUtils.java @@ -10,4 +10,12 @@ public static String getMicrobatMethodFullName(String className, Method method) .append(method.getSignature().replace(";", ":")); return sb.toString(); } + + public static boolean checkTestResult(String msg) { + int sIdx = msg.indexOf(";"); + if (sIdx < 0 || msg.length() < sIdx) { + return false; + } + return Boolean.valueOf(msg.substring(0, sIdx)); + } } diff --git a/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ObjectAccessInstrumentatorTest.java b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ObjectAccessInstrumentatorTest.java new file mode 100644 index 000000000..e57a7a714 --- /dev/null +++ b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ObjectAccessInstrumentatorTest.java @@ -0,0 +1,143 @@ +package microbat.instrumentation.aggreplay; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.instrument.ClassDefinition; +import java.util.logging.Handler; + +import org.apache.bcel.Repository; +import org.apache.bcel.classfile.ClassFormatException; +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.Code; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.INVOKESTATIC; +import org.apache.bcel.generic.InstructionHandle; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.MethodGen; +import org.apache.bcel.util.ByteSequence; +import org.junit.Test; +import org.junit.internal.runners.TestClass; +import org.junit.validator.PublicClassValidator; + +import javassist.bytecode.Opcode; +import microbat.instrumentation.AgentParams; +import microbat.instrumentation.CommandLine; +import microbat.instrumentation.instr.aggreplay.ObjectAccessInstrumentator; + + +/** + * Utility Class for testing a generic instrumentator + * @author Gabau + * + */ +public class ObjectAccessInstrumentatorTest { + private static class InjectedObjectAccessInstrumentor extends ObjectAccessInstrumentator { + private static int counter = 0; + public InjectedObjectAccessInstrumentor() { + super(InjectedObjectAccessInstrumentor.class, AgentParams.initFrom(new CommandLine())); + } + public static void _onNewObject(Object object) { + counter++; + } + + public static void _onObjectWrite(Object object, String field) { + counter++; + } + + public static void _onObjectRead(Object object, String field) { + counter++; + } + } + + private static class TestClass { + int value = 0; + public static void main(String[] args) { + TestClass tClass = new TestClass(); + TestClass tClass2 = new TestClass(); + tClass.value = 1000; + tClass.value = 100; + tClass2.value = tClass.value; + } + } + private ClassGen getClassGen(byte[] classData, String classFName) throws ClassFormatException, IOException { + // verify that + ClassParser cParser = new ClassParser(new ByteArrayInputStream(classData), classFName); + JavaClass javaClass = cParser.parse(); + ClassGen cGen = new ClassGen(javaClass); + return cGen; + } + + private static void assertInstructionOpcode(int opcode, InstructionHandle handle) { + assertEquals(opcode, handle.getInstruction().getOpcode()); + } + + public void testInstrumentation(Class testClass) throws Exception { + InjectedObjectAccessInstrumentor toTestInstrumentor + = new InjectedObjectAccessInstrumentor(); + JavaClass jClass = null; + try { + jClass = Repository.lookupClass(testClass); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + byte[] result = null; + result = toTestInstrumentor.instrument(jClass.getClassName(), jClass.getBytes()); + assert(result != null); + + // verify that NEW, GETFIELD and PUTFIELD is instrumented corretly + ClassGen cGen = getClassGen(result, jClass.getClassName()); + for (Method method : cGen.getMethods()) { + Code code = method.getCode(); + InstructionList iList = new InstructionList(code.getCode()); + for (InstructionHandle iHandle : iList) { + // assert that dup is called after + invoke + if (iHandle.getInstruction().getOpcode() == Opcode.NEW) { + InstructionHandle nextInstructionHandle = iHandle.getNext(); + assertNotNull(nextInstructionHandle); + assertTrue("Should dup new object", + nextInstructionHandle.getInstruction().getOpcode() == Opcode.DUP); + InstructionHandle nextNextInstructionHandle = nextInstructionHandle.getNext(); + assertTrue("Should invoke after dup", + nextNextInstructionHandle.getInstruction().getOpcode() == Opcode.INVOKESTATIC); + // should invoke method "_onNewObject + INVOKESTATIC invokestatic = (INVOKESTATIC) nextNextInstructionHandle.getInstruction(); + assertEquals(invokestatic.getClassName(cGen.getConstantPool()), + InjectedObjectAccessInstrumentor.class.getName()); + + continue; + } + + if (iHandle.getInstruction().getOpcode() == Opcode.GETFIELD) { + InstructionHandle previousInstructionHandle = iHandle.getPrev(); + assertNotNull(previousInstructionHandle); + // should be invoke static + assertInstructionOpcode(Opcode.INVOKESTATIC, previousInstructionHandle); + InstructionHandle ppInstructionHandle = previousInstructionHandle.getPrev(); + assertInstructionOpcode(Opcode.LDC, ppInstructionHandle); + assertInstructionOpcode(Opcode.DUP, ppInstructionHandle.getPrev()); + continue; + } + + } + } + + + + + + } + + @Test + public void testTestClass() throws Exception { + testInstrumentation(TestClass.class); + TestClass.main(null); + } + +} diff --git a/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ParserTest.java b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ParserTest.java new file mode 100644 index 000000000..b1ceb3132 --- /dev/null +++ b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/ParserTest.java @@ -0,0 +1,22 @@ +package microbat.instrumentation.aggreplay; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.RecorderObjectId; +import microbat.instrumentation.model.id.ObjectId; + +public class ParserTest { + + + @Test + public void basicTest() { + + } +} diff --git a/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/RecordingOutputTest.java b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/RecordingOutputTest.java new file mode 100644 index 000000000..5aa2b523c --- /dev/null +++ b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/RecordingOutputTest.java @@ -0,0 +1,105 @@ +package microbat.instrumentation.aggreplay.parser; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; + +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.RecordingOutput; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.instr.aggreplay.shared.parser.MemoryLocationParser; +import microbat.instrumentation.model.generator.ObjectIdGenerator; +import microbat.instrumentation.model.generator.SharedMemoryGenerator; +import microbat.instrumentation.model.generator.SharedVariableObjectGenerator; +import microbat.instrumentation.model.id.Event; +import microbat.instrumentation.model.id.MemoryLocation; +import microbat.instrumentation.model.id.ObjectFieldMemoryLocation; +import microbat.instrumentation.model.id.ObjectId; +import microbat.instrumentation.model.id.ReadCountVector; +import microbat.instrumentation.model.id.ReadWriteAccessList; +import microbat.instrumentation.model.id.SharedMemoryLocation; +import microbat.instrumentation.model.id.ThreadId; +import microbat.instrumentation.model.storage.Storable; + +public class RecordingOutputTest { + + @Test + public void testParsingObjectFieldLocation() throws IOException { + + ObjectFieldMemoryLocation ofml = new ObjectFieldMemoryLocation("apple", new ObjectId()); + MemoryLocationParser parser = new MemoryLocationParser(); + ParseData parseData = fromStorableData(ofml); + MemoryLocation result = parser.parse(parseData); + assertEquals(ofml, result); + + } + + private ParseData fromStorableData(Storable data) throws IOException { + SharedDataParser parser = new SharedDataParser(); + String result = data.getFromStore(); + return parser.parse(new StringReader(result)).get(0); + } + + @Test + public void testParsingReadCountVector() throws IOException { + ReadCountVector rcv = new ReadCountVector(); + ObjectFieldMemoryLocation ofml = new ObjectFieldMemoryLocation("apple", new ObjectId()); + rcv.increment(0, ofml); + String data = rcv.getFromStore(); + SharedDataParser parser = new SharedDataParser(); + List parseData = parser.parse(new StringReader(data)); + ReadCountVector result = new ReadCountVector(); + assertEquals(result.parse(parseData.get(0)), rcv); + } + + @Test + public void testThreadParsing() throws IOException { + ThreadId r = new ThreadId(100); + ThreadId g = r.createChild(0); + ThreadId b = g.createChild(10); + LinkedList toTest = new LinkedList<>(); + toTest.add(r); + toTest.add(g); + toTest.add(b); + + + RecordingOutput output = new RecordingOutput( + new ReadWriteAccessList(), + toTest, + Collections.emptyList(), + Collections.>emptyMap()); + testRecordingOutputParsing(output); + + } + + private void testRecordingOutputParsing(RecordingOutput data) throws IOException { + String dump = data.getFromStore(); + StringReader values = new StringReader(dump); + List result = new SharedDataParser().parse(values); + assertEquals(data, new RecordingOutput().parse(result.get(0))); + } + + @Test + public void testRecordReadWriteOutput() { + ReadWriteAccessList rwal = new ReadWriteAccessList(); + ReadCountVector rcv = new ReadCountVector(); + ObjectFieldMemoryLocation memLocation = new ObjectFieldMemoryLocation("a", new ObjectId()); + SharedMemoryLocation smlLocation = new SharedMemoryLocation(memLocation); + rwal.add(memLocation, new Event(smlLocation), rcv); + + } + + @Test + public void testBasicRecordingOutput() { + + + } + +} diff --git a/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/SharedVariableOutputTest.java b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/SharedVariableOutputTest.java new file mode 100644 index 000000000..c7c1cc200 --- /dev/null +++ b/microbat_instrumentator/src/test/java/microbat/instrumentation/aggreplay/parser/SharedVariableOutputTest.java @@ -0,0 +1,66 @@ +package microbat.instrumentation.aggreplay.parser; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import org.junit.Test; + +import microbat.instrumentation.instr.aggreplay.output.SharedVariableOutput; +import microbat.instrumentation.instr.aggreplay.shared.ParseData; +import microbat.instrumentation.instr.aggreplay.shared.SharedDataParser; +import microbat.instrumentation.model.generator.SharedVariableObjectGenerator; + +public class SharedVariableOutputTest { + + /** + * Thread used to test, adds access to a given object in a different thread + * for a particular string + * @author Gabau + * + */ + private static class BasicIncrement extends Thread { + Object testObject; + SharedVariableObjectGenerator objGenerator; + String fieldName; + + protected BasicIncrement(Object testObject, + SharedVariableObjectGenerator objGenerator, String fieldName) { + this.testObject = testObject; + this.objGenerator = objGenerator; + this.fieldName = fieldName; + } + + @Override + public void run() { + this.objGenerator + .getId(testObject) + .addAccess(Thread.currentThread() + .getId(), fieldName); + } + + public void startUntilStop() throws InterruptedException { + this.start(); + this.join(); + } + + } + + @Test + public void testSimpleObjectGeneration() throws IOException, InterruptedException { + SharedVariableObjectGenerator generator = new SharedVariableObjectGenerator(); + generator.createId(generator); + SharedVariableOutput output = new SharedVariableOutput(generator); + String result = output.getFromStore(); + BasicIncrement increment1 = new BasicIncrement(generator, generator, "field"); + BasicIncrement increment2 = new BasicIncrement(generator, generator, "field"); + increment1.startUntilStop(); + increment2.startUntilStop(); + List data = new SharedDataParser().parse(new StringReader(result)); + SharedVariableOutput newOutput = new SharedVariableOutput(data.get(0)); + assertEquals(newOutput.getObjects(), output.getObjects()); + } + +} diff --git a/microbat_instrumentator/src/test/java/microbat/tools/JarPackageTool.java b/microbat_instrumentator/src/test/java/microbat/tools/JarPackageTool.java index 627d6a18c..f76f1a205 100644 --- a/microbat_instrumentator/src/test/java/microbat/tools/JarPackageTool.java +++ b/microbat_instrumentator/src/test/java/microbat/tools/JarPackageTool.java @@ -63,7 +63,7 @@ public static void main(String[] args) throws Exception { .append("microbat"); vmRunner.startAndWaitUntilStop(cmd.toCollection()); cmd.clear(); - + /* export instrumentor.jar */ cmd.append(TestConfiguration.getJavaHome() + "/bin/jar") .append("cfm") diff --git a/microbat_test_runner/src/microbat/evaluation/factory/MicroBatTestRunnerFactory.java b/microbat_test_runner/src/microbat/evaluation/factory/MicroBatTestRunnerFactory.java index ded12e035..345725430 100644 --- a/microbat_test_runner/src/microbat/evaluation/factory/MicroBatTestRunnerFactory.java +++ b/microbat_test_runner/src/microbat/evaluation/factory/MicroBatTestRunnerFactory.java @@ -7,9 +7,19 @@ // Decides which test runner to use e.g. junit 4/5, junit 3, testng public class MicroBatTestRunnerFactory { + + private boolean forceJunit3Or4 = false; + + public MicroBatTestRunnerFactory(boolean forceJunit3Or4) { + this.forceJunit3Or4 = forceJunit3Or4; + } + public TestRunner create(String className, String methodName) { JUnitTestFinder junitTestFinder = new JUnitTestFinder(); - boolean isJUnit3Or4Test = junitTestFinder.junit3Or4TestExists(className, methodName); + boolean isJUnit3Or4Test = true; + if (!forceJunit3Or4) { + isJUnit3Or4Test = junitTestFinder.junit3Or4TestExists(className, methodName); + } if (isJUnit3Or4Test) { return new MicroBatJUnit3And4TestRunner(); } diff --git a/microbat_test_runner/src/microbat/evaluation/junit/MicroBatTestRunner.java b/microbat_test_runner/src/microbat/evaluation/junit/MicroBatTestRunner.java index bdca3f0d8..5a92844cc 100644 --- a/microbat_test_runner/src/microbat/evaluation/junit/MicroBatTestRunner.java +++ b/microbat_test_runner/src/microbat/evaluation/junit/MicroBatTestRunner.java @@ -8,7 +8,12 @@ public class MicroBatTestRunner { public static void main(String[] args){ String className = args[0]; String methodName = args[1]; - MicroBatTestRunnerFactory testRunnerFactory = new MicroBatTestRunnerFactory(); + boolean forceJunit3Or4 = false; + if (args.length > 2) { + forceJunit3Or4 = true; + } + + MicroBatTestRunnerFactory testRunnerFactory = new MicroBatTestRunnerFactory(forceJunit3Or4); TestRunner testRunner = testRunnerFactory.create(className, methodName); testRunner.runTest(className, methodName); }