Skip to content

Commit 442e343

Browse files
authored
Handle more types of nodes and kwargs (#17)
* New func_call_kwargs separate from func_call_args * Resolve various data types Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
1 parent 9e12fb5 commit 442e343

3 files changed

Lines changed: 77 additions & 37 deletions

File tree

precli/core/parser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from importlib.metadata import entry_points
55

66
import tree_sitter_languages
7+
from tree_sitter import Node
78
from tree_sitter import Tree
89

910
from precli.core.result import Result
@@ -24,7 +25,7 @@ def file_extension(self) -> str:
2425
pass
2526

2627
@staticmethod
27-
def traverse_tree(tree: Tree):
28+
def traverse_tree(tree: Tree) -> Node:
2829
cursor = tree.walk()
2930

3031
reached_root = False

precli/core/rule.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,8 @@ def match_call_kwarg(
9595
arg_value: str = None,
9696
) -> bool:
9797
if context["node"].type == "call" and context["func_call_args"]:
98-
for arg in context["func_call_args"]:
99-
if isinstance(arg, dict):
100-
if arg.get(arg_name) == arg_value:
101-
return True
98+
if arg_name in context["func_call_kwargs"]:
99+
return context["func_call_kwargs"][arg_name] == arg_value
102100

103101
@abstractmethod
104102
def analyze(self, context: dict) -> Result:

precli/parsers/python.py

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Copyright 2023 Secure Saurce LLC
2+
import ast
3+
24
from tree_sitter import Node
35

46
from precli.core.parser import Parser
@@ -50,63 +52,102 @@ def import_from_statement(self, nodes: list[Node]) -> dict:
5052

5153
return imports
5254

53-
def get_qual_value(self, context: dict, node: Node) -> str:
54-
if node.type == "attribute":
55-
attribute = node
56-
name = attribute.text
57-
if b"." in name:
58-
name = name.rpartition(b".")[0]
59-
60-
if name in context["imports"]:
61-
return attribute.text.replace(name, context["imports"][name])
62-
elif node.type == "identifier":
63-
name = node.text
64-
if name in context["imports"]:
65-
return context["imports"][name]
66-
elif node.type == "keyword_argument":
67-
kwarg = {}
68-
keyword = node.children[0].text
69-
kwarg[keyword] = self.get_qual_value(context, node.children[2])
70-
return kwarg
71-
72-
def call(self, context: dict, nodes: list[Node]):
55+
def get_call_arg(self, context: dict, node: Node) -> str:
56+
match node.type:
57+
case "attribute":
58+
attribute = node
59+
name = attribute.text
60+
if b"." in name:
61+
name = name.rpartition(b".")[0]
62+
if name in context["imports"]:
63+
qual_name = context["imports"][name]
64+
return attribute.text.replace(name, qual_name)
65+
# TODO: else return attr text?
66+
case "identifier":
67+
name = node.text
68+
if name in context["imports"]:
69+
return context["imports"][name]
70+
else:
71+
return name
72+
case "dictionary":
73+
# TODO: need to avoid use of decode
74+
return ast.literal_eval(node.text.decode())
75+
case "list":
76+
# TODO: need to avoid use of decode
77+
return ast.literal_eval(node.text.decode())
78+
case "tuple":
79+
# TODO: need to avoid use of decode
80+
return ast.literal_eval(node.text.decode())
81+
case "string":
82+
# TODO: bytes and f-type strings are messed up
83+
return node.text
84+
case "integer":
85+
# TODO: hex, octal, binary
86+
return int(node.text)
87+
case "float":
88+
return float(node.text)
89+
case "true":
90+
return True
91+
case "false":
92+
return False
93+
case "none":
94+
return None
95+
case _:
96+
# TODO: complex
97+
print(node.type)
98+
print(node.text)
99+
100+
def get_call_kwarg(self, context: dict, node: Node) -> dict:
101+
kwarg = dict()
102+
keyword = node.children[0].text
103+
kwarg[keyword] = self.get_call_arg(context, node.children[2])
104+
return kwarg
105+
106+
def call(self, context: dict, nodes: list[Node]) -> tuple:
73107
# Resolve the fully qualified function name
74108
func_call_qual = ""
75109
first_node = next(nodes)
76-
func_call_qual = self.get_qual_value(context, first_node)
110+
func_call_qual = self.get_call_arg(context, first_node)
77111

78112
# Get the arguments of the function call
79-
func_call_args = []
113+
func_call_args = list()
114+
func_call_kwargs = dict()
80115
second_node = next(nodes)
81116
if second_node.type == "argument_list":
82117
for child in second_node.children:
83118
if child.type not in "(,)":
84-
arg_value = self.get_qual_value(context, child)
85-
func_call_args.append(arg_value)
119+
if child.type == "keyword_argument":
120+
kwarg = self.get_call_kwarg(context, child)
121+
func_call_kwargs = func_call_kwargs | kwarg
122+
else:
123+
arg = self.get_call_arg(context, child)
124+
func_call_args.append(arg)
86125

87-
return (func_call_qual, func_call_args)
126+
return (func_call_qual, func_call_args, func_call_kwargs)
88127

89128
def parse(self, data: bytes) -> list[Result]:
90-
results = []
129+
results = list()
91130
context = dict()
92-
context["imports"] = {}
131+
context["imports"] = dict()
93132
tree = self.parser.parse(data)
94133

95134
for node in Parser.traverse_tree(tree):
96135
context["node"] = node
97136
match node.type:
98137
case "import_statement":
99-
imps = self.import_statement(iter(node.children))
138+
children = iter(node.children)
139+
imps = self.import_statement(children)
100140
context["imports"].update(imps)
101-
102141
case "import_from_statement":
103-
imps = self.import_from_statement(iter(node.children))
142+
children = iter(node.children)
143+
imps = self.import_from_statement(children)
104144
context["imports"].update(imps)
105-
106145
case "call":
107-
(func, args) = self.call(context, iter(node.children))
146+
children = iter(node.children)
147+
(func, args, kwargs) = self.call(context, children)
108148
context["func_call_qual"] = func
109149
context["func_call_args"] = args
150+
context["func_call_kwargs"] = kwargs
110151

111152
for rule in self.rules.values():
112153
result = rule.analyze(context)

0 commit comments

Comments
 (0)