coretrace-python-analyzer 0.1.0__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.
- coretrace_python/__init__.py +4 -0
- coretrace_python/__main__.py +4 -0
- coretrace_python/abstract/__init__.py +45 -0
- coretrace_python/abstract/constants.py +226 -0
- coretrace_python/abstract/heap.py +252 -0
- coretrace_python/abstract/ranges.py +285 -0
- coretrace_python/abstract/values.py +56 -0
- coretrace_python/analysis/__init__.py +31 -0
- coretrace_python/analysis/manager.py +165 -0
- coretrace_python/analysis/provider.py +73 -0
- coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +46 -0
- coretrace_python/bundled/dependency/dependency_policy/plugin.toml +9 -0
- coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +9 -0
- coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +56 -0
- coretrace_python/bundled/dependency/sample_advisories/plugin.toml +9 -0
- coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +109 -0
- coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +9 -0
- coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +43 -0
- coretrace_python/bundled/models/cli/cli_models.py +35 -0
- coretrace_python/bundled/models/cli/plugin.toml +9 -0
- coretrace_python/bundled/models/credentials/credential_models.py +43 -0
- coretrace_python/bundled/models/credentials/plugin.toml +9 -0
- coretrace_python/bundled/models/django/django_models.py +123 -0
- coretrace_python/bundled/models/django/plugin.toml +9 -0
- coretrace_python/bundled/models/fastapi/fastapi_models.py +29 -0
- coretrace_python/bundled/models/fastapi/plugin.toml +9 -0
- coretrace_python/bundled/models/flask/flask_models.py +53 -0
- coretrace_python/bundled/models/flask/plugin.toml +9 -0
- coretrace_python/bundled/models/http_clients/http_client_models.py +43 -0
- coretrace_python/bundled/models/http_clients/plugin.toml +9 -0
- coretrace_python/bundled/models/python_stdlib/plugin.toml +9 -0
- coretrace_python/bundled/models/python_stdlib/python_stdlib.py +68 -0
- coretrace_python/bundled/models/sqlalchemy/plugin.toml +9 -0
- coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +47 -0
- coretrace_python/bundled/secrets/config_secrets/config_secrets.py +38 -0
- coretrace_python/bundled/secrets/config_secrets/plugin.toml +9 -0
- coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +19 -0
- coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +9 -0
- coretrace_python/bundled/security/command_injection/command_injection.py +17 -0
- coretrace_python/bundled/security/command_injection/plugin.toml +9 -0
- coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +17 -0
- coretrace_python/bundled/security/insecure_deserialization/plugin.toml +9 -0
- coretrace_python/bundled/security/open_redirect/open_redirect.py +17 -0
- coretrace_python/bundled/security/open_redirect/plugin.toml +9 -0
- coretrace_python/bundled/security/path_traversal/path_traversal.py +17 -0
- coretrace_python/bundled/security/path_traversal/plugin.toml +9 -0
- coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +21 -0
- coretrace_python/bundled/security/plaintext_credentials/plugin.toml +9 -0
- coretrace_python/bundled/security/sql_injection/plugin.toml +9 -0
- coretrace_python/bundled/security/sql_injection/sql_injection.py +17 -0
- coretrace_python/bundled/security/ssrf/plugin.toml +9 -0
- coretrace_python/bundled/security/ssrf/ssrf.py +17 -0
- coretrace_python/bundled/security/xss/plugin.toml +9 -0
- coretrace_python/bundled/security/xss/xss.py +17 -0
- coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +19 -0
- coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +9 -0
- coretrace_python/bundled/syntax/flask_debug/flask_debug.py +63 -0
- coretrace_python/bundled/syntax/flask_debug/plugin.toml +9 -0
- coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +51 -0
- coretrace_python/bundled/syntax/missing_timeout/plugin.toml +9 -0
- coretrace_python/bundled/syntax/weak_crypto/plugin.toml +9 -0
- coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +19 -0
- coretrace_python/cache.py +310 -0
- coretrace_python/cfg/__init__.py +46 -0
- coretrace_python/cfg/builder.py +589 -0
- coretrace_python/cfg/dominance.py +183 -0
- coretrace_python/cfg/model.py +166 -0
- coretrace_python/cli.py +216 -0
- coretrace_python/dataflow/__init__.py +29 -0
- coretrace_python/dataflow/lattice.py +78 -0
- coretrace_python/dataflow/solver.py +96 -0
- coretrace_python/dependency/__init__.py +44 -0
- coretrace_python/dependency/advisories.py +168 -0
- coretrace_python/dependency/correlation.py +87 -0
- coretrace_python/dependency/graph.py +274 -0
- coretrace_python/dependency/policy.py +65 -0
- coretrace_python/dependency/sbom.py +65 -0
- coretrace_python/engine.py +688 -0
- coretrace_python/findings/__init__.py +13 -0
- coretrace_python/findings/coverage.py +45 -0
- coretrace_python/findings/model.py +49 -0
- coretrace_python/findings/refutation.py +427 -0
- coretrace_python/frontend/__init__.py +18 -0
- coretrace_python/frontend/ast_adapter.py +447 -0
- coretrace_python/frontend/parser.py +23 -0
- coretrace_python/hir/__init__.py +5 -0
- coretrace_python/hir/nodes.py +584 -0
- coretrace_python/hir/visitors.py +36 -0
- coretrace_python/interprocedural/__init__.py +49 -0
- coretrace_python/interprocedural/callgraph.py +291 -0
- coretrace_python/interprocedural/modulegraph.py +218 -0
- coretrace_python/interprocedural/summaries.py +463 -0
- coretrace_python/ir/__init__.py +5 -0
- coretrace_python/ir/defuse.py +74 -0
- coretrace_python/ir/lowering.py +575 -0
- coretrace_python/ir/model.py +481 -0
- coretrace_python/ir/printer.py +201 -0
- coretrace_python/ir/ssa.py +277 -0
- coretrace_python/plugins/__init__.py +58 -0
- coretrace_python/plugins/api.py +153 -0
- coretrace_python/plugins/detectors.py +114 -0
- coretrace_python/plugins/loader.py +84 -0
- coretrace_python/plugins/manifest.py +112 -0
- coretrace_python/plugins/registry.py +34 -0
- coretrace_python/plugins/secrets.py +330 -0
- coretrace_python/reporters/__init__.py +22 -0
- coretrace_python/reporters/json_format.py +48 -0
- coretrace_python/reporters/report.py +23 -0
- coretrace_python/reporters/sarif.py +70 -0
- coretrace_python/reporters/text.py +24 -0
- coretrace_python/semantic/__init__.py +9 -0
- coretrace_python/semantic/identity.py +39 -0
- coretrace_python/semantic/imports.py +131 -0
- coretrace_python/semantic/scopes.py +473 -0
- coretrace_python/semantic/symbols.py +87 -0
- coretrace_python/source/__init__.py +13 -0
- coretrace_python/source/manager.py +81 -0
- coretrace_python/source/model.py +57 -0
- coretrace_python/taint/__init__.py +55 -0
- coretrace_python/taint/engine.py +800 -0
- coretrace_python/taint/models.py +317 -0
- coretrace_python/taint/routes.py +99 -0
- coretrace_python_analyzer-0.1.0.dist-info/METADATA +74 -0
- coretrace_python_analyzer-0.1.0.dist-info/RECORD +126 -0
- coretrace_python_analyzer-0.1.0.dist-info/WHEEL +4 -0
- coretrace_python_analyzer-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""Adapter from Python's standard-library AST to parser-independent PyHIR."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from typing import NoReturn
|
|
7
|
+
|
|
8
|
+
from coretrace_python.hir import nodes
|
|
9
|
+
from coretrace_python.source import SourceFile, SourceSpan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HIRBuildError(Exception):
|
|
13
|
+
"""A source-located failure to represent parsed syntax as PyHIR."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_BINARY_OPERATORS = {
|
|
17
|
+
ast.Add: "add",
|
|
18
|
+
ast.Sub: "sub",
|
|
19
|
+
ast.Mult: "mul",
|
|
20
|
+
ast.Div: "div",
|
|
21
|
+
ast.FloorDiv: "floor_div",
|
|
22
|
+
ast.Mod: "mod",
|
|
23
|
+
ast.Pow: "pow",
|
|
24
|
+
ast.LShift: "lshift",
|
|
25
|
+
ast.RShift: "rshift",
|
|
26
|
+
ast.BitOr: "bit_or",
|
|
27
|
+
ast.BitXor: "bit_xor",
|
|
28
|
+
ast.BitAnd: "bit_and",
|
|
29
|
+
ast.MatMult: "matmul",
|
|
30
|
+
}
|
|
31
|
+
_UNARY_OPERATORS = {ast.Invert: "invert", ast.Not: "not", ast.UAdd: "pos", ast.USub: "neg"}
|
|
32
|
+
_COMPREHENSIONS = {ast.ListComp: "list", ast.SetComp: "set", ast.GeneratorExp: "generator", ast.DictComp: "dict"}
|
|
33
|
+
_COMPARE_OPERATORS = {
|
|
34
|
+
ast.Eq: "eq",
|
|
35
|
+
ast.NotEq: "not_eq",
|
|
36
|
+
ast.Lt: "lt",
|
|
37
|
+
ast.LtE: "lt_eq",
|
|
38
|
+
ast.Gt: "gt",
|
|
39
|
+
ast.GtE: "gt_eq",
|
|
40
|
+
ast.Is: "is",
|
|
41
|
+
ast.IsNot: "is_not",
|
|
42
|
+
ast.In: "in",
|
|
43
|
+
ast.NotIn: "not_in",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AstHIRBuilder:
|
|
48
|
+
"""Build PyHIR while containing every dependency on ``ast`` in this adapter."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, source: SourceFile) -> None:
|
|
51
|
+
self._source = source
|
|
52
|
+
|
|
53
|
+
def span(self, node: ast.AST) -> SourceSpan:
|
|
54
|
+
end_line = getattr(node, "end_lineno", None)
|
|
55
|
+
end_offset = getattr(node, "end_col_offset", None)
|
|
56
|
+
return SourceSpan(
|
|
57
|
+
source_id=self._source.source_id,
|
|
58
|
+
start_line=getattr(node, "lineno", 1),
|
|
59
|
+
start_column=getattr(node, "col_offset", 0) + 1,
|
|
60
|
+
end_line=end_line,
|
|
61
|
+
end_column=end_offset + 1 if end_offset is not None else None,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def fail(self, node: ast.AST, message: str | None = None) -> NoReturn:
|
|
65
|
+
span = self.span(node)
|
|
66
|
+
detail = message or f"unsupported syntax: {type(node).__name__}"
|
|
67
|
+
raise HIRBuildError(f"{span.display()}: {detail}")
|
|
68
|
+
|
|
69
|
+
def expression(self, node: ast.expr) -> nodes.Expression:
|
|
70
|
+
span = self.span(node)
|
|
71
|
+
if isinstance(node, ast.Name):
|
|
72
|
+
return nodes.Name(node.id, span)
|
|
73
|
+
if isinstance(node, ast.Constant):
|
|
74
|
+
return nodes.Constant(node.value, span)
|
|
75
|
+
if isinstance(node, ast.BinOp):
|
|
76
|
+
operator = _BINARY_OPERATORS.get(type(node.op))
|
|
77
|
+
if operator is None:
|
|
78
|
+
self.fail(node.op)
|
|
79
|
+
return nodes.BinaryOp(operator, self.expression(node.left), self.expression(node.right), span)
|
|
80
|
+
if isinstance(node, ast.UnaryOp):
|
|
81
|
+
operator = _UNARY_OPERATORS.get(type(node.op))
|
|
82
|
+
if operator is None:
|
|
83
|
+
self.fail(node.op)
|
|
84
|
+
return nodes.UnaryOp(operator, self.expression(node.operand), span)
|
|
85
|
+
if isinstance(node, ast.Compare):
|
|
86
|
+
comparisons: list[nodes.Expression] = []
|
|
87
|
+
left = self.expression(node.left)
|
|
88
|
+
for op, comparator in zip(node.ops, node.comparators, strict=True):
|
|
89
|
+
operator = _COMPARE_OPERATORS.get(type(op))
|
|
90
|
+
if operator is None:
|
|
91
|
+
self.fail(op)
|
|
92
|
+
right = self.expression(comparator)
|
|
93
|
+
comparisons.append(nodes.Compare(operator, left, right, span))
|
|
94
|
+
left = right
|
|
95
|
+
if len(comparisons) == 1:
|
|
96
|
+
return comparisons[0]
|
|
97
|
+
return nodes.BoolOp("and", tuple(comparisons), span)
|
|
98
|
+
if isinstance(node, ast.BoolOp):
|
|
99
|
+
operator = "and" if isinstance(node.op, ast.And) else "or"
|
|
100
|
+
return nodes.BoolOp(operator, tuple(self.expression(v) for v in node.values), span)
|
|
101
|
+
if isinstance(node, ast.Tuple):
|
|
102
|
+
return nodes.Tuple(self.elements(node.elts), span)
|
|
103
|
+
if isinstance(node, ast.List):
|
|
104
|
+
return nodes.List(self.elements(node.elts), span)
|
|
105
|
+
if isinstance(node, ast.Dict):
|
|
106
|
+
items: list[tuple[nodes.Expression | None, nodes.Expression]] = []
|
|
107
|
+
for key, value in zip(node.keys, node.values, strict=True):
|
|
108
|
+
items.append((None if key is None else self.expression(key), self.expression(value)))
|
|
109
|
+
return nodes.Dict(tuple(items), span)
|
|
110
|
+
if isinstance(node, ast.JoinedStr):
|
|
111
|
+
return nodes.FormattedString(tuple(self.formatted_parts(node)), span)
|
|
112
|
+
if isinstance(node, ast.Slice):
|
|
113
|
+
return nodes.Slice(
|
|
114
|
+
self.expression(node.lower) if node.lower is not None else None,
|
|
115
|
+
self.expression(node.upper) if node.upper is not None else None,
|
|
116
|
+
self.expression(node.step) if node.step is not None else None,
|
|
117
|
+
span,
|
|
118
|
+
)
|
|
119
|
+
if isinstance(node, ast.Starred):
|
|
120
|
+
return nodes.Starred(self.expression(node.value), span)
|
|
121
|
+
if isinstance(node, ast.Set):
|
|
122
|
+
return nodes.Set(self.elements(node.elts), span)
|
|
123
|
+
if isinstance(node, ast.IfExp):
|
|
124
|
+
return nodes.Conditional(
|
|
125
|
+
self.expression(node.test), self.expression(node.body), self.expression(node.orelse), span
|
|
126
|
+
)
|
|
127
|
+
if isinstance(node, ast.Lambda):
|
|
128
|
+
return nodes.Lambda(self.parameters(node.args), self.expression(node.body), span)
|
|
129
|
+
if isinstance(node, ast.DictComp):
|
|
130
|
+
generators = tuple(self.generator(generator) for generator in node.generators)
|
|
131
|
+
return nodes.Comprehension("dict", self.expression(node.value), generators, span, self.expression(node.key))
|
|
132
|
+
if isinstance(node, ast.Attribute):
|
|
133
|
+
return nodes.Attribute(self.expression(node.value), node.attr, span)
|
|
134
|
+
if isinstance(node, ast.Subscript):
|
|
135
|
+
return nodes.Subscript(self.expression(node.value), self.expression(node.slice), span)
|
|
136
|
+
if isinstance(node, ast.Call):
|
|
137
|
+
arguments = tuple(self.expression(argument) for argument in node.args)
|
|
138
|
+
keywords = tuple(
|
|
139
|
+
nodes.Keyword(keyword.arg, self.expression(keyword.value), self.span(keyword))
|
|
140
|
+
for keyword in node.keywords
|
|
141
|
+
)
|
|
142
|
+
return nodes.Call(self.expression(node.func), arguments, keywords, span)
|
|
143
|
+
if isinstance(node, ast.Await):
|
|
144
|
+
return nodes.Await(self.expression(node.value), span)
|
|
145
|
+
if isinstance(node, ast.Yield):
|
|
146
|
+
yielded = self.expression(node.value) if node.value is not None else None
|
|
147
|
+
return nodes.Yield(yielded, span)
|
|
148
|
+
if isinstance(node, ast.YieldFrom):
|
|
149
|
+
self.fail(node, "yield from is not supported yet")
|
|
150
|
+
if isinstance(node, (ast.ListComp, ast.SetComp, ast.GeneratorExp)):
|
|
151
|
+
generators = tuple(self.generator(generator) for generator in node.generators)
|
|
152
|
+
return nodes.Comprehension(
|
|
153
|
+
_COMPREHENSIONS[type(node)], self.expression(node.elt), generators, span
|
|
154
|
+
)
|
|
155
|
+
self.fail(node)
|
|
156
|
+
|
|
157
|
+
def elements(self, elements: list[ast.expr]) -> tuple[nodes.Expression, ...]:
|
|
158
|
+
return tuple(self.expression(element) for element in elements)
|
|
159
|
+
|
|
160
|
+
def formatted_parts(self, node: ast.JoinedStr) -> list[nodes.Expression]:
|
|
161
|
+
parts: list[nodes.Expression] = []
|
|
162
|
+
for value in node.values:
|
|
163
|
+
if isinstance(value, ast.Constant):
|
|
164
|
+
parts.append(nodes.Constant(value.value, self.span(value)))
|
|
165
|
+
elif isinstance(value, ast.FormattedValue):
|
|
166
|
+
parts.append(self.expression(value.value))
|
|
167
|
+
if isinstance(value.format_spec, ast.JoinedStr):
|
|
168
|
+
parts.extend(p for p in self.formatted_parts(value.format_spec) if not isinstance(p, nodes.Constant))
|
|
169
|
+
else: # pragma: no cover - the grammar allows nothing else
|
|
170
|
+
self.fail(value)
|
|
171
|
+
return parts
|
|
172
|
+
|
|
173
|
+
def target(self, node: ast.expr) -> nodes.Target:
|
|
174
|
+
if isinstance(node, ast.Name):
|
|
175
|
+
return nodes.Name(node.id, self.span(node))
|
|
176
|
+
if isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
177
|
+
target = self.expression(node)
|
|
178
|
+
assert isinstance(target, (nodes.Attribute, nodes.Subscript))
|
|
179
|
+
return target
|
|
180
|
+
if isinstance(node, (ast.Tuple, ast.List)):
|
|
181
|
+
for element in node.elts:
|
|
182
|
+
if isinstance(element, ast.Starred):
|
|
183
|
+
self.fail(element, "starred assignment targets are not supported yet")
|
|
184
|
+
return nodes.Tuple(tuple(self.target(element) for element in node.elts), self.span(node))
|
|
185
|
+
self.fail(node, f"unsupported assignment target: {type(node).__name__}")
|
|
186
|
+
|
|
187
|
+
def generator(self, node: ast.comprehension) -> nodes.ComprehensionGenerator:
|
|
188
|
+
if node.is_async:
|
|
189
|
+
self.fail(node.target, "async comprehensions are not supported yet")
|
|
190
|
+
target = self.target(node.target)
|
|
191
|
+
conditions = tuple(self.expression(condition) for condition in node.ifs)
|
|
192
|
+
# ``ast.comprehension`` carries no location; span it from the target to the last clause.
|
|
193
|
+
last = self.span(node.ifs[-1] if node.ifs else node.iter)
|
|
194
|
+
span = SourceSpan(
|
|
195
|
+
self._source.source_id,
|
|
196
|
+
target.span.start_line,
|
|
197
|
+
target.span.start_column,
|
|
198
|
+
last.end_line,
|
|
199
|
+
last.end_column,
|
|
200
|
+
)
|
|
201
|
+
return nodes.ComprehensionGenerator(target, self.expression(node.iter), conditions, span)
|
|
202
|
+
|
|
203
|
+
def statements(self, node: ast.stmt) -> list[nodes.Statement]:
|
|
204
|
+
"""The HIR statements of one source statement: several for ``a = b = value``,
|
|
205
|
+
which assigns a hidden local once and every target from it."""
|
|
206
|
+
|
|
207
|
+
span = self.span(node)
|
|
208
|
+
if isinstance(node, ast.Assign) and len(node.targets) > 1:
|
|
209
|
+
hidden = nodes.Name(f"_coretrace_chain_{span.start_line}_{span.start_column}", span)
|
|
210
|
+
found: list[nodes.Statement] = [nodes.Assign(hidden, self.expression(node.value), span)]
|
|
211
|
+
found.extend(nodes.Assign(self.target(target), hidden, self.span(target)) for target in node.targets)
|
|
212
|
+
return found
|
|
213
|
+
return [self.statement(node)]
|
|
214
|
+
|
|
215
|
+
def statement(self, node: ast.stmt) -> nodes.Statement:
|
|
216
|
+
span = self.span(node)
|
|
217
|
+
if isinstance(node, ast.Assign):
|
|
218
|
+
return nodes.Assign(self.target(node.targets[0]), self.expression(node.value), span)
|
|
219
|
+
if isinstance(node, ast.AnnAssign):
|
|
220
|
+
# The annotation never affects behaviour; a bare declaration binds nothing
|
|
221
|
+
# but names a dataclass field or an annotated local.
|
|
222
|
+
if node.value is None:
|
|
223
|
+
annotation: nodes.Expression | None = None
|
|
224
|
+
try:
|
|
225
|
+
annotation = self.expression(node.annotation)
|
|
226
|
+
except HIRBuildError:
|
|
227
|
+
annotation = None
|
|
228
|
+
name = node.target.id if isinstance(node.target, ast.Name) else None
|
|
229
|
+
return nodes.Declaration(name, annotation, span) if name is not None else nodes.Pass(span)
|
|
230
|
+
return nodes.Assign(self.target(node.target), self.expression(node.value), span)
|
|
231
|
+
if isinstance(node, ast.AugAssign):
|
|
232
|
+
operator = _BINARY_OPERATORS.get(type(node.op))
|
|
233
|
+
if operator is None:
|
|
234
|
+
self.fail(node.op)
|
|
235
|
+
target = self.target(node.target)
|
|
236
|
+
if isinstance(target, nodes.Tuple):
|
|
237
|
+
self.fail(node.target, "unsupported augmented assignment target")
|
|
238
|
+
return nodes.AugAssign(target, operator, self.expression(node.value), span)
|
|
239
|
+
if isinstance(node, ast.Assert):
|
|
240
|
+
message = self.expression(node.msg) if node.msg is not None else None
|
|
241
|
+
return nodes.Assert(self.expression(node.test), message, span)
|
|
242
|
+
if isinstance(node, (ast.With, ast.AsyncWith)):
|
|
243
|
+
items = []
|
|
244
|
+
for item in node.items:
|
|
245
|
+
bound: nodes.Name | None = None
|
|
246
|
+
if item.optional_vars is not None:
|
|
247
|
+
if not isinstance(item.optional_vars, ast.Name):
|
|
248
|
+
self.fail(
|
|
249
|
+
item.optional_vars, "only a single name is supported as a with target"
|
|
250
|
+
)
|
|
251
|
+
bound = nodes.Name(item.optional_vars.id, self.span(item.optional_vars))
|
|
252
|
+
items.append(
|
|
253
|
+
nodes.WithItem(
|
|
254
|
+
self.expression(item.context_expr), bound, self.span(item.context_expr)
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return nodes.With(tuple(items), self.block(node.body), isinstance(node, ast.AsyncWith), span)
|
|
258
|
+
if isinstance(node, ast.Return):
|
|
259
|
+
value = self.expression(node.value) if node.value is not None else None
|
|
260
|
+
return nodes.Return(value, span)
|
|
261
|
+
if isinstance(node, ast.Expr):
|
|
262
|
+
return nodes.ExpressionStatement(self.expression(node.value), span)
|
|
263
|
+
if isinstance(node, ast.Pass):
|
|
264
|
+
return nodes.Pass(span)
|
|
265
|
+
if isinstance(node, ast.Import):
|
|
266
|
+
aliases = tuple(
|
|
267
|
+
nodes.ImportAlias(alias.name, alias.asname, self.span(alias)) for alias in node.names
|
|
268
|
+
)
|
|
269
|
+
return nodes.Import(aliases, span)
|
|
270
|
+
if isinstance(node, ast.ImportFrom):
|
|
271
|
+
aliases = tuple(
|
|
272
|
+
nodes.ImportAlias(alias.name, alias.asname, self.span(alias)) for alias in node.names
|
|
273
|
+
)
|
|
274
|
+
return nodes.ImportFrom(node.module, aliases, node.level, span)
|
|
275
|
+
if isinstance(node, ast.If):
|
|
276
|
+
return nodes.If(
|
|
277
|
+
self.expression(node.test), self.block(node.body), self.block(node.orelse), span
|
|
278
|
+
)
|
|
279
|
+
if isinstance(node, ast.While):
|
|
280
|
+
return nodes.While(self.expression(node.test), self.block(node.body), span, self.block(node.orelse))
|
|
281
|
+
if isinstance(node, (ast.For, ast.AsyncFor)):
|
|
282
|
+
body = self.block(node.body)
|
|
283
|
+
if isinstance(node.target, ast.Name):
|
|
284
|
+
target = nodes.Name(node.target.id, self.span(node.target))
|
|
285
|
+
else:
|
|
286
|
+
# ``for k, v in items`` binds a hidden local and destructures it first.
|
|
287
|
+
target_span = self.span(node.target)
|
|
288
|
+
target = nodes.Name(f"_coretrace_item_{target_span.start_line}_{target_span.start_column}", target_span)
|
|
289
|
+
body = (nodes.Assign(self.target(node.target), target, target_span), *body)
|
|
290
|
+
return nodes.For(
|
|
291
|
+
target,
|
|
292
|
+
self.expression(node.iter),
|
|
293
|
+
body,
|
|
294
|
+
isinstance(node, ast.AsyncFor),
|
|
295
|
+
span,
|
|
296
|
+
self.block(node.orelse),
|
|
297
|
+
)
|
|
298
|
+
if isinstance(node, ast.Break):
|
|
299
|
+
return nodes.Break(span)
|
|
300
|
+
if isinstance(node, ast.Delete):
|
|
301
|
+
return nodes.Delete(tuple(self.target(target) for target in node.targets), span)
|
|
302
|
+
if isinstance(node, ast.Match):
|
|
303
|
+
cases = tuple(
|
|
304
|
+
nodes.MatchCase(
|
|
305
|
+
self.pattern(case.pattern),
|
|
306
|
+
self.expression(case.guard) if case.guard is not None else None,
|
|
307
|
+
self.block(case.body),
|
|
308
|
+
self.span(case.pattern),
|
|
309
|
+
)
|
|
310
|
+
for case in node.cases
|
|
311
|
+
)
|
|
312
|
+
return nodes.Match(self.expression(node.subject), cases, span)
|
|
313
|
+
if isinstance(node, ast.Continue):
|
|
314
|
+
return nodes.Continue(span)
|
|
315
|
+
if isinstance(node, ast.Raise):
|
|
316
|
+
exception = self.expression(node.exc) if node.exc is not None else None
|
|
317
|
+
cause = self.expression(node.cause) if node.cause is not None else None
|
|
318
|
+
return nodes.Raise(exception, span, cause)
|
|
319
|
+
if isinstance(node, ast.Try):
|
|
320
|
+
handlers = []
|
|
321
|
+
for handler in node.handlers:
|
|
322
|
+
handler_type = self.expression(handler.type) if handler.type is not None else None
|
|
323
|
+
handlers.append(
|
|
324
|
+
nodes.ExceptHandler(
|
|
325
|
+
handler_type, handler.name, self.block(handler.body), self.span(handler)
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
return nodes.Try(
|
|
329
|
+
self.block(node.body),
|
|
330
|
+
tuple(handlers),
|
|
331
|
+
self.block(node.orelse),
|
|
332
|
+
self.block(node.finalbody),
|
|
333
|
+
span,
|
|
334
|
+
)
|
|
335
|
+
if isinstance(node, getattr(ast, "TryStar", ())):
|
|
336
|
+
self.fail(node, "except* groups are not supported yet")
|
|
337
|
+
if isinstance(node, ast.Global):
|
|
338
|
+
return nodes.Global(tuple(node.names), span)
|
|
339
|
+
if isinstance(node, ast.Nonlocal):
|
|
340
|
+
return nodes.Nonlocal(tuple(node.names), span)
|
|
341
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
342
|
+
return self.function(node)
|
|
343
|
+
if isinstance(node, ast.ClassDef):
|
|
344
|
+
return self.class_definition(node)
|
|
345
|
+
self.fail(node)
|
|
346
|
+
|
|
347
|
+
def pattern(self, node: ast.pattern) -> nodes.Pattern:
|
|
348
|
+
span = self.span(node)
|
|
349
|
+
if isinstance(node, ast.MatchValue):
|
|
350
|
+
return nodes.ValuePattern(self.expression(node.value), span)
|
|
351
|
+
if isinstance(node, ast.MatchSingleton):
|
|
352
|
+
return nodes.SingletonPattern(node.value, span)
|
|
353
|
+
if isinstance(node, ast.MatchAs):
|
|
354
|
+
if node.name is None:
|
|
355
|
+
return nodes.WildcardPattern(span)
|
|
356
|
+
inner = self.pattern(node.pattern) if node.pattern is not None else None
|
|
357
|
+
return nodes.CapturePattern(node.name, inner, span)
|
|
358
|
+
if isinstance(node, ast.MatchOr):
|
|
359
|
+
return nodes.OrPattern(tuple(self.pattern(p) for p in node.patterns), span)
|
|
360
|
+
if isinstance(node, ast.MatchStar):
|
|
361
|
+
return nodes.StarPattern(node.name, span)
|
|
362
|
+
if isinstance(node, ast.MatchSequence):
|
|
363
|
+
return nodes.SequencePattern(tuple(self.pattern(p) for p in node.patterns), span)
|
|
364
|
+
if isinstance(node, ast.MatchMapping):
|
|
365
|
+
return nodes.MappingPattern(
|
|
366
|
+
tuple(self.expression(k) for k in node.keys),
|
|
367
|
+
tuple(self.pattern(p) for p in node.patterns),
|
|
368
|
+
node.rest,
|
|
369
|
+
span,
|
|
370
|
+
)
|
|
371
|
+
if isinstance(node, ast.MatchClass):
|
|
372
|
+
return nodes.ClassPattern(
|
|
373
|
+
self.expression(node.cls),
|
|
374
|
+
tuple(self.pattern(p) for p in node.patterns),
|
|
375
|
+
tuple(node.kwd_attrs),
|
|
376
|
+
tuple(self.pattern(p) for p in node.kwd_patterns),
|
|
377
|
+
span,
|
|
378
|
+
)
|
|
379
|
+
return nodes.UnsupportedPattern(type(node).__name__, span)
|
|
380
|
+
|
|
381
|
+
def block(self, statements: list[ast.stmt]) -> tuple[nodes.Statement, ...]:
|
|
382
|
+
return tuple(found for statement in statements for found in self.statements(statement))
|
|
383
|
+
|
|
384
|
+
def class_definition(self, node: ast.ClassDef) -> nodes.Class:
|
|
385
|
+
if node.keywords:
|
|
386
|
+
self.fail(node, "class keyword arguments are not supported yet")
|
|
387
|
+
bases = tuple(self.expression(base) for base in node.bases)
|
|
388
|
+
body = self.block(node.body)
|
|
389
|
+
decorators = tuple(self.expression(d) for d in node.decorator_list)
|
|
390
|
+
return nodes.Class(node.name, bases, body, self.span(node), decorators)
|
|
391
|
+
|
|
392
|
+
def function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> nodes.Function:
|
|
393
|
+
body = self.block(node.body)
|
|
394
|
+
return nodes.Function(
|
|
395
|
+
name=node.name,
|
|
396
|
+
parameters=self.parameters(node.args),
|
|
397
|
+
body=body,
|
|
398
|
+
is_async=isinstance(node, ast.AsyncFunctionDef),
|
|
399
|
+
span=self.span(node),
|
|
400
|
+
decorators=tuple(self.expression(d) for d in node.decorator_list),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def parameters(self, arguments: ast.arguments) -> tuple[nodes.Parameter, ...]:
|
|
404
|
+
positional = [*arguments.posonlyargs, *arguments.args]
|
|
405
|
+
defaults: list[ast.expr | None] = [None] * (len(positional) - len(arguments.defaults))
|
|
406
|
+
defaults.extend(arguments.defaults)
|
|
407
|
+
parameters: list[nodes.Parameter] = []
|
|
408
|
+
for argument, default in zip(positional, defaults, strict=True):
|
|
409
|
+
parameters.append(self.parameter(argument, default, "positional"))
|
|
410
|
+
if arguments.vararg is not None:
|
|
411
|
+
parameters.append(self.parameter(arguments.vararg, None, "var_positional"))
|
|
412
|
+
for argument, default in zip(arguments.kwonlyargs, arguments.kw_defaults, strict=True):
|
|
413
|
+
parameters.append(self.parameter(argument, default, "keyword"))
|
|
414
|
+
if arguments.kwarg is not None:
|
|
415
|
+
parameters.append(self.parameter(arguments.kwarg, None, "var_keyword"))
|
|
416
|
+
return tuple(parameters)
|
|
417
|
+
|
|
418
|
+
def parameter(self, argument: ast.arg, default: ast.expr | None, kind: str) -> nodes.Parameter:
|
|
419
|
+
value = self.expression(default) if default is not None else None
|
|
420
|
+
annotation: nodes.Expression | None = None
|
|
421
|
+
if argument.annotation is not None:
|
|
422
|
+
# Annotations never affect behaviour; one the HIR cannot represent is dropped.
|
|
423
|
+
try:
|
|
424
|
+
annotation = self.expression(argument.annotation)
|
|
425
|
+
except HIRBuildError:
|
|
426
|
+
annotation = None
|
|
427
|
+
return nodes.Parameter(argument.arg, self.span(argument), value, kind, annotation)
|
|
428
|
+
|
|
429
|
+
def module(self, tree: ast.Module) -> nodes.Module:
|
|
430
|
+
body = self.block(tree.body)
|
|
431
|
+
if tree.body:
|
|
432
|
+
first_span = self.span(tree.body[0])
|
|
433
|
+
last_span = self.span(tree.body[-1])
|
|
434
|
+
span = SourceSpan(
|
|
435
|
+
self._source.source_id,
|
|
436
|
+
first_span.start_line,
|
|
437
|
+
first_span.start_column,
|
|
438
|
+
last_span.end_line,
|
|
439
|
+
last_span.end_column,
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
span = SourceSpan(self._source.source_id, 1, 1, 1, 1)
|
|
443
|
+
return nodes.Module(self._source.module_name, body, span, self._source.is_package)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def build_module(source: SourceFile, tree: ast.Module) -> nodes.Module:
|
|
447
|
+
return AstHIRBuilder(source).module(tree)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
|
|
5
|
+
from coretrace_python.source import SourceFile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ParseError(Exception):
|
|
9
|
+
"""A source-located Python parsing failure."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_source(source: str, filename: str = "<unknown>") -> ast.Module:
|
|
13
|
+
try:
|
|
14
|
+
return ast.parse(source, filename=filename)
|
|
15
|
+
except SyntaxError as error:
|
|
16
|
+
line = error.lineno or 0
|
|
17
|
+
column = error.offset or 0
|
|
18
|
+
message = error.msg or "invalid syntax"
|
|
19
|
+
raise ParseError(f"{filename}:{line}:{column}: {message}") from error
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_source_file(source: SourceFile) -> ast.Module:
|
|
23
|
+
return parse_source(source.text, filename=str(source.source_id))
|