diff --git a/base_image.dockerfile b/base_image.dockerfile
index 2da55d58..ed7668a3 100644
--- a/base_image.dockerfile
+++ b/base_image.dockerfile
@@ -6,7 +6,7 @@ RUN apt-get update -y && apt-get upgrade -y && \
apt-get install -y cmake z3 python3.10 python3.10-dev python3-distutils python3-pip python3-apt python3.10-venv pkg-config libcairo2-dev libjpeg-dev libgif-dev \
openjdk-8-jdk git maven wget && \
DEBIAN_FRONTEND=noninteractive \
- apt-get install -y --no-install-recommends --assume-yes build-essential libpq-dev unzip
+ apt-get install -y --no-install-recommends --assume-yes build-essential libpq-dev unzip libcairo2-dev pkg-config python3.10-dev
RUN apt-get update && apt-get install -y nodejs npm
FROM python-build AS ecstatic-build
diff --git a/requirements.txt b/requirements.txt
index f9ffc6a2..8a28fd0a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,7 @@ pytest~=6.2.5
frozendict~=2.1.3
jsonschema~=4.3.2
networkx~=2.6.3
+requests==2.28.1
docker~=5.0.3
matplotlib~=3.5.1
jsonpickle~=2.1.0
diff --git a/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py b/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py
new file mode 100644
index 00000000..e112e8ed
--- /dev/null
+++ b/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py
@@ -0,0 +1,66 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+from typing import Iterable, Tuple
+
+from src.ecstatic.debugging.AbstractDeltaDebugger import DeltaDebuggingPredicate, GroundTruth
+from src.ecstatic.debugging.BenchmarkDeltaDebugger import BenchmarkDeltaDebugger
+from src.ecstatic.debugging.JavaDeltaDebugger import JavaDeltaDebugger
+from src.ecstatic.util.PartialOrder import PartialOrderType
+from src.ecstatic.util.PotentialViolation import PotentialViolation
+
+logger = logging.getLogger(__name__)
+
+
+class JavaBenchmarkDeltaDebugger(BenchmarkDeltaDebugger, JavaDeltaDebugger):
+ def make_predicates(self, potential_violation: PotentialViolation) -> Iterable[Tuple[DeltaDebuggingPredicate, GroundTruth]]:
+ """
+ Returns the predicates and ground truths for a potential violation. If the potential violation's main
+ partial order (i.e., in the case that there are two potential partial order violations, we want the partial
+ order that matches the order of the jobs, such that job1 has some relationship to job2).
+ :param potential_violation: The non-violation
+ :return:
+ """
+ mp = potential_violation.get_main_partial_order()
+ if (not potential_violation.is_violation) and (len(potential_violation.expected_diffs) > 0) and \
+ mp.is_explicit():
+ match mp.type:
+ case PartialOrderType.MORE_SOUND_THAN:
+ # If it's a soundness partial order, then we are not sure how many of the additional edges
+ # produces by the more sound configuration
+ def predicate(pv: PotentialViolation):
+ return pv.expected_diffs <= potential_violation.expected_diffs
+ ground_truth = {
+ "partial_order": str(mp),
+ "left_preserve_at_least_one": [str(e) for e in potential_violation.expected_diffs]
+ }
+ yield predicate, ground_truth
+ case PartialOrderType.MORE_PRECISE_THAN:
+ # If it's a precision partial order, we create a new benchmark for each of the edges missing in the
+ # more precise result, since we can be confident they are all false positives.
+ for e in potential_violation.expected_diffs:
+ def predicate(pv: PotentialViolation):
+ return e in pv.expected_diffs
+ ground_truth = {
+ "partial_order": str(mp),
+ "left_preserve_all": [str(e)]
+ }
+ yield predicate, ground_truth
+ case _:
+ raise RuntimeError("Pattern matching on main partial order failed.")
+
diff --git a/src/ecstatic/dispatcher/DockerManager.py b/src/ecstatic/dispatcher/DockerManager.py
index 51b7b2ff..68c80ff0 100644
--- a/src/ecstatic/dispatcher/DockerManager.py
+++ b/src/ecstatic/dispatcher/DockerManager.py
@@ -30,7 +30,6 @@
def build_image(tool: str, nocache: bool = False):
env = os.environ
- os.environ["DOCKER_DEFAULT_PLATFORM"] = "linux/amd64"
if tool == 'base':
logger.info("Creating base image")
cmd = ['docker', 'build', '.', '-f', 'base_image.dockerfile', '-t', get_image_name(tool)]
diff --git a/src/ecstatic/readers/AmanDroidReader.py b/src/ecstatic/readers/AmanDroidReader.py
new file mode 100644
index 00000000..42c96c78
--- /dev/null
+++ b/src/ecstatic/readers/AmanDroidReader.py
@@ -0,0 +1,44 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+from typing import List, Tuple
+
+from src.ecstatic.readers.AbstractReader import AbstractReader
+
+
+logger = logging.getLogger(__name__)
+class AmanDroidReader(AbstractReader):
+
+ def import_file(self, file):
+ flows: List[Tuple[str, str]] = []
+ isFlow = False
+ with open(file) as f:
+ lines = f.readlines()
+ for line in lines:
+ if isFlow:
+ if 'Source:' in line:
+ source = line.split(': ', 1)[1]
+ elif 'Sink:' in line:
+ sink = line.split(': ', 1)[1]
+ flows.append((source, sink))
+ isFlow = isFlow == False
+ else:
+ if 'TaintPath:' in line:
+ isFlow = isFlow == False
+ return flows
+
\ No newline at end of file
diff --git a/src/ecstatic/readers/DroidSafeReader.py b/src/ecstatic/readers/DroidSafeReader.py
new file mode 100644
index 00000000..d152bf6b
--- /dev/null
+++ b/src/ecstatic/readers/DroidSafeReader.py
@@ -0,0 +1,37 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+from typing import List, Tuple
+
+from src.ecstatic.readers.AbstractReader import AbstractReader
+
+
+logger = logging.getLogger(__name__)
+class DroidSafeReader(AbstractReader):
+
+ def import_file(self, file):
+ flows: List[Tuple[str, str]] = []
+ with open(file) as f:
+ lines = f.readlines()
+ for line in lines:
+ if line.startswith('FLOW:'):
+ source = line.rsplit('|', 1)[-1].split('<=')[1]
+ sink = line.rsplit('|', 1)[-1].split('<=')[0]
+ flows.append((source, sink))
+ return flows
+
\ No newline at end of file
diff --git a/src/ecstatic/readers/ReaderFactory.py b/src/ecstatic/readers/ReaderFactory.py
index 960f9b60..697fe112 100644
--- a/src/ecstatic/readers/ReaderFactory.py
+++ b/src/ecstatic/readers/ReaderFactory.py
@@ -22,6 +22,11 @@
from src.ecstatic.readers.callgraph.DOOPCallGraphReader import DOOPCallGraphReader
from src.ecstatic.readers.callgraph.SOOTCallGraphReader import SOOTCallGraphReader
from src.ecstatic.readers.callgraph.WALACallGraphReader import WALACallGraphReader
+from src.ecstatic.readers.callgraph.TAJSCallGraphReader import TAJSCallGraphReader
+from src.ecstatic.readers.callgraph.WALAJSCallGraphReader import WALAJSCallGraphReader
+from src.ecstatic.readers.DroidSafeReader import DroidSafeReader
+from src.ecstatic.readers.AmanDroidReader import AmanDroidReader
+
def get_reader_for_task_and_tool(task: str, name: str, *args) -> Any:
match task.lower():
@@ -29,10 +34,14 @@ def get_reader_for_task_and_tool(task: str, name: str, *args) -> Any:
match name.lower():
case "soot": return SOOTCallGraphReader(*args)
case "wala": return WALACallGraphReader(*args)
+ case "wala-js": return WALAJSCallGraphReader(*args)
case "doop": return DOOPCallGraphReader(*args)
+ case "tajs": return TAJSCallGraphReader(*args)
case _: raise NotImplementedError(f"No support for task {task} on tool {name}")
case "taint":
match name.lower():
case "flowdroid": return FlowDroidFlowReader(*args)
+ case "droidsafe": return DroidSafeReader(*args)
+ case "amandroid": return AmanDroidReader(*args)
case _: raise NotImplementedError(f"No support for task {task} on tool {name}")
case _: raise NotImplementedError(f"No support for task {task}.")
diff --git a/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py b/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py
new file mode 100644
index 00000000..d9036071
--- /dev/null
+++ b/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py
@@ -0,0 +1,45 @@
+from src.ecstatic.readers.callgraph.AbstractCallGraphReader import AbstractCallGraphReader
+from src.ecstatic.util.CGCallSite import CGCallSite
+from src.ecstatic.util.CGTarget import CGTarget
+
+from abc import ABC
+from typing import Tuple, List, Any
+
+class TAJSCallGraphReader(AbstractCallGraphReader):
+ def import_file(self, file):
+ callgraph: List[Tuple[Any, Any]] = []
+ edges = []
+ nodes = {}
+ with open(file) as f:
+ lines = f.readlines()
+ for i in lines:
+ # print(i)
+ if "->" in i:
+ parts = i.strip().split(" -> ")
+ edges.append((parts[0], parts[1]))
+ print("edge:", i)
+ else:
+ parts = []
+ node = []
+ try:
+ parts = i.strip().split(" ")
+ node = parts[2].strip(" label=").strip('"]').strip('"]}').split("\\n")
+ except:
+ parts = i.strip().split(" ")
+ nodes[parts[0]] = node
+ print("node:", i)
+ for j in edges:
+ callid, targetid = j
+ call_funct = nodes[callid][0]
+ if (nodes[callid] != ['']):
+ call_location = nodes[callid][1]
+ else:
+ call_location = ''
+ target_funct = nodes[targetid][0]
+ target_location = nodes[targetid][1]
+ # add tuple of CGCallsite and CGTarget to list
+ callsite = CGCallSite(call_funct, call_location, '')
+ target = CGTarget(target_location, '')
+ print(call_funct, call_location, target_location)
+ callgraph.append((callsite, target))
+ return callgraph
\ No newline at end of file
diff --git a/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py b/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py
new file mode 100644
index 00000000..a81bc284
--- /dev/null
+++ b/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py
@@ -0,0 +1,30 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+from typing import Tuple, Any
+
+from src.ecstatic.readers.callgraph.AbstractCallGraphReader import AbstractCallGraphReader
+from src.ecstatic.util.CGCallSite import CGCallSite
+from src.ecstatic.util.CGTarget import CGTarget
+
+
+class WALAJSCallGraphReader(AbstractCallGraphReader):
+
+ def process_line(self, line: str) -> Tuple[Any, Any]:
+ match line.split("\t"):
+ case [caller_function, caller_line, caller_context, callee_target, callee_context]:
+ return CGCallSite(caller_function, caller_line, caller_context), CGTarget(callee_target, callee_context)
+ case _: raise ValueError(f"Could not read line {line}")
\ No newline at end of file
diff --git a/src/ecstatic/runners/AmanDroidRunner.py b/src/ecstatic/runners/AmanDroidRunner.py
new file mode 100644
index 00000000..f5eb186c
--- /dev/null
+++ b/src/ecstatic/runners/AmanDroidRunner.py
@@ -0,0 +1,78 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public Licese for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import importlib
+import logging
+import os
+import shutil
+import subprocess
+import uuid
+from typing import Tuple, List
+
+from src.ecstatic.runners.CommandLineToolRunner import CommandLineToolRunner
+from src.ecstatic.util.UtilClasses import BenchmarkRecord
+from src.ecstatic.util import FuzzingJob
+
+
+logger = logging.getLogger(__name__)
+
+class AmanDroidRunner (CommandLineToolRunner):
+
+ def get_timeout_option(self) -> List[str]:
+ print("Amandroid does not support the timeout option. Sorry!")
+ exit(-1)
+ return []
+
+ def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]:
+ return f"{benchmark_record.name}".split()
+
+ def get_output_option(self, output_file: str) -> List[str]:
+ return f"-o {output_file}".split()
+
+ def get_base_command(self) -> List[str]:
+ return "java -jar /amandroid/argus-saf-3.2.1-SNAPSHOT-assembly.jar t".split()
+
+ def try_run_job(self, job: FuzzingJob, output_folder: str) -> Tuple[str, str]:
+ logging.info(f'Job configuration is {[(str(k), str(v)) for k, v in job.configuration.items()]}')
+ config_hash = self.dict_hash(job.configuration)
+ id = uuid.uuid1().hex
+ result_dir = f'/amandroid/{config_hash}_{id}/'
+ cmd = self.get_base_command()
+ cmd.extend(self.get_output_option(result_dir))
+ cmd.extend(self.get_input_option(job.target))
+ logging.info(f"Cmd is {cmd}")
+ print(f"Cmd is {cmd}")
+
+ ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
+ logger.info(f"Stdout from command {' '.join(cmd)} is {ps.stdout}")
+
+ try:
+ result_dir_full = os.path.join(result_dir, os.path.basename(job.target.name).replace('.apk', ''))
+ intermediate_file = os.path.join(result_dir_full, 'result/AppData.txt')
+ except UnboundLocalError:
+ raise RuntimeError(ps.stdout)
+
+ output_file = self.get_output(output_folder, job)
+ shutil.move(intermediate_file, output_file)
+ logging.info(f'Moved {intermediate_file} to {output_file}')
+
+ if not os.path.exists(output_file):
+ raise RuntimeError(ps.stdout)
+ return output_file, ps.stdout
+
+
diff --git a/src/ecstatic/runners/DroidSafeRunner.py b/src/ecstatic/runners/DroidSafeRunner.py
new file mode 100644
index 00000000..1cf10776
--- /dev/null
+++ b/src/ecstatic/runners/DroidSafeRunner.py
@@ -0,0 +1,69 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import importlib
+import logging
+import os
+import shutil
+import subprocess
+import uuid
+from typing import Tuple, List
+
+from src.ecstatic.runners.AbstractCommandLineToolRunner import AbstractCommandLineToolRunner
+from src.ecstatic.util import FuzzingJob
+
+
+logger = logging.getLogger(__name__)
+
+
+class DroidSafeRunner(AbstractCommandLineToolRunner):
+
+ def get_timeout_option(self) -> List[str]:
+ print("Droidsafe does not support the timeout option. Sorry!")
+ exit(-1)
+ return []
+
+ def try_run_job(self, job: FuzzingJob, output_folder: str) -> Tuple[str, str]:
+ target_basedir = os.path.join(os.getenv('DROIDSAFE_SRC_HOME'), 'runs')
+ app_name = os.path.basename(job.target.name).replace('.apk', '')
+ config_hash = self.dict_hash(job.configuration)
+ id = uuid.uuid1().hex
+ target_dir = os.path.join(target_basedir, f'{config_hash}_{app_name}_{id}')
+
+ droidsafe_shell = importlib.resources.path(f"src.resources.tools.droidsafe", "droidsafe.sh")
+ cmd = [droidsafe_shell]
+ cmd.append(job.target.name)
+ cmd.append(f'{config_hash}_{app_name}_{id}')
+ cmd.append(self.dict_to_config_str(job.configuration))
+ logger.info(f'Running job with configuration {self.dict_hash(job.configuration)} on apk {job.target.name}')
+ ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
+ logger.info(f'Stdout for cmd {" ".join([str(c) for c in cmd])} was {ps.stdout}')
+ logger.info(f'Job on configuration {self.dict_hash(job.configuration)} on apk {job.target.name} done.')
+
+ output_file = self.get_output(output_folder, job)
+
+ try:
+ target_dir_gen = os.path.join(target_dir, 'droidsafe-gen')
+ intermediate_file = os.path.join(target_dir_gen, "info-flow-results.txt")
+ except UnboundLocalError:
+ raise RuntimeError(ps.stdout)
+ shutil.copyfile(intermediate_file, output_file)
+ logger.info(f'Copied {intermediate_file} to {output_file}')
+
+ return output_file, ps.stdout
+
diff --git a/src/ecstatic/runners/RunnerFactory.py b/src/ecstatic/runners/RunnerFactory.py
index de9cd576..c960c4d3 100644
--- a/src/ecstatic/runners/RunnerFactory.py
+++ b/src/ecstatic/runners/RunnerFactory.py
@@ -22,7 +22,12 @@
from src.ecstatic.runners.DOOPRunner import DOOPRunner
from src.ecstatic.runners.FlowDroidRunner import FlowDroidRunner
from src.ecstatic.runners.SOOTRunner import SOOTRunner
+from src.ecstatic.runners.WALAJSRunner import WALAJSRunner
from src.ecstatic.runners.WALARunner import WALARunner
+from src.ecstatic.runners.TAJSRunner import TAJSRunner
+from src.ecstatic.runners.DroidSafeRunner import DroidSafeRunner
+from src.ecstatic.runners.AmanDroidRunner import AmanDroidRunner
+
logger = logging.getLogger(__name__)
@@ -33,4 +38,8 @@ def get_runner_for_tool(name: str, *args) -> AbstractCommandLineToolRunner:
case "wala": return WALARunner(*args)
case "doop": return DOOPRunner(*args)
case "flowdroid": return FlowDroidRunner(*args)
+ case "wala-js": return WALAJSRunner(*args)
+ case "tajs": return TAJSRunner(*args)
+ case "droidsafe": return DroidSafeRunner(*args)
+ case "amandroid": return AmanDroidRunner(*args)
case _ : raise NotImplementedError(f"No support for runner for {name}")
diff --git a/src/ecstatic/runners/TAJSRunner.py b/src/ecstatic/runners/TAJSRunner.py
new file mode 100644
index 00000000..4b463431
--- /dev/null
+++ b/src/ecstatic/runners/TAJSRunner.py
@@ -0,0 +1,84 @@
+import os.path
+from typing import List, Dict
+
+from src.ecstatic.runners.CommandLineToolRunner import CommandLineToolRunner
+from src.ecstatic.util.UtilClasses import BenchmarkRecord
+
+
+class TAJSRunner (CommandLineToolRunner):
+ def get_timeout_option(self) -> List[str]:
+ if self.timeout is None:
+ return []
+ else:
+ return f"-time-limit {self.timeout * 60}".split(" ")
+
+ def get_whole_program(self) -> List[str]:
+ # shouldn't be necessary
+ return []
+
+ def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]:
+ return f"{benchmark_record.name}".split()
+
+ def get_output_option(self, output_file: str) -> List[str]:
+ # callgraph recieves file path argument
+ return f"-callgraph {output_file}".split()
+
+ def get_task_option(self, task: str) -> List[str]:
+ # might need to add to this
+ if task == 'cg':
+ return []
+ else:
+ raise NotImplementedError(f'TAJS does not support task {task}.')
+
+ def dict_to_config_str(self, config_as_dict: Dict[str, str]) -> str:
+ """
+ We need special handling of TAJS's options, because of unsound options and commands without level value
+
+
+ Parameters
+ ----------
+ config_as_dict: The dictionary specifying the configuration.
+ Returns
+ -------
+ The corresponding command-line string.
+ """
+ config_as_str = ""
+ for k, v in config_as_dict.items():
+ k: Option
+ v: Level
+ for t in k.tags:
+ if t.startswith('unsound'):
+ config_as_str = config_as_str + f"-unsound -{k.name} "
+ rest_of_config = ""
+ for k, v in config_as_dict.items():
+ if len([t for t in k.tags if t.startswith('unsound')]) == 0:
+ k: Option
+ v: Level
+ if isinstance(v.level_name, int) or \
+ v.level_name.lower() not in ['false', 'true']:
+ rest_of_config += f'-{k.name} {v.level_name} '
+ elif v.level_name.lower() == 'true':
+ rest_of_config += f'-{k.name} '
+#
+ # Compute string for the rest of the options which are not unsound.
+ # this part may not work
+ return rest_of_config + config_as_str
+ #config_as_str = "";
+ #for key in config_as_dict:
+ # if key.startswith('isunsound_'):
+ # #phase option, append like this -p 'phase' 'key':'value'
+ #format is "phase_'phase'_'key'" = VALUE
+ # config_as_str = config_as_str + f"-unsound -{key[key.find('_')+1:]} "
+ # else:
+ # if(config_as_dict[key] == 'true'):
+ # config_as_str = config_as_str + f"-{key} "
+ # elif(config_as_dict[key] == 'false'):
+ # config_as_str = config_as_str; #dumb options that are turned off by not giving them?
+ # else:
+ # config_as_str = config_as_str + f"-{key} {config_as_dict[key]} "
+ #
+ #return config_as_str;
+
+ def get_base_command(self) -> List[str]:
+ return "java -jar /TAJS/dist/tajs-all.jar".split()
+
diff --git a/src/ecstatic/runners/WALAJSRunner.py b/src/ecstatic/runners/WALAJSRunner.py
new file mode 100644
index 00000000..16651320
--- /dev/null
+++ b/src/ecstatic/runners/WALAJSRunner.py
@@ -0,0 +1,32 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+from typing import List
+
+from src.ecstatic.runners.WALARunner import WALARunner
+from src.ecstatic.util.UtilClasses import BenchmarkRecord
+
+
+class WALAJSRunner(WALARunner):
+ def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]:
+ return f"--scripts {benchmark_record.name}".split(" ")
+
+ def get_output_option(self, output_file: str) -> List[str]:
+ return f"--cgoutput {output_file}".split(" ")
+
+
+ def get_base_command(self) -> List[str]:
+ return "java -jar /WalaJSCallgraph/target/jscallgraph-0.0.1-SNAPSHOT-jar-with-dependencies.jar".split(" ")
\ No newline at end of file
diff --git a/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py b/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py
new file mode 100644
index 00000000..9e691a64
--- /dev/null
+++ b/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py
@@ -0,0 +1,43 @@
+# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration
+#
+# Copyright (c) 2022.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+
+from src.ecstatic.models.Flow import Flow
+from src.ecstatic.violation_checkers.AbstractViolationChecker import AbstractViolationChecker
+from src.ecstatic.readers.AbstractReader import AbstractReader
+from pathlib import Path
+from typing import Optional, TypeVar
+
+logger = logging.getLogger(__name__)
+T = TypeVar('T') # Indicates the type of content in the results (e.g., call graph edges or flows)
+class AndroidTaintViolationChecker(AbstractViolationChecker):
+
+ def __init__(self, jobs: int, reader: AbstractReader, output_folder: Path, ground_truths: Optional[Path] = None,
+ write_to_files=True):
+ self.output_folder = output_folder
+ self.jobs: int = jobs
+ self.reader = reader
+ self.ground_truths: Path = None
+ self.write_to_files = write_to_files
+ logger.debug(f'Ground truths are {self.ground_truths}')
+
+ def is_true_positive(self, raw_result: T) -> bool:
+ raise NotImplementedError("AndroidTaint Tools do not support ground truths yet.")
+
+ def is_false_positive(self, raw_result: T) -> bool:
+ raise NotImplementedError("AndroidTaint Tools do not support ground truths yet.")
diff --git a/src/ecstatic/violation_checkers/ViolationCheckerFactory.py b/src/ecstatic/violation_checkers/ViolationCheckerFactory.py
index 3983373b..c486b0a5 100644
--- a/src/ecstatic/violation_checkers/ViolationCheckerFactory.py
+++ b/src/ecstatic/violation_checkers/ViolationCheckerFactory.py
@@ -19,6 +19,7 @@
from src.ecstatic.violation_checkers.AbstractViolationChecker import AbstractViolationChecker
from src.ecstatic.violation_checkers.CallgraphViolationChecker import CallgraphViolationChecker
from src.ecstatic.violation_checkers.FlowDroidFlowViolationChecker import FlowDroidFlowViolationChecker
+from src.ecstatic.violation_checkers.AndroidTaintViolationChecker import AndroidTaintViolationChecker
def get_violation_checker_for_task(task: str, tool: str, **kwargs) -> AbstractViolationChecker:
@@ -26,5 +27,7 @@ def get_violation_checker_for_task(task: str, tool: str, **kwargs) -> AbstractVi
return CallgraphViolationChecker(**kwargs)
elif task.lower() == "taint" and tool.lower() == "flowdroid":
return FlowDroidFlowViolationChecker(**kwargs)
+ elif "droidsafe" or "amandroid":
+ return AndroidTaintViolationChecker(**kwargs)
else:
raise ValueError(f"No violation checker exists for task {task} on tool {tool}.")
\ No newline at end of file
diff --git a/src/resources/configuration_spaces/amandroid_config.json b/src/resources/configuration_spaces/amandroid_config.json
new file mode 100644
index 00000000..4e7cab3e
--- /dev/null
+++ b/src/resources/configuration_spaces/amandroid_config.json
@@ -0,0 +1,20 @@
+{
+ "name": "AmanDroid",
+ "options": [
+ {
+ "name": "static_init",
+ "levels": [
+ "FALSE",
+ "TRUE"
+ ],
+ "default": "FALSE",
+ "orders": [
+ {
+ "left": "TRUE",
+ "order": "MPT",
+ "right": "FALSE"
+ }
+ ]
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/src/resources/configuration_spaces/droidsafe_config.json b/src/resources/configuration_spaces/droidsafe_config.json
new file mode 100644
index 00000000..81cef644
--- /dev/null
+++ b/src/resources/configuration_spaces/droidsafe_config.json
@@ -0,0 +1,50 @@
+{
+ "name": "DroidSafe",
+ "options": [
+ {
+ "name": "analyzestrings_unfiltered",
+ "levels": [
+ "FALSE",
+ "TRUE"
+ ],
+ "default": "FALSE",
+ "orders": [
+ {
+ "left": "TRUE",
+ "order": "MPT",
+ "right": "FALSE"
+ }
+ ]
+ },
+ {
+ "name": "filetransforms",
+ "levels": [
+ "FALSE",
+ "TRUE"
+ ],
+ "default": "FALSE",
+ "orders": [
+ {
+ "left": "TRUE",
+ "order": "MST",
+ "right": "FALSE"
+ }
+ ]
+ },
+ {
+ "name": "ignorenocontextflows",
+ "levels": [
+ "TRUE",
+ "FALSE"
+ ],
+ "default": "FALSE",
+ "orders": [
+ {
+ "left": "TRUE",
+ "order": "MST",
+ "right": "FALSE"
+ }
+ ]
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/src/resources/grammars/amandroid.json b/src/resources/grammars/amandroid.json
new file mode 100644
index 00000000..a34371e7
--- /dev/null
+++ b/src/resources/grammars/amandroid.json
@@ -0,0 +1,9 @@
+{
+ "": [""],
+ "": ["