From 76f7cd9a3ec168cd83b7c5a38f2396789a4ca12a Mon Sep 17 00:00:00 2001 From: "Joanna Cecilia Da Silva Santos (RIT Student)" Date: Sun, 11 Apr 2021 19:42:54 -0400 Subject: [PATCH] Source code for salsa --- .gitignore | 138 +++++ salsa-src/README.md | 19 + salsa-src/pom.xml | 133 +++++ salsa-src/src/main/java/Salsa.java | 255 ++++++++ ...AbstractSerializationCallGraphBuilder.java | 152 +++++ .../AbstractSerializationHandler.java | 45 ++ .../analysis/PointerAnalysisPolicy.java | 44 ++ .../analysis/SerializationPointsToSolver.java | 70 +++ .../analysis/salsa/FakeInstanceKey.java | 55 ++ .../salsa/SalsaContextInterpreter.java | 91 +++ .../salsa/SalsaDelegatingContextSelector.java | 68 +++ .../SalsaDelegatingInstanceKeyFactory.java | 95 +++ .../SalsaDelegatingSSAContextInterpreter.java | 145 +++++ .../salsa/SalsaNCFACallGraphBuilder.java | 126 ++++ .../SalsaSSAPropagationCallGraphBuilder.java | 369 ++++++++++++ .../salsa/SalsaZeroXCallGraphBuilder.java | 83 +++ .../SalsaZeroXContainerCallGraphBuilder.java | 54 ++ .../salsa/UnsoundSerializationHandler.java | 328 ++++++++++ .../dispatcher/DefaultDispatcher.java | 30 + .../callgraph/dispatcher/IDispatcher.java | 27 + .../dispatcher/SerializationDispatcher.java | 89 +++ .../UnsoundSerializationDispatcher.java | 22 + .../callgraph/model/AbstractClassModel.java | 221 +++++++ .../design/callgraph/model/MethodModel.java | 511 ++++++++++++++++ .../model/ObjectInputStreamModel.java | 37 ++ .../model/ObjectOutputStreamModel.java | 29 + .../serializer/DotCallGraphSerializer.java | 30 + .../serializer/ICallGraphSerializer.java | 16 + .../serializer/JDynCallGraphSerializer.java | 47 ++ .../serializer/JavaCallGraphSerializer.java | 53 ++ .../serializer/JsonJcgSerializer.java | 145 +++++ .../design/callgraph/util/AnalysisUtils.java | 53 ++ .../se/design/callgraph/util/ModelUtils.java | 46 ++ .../se/design/callgraph/util/NameUtils.java | 30 + .../callgraph/util/SerializationUtils.java | 170 ++++++ .../design/callgraph/util/TypeCategory.java | 12 + .../resources/Java60RegressionExclusions.txt | 18 + .../ObjectInputStreamModel.java.model | 559 ++++++++++++++++++ .../main/resources/exclusions+util+thread.txt | 38 ++ .../resources/exclusions-with-servlets.txt | 39 ++ salsa-src/src/main/resources/exclusions.txt | 27 + salsa-src/src/test/java/SalsaCliTest.java | 13 + .../salsa/rq1/soundness/JCGTest.java | 181 ++++++ .../rq3/performance/PerformanceTest.java | 188 ++++++ .../evaluation/utils/AbstractCatsTest.java | 158 +++++ .../evaluation/utils/CATSTestCases.java | 33 ++ .../evaluation/utils/TestDataSets.java | 78 +++ .../evaluation/utils/TestUtilities.java | 100 ++++ .../evaluation/utils/XCorpusTestCases.java | 43 ++ .../se/design/callgraph/examples/Demo.java | 64 ++ .../callgraph/examples/PaperExample.java | 78 +++ 51 files changed, 5455 insertions(+) create mode 100644 .gitignore create mode 100644 salsa-src/README.md create mode 100644 salsa-src/pom.xml create mode 100644 salsa-src/src/main/java/Salsa.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationCallGraphBuilder.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationHandler.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/PointerAnalysisPolicy.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/SerializationPointsToSolver.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/FakeInstanceKey.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaContextInterpreter.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingContextSelector.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingInstanceKeyFactory.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingSSAContextInterpreter.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaNCFACallGraphBuilder.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaSSAPropagationCallGraphBuilder.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXCallGraphBuilder.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXContainerCallGraphBuilder.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/UnsoundSerializationHandler.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/DefaultDispatcher.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/IDispatcher.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/SerializationDispatcher.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/UnsoundSerializationDispatcher.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/model/AbstractClassModel.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/model/MethodModel.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectInputStreamModel.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectOutputStreamModel.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/DotCallGraphSerializer.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/ICallGraphSerializer.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JDynCallGraphSerializer.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JavaCallGraphSerializer.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JsonJcgSerializer.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/util/AnalysisUtils.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/util/ModelUtils.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/util/NameUtils.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/util/SerializationUtils.java create mode 100644 salsa-src/src/main/java/edu/rit/se/design/callgraph/util/TypeCategory.java create mode 100644 salsa-src/src/main/resources/Java60RegressionExclusions.txt create mode 100644 salsa-src/src/main/resources/ObjectInputStreamModel.java.model create mode 100644 salsa-src/src/main/resources/exclusions+util+thread.txt create mode 100644 salsa-src/src/main/resources/exclusions-with-servlets.txt create mode 100644 salsa-src/src/main/resources/exclusions.txt create mode 100644 salsa-src/src/test/java/SalsaCliTest.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq1/soundness/JCGTest.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq3/performance/PerformanceTest.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/AbstractCatsTest.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/CATSTestCases.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestDataSets.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestUtilities.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/XCorpusTestCases.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/Demo.java create mode 100644 salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/PaperExample.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90c3596 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# OS generated files # +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# weird Icon\r that gets created on mac os +Icon + +############################################################ +#### CONFIGURATION FOR IGNORING INTELLIJ-SPECIFIC FILES #### +############################################################ + +# Source: https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser +# Ignores Icon? files that get created on Mac OS +Icon + + + + + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + diff --git a/salsa-src/README.md b/salsa-src/README.md new file mode 100644 index 0000000..33335f9 --- /dev/null +++ b/salsa-src/README.md @@ -0,0 +1,19 @@ +# Salsa + +Source code for **SALSA** (**S**tatic **A**na**L**ysis for **S**eri**A**lization). +It improves existing off-the-shelf pointer analysis to support the construction of callgraph that handles programs with serialization/deserialization. + + +## Requirements + +* Maven 3.x +* Java 1.8 + +## Built With +* [WALA](https://github.com/WALA/wala) + +## Questions, comments, or feedback? +* For Bugs/Questions: *File a bug report.* +* Improvement feedback: please contact *Joanna C. S. Santos* (`@joannacss` - Bitbucket / `@joannacss` - GitHub) + +### Usage diff --git a/salsa-src/pom.xml b/salsa-src/pom.xml new file mode 100644 index 0000000..e28cf4e --- /dev/null +++ b/salsa-src/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + edu.rit.se.design + salsa + 0.2 + + salsa + http://design.se.rit.edu + + + UTF-8 + 1.8 + 1.8 + + + + + edu.rit.se.design + dodo-utils + 0.2 + + + + + com.google.code.gson + gson + 2.8.5 + compile + + + + + com.ibm.wala + com.ibm.wala.core + 1.5.4 + + + com.ibm.wala + com.ibm.wala.util + 1.5.4 + jar + + + com.ibm.wala + com.ibm.wala.cast + 1.5.4 + + + com.ibm.wala + com.ibm.wala.shrike + 1.5.4 + + + com.ibm.wala + com.ibm.wala.cast.java + 1.5.4 + + + com.ibm.wala + com.ibm.wala.dalvik + 1.5.4 + + + com.ibm.wala + com.ibm.wala.cast.java.ecj + 1.5.4 + + + + + org.apache.commons + commons-lang3 + 3.7 + jar + + + + + commons-cli + commons-cli + 1.4 + + + + + commons-io + commons-io + 2.7 + + + + + + org.junit.platform + junit-platform-launcher + 1.7.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.7.1 + test + + + org.junit.vintage + junit-vintage-engine + 5.7.1 + test + + + + + org.junit.jupiter + junit-jupiter-api + 5.7.1 + test + + + + org.javassist + javassist + 3.27.0-GA + + + + + + + diff --git a/salsa-src/src/main/java/Salsa.java b/salsa-src/src/main/java/Salsa.java new file mode 100644 index 0000000..8035525 --- /dev/null +++ b/salsa-src/src/main/java/Salsa.java @@ -0,0 +1,255 @@ +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.propagation.PointerKey; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.util.MonitorUtil; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.analysis.salsa.SalsaSSAPropagationCallGraphBuilder; +import edu.rit.se.design.callgraph.analysis.salsa.SalsaZeroXCallGraphBuilder; +import edu.rit.se.design.callgraph.model.MethodModel; +import edu.rit.se.design.callgraph.serializer.DotCallGraphSerializer; +import edu.rit.se.design.callgraph.serializer.JsonJcgSerializer; +import edu.rit.se.design.dodo.utils.debug.DodoLogger; +import edu.rit.se.design.dodo.utils.viz.ProjectAnalysisViewer; +import org.apache.commons.cli.*; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.*; +import static edu.rit.se.design.callgraph.util.AnalysisUtils.*; + +/** + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class Salsa { + + // argument names + public static final String PRINT_MODELS = "print-models"; + public static final String VIEW_UI = "view-ui"; + public static final String EXCLUSIONS = "exclusions"; + public static final String FORMAT = "format"; + public static final String OUTPUT = "output"; + public static final String JAR = "jar"; + public static final String ANALYSIS = "analysis"; // possible values: x-CFA, 0-x-CFA, or 0-x-Container-CFA + // default values for args + public static final String DEFAULT_EXCLUSIONS_FILE = "exclusions.txt"; + // for parsing the PA algorithm + private static final Pattern p = Pattern.compile("((\\d+)-CFA)|(0-(\\d+)-CFA)|(0-(\\d+)-Container-CFA)"); + + public static void main( String[] args) throws IOException, ClassHierarchyException, CallGraphBuilderCancelException { + Logger logger = DodoLogger.getLogger(Salsa.class, false); + + CommandLine cmd = setUpCommandLine(Salsa.class, args); + String jarFilePath = cmd.getOptionValue(JAR); + String format = cmd.getOptionValue(FORMAT); + File outputFile = new File(cmd.getOptionValue(OUTPUT)); + String exclusions = cmd.hasOption(EXCLUSIONS) ? cmd.getOptionValue(EXCLUSIONS) : Salsa.class.getClassLoader().getResource(DEFAULT_EXCLUSIONS_FILE).toString(); + boolean showUi = cmd.hasOption(VIEW_UI); + boolean printMethods = cmd.hasOption(PRINT_MODELS); + PointerAnalysisPolicy paPolicy = parsePointerAnalysisPolicy(cmd.getOptionValue(ANALYSIS)); + + + long start = System.currentTimeMillis(); + logger.info("Starting analysis"); + logger.info("\tJar: " + jarFilePath); + logger.info("\tOutput: " + outputFile); + logger.info("\tFormat: " + format); + logger.info("\t2nd Policy: " + paPolicy); +// logger.info("\tAlgorithm: " + useTaintBasedAlgorithm); + + + // Basic Variables + File exclusionFile = new File(exclusions); + AnalysisScope scope = makeAnalysisScope(jarFilePath, exclusionFile); + IClassHierarchy cha = makeIClassHierarchy(scope); + AnalysisOptions options = makeAnalysisOptions(scope, cha); + AnalysisCache cache = makeAnalysisCache(); + + // call graph construction + CallGraphBuilder builder = SalsaZeroXCallGraphBuilder.make(scope, options, cache, cha, paPolicy); + CallGraph cg = (CallGraph) builder.makeCallGraph(options, new CustomMonitor(logger)); + long end = System.currentTimeMillis(); + + logger.info("Call graph computed in " + ((end - start) / 1000L) + " seconds"); + + // Visualize call graph in Java Swing + if (showUi) { + Set deserializedObjects = + ((SalsaSSAPropagationCallGraphBuilder) builder).getDeserializedObjects(); + new ProjectAnalysisViewer(cg, deserializedObjects, false).setTitle(jarFilePath); + } + // Prints synthetic methods created by Salsa + if (printMethods) { + for (CGNode cgNode : cg) { + if (cgNode.getMethod() instanceof MethodModel) { + System.out.println(cgNode); + String[] lines = cgNode.getIR().toString().split("Instructions:\n")[1].split("\n"); + for (String line : lines) { + if (line.startsWith("BB")) continue; + System.out.println("\t" + line); + } + } + } + } + + + // saving results + switch (OutputFormat.valueOf(format.toUpperCase())) { + case DOT: + new DotCallGraphSerializer().save(cg, outputFile); + break; + case JSON: + new JsonJcgSerializer().save(cg, outputFile); + } + } + + + private static PointerAnalysisPolicy parsePointerAnalysisPolicy(String analysis) { + Matcher matcher = p.matcher(analysis.trim()); + if (!matcher.find()) throw new IllegalArgumentException("Unknown analysis policy " + analysis); + + + if (analysis.matches("\\d+-CFA")) + return new PointerAnalysisPolicy(nCFA, Integer.valueOf(matcher.group(2))); + + if (analysis.matches("0-\\d+-CFA")) + return new PointerAnalysisPolicy(ZeroXCFA, Integer.valueOf(matcher.group(4))); + + if (analysis.matches("0-\\d+-Containter-CFA")) + return new PointerAnalysisPolicy(ZeroXContainerCFA, Integer.valueOf(matcher.group(6))); + + + throw new IllegalArgumentException("Unknown analysis policy " + analysis); + + } + + /** + * Set ups the options for the CLI + * + * @param args program arguments + * @return a {@link CommandLine} instance for retrieving the program args + */ + private static CommandLine setUpCommandLine(Class cliClass, String[] args) { + + + Option jar = new Option("j", JAR, true, "Path to the project's JAR file"); + jar.setRequired(true); + + Option output = new Option(OUTPUT.substring(0, 1), OUTPUT, true, "Path to the output file with the serialized call graph"); + output.setRequired(true); + + Option formatOpt = new Option(FORMAT.substring(0, 1), FORMAT, true, "Output format (possible values: json, csv [default = json])"); + formatOpt.setType(OutputFormat.class); + formatOpt.setRequired(true); + + + Option exclusionFile = new Option(EXCLUSIONS.substring(0, 1), EXCLUSIONS, true, "Path to the exclusions file"); + exclusionFile.setRequired(false); + + + Option viewUi = new Option(null, VIEW_UI, false, "Shows call graph in a Java Swing UI"); + viewUi.setRequired(false); + + Option printMethodModels = new Option(null, PRINT_MODELS, false, "Prints to the console all the synthetic methods created"); + printMethodModels.setRequired(false); + + + Option paPolicy = new Option(ANALYSIS.substring(0, 1), ANALYSIS, true, "Pointer analysis choice (n-CFA, 0-n-CFA, 0-n-Container-CFA)"); + paPolicy.setRequired(true); + + + DefaultParser parser = new DefaultParser(); + Options options = new Options(); + + options.addOption(jar); + options.addOption(output); + options.addOption(formatOpt); + options.addOption(viewUi); + options.addOption(printMethodModels); + options.addOption(exclusionFile); + options.addOption(paPolicy); + + + try { + if (args.length == 0) showUsageAndExit(cliClass, options); + return parser.parse(options, args); + } catch (ParseException ex) { + System.err.println(ex.getMessage()); + showUsageAndExit(cliClass, options); + return null; + } + } + + /** + * Simply prints a help menu for using this command line interface. + * + * @param options the options used to set up the command line + */ + private static void showUsageAndExit(Class cliClass, Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(cliClass.getSimpleName(), options); + System.exit(-1); + } + + public enum OutputFormat { + JSON, DOT + } + + /** + * Simple monitor that prints out in the console what is going on. + */ + private static class CustomMonitor implements MonitorUtil.IProgressMonitor { + private final Logger logger; + private boolean isCanceled = false; + private int taskNo; + + private CustomMonitor(Logger logger) { + this.logger = logger; + } + + + @Override + public void beginTask(String s, int i) { + logger.info("BEGIN TASK #" + i + ": " + s); + this.taskNo = i; + } + + @Override + public void subTask(String s) { + logger.info("SUBTASK " + s); + } + + @Override + public void cancel() { + logger.info("CANCEL"); + isCanceled = true; + } + + @Override + public boolean isCanceled() { + return isCanceled; + } + + @Override + public void done() { + logger.info("DONE"); + } + + @Override + public void worked(int i) { + logger.info(String.format("\tWORKED %d.%d", taskNo, i)); + } + + @Override + public String getCancelMessage() { + return "Error happened"; + } + } + + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationCallGraphBuilder.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationCallGraphBuilder.java new file mode 100644 index 0000000..4713ce2 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationCallGraphBuilder.java @@ -0,0 +1,152 @@ +package edu.rit.se.design.callgraph.analysis; + +import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; +import com.ibm.wala.ipa.callgraph.propagation.PointerKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.SSAPropagationCallGraphBuilder; +import com.ibm.wala.ipa.callgraph.propagation.cfa.CallerSiteContext; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; +import com.ibm.wala.types.Selector; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.CancelException; +import edu.rit.se.design.callgraph.model.AbstractClassModel; +import edu.rit.se.design.callgraph.model.ObjectInputStreamModel; +import edu.rit.se.design.callgraph.model.ObjectOutputStreamModel; +import edu.rit.se.design.dodo.utils.wala.WalaUtils; +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.HashSet; +import java.util.Set; + +import static edu.rit.se.design.callgraph.util.NameUtils.*; + +public class AbstractSerializationCallGraphBuilder extends SSAPropagationCallGraphBuilder { + + /** + * constant for using when getting the "this" pointer in an instruction + */ + protected static final int THIS_POINTER = 1; + /** + * enable for printing statements during call graph construction and visitor + */ + protected static boolean DEBUG_SALSA_CG = false; + protected static boolean DEBUG_SALSA_VISITOR = DEBUG_SALSA_CG | false; + /** + * models for the ObjectInputStream and ObjectOutputStream classes + */ + protected final ObjectInputStreamModel objectInputStreamModel; + protected final ObjectOutputStreamModel objectOutputStreamModel; + + /** + * Set of model nodes to check against + */ + protected final Set models; + + + /** + * Caching results + */ + protected final IAnalysisCacheView cache; + + + /** + * worklist for serialization and deserialization: (caller, invoke instruction, target) + */ + protected final Set> serializationWorkList; + protected final Set> deserializationWorkList; + + /** + * The types of the primary(untainted) propagation graph and secondary(tainted) propagation type + */ + protected final PointerAnalysisPolicy defaultPaPolicy; + protected final PointerAnalysisPolicy taintedPaPolicy; + + protected AbstractSerializationCallGraphBuilder(IMethod abstractRootMethod, + AnalysisOptions options, + IAnalysisCacheView cache, + PointerKeyFactory pointerKeyFactory, + PointerAnalysisPolicy defaultPaPolicy, + PointerAnalysisPolicy taintedPaPolicy) { + super(abstractRootMethod, options, cache, pointerKeyFactory); + this.models = new HashSet<>(); + this.cache = cache; + this.defaultPaPolicy = defaultPaPolicy; + this.taintedPaPolicy = taintedPaPolicy; + this.deserializationWorkList = new HashSet<>(); + this.serializationWorkList = new HashSet<>(); + this.objectInputStreamModel = new ObjectInputStreamModel(cha, options, cache); + this.objectOutputStreamModel = new ObjectOutputStreamModel(cha, options, cache); + } + + + public Set> getSerializationWorkList() { + return serializationWorkList; + } + + public Set> getDeserializationWorkList() { + return deserializationWorkList; + } + + /** + * @param caller the caller node + * @param site + * @param recv + * @param iKey an abstraction of the receiver of the call (or null if not applicable) + * @return the CGNode to which this particular call should dispatch. + */ + @Override + protected CGNode getTargetForCall(CGNode caller, CallSiteReference site, IClass recv, InstanceKey[] iKey) { + CGNode target = super.getTargetForCall(caller, site, recv, iKey); + try { + // verifies whether the resolved target should be replaced by our model + target = replaceIfNeeded(caller, site, target); + } catch (CancelException e) { + target = null; + e.printStackTrace(); + } + return target; + } + + + /** + * Verifies whether the call target should be replaced by our models for {@link java.io.ObjectInputStream} and {@link java.io.ObjectOutputStream} classes. + * Only replaces for call sites at the application scope. + * + * @param caller the caller of the invocation + * @param target the cg node target of an invocation + * @return returns the param target as is or a synthetic node to our our models for {@link java.io.ObjectInputStream} and {@link java.io.ObjectOutputStream} classes in case the target has to be replaced. + */ + private CGNode replaceIfNeeded(CGNode caller, CallSiteReference site, CGNode target) throws CancelException { + if (caller == null || target == null) return target; + + IMethod targetMethod = target.getMethod(); + TypeReference typeRef = targetMethod.getDeclaringClass().getReference(); + Selector targetSelector = targetMethod.getSelector(); + if (WalaUtils.isApplicationScope(caller)) { + if ((typeRef.equals(JavaIoObjectInputStream) && targetSelector.equals(readObjectSelector)) || + (typeRef.equals(JavaIoObjectOutputStream) && targetSelector.equals(writeObjectSelector))) { + AbstractClassModel model = typeRef.equals(JavaIoObjectInputStream) ? objectInputStreamModel : objectOutputStreamModel; + Set> worklist = typeRef.equals(JavaIoObjectInputStream) ? deserializationWorkList : serializationWorkList; + CGNode newTarget = callGraph.findOrCreateNode(model.getMethod(targetSelector), new CallerSiteContext(caller, site)); + for (SSAAbstractInvokeInstruction call : caller.getIR().getCalls(site)) { + worklist.add(new ImmutableTriple<>(caller, call, newTarget)); + } + if (DEBUG_SALSA_CG) System.out.println("Replacing " + target + " to " + newTarget); + + // adds to the set of model methods + models.add(newTarget); + + // invalidate previous cache + callGraph.getAnalysisCache().invalidate(targetMethod, target.getContext()); + return newTarget; + } + } + return target; + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationHandler.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationHandler.java new file mode 100644 index 0000000..d7f8c99 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/AbstractSerializationHandler.java @@ -0,0 +1,45 @@ +package edu.rit.se.design.callgraph.analysis; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.ipa.callgraph.propagation.SSAPropagationCallGraphBuilder; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.MonitorUtil; + +import java.util.HashSet; +import java.util.Set; + +/** + * Abstract class for the concrete implementations for how serialization should be handled in terms of points-to analysis. + * + * @author Joanna C. S. Santos + */ +public abstract class AbstractSerializationHandler { + protected final SSAPropagationCallGraphBuilder builder; + protected final Set serializableClasses; + + public AbstractSerializationHandler(SSAPropagationCallGraphBuilder builder) { + this.builder = builder; + this.serializableClasses = computeSerializableClasses(); + } + + public abstract void handleSerializationRelatedFeatures(MonitorUtil.IProgressMonitor monitor); + + /** + * Computes the set of serializable classes. + */ + + private Set computeSerializableClasses() { + IClassHierarchy cha = builder.getClassHierarchy(); + Set classes = new HashSet<>(); + IClass serialInterface = cha.lookupClass(TypeReference.JavaIoSerializable); + // iterates all classes in the classpath to compute what is serializable or not + for (IClass c : cha) { + if (cha.implementsInterface(c, serialInterface)) { + classes.add(c); + } + } + + return classes; + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/PointerAnalysisPolicy.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/PointerAnalysisPolicy.java new file mode 100644 index 0000000..5281e6c --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/PointerAnalysisPolicy.java @@ -0,0 +1,44 @@ +package edu.rit.se.design.callgraph.analysis; + +/** + * Enumeration that selects which propagation algorithm to use. + * + * @author Joanna C. S. Santos + * @author Reese A. Jones + */ +public class PointerAnalysisPolicy { + public PolicyType policyType; + /** + * a value for the n or x in nCFA, ZeroXCFA, ZeroXContainerCFA + */ + public int policyNumber; + /** + * @param policyNumber the bound n in the algorithms: n-CFA, 0-n-CFA, 0-n-Container-CFA. + * @param policyType the pointer analysis method (n-CFA, 0-n-CFA, 0-n-Container-CFA) + */ + public PointerAnalysisPolicy(PolicyType policyType, int policyNumber) { + this.policyType = policyType; + this.policyNumber = policyNumber; + } + + @Override + public String toString() { + switch (policyType) { + case nCFA: + return String.format("%d-CFA", policyNumber); + case ZeroXCFA: + return String.format("0-%d-CFA", policyNumber); + case ZeroXContainerCFA: + return String.format("0-%d-Container-CFA", policyNumber); + } + throw new IllegalArgumentException("Unknown value " + this); + } + + + public enum PolicyType { + nCFA, + ZeroXCFA, + ZeroXContainerCFA; + } +} + diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/SerializationPointsToSolver.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/SerializationPointsToSolver.java new file mode 100644 index 0000000..47bf3b0 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/SerializationPointsToSolver.java @@ -0,0 +1,70 @@ +package edu.rit.se.design.callgraph.analysis; + +import com.ibm.wala.fixedpoint.impl.AbstractFixedPointSolver; +import com.ibm.wala.fixedpoint.impl.Worklist; +import com.ibm.wala.ipa.callgraph.propagation.AbstractPointsToSolver; +import com.ibm.wala.ipa.callgraph.propagation.IPointsToSolver; +import com.ibm.wala.ipa.callgraph.propagation.PropagationSystem; +import com.ibm.wala.ipa.callgraph.propagation.SSAPropagationCallGraphBuilder; +import com.ibm.wala.util.CancelException; +import com.ibm.wala.util.MonitorUtil; + +import java.lang.reflect.Field; + + +/** + * A fixed-point iterative solver for performing pointer analysis on programs that uses serialization features. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class SerializationPointsToSolver extends AbstractPointsToSolver { + private IPointsToSolver delegate; + private AbstractSerializationHandler serializationHandler; + private int i = 1; // to mark the iterations for this subtask related to serialization support + private int delta = 0; // to mark how many extra elements were added by Salsa to the worklist + + public SerializationPointsToSolver(PropagationSystem system, SSAPropagationCallGraphBuilder builder, AbstractSerializationHandler serializationHandler, IPointsToSolver delegate) { + super(system, builder); + this.delegate = delegate; + this.serializationHandler = serializationHandler; + } + + @Override + public void solve(MonitorUtil.IProgressMonitor monitor) throws IllegalArgumentException, CancelException { + + + do { + if (monitor != null) monitor.beginTask("Points-to analysis", i); + // ensures that all is computed normally + this.delegate.solve(monitor); + int before = getWorkListSize(); + // adding constraints from newly (synthetic) nodes that replaced calls to ObjectInputStream + if (monitor != null) monitor.subTask("Serialization-related Features"); + serializationHandler.handleSerializationRelatedFeatures(monitor); + if (monitor != null) monitor.worked(i++); + delta += (getWorkListSize() - before); + } while (!getSystem().emptyWorkList()); + + if (monitor != null) { + monitor.subTask("SerializationPointsToSolver::extra iterations= " + i); + monitor.subTask("SerializationPointsToSolver::delta= " + delta); + } + } + + + private int getWorkListSize() { + try { + Field privateField = AbstractFixedPointSolver.class.getDeclaredField("workList"); + privateField.setAccessible(true); + + Worklist list = (Worklist) privateField.get(getSystem()); + + return list.size(); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + + } + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/FakeInstanceKey.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/FakeInstanceKey.java new file mode 100644 index 0000000..556c7c7 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/FakeInstanceKey.java @@ -0,0 +1,55 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.NewSiteReference; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.debug.Assertions; + +import java.util.Iterator; +import java.util.Objects; + +/** + * To represent a "fake" instance key (synthetic). + * This is used to instantiate tainted pointers for creating suitable call graphs for serialization/deserialization analysis. + * Joanna C. S. Santos (jds5109@rit.edu) + */ +public class FakeInstanceKey implements InstanceKey { + + private IClass serializableType; + + public FakeInstanceKey(IClass serializableType) { + this.serializableType = serializableType; + } + + @Override + public IClass getConcreteType() { + return this.serializableType; + } + + @Override + public String toString() { + return "FakeInstanceKey{" + serializableType + "}"; + } + + @Override + public Iterator> getCreationSites(CallGraph CG) { + Assertions.UNREACHABLE("You shouldn't be calling this because this variable had no previous allocation (synthetic)"); + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FakeInstanceKey that = (FakeInstanceKey) o; + return Objects.equals(serializableType, that.serializableType); + } + + @Override + public int hashCode() { + return Objects.hash(serializableType); + } +} \ No newline at end of file diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaContextInterpreter.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaContextInterpreter.java new file mode 100644 index 0000000..b2c7136 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaContextInterpreter.java @@ -0,0 +1,91 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.cfg.ControlFlowGraph; +import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.ipa.callgraph.AnalysisCache; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.callgraph.propagation.cfa.ContextInsensitiveSSAInterpreter; +import com.ibm.wala.ssa.*; +import edu.rit.se.design.callgraph.model.MethodModel; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * An object that provides an interface to local method information needed for CFA. + * This context interpreter only understands nodes whose methods are a {@link MethodModel}. + * + * @author Joanna C. S. Santos + */ +public class SalsaContextInterpreter extends ContextInsensitiveSSAInterpreter { + + public SalsaContextInterpreter(AnalysisOptions options, IAnalysisCacheView cache) { + super(options, cache); + } + + @Override + public boolean understands(CGNode node) { + return node.getMethod() instanceof MethodModel; + } + + @Override + public IR getIR(CGNode node) { + if (node == null) throw new IllegalArgumentException("node is null"); + return getAnalysisCache().getIR(node.getMethod(), node.getContext()); + } + + @Override + public IRView getIRView(CGNode node) { + return getIR(node); + } + + @Override + public int getNumberOfStatements(CGNode node) { + IR ir = getIR(node); + return (ir == null) ? -1 : ir.getInstructions().length; + } + + @Override + public boolean recordFactoryType(CGNode node, IClass klass) { + return false; + } + + @Override + public ControlFlowGraph getCFG(CGNode N) { + IR ir = getIR(N); + return ir == null ? null : ir.getControlFlowGraph(); + } + + @Override + public DefUse getDU(CGNode node) { + if (node == null) { + throw new IllegalArgumentException("node is null"); + } + IR ir = getAnalysisCache().getIR(node.getMethod(), node.getContext()); + return ((AnalysisCache) getAnalysisCache()).getSSACache().findOrCreateDU(ir, node.getContext()); + } + + @Override + public Iterator iterateCallSites(CGNode node) { + MethodModel sm = (MethodModel) node.getMethod(); + SSAInstruction[] statements = sm.getStatements(node.getContext()); + List result = new LinkedList<>(); + SSAInstruction.Visitor v = + new SSAInstruction.Visitor() { + @Override + public void visitInvoke(SSAInvokeInstruction instruction) { + result.add(instruction.getCallSite()); + } + }; + for (SSAInstruction s : statements) { + if (s != null) { + s.visit(v); + } + } + return result.iterator(); + } +} \ No newline at end of file diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingContextSelector.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingContextSelector.java new file mode 100644 index 0000000..285dfdd --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingContextSelector.java @@ -0,0 +1,68 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + + +import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.Context; +import com.ibm.wala.ipa.callgraph.ContextSelector; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; +import com.ibm.wala.util.intset.IntSet; + + +/** + * It controls the context selection policy via delegation. + * + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + * @see ContextSelector + */ +public class SalsaDelegatingContextSelector implements ContextSelector { + + private ContextSelector A; + private ContextSelector B; + private SalsaSSAPropagationCallGraphBuilder builder; + + public SalsaDelegatingContextSelector( + SalsaSSAPropagationCallGraphBuilder builder, + ContextSelector A, + ContextSelector B) { + + if (A == null) + throw new IllegalArgumentException("Untainted ContextInterpreter for ContextInterpreter delegation is null"); + if (B == null) + throw new IllegalArgumentException("Tainted ContextInterpreter for ContextInterpreter delegation is null"); + if (builder == null) + throw new IllegalArgumentException("Builder for ContextInterpreter delegation is null"); + + this.builder = builder; + this.A = A; + this.B = B; + } + + /** + * Delegation-based implementation. + * If the caller is + * + * @param caller the node making the invocation + * @param site callsite + * @param callee the node corresponding to the invocation target + * @param actualParameters the passes instance types + * @return the {@link Context} to be used in this call. + */ + @Override + public Context getCalleeTarget(CGNode caller, CallSiteReference site, IMethod callee, InstanceKey[] actualParameters) { + if (!builder.isSyntheticModel(caller)) + return A.getCalleeTarget(caller, site, callee, actualParameters); + + return B.getCalleeTarget(caller, site, callee, actualParameters); + } + + @Override + public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) { + if (!builder.isSyntheticModel(caller)) + return A.getRelevantParameters(caller, site); + + return B.getRelevantParameters(caller, site); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingInstanceKeyFactory.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingInstanceKeyFactory.java new file mode 100644 index 0000000..b9d9add --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingInstanceKeyFactory.java @@ -0,0 +1,95 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.classLoader.NewSiteReference; +import com.ibm.wala.classLoader.ProgramCounter; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKeyFactory; +import com.ibm.wala.types.TypeReference; + + +/** + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class SalsaDelegatingInstanceKeyFactory implements InstanceKeyFactory { + + private SalsaSSAPropagationCallGraphBuilder builder; + private InstanceKeyFactory A; + private InstanceKeyFactory B; + + public SalsaDelegatingInstanceKeyFactory( + SalsaSSAPropagationCallGraphBuilder builder, + InstanceKeyFactory A, + InstanceKeyFactory B) { + + if (A == null) + throw new IllegalArgumentException("Untainted ContextInterpreter for ContextInterpreter delegation is null"); + if (B == null) + throw new IllegalArgumentException("Tainted ContextInterpreter for ContextInterpreter delegation is null"); + if (builder == null) + throw new IllegalArgumentException("Builder for ContextInterpreter delegation is null"); + + this.builder = builder; + this.A = A; + this.B = B; + } + + @Override + public InstanceKey getInstanceKeyForAllocation(CGNode node, NewSiteReference allocation) { + if (!builder.isSyntheticModel(node)) + return A.getInstanceKeyForAllocation(node, allocation); + + return B.getInstanceKeyForAllocation(node, allocation); + } + + @Override + public InstanceKey getInstanceKeyForMultiNewArray(CGNode node, NewSiteReference allocation, int dim) { + if (!builder.isSyntheticModel(node)) + return A.getInstanceKeyForMultiNewArray(node, allocation, dim); + + return B.getInstanceKeyForMultiNewArray(node, allocation, dim); + } + + @Override + public InstanceKey getInstanceKeyForConstant(TypeReference type, T S) { + // Currently the primary InstanceKeyFactory is used, + // since there is no way to determine which one to use + return A.getInstanceKeyForConstant(type, S); + } + + @Override + public InstanceKey getInstanceKeyForPEI(CGNode node, ProgramCounter instr, TypeReference type) { + if (!builder.isSyntheticModel(node)) + return A.getInstanceKeyForPEI(node, instr, type); + + return B.getInstanceKeyForPEI(node, instr, type); + } + + @Override + public InstanceKey getInstanceKeyForMetadataObject(Object obj, TypeReference objType) { + // Currently the primary InstanceKeyFactory is used, + // since there is no way to determine which one to use + return A.getInstanceKeyForMetadataObject(obj, objType); + } + + /** + * This method is needed for making ZeroXContainer ContextSelector. It only accepts ZeroXCFA, + * so this class cannot be used and one of its InstanceKeyFactorys must directly be used. + * + * @return + */ + public InstanceKeyFactory getPrimaryInstanceKeyFactory() { + return A; + } + + /** + * This method is needed for making ZeroXContainer ContextSelector. It only accepts ZeroXCFA, + * so this class cannot be used and one of its InstanceKeyFactorys must directly be used. + * + * @return + */ + public InstanceKeyFactory getSecondaryInstanceKeyFactory() { + return B; + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingSSAContextInterpreter.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingSSAContextInterpreter.java new file mode 100644 index 0000000..90013e5 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaDelegatingSSAContextInterpreter.java @@ -0,0 +1,145 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + + +import com.ibm.wala.cfg.ControlFlowGraph; +import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.NewSiteReference; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter; +import com.ibm.wala.ssa.*; +import com.ibm.wala.types.FieldReference; + +import java.util.Iterator; + +/** + * Interprets a method and return information needed for CFA. + * This uses a delegation pattern. If node is tainted, delegates to the A context interpreter, otherwise, it delegates to B. + * + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ + +public class SalsaDelegatingSSAContextInterpreter implements SSAContextInterpreter { + + SalsaSSAPropagationCallGraphBuilder builder; + /** + * reference to the interpreter of the underlying PA policy + */ + private SSAContextInterpreter normalInterpreter; + /** + * interpreter tailored for our model nodes (synthetic) + */ + private SSAContextInterpreter modelNodesInterpreter; + + /** + * @param builder callgraph builder + * @param normalInterpreter context interpreter for non-model nodes + * @param modelNodesInterpreter context interpreter for model nodes + */ + public SalsaDelegatingSSAContextInterpreter( + SalsaSSAPropagationCallGraphBuilder builder, + SSAContextInterpreter normalInterpreter, + SSAContextInterpreter modelNodesInterpreter) { + + if (normalInterpreter == null) + throw new IllegalArgumentException("Untainted ContextInterpreter for ContextInterpreter delegation is null"); + if (modelNodesInterpreter == null) + throw new IllegalArgumentException("Tainted ContextInterpreter for ContextInterpreter delegation is null"); + if (builder == null) + throw new IllegalArgumentException("Builder for ContextInterpreter delegation is null"); + + this.builder = builder; + this.normalInterpreter = normalInterpreter; + this.modelNodesInterpreter = modelNodesInterpreter; + } + + @Override + public Iterator iterateNewSites(CGNode node) { +// System.out.println(node); + if (!builder.isSyntheticModel(node)) + return normalInterpreter.iterateNewSites(node); + + return modelNodesInterpreter.iterateNewSites(node); + } + + @Override + public Iterator iterateFieldsRead(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.iterateFieldsRead(node); + + return modelNodesInterpreter.iterateFieldsRead(node); + } + + @Override + public Iterator iterateFieldsWritten(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.iterateFieldsWritten(node); + + return modelNodesInterpreter.iterateFieldsWritten(node); + } + + @Override + public boolean recordFactoryType(CGNode node, IClass klass) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.recordFactoryType(node, klass); + + return modelNodesInterpreter.recordFactoryType(node, klass); + } + + @Override + public boolean understands(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.understands(node); + + return modelNodesInterpreter.understands(node); + } + + @Override + public Iterator iterateCallSites(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.iterateCallSites(node); + + return modelNodesInterpreter.iterateCallSites(node); + } + + @Override + public IR getIR(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.getIR(node); + + return modelNodesInterpreter.getIR(node); + } + + @Override + public IRView getIRView(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.getIRView(node); + + return modelNodesInterpreter.getIRView(node); + } + + @Override + public DefUse getDU(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.getDU(node); + + return modelNodesInterpreter.getDU(node); + } + + @Override + public int getNumberOfStatements(CGNode node) { + if (!builder.isSyntheticModel(node)) + return normalInterpreter.getNumberOfStatements(node); + + return modelNodesInterpreter.getNumberOfStatements(node); + } + + @Override + public ControlFlowGraph getCFG(CGNode n) { + if (!builder.isSyntheticModel(n)) + return normalInterpreter.getCFG(n); + + return modelNodesInterpreter.getCFG(n); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaNCFACallGraphBuilder.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaNCFACallGraphBuilder.java new file mode 100644 index 0000000..17f47ba --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaNCFACallGraphBuilder.java @@ -0,0 +1,126 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.Language; +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.impl.Util; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.PointerKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter; +import com.ibm.wala.ipa.callgraph.propagation.cfa.DefaultPointerKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXInstanceKeys; +import com.ibm.wala.ipa.callgraph.propagation.rta.RTAContextInterpreter; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; + +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.nCFA; + + +/** + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ + +public class SalsaNCFACallGraphBuilder extends SalsaSSAPropagationCallGraphBuilder { + + // When Util.makeNCFABuilder is called the instance key factory is overridden after the original nCFA builder is created. + // This leaves us with two versions of nCFA. This boolean controls which version the tainted builder is based on. + private boolean UtilVersion; + + public SalsaNCFACallGraphBuilder( + IMethod abstractRootMethod, + AnalysisOptions options, + IAnalysisCacheView cache, + PointerKeyFactory pointerKeyFactory, + ContextSelector appContextSelector, + ContextSelector delegatedAppContextSelector, + SSAContextInterpreter appContextInterpreter, + SSAContextInterpreter delegatedAppContextInterpreter, + int val, + PointerAnalysisPolicy delegatedType, + boolean UtilVersion) { + super(abstractRootMethod, + options, cache, + pointerKeyFactory, + appContextSelector, + delegatedAppContextSelector, + appContextInterpreter, + delegatedAppContextInterpreter, + new PointerAnalysisPolicy(nCFA, val), + delegatedType); + this.UtilVersion = UtilVersion; + } + + @SuppressWarnings("rawtypes") + public static CallGraphBuilder make( + AnalysisScope scope, + AnalysisOptions options, + IAnalysisCacheView cache, + IClassHierarchy cha, + int val, + PointerAnalysisPolicy type, + boolean UtilVersion) { + Util.addDefaultSelectors(options, cha); + Util.addDefaultBypassLogic(options, scope, Util.class.getClassLoader(), cha); + return new SalsaNCFACallGraphBuilder( + Language.JAVA.getFakeRootMethod(cha, options, cache), + options, cache, + new DefaultPointerKeyFactory(), + null, null, + null, null, + val, type, + UtilVersion); + } + + @SuppressWarnings("rawtypes") + public static CallGraphBuilder make( + AnalysisScope scope, + AnalysisOptions options, + IAnalysisCacheView cache, + IClassHierarchy cha, + int val, + PointerAnalysisPolicy type) { + // Auto defaults to Util.makeNCFA version + return make(scope, options, cache, cha, val, type, true); + } + + @Override + protected ContextSelector makePrimaryContextSelector(ContextSelector appContextSelector) { + return this.makeNCFAContextSelector(appContextSelector, defaultPaPolicy.policyNumber); + } + + @Override + protected SSAContextInterpreter makePrimarySSAContextInterpreter(SSAContextInterpreter appContextSelector) { + return this.makeNCFAContextInterpreter(appContextSelector); + } + + @Override + protected InstanceKeyFactory makePrimaryInstanceKeyFactory() { + if (!UtilVersion) + return this.makeNCFAInstanceKeyFactory(); + else + return makeSpecificUtilInstanceKeyFactory(); + } + + /** + * Util.makeNCFA changes the InstanceKeyFactory used rather than using the default. + * This method simulates making the NCFA the same as the Util method. + * + * @return + */ + private InstanceKeyFactory makeSpecificUtilInstanceKeyFactory() { + RTAContextInterpreter contextInterpreter = getContextInterpreter(); + if (contextInterpreter == null) + throw new RuntimeException(" ContextInterpreter was null while making SpecificUtilInstanceKeyFactory"); + + return new ZeroXInstanceKeys( + options, + cha, + contextInterpreter, + ZeroXInstanceKeys.ALLOCATIONS + | ZeroXInstanceKeys.SMUSH_MANY + | ZeroXInstanceKeys.SMUSH_PRIMITIVE_HOLDERS + | ZeroXInstanceKeys.SMUSH_STRINGS + | ZeroXInstanceKeys.SMUSH_THROWABLES); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaSSAPropagationCallGraphBuilder.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaSSAPropagationCallGraphBuilder.java new file mode 100644 index 0000000..607196e --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaSSAPropagationCallGraphBuilder.java @@ -0,0 +1,369 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.analysis.reflection.ReflectionContextInterpreter; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.ContextSelector; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.callgraph.impl.DefaultContextSelector; +import com.ibm.wala.ipa.callgraph.impl.DelegatingContextSelector; +import com.ibm.wala.ipa.callgraph.propagation.*; +import com.ibm.wala.ipa.callgraph.propagation.cfa.*; +import com.ibm.wala.ipa.callgraph.propagation.rta.RTAContextInterpreter; +import edu.rit.se.design.callgraph.analysis.AbstractSerializationCallGraphBuilder; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.analysis.SerializationPointsToSolver; + +import java.util.HashSet; +import java.util.Set; + + +/** + * A propagation call graph builder that enhances underlying policy by allowing support of serialization and deserialization. + * + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public abstract class SalsaSSAPropagationCallGraphBuilder extends AbstractSerializationCallGraphBuilder { + + /** + * Tracks objects serialized and deserialized + */ + protected Set deserializedObjects; // set D in the paper + protected Set serializedObjects; // set S in the paper + + protected SalsaSSAPropagationCallGraphBuilder( + IMethod abstractRootMethod, + AnalysisOptions options, + IAnalysisCacheView cache, + PointerKeyFactory pointerKeyFactory, + ContextSelector appContextSelector, + ContextSelector delegatedAppContextSelector, + SSAContextInterpreter appContextInterpreter, + SSAContextInterpreter delegatedAppContextInterpreter, + PointerAnalysisPolicy defaultPaPolicy, + PointerAnalysisPolicy taintedPaPolicy) { + super(abstractRootMethod, options, cache, pointerKeyFactory, defaultPaPolicy, taintedPaPolicy); + + + this.deserializedObjects = new HashSet<>(); + this.serializedObjects = new HashSet<>(); + + // Since ZeroX has some has some instance key weirdness, makeSSAContextInterpreter needs to be called before makeContextSelector + setContextInterpreter(makeSSAContextInterpreter(appContextInterpreter, delegatedAppContextInterpreter)); + + // Since nCFA has some instance key weirdness, makeInstanceKeyFactory needs to be called after setContextInterpreter + setInstanceKeys(makeInstanceKeyFactory()); + + // Since ZeroXContainer has some instance key weirdness, makeContextSelector needs to be called after makeInstanceKeyFactory + setContextSelector(makeContextSelector(appContextSelector, delegatedAppContextSelector)); + } + + +// + + + @Override + protected IPointsToSolver makeSolver() { + UnsoundSerializationHandler serializationHandler = new UnsoundSerializationHandler(this); + IPointsToSolver delegateSolver = super.makeSolver(); + return new SerializationPointsToSolver(system, this, serializationHandler, delegateSolver); + } + +// + +// + + /** + * Makes the ContextSelector to be used with tainted and untainted nodes + * + * @param appContextSelector The inputted context selector, can be null or another ContextSelector + * @param delegatedAppContextSelector The inputted context selector, can be null or another ContextSelector + * @return The context selector to use for a taint-based call graph construction + */ + protected ContextSelector makeContextSelector( + ContextSelector appContextSelector, + ContextSelector delegatedAppContextSelector) { + return new SalsaDelegatingContextSelector( + this, + makePrimaryContextSelector(appContextSelector), + makeSecondaryContextSelector(delegatedAppContextSelector)); + } + + /** + * Makes the ContextSelector to be used with normal and model nodes + * + * @param appContextInterpreter + * @param delegatedAppContextInterpreter + * @return + */ + protected SSAContextInterpreter makeSSAContextInterpreter( + SSAContextInterpreter appContextInterpreter, + SSAContextInterpreter delegatedAppContextInterpreter) { + SSAContextInterpreter normalInterpreter = makePrimarySSAContextInterpreter(appContextInterpreter); + SSAContextInterpreter modelInterpreter = makeSecondaryContextInterpreter(delegatedAppContextInterpreter); + return new SalsaDelegatingSSAContextInterpreter(this, normalInterpreter, modelInterpreter); + } + + /** + * Makes the InstanceKeyFactory to be used with normal and model nodes + * + * @return + */ + protected InstanceKeyFactory makeInstanceKeyFactory() { + return new SalsaDelegatingInstanceKeyFactory( + this, + makePrimaryInstanceKeyFactory(), + makeSecondaryInstanceKeyFactory()); + } +// + +// + + /** + * Makes the ContextSelector to be used in a normal node + * + * @param appContextSelector The context selector to use in the primary + * @return The ContextSelector for the specific class + */ + protected abstract ContextSelector makePrimaryContextSelector(ContextSelector appContextSelector); + + /** + * Makes the SSAContextInterpreter to be used in a normal node + * + * @param appContextSelector The context interpreter to use in the primary + * @return The SSAContextInterpreter for the specific class + */ + protected abstract SSAContextInterpreter makePrimarySSAContextInterpreter(SSAContextInterpreter appContextSelector); + + /** + * Makes the InstanceKeyFactory to be used in a normal node + * + * @return The InstanceKeyFactory for the specific class + */ + protected abstract InstanceKeyFactory makePrimaryInstanceKeyFactory(); +// + +// + + /** + * Makes the ContextSelector to be used for a synthetic (model) node. + * + * @param appContextSelector The inputted context selector, can be null or another ContextSelector + * @return The context selector to use for model (synthetic) nodes + */ + protected ContextSelector makeSecondaryContextSelector( + ContextSelector appContextSelector) { + switch (taintedPaPolicy.policyType) { + case nCFA: + return makeNCFAContextSelector(appContextSelector, taintedPaPolicy.policyNumber); + case ZeroXCFA: + return makeZeroXContextSelector(appContextSelector); + case ZeroXContainerCFA: + return makeZeroXContainerContextSelector(appContextSelector, false); + default: + throw new UnsupportedOperationException("Unknown policy " + taintedPaPolicy); + } + } + + /** + * Makes the SSAContextInterpreter to be used for a model node + * + * @param appSSAContextInterpreter The inputted context selector, can be null or another ContextSelector + * @return The context interpreter to use for tainted nodes + */ + protected SSAContextInterpreter makeSecondaryContextInterpreter( + SSAContextInterpreter appSSAContextInterpreter) { +// switch (analysisPolicy) { +// case nCFA: +// return makeNCFAContextInterpreter(appSSAContextInterpreter); +// case ZeroXCFA: +// return makeZeroXContextInterpreter(appSSAContextInterpreter); +// case ZeroXContainerCFA: +// default: +// // If no proper type is found, use the same as the specific +// return makePrimarySSAContextInterpreter(appSSAContextInterpreter); +// } +// return makeNCFAContextInterpreter(appSSAContextInterpreter); + return new SalsaContextInterpreter(options, cache); + } + + /** + * Makes the InstanceKeyFactory to be used for a taintedNode + * + * @return The InstanceKeyFactory to use for tainted nodes + */ + protected InstanceKeyFactory makeSecondaryInstanceKeyFactory() { + switch (taintedPaPolicy.policyType) { + case nCFA: + return makeNCFAInstanceKeyFactory(); + case ZeroXCFA: + return makeZeroXInstanceKeyFactory(taintedPaPolicy.policyNumber); + case ZeroXContainerCFA: + default: + // If no proper type is found, use the same as the specific + return makePrimaryInstanceKeyFactory(); + } + } +// + +// + + /** + * Makes the same context selector as nCFABuilder + * + * @param appContextSelector + * @param val a value for the n or x in nCFA, ZeroXCFA, ZeroXContainerCFA + * @return The ContextSelector for nCFA + */ + protected ContextSelector makeNCFAContextSelector( + ContextSelector appContextSelector, + int val) { + ContextSelector def = new DefaultContextSelector(options, cha); + ContextSelector contextSelector = + appContextSelector == null ? def : new DelegatingContextSelector(appContextSelector, def); + return new nCFAContextSelector(val, contextSelector); + } + + /** + * Makes the same context interpreter as nCFABuilder + * + * @param appContextInterpreter + * @return The SSAContextInterpreter for nCFA + */ + protected SSAContextInterpreter makeNCFAContextInterpreter( + SSAContextInterpreter appContextInterpreter) { + SSAContextInterpreter defI = new DefaultSSAInterpreter(options, cache); + defI = + new DelegatingSSAContextInterpreter( + ReflectionContextInterpreter.createReflectionContextInterpreter( + cha, options, getAnalysisCache()), + defI); + SSAContextInterpreter contextInterpreter = + appContextInterpreter == null + ? defI + : new DelegatingSSAContextInterpreter(appContextInterpreter, defI); + return contextInterpreter; + } + + /** + * Makes the same InstanceKeyFactory as nCFABuilder + * + * @return The InstanceKeyFactory for nCFA + */ + protected InstanceKeyFactory makeNCFAInstanceKeyFactory() { + return new ClassBasedInstanceKeys(options, cha); + } +// + +// + + /** + * Makes the same context selector as ZeroXBuilder + * + * @param appContextSelector + * @return The ContextSelector for ZeroX + */ + protected ContextSelector makeZeroXContextSelector( + ContextSelector appContextSelector) { + ContextSelector def = new DefaultContextSelector(options, cha); + ContextSelector contextSelector = + appContextSelector == null ? def : new DelegatingContextSelector(appContextSelector, def); + return contextSelector; + } + + /** + * Makes the same context interpreter as ZeroXBuilder + * + * @param appContextInterpreter + * @return The SSAContextInterpreter for ZeroX + */ + protected SSAContextInterpreter makeZeroXContextInterpreter( + SSAContextInterpreter appContextInterpreter) { + SSAContextInterpreter c = new DefaultSSAInterpreter(options, cache); + c = + new DelegatingSSAContextInterpreter( + ReflectionContextInterpreter.createReflectionContextInterpreter( + cha, options, getAnalysisCache()), + c); + SSAContextInterpreter contextInterpreter = + appContextInterpreter == null + ? c + : new DelegatingSSAContextInterpreter(appContextInterpreter, c); + return contextInterpreter; + } + + /** + * Makes the same InstanceKeyFactory as ZeroXBuilder + * + * @param val the X in ZeroX + * @return The InstanceKeyFactory for ZeroX + */ + protected InstanceKeyFactory makeZeroXInstanceKeyFactory(int val) { + RTAContextInterpreter contextInterpreter = getContextInterpreter(); + if (contextInterpreter == null) + throw new RuntimeException("ContextInterpreter was null, while creating a ZeroXInstanceKeyFactory"); + + return new ZeroXInstanceKeys(options, cha, contextInterpreter, val); + } +// + +// + + /** + * Makes the same context selector as ZeroXContainerBuilder + * + * @param appContextSelector + * @param primaryInstanceKeyFactory Whether the primary instance key factory is the one to get from the delegatingInstanceKeyFactory + * @return The ContextSelector for ZeroXContainer + */ + protected ContextSelector makeZeroXContainerContextSelector(ContextSelector appContextSelector, boolean primaryInstanceKeyFactory) { + + SalsaDelegatingInstanceKeyFactory salsaDelegatingInstanceKeyFactory = (SalsaDelegatingInstanceKeyFactory) getInstanceKeys(); + if (salsaDelegatingInstanceKeyFactory == null) + throw new RuntimeException("InstanceKeyFactory was null while making ZeroXContainerContextSelector"); + + ZeroXInstanceKeys zeroXInstanceKeys; + if (primaryInstanceKeyFactory) + zeroXInstanceKeys = (ZeroXInstanceKeys) salsaDelegatingInstanceKeyFactory.getPrimaryInstanceKeyFactory(); + else + zeroXInstanceKeys = (ZeroXInstanceKeys) salsaDelegatingInstanceKeyFactory.getSecondaryInstanceKeyFactory(); + + if (zeroXInstanceKeys == null) + throw new RuntimeException("InstanceKeyFactory delegate was null while making ZeroXContainerContextSelector"); + + return new DelegatingContextSelector( + new ContainerContextSelector(cha, zeroXInstanceKeys), + makeZeroXContextSelector(appContextSelector)); + } +// + +// getDeserializedObjects() { + return deserializedObjects; + } + + /** + * Return the pointers to objects that were serialized in the program. + * + * @return + */ + public Set getSerializedObjects() { + return serializedObjects; + } + + public boolean isSyntheticModel(CGNode node) { + return models.contains(node); + } + + + // + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXCallGraphBuilder.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXCallGraphBuilder.java new file mode 100644 index 0000000..3610156 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXCallGraphBuilder.java @@ -0,0 +1,83 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.Language; +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.impl.Util; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.PointerKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter; +import com.ibm.wala.ipa.callgraph.propagation.cfa.DefaultPointerKeyFactory; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; + +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.ZeroXCFA; + + +/** + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ + +public class SalsaZeroXCallGraphBuilder extends SalsaSSAPropagationCallGraphBuilder { + + protected SalsaZeroXCallGraphBuilder( + IMethod abstractRootMethod, + AnalysisOptions options, IAnalysisCacheView cache, + PointerKeyFactory pointerKeyFactory, + ContextSelector appContextSelector, int primaryPolicyVal, + ContextSelector delegatedAppContextSelector, + SSAContextInterpreter appContextInterpreter, SSAContextInterpreter delegatedAppContextInterpreter, + PointerAnalysisPolicy type) { + super(abstractRootMethod, options, cache, pointerKeyFactory, + appContextSelector, delegatedAppContextSelector, + appContextInterpreter, delegatedAppContextInterpreter, + new PointerAnalysisPolicy(ZeroXCFA, primaryPolicyVal), type); + } + + // This constructor is used for ZeroXContainter.super call + protected SalsaZeroXCallGraphBuilder(IMethod abstractRootMethod, AnalysisOptions options, IAnalysisCacheView cache, + PointerKeyFactory pointerKeyFactory, ContextSelector appContextSelector, + ContextSelector delegatedAppContextSelector, SSAContextInterpreter appContextInterpreter, + SSAContextInterpreter delegatedAppContextInterpreter, PointerAnalysisPolicy PrimaryType, PointerAnalysisPolicy delegatedType) { + super(abstractRootMethod, options, cache, pointerKeyFactory, + appContextSelector, delegatedAppContextSelector, + appContextInterpreter, delegatedAppContextInterpreter, + PrimaryType, delegatedType); + } + + @SuppressWarnings("rawtypes") + public static CallGraphBuilder make( + AnalysisScope scope, + AnalysisOptions options, + IAnalysisCacheView cache, + IClassHierarchy cha, + PointerAnalysisPolicy type) { + Util.addDefaultSelectors(options, cha); + Util.addDefaultBypassLogic(options, scope, Util.class.getClassLoader(), cha); + return new SalsaZeroXCallGraphBuilder( + Language.JAVA.getFakeRootMethod(cha, options, cache), + options, cache, + new DefaultPointerKeyFactory(), + null, type.policyNumber, + null, + null, null, + type); + } + + @Override + protected ContextSelector makePrimaryContextSelector(ContextSelector appContextSelector) { + return makeZeroXContextSelector(appContextSelector); + } + + @Override + protected SSAContextInterpreter makePrimarySSAContextInterpreter(SSAContextInterpreter appContextSelector) { + return makeZeroXContextInterpreter(appContextSelector); + } + + @Override + protected InstanceKeyFactory makePrimaryInstanceKeyFactory() { + return makeZeroXInstanceKeyFactory(defaultPaPolicy.policyNumber); + } + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXContainerCallGraphBuilder.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXContainerCallGraphBuilder.java new file mode 100644 index 0000000..fd91e2f --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/SalsaZeroXContainerCallGraphBuilder.java @@ -0,0 +1,54 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.Language; +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.propagation.PointerKeyFactory; +import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter; +import com.ibm.wala.ipa.callgraph.propagation.cfa.DefaultPointerKeyFactory; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; + +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.ZeroXCFA; + + +/** + * @author Reese A. Jones (raj8065@rit.edu) + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class SalsaZeroXContainerCallGraphBuilder extends SalsaZeroXCallGraphBuilder { + + protected SalsaZeroXContainerCallGraphBuilder(IMethod abstractRootMethod, AnalysisOptions options, + IAnalysisCacheView cache, PointerKeyFactory pointerKeyFactory, ContextSelector appContextSelector, + int primaryPolicyVal, ContextSelector delegatedAppContextSelector, + SSAContextInterpreter appContextInterpreter, SSAContextInterpreter delegatedAppContextInterpreter, + PointerAnalysisPolicy type) { + super(abstractRootMethod, options, cache, pointerKeyFactory, + appContextSelector, delegatedAppContextSelector, + appContextInterpreter, delegatedAppContextInterpreter, + new PointerAnalysisPolicy(ZeroXCFA, primaryPolicyVal), type); + } + + @SuppressWarnings("rawtypes") + public static CallGraphBuilder make( + AnalysisScope scope, + AnalysisOptions options, + IAnalysisCacheView cache, + IClassHierarchy cha, + PointerAnalysisPolicy type) { + return new SalsaZeroXContainerCallGraphBuilder( + Language.JAVA.getFakeRootMethod(cha, options, cache), + options, cache, + new DefaultPointerKeyFactory(), + null, type.policyNumber, + null, + null, null, + type); + } + + @Override + protected ContextSelector makePrimaryContextSelector(ContextSelector appContextSelector) { + return makeZeroXContainerContextSelector(appContextSelector, true); + } + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/UnsoundSerializationHandler.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/UnsoundSerializationHandler.java new file mode 100644 index 0000000..d90896b --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/analysis/salsa/UnsoundSerializationHandler.java @@ -0,0 +1,328 @@ +package edu.rit.se.design.callgraph.analysis.salsa; + + +import com.ibm.wala.classLoader.*; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.Context; +import com.ibm.wala.ipa.callgraph.propagation.ConcreteTypeKey; +import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; +import com.ibm.wala.ipa.callgraph.propagation.PointerKey; +import com.ibm.wala.ipa.slicer.NormalReturnCaller; +import com.ibm.wala.ipa.slicer.NormalStatement; +import com.ibm.wala.ipa.slicer.Slicer; +import com.ibm.wala.ipa.slicer.Statement; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; +import com.ibm.wala.ssa.SSACheckCastInstruction; +import com.ibm.wala.ssa.SSANewInstruction; +import com.ibm.wala.ssa.SSAPhiInstruction; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.types.generics.ArrayTypeSignature; +import com.ibm.wala.types.generics.ClassTypeSignature; +import com.ibm.wala.types.generics.TypeArgument; +import com.ibm.wala.types.generics.TypeSignature; +import com.ibm.wala.util.CancelException; +import com.ibm.wala.util.MonitorUtil; +import com.ibm.wala.util.collections.HashSetFactory; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.intset.OrdinalSet; +import edu.rit.se.design.callgraph.analysis.AbstractSerializationCallGraphBuilder; +import edu.rit.se.design.callgraph.analysis.AbstractSerializationHandler; +import edu.rit.se.design.callgraph.model.MethodModel; +import edu.rit.se.design.callgraph.util.NameUtils; +import edu.rit.se.design.callgraph.util.SerializationUtils; +import edu.rit.se.design.callgraph.util.TypeCategory; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.ibm.wala.ipa.slicer.Slicer.ControlDependenceOptions.NONE; +import static com.ibm.wala.ipa.slicer.Slicer.DataDependenceOptions.REFLECTION; +import static com.ibm.wala.shrikeBT.IInvokeInstruction.Dispatch.VIRTUAL; +import static edu.rit.se.design.callgraph.util.NameUtils.*; +import static edu.rit.se.design.callgraph.util.TypeCategory.*; + +/** + * This class implements support for serialization-related features. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class UnsoundSerializationHandler extends AbstractSerializationHandler { + + public UnsoundSerializationHandler(AbstractSerializationCallGraphBuilder builder) { + super(builder); + } + + @Override + public void handleSerializationRelatedFeatures(MonitorUtil.IProgressMonitor monitor) { + try { + Set changedNodes = HashSetFactory.make(); + handleDeserialization(changedNodes, monitor); + ((AbstractSerializationCallGraphBuilder) builder).getDeserializationWorkList().clear(); + handleSerialization(changedNodes, monitor); + ((AbstractSerializationCallGraphBuilder) builder).getSerializationWorkList().clear(); + // tag them as changed, so it can go through again the visiting process + for (CGNode cgNode : changedNodes) builder.addConstraintsFromChangedNode(cgNode, monitor); + } catch (CancelException e) { + throw new RuntimeException(e); + } + } + +// + + /** + * Makes changes to the models, adding the instructions needed for tricking the pointer analysis to tame with object serialization. + * + * @param changedNodes (acts as an output) it adds to the set nodes that were modified and need to be re-analyzed by the pointer analysis engine. + * @param monitor progress monitor (though current implementation does not emit any progress) + */ + protected void handleSerialization(Set changedNodes, MonitorUtil.IProgressMonitor monitor) { + Set> serializationWorkList = ((AbstractSerializationCallGraphBuilder) builder).getSerializationWorkList(); + for (Triple triple : serializationWorkList) { + CGNode caller = triple.getLeft(); + SSAAbstractInvokeInstruction call = triple.getMiddle(); + CGNode target = triple.getRight(); + + Set allocations = getAllocatedParameterType(caller, call); + MethodModel methodModel = ((MethodModel) target.getMethod()); + Context context = target.getContext(); + int previousSize = methodModel.getNumberOfStatements(context); + + + int topLevelObjectNumber = 2; // v2 is the object parameter passed to the method model + for (NewSiteReference site : allocations) { + TypeReference topLevelReference = site.getDeclaredType(); + IClass topLevelClass = builder.getClassHierarchy().lookupClass(topLevelReference); + // vCast = (T) v2 + int topLevelCast = methodModel.addCheckcast(context, new TypeReference[]{topLevelReference}, topLevelObjectNumber, true); + + // if class has writeReplace() callback, we mimic its invocation + IMethod writeReplaceCallbackMethod = topLevelClass.getMethod(writeReplaceCallbackSelector); + if (writeReplaceCallbackMethod != null) { + // vCast.writeReplace() + methodModel.addInvocation(context, new int[]{topLevelCast}, writeReplaceCallbackMethod.getReference(), VIRTUAL); + } + + // if class has writeObject() callback, we mimic its invocation + IMethod writeObjectCallbackMethod = topLevelClass.getMethod(writeObjectCallbackSelector); + if (writeObjectCallbackMethod != null) { + // vCast.writeObject(v1) + methodModel.addInvocation(context, new int[]{topLevelCast, 1}, writeObjectCallbackMethod.getReference(), VIRTUAL); + } + // handles inner (non-static) fields, including from super classes + TypeCategory typeCategory = SerializationUtils.getTypeCategory(builder.cha, topLevelReference); + if (typeCategory == OBJECT) + handleInnerClassObjects(caller, context, methodModel, site, topLevelClass, topLevelCast); + } + if (previousSize != methodModel.getNumberOfStatements(context)) { + // invalidate previous cache + builder.getAnalysisCache().invalidate(methodModel, context); + changedNodes.add(target); + } + } + } + + + protected void handleInnerClassObjects(CGNode caller, Context context, MethodModel methodModel, NewSiteReference site, IClass topLevelClass, int topLevelCast) { + Collection allInstanceFields = topLevelClass.getAllInstanceFields(); + InstanceKey topObjIk = builder.getInstanceKeyForAllocation(caller, site); + for (IField iField : allInstanceFields) { + PointerKey pkForField = builder.getPointerKeyForInstanceField(topObjIk, iField); + OrdinalSet concreteFieldTypes = builder.getPointerAnalysis().getPointsToSet(pkForField); + // iterate the allocated types for the inner fields + for (InstanceKey concreteFieldType : concreteFieldTypes) { + IMethod innerCallbackMethod = concreteFieldType.getConcreteType().getMethod(writeObjectCallbackSelector); + if (innerCallbackMethod != null) { + // vx = checkcast (ConcreteType)vCast + int varInnerField = methodModel.addGetInstance(context, iField.getReference(), topLevelCast); + int varInnerCast = methodModel.addCheckcast(context, new TypeReference[]{concreteFieldType.getConcreteType().getReference()}, varInnerField, true); + // we add the equivalent of v.writeObject(v1) + methodModel.addInvocation(context, new int[]{varInnerCast, 1}, innerCallbackMethod.getReference(), VIRTUAL); + } + } + } + } + + /** + * Computes the concrete types of the objects being serialized. + * + * @param caller + * @param call + * @return + * @throws CancelException + */ + protected Set getAllocatedParameterType(CGNode caller, SSAAbstractInvokeInstruction call) { + Set result = new HashSet<>(); + int varNoObject = call.getUse(1); // variable number for the object being serialized + PointerKey pkParameter = builder.getPointerKeyForLocal(caller, varNoObject); + OrdinalSet pointsToSet = builder.getPointerAnalysis().getPointsToSet(pkParameter); + + pointsToSet.forEach(instanceKey -> { + if (!(instanceKey instanceof FakeInstanceKey)) { + Iterator> creationSites = instanceKey.getCreationSites(builder.getCallGraph()); + creationSites.forEachRemaining(pair -> { + result.add(pair.snd); + }); + } + }); + return result; + } +// + + +// + + /** + * Makes changes to the models, adding the instructions needed for tricking the pointer analysis to tame with object deserialization. + * + * @param changedNodes (acts as an output) it adds to the set nodes that were modified and need to be re-analyzed by the pointer analysis engine. + * @param monitor progress monitor (though current implementation does not emit any progress) + */ + protected void handleDeserialization(Set changedNodes, MonitorUtil.IProgressMonitor monitor) throws CancelException { + + for (Triple triple : ((AbstractSerializationCallGraphBuilder) builder).getDeserializationWorkList()) { + + CGNode caller = triple.getLeft(); + SSAAbstractInvokeInstruction call = triple.getMiddle(); + CGNode target = triple.getRight(); + // compute slices to find downcasts + NormalReturnCaller st = new NormalReturnCaller(caller, call.iIndex()); + Collection slice = Slicer.computeForwardSlice(st, builder.getCallGraph(), builder.getPointerAnalysis(), REFLECTION, NONE); + Set casts = slice.stream() + .filter(s -> + s.getKind() == Statement.Kind.NORMAL && + (((NormalStatement) s).getInstruction() instanceof SSACheckCastInstruction)) + .map(s -> (SSACheckCastInstruction) ((NormalStatement) s).getInstruction()) + .collect(Collectors.toSet()); + + + MethodModel methodModel = ((MethodModel) target.getMethod()); + Context context = target.getContext(); + ArrayList returnValues = new ArrayList<>(); + for (SSACheckCastInstruction cast : casts) { + Arrays.asList(cast.getDeclaredResultTypes()).stream() + .map(typeReference -> builder.getClassHierarchy().lookupClass(typeReference)) + .filter(klass -> klass != null) + .forEach(c -> { + Collection subClasses = builder.cha.computeSubClasses(c.getReference()); + for (IClass klass : subClasses) { + SSANewInstruction ssaNewInstruction = methodModel.addAllocation(context, klass.getReference()); + returnValues.add(ssaNewInstruction.getDef()); + + // if class implements the readObject() callback, invokes it + IMethod readObjectCallbackMethod = klass.getMethod(NameUtils.readObjectCallbackSelector); + if (readObjectCallbackMethod != null) { + methodModel.addInvocation(context, new int[]{ssaNewInstruction.getDef(), 1}, readObjectCallbackMethod.getReference(), VIRTUAL); + } + + // if class implements the readObjectNoData() callback, invokes it + IMethod readObjectNoDataCallbackMethod = klass.getMethod(readObjectNoDataCallbackSelector); + if (readObjectNoDataCallbackMethod != null) { + methodModel.addInvocation(context, new int[]{ssaNewInstruction.getDef(), 1}, readObjectNoDataCallbackMethod.getReference(), VIRTUAL); + } + + // if class implements the readResolve() callback, invokes it + IMethod readResolveCallbackMethod = klass.getMethod(NameUtils.readResolveCallbackSelector); + if (readResolveCallbackMethod != null) { + methodModel.addInvocation(context, new int[]{ssaNewInstruction.getDef()}, readResolveCallbackMethod.getReference(), VIRTUAL); + } + + // if class implements the validateObject() callback, invokes it + IMethod validateObjectCallbackMethod = klass.getMethod(validateObjectCallbackSelector); + if (validateObjectCallbackMethod != null) { + methodModel.addInvocation(context, new int[]{ssaNewInstruction.getDef()}, validateObjectCallbackMethod.getReference(), VIRTUAL); + } + + + // handles inner (non-static) fields, including from super classes + TypeCategory typeCategory = SerializationUtils.getTypeCategory(builder.cha, klass.getReference()); + if (typeCategory == OBJECT) + handleInnerDeserializedObjects(caller, context, target, klass, ssaNewInstruction); + } + }); + } + + if (casts.isEmpty()) { + Collection subClasses = builder.cha.computeSubClasses(caller.getMethod().getDeclaringClass().getReference()); + //if we hit here, means the call came from an inner callback, this below is a workaround! + IClass klass = caller.getMethod().getDeclaringClass(); + SSANewInstruction ssaNewInstruction = methodModel.addAllocation(context, klass.getReference()); + returnValues.add(ssaNewInstruction.getDef()); + IMethod readObjectCallbackMethod = klass.getMethod(NameUtils.readObjectCallbackSelector); + if (readObjectCallbackMethod != null) { + methodModel.addInvocation(context, new int[]{ssaNewInstruction.getDef(), 1}, readObjectCallbackMethod.getReference(), VIRTUAL); + } + // handles inner (non-static) fields, including from super classes + TypeCategory typeCategory = SerializationUtils.getTypeCategory(builder.cha, klass.getReference()); + if (typeCategory != PRIMITIVE && typeCategory != IGNORED && typeCategory != ARRAY) + handleInnerDeserializedObjects(caller, context, target, klass, ssaNewInstruction); + } + + if (returnValues.size() > 1) { + SSAPhiInstruction phi = methodModel.addPhi(context, returnValues); + methodModel.addReturnObject(context, phi.getDef()); + } else /*if (returnValues.size() == 1)*/ { + methodModel.addReturnObject(context, returnValues.get(0)); + } + // invalidate the cache + builder.getAnalysisCache().invalidate(target.getMethod(), target.getContext()); + + // mark node as modified to be re-traversed + changedNodes.add(target); + } + } + + + protected void handleInnerDeserializedObjects(CGNode caller, Context context, CGNode target, IClass klass, SSANewInstruction ssaNewInstruction) { + MethodModel methodModel = ((MethodModel) target.getMethod()); + Collection allInstanceFields = klass.getAllInstanceFields(); + InstanceKey topObjIk = builder.getInstanceKeyForAllocation(caller, ssaNewInstruction.getNewSite()); + if (topObjIk == null) return; //FIXME + TypeCategory classCategory = SerializationUtils.getTypeCategory(builder.cha, klass.getReference()); + boolean isCollection = classCategory == LIST || classCategory == MAP || classCategory == SET; + + for (IField iField : allInstanceFields) { + TypeReference fieldTypeRef = iField.getFieldTypeReference(); + if (fieldTypeRef.isPrimitiveType()) continue; + IClass iFieldStaticType = builder.cha.lookupClass(fieldTypeRef); + if (isCollection) iFieldStaticType = extractGenericType(iField); + + PointerKey pkForField = builder.getPointerKeyForInstanceField(topObjIk, iField); + Set possibleTypes = SerializationUtils.computePossibleTypes(builder.cha, klass, iFieldStaticType, serializableClasses, true, 100); + for (IClass concreteFieldType : possibleTypes) { + IMethod innerCallbackMethod = concreteFieldType.getMethod(readObjectCallbackSelector); + if (innerCallbackMethod != null) { + +// builder.getSystem().newConstraint(pkForField, new ConcreteTypeKey(concreteFieldType)); + // vx = checkcast (ConcreteType)vCast + int varInnerField = methodModel.addGetInstance(context, iField.getReference(), ssaNewInstruction.getDef()); + builder.getSystem().newConstraint(builder.getPointerKeyForLocal(target, varInnerField), new ConcreteTypeKey(concreteFieldType)); + int varInnerCast = methodModel.addCheckcast(context, new TypeReference[]{concreteFieldType.getReference()}, varInnerField, true); + // we add the equivalent of v.writeObject(v1) + methodModel.addInvocation(context, new int[]{varInnerCast, 1}, innerCallbackMethod.getReference(), VIRTUAL); + } + } + + } + } + + protected IClass extractGenericType(IField iField) { + if (iField != null && iField instanceof FieldImpl) { + TypeSignature genericSignature = ((FieldImpl) iField).getGenericSignature(); + if (genericSignature != null) { + if (genericSignature.isClassTypeSignature()) { + TypeArgument typeArgument = ((ClassTypeSignature) genericSignature).getTypeArguments()[0]; + TypeSignature fieldTypeSignature = typeArgument.getFieldTypeSignature(); + if (fieldTypeSignature != null && fieldTypeSignature.isClassTypeSignature()) + return ClassTypeSignature.lookupClass(builder.cha, (ClassTypeSignature) fieldTypeSignature); + } else if (genericSignature.isArrayTypeSignature()) { + TypeSignature contents = ((ArrayTypeSignature) genericSignature).getContents(); + } + } + } + return builder.cha.lookupClass(iField.getFieldTypeReference()); + } + + +// +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/DefaultDispatcher.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/DefaultDispatcher.java new file mode 100644 index 0000000..c3cbca2 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/DefaultDispatcher.java @@ -0,0 +1,30 @@ +package edu.rit.se.design.callgraph.dispatcher; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This concrete implementation computes possible types to be allocated based on CHA. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class DefaultDispatcher implements IDispatcher { + /** + * A method that computes the set of possible instantiations based on the current method invocation. + * Client analyses provide a concrete implementation for this method in order to help with their precision (slightly) instead of using solely CHA. + * + * @param node + * @param invokeInstruction + * @return + */ + @Override + public Set computePossibleTypesForCall(CGNode node, SSAAbstractInvokeInstruction invokeInstruction) { + Set possibleTargets = node.getClassHierarchy().getPossibleTargets(invokeInstruction.getDeclaredTarget()); + return possibleTargets.stream().map(m -> m.getDeclaringClass()).collect(Collectors.toSet()); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/IDispatcher.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/IDispatcher.java new file mode 100644 index 0000000..5a95033 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/IDispatcher.java @@ -0,0 +1,27 @@ +package edu.rit.se.design.callgraph.dispatcher; + + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; + +import java.util.Set; + +/** + * Computes the dispatches for a method invocation. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public interface IDispatcher { + + /** + * A method that computes the set of possible instantiations based on the current method invocation. + * Client analyses provide a concrete implementation for this method in order to help with their precision (slightly) instead of using solely CHA. + * + * @param node + * @param invokeInstruction + * @return + */ + Set computePossibleTypesForCall(CGNode node, SSAAbstractInvokeInstruction invokeInstruction); + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/SerializationDispatcher.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/SerializationDispatcher.java new file mode 100644 index 0000000..d31d38b --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/SerializationDispatcher.java @@ -0,0 +1,89 @@ +package edu.rit.se.design.callgraph.dispatcher; + + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; +import com.ibm.wala.types.TypeReference; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import static edu.rit.se.design.callgraph.util.SerializationUtils.isAccessible; +import static edu.rit.se.design.dodo.utils.wala.WalaUtils.isApplicationScope; + +/** + * @author Joanna C. S. Santos + */ +public class SerializationDispatcher implements IDispatcher { + + // parametrization for fine-tuning the performance + + private final IClass serialInterface; + /** + * If true, then it filters the sets of possible targets including only classes in the application scope + * (filtering only if the set of possible targets in an invocation is higher than the pruningThreshold) + */ + private boolean prune; + /** + * Cut-off threshold + */ + private int pruningThreshold; + + + public SerializationDispatcher(IClassHierarchy cha, boolean prune, int pruningThreshold) { + this.prune = prune; + this.pruningThreshold = pruningThreshold; + this.serialInterface = cha.lookupClass(TypeReference.JavaIoSerializable); + } + + public SerializationDispatcher(IClassHierarchy cha) { + this(cha, true, 100); + } + + + /** + * This method computes the set of possible instantiations based on the current method invocation. + * First it uses CHA to compute the set of possible types, then it keeps only the serializable types that are accessible (following the acessibility rules, private/package/public). + * That helps with precision (slightly). + * + * @param node call graph node that has the current invocation instruction + * @param invokeInstruction invoke instruction + * @return the set of possible types for a given call. + */ + @Override + public Set computePossibleTypesForCall(CGNode node, SSAAbstractInvokeInstruction invokeInstruction) { + IClassHierarchy cha = node.getClassHierarchy(); + // the static type of the receiver object + IClass staticType = cha.lookupClass(invokeInstruction.getDeclaredTarget().getDeclaringClass()); + IClass declaringClass = node.getMethod().getDeclaringClass(); + + // uses CHA to compute the possible types + Set possibleTargets = cha.getPossibleTargets(invokeInstruction.getDeclaredTarget()); + + // keeps only the serializable classes and that are accessible + Set concreteTypes = new HashSet<>(); + for (IMethod possibleTarget : possibleTargets) { + IClass targetIClass = possibleTarget.getDeclaringClass(); + if (cha.implementsInterface(targetIClass, serialInterface)) { + boolean hasNoImplementation = targetIClass.isInterface() || targetIClass.isAbstract(); + boolean isAccessible = isAccessible(declaringClass, targetIClass); + if (!hasNoImplementation && isAccessible) { + concreteTypes.add(targetIClass); + } + } + } + + + // prune away primordial classes if the computed sets are fairly large + if (prune && concreteTypes.size() > pruningThreshold) { + concreteTypes = concreteTypes.stream().filter(c -> isApplicationScope(c)).collect(Collectors.toSet()); + } + return concreteTypes; + } + + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/UnsoundSerializationDispatcher.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/UnsoundSerializationDispatcher.java new file mode 100644 index 0000000..91a96d5 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/dispatcher/UnsoundSerializationDispatcher.java @@ -0,0 +1,22 @@ +package edu.rit.se.design.callgraph.dispatcher; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; + +import java.util.Set; + +public class UnsoundSerializationDispatcher implements IDispatcher { + /** + * A method that computes the set of possible instantiations based on the current method invocation. + * Client analyses provide a concrete implementation for this method in order to help with their precision (slightly) instead of using solely CHA. + * + * @param node + * @param invokeInstruction + * @return + */ + @Override + public Set computePossibleTypesForCall(CGNode node, SSAAbstractInvokeInstruction invokeInstruction) { + return null; + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/AbstractClassModel.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/AbstractClassModel.java new file mode 100644 index 0000000..ac24b24 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/AbstractClassModel.java @@ -0,0 +1,221 @@ +package edu.rit.se.design.callgraph.model; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IField; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.SyntheticClass; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.types.Selector; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.strings.Atom; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import static com.ibm.wala.types.ClassLoaderReference.Primordial; + +/** + * Abstraction to create classes' models (synthetic classes). + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public abstract class AbstractClassModel extends SyntheticClass { + + protected static final String PREFIX = "Lsalsa.model."; + + /** + * Original class being modeled. Used to delegate methods (isPublic, modifiers, etc) + */ + protected final IClass originalClass; + + /** + * synthetic (model) methods + */ + protected Set methods; + /** + * To be used by models for caching / performing analysis as expected + */ + protected AnalysisOptions options; + protected IAnalysisCacheView cache; + + + public AbstractClassModel(IClassHierarchy cha, AnalysisOptions options, IAnalysisCacheView cache, String originalClassName) { + super(getSyntheticTypeRef(originalClassName), cha); + this.options = options; + this.cache = cache; + this.originalClass = cha.lookupClass(TypeReference.findOrCreate(Primordial, "Ljava/io/" + originalClassName)); + if (this.originalClass == null) + throw new IllegalStateException(String.format("Could not find Ljava/io/%s in the class hierarchy", originalClassName)); + initSyntheticMethods(); + + // add to the class hierarchy to prevent "class not found" errors when adding edges to synthetic methods + cha.addClass(this); + } + + protected static TypeReference getSyntheticTypeRef(String originalClassName) { + return TypeReference.findOrCreate(Primordial, PREFIX + originalClassName); + } + + protected abstract void initSyntheticMethods(); + + /** + * @return true iff this class is public + */ + @Override + public boolean isPublic() { + return this.originalClass.isPublic(); + } + + /** + * @return true iff this class is private + */ + @Override + public boolean isPrivate() { + return this.originalClass.isPrivate(); + } + + /** + * Return the integer that encodes the class's modifiers, as defined by the JVM specification + * + * @return the integer that encodes the class's modifiers, as defined by the JVM specification + */ + @Override + public int getModifiers() throws UnsupportedOperationException { + return this.originalClass.getModifiers(); + } + + /** + * @return the superclass, or null if java.lang.Object + * @throws IllegalStateException if there's some problem determining the superclass + */ + @Override + public IClass getSuperclass() { + return this.originalClass.getSuperclass(); + } + + /** + * @return Collection of (IClass) interfaces this class directly implements. If this class is an + * interface, returns the interfaces it immediately extends. + */ + @Override + public Collection getDirectInterfaces() { + return this.originalClass.getDirectInterfaces(); + } + + /** + * @return Collection of (IClass) interfaces this class implements, including all ancestors of + * interfaces immediately implemented. If this class is an interface, it returns all + * super-interfaces. + */ + @Override + public Collection getAllImplementedInterfaces() { + return this.originalClass.getAllImplementedInterfaces(); + } + + /** + * Finds method matching signature. Delegates to superclass if not found. + * + * @param selector a method signature + * @return IMethod from this class matching the signature; null if not found in this class or any + * superclass. + */ + @Override + public IMethod getMethod(Selector selector) { + return methods.stream() + .filter(m -> m.getSelector().equals(selector)) + .findFirst() + .orElse(null); + } +// + + /** + * Finds a field. + * + * @param name + * @throws IllegalStateException if the class contains multiple fields with name {@code name}. + */ + @Override + public IField getField(Atom name) { + return null; // this model has no fields TODO maybe it will have some fields for the sake of modeling + } + +// + + /** + * @return the method that is this class's initializer, or null if none + */ + @Override + public IMethod getClassInitializer() { + return null; // no class initializer TODO maybe add something if needed + } + + /** + * @return an Iterator of the IMethods declared by this class. + */ + @Override + public Collection getDeclaredMethods() { + return Collections.unmodifiableCollection(methods); + } + + /** + * Compute the instance fields declared by this class or any of its superclasses. + */ + @Override + public Collection getAllInstanceFields() { + return Collections.emptySet(); // model has no non-static (instance) fields + } + + /** + * Compute the static fields declared by this class or any of its superclasses. + */ + @Override + public Collection getAllStaticFields() { + return Collections.emptySet(); // model has no static fields + } + + /** + * Compute the instance and static fields declared by this class or any of its superclasses. + */ + @Override + public Collection getAllFields() { + return Collections.emptySet(); // model has no fields, and we assume no fields on superclasses + } + + /** + * Compute the methods declared by this class or any of its superclasses. + */ + @Override + public Collection getAllMethods() { + return getDeclaredMethods(); //FIXME? should this be delegated? + } + + /** + * Compute the instance fields declared by this class. + * + * @return Collection of IFields + */ + @Override + public Collection getDeclaredInstanceFields() { + return Collections.emptySet(); + } + + /** + * @return Collection of IField + */ + @Override + public Collection getDeclaredStaticFields() { + return Collections.emptySet(); + } + + /** + * Does 'this' refer to a reference type? If not, then it refers to a primitive type. + */ + @Override + public boolean isReferenceType() { + return getReference().isReferenceType(); + } + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/MethodModel.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/MethodModel.java new file mode 100644 index 0000000..6eb82ab --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/MethodModel.java @@ -0,0 +1,511 @@ +package edu.rit.se.design.callgraph.model; + +import com.ibm.wala.cfg.InducedCFG; +import com.ibm.wala.classLoader.*; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.Context; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.ipa.summaries.SyntheticIR; +import com.ibm.wala.shrikeBT.IInvokeInstruction; +import com.ibm.wala.ssa.*; +import com.ibm.wala.types.FieldReference; +import com.ibm.wala.types.MethodReference; +import com.ibm.wala.types.Selector; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.collections.HashMapFactory; +import com.ibm.wala.util.warnings.Warning; +import com.ibm.wala.util.warnings.Warnings; +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.*; + + +/** + * Generic class for synthetic (model) methods of {@link AbstractClassModel}. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class MethodModel extends SyntheticMethod { + + public final IClassHierarchy cha; + protected final IAnalysisCacheView cache; + protected final SSAInstructionFactory instructionFactory; + private final AnalysisOptions options; + private final int initialNextLocal; + protected TypeReference[] exceptions; + +// private final List statements; +// private Map constant2ValueNumber = HashMapFactory.make(); +// public int nextLocal; + // + private Map, Map, Integer>> statementsPerContext; + + + public MethodModel(Selector selector, TypeReference[] declaredExceptions, IClass declaringClass, IClassHierarchy cha, AnalysisOptions options, IAnalysisCacheView cache) { + super(MethodReference.findOrCreate(declaringClass.getReference(), selector), declaringClass, false, false); + if (cache == null || cha == null || declaredExceptions == null) + throw new IllegalArgumentException("cha, cache and declaredExceptions parameters cannot be null"); + this.cha = cha; + this.options = options; + this.cache = cache; + this.instructionFactory = declaringClass.getClassLoader().getInstructionFactory(); + this.exceptions = declaredExceptions; + this.initialNextLocal = getNumberOfParameters() + declaredExceptions.length + 1; + this.statementsPerContext = new HashMap<>(); + } + + @SuppressWarnings("deprecation") + @Override + public SSAInstruction[] getStatements(SSAOptions options) { +// Assertions.UNREACHABLE("this shouldnt be called"); +// return null; + return NO_STATEMENTS; + } + + + public SSAInstruction[] getStatements(Context c) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + SSAInstruction[] instructions = new SSAInstruction[statements.size()]; + statements.toArray(instructions); + return instructions; + } + + public boolean isModelComputed(Context c) { + return statementsPerContext.containsKey(c); + } + + @Override + public IR makeIR(Context context, SSAOptions options) { +// System.out.println("Invoking " + getClass().getSimpleName() + ".makeIR (ctx=" + context + ")"); +// IR ir = this.cache.getIR(this, context); +// if (ir == null) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(context, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + SSAInstruction[] instructions = new SSAInstruction[statements.size()]; + statements.toArray(instructions); + Map constants = null; + Map constant2ValueNumber = triple.getMiddle(); + if (!constant2ValueNumber.isEmpty()) { + constants = HashMapFactory.make(constant2ValueNumber.size()); + for (Map.Entry entry : constant2ValueNumber.entrySet()) { + constants.put(entry.getValue(), entry.getKey()); + } + } + InducedCFG cfg = makeControlFlowGraph(instructions);/*this.getDeclaringClass() + .getClassLoader() + .getLanguage() + .makeInducedCFG(instructions, this, context);*/ + IR ir = new SyntheticIR(this, context, cfg, instructions, options, constants); +// } + + return ir; + } + +// public int addLocal(Context c) { +// return nextLocal++; +// } + + /** + * @param c + * @param resultVar variable number that is being returned + * @param isPrimitive if returned value is a primitive + * @return + */ + public SSAReturnInstruction addReturn(Context c, int resultVar, boolean isPrimitive) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + SSAReturnInstruction s = this.instructionFactory.ReturnInstruction(statements.size(), resultVar, isPrimitive); + statements.add(s); + + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(statements, triple.getMiddle(), triple.getRight())); + return s; + } + + /** + * @param c + * @param resultVar variable number that is being returned + * @return + */ + public SSAReturnInstruction addReturnObject(Context c, int resultVar) { + return this.addReturn(c, resultVar, false); + } + + /** + * Adds a method invocation instruction to this synthetic method. + * + * @return the invoke instructions added by this operation + * @throws IllegalArgumentException if site is null + */ + public SSAAbstractInvokeInstruction addInvocation(Context c, int[] params, MethodReference target, IInvokeInstruction.IDispatch invocationCode) { + if (target == null || invocationCode == null) { + throw new IllegalArgumentException("target and/or invocationCode is/are null"); + } + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + int nextLocal = triple.getRight(); + CallSiteReference newSite = CallSiteReference.make(statements.size(), target, invocationCode); + SSAAbstractInvokeInstruction s = (newSite.getDeclaredTarget().getReturnType().equals(TypeReference.Void)) ? + this.instructionFactory.InvokeInstruction(statements.size(), params, nextLocal++, newSite, null) : + this.instructionFactory.InvokeInstruction(statements.size(), nextLocal++, params, nextLocal++, newSite, null); + statements.add(s); + + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(statements, triple.getMiddle(), nextLocal)); + return s; + } + + /** + * Add a New statement of the given type + * + *

Side effect: adds call to default constructor of given type if one exists. + * + * @return instruction added, or null + * @throws IllegalArgumentException if T is null + */ + public SSANewInstruction addAllocation(Context c, TypeReference T) { + return addAllocation(c, T, true); + } + +// /** +// * Add a New statement of the given array type and length +// */ +// public SSANewInstruction add1DArrayAllocation(TypeReference T, int length) { +// int instance = nextLocal++; +// NewSiteReference ref = NewSiteReference.make(statements.size(), T); +// assert T.isArrayType(); +// assert ((ArrayClass) cha.lookupClass(T)).getDimensionality() == 1; +// int[] sizes = new int[1]; +// Arrays.fill(sizes, getValueNumberForIntConstant(length)); +// SSANewInstruction result = instructionFactory.NewInstruction(statements.size(), instance, ref, sizes); +// statements.add(result); +//// cache.invalidate(this, Everywhere.EVERYWHERE); +// return result; +// } + +// /** +// * Add a New statement of the given type +// */ +// public SSANewInstruction addAllocationWithoutCtor(TypeReference T) { +// return addAllocation(T, false); +// } + + /** + * Add a New statement of the given type + * + * @param ctx current context + * @param typeRef type being allocated + * @param invokeCtor if the closest non-serializable default constructor should be invoked + * @return instruction added, or null + * @throws IllegalArgumentException if typeRef is null + */ + private SSANewInstruction addAllocation(Context ctx, TypeReference typeRef, boolean invokeCtor) { + if (typeRef == null) { + throw new IllegalArgumentException("typeRef is null"); + } + + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(ctx, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + Integer nextLocal = triple.getRight(); + + int instance = nextLocal++; + SSANewInstruction result = null; + + if (typeRef.isReferenceType()) { + NewSiteReference ref = NewSiteReference.make(statements.size(), typeRef); + if (typeRef.isArrayType()) { + int[] sizes = new int[ArrayClass.getArrayTypeDimensionality(typeRef)]; + Arrays.fill(sizes, getValueNumberForIntConstant(ctx, 1)); + result = instructionFactory.NewInstruction(statements.size(), instance, ref, sizes); + } else { + result = instructionFactory.NewInstruction(statements.size(), instance, ref); + } + statements.add(result); + + IClass klass = cha.lookupClass(typeRef); + if (klass == null) { + Warnings.add(AllocationFailure.create(typeRef)); + return null; + } + + if (klass.isArrayClass()) { + int arrayRef = result.getDef(); + TypeReference e = klass.getReference().getArrayElementType(); + while (e != null && !e.isPrimitiveType()) { + // allocate an instance for the array contents + NewSiteReference n = NewSiteReference.make(statements.size(), e); + int alloc = nextLocal++; + SSANewInstruction ni = null; + if (e.isArrayType()) { + int[] sizes = new int[((ArrayClass) cha.lookupClass(typeRef)).getDimensionality()]; + Arrays.fill(sizes, getValueNumberForIntConstant(ctx, 1)); + ni = instructionFactory.NewInstruction(statements.size(), alloc, n, sizes); + } else { + ni = instructionFactory.NewInstruction(statements.size(), alloc, n); + } + statements.add(ni); + + // emit an astore + SSAArrayStoreInstruction store = + instructionFactory.ArrayStoreInstruction( + statements.size(), arrayRef, getValueNumberForIntConstant(ctx, 0), alloc, e); + statements.add(store); + + e = e.isArrayType() ? e.getArrayElementType() : null; + arrayRef = alloc; + } + } + // update the inner cache + statementsPerContext.put(ctx, new ImmutableTriple<>(statements, triple.getMiddle(), nextLocal)); + + if (invokeCtor) { +// // find the closest default constructor +// IMethod constructor = null; +// IClass superclass = klass.getSuperclass(); +// IClass serialInterface = cha.lookupClass(TypeReference.JavaIoSerializable); +// +// while (constructor == null && superclass != null) { +// if (!cha.implementsInterface(superclass, serialInterface)) +// constructor = cha.resolveMethod(superclass, MethodReference.initSelector); +// superclass = superclass.getSuperclass().getSuperclass(); +// } +// // this assertion should never fail, since it is garanteed that java.lang.Object +// // has such a constructor +// assert constructor != null; +// addInvocation(ctx, new int[]{instance}, constructor.getReference(), IInvokeInstruction.Dispatch.SPECIAL); + + + IMethod ctor = cha.resolveMethod(klass, MethodReference.initSelector); + if (ctor != null) { + addInvocation(ctx, new int[]{instance}, ctor.getReference(), IInvokeInstruction.Dispatch.SPECIAL); + } else { + IMethod superConstructor = klass.getMethod(MethodReference.initSelector); + if (superConstructor != null) + addInvocation(ctx, new int[]{instance}, superConstructor.getReference(), IInvokeInstruction.Dispatch.SPECIAL); + } + } + } + + + return result; + } + + public int getValueNumberForIntConstant(Context c, int constant) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + Map constant2ValueNumber = triple.getMiddle(); + int nextLocal = triple.getRight(); + ConstantValue v = new ConstantValue(constant); + Integer result = constant2ValueNumber.get(v); + if (result == null) { + result = nextLocal++; + constant2ValueNumber.put(v, result); + } + + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(triple.getLeft(), constant2ValueNumber, nextLocal)); + + return result; + } + + public int getNumberOfStatements(Context context) { + Triple, Map, Integer> triple = this.statementsPerContext.get(context); + return triple == null ? 0 : triple.getLeft().size(); + } + +// +// public int getValueNumberForByteConstant(byte c) { +// // treat it like an int constant for now. +// ConstantValue v = new ConstantValue(c); +// Integer result = constant2ValueNumber.get(v); +// if (result == null) { +// result = nextLocal++; +// constant2ValueNumber.put(v, result); +// } +// return result; +// } +// +// public int getValueNumberForCharConstant(char c) { +// // treat it like an int constant for now. +// ConstantValue v = new ConstantValue(c); +// Integer result = constant2ValueNumber.get(v); +// if (result == null) { +// result = nextLocal++; +// constant2ValueNumber.put(v, result); +// } +// return result; +// } + + public SSAPhiInstruction addPhi(Context c, List values) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + Integer nextLocal = triple.getRight(); + + int result = nextLocal++; + int valArray[] = new int[values.size()]; + for (int i = 0; i < values.size(); i++) { + valArray[i] = values.get(i); + } + SSAPhiInstruction phi = instructionFactory.PhiInstruction(statements.size(), result, valArray); + statements.add(phi); + + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(statements, triple.getMiddle(), nextLocal)); + return phi; + } + + public int addGetInstance(Context c, FieldReference ref, int object) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + Integer nextLocal = triple.getRight(); + + // add the instruction + int result = nextLocal++; + statements.add(instructionFactory.GetInstruction(statements.size(), result, object, ref)); + + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(statements, triple.getMiddle(), nextLocal)); + + return result; + } + + public int addCheckcast(Context c, TypeReference[] types, int rv, boolean isPEI) { + Triple, Map, Integer> triple = statementsPerContext.getOrDefault(c, new ImmutableTriple<>(new ArrayList<>(), new HashMap<>(), initialNextLocal)); + List statements = triple.getLeft(); + int nextLocal = triple.getRight(); + int lv = nextLocal++; + statements.add(instructionFactory.CheckCastInstruction(statements.size(), lv, rv, types, isPEI)); + // update the inner cache + statementsPerContext.put(c, new ImmutableTriple<>(statements, triple.getMiddle(), nextLocal)); + return lv; + } + +// public int addGetStatic(FieldReference ref) { +// int result = nextLocal++; +// statements.add(instructionFactory.GetInstruction(statements.size(), result, ref)); +// return result; +// } + + /** + * A warning for when we fail to allocate a type in the fake root method + */ + private static class AllocationFailure extends Warning { + + final TypeReference t; + + AllocationFailure(TypeReference t) { + super(Warning.SEVERE); + this.t = t; + } + + public static AllocationFailure create(TypeReference t) { + return new AllocationFailure(t); + } + + @Override + public String getMsg() { + return getClass().toString() + " : " + t; + } + } + +// public void addSetInstance(final FieldReference ref, final int baseObject, final int value) { +// statements.add(instructionFactory.PutInstruction(statements.size(), baseObject, value, ref)); +// } +// +// public void addSetStatic(final FieldReference ref, final int value) { +// statements.add(instructionFactory.PutInstruction(statements.size(), value, ref)); +// } +// +// public void addSetArrayField( +// final TypeReference elementType, +// final int baseObject, +// final int indexValue, +// final int value) { +// statements.add( +// instructionFactory.ArrayStoreInstruction(statements.size(), baseObject, indexValue, value, elementType)); +// } +// +// public int addGetArrayField( +// final TypeReference elementType, final int baseObject, final int indexValue) { +// int result = nextLocal++; +// statements.add( +// instructionFactory.ArrayLoadInstruction(statements.size(), result, baseObject, indexValue, elementType)); +// return result; +// } +// +// public RTAContextInterpreter getInterpreter() { +// return new RTAContextInterpreter() { +// +// @Override +// public Iterator iterateNewSites(CGNode node) { +// ArrayList result = new ArrayList<>(); +// SSAInstruction[] statements = getStatements(options.getSSAOptions()); +// for (SSAInstruction statement : statements) { +// if (statement instanceof SSANewInstruction) { +// SSANewInstruction s = (SSANewInstruction) statement; +// result.add(s.getNewSite()); +// } +// } +// return result.iterator(); +// } +// +// public Iterator getInvokeStatements() { +// ArrayList result = new ArrayList<>(); +// SSAInstruction[] statements = getStatements(options.getSSAOptions()); +// for (SSAInstruction statement : statements) { +// if (statement instanceof SSAInvokeInstruction) { +// result.add(statement); +// } +// } +// return result.iterator(); +// } +// +// @Override +// public Iterator iterateCallSites(CGNode node) { +// final Iterator I = getInvokeStatements(); +// return new Iterator() { +// @Override +// public boolean hasNext() { +// return I.hasNext(); +// } +// +// @Override +// public CallSiteReference next() { +// SSAInvokeInstruction s = (SSAInvokeInstruction) I.next(); +// return s.getCallSite(); +// } +// +// @Override +// public void remove() { +// Assertions.UNREACHABLE(); +// } +// }; +// } +// +// @Override +// public boolean understands(CGNode node) { +// return node.getMethod() +// .getDeclaringClass() +// .equals(MethodModel.this.getDeclaringClass()); +// } +// +// @Override +// public boolean recordFactoryType(CGNode node, IClass klass) { +// // not a factory type +// return false; +// } +// +// @Override +// public Iterator iterateFieldsRead(CGNode node) { +// return EmptyIterator.instance(); +// } +// +// @Override +// public Iterator iterateFieldsWritten(CGNode node) { +// return EmptyIterator.instance(); +// } +// }; +// } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectInputStreamModel.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectInputStreamModel.java new file mode 100644 index 0000000..eccd6d7 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectInputStreamModel.java @@ -0,0 +1,37 @@ +package edu.rit.se.design.callgraph.model; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.shrikeCT.InvalidClassFileException; +import com.ibm.wala.util.collections.HashSetFactory; + +import static edu.rit.se.design.callgraph.util.NameUtils.readObjectSelector; + +public class ObjectInputStreamModel extends AbstractClassModel { + + + public ObjectInputStreamModel(IClassHierarchy cha, AnalysisOptions options, IAnalysisCacheView cache) { + super(cha, options, cache, "ObjectInputStream"); + } + + @Override + protected void initSyntheticMethods() { + try { + IMethod actualReadObjectMethod = this.originalClass.getMethod(readObjectSelector); + this.methods = HashSetFactory.make(); + this.methods.add( + new MethodModel( + readObjectSelector, + actualReadObjectMethod.getDeclaredExceptions(), + this, + this.originalClass.getClassHierarchy(), + options, + cache) + ); + } catch (InvalidClassFileException e) { + throw new IllegalStateException(e); + } + } +} \ No newline at end of file diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectOutputStreamModel.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectOutputStreamModel.java new file mode 100644 index 0000000..a17f174 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/model/ObjectOutputStreamModel.java @@ -0,0 +1,29 @@ +package edu.rit.se.design.callgraph.model; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.AnalysisOptions; +import com.ibm.wala.ipa.callgraph.IAnalysisCacheView; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.shrikeCT.InvalidClassFileException; +import com.ibm.wala.util.collections.HashSetFactory; + +import static edu.rit.se.design.callgraph.util.NameUtils.writeObjectSelector; + +public class ObjectOutputStreamModel extends AbstractClassModel { + public ObjectOutputStreamModel(IClassHierarchy cha, AnalysisOptions options, IAnalysisCacheView cache) { + super(cha, options, cache, "ObjectOutputStream"); + } + + @Override + protected void initSyntheticMethods() { + // inits synthetic method models + try { + this.methods = HashSetFactory.make(); + IMethod actualWriteObjectMethod = this.originalClass.getMethod(writeObjectSelector); + IClassHierarchy cha = originalClass.getClassHierarchy(); + this.methods.add(new MethodModel(writeObjectSelector, actualWriteObjectMethod.getDeclaredExceptions(), this, cha, options, cache)); + } catch (InvalidClassFileException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/DotCallGraphSerializer.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/DotCallGraphSerializer.java new file mode 100644 index 0000000..80a75bb --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/DotCallGraphSerializer.java @@ -0,0 +1,30 @@ +package edu.rit.se.design.callgraph.serializer; + +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import edu.rit.se.design.dodo.utils.viz.GraphVisualizer; + +import java.io.File; + +/** + * Saves a call graph in dot format. + * + * @author Joanna C. S. Santos + */ +public class DotCallGraphSerializer implements ICallGraphSerializer { + /** + * Saves a file in DOT format. + * + * @param cg call graph + * @param outputFile where to save the file + */ + @Override + public void save(CallGraph cg, File outputFile) { + new GraphVisualizer("Call graph", + GraphVisualizer.getDefaultCgNodeLabeller(), + GraphVisualizer.getDefaultCgNodeHighlighter(), + null, + GraphVisualizer.getDefaultCgNodeRemover(cg)) + .generateVisualGraph(cg, outputFile); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/ICallGraphSerializer.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/ICallGraphSerializer.java new file mode 100644 index 0000000..1e65ede --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/ICallGraphSerializer.java @@ -0,0 +1,16 @@ +package edu.rit.se.design.callgraph.serializer; + +import com.ibm.wala.ipa.callgraph.CallGraph; + +import java.io.File; + +public interface ICallGraphSerializer { + + /** + * Saves a file in a given format. + * + * @param cg call graph + * @param outputFile where to save the file + */ + void save(CallGraph cg, File outputFile); +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JDynCallGraphSerializer.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JDynCallGraphSerializer.java new file mode 100644 index 0000000..ff8a501 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JDynCallGraphSerializer.java @@ -0,0 +1,47 @@ +package edu.rit.se.design.callgraph.serializer; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +import static edu.rit.se.design.callgraph.serializer.JavaCallGraphSerializer.method2String; +import static java.lang.String.format; + +/** + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class JDynCallGraphSerializer implements ICallGraphSerializer { + /** + * Saves a file in a given format. + * + * @param cg call graph + * @param outputFile where to save the file + */ + @Override + public void save(CallGraph cg, File outputFile) { + try { + StringBuilder stringBuilder = new StringBuilder(); + for (CGNode cgNode : cg) { + IMethod method = cgNode.getMethod(); + cg.getSuccNodes(cgNode).forEachRemaining(target -> { + stringBuilder.append( + format("%s %s %s %s\n", + method2String(method), + method2String(target.getMethod()), + method.getDeclaringClass().getClassLoader().getReference().getName().toString(), + target.getMethod().getDeclaringClass().getClassLoader().getReference().getName().toString() + ) + ); + }); + } + + FileUtils.write(outputFile, stringBuilder.toString(), "UTF-8"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JavaCallGraphSerializer.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JavaCallGraphSerializer.java new file mode 100644 index 0000000..91e159c --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JavaCallGraphSerializer.java @@ -0,0 +1,53 @@ +package edu.rit.se.design.callgraph.serializer; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +/** + * Saves a call graph with the same format as the Java Call Graph tool (https://github.com/gousiosg/java-callgraph). + * + * @author Joanna C. S. Santos + */ +public class JavaCallGraphSerializer implements ICallGraphSerializer { + /** + * Converts an {@link IMethod} to a string. + * + * @param method ther method to be converted + * @return a string like fully.qualified.class.Name:methodName + */ + public static String method2String(IMethod method) { + return String.format("%s:%s", + method.getDeclaringClass().getName().toString().substring(1).replace("/", "."), + method.getName().toString()); + } + + /** + * Saves a file in a format that matches the Java Call Graph tool. + * (https://github.com/gousiosg/java-callgraph) + * + * @param cg call graph + * @param outputFile where to save the file + */ + @Override + public void save(CallGraph cg, File outputFile) { + try { + StringBuilder stringBuilder = new StringBuilder(); + for (CGNode cgNode : cg) { + IMethod method = cgNode.getMethod(); + cg.getSuccNodes(cgNode).forEachRemaining(target -> { + stringBuilder.append(String.format("%s %s\n", method2String(method), method2String(target.getMethod()))); + }); + } + + FileUtils.write(outputFile, stringBuilder.toString(), "UTF-8"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JsonJcgSerializer.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JsonJcgSerializer.java new file mode 100644 index 0000000..cdb5c75 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/serializer/JsonJcgSerializer.java @@ -0,0 +1,145 @@ +package edu.rit.se.design.callgraph.serializer; + + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import com.ibm.wala.types.MethodReference; +import com.ibm.wala.types.TypeReference; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.*; + +/** + * Serializes a class using the format described in the paper by Reif et al. + * + * @author Joanna C. S. Santos + */ +public class JsonJcgSerializer implements ICallGraphSerializer { + + + private static String toJVMString(TypeReference typeReference) { + if (typeReference.isClassType() || isArrayOfClassType(typeReference)) { + return typeReference.getName().toString() + ";"; + } else { + return typeReference.getName().toString(); + } + } + + private static boolean isArrayOfClassType(TypeReference typeReference) { + if (typeReference.isArrayType()) { + TypeReference elementType = typeReference.getArrayElementType(); + if (elementType.isClassType()) { + return true; + } else { + return isArrayOfClassType(elementType); + } + } else { + return false; + } + } + + + // Method(name: String, declaringClass: String, returnType: String, parameterTypes: List[String]) + private static JsonObject methodToJson(MethodReference method) { + JsonObject jsonObject = new JsonObject(); + + + String name = method.getName().toString(); + String declaringClass = toJVMString(method.getDeclaringClass()); + String returnType = toJVMString(method.getReturnType()); + JsonArray parameterTypes = new JsonArray(); + for (int i = 0; i < method.getNumberOfParameters(); i++) { + parameterTypes.add(toJVMString(method.getParameterType(i))); + } + + jsonObject.addProperty("name", name); + jsonObject.addProperty("declaringClass", declaringClass); + jsonObject.addProperty("returnType", returnType); + jsonObject.addProperty("parameterTypes", returnType); + + return jsonObject; + } + + // CallSite(declaredTarget: Method, line: Int, pc: Option[Int], targets: Set[Method]) + private static JsonObject callsiteToJson(CallGraph cg, CGNode cgNode, CallSiteReference cs) { + JsonObject jsonObject = new JsonObject(); + MethodReference declaredTarget = cs.getDeclaredTarget(); + int pc = cs.getProgramCounter(); + int lineno = -1; + try { + lineno = cgNode.getMethod().getLineNumber(pc); + } catch (ArrayIndexOutOfBoundsException e) { + lineno = -1; + } + jsonObject.add("declaredTarget", methodToJson(declaredTarget)); + jsonObject.addProperty("line", lineno); + jsonObject.addProperty("pc", pc); + + JsonArray targets = new JsonArray(); + for (CGNode possibleTarget : cg.getPossibleTargets(cgNode, cs)) { + targets.add(methodToJson(possibleTarget.getMethod().getReference())); + } + + + jsonObject.add("targets", targets); + + + return jsonObject; + } + + + /** + * Saves a file in a given format. + * + * @param cg call graph + * @param outputFile where to save the file + */ + @Override + public void save(CallGraph cg, File outputFile) { + + Queue worklist = new LinkedList(cg.getEntrypointNodes()); + Set processed = new HashSet(); + + Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + JsonObject results = new JsonObject(); + JsonArray reachableMethods = new JsonArray(); + + // FORMAT: + // ReachableMethod(method: Method, callSites: Set[CallSite]) + // CallSite(declaredTarget: Method, line: Int, pc: Option[Int], targets: Set[Method]) + // Method(name: String, declaringClass: String, returnType: String, parameterTypes: List[String]) + while (!worklist.isEmpty()) { + CGNode cgNode = worklist.poll(); + processed.add(cgNode); + Iterator callSiteReferenceIterator = cgNode.iterateCallSites(); + callSiteReferenceIterator.forEachRemaining(csr -> { + Set possibleTargets = cg.getPossibleTargets(cgNode, csr); + for (CGNode possibleTarget : possibleTargets) { + if (!processed.contains(possibleTarget)) { + worklist.add(possibleTarget); + } + } + }); + + } + + + results.add("reachableMethods", reachableMethods); + // saves to file + try (Writer writer = new FileWriter(outputFile)) { + gson.toJson(results, writer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/AnalysisUtils.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/AnalysisUtils.java new file mode 100644 index 0000000..5c01217 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/AnalysisUtils.java @@ -0,0 +1,53 @@ +package edu.rit.se.design.callgraph.util; + +import com.ibm.wala.cast.ir.ssa.AstIRFactory; +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.impl.Util; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.ClassHierarchyFactory; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.util.config.AnalysisScopeReader; + +import java.io.File; +import java.io.IOException; + +/** + * Utility class for creating required data structures for call graph construction. + * {@link IClassHierarchy}, {@link AnalysisScope}, {@link AnalysisOptions} + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class AnalysisUtils { + public static AnalysisScope makeAnalysisScope(String sourceFile, File exclusions) throws IOException { + return AnalysisScopeReader.makeJavaBinaryAnalysisScope( + sourceFile, + exclusions); + } + + + public static IClassHierarchy makeIClassHierarchy(AnalysisScope scope) throws ClassHierarchyException { + return ClassHierarchyFactory.make(scope); + } + + public static AnalysisOptions makeAnalysisOptions(AnalysisScope scope, IClassHierarchy cha) { + return makeAnalysisOptions(scope, cha, false); + } + + + public static AnalysisOptions makeAnalysisOptions(AnalysisScope scope, IClassHierarchy cha, boolean enableReflection) { + Iterable entrypoints = Util.makeMainEntrypoints(scope, cha); + + AnalysisOptions options = new AnalysisOptions(); + options.setEntrypoints(entrypoints); + if (enableReflection) + options.setReflectionOptions(AnalysisOptions.ReflectionOptions.FULL); + else + options.setReflectionOptions(AnalysisOptions.ReflectionOptions.NONE); + return options; + } + + + public static AnalysisCache makeAnalysisCache() { + return new AnalysisCacheImpl(AstIRFactory.makeDefaultFactory()); + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/ModelUtils.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/ModelUtils.java new file mode 100644 index 0000000..a23d472 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/ModelUtils.java @@ -0,0 +1,46 @@ +package edu.rit.se.design.callgraph.util; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; + +import java.util.LinkedList; +import java.util.List; + +import static edu.rit.se.design.callgraph.util.NameUtils.readObjectNoDataCallbackSelector; +import static edu.rit.se.design.callgraph.util.NameUtils.validateObjectCallbackSelector; + +/** + * Utilities for abstracting {@link java.io.ObjectInputStream} and {@link java.io.ObjectOutputStream} classes. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class ModelUtils { + public static final boolean DEBUG_SALSA_MODEL_UTILS = true; + + + public static List getDeserializationCallbacks(IClass klass) { + List cbMethods = new LinkedList<>(); + + // if class implements the readObject() callback + IMethod readObjectCallbackMethod = klass.getMethod(NameUtils.readObjectCallbackSelector); + if (readObjectCallbackMethod != null && readObjectCallbackMethod.getDeclaringClass().equals(klass)) + cbMethods.add(readObjectCallbackMethod); + + // if class implements the readObjectNoData() callback + IMethod readObjectNoDataCallbackMethod = klass.getMethod(readObjectNoDataCallbackSelector); + if (readObjectNoDataCallbackMethod != null && readObjectNoDataCallbackMethod.getDeclaringClass().equals(klass)) + cbMethods.add(readObjectNoDataCallbackMethod); + + // if class implements the readResolve() callback + IMethod readResolveCallbackMethod = klass.getMethod(NameUtils.readResolveCallbackSelector); + if (readResolveCallbackMethod != null && readResolveCallbackMethod.getDeclaringClass().equals(klass)) + cbMethods.add(readResolveCallbackMethod); + + // if class implements the validateObject() callback + IMethod validateObjectCallbackMethod = klass.getMethod(validateObjectCallbackSelector); + if (validateObjectCallbackMethod != null && validateObjectCallbackMethod.getDeclaringClass().equals(klass)) + cbMethods.add(validateObjectCallbackMethod); + + return cbMethods; + } +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/NameUtils.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/NameUtils.java new file mode 100644 index 0000000..ae32c04 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/NameUtils.java @@ -0,0 +1,30 @@ +package edu.rit.se.design.callgraph.util; + +import com.ibm.wala.types.Descriptor; +import com.ibm.wala.types.Selector; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.strings.Atom; + +import static com.ibm.wala.types.ClassLoaderReference.Primordial; + +/** + * This class has selectors and type references useful for finding callback methods and creating synthetic methods. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class NameUtils { + // used for finding serialization and deserialization points + public static TypeReference JavaIoObjectInputStream = TypeReference.findOrCreate(Primordial, "Ljava/io/ObjectInputStream"); + public static TypeReference JavaIoObjectOutputStream = TypeReference.findOrCreate(Primordial, "Ljava/io/ObjectOutputStream"); + public static Selector readObjectSelector = new Selector(Atom.findOrCreateAsciiAtom("readObject"), Descriptor.findOrCreateUTF8("()Ljava/lang/Object;")); + public static Selector writeObjectSelector = new Selector(Atom.findOrCreateAsciiAtom("writeObject"), Descriptor.findOrCreateUTF8("(Ljava/lang/Object;)V")); + // used for finding callback methods + public static Selector writeObjectCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("writeObject"), Descriptor.findOrCreateUTF8("(Ljava/io/ObjectOutputStream;)V")); + public static Selector writeReplaceCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("writeReplace"), Descriptor.findOrCreateUTF8("()Ljava/lang/Object;")); + // deserialization callback methods + public static Selector readObjectCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("readObject"), Descriptor.findOrCreateUTF8("(Ljava/io/ObjectInputStream;)V")); + public static Selector readObjectNoDataCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("readObjectNoData"), Descriptor.findOrCreateUTF8("()V")); + public static Selector readResolveCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("readResolve"), Descriptor.findOrCreateUTF8("()Ljava/lang/Object;")); + public static Selector validateObjectCallbackSelector = new Selector(Atom.findOrCreateAsciiAtom("validateObject"), Descriptor.findOrCreateUTF8("()V")); + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/SerializationUtils.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/SerializationUtils.java new file mode 100644 index 0000000..c1d1e14 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/SerializationUtils.java @@ -0,0 +1,170 @@ +package edu.rit.se.design.callgraph.util; + +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.shrikeBT.Constants; +import com.ibm.wala.types.TypeReference; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.ibm.wala.types.ClassLoaderReference.Primordial; +import static com.ibm.wala.types.TypeName.string2TypeName; +import static com.ibm.wala.types.TypeReference.JavaUtilMap; +import static com.ibm.wala.types.TypeReference.JavaUtilSet; +import static edu.rit.se.design.dodo.utils.wala.WalaUtils.isApplicationScope; + + +/** + * Utilities for aiding the inference of possible allocations for fields within deserialized objects. + * + * @author Joanna C. S. Santos - jds5109@rit.edu + */ +public class SerializationUtils { + + + public static final TypeReference JavaUtilList = TypeReference.findOrCreate(Primordial, string2TypeName("Ljava/util/List")); + public static final TypeReference JavaUtilArrayList = TypeReference.findOrCreate(Primordial, string2TypeName("Ljava/util/ArrayList")); + public static final TypeReference JavaUtilHashMap = TypeReference.findOrCreate(Primordial, string2TypeName("Ljava/util/HashMap")); + + + /** + * Determines the category of a {@link TypeReference} as listed in {@link TypeCategory}. + * + * @param cha class hierarchy + * @param typeReference the type under analysis + * @return the type of the field as indicated in {@link TypeCategory}. + */ + public static TypeCategory getTypeCategory(IClassHierarchy cha, TypeReference typeReference) { + // field is primitive + if (typeReference.isPrimitiveType()) + return TypeCategory.PRIMITIVE; + + // field is an array + if (typeReference.isArrayType()) + return TypeCategory.ARRAY; + + IClass fieldClass = cha.lookupClass(typeReference); + + if (fieldClass != null) { + // field is a java.util.List + IClass listClass = cha.lookupClass(JavaUtilList); + if (cha.isAssignableFrom(listClass, fieldClass)) + return TypeCategory.LIST; + + // field is a java.util.Set + IClass setClass = cha.lookupClass(JavaUtilSet); + if (cha.isAssignableFrom(setClass, fieldClass)) + return TypeCategory.SET; + + // field is a java.util.Map + IClass mapClass = cha.lookupClass(JavaUtilMap); + if (cha.isAssignableFrom(mapClass, fieldClass)) + return TypeCategory.MAP; + + // if we reach this point, it was not any of the collection types above, so we just return + return TypeCategory.OBJECT; + } + + return TypeCategory.IGNORED; + } + + + /** + * Compute the set of possible types for a given

object
. + * + * @param cha class hierarchy. + * @param declaringClass the class where the object was declared. + * @param type the static type of the object. + * @param serializableClasses set of classes in the project that are serializable + * @param prune whether we should prune or not upon large sets of possible types + * @param pruningThreshold the size tolerated; above this threshold the set is trimmed to include application-only classes + * @return + */ + public static Set computePossibleTypes(IClassHierarchy cha, IClass declaringClass, IClass type, Set serializableClasses, boolean prune, int pruningThreshold) { + if (type == null) return Collections.emptySet(); + TypeCategory typeCategory = getTypeCategory(cha, type.getReference()); + + switch (typeCategory) { + // three cases below are collection types + case LIST: +// return new HashSet<>(Arrays.asList(cha.lookupClass(JavaUtilArrayList))); + case SET: +// return new HashSet<>(Arrays.asList(cha.lookupClass(JavaUtilHashSet))); + case MAP: +// return new HashSet<>(Arrays.asList(cha.lookupClass(JavaUtilHashMap))); + // for all other cases that the type is not a collection + case OBJECT: + case ARRAY: + + IClass fieldClassType = typeCategory == TypeCategory.ARRAY ? + cha.lookupClass(type.getReference().getInnermostElementType()) : // if is array, the concrete type is based on T[] + type; + + Set concreteTypes = serializableClasses.stream() + .filter(s -> type != null && + !s.isInterface() && !s.isAbstract() && + isAccessible(declaringClass, s) && + isTypeSafe(cha, fieldClassType, s)) + .collect(Collectors.toSet()); + + + // prune away primordial classes if the computed sets are fairly large + if (prune && concreteTypes.size() > pruningThreshold) { + concreteTypes = concreteTypes.stream().filter(c -> isApplicationScope(c)).collect(Collectors.toSet()); + } + return concreteTypes; + } + + throw new UnsupportedOperationException("We only support the computation of possible set for " + Arrays.toString(TypeCategory.values())); + + } + + + /** + * Is the sClass accessible to fieldClass? + * + * @param fieldClass the class that declares the field + * @param sClass possible concrete type (a class to be tested upon) + * @return true if the sClass is accessible to fieldClass (false otherwise). + */ + public static boolean isAccessible(IClass fieldClass, IClass sClass) { + + // one of the three conditions below has to hold + + // (1) class is public + if (sClass.isPublic()) return true; + + // (2) class is package accessible and they are within the same package + boolean isPackageAccessible = ((sClass.getModifiers() & Constants.ACC_PUBLIC) == 0) && ((sClass.getModifiers() & Constants.ACC_PRIVATE) == 0) && ((sClass.getModifiers() & Constants.ACC_PROTECTED) == 0); + boolean isInSamePackage = Objects.equals(sClass.getName().getPackage(), fieldClass.getName().getPackage()); + if (isPackageAccessible && isInSamePackage) return true; + + + // (3) class is protected, wrapped in another class T, and the fieldClass extends T + boolean isProtected = ((sClass.getModifiers() & Constants.ACC_PROTECTED) != 0); + // TODO + // IClass wrapperClass = ?; + // boolean isChildren = cha.isSubclassOf(fieldClass, wrapperClass); + + return false; + + } + + /** + * Does an expression fieldClass x := sClass y typecheck? + * + * @param cha class hierarchy + * @param fieldClass the static type of the field under analysis + * @param sClass possible concrete type + * @return true if sClass a subtype of fieldClass; false otherwise. + */ + public static boolean isTypeSafe(IClassHierarchy cha, IClass fieldClass, IClass sClass) { + return fieldClass != null && sClass != null && cha.isAssignableFrom(fieldClass, sClass); + } + + +} diff --git a/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/TypeCategory.java b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/TypeCategory.java new file mode 100644 index 0000000..ad5aeb0 --- /dev/null +++ b/salsa-src/src/main/java/edu/rit/se/design/callgraph/util/TypeCategory.java @@ -0,0 +1,12 @@ +package edu.rit.se.design.callgraph.util; + + +/** + * Enumerates all possible categories for a static type. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public enum TypeCategory { + PRIMITIVE, ARRAY, OBJECT, LIST, SET, MAP, // common data types + IGNORED // special flag to indicate that the field's static type is not in the CHA due to the exclusion file +} diff --git a/salsa-src/src/main/resources/Java60RegressionExclusions.txt b/salsa-src/src/main/resources/Java60RegressionExclusions.txt new file mode 100644 index 0000000..0a868f5 --- /dev/null +++ b/salsa-src/src/main/resources/Java60RegressionExclusions.txt @@ -0,0 +1,18 @@ +java\/awt\/.* +javax\/swing\/.* +sun\/awt\/.* +sun\/swing\/.* +com\/sun\/.* +sun\/.* +org\/netbeans\/.* +org\/openide\/.* +com\/ibm\/crypto\/.* +com\/ibm\/security\/.* +org\/apache\/xerces\/.* +dalvik\/.* +java\/io\/ObjectStreamClass* +apple\/.* +com\/apple\/.* +jdk\/.* +org\/omg\/.* +org\/w3c\/.* diff --git a/salsa-src/src/main/resources/ObjectInputStreamModel.java.model b/salsa-src/src/main/resources/ObjectInputStreamModel.java.model new file mode 100644 index 0000000..14c9c69 --- /dev/null +++ b/salsa-src/src/main/resources/ObjectInputStreamModel.java.model @@ -0,0 +1,559 @@ +package salsa.model; + +import sun.misc.ObjectInputFilter; + +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.Objects; + +import static com.ibm.wala.shrikeBT.IInvokeInstruction.Dispatch.VIRTUAL; + + +public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants { + /** + * an index to model the current read byte + */ + private final int i; + /** + * filter stream for handling block data conversion + */ + private final Object[] bin; + + + /** + * validation callback list + */ + private final ValidationList vlist; + + + + /** + * Creates an ObjectInputStream that reads from the specified InputStream. + * A serialization stream header is read from the stream and verified. + * This constructor will block until the corresponding ObjectOutputStream + * has written and flushed the header. + * + *

If a security manager is installed, this constructor will check for + * the "enableSubclassImplementation" SerializablePermission when invoked + * directly or indirectly by the constructor of a subclass which overrides + * the ObjectInputStream.readFields or ObjectInputStream.readUnshared + * methods. + * + * @param in input stream to read from + * @throws StreamCorruptedException if the stream header is incorrect + * @throws IOException if an I/O error occurs while reading stream header + * @throws SecurityException if untrusted subclass illegally overrides + * security-sensitive methods + * @throws NullPointerException if in is null + * @see ObjectInputStream#ObjectInputStream() + * @see ObjectInputStream#readFields() + * @see ObjectOutputStream#ObjectOutputStream(OutputStream) + */ + public ObjectInputStream(InputStream in) throws IOException { + this.bin = new Object[Integer.MAX_VALUE]; + this.vlist = new ValidationList(); + this.i = 0; + } + + + public final Object readObject() throws IOException, ClassNotFoundException { + + Object obj = bin[i++]; + boolean hasData = hasData[i]; + if(hasData){ + if ($hasReadObjectMethod($obj)) { + $allocation = $addAllocation($class); + $add($returnValues, $allocation.def); + $addInvocation([$allocation,$THIS], $READ_OBJECT_CB); + } + }else{ + if ($hasReadObjectNoDataMethod($obj)) { + $allocation = $addAllocation($class); + $add($returnValues, $allocation.def); + $addInvocation([$allocation,$THIS], $READ_OBJECT_NO_DATA_CB); + } + } + + + if ($hasReadResolveMethod($obj)) { + $allocation = $addAllocation($class); + $add($returnValues, $allocation.def); + $addInvocation([$allocation,$THIS], $READ_RESOLVE_CB); + } + + + + this.vlist.doCallbacks(); + + } + + + /** + * Reads an "unshared" object from the ObjectInputStream. This method is + * identical to readObject, except that it prevents subsequent calls to + * readObject and readUnshared from returning additional references to the + * deserialized instance obtained via this call. Specifically: + *

    + *
  • If readUnshared is called to deserialize a back-reference (the + * stream representation of an object which has been written + * previously to the stream), an ObjectStreamException will be + * thrown. + * + *
  • If readUnshared returns successfully, then any subsequent attempts + * to deserialize back-references to the stream handle deserialized + * by readUnshared will cause an ObjectStreamException to be thrown. + *
+ * Deserializing an object via readUnshared invalidates the stream handle + * associated with the returned object. Note that this in itself does not + * always guarantee that the reference returned by readUnshared is unique; + * the deserialized object may define a readResolve method which returns an + * object visible to other parties, or readUnshared may return a Class + * object or enum constant obtainable elsewhere in the stream or through + * external means. If the deserialized object defines a readResolve method + * and the invocation of that method returns an array, then readUnshared + * returns a shallow clone of that array; this guarantees that the returned + * array object is unique and cannot be obtained a second time from an + * invocation of readObject or readUnshared on the ObjectInputStream, + * even if the underlying data stream has been manipulated. + * + *

ObjectInputStream subclasses which override this method can only be + * constructed in security contexts possessing the + * "enableSubclassImplementation" SerializablePermission; any attempt to + * instantiate such a subclass without this permission will cause a + * SecurityException to be thrown. + * + * @return reference to deserialized object + * @throws ClassNotFoundException if class of an object to deserialize + * cannot be found + * @throws StreamCorruptedException if control information in the stream + * is inconsistent + * @throws ObjectStreamException if object to deserialize has already + * appeared in stream + * @throws OptionalDataException if primitive data is next in stream + * @throws IOException if an I/O error occurs during deserialization + * @since 1.4 + */ + public Object readUnshared() throws IOException, ClassNotFoundException { + // if nested read, passHandle contains handle of enclosing object + int outerHandle = passHandle; + try { + Object obj = readObject0(true); + handles.markDependency(outerHandle, passHandle); + ClassNotFoundException ex = handles.lookupException(passHandle); + if (ex != null) { + throw ex; + } + if (depth == 0) { + vlist.doCallbacks(); + } + return obj; + } finally { + passHandle = outerHandle; + if (closed && depth == 0) { + clear(); + } + } + } + + /** + * Read the non-static and non-transient fields of the current class from + * this stream. This may only be called from the readObject method of the + * class being deserialized. It will throw the NotActiveException if it is + * called otherwise. + * + * @throws ClassNotFoundException if the class of a serialized object + * could not be found. + * @throws IOException if an I/O error occurs. + * @throws NotActiveException if the stream is not currently reading + * objects. + */ + public void defaultReadObject() throws IOException, ClassNotFoundException { + + + } + + /** + * Reads the persistent fields from the stream and makes them available by + * name. + * + * @return the GetField object representing the persistent + * fields of the object being deserialized + * @throws ClassNotFoundException if the class of a serialized object + * could not be found. + * @throws IOException if an I/O error occurs. + * @throws NotActiveException if the stream is not currently reading + * objects. + * @since 1.2 + */ + public ObjectInputStream.GetField readFields() throws IOException, ClassNotFoundException { + SerialCallbackContext ctx = curContext; + if (ctx == null) { + throw new NotActiveException("not in call to readObject"); + } + Object curObj = ctx.getObj(); + ObjectStreamClass curDesc = ctx.getDesc(); + bin.setBlockDataMode(false); + GetFieldImpl getField = new GetFieldImpl(curDesc); + getField.readFields(); + bin.setBlockDataMode(true); + if (!curDesc.hasWriteObjectData()) { + /* + * Fix for 4360508: since stream does not contain terminating + * TC_ENDBLOCKDATA tag, set flag so that reading code elsewhere + * knows to simulate end-of-custom-data behavior. + */ + defaultDataEnd = true; + } + + return getField; + } + + /** + * Register an object to be validated before the graph is returned. While + * similar to resolveObject these validations are called after the entire + * graph has been reconstituted. Typically, a readObject method will + * register the object with the stream so that when all of the objects are + * restored a final set of validations can be performed. + * + * @param obj the object to receive the validation callback. + * @param prio controls the order of callbacks;zero is a good default. + * Use higher numbers to be called back earlier, lower numbers for + * later callbacks. Within a priority, callbacks are processed in + * no particular order. + * @throws NotActiveException The stream is not currently reading objects + * so it is invalid to register a callback. + * @throws InvalidObjectException The validation object is null. + */ + public void registerValidation(ObjectInputValidation obj, int prio) throws NotActiveException, InvalidObjectException { + vlist.register(obj, prio); + } + + + + + + /** + * Reads a byte of data. This method will block if no input is available. + * + * @return the byte read, or -1 if the end of the stream is reached. + * @throws IOException If an I/O error has occurred. + */ + public int read() throws IOException { + return bin.read(); + } + + /** + * Reads into an array of bytes. This method will block until some input + * is available. Consider using java.io.DataInputStream.readFully to read + * exactly 'length' bytes. + * + * @param buf the buffer into which the data is read + * @param off the start offset of the data + * @param len the maximum number of bytes read + * @return the actual number of bytes read, -1 is returned when the end of + * the stream is reached. + * @throws IOException If an I/O error has occurred. + * @see java.io.DataInputStream#readFully(byte[], int, int) + */ + public int read(byte[] buf, int off, int len) throws IOException { + if (buf == null) { + throw new NullPointerException(); + } + int endoff = off + len; + if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) { + throw new IndexOutOfBoundsException(); + } + return bin.read(buf, off, len, false); + } + + /** + * Returns the number of bytes that can be read without blocking. + * + * @return the number of available bytes. + * @throws IOException if there are I/O errors while reading from the + * underlying InputStream + */ + public int available() throws IOException { + return bin.available(); + } + + /** + * Closes the input stream. Must be called to release any resources + * associated with the stream. + * + * @throws IOException If an I/O error has occurred. + */ + public void close() throws IOException { + /* + * Even if stream already closed, propagate redundant close to + * underlying stream to stay consistent with previous implementations. + */ + closed = true; + if (depth == 0) { + clear(); + } + bin.close(); + } + + /** + * Reads in a boolean. + * + * @return the boolean read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public boolean readBoolean() throws IOException { + return bin.readBoolean(); + } + + /** + * Reads an 8 bit byte. + * + * @return the 8 bit byte read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public byte readByte() throws IOException { + return bin.readByte(); + } + + /** + * Reads an unsigned 8 bit byte. + * + * @return the 8 bit byte read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public int readUnsignedByte() throws IOException { + return bin.readUnsignedByte(); + } + + /** + * Reads a 16 bit char. + * + * @return the 16 bit char read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public char readChar() throws IOException { + return bin.readChar(); + } + + /** + * Reads a 16 bit short. + * + * @return the 16 bit short read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public short readShort() throws IOException { + return bin.readShort(); + } + + /** + * Reads an unsigned 16 bit short. + * + * @return the 16 bit short read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public int readUnsignedShort() throws IOException { + return bin.readUnsignedShort(); + } + + /** + * Reads a 32 bit int. + * + * @return the 32 bit integer read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public int readInt() throws IOException { + return bin.readInt(); + } + + /** + * Reads a 64 bit long. + * + * @return the read 64 bit long. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public long readLong() throws IOException { + return bin.readLong(); + } + + /** + * Reads a 32 bit float. + * + * @return the 32 bit float read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public float readFloat() throws IOException { + return bin.readFloat(); + } + + /** + * Reads a 64 bit double. + * + * @return the 64 bit double read. + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public double readDouble() throws IOException { + return bin.readDouble(); + } + + /** + * Reads bytes, blocking until all bytes are read. + * + * @param buf the buffer into which the data is read + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public void readFully(byte[] buf) throws IOException { + bin.readFully(buf, 0, buf.length, false); + } + + /** + * Reads bytes, blocking until all bytes are read. + * + * @param buf the buffer into which the data is read + * @param off the start offset of the data + * @param len the maximum number of bytes to read + * @throws EOFException If end of file is reached. + * @throws IOException If other I/O error has occurred. + */ + public void readFully(byte[] buf, int off, int len) throws IOException { + int endoff = off + len; + if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) { + throw new IndexOutOfBoundsException(); + } + bin.readFully(buf, off, len, false); + } + + /** + * Skips bytes. + * + * @param len the number of bytes to be skipped + * @return the actual number of bytes skipped. + * @throws IOException If an I/O error has occurred. + */ + public int skipBytes(int len) throws IOException { + return bin.skipBytes(len); + } + + /** + * Reads in a line that has been terminated by a \n, \r, \r\n or EOF. + * + * @return a String copy of the line. + * @throws IOException if there are I/O errors while reading from the + * underlying InputStream + * @deprecated This method does not properly convert bytes to characters. + * see DataInputStream for the details and alternatives. + */ + @Deprecated + public String readLine() throws IOException { + return String.valueOf(bin[i++]); + } + + /** + * Reads a String in + * modified UTF-8 + * format. + * + * @return the String. + * @throws IOException if there are I/O errors while reading from the + * underlying InputStream + * @throws UTFDataFormatException if read bytes do not represent a valid + * modified UTF-8 encoding of a string + */ + public String readUTF() throws IOException { + return bin.readUTF(); + } + + /** + * Prioritized list of callbacks to be performed once object graph has been + * completely deserialized. + */ + private static class ValidationList { + + private static class Callback { + final ObjectInputValidation obj; + final int priority; + Callback next; + + + Callback(ObjectInputValidation obj, int priority, Callback next) { + this.obj = obj; + this.priority = priority; + this.next = next; + this.acc = acc; + } + } + + /** + * linked list of callbacks + */ + private Callback list; + + /** + * Creates new (empty) ValidationList. + */ + ValidationList() { + } + + /** + * Registers callback. Throws InvalidObjectException if callback + * object is null. + */ + void register(ObjectInputValidation obj, int priority) throws InvalidObjectException { + if (obj == null) { + throw new InvalidObjectException("null callback"); + } + + Callback prev = null, cur = list; + while (cur != null && priority < cur.priority) { + prev = cur; + cur = cur.next; + } + AccessControlContext acc = AccessController.getContext(); + if (prev != null) { + prev.next = new Callback(obj, priority, cur, acc); + } else { + list = new Callback(obj, priority, list, acc); + } + } + + /** + * Invokes all registered callbacks and clears the callback list. + * Callbacks with higher priorities are called first; those with equal + * priorities may be called in any order. If any of the callbacks + * throws an InvalidObjectException, the callback process is terminated + * and the exception propagated upwards. + */ + void doCallbacks() throws InvalidObjectException { + while (list != null) { + list.obj.validateObject(); + list = list.next; + } + } + + /** + * Resets the callback list to its initial (empty) state. + */ + public void clear() { + list = null; + } + } + + + + + + +} diff --git a/salsa-src/src/main/resources/exclusions+util+thread.txt b/salsa-src/src/main/resources/exclusions+util+thread.txt new file mode 100644 index 0000000..f3b190c --- /dev/null +++ b/salsa-src/src/main/resources/exclusions+util+thread.txt @@ -0,0 +1,38 @@ +oracle\/.* +javafx\/.* +com\/oracle\/.* +java\/applet\/.* +java\/time\/.* +javax\/imageio\/.* +javax\/sound\/.* +java\/nio\/.* +java\/lang\/Security* +java\/security\/.* +java\/awt\/.* +javax\/swing\/.* +sun\/awt\/.* +sun\/swing\/.* +com\/sun\/.* +sun\/.* +org\/netbeans\/.* +org\/openide\/.* +com\/ibm\/crypto\/.* +com\/ibm\/security\/.* +org\/apache\/xerces\/.* +dalvik\/.* +apple\/.* +com\/apple\/.* +jdk\/.* +org\/omg\/.* +org\/w3c\/.* +java\/util\/concurrent\/.* +java\/util\/function\/.* +java\/util\/jar\/.* +java\/util\/logging\/.* +java\/util\/prefs\/.* +java\/util\/regex\/.* +java\/util\/spi\/.* +java\/util\/stream\/.* +java\/util\/zip\/.* +java\/lang\/Thread* + diff --git a/salsa-src/src/main/resources/exclusions-with-servlets.txt b/salsa-src/src/main/resources/exclusions-with-servlets.txt new file mode 100644 index 0000000..2ce8288 --- /dev/null +++ b/salsa-src/src/main/resources/exclusions-with-servlets.txt @@ -0,0 +1,39 @@ +oracle\/.* +javafx\/.* +com\/oracle\/.* +java\/applet\/.* +java\/time\/.* +javax\/imageio\/.* +javax\/sound\/.* +java\/nio\/.* +java\/lang\/Thread* +java\/lang\/Security* +java\/security\/.* +java\/awt\/.* +javax\/swing\/.* +sun\/awt\/.* +sun\/swing\/.* +com\/sun\/.* +sun\/.* +org\/netbeans\/.* +org\/openide\/.* +com\/ibm\/crypto\/.* +com\/ibm\/security\/.* +org\/apache\/xerces\/.* +dalvik\/.* +apple\/.* +com\/apple\/.* +jdk\/.* +org\/omg\/.* +org\/w3c\/.* +java\/util\/concurrent\/.* +java\/util\/function\/.* +java\/util\/jar\/.* +java\/util\/logging\/.* +java\/util\/prefs\/.* +java\/util\/regex\/.* +java\/util\/spi\/.* +java\/util\/stream\/.* +java\/util\/zip\/.* +java\/lang\/Thread* + diff --git a/salsa-src/src/main/resources/exclusions.txt b/salsa-src/src/main/resources/exclusions.txt new file mode 100644 index 0000000..923194f --- /dev/null +++ b/salsa-src/src/main/resources/exclusions.txt @@ -0,0 +1,27 @@ +oracle\/.* +javafx\/.* +com\/oracle\/.* +java\/applet\/.* +java\/time\/.* +javax\/imageio\/.* +javax\/sound\/.* +java\/nio\/.* +java\/lang\/Security* +java\/security\/.* +java\/awt\/.* +javax\/swing\/.* +sun\/awt\/.* +sun\/swing\/.* +com\/sun\/.* +sun\/.* +org\/netbeans\/.* +org\/openide\/.* +com\/ibm\/crypto\/.* +com\/ibm\/security\/.* +org\/apache\/xerces\/.* +dalvik\/.* +apple\/.* +com\/apple\/.* +jdk\/.* +org\/omg\/.* +org\/w3c\/.* diff --git a/salsa-src/src/test/java/SalsaCliTest.java b/salsa-src/src/test/java/SalsaCliTest.java new file mode 100644 index 0000000..4912a34 --- /dev/null +++ b/salsa-src/src/test/java/SalsaCliTest.java @@ -0,0 +1,13 @@ +import com.ibm.wala.ipa.callgraph.CallGraphBuilderCancelException; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import edu.rit.se.design.callgraph.evaluation.utils.XCorpusTestCases; + +import java.io.IOException; + +public class SalsaCliTest { + + public static void main(String[] args) throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + Salsa.main(XCorpusTestCases.LOG4J_TC_ARGS); + } + +} \ No newline at end of file diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq1/soundness/JCGTest.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq1/soundness/JCGTest.java new file mode 100644 index 0000000..65b4715 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq1/soundness/JCGTest.java @@ -0,0 +1,181 @@ +package edu.rit.se.design.callgraph.evaluation.salsa.rq1.soundness; + +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.types.MethodReference; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.analysis.salsa.SalsaZeroXCallGraphBuilder; +import edu.rit.se.design.callgraph.dispatcher.SerializationDispatcher; +import edu.rit.se.design.callgraph.serializer.JavaCallGraphSerializer; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import static com.ibm.wala.types.ClassLoaderReference.Application; +import static com.ibm.wala.types.ClassLoaderReference.Primordial; +import static edu.rit.se.design.callgraph.TestUtilities.*; +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.*; +import static edu.rit.se.design.callgraph.evaluation.utils.CATSTestCases.*; +import static edu.rit.se.design.callgraph.util.AnalysisUtils.*; +import static java.lang.String.format; + +public class JCGTest { + + + private boolean taintBased; + private CallGraph computedCg; + private String sampleName; + + + private static final PointerAnalysisPolicy policy = +// new PointerAnalysisPolicy(ZeroXCFA,1); +// new PointerAnalysisPolicy(nCFA, 1); + new PointerAnalysisPolicy(nCFA, 2); + + + @BeforeClass + public static void printTestHeader() { + System.out.println("Approach\tTC\tPolicy\t# Nodes\t#Edges"); + } + + @After + public void tearDown() throws Exception { + JavaCallGraphSerializer cgSerializer = new JavaCallGraphSerializer(); + CallGraphStats.CGStats stats = CallGraphStats.getCGStats(computedCg); + + String algorithmName = taintBased ? "Seneca" : "Salsa"; + System.out.println(format("%s\t%s\t%s\t%s\t%s", algorithmName, sampleName, policy, stats.getNNodes(), stats.getNEdges())); + + + String approach; + try { + // if the int parsing succeeds, test case represents a test from the JCG suite (CATS) + int i = Integer.valueOf(sampleName.substring(3, 4)); +// approach = format("Ser%d-Seneca-%s-%s", i, algorithmName, policy.toString()); + approach = format("Ser%d-Salsa-%s", i, policy.toString()); + } catch (NumberFormatException ex) { + // sample is not from JCG test suite + approach = format("%s-Seneca-%s-%s", algorithmName, sampleName, policy.toString()); + } + File txtFile = new File(format("%s/%s.txt", SOAP_2021_STATIC_CGS_FOLDER, approach)); + cgSerializer.save(computedCg, txtFile); + + } + + private CallGraph computeCallGraph(boolean taintBased, String sample) throws ClassHierarchyException, IOException, CallGraphBuilderCancelException { + + File exclusions = new File(EXCLUSIONS_FILE); + // Basic Variables + AnalysisScope scope = makeAnalysisScope(sample, exclusions); + IClassHierarchy cha = makeIClassHierarchy(scope); + AnalysisOptions options = makeAnalysisOptions(scope, cha); + AnalysisCache cache = makeAnalysisCache(); + + CallGraphBuilder builder = SalsaZeroXCallGraphBuilder.make(scope, options, cache, cha, policy); + CallGraph cg = builder.makeCallGraph(options, /*new CustomMonitor()*/ null); + this.computedCg = cg; + this.sampleName = new File(sample).getName(); + this.taintBased = taintBased; + return cg; + } + + +// @Test +// public void testFTfJPRunningExample() throws Exception { +// CallGraph cg = computeCallGraph(false, TestDataSets.RUNNING_EXAMPLE); +// Assert.assertNotNull(cg); +// cg = computeCallGraph(true, TestDataSets.RUNNING_EXAMPLE); +// Assert.assertNotNull(cg); +// } + + +// @Test +// public void testPLDIRunningExample() throws Exception { +// CallGraph cg = computeCallGraph(false, TestDataSets.PLDI_RUNNING_EXAMPLE); +// Assert.assertNotNull(cg); +// cg = computeCallGraph(true, TestDataSets.PLDI_RUNNING_EXAMPLE); +// Assert.assertNotNull(cg); +// } + + + // + @Test + public void testSer1DowncastBased() throws Exception { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER1); + MethodReference from = createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"); + MethodReference to = createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer2DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER2); + MethodReference from = createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"); + MethodReference to = createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer3DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER3); + MethodReference from = createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"); + MethodReference to = createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer4DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER4); + MethodReference from = createMethodRef(Application, "Lser/Demo", "readObject", "(Ljava/io/ObjectInputStream;)V"); + MethodReference to = createMethodRef(Primordial, "Ljava/io/ObjectInputStream", "defaultReadObject", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer5DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER5); + MethodReference from = createMethodRef(Application, "Lser/Demo", "readObject", "(Ljava/io/ObjectInputStream;)V"); + MethodReference to = createMethodRef(Primordial, "Ljava/io/ObjectInputStream", "defaultReadObject", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer6DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER6); + MethodReference from = createMethodRef(Application, "Lser/Demo", "writeReplace", "()Ljava/lang/Object;"); + MethodReference to = createMethodRef(Application, "Lser/Demo", "replace", "()Ljava/lang/Object;"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer7DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER7); + MethodReference from = createMethodRef(Application, "Lser/Demo", "readResolve", "()Ljava/lang/Object;"); + MethodReference to = createMethodRef(Application, "Lser/Demo", "replace", "()Ljava/lang/Object;"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer8DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER8); + MethodReference from = createMethodRef(Application, "Lser/Demo", "validateObject", "()V"); + MethodReference to = createMethodRef(Application, "Lser/Demo", "callback", "()V"); + checkDirectCall(cg, from, to); + } + + @Test + public void testSer9DowncastBased() throws ClassHierarchyException, CallGraphBuilderCancelException, IOException { + CallGraph cg = computeCallGraph(false, CASE_STUDY_SER9); + MethodReference from = createMethodRef(Application, "Lser/Superclass", "", "()V"); + MethodReference to = createMethodRef(Application, "Lser/Superclass", "callback", "()V"); + checkDirectCall(cg, from, to); + } + // + + + +} \ No newline at end of file diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq3/performance/PerformanceTest.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq3/performance/PerformanceTest.java new file mode 100644 index 0000000..a953781 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/salsa/rq3/performance/PerformanceTest.java @@ -0,0 +1,188 @@ +package edu.rit.se.design.callgraph.evaluation.salsa.rq3.performance; + +import com.ibm.wala.cast.ir.ssa.AstIRFactory; +import com.ibm.wala.ipa.callgraph.*; +import com.ibm.wala.ipa.callgraph.impl.AllApplicationEntrypoints; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.ClassHierarchyFactory; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.types.ClassLoaderReference; +import com.ibm.wala.util.MonitorUtil; +import com.ibm.wala.util.config.AnalysisScopeReader; +import com.ibm.wala.util.io.FileUtil; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.analysis.salsa.SalsaNCFACallGraphBuilder; +import edu.rit.se.design.callgraph.analysis.salsa.SalsaZeroXCallGraphBuilder; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.jar.JarFile; + +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.*; + + +public class PerformanceTest { + private static String ROOT = "/Users/joanna/Documents/Portfolio/GitHub/pldi-2021-paper/xcorpus/"; + + private static String OSCACHE = ROOT + "oscache-2.4.1/oscache-2.4.1.jar"; + private static String OPENJMS = ROOT + "openjms-0.7.7-beta-1/openjms-0.7.7-beta-1.jar"; + + + private static String LOG4J = ROOT + "log4j-1.2.16/log4j-1.2.16.jar"; + private static String COMMONSCOLLE = ROOT + "commons-collections-3.2.1/commons-collections-3.2.1.jar"; + private static String HTMLUNIT = ROOT + "htmlunit-2.8/htmlunit-2.8.jar"; + private static String POOKA = ROOT + "pooka-3.0-080505/pooka-3.0-080505.jar"; + + private static final String XALAN = ROOT + "xalan-2.7.1/xalan-2.7.1.jar"; + private static final String CASTOR = ROOT + "castor-1.3.1/castor-1.3.1.jar"; + + + private static final String MEGAMEK = ROOT + "megamek-0.35.18/megamek-0.35.18.jar"; + + + private static String JFREECHART = ROOT + "jfreechart-1.0.13/jfreechart-1.0.13.jar"; + + + private static String JMONEY = ROOT + "jmoney-0.4.4/jmoney-0.4.4.jar"; + private static String JGRAPH = ROOT + "jgraphpad-5.10.0.2/jgraphpad-5.10.0.2.jar"; + private static String XERCES = ROOT + "xerces-2.10.0/xerces-2.10.0.jar"; + + private static String WEKA = ROOT + "weka-3-7-9/weka-3-7-9.jar"; + + + private static void computeCallGraph(String sample, CallGraphBuilder builder, AnalysisOptions options) throws CallGraphBuilderCancelException { + long begin = System.currentTimeMillis(); + CallGraph cg = builder.makeCallGraph(options, new CustomMonitor()); + long end = System.currentTimeMillis(); + System.out.println(builder.getClass().getSimpleName() + "\t" + new File(sample).getName() + "\t" + (end - begin)); + } + + + public static void main(String[] args) throws IOException, CallGraphBuilderCancelException, ClassHierarchyException { + System.out.println("Sample\tTime (ms)"); + // Inputs + String[] samples = new String[]{LOG4J, HTMLUNIT, POOKA, MEGAMEK}; + for (String sample : samples) { + File exclusions = new File("Java60RegressionExclusions.txt"); + + // Basic Variables + AnalysisScope scope = makeAnalysisScope(sample, exclusions); + IClassHierarchy cha = makeIClassHierarchy(scope); + AnalysisOptions options = makeAnalysisOptions(scope, cha); + AnalysisCache cache = makeAnalysisCache(); + +// // WALA 0-1-CFA +// computeCallGraph(sample, Util.makeZeroOneCFABuilder(JAVA, options, cache, cha, scope), options); +// // SALSA 0-1-CFA + computeCallGraph(sample, SalsaZeroXCallGraphBuilder.make(scope, options, cache, cha, new PointerAnalysisPolicy(ZeroXCFA, 1)), options); + + // WALA 1-CFA +// computeCallGraph(sample, Util.makeNCFABuilder(1, options, cache, cha, scope), options); + // SALSA 1-CFA + computeCallGraph(sample, SalsaNCFACallGraphBuilder.make(scope, options, cache, cha, 1, new PointerAnalysisPolicy(nCFA, 1)), options); + +// cg.stream().filter(n -> n.getMethod() instanceof MethodModel).collect(Collectors.toList()); +// new ProjectAnalysisViewer(cg, null, false).setTitle(sample); + } + + + } + + + private static class CustomMonitor implements MonitorUtil.IProgressMonitor { + private boolean isCanceled = false; + + @Override + public void beginTask(String s, int i) { + System.out.println("begin task " + s + " / i = " + i); + } + + @Override + public void subTask(String s) { + System.out.println("sub task " + s); + } + + @Override + public void cancel() { + System.out.println("cancel"); + isCanceled = true; + } + + @Override + public boolean isCanceled() { + return isCanceled; + } + + @Override + public void done() { + System.out.println("done"); + } + + @Override + public void worked(int i) { + System.out.println("worked i = " + i); + } + + @Override + public String getCancelMessage() { + return "Some shit happened?"; + } + } +// + + public static AnalysisScope makeAnalysisScope(String sourceFile, File exclusions) throws IOException { + AnalysisScope scope = AnalysisScopeReader.makeJavaBinaryAnalysisScope( + sourceFile, + exclusions); + File parentFolder = new File(sourceFile).getParentFile(); + File libFolder = new File(parentFolder.getAbsolutePath() + "/default-lib"); + for (String jarFile : getJarsInDirectory(libFolder.getAbsolutePath())) { + scope.addToScope(ClassLoaderReference.Extension, new JarFile(jarFile)); + } + return scope; + } + //NOTE: Code methods below are minor adjustments from the code at: + // - com.ibm.wala.core/src/com/ibm/wala/properties/WalaProperties.java + + /** + * Returns a list of jar files in a given directory + * + * @param dir directory to be searched + * @return + */ + private static String[] getJarsInDirectory(String dir) { + File f = new File(dir); + if (f.exists() && !f.isDirectory()) throw new IllegalArgumentException("Not a directory: " + dir); + + + Collection col = FileUtil.listFiles(dir, ".*\\.jar$", true); + String[] result = new String[col.size()]; + int i = 0; + for (File jarFile : col) result[i++] = jarFile.getAbsolutePath(); + + return result; + } + + public static IClassHierarchy makeIClassHierarchy(AnalysisScope scope) throws ClassHierarchyException { + return ClassHierarchyFactory.make(scope); + } + + public static AnalysisOptions makeAnalysisOptions(AnalysisScope scope, IClassHierarchy cha) { + Iterable entrypoints = + //Util.makeMainEntrypoints(scope, cha); + //.makeMainEntrypoints(scope, cha); + new AllApplicationEntrypoints(scope, cha); + AnalysisOptions options = new AnalysisOptions(); + options.setEntrypoints(entrypoints); + options.setReflectionOptions(AnalysisOptions.ReflectionOptions.NONE); + + return options; + } + + public static AnalysisCache makeAnalysisCache() { + return new AnalysisCacheImpl(AstIRFactory.makeDefaultFactory()); + } +// + +} \ No newline at end of file diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/AbstractCatsTest.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/AbstractCatsTest.java new file mode 100644 index 0000000..f85ed05 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/AbstractCatsTest.java @@ -0,0 +1,158 @@ +package edu.rit.se.design.callgraph.evaluation.utils; + +import com.ibm.wala.ipa.callgraph.CallGraph; +import com.ibm.wala.ipa.callgraph.CallGraphBuilderCancelException; +import com.ibm.wala.ipa.callgraph.CallGraphStats; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.types.MethodReference; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.serializer.JavaCallGraphSerializer; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static com.ibm.wala.types.ClassLoaderReference.Application; +import static com.ibm.wala.types.ClassLoaderReference.Primordial; +import static edu.rit.se.design.callgraph.TestUtilities.checkDirectCall; +import static edu.rit.se.design.callgraph.TestUtilities.createMethodRef; +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.ZeroXCFA; +import static edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy.PolicyType.nCFA; +import static edu.rit.se.design.callgraph.evaluation.utils.CATSTestCases.*; +import static java.lang.String.format; +import static java.lang.String.join; + +/** + * Evaluates a call graph construction algorithm using the Call Graph & Assessement Suite (CATS). + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public abstract class AbstractCatsTest { + private final String outputFolder; + private final Map> expectedResults; + private final String approachName; + private final PointerAnalysisPolicy[] taintedPolicies = new PointerAnalysisPolicy[]{ + new PointerAnalysisPolicy(ZeroXCFA, 1), + new PointerAnalysisPolicy(nCFA, 1), + new PointerAnalysisPolicy(nCFA, 2) + }; + + protected AbstractCatsTest(String outputFolder, String approachName) { + this.outputFolder = outputFolder; + this.approachName = approachName; + this.expectedResults = new HashMap<>(); + expectedResults.put(CASE_STUDY_SER1, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"), + createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER2, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"), + createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER3, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "writeObject", "(Ljava/io/ObjectOutputStream;)V"), + createMethodRef(Primordial, "Ljava/io/ObjectOutputStream", "defaultWriteObject", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER4, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "readObject", "(Ljava/io/ObjectInputStream;)V"), + createMethodRef(Primordial, "Ljava/io/ObjectInputStream", "defaultReadObject", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER5, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "readObject", "(Ljava/io/ObjectInputStream;)V"), + createMethodRef(Primordial, "Ljava/io/ObjectInputStream", "defaultReadObject", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER6, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "writeReplace", "()Ljava/lang/Object;"), + createMethodRef(Application, "Lser/Demo", "replace", "()Ljava/lang/Object;") + ) + ); + expectedResults.put(CASE_STUDY_SER7, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "readResolve", "()Ljava/lang/Object;"), + createMethodRef(Application, "Lser/Demo", "replace", "()Ljava/lang/Object;") + ) + ); + expectedResults.put(CASE_STUDY_SER8, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Demo", "validateObject", "()V"), + createMethodRef(Application, "Lser/Demo", "callback", "()V") + ) + ); + expectedResults.put(CASE_STUDY_SER9, + new ImmutablePair<>( + createMethodRef(Application, "Lser/Superclass", "", "()V"), + createMethodRef(Application, "Lser/Superclass", "callback", "()V") + ) + ); + } + + @BeforeAll + public static void printTestHeader() { + System.out.println("Approach\tTC\tPolicy\t# Nodes\t#Edges"); + } + + @TestFactory + public Collection jcgTests() { + Collection dynamicTests = new ArrayList<>(); + for (String projectPath : expectedResults.keySet()) { + for (PointerAnalysisPolicy policy : taintedPolicies) { + String projectName = FilenameUtils.getBaseName(projectPath).split("-")[0]; + String testName = join("_", projectName, approachName, policy.toString()); + DynamicTest dTest = DynamicTest.dynamicTest(testName, () -> { + CallGraph cg = computeCallGraph(projectPath, policy); + Pair edge = expectedResults.get(projectPath); + checkDirectCall(cg, edge.getLeft(), edge.getRight()); + saveCallGraph(cg, projectName, policy); + }); + dynamicTests.add(dTest); + } + + } + return dynamicTests; + } + + private void saveCallGraph(CallGraph computedCg, String sampleName, PointerAnalysisPolicy policy) { + CallGraphStats.CGStats stats = CallGraphStats.getCGStats(computedCg); + System.out.println(format("%s\t%s\t%s\t%s\t%s", approachName, sampleName, policy, stats.getNNodes(), stats.getNEdges())); + + + String txtFilename; + try { + // if the int parsing succeeds, test case represents a test from the JCG suite (CATS) + int i = Integer.valueOf(sampleName.substring(3, 4)); + txtFilename = format("Ser%d-%s-%s", i, approachName, policy.toString()); + } catch (NumberFormatException ex) { + // sample is not from JCG test suite + txtFilename = format("%s-%s-%s", sampleName, approachName, policy.toString()); + } + + File txtFile = new File(format("%s/%s.txt", outputFolder, txtFilename)); + JavaCallGraphSerializer cgSerializer = new JavaCallGraphSerializer(); + cgSerializer.save(computedCg, txtFile); + } + + + protected abstract CallGraph computeCallGraph(String projectPath, PointerAnalysisPolicy policy) throws IOException, ClassHierarchyException, CallGraphBuilderCancelException; + + +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/CATSTestCases.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/CATSTestCases.java new file mode 100644 index 0000000..8025454 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/CATSTestCases.java @@ -0,0 +1,33 @@ +package edu.rit.se.design.callgraph.evaluation.utils; + +import edu.rit.se.design.callgraph.TestUtilities; + +/** + * Simply acts as an aggregator for paths to case studies (actually used in the paper). + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class CATSTestCases { + // Projects from CATS dataset + public static String CASE_STUDY_SER1 = TestUtilities.TC_ROOT_FOLDER + "Ser1-JRE1.8.jar"; + public static String CASE_STUDY_SER2 = TestUtilities.TC_ROOT_FOLDER + "Ser2-JRE1.8.jar"; + public static String CASE_STUDY_SER3 = TestUtilities.TC_ROOT_FOLDER + "Ser3-JRE1.8.jar"; + public static String CASE_STUDY_SER4 = TestUtilities.TC_ROOT_FOLDER + "Ser4-JRE1.8.jar"; + public static String CASE_STUDY_SER5 = TestUtilities.TC_ROOT_FOLDER + "Ser5-JRE1.8.jar"; + public static String CASE_STUDY_SER6 = TestUtilities.TC_ROOT_FOLDER + "Ser6-JRE1.8.jar"; + public static String CASE_STUDY_SER7 = TestUtilities.TC_ROOT_FOLDER + "Ser7-JRE1.8.jar"; + public static String CASE_STUDY_SER8 = TestUtilities.TC_ROOT_FOLDER + "Ser8-JRE1.8.jar"; + public static String CASE_STUDY_SER9 = TestUtilities.TC_ROOT_FOLDER + "Ser9-JRE1.8.jar"; + + + public static String[] SER1_ARGS = new String[]{ + "-j",CASE_STUDY_SER1, + "-o", "./target/ser1.cg.dot", + "-f","dot", + "--analysis","0-1-CFA", + "--view-ui", + "--print-models", + }; + + +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestDataSets.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestDataSets.java new file mode 100644 index 0000000..94e54ed --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestDataSets.java @@ -0,0 +1,78 @@ +package edu.rit.se.design.callgraph.evaluation.utils; + +/** + * Simply acts as an aggregator for paths to testing programs. + * + * @author Joanna C. S. Santos (jds5109@rit.edu) + */ +public class TestDataSets { + + + public static String ROOT_FOLDER = "/Users/joanna/Documents/Salsa/TestProjects/"; + + public static String RUNNING_EXAMPLE = ROOT_FOLDER + "RunningExample-JRE1.8.jar"; // Example within the FTfJP paper + public static String PLDI_RUNNING_EXAMPLE = ROOT_FOLDER + "PLDIRunningExample-JRE1.8.jar"; // Example within the PLDI paper + public static String SERIALIZATION_EXAMPLE1 = ROOT_FOLDER + "SerializationExample1-JRE1.8.jar"; // More sophisticated within the paper + public static String SERIALIZATION_EXAMPLE2 = ROOT_FOLDER + "SerializationExample2-JRE1.8.jar"; // Example within the paper + public static String REFLECTION_EXAMPLE = ROOT_FOLDER + "ReflectionSample-JRE1.8.jar"; // Used for getting inspiration from the WALA's implementation of reflection features + public static String SOAP_RUNNING_EXAMPLE = ROOT_FOLDER + "SOAPPaperExample-JRE1.8.jar"; // Example within the SOAP paper + + + + + + // Used for demonstrating multiple scenarios where different field types can be used in an exploit + public static String PROPOSAL_EXAMPLE = System.getProperty("user.home") + "/Google Drive/Research Assistant/Projects/Weaknesses/DODO-TestData/sample-code/vulnerable-samples/build/ProposalExample-JRE1.7.jar"; + + + // CVEs + public static String COMMONS_FILE_UPLOAD_VULN = System.getProperty("user.home") + "/Documents/Portfolio/GitHub/pldi-2021-paper/cves/commons-fileupload/commons-fileupload-1.3.2.jar"; + + + // DRIVERS + public static String CLIENT_COMMONS_FILE_UPLOAD = System.getProperty("user.home") + "/Documents/Portfolio/GitHub/pldi-2021-paper/cves/Driver-commons-fileupload-1.3.2.jar"; + + + public static String[] SOAP_RUNNING_EXAMPLE_ARGS = new String[]{ + "-j",SOAP_RUNNING_EXAMPLE, + "-o", "./target/SOAPPaperExample-JRE1.8.cg.dot", + "-f","dot", + "--analysis","1-CFA", + "--view-ui", + "--taint", + "--print-models", + }; + + + public static String[] PROPOSAL_EXAMPLE_ARGS = new String[]{ + "-j",PROPOSAL_EXAMPLE, + "-o", "./target/ProposalExample-JRE1.7.cg.dot", + "-f","dot", + "--analysis","1-CFA", + "--view-ui", + "--taint", + "--print-models", + }; + + + public static String[] CLIENT_COMMONS_FILE_UPLOAD_ARGS = new String[]{ + "-j",CLIENT_COMMONS_FILE_UPLOAD, + "-o", "./target/Driver-commons-fileupload-1.3.2.jar", + "-f","dot", + "--analysis","1-CFA", + "--view-ui", + "--taint", + "--print-models", + }; + + public static String[] COMMONS_FILE_UPLOAD_VULN_ARGS = new String[]{ + "-j",COMMONS_FILE_UPLOAD_VULN, + "-o", "./target/commons-fileupload-1.3.2.jar", + "-f","dot", + "--analysis","1-CFA", + "--view-ui", + "--taint", + "--print-models", + }; + +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestUtilities.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestUtilities.java new file mode 100644 index 0000000..006845c --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/TestUtilities.java @@ -0,0 +1,100 @@ +package edu.rit.se.design.callgraph; + +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ipa.callgraph.CGNode; +import com.ibm.wala.ipa.callgraph.CallGraph; +import com.ibm.wala.types.*; +import com.ibm.wala.util.graph.traverse.DFSAllPathsFinder; +import com.ibm.wala.util.strings.Atom; +import edu.rit.se.design.callgraph.analysis.PointerAnalysisPolicy; +import edu.rit.se.design.callgraph.serializer.ICallGraphSerializer; +import edu.rit.se.design.callgraph.serializer.JDynCallGraphSerializer; +import edu.rit.se.design.dodo.utils.viz.GraphVisualizer; +import org.junit.Assert; + +import java.io.File; +import java.util.List; +import java.util.Set; + + + +public class TestUtilities { + + public static final String TC_ROOT_FOLDER = System.getProperty("user.home") + "/Documents/Portfolio/GitHub/fse-2021-serialization/dataset/build/"; + public static final String EXCLUSIONS_FILE = "exclusions.txt"; + public static final String SOAP_2021_STATIC_CGS_FOLDER = System.getProperty("user.home") + "/Documents/Portfolio/GitHub/soap-2021-paper/static-cgs"; + + + public static int findAllPath(CallGraph cg, MethodReference from, MethodReference to) { + CGNode fromNode = cg.getNodes(from).iterator().next(); + DFSAllPathsFinder finder = new DFSAllPathsFinder<>(cg, fromNode, node -> node.getMethod().getReference().equals(to)); + int count = 0; + List path; + while ((path = finder.find()) != null) { + System.err.println(path); + count++; + } + return count; + } + + /** + * Creates a method reference object needed for performing testing if a given method is in the call graph. + * + * @param cl class loader (application, extension, primordial) + * @param className class name in bytecode format (i.e., L/java/lang/String) + * @param methodName method name + * @param descriptor descriptor in bytecode format (e.g. (Ljava/lang/String)V) + * @return a {@link MethodReference} + */ + public static MethodReference createMethodRef(ClassLoaderReference cl, String className, String methodName, String descriptor) { + TypeName typeName = TypeName.string2TypeName(className); + TypeReference tref = TypeReference.findOrCreate(cl, typeName); + Atom mn = Atom.findOrCreateUnicodeAtom(methodName); + Descriptor md = Descriptor.findOrCreateUTF8(descriptor); + return MethodReference.findOrCreate(tref, mn, md); + } + + /** + * Asserts that there is a direct call from node n to a node x. + * + * @param cg + * @param from + * @param to + */ + public static void checkDirectCall(CallGraph cg, MethodReference from, MethodReference to) { + Set fromNodes = cg.getNodes(from); + Set toNodes = cg.getNodes(to); + Assert.assertTrue("Missing " + from.getSignature() + " from call graph", fromNodes.size() == 1); + Assert.assertTrue("Missing " + to.getSignature() + " from call graph", toNodes.size() == 1); + Assert.assertTrue(cg.hasEdge(fromNodes.iterator().next(), toNodes.iterator().next())); + } + + + private static IMethod getSingleEntrypointMethod(CallGraph cg){ + return cg.getEntrypointNodes().iterator().next().getMethod(); + } + + public static void saveCallGraphs(String outputFolder, CallGraph computedCg, PointerAnalysisPolicy policy, String sampleName, String approachName) { + String filepath = String.format("./target/%s_%s.dot", sampleName, policy); + File outputDotFile = new File(filepath); + new GraphVisualizer("Call graph view for " + sampleName, + GraphVisualizer.getDefaultCgNodeLabeller(), + GraphVisualizer.getDefaultCgNodeHighlighter(), + null, + GraphVisualizer.getDefaultCgNodeRemover(computedCg)) + .generateVisualGraph(computedCg, outputDotFile); + + String mainClass = getSingleEntrypointMethod(computedCg) + .getDeclaringClass() + .getName() + .toString() + .substring(11); + String projectName = sampleName.split("-")[0]; + String filename = String.format("%s-%s-%s.txt", approachName, policy.toString(), mainClass); + File txtFile = new File(String.format("%s/%s/%s", outputFolder, projectName, filename)); + + ICallGraphSerializer cgSerializer = new JDynCallGraphSerializer(); //new JavaCallGraphSerializer(); + cgSerializer.save(computedCg, txtFile); + } + +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/XCorpusTestCases.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/XCorpusTestCases.java new file mode 100644 index 0000000..01562f2 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/evaluation/utils/XCorpusTestCases.java @@ -0,0 +1,43 @@ +package edu.rit.se.design.callgraph.evaluation.utils; + + +import edu.rit.se.design.callgraph.TestUtilities; + +public class XCorpusTestCases { + + + public static final String BATIK_TC = TestUtilities.TC_ROOT_FOLDER + "batik-testcases.jar"; + public static final String CASTOR_TC = TestUtilities.TC_ROOT_FOLDER + "castor-testcases.jar"; + public static final String HTMLUNIT_TC = TestUtilities.TC_ROOT_FOLDER + "htmlunit-testcases.jar"; + public static final String JAMES_TC = TestUtilities.TC_ROOT_FOLDER + "james-testcases.jar"; + public static final String LOG4J_TC = TestUtilities.TC_ROOT_FOLDER + "log4j-testcases.jar"; + public static final String COMMONS_COLLECTION_TC = TestUtilities.TC_ROOT_FOLDER + "commons-collections-testcases.jar"; + public static final String JEDIT_TC = TestUtilities.TC_ROOT_FOLDER + "jedit-testcases.jar"; + public static final String JPF_TC = TestUtilities.TC_ROOT_FOLDER + "jpf-testcases.jar"; + + + // Args + + public static String[] LOG4J_TC_ARGS = new String[]{ + "-j", LOG4J_TC, + "-o", "./target/log4j-testcases.dot", + "-f", "dot", + "--analysis", "1-CFA", + "--view-ui", +// "--taint", + "--print-models", + }; + + + public static String[] JPF_TC_ARGS = new String[]{ + "-j", JPF_TC, + "-o", "./target/jpf-testcases.dot", + "-f", "dot", + "--analysis", "1-CFA", + "--view-ui", + "--taint", + "--print-models", + }; + + +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/Demo.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/Demo.java new file mode 100644 index 0000000..bd5d46e --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/Demo.java @@ -0,0 +1,64 @@ +package edu.rit.se.design.callgraph.examples; + +import java.io.*; + +public class Demo extends Superclass implements Serializable, ObjectInputValidation { + + static final long serialVersionUID = 42L; + + public void validateObject() throws InvalidObjectException { + System.out.println("Demo.validateObject()"); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.registerValidation(this, 0); + in.defaultReadObject(); + System.out.println("Demo.readObject()"); + } + private void readObjectNoData() throws ObjectStreamException{ + System.out.println("Demo.readObjectNoData()"); + } + + private Object readResolve() throws ObjectStreamException { + System.out.println("Demo.readResolve()"); + return this; + } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + System.out.println("Demo.writeObject()"); + } + private Object writeReplace() throws ObjectStreamException { + System.out.println("Demo.writeReplace()"); + return this; + } + + public static void main(String[] args) throws Exception { + + System.out.println("SERIALIZATION"); + Demo serialize = new Demo(); + FileOutputStream fos = new FileOutputStream("test.ser"); + ObjectOutputStream out = new ObjectOutputStream(fos); + out.writeObject(serialize); + out.close(); + + System.out.println("DESERIALIZATION"); + + FileInputStream fis = new FileInputStream("test.ser"); + ObjectInputStream in = new ObjectInputStream(fis); + Demo obj = (Demo) in.readObject(); + in.close(); + + + } +} + +class Superclass { + public void callback() { + System.out.println("Superclass.callback()"); + } + + public Superclass() { + callback(); + } +} diff --git a/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/PaperExample.java b/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/PaperExample.java new file mode 100644 index 0000000..4904a79 --- /dev/null +++ b/salsa-src/src/test/java/edu/rit/se/design/callgraph/examples/PaperExample.java @@ -0,0 +1,78 @@ +package edu.rit.se.design.callgraph.examples; + +import java.io.*; +import java.util.Arrays; +import java.util.List; + +public class PaperExample { + + + public static void main(String[] args) throws Exception { + // serialization + Shelter s1 = new Shelter( + Arrays.asList(new Dog("Max"), new Cat("Joy")) + ); + FileOutputStream f = new FileOutputStream(new File("shelter.ser")); + ObjectOutputStream out = new ObjectOutputStream(f); + out.writeObject(s1); + + // deserialization + FileInputStream fs = new FileInputStream(new File("shelter.ser")); + ObjectInputStream in = new ObjectInputStream(fs); + Shelter s2 = (Shelter) in.readObject(); + new File("shelter.ser").delete(); + } + + +} + + +class Pet implements Serializable { + protected String name; + public Pet(String name) { + this.name = name; + } +} + +class Cat extends Pet implements Serializable { + public Cat(String name) { + super(name); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + System.out.println("Cat.readObject()"); + } + + private void writeObject(ObjectOutputStream s) + throws IOException { + s.defaultWriteObject(); + System.out.println("Cat.writeObject()"); + } +} + +class Dog extends Pet implements Serializable { + + public Dog(String name) { + super(name); + } + + private Object readResolve() throws ObjectStreamException { + System.out.println("Dog.readResolve()"); + return this; + } + + private Object writeReplace() throws ObjectStreamException { + System.out.println("Dog.writeReplace()"); + return this; + } +} + +class Shelter implements Serializable { + private List pets; + + public Shelter(List pets) { + this.pets = pets; + } +}