Skip to content

Commit

Permalink
Fixed issue with deep site-sensitive calls
Browse files Browse the repository at this point in the history
  • Loading branch information
AryazE committed Aug 15, 2024
1 parent 0bc0fb7 commit 0885eb1
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 35 deletions.
47 changes: 28 additions & 19 deletions src/dynapyt/instrument/CodeInstrumenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
from .IIDs import IIDs
from pathlib import Path

site_sensitive_functions = [
"breakpoint",
"dir",
"eval",
"exec",
"globals",
"help",
"locals",
"super",
"vars",
]


class CodeInstrumenter(m.MatcherDecoratableTransformer):
METADATA_DEPENDENCIES = (
Expand Down Expand Up @@ -137,13 +149,7 @@ def __create_import(self, names):
def __wrap_in_lambda(self, original_node, updated_node):
if len(m.findall(original_node, m.Await())) > 0:
return updated_node
if m.matches(updated_node, m.Call(func=m.Name("super"), args=[])):
class_arg = cst.Arg(value=cst.Name(value=self.current_class[-1]))
function_arg = cst.Arg(
value=cst.Name(value=self.current_function[-1]["params"].value)
)
new_node = updated_node.with_changes(args=[class_arg, function_arg])
return cst.Lambda(params=cst.Parameters(params=[]), body=new_node)
used_calls = list(set(m.findall(updated_node, m.Call())))
used_names = list(m.findall(original_node, m.Name()))
unique_names = set()
parameters = []
Expand Down Expand Up @@ -182,6 +188,20 @@ def __wrap_in_lambda(self, original_node, updated_node):
)
)
unique_names.add(n.value)
for c in used_calls:
if any(
[
m.matches(c.func, m.Name(value=ssf))
for ssf in site_sensitive_functions
]
):
parameters.append(
cst.Param(
name=c.func,
default=c,
)
)
updated_node = updated_node.deep_replace(c, c.func)
if m.matches(updated_node, m.Tuple()) and (len(updated_node.lpar) == 0):
lambda_expr = cst.Lambda(
params=cst.Parameters(params=parameters),
Expand Down Expand Up @@ -1403,17 +1423,6 @@ def leave_Call(self, original_node: cst.Call, updated_node: cst.Call):
)
):
return updated_node
site_sensitive_functions = [
"breakpoint",
"dir",
"eval",
"exec",
"globals",
"help",
"locals",
"super",
"vars",
]
callee_name = cst.Attribute(
value=cst.Name(value="_rt"), attr=cst.Name(value="_call_")
)
Expand Down Expand Up @@ -1882,7 +1891,7 @@ def leave_WithItem(self, original_node, updated_node):
and "exit_with" not in self.selected_hooks
):
return updated_node

iid = self.__create_iid(original_node)
ast_arg = cst.Arg(value=cst.Name("_dynapyt_ast_"))
iid_arg = cst.Arg(value=cst.Integer(value=str(iid)))
Expand Down
46 changes: 36 additions & 10 deletions src/dynapyt/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,50 @@ def filtered(self, func, f, args):
sub_args = args[2:]
else:
return False
sub_arg_names = []
for arg in sub_args:
if arg == () or arg == [] or arg == {}:
continue
if type(arg) == tuple and len(arg) == 1:
arg = arg[0]
try:
name = arg.__dict__.get("__name__", None)
if name is not None:
sub_arg_names.append(name)
continue
else:
no_dict = True
except AttributeError:
no_dict = True
try:
sub_arg_names.append(arg.__name__)
continue
except AttributeError:
no_name = True
if no_dict and no_name:
sub_arg_names.append(str(arg))
if (
func.__name__ == "post_call"
and sub_args[0] == sub_args[1]
and type(sub_args[0]) == super
):
sub_arg_names.append("super")
return_value = False
while START in docs:
start = docs.find(START)
end = docs.find(END)
fltr = docs[start + len(START) : end].strip()
patterns = fltr.split(" -> ")[1].split(SEPERATOR)
try:
if fltr.startswith("only ->") and any(
[arg.__dict__.get("__name__", None) in patterns for arg in sub_args]
):
if fltr.startswith("only ->"):
return_value = True
if any([arg in patterns for arg in sub_arg_names]):
return False
elif fltr.startswith("ignore ->") and any(
[arg.__dict__.get("__name__", None) in patterns for arg in sub_args]
):
elif fltr.startswith("ignore ->"):
return_value = False
if any([arg in patterns for arg in sub_arg_names]):
return True
except AttributeError:
pass
docs = docs[end + len(END) :].lstrip()
return False
return return_value

def call_if_exists(self, f, *args):
return_value = None
Expand Down
1 change: 1 addition & 0 deletions tests/filters/read_ignore/skip.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO: Add support for filtering based on variable name
1 change: 1 addition & 0 deletions tests/filters/read_only/skip.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO: Add support for filtering based on variable name
2 changes: 1 addition & 1 deletion tests/filters/string_literal_only/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TestAnalysis(BaseAnalysis):
def begin_execution(self) -> None:
print("begin execution")

@only(patterns=['"abc"'])
@only(patterns=["'abc'", '"abc"', "abc"])
def string(self, dyn_ast: str, iid: int, val: Any) -> Any:
print(f'string literal "{val}"')

Expand Down
8 changes: 8 additions & 0 deletions tests/filters/super/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dynapyt.analyses.BaseAnalysis import BaseAnalysis
from dynapyt.instrument.filters import only


class TestAnalysis(BaseAnalysis):
@only(patterns=["foo"])
def post_call(self, dyn_ast: str, iid: int, result, call, pos_args, kw_args):
print(f"post call of {call}")
1 change: 1 addition & 0 deletions tests/filters/super/expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<class 'filters.super.program.X'>
8 changes: 8 additions & 0 deletions tests/filters/super/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class X:
def __getattribute__(self, name: str):
res = super().__getattribute__(name)
return res


x = X()
print(x.__class__)
15 changes: 15 additions & 0 deletions tests/regression/deep_site_sensitive_calls/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dynapyt.analyses.BaseAnalysis import BaseAnalysis


class TestAnalysis(BaseAnalysis):
def begin_execution(self) -> None:
print("begin execution")

def pre_call(self, dyn_ast: str, iid: int, function, pos_args, kw_args) -> None:
print(f"pre call of {function.__name__}")

def binary_operation(self, dyn_ast, iid, op, left, right, result):
print(f"binary operation {left} {op} {right} = {result}")

def end_execution(self) -> None:
print("end execution")
10 changes: 10 additions & 0 deletions tests/regression/deep_site_sensitive_calls/expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
begin execution
pre call of B
pre call of __init__
pre call of f
pre call of f
binary operation 1 Add 2 = 3
pre call of len
pre call of len
binary operation 14 Add 14 = 28
end execution
21 changes: 21 additions & 0 deletions tests/regression/deep_site_sensitive_calls/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class A:
def __init__(self):
self.a = 1

def f(self):
return self.a


class B(A):
def __init__(self):
super().__init__()
self.b = 2

def f(self):
return super().f() + self.b


b = B()
b.f()

c = len(locals()) + len(globals())
5 changes: 0 additions & 5 deletions tests/regression/filters_getattr/expected.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
pree call of deferred_error
pree call of ValueError
pree call of new
pree call of DeferredError
pree call of print
OK
# Exception: Some error text
2 changes: 2 additions & 0 deletions tests/run_single_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def correct_coverage(expected: str, actual: str) -> bool:

def test_runner(directory_pair: Tuple[str, str], capsys):
abs_dir, rel_dir = directory_pair
if (Path(abs_dir) / "skip.txt").exists():
pytest.skip(f"Skipping test {rel_dir}")

exception = None

Expand Down

0 comments on commit 0885eb1

Please sign in to comment.