From 4574cf5c7debe10879f309b6c31320a580d79b9c Mon Sep 17 00:00:00 2001 From: Aryaz Eghbali Date: Tue, 20 Feb 2024 17:26:59 +0100 Subject: [PATCH] Added solutions to tutorial tasks --- .../BranchCoverageAnalysis.py | 17 +++++ .../tutorial_analyses/CallGraphAnalysis.py | 68 +++++++++++++++++++ .../OppositeBranchAnalysis.py | 9 +++ .../SlowStringConcatAnalysis.py | 34 ++++++++++ 4 files changed, 128 insertions(+) create mode 100644 tutorial/tutorial-analyses/src/tutorial_analyses/BranchCoverageAnalysis.py create mode 100644 tutorial/tutorial-analyses/src/tutorial_analyses/CallGraphAnalysis.py create mode 100644 tutorial/tutorial-analyses/src/tutorial_analyses/OppositeBranchAnalysis.py create mode 100644 tutorial/tutorial-analyses/src/tutorial_analyses/SlowStringConcatAnalysis.py diff --git a/tutorial/tutorial-analyses/src/tutorial_analyses/BranchCoverageAnalysis.py b/tutorial/tutorial-analyses/src/tutorial_analyses/BranchCoverageAnalysis.py new file mode 100644 index 0000000..ded9f7b --- /dev/null +++ b/tutorial/tutorial-analyses/src/tutorial_analyses/BranchCoverageAnalysis.py @@ -0,0 +1,17 @@ +from typing import Optional +from dynapyt.analyses.BaseAnalysis import BaseAnalysis + + +class BranchCoverageAnalysis(BaseAnalysis): + def __init__(self): + super().__init__() + self.branches = {} + + def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool): + if (dyn_ast, iid, cond_value) not in self.branches: + self.branches[(dyn_ast, iid, cond_value)] = 0 + self.branches[(dyn_ast, iid, cond_value)] += 1 + + def end_execution(self): + for k, v in self.branches.items(): + print(f"Branch {k[1]} was taken with value {k[2]} {v} times") diff --git a/tutorial/tutorial-analyses/src/tutorial_analyses/CallGraphAnalysis.py b/tutorial/tutorial-analyses/src/tutorial_analyses/CallGraphAnalysis.py new file mode 100644 index 0000000..5d543d5 --- /dev/null +++ b/tutorial/tutorial-analyses/src/tutorial_analyses/CallGraphAnalysis.py @@ -0,0 +1,68 @@ +from typing import Callable, Tuple, Dict +import logging +import libcst.matchers as m +from dynapyt.analyses.BaseAnalysis import BaseAnalysis +from dynapyt.utils.nodeLocator import get_parent_by_type +import os +from inspect import getmodule + + +class CallGraphAnalysis(BaseAnalysis): + def __init__(self): + super(CallGraphAnalysis, self).__init__() + logging.basicConfig(filename="dynapyt.json", format="%(message)s", level=logging.INFO) + self.graph = {} + + def pre_call(self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict): + """ + DynaPyt hook for pre function call + """ + ast, iids = self._get_ast(dyn_ast) + module = getmodule(function) + module = str(module).split(" ")[1] if module is not None else "''" + # calling function + caller = get_parent_by_type(ast, iids.iid_to_location[iid], m.FunctionDef()) + # called function + if hasattr(function, "__qualname__"): + # module of the callee is added through the module if present + callee = module[1:-1] + "." + function.__qualname__ if module != "''" else function.__qualname__ + else: + # this is done to capture functions whose function.__qualname__ is not defined, + # but the string gives an idea as to which function is called. + # Captures MarkDecorator annotations, lambdas object calls, etc + temp = str(function) + callee = temp + + # file name + key = dyn_ast.replace(".py.orig", "").replace(os.sep, ".") + # format = "file" + + if caller is None: + f = key + else: + # if caller is a part of class, find the class name + caller_parent = get_parent_by_type(ast, iids.iid_to_location[iid], m.ClassDef()) + if caller_parent is None: + f = key + "." + caller.name.value + # format += ".func" + else: + f = key + "." + caller_parent.name.value + "." + caller.name.value + # format += ".class.func" + + # if caller already added + if f in self.graph.keys(): + temp = self.graph[f] + # filter dupilcate callees + if callee not in temp: + temp.append(callee) + self.graph[f] = temp + else: + # self.graph[f] = [format, callee] + self.graph[f] = [callee] + + def end_execution(self): + """ + DynaPyt hook for end of execution + """ + # to avoid serialization failures in converting dict to json + print(self.graph) diff --git a/tutorial/tutorial-analyses/src/tutorial_analyses/OppositeBranchAnalysis.py b/tutorial/tutorial-analyses/src/tutorial_analyses/OppositeBranchAnalysis.py new file mode 100644 index 0000000..b699e83 --- /dev/null +++ b/tutorial/tutorial-analyses/src/tutorial_analyses/OppositeBranchAnalysis.py @@ -0,0 +1,9 @@ +from typing import Optional +from dynapyt.analyses.BaseAnalysis import BaseAnalysis + + +class OppositeBranchAnalysis(BaseAnalysis): + def enter_control_flow( + self, dyn_ast: str, iid: int, cond_value: bool + ) -> Optional[bool]: + return not cond_value diff --git a/tutorial/tutorial-analyses/src/tutorial_analyses/SlowStringConcatAnalysis.py b/tutorial/tutorial-analyses/src/tutorial_analyses/SlowStringConcatAnalysis.py new file mode 100644 index 0000000..dcd9d56 --- /dev/null +++ b/tutorial/tutorial-analyses/src/tutorial_analyses/SlowStringConcatAnalysis.py @@ -0,0 +1,34 @@ +from typing import Iterable +from dynapyt.analyses.BaseAnalysis import BaseAnalysis + + +class SlowStringConcatAnalysis(BaseAnalysis): + def __init__(self): + super().__init__() + self.in_loop = [] + self.concat_count = [] + self.threshold = 5 + + def enter_for(self, dyn_ast: str, iid: int, next_value, iterable: Iterable): + if ( + self.in_loop + and self.in_loop[-1][0] == dyn_ast + and self.in_loop[-1][1] == iid + ): + pass + else: + self.in_loop.append((dyn_ast, iid)) + self.concat_count.append(0) + + def exit_for(self, dyn_ast: str, iid: int): + curr = self.in_loop.pop() + assert curr[0] == dyn_ast + assert curr[1] == iid + self.concat_count.pop() + + def add_assign(self, dyn_ast: str, iid: int, lhs, rhs): + if self.in_loop: + if isinstance(rhs, str): + self.concat_count[-1] += 1 + if self.concat_count[-1] >= self.threshold: + print(f"Possible slow string concatenation in {dyn_ast} at {iid}")