skylos 1.0.10__py3-none-any.whl → 2.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skylos/__init__.py +9 -3
- skylos/analyzer.py +674 -168
- skylos/cfg_visitor.py +60 -0
- skylos/cli.py +719 -235
- skylos/codemods.py +277 -0
- skylos/config.py +50 -0
- skylos/constants.py +78 -0
- skylos/gatekeeper.py +147 -0
- skylos/linter.py +18 -0
- skylos/rules/base.py +20 -0
- skylos/rules/danger/calls.py +119 -0
- skylos/rules/danger/danger.py +157 -0
- skylos/rules/danger/danger_cmd/cmd_flow.py +75 -0
- skylos/rules/danger/danger_fs/__init__.py +0 -0
- skylos/rules/danger/danger_fs/path_flow.py +79 -0
- skylos/rules/danger/danger_net/__init__.py +0 -0
- skylos/rules/danger/danger_net/ssrf_flow.py +80 -0
- skylos/rules/danger/danger_sql/__init__.py +0 -0
- skylos/rules/danger/danger_sql/sql_flow.py +245 -0
- skylos/rules/danger/danger_sql/sql_raw_flow.py +96 -0
- skylos/rules/danger/danger_web/__init__.py +0 -0
- skylos/rules/danger/danger_web/xss_flow.py +170 -0
- skylos/rules/danger/taint.py +110 -0
- skylos/rules/quality/__init__.py +0 -0
- skylos/rules/quality/complexity.py +95 -0
- skylos/rules/quality/logic.py +96 -0
- skylos/rules/quality/nesting.py +101 -0
- skylos/rules/quality/structure.py +99 -0
- skylos/rules/secrets.py +325 -0
- skylos/server.py +554 -0
- skylos/visitor.py +502 -90
- skylos/visitors/__init__.py +0 -0
- skylos/visitors/framework_aware.py +437 -0
- skylos/visitors/test_aware.py +74 -0
- skylos-2.5.2.dist-info/METADATA +21 -0
- skylos-2.5.2.dist-info/RECORD +42 -0
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/WHEEL +1 -1
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/top_level.txt +0 -1
- skylos-1.0.10.dist-info/METADATA +0 -8
- skylos-1.0.10.dist-info/RECORD +0 -21
- test/compare_tools.py +0 -604
- test/diagnostics.py +0 -364
- test/sample_repo/app.py +0 -13
- test/sample_repo/sample_repo/commands.py +0 -81
- test/sample_repo/sample_repo/models.py +0 -122
- test/sample_repo/sample_repo/routes.py +0 -89
- test/sample_repo/sample_repo/utils.py +0 -36
- test/test_skylos.py +0 -456
- test/test_visitor.py +0 -220
- {test → skylos/rules}/__init__.py +0 -0
- {test/sample_repo → skylos/rules/danger}/__init__.py +0 -0
- {test/sample_repo/sample_repo → skylos/rules/danger/danger_cmd}/__init__.py +0 -0
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/entry_points.txt +0 -0
skylos/visitor.py
CHANGED
|
@@ -1,24 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
import ast
|
|
2
|
+
import ast
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
PYTHON_BUILTINS = {
|
|
7
|
+
"print",
|
|
8
|
+
"len",
|
|
9
|
+
"str",
|
|
10
|
+
"int",
|
|
11
|
+
"float",
|
|
12
|
+
"list",
|
|
13
|
+
"dict",
|
|
14
|
+
"set",
|
|
15
|
+
"tuple",
|
|
16
|
+
"range",
|
|
17
|
+
"open",
|
|
18
|
+
"reversed",
|
|
19
|
+
"super",
|
|
20
|
+
"object",
|
|
21
|
+
"type",
|
|
22
|
+
"enumerate",
|
|
23
|
+
"zip",
|
|
24
|
+
"map",
|
|
25
|
+
"filter",
|
|
26
|
+
"sorted",
|
|
27
|
+
"sum",
|
|
28
|
+
"min",
|
|
29
|
+
"next",
|
|
30
|
+
"iter",
|
|
31
|
+
"bytes",
|
|
32
|
+
"bytearray",
|
|
33
|
+
"format",
|
|
34
|
+
"round",
|
|
35
|
+
"abs",
|
|
36
|
+
"complex",
|
|
37
|
+
"hash",
|
|
38
|
+
"id",
|
|
39
|
+
"bool",
|
|
40
|
+
"callable",
|
|
41
|
+
"getattr",
|
|
42
|
+
"max",
|
|
43
|
+
"all",
|
|
44
|
+
"any",
|
|
45
|
+
"setattr",
|
|
46
|
+
"hasattr",
|
|
47
|
+
"isinstance",
|
|
48
|
+
"globals",
|
|
49
|
+
"locals",
|
|
50
|
+
"vars",
|
|
51
|
+
"dir",
|
|
52
|
+
"property",
|
|
53
|
+
"classmethod",
|
|
54
|
+
"staticmethod",
|
|
55
|
+
}
|
|
56
|
+
DYNAMIC_PATTERNS = {"getattr", "globals", "eval", "exec"}
|
|
57
|
+
|
|
58
|
+
## "🥚" hi :)
|
|
4
59
|
|
|
5
|
-
PYTHON_BUILTINS={"print","len","str","int","float","list","dict","set","tuple","range","open","super","object","type","enumerate","zip","map","filter","sorted","reversed","sum","min","max","all","any","next","iter","repr","chr","ord","bytes","bytearray","memoryview","format","round","abs","pow","divmod","complex","hash","id","bool","callable","getattr","setattr","delattr","hasattr","isinstance","issubclass","globals","locals","vars","dir","property","classmethod","staticmethod"}
|
|
6
|
-
DYNAMIC_PATTERNS={"getattr","globals","eval","exec"}
|
|
7
60
|
|
|
8
61
|
class Definition:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def __init__(self, n, t, f, l):
|
|
12
|
-
self.name = n
|
|
62
|
+
def __init__(self, name, t, filename, line):
|
|
63
|
+
self.name = name
|
|
13
64
|
self.type = t
|
|
14
|
-
self.filename =
|
|
15
|
-
self.line =
|
|
16
|
-
self.simple_name =
|
|
65
|
+
self.filename = filename
|
|
66
|
+
self.line = line
|
|
67
|
+
self.simple_name = name.split(".")[-1]
|
|
17
68
|
self.confidence = 100
|
|
18
69
|
self.references = 0
|
|
19
70
|
self.is_exported = False
|
|
20
|
-
self.in_init = "__init__.py" in str(
|
|
21
|
-
|
|
71
|
+
self.in_init = "__init__.py" in str(filename)
|
|
72
|
+
|
|
22
73
|
def to_dict(self):
|
|
23
74
|
if self.type == "method" and "." in self.name:
|
|
24
75
|
parts = self.name.split(".")
|
|
@@ -28,8 +79,8 @@ class Definition:
|
|
|
28
79
|
output_name = self.name
|
|
29
80
|
else:
|
|
30
81
|
output_name = self.simple_name
|
|
31
|
-
|
|
32
|
-
return{
|
|
82
|
+
|
|
83
|
+
return {
|
|
33
84
|
"name": output_name,
|
|
34
85
|
"full_name": self.name,
|
|
35
86
|
"simple_name": self.simple_name,
|
|
@@ -38,70 +89,125 @@ class Definition:
|
|
|
38
89
|
"basename": Path(self.filename).name,
|
|
39
90
|
"line": self.line,
|
|
40
91
|
"confidence": self.confidence,
|
|
41
|
-
"references": self.references
|
|
92
|
+
"references": self.references,
|
|
42
93
|
}
|
|
43
94
|
|
|
95
|
+
|
|
44
96
|
class Visitor(ast.NodeVisitor):
|
|
45
|
-
def __init__(self,mod,file):
|
|
46
|
-
self.mod=mod
|
|
47
|
-
self.file=file
|
|
48
|
-
self.defs=[]
|
|
49
|
-
self.refs=[]
|
|
50
|
-
self.cls=None
|
|
51
|
-
self.alias={}
|
|
52
|
-
self.dyn=set()
|
|
53
|
-
self.exports=set()
|
|
97
|
+
def __init__(self, mod, file):
|
|
98
|
+
self.mod = mod
|
|
99
|
+
self.file = file
|
|
100
|
+
self.defs = []
|
|
101
|
+
self.refs = []
|
|
102
|
+
self.cls = None
|
|
103
|
+
self.alias = {}
|
|
104
|
+
self.dyn = set()
|
|
105
|
+
self.exports = set()
|
|
54
106
|
self.current_function_scope = []
|
|
107
|
+
self.current_function_params = []
|
|
108
|
+
self.local_var_maps = []
|
|
109
|
+
self.in_cst_class = 0
|
|
110
|
+
self.local_type_maps = []
|
|
111
|
+
self._dataclass_stack = []
|
|
112
|
+
self.dataclass_fields = set()
|
|
113
|
+
self.first_read_lineno = {}
|
|
114
|
+
|
|
115
|
+
def add_def(self, name, t, line):
|
|
116
|
+
found = False
|
|
117
|
+
for d in self.defs:
|
|
118
|
+
if d.name == name:
|
|
119
|
+
found = True
|
|
120
|
+
break
|
|
121
|
+
if not found:
|
|
122
|
+
self.defs.append(Definition(name, t, self.file, line))
|
|
55
123
|
|
|
56
|
-
def
|
|
57
|
-
|
|
124
|
+
def add_ref(self, name):
|
|
125
|
+
self.refs.append((name, self.file))
|
|
58
126
|
|
|
59
|
-
def
|
|
127
|
+
def qual(self, name):
|
|
128
|
+
if name in self.alias:
|
|
129
|
+
return self.alias[name]
|
|
130
|
+
if name in PYTHON_BUILTINS:
|
|
131
|
+
if self.mod:
|
|
132
|
+
mod_candidate = f"{self.mod}.{name}"
|
|
133
|
+
else:
|
|
134
|
+
mod_candidate = name
|
|
135
|
+
if any(d.name == mod_candidate for d in self.defs):
|
|
136
|
+
return mod_candidate
|
|
137
|
+
|
|
138
|
+
if self.mod:
|
|
139
|
+
return f"{self.mod}.{name}"
|
|
140
|
+
else:
|
|
141
|
+
return name
|
|
60
142
|
|
|
61
|
-
def
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
|
|
143
|
+
def visit_Global(self, node):
|
|
144
|
+
if self.current_function_scope and self.local_var_maps:
|
|
145
|
+
for name in node.names:
|
|
146
|
+
self.local_var_maps[-1][name] = f"{self.mod}.{name}"
|
|
147
|
+
return
|
|
65
148
|
|
|
66
149
|
def visit_annotation(self, node):
|
|
67
150
|
if node is not None:
|
|
68
151
|
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
69
152
|
self.visit_string_annotation(node.value)
|
|
70
|
-
elif hasattr(node,
|
|
153
|
+
elif hasattr(node, "s") and isinstance(node.s, str):
|
|
71
154
|
self.visit_string_annotation(node.s)
|
|
72
155
|
else:
|
|
73
156
|
self.visit(node)
|
|
74
157
|
|
|
75
158
|
def visit_string_annotation(self, annotation_str):
|
|
159
|
+
if not isinstance(annotation_str, str):
|
|
160
|
+
return
|
|
161
|
+
|
|
76
162
|
try:
|
|
77
|
-
parsed = ast.parse(annotation_str, mode=
|
|
163
|
+
parsed = ast.parse(annotation_str, mode="eval")
|
|
78
164
|
self.visit(parsed.body)
|
|
79
165
|
except:
|
|
80
|
-
|
|
166
|
+
for tok in re.findall(r"[A-Za-z_][A-Za-z0-9_]*", annotation_str):
|
|
167
|
+
self.add_ref(tok)
|
|
81
168
|
|
|
82
|
-
def visit_Import(self,node):
|
|
169
|
+
def visit_Import(self, node):
|
|
83
170
|
for a in node.names:
|
|
84
|
-
full=a.name
|
|
85
|
-
self.alias[a.asname or a.name.split(".")[-1]]=full
|
|
86
|
-
self.add_def(full,"import",node.lineno)
|
|
171
|
+
full = a.name
|
|
87
172
|
|
|
88
|
-
|
|
89
|
-
|
|
173
|
+
if a.asname:
|
|
174
|
+
alias_name = a.asname
|
|
175
|
+
target = full
|
|
176
|
+
else:
|
|
177
|
+
head = full.split(".", 1)[0]
|
|
178
|
+
alias_name = head
|
|
179
|
+
target = head
|
|
180
|
+
|
|
181
|
+
self.alias[alias_name] = target
|
|
182
|
+
self.add_def(target, "import", node.lineno)
|
|
183
|
+
|
|
184
|
+
def visit_ImportFrom(self, node):
|
|
185
|
+
if node.module is None:
|
|
186
|
+
return
|
|
90
187
|
for a in node.names:
|
|
91
|
-
if a.name=="*":
|
|
92
|
-
|
|
188
|
+
if a.name == "*":
|
|
189
|
+
continue
|
|
190
|
+
base = node.module
|
|
93
191
|
if node.level:
|
|
94
|
-
parts=self.mod.split(".")
|
|
95
|
-
base=".".join(parts[
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
192
|
+
parts = self.mod.split(".")
|
|
193
|
+
base = ".".join(parts[: -node.level]) + (
|
|
194
|
+
f".{node.module}" if node.module else ""
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
full = f"{base}.{a.name}"
|
|
198
|
+
if a.asname:
|
|
199
|
+
self.alias[a.asname] = full
|
|
200
|
+
self.add_def(full, "import", node.lineno)
|
|
201
|
+
else:
|
|
202
|
+
self.alias[a.name] = full
|
|
203
|
+
self.add_def(full, "import", node.lineno)
|
|
99
204
|
|
|
100
205
|
def visit_arguments(self, args):
|
|
101
206
|
for arg in args.args:
|
|
102
207
|
self.visit_annotation(arg.annotation)
|
|
103
208
|
for arg in args.posonlyargs:
|
|
104
209
|
self.visit_annotation(arg.annotation)
|
|
210
|
+
|
|
105
211
|
for arg in args.kwonlyargs:
|
|
106
212
|
self.visit_annotation(arg.annotation)
|
|
107
213
|
if args.vararg:
|
|
@@ -114,55 +220,183 @@ class Visitor(ast.NodeVisitor):
|
|
|
114
220
|
if default:
|
|
115
221
|
self.visit(default)
|
|
116
222
|
|
|
117
|
-
def visit_FunctionDef(self,node):
|
|
118
|
-
outer_scope_prefix =
|
|
119
|
-
|
|
223
|
+
def visit_FunctionDef(self, node):
|
|
224
|
+
outer_scope_prefix = (
|
|
225
|
+
".".join(self.current_function_scope) + "."
|
|
226
|
+
if self.current_function_scope
|
|
227
|
+
else ""
|
|
228
|
+
)
|
|
229
|
+
|
|
120
230
|
if self.cls:
|
|
121
231
|
name_parts = [self.mod, self.cls, outer_scope_prefix + node.name]
|
|
122
232
|
else:
|
|
123
233
|
name_parts = [self.mod, outer_scope_prefix + node.name]
|
|
124
|
-
|
|
234
|
+
|
|
125
235
|
qualified_name = ".".join(filter(None, name_parts))
|
|
126
236
|
|
|
127
|
-
|
|
128
|
-
|
|
237
|
+
if self.cls:
|
|
238
|
+
def_type = "method"
|
|
239
|
+
else:
|
|
240
|
+
def_type = "function"
|
|
241
|
+
self.add_def(qualified_name, def_type, node.lineno)
|
|
242
|
+
|
|
129
243
|
self.current_function_scope.append(node.name)
|
|
130
|
-
|
|
244
|
+
self.local_var_maps.append({})
|
|
245
|
+
self.local_type_maps.append({})
|
|
246
|
+
|
|
247
|
+
old_params = self.current_function_params
|
|
248
|
+
self.current_function_params = []
|
|
249
|
+
|
|
131
250
|
for d_node in node.decorator_list:
|
|
132
251
|
self.visit(d_node)
|
|
133
|
-
|
|
252
|
+
|
|
253
|
+
for arg in node.args.args:
|
|
254
|
+
param_name = f"{qualified_name}.{arg.arg}"
|
|
255
|
+
self.add_def(param_name, "parameter", node.lineno)
|
|
256
|
+
self.current_function_params.append((arg.arg, param_name))
|
|
257
|
+
|
|
134
258
|
self.visit_arguments(node.args)
|
|
135
259
|
self.visit_annotation(node.returns)
|
|
136
|
-
|
|
260
|
+
|
|
137
261
|
for stmt in node.body:
|
|
138
262
|
self.visit(stmt)
|
|
263
|
+
|
|
139
264
|
self.current_function_scope.pop()
|
|
140
|
-
|
|
141
|
-
|
|
265
|
+
self.current_function_params = old_params
|
|
266
|
+
self.local_var_maps.pop()
|
|
267
|
+
self.local_type_maps.pop()
|
|
268
|
+
|
|
269
|
+
visit_AsyncFunctionDef = visit_FunctionDef
|
|
270
|
+
|
|
271
|
+
def visit_ClassDef(self, node):
|
|
272
|
+
cname = f"{self.mod}.{node.name}"
|
|
273
|
+
self.add_def(cname, "class", node.lineno)
|
|
274
|
+
|
|
275
|
+
is_cst = False
|
|
276
|
+
is_dc = False
|
|
142
277
|
|
|
143
|
-
def visit_ClassDef(self,node):
|
|
144
|
-
cname=f"{self.mod}.{node.name}"
|
|
145
|
-
self.add_def(cname,"class",node.lineno)
|
|
146
|
-
|
|
147
278
|
for base in node.bases:
|
|
279
|
+
base_name = ""
|
|
280
|
+
|
|
281
|
+
if isinstance(base, ast.Attribute):
|
|
282
|
+
base_name = base.attr
|
|
283
|
+
|
|
284
|
+
elif isinstance(base, ast.Name):
|
|
285
|
+
base_name = base.id
|
|
148
286
|
self.visit(base)
|
|
287
|
+
|
|
288
|
+
if base_name in {"CSTTransformer", "CSTVisitor"}:
|
|
289
|
+
is_cst = True
|
|
290
|
+
|
|
149
291
|
for keyword in node.keywords:
|
|
150
292
|
self.visit(keyword.value)
|
|
293
|
+
|
|
151
294
|
for decorator in node.decorator_list:
|
|
295
|
+
|
|
296
|
+
def _is_dc(dec):
|
|
297
|
+
if isinstance(dec, ast.Call):
|
|
298
|
+
target = dec.func
|
|
299
|
+
else:
|
|
300
|
+
target = dec
|
|
301
|
+
|
|
302
|
+
if isinstance(target, ast.Name):
|
|
303
|
+
return target.id == "dataclass"
|
|
304
|
+
|
|
305
|
+
if isinstance(target, ast.Attribute):
|
|
306
|
+
return target.attr == "dataclass"
|
|
307
|
+
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
if _is_dc(decorator):
|
|
311
|
+
is_dc = True
|
|
152
312
|
self.visit(decorator)
|
|
153
|
-
|
|
154
|
-
prev=self.cls
|
|
155
|
-
|
|
156
|
-
|
|
313
|
+
|
|
314
|
+
prev = self.cls
|
|
315
|
+
if is_cst:
|
|
316
|
+
self.in_cst_class += 1
|
|
317
|
+
|
|
318
|
+
self.cls = node.name
|
|
319
|
+
self._dataclass_stack.append(is_dc)
|
|
320
|
+
for b in node.body:
|
|
321
|
+
self.visit(b)
|
|
322
|
+
|
|
323
|
+
self.cls = prev
|
|
324
|
+
self._dataclass_stack.pop()
|
|
325
|
+
|
|
326
|
+
if is_cst:
|
|
327
|
+
self.in_cst_class -= 1
|
|
157
328
|
|
|
158
329
|
def visit_AnnAssign(self, node):
|
|
159
330
|
self.visit_annotation(node.annotation)
|
|
160
331
|
if node.value:
|
|
161
332
|
self.visit(node.value)
|
|
162
|
-
|
|
333
|
+
|
|
334
|
+
def _define(t):
|
|
335
|
+
if isinstance(t, ast.Name):
|
|
336
|
+
name_simple = t.id
|
|
337
|
+
scope_parts = [self.mod]
|
|
338
|
+
if self.cls:
|
|
339
|
+
scope_parts.append(self.cls)
|
|
340
|
+
|
|
341
|
+
if self.current_function_scope:
|
|
342
|
+
scope_parts.extend(self.current_function_scope)
|
|
343
|
+
prefix = ".".join(filter(None, scope_parts))
|
|
344
|
+
if prefix:
|
|
345
|
+
var_name = f"{prefix}.{name_simple}"
|
|
346
|
+
else:
|
|
347
|
+
var_name = name_simple
|
|
348
|
+
|
|
349
|
+
self.add_def(var_name, "variable", t.lineno)
|
|
350
|
+
if (
|
|
351
|
+
self._dataclass_stack
|
|
352
|
+
and self._dataclass_stack[-1]
|
|
353
|
+
and self.cls
|
|
354
|
+
and not self.current_function_scope
|
|
355
|
+
):
|
|
356
|
+
self.dataclass_fields.add(var_name)
|
|
357
|
+
|
|
358
|
+
if self.current_function_scope and self.local_var_maps:
|
|
359
|
+
self.local_var_maps[-1][name_simple] = var_name
|
|
360
|
+
|
|
361
|
+
elif isinstance(t, (ast.Tuple, ast.List)):
|
|
362
|
+
for elt in t.elts:
|
|
363
|
+
_define(elt)
|
|
364
|
+
|
|
365
|
+
_define(node.target)
|
|
163
366
|
|
|
164
367
|
def visit_AugAssign(self, node):
|
|
165
|
-
|
|
368
|
+
if isinstance(node.target, ast.Name):
|
|
369
|
+
nm = node.target.id
|
|
370
|
+
if (
|
|
371
|
+
self.current_function_scope
|
|
372
|
+
and self.local_var_maps
|
|
373
|
+
and self.local_var_maps
|
|
374
|
+
and nm in self.local_var_maps[-1]
|
|
375
|
+
):
|
|
376
|
+
# self.add_ref(self.local_var_maps[-1][nm])
|
|
377
|
+
fq = self.local_var_maps[-1][nm]
|
|
378
|
+
self.add_ref(fq)
|
|
379
|
+
var_name = fq
|
|
380
|
+
|
|
381
|
+
else:
|
|
382
|
+
self.add_ref(self.qual(nm))
|
|
383
|
+
scope_parts = [self.mod]
|
|
384
|
+
if self.cls:
|
|
385
|
+
scope_parts.append(self.cls)
|
|
386
|
+
|
|
387
|
+
if self.current_function_scope:
|
|
388
|
+
scope_parts.extend(self.current_function_scope)
|
|
389
|
+
prefix = ".".join(filter(None, scope_parts))
|
|
390
|
+
if prefix:
|
|
391
|
+
var_name = f"{prefix}.{nm}"
|
|
392
|
+
else:
|
|
393
|
+
var_name = nm
|
|
394
|
+
|
|
395
|
+
self.add_def(var_name, "variable", node.lineno)
|
|
396
|
+
if self.current_function_scope and self.local_var_maps:
|
|
397
|
+
self.local_var_maps[-1][nm] = var_name
|
|
398
|
+
else:
|
|
399
|
+
self.visit(node.target)
|
|
166
400
|
self.visit(node.value)
|
|
167
401
|
|
|
168
402
|
def visit_Subscript(self, node):
|
|
@@ -178,6 +412,56 @@ class Visitor(ast.NodeVisitor):
|
|
|
178
412
|
self.visit(node.step)
|
|
179
413
|
|
|
180
414
|
def visit_Assign(self, node):
|
|
415
|
+
def process_target_for_def(target_node):
|
|
416
|
+
if isinstance(target_node, ast.Name):
|
|
417
|
+
name_simple = target_node.id
|
|
418
|
+
if (
|
|
419
|
+
name_simple == "METADATA_DEPENDENCIES"
|
|
420
|
+
and self.cls
|
|
421
|
+
and self.in_cst_class > 0
|
|
422
|
+
):
|
|
423
|
+
return
|
|
424
|
+
if (
|
|
425
|
+
name_simple == "__all__"
|
|
426
|
+
and not self.current_function_scope
|
|
427
|
+
and not self.cls
|
|
428
|
+
):
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
scope_parts = [self.mod]
|
|
432
|
+
if self.cls:
|
|
433
|
+
scope_parts.append(self.cls)
|
|
434
|
+
if self.current_function_scope:
|
|
435
|
+
scope_parts.extend(self.current_function_scope)
|
|
436
|
+
|
|
437
|
+
if (
|
|
438
|
+
self.current_function_scope
|
|
439
|
+
and self.local_var_maps
|
|
440
|
+
and name_simple in self.local_var_maps[-1]
|
|
441
|
+
):
|
|
442
|
+
var_name = self.local_var_maps[-1][name_simple]
|
|
443
|
+
else:
|
|
444
|
+
prefix = ".".join(filter(None, scope_parts))
|
|
445
|
+
if prefix:
|
|
446
|
+
var_name = f"{prefix}.{name_simple}"
|
|
447
|
+
else:
|
|
448
|
+
var_name = name_simple
|
|
449
|
+
|
|
450
|
+
self.add_def(var_name, "variable", target_node.lineno)
|
|
451
|
+
if (
|
|
452
|
+
self.current_function_scope
|
|
453
|
+
and self.local_var_maps
|
|
454
|
+
and name_simple not in self.local_var_maps[-1]
|
|
455
|
+
):
|
|
456
|
+
self.local_var_maps[-1][name_simple] = var_name
|
|
457
|
+
|
|
458
|
+
elif isinstance(target_node, (ast.Tuple, ast.List)):
|
|
459
|
+
for elt in target_node.elts:
|
|
460
|
+
process_target_for_def(elt)
|
|
461
|
+
|
|
462
|
+
for t in node.targets:
|
|
463
|
+
process_target_for_def(t)
|
|
464
|
+
|
|
181
465
|
for target in node.targets:
|
|
182
466
|
if isinstance(target, ast.Name) and target.id == "__all__":
|
|
183
467
|
if isinstance(node.value, (ast.List, ast.Tuple)):
|
|
@@ -185,47 +469,175 @@ class Visitor(ast.NodeVisitor):
|
|
|
185
469
|
value = None
|
|
186
470
|
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
|
|
187
471
|
value = elt.value
|
|
188
|
-
elif hasattr(elt,
|
|
472
|
+
elif hasattr(elt, "s") and isinstance(elt.s, str):
|
|
189
473
|
value = elt.s
|
|
190
|
-
|
|
474
|
+
|
|
191
475
|
if value is not None:
|
|
192
|
-
|
|
193
|
-
|
|
476
|
+
if self.mod:
|
|
477
|
+
export_name = f"{self.mod}.{value}"
|
|
478
|
+
else:
|
|
479
|
+
export_name = value
|
|
480
|
+
|
|
481
|
+
self.add_ref(export_name)
|
|
194
482
|
self.add_ref(value)
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
if isinstance(node.value, ast.Call):
|
|
486
|
+
callee = node.value.func
|
|
487
|
+
fqname = None
|
|
488
|
+
|
|
489
|
+
if isinstance(callee, ast.Name):
|
|
490
|
+
fqname = self.alias.get(callee.id, self.qual(callee.id))
|
|
491
|
+
|
|
492
|
+
elif isinstance(callee, ast.Attribute):
|
|
493
|
+
parts = []
|
|
494
|
+
cur = callee
|
|
495
|
+
while isinstance(cur, ast.Attribute):
|
|
496
|
+
parts.append(cur.attr)
|
|
497
|
+
cur = cur.value
|
|
498
|
+
head = None
|
|
499
|
+
if isinstance(cur, ast.Name):
|
|
500
|
+
head = self.alias.get(cur.id, self.qual(cur.id))
|
|
501
|
+
if head:
|
|
502
|
+
fqname = ".".join([head] + list(reversed(parts)))
|
|
503
|
+
|
|
504
|
+
if fqname and self.current_function_scope and self.local_type_maps:
|
|
505
|
+
|
|
506
|
+
def _mark_target(t):
|
|
507
|
+
if isinstance(t, ast.Name):
|
|
508
|
+
self.local_type_maps[-1][t.id] = fqname
|
|
509
|
+
elif isinstance(t, (ast.Tuple, ast.List)):
|
|
510
|
+
for elt in t.elts:
|
|
511
|
+
_mark_target(elt)
|
|
512
|
+
|
|
513
|
+
for t in node.targets:
|
|
514
|
+
_mark_target(t)
|
|
515
|
+
except Exception:
|
|
516
|
+
pass
|
|
517
|
+
|
|
195
518
|
self.generic_visit(node)
|
|
196
519
|
|
|
197
520
|
def visit_Call(self, node):
|
|
198
521
|
self.generic_visit(node)
|
|
199
|
-
|
|
200
|
-
if
|
|
201
|
-
|
|
522
|
+
|
|
523
|
+
if (
|
|
524
|
+
isinstance(node.func, ast.Name)
|
|
525
|
+
and node.func.id in ("getattr", "hasattr")
|
|
526
|
+
and len(node.args) >= 2
|
|
527
|
+
):
|
|
528
|
+
if isinstance(node.args[1], ast.Constant) and isinstance(
|
|
529
|
+
node.args[1].value, str
|
|
530
|
+
):
|
|
202
531
|
attr_name = node.args[1].value
|
|
203
532
|
self.add_ref(attr_name)
|
|
204
|
-
|
|
533
|
+
|
|
205
534
|
if isinstance(node.args[0], ast.Name):
|
|
206
535
|
module_name = node.args[0].id
|
|
207
|
-
if module_name != "self":
|
|
536
|
+
if module_name != "self":
|
|
208
537
|
qualified_name = f"{self.qual(module_name)}.{attr_name}"
|
|
209
538
|
self.add_ref(qualified_name)
|
|
210
|
-
|
|
539
|
+
|
|
540
|
+
elif isinstance(node.args[0], ast.Name):
|
|
541
|
+
target_name = node.args[0].id
|
|
542
|
+
if target_name != "self":
|
|
543
|
+
if self.mod:
|
|
544
|
+
self.dyn.add(self.mod.split(".")[0])
|
|
545
|
+
else:
|
|
546
|
+
self.dyn.add("")
|
|
547
|
+
|
|
211
548
|
elif isinstance(node.func, ast.Name) and node.func.id == "globals":
|
|
212
|
-
parent = getattr(node,
|
|
213
|
-
if (
|
|
214
|
-
isinstance(parent
|
|
215
|
-
isinstance(parent.slice
|
|
549
|
+
parent = getattr(node, "parent", None)
|
|
550
|
+
if (
|
|
551
|
+
isinstance(parent, ast.Subscript)
|
|
552
|
+
and isinstance(parent.slice, ast.Constant)
|
|
553
|
+
and isinstance(parent.slice.value, str)
|
|
554
|
+
):
|
|
216
555
|
func_name = parent.slice.value
|
|
217
556
|
self.add_ref(func_name)
|
|
218
557
|
self.add_ref(f"{self.mod}.{func_name}")
|
|
219
558
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self.
|
|
223
|
-
|
|
559
|
+
elif isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
|
|
560
|
+
root_mod = ""
|
|
561
|
+
if self.mod:
|
|
562
|
+
root_mod = self.mod.split(".")[0]
|
|
563
|
+
self.dyn.add(root_mod)
|
|
564
|
+
|
|
565
|
+
def visit_Name(self, node):
|
|
566
|
+
if isinstance(node.ctx, ast.Load):
|
|
567
|
+
for param_name, param_full_name in self.current_function_params:
|
|
568
|
+
if node.id == param_name:
|
|
569
|
+
self.first_read_lineno.setdefault(param_full_name, node.lineno)
|
|
570
|
+
self.add_ref(param_full_name)
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
if (
|
|
574
|
+
self.current_function_scope
|
|
575
|
+
and self.local_var_maps
|
|
576
|
+
and node.id in self.local_var_maps[-1]
|
|
577
|
+
):
|
|
578
|
+
fq = self.local_var_maps[-1][node.id]
|
|
579
|
+
self.first_read_lineno.setdefault(fq, node.lineno)
|
|
580
|
+
self.add_ref(fq)
|
|
581
|
+
return
|
|
224
582
|
|
|
225
|
-
|
|
583
|
+
qualified = self.qual(node.id)
|
|
584
|
+
self.first_read_lineno.setdefault(qualified, node.lineno)
|
|
585
|
+
self.add_ref(qualified)
|
|
586
|
+
if node.id in DYNAMIC_PATTERNS:
|
|
587
|
+
self.dyn.add(self.mod.split(".")[0])
|
|
588
|
+
|
|
589
|
+
def visit_Attribute(self, node):
|
|
226
590
|
self.generic_visit(node)
|
|
227
|
-
|
|
228
|
-
|
|
591
|
+
|
|
592
|
+
if not isinstance(node.ctx, ast.Load):
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
if isinstance(node.value, ast.Name):
|
|
596
|
+
base = node.value.id
|
|
597
|
+
|
|
598
|
+
param_hit = None
|
|
599
|
+
for param_name, param_full in self.current_function_params:
|
|
600
|
+
if base == param_name:
|
|
601
|
+
param_hit = (param_name, param_full)
|
|
602
|
+
break
|
|
603
|
+
|
|
604
|
+
if param_hit:
|
|
605
|
+
self.add_ref(param_hit[1])
|
|
606
|
+
|
|
607
|
+
if self.cls and base in {"self", "cls"}:
|
|
608
|
+
if self.mod:
|
|
609
|
+
owner = f"{self.mod}.{self.cls}"
|
|
610
|
+
else:
|
|
611
|
+
owner = self.cls
|
|
612
|
+
|
|
613
|
+
self.add_ref(f"{owner}.{node.attr}")
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
if (
|
|
617
|
+
self.current_function_scope
|
|
618
|
+
and self.local_type_maps
|
|
619
|
+
and self.local_type_maps[-1].get(base)
|
|
620
|
+
):
|
|
621
|
+
self.add_ref(f"{self.local_type_maps[-1][base]}.{node.attr}")
|
|
622
|
+
return
|
|
623
|
+
|
|
624
|
+
self.add_ref(f"{self.qual(base)}.{node.attr}")
|
|
625
|
+
|
|
626
|
+
def visit_NamedExpr(self, node):
|
|
627
|
+
self.visit(node.value)
|
|
628
|
+
if isinstance(node.target, ast.Name):
|
|
629
|
+
nm = node.target.id
|
|
630
|
+
scope_parts = [self.mod]
|
|
631
|
+
if self.cls:
|
|
632
|
+
scope_parts.append(self.cls)
|
|
633
|
+
if self.current_function_scope:
|
|
634
|
+
scope_parts.extend(self.current_function_scope)
|
|
635
|
+
prefix = ".".join(filter(None, scope_parts))
|
|
636
|
+
var_name = f"{prefix}.{nm}" if prefix else nm
|
|
637
|
+
self.add_def(var_name, "variable", node.lineno)
|
|
638
|
+
if self.current_function_scope and self.local_var_maps:
|
|
639
|
+
self.local_var_maps[-1][nm] = var_name
|
|
640
|
+
self.add_ref(var_name)
|
|
229
641
|
|
|
230
642
|
def visit_keyword(self, node):
|
|
231
643
|
self.visit(node.value)
|
|
@@ -250,4 +662,4 @@ class Visitor(ast.NodeVisitor):
|
|
|
250
662
|
self.visit(item)
|
|
251
663
|
elif isinstance(value, ast.AST):
|
|
252
664
|
value.parent = node
|
|
253
|
-
self.visit(value)
|
|
665
|
+
self.visit(value)
|