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,463 @@
|
|
|
1
|
+
"""Function summaries (architecture §19).
|
|
2
|
+
|
|
3
|
+
A summary says which parameters a function's return value depends on and which external
|
|
4
|
+
symbols its parameters reach, directly or through calls to other known functions. It is
|
|
5
|
+
computed as a dependence data-flow problem over the SSA form and iterated to a fixpoint
|
|
6
|
+
over the call graph, so recursion converges to the least solution. Summaries carry no
|
|
7
|
+
security knowledge: the taint engine decides which external symbols matter.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from types import MappingProxyType
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
from coretrace_python.abstract import (
|
|
18
|
+
ATTRIBUTES,
|
|
19
|
+
ELEMENTS,
|
|
20
|
+
HeapAnalysis,
|
|
21
|
+
HeapFacts,
|
|
22
|
+
HeapLocation,
|
|
23
|
+
mutated_by,
|
|
24
|
+
)
|
|
25
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
26
|
+
from coretrace_python.cfg import CFG, BlockId, CFGAnalysis
|
|
27
|
+
from coretrace_python.dataflow import DataflowProblem, Direction, solve
|
|
28
|
+
from coretrace_python.interprocedural.callgraph import (
|
|
29
|
+
CallGraph,
|
|
30
|
+
CallGraphAnalysis,
|
|
31
|
+
ExternalSymbol,
|
|
32
|
+
KnownFunction,
|
|
33
|
+
UnknownTarget,
|
|
34
|
+
)
|
|
35
|
+
from coretrace_python.ir.model import (
|
|
36
|
+
BasicBlock,
|
|
37
|
+
Branch,
|
|
38
|
+
BuildDict,
|
|
39
|
+
BuildList,
|
|
40
|
+
BuildSet,
|
|
41
|
+
BuildTuple,
|
|
42
|
+
Call,
|
|
43
|
+
Constant,
|
|
44
|
+
ForNext,
|
|
45
|
+
FunctionIR,
|
|
46
|
+
GetAttr,
|
|
47
|
+
GetItem,
|
|
48
|
+
GetIter,
|
|
49
|
+
Global,
|
|
50
|
+
Instruction,
|
|
51
|
+
Jump,
|
|
52
|
+
MakeFunction,
|
|
53
|
+
Phi,
|
|
54
|
+
Return,
|
|
55
|
+
SetAttr,
|
|
56
|
+
SetItem,
|
|
57
|
+
Symbol,
|
|
58
|
+
Undefined,
|
|
59
|
+
Value,
|
|
60
|
+
)
|
|
61
|
+
from coretrace_python.ir.ssa import SSAAnalysis
|
|
62
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
63
|
+
from coretrace_python.source import SourceSpan
|
|
64
|
+
|
|
65
|
+
Dependencies = frozenset[int]
|
|
66
|
+
NONE: Dependencies = frozenset()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class Dep:
|
|
71
|
+
"""What a value depends on: parameter indices and results of external symbols."""
|
|
72
|
+
|
|
73
|
+
parameters: Dependencies = NONE
|
|
74
|
+
externals: frozenset[SymbolId] = frozenset()
|
|
75
|
+
|
|
76
|
+
def __or__(self, other: Dep) -> Dep:
|
|
77
|
+
return Dep(self.parameters | other.parameters, self.externals | other.externals)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
EMPTY = Dep()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class ExternalCall:
|
|
85
|
+
"""An external symbol reached from this function; dependencies are parameter indices."""
|
|
86
|
+
|
|
87
|
+
symbol: SymbolId
|
|
88
|
+
argument_dependencies: tuple[Dependencies, ...]
|
|
89
|
+
keyword_dependencies: Dependencies
|
|
90
|
+
location: SourceSpan
|
|
91
|
+
call_site: SourceSpan | None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class Mutation:
|
|
96
|
+
"""What the function stores into ``field`` of its ``parameter``: data of other
|
|
97
|
+
parameters, and results of external symbols (§22)."""
|
|
98
|
+
|
|
99
|
+
parameter: int
|
|
100
|
+
field: str
|
|
101
|
+
dependencies: Dependencies
|
|
102
|
+
externals: frozenset[SymbolId]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class FunctionSummary:
|
|
107
|
+
name: str
|
|
108
|
+
parameters: int
|
|
109
|
+
return_dependencies: Dependencies
|
|
110
|
+
external_calls: tuple[ExternalCall, ...]
|
|
111
|
+
unsupported: bool = False
|
|
112
|
+
return_externals: frozenset[SymbolId] = frozenset()
|
|
113
|
+
mutations: tuple[Mutation, ...] = ()
|
|
114
|
+
side_effects: frozenset[str] = frozenset()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SummaryIndex:
|
|
118
|
+
"""Summaries of every project function, keyed by project symbol (§21)."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, summaries: Mapping[SymbolId, FunctionSummary] | None = None) -> None:
|
|
121
|
+
self.summaries: Mapping[SymbolId, FunctionSummary] = MappingProxyType(
|
|
122
|
+
dict(summaries or {})
|
|
123
|
+
)
|
|
124
|
+
self.symbols = tuple(sorted(self.summaries, key=str))
|
|
125
|
+
|
|
126
|
+
def summary(self, symbol: SymbolId) -> FunctionSummary | None:
|
|
127
|
+
return self.summaries.get(symbol)
|
|
128
|
+
|
|
129
|
+
def __eq__(self, other: object) -> bool:
|
|
130
|
+
return isinstance(other, SummaryIndex) and dict(self.summaries) == dict(other.summaries)
|
|
131
|
+
|
|
132
|
+
def __hash__(self) -> int:
|
|
133
|
+
return hash(self.symbols)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ProjectSummaries(Analysis[SummaryIndex]):
|
|
137
|
+
"""The project-wide summary index. The engine provides it for multi-file analysis;
|
|
138
|
+
on its own a module sees an empty index, which is the single-file behaviour."""
|
|
139
|
+
|
|
140
|
+
name: ClassVar[str] = "interprocedural.project"
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def compute(cls, ctx: AnalysisContext) -> SummaryIndex:
|
|
144
|
+
return SummaryIndex()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SummaryTable:
|
|
148
|
+
def __init__(self, summaries: Mapping[str, FunctionSummary]) -> None:
|
|
149
|
+
self._summaries = MappingProxyType(dict(summaries))
|
|
150
|
+
self.names = tuple(summaries)
|
|
151
|
+
|
|
152
|
+
def summary(self, name: str) -> FunctionSummary:
|
|
153
|
+
return self._summaries[name]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
Key = Value | HeapLocation
|
|
157
|
+
State = Mapping[Key, Dep]
|
|
158
|
+
_CallKey = tuple[SymbolId, SourceSpan, SourceSpan | None]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class _DependenceProblem(DataflowProblem[State]):
|
|
162
|
+
direction: ClassVar[Direction] = Direction.FORWARD
|
|
163
|
+
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
name: str,
|
|
167
|
+
function: FunctionIR,
|
|
168
|
+
graph: CallGraph,
|
|
169
|
+
table: Mapping[str, FunctionSummary],
|
|
170
|
+
project: Mapping[SymbolId, FunctionSummary] | None = None,
|
|
171
|
+
heap: HeapFacts | None = None,
|
|
172
|
+
) -> None:
|
|
173
|
+
self.name = name
|
|
174
|
+
self.function = function
|
|
175
|
+
self.graph = graph
|
|
176
|
+
self.table = table
|
|
177
|
+
self.project = project or {}
|
|
178
|
+
self.heap = heap or HeapFacts({})
|
|
179
|
+
self.blocks = {block.id: block for block in function.blocks}
|
|
180
|
+
self.defs: dict[Value, Instruction] = {
|
|
181
|
+
i.result: i for block in function.blocks for i in block.instructions if i.result
|
|
182
|
+
}
|
|
183
|
+
self.external: dict[_CallKey, ExternalCall] = {}
|
|
184
|
+
self.returns: Dep = EMPTY
|
|
185
|
+
self.stores: dict[HeapLocation, Dep] = {}
|
|
186
|
+
self.mutated_globals: set[str] = set()
|
|
187
|
+
|
|
188
|
+
# ------------------------------------------------------------------ heap
|
|
189
|
+
|
|
190
|
+
def deep(self, value: Value, state: Mapping[Key, Dep]) -> Dep:
|
|
191
|
+
"""What a value depends on, contents of the objects it points to included."""
|
|
192
|
+
|
|
193
|
+
deps = state.get(value, EMPTY)
|
|
194
|
+
for field in (ELEMENTS, ATTRIBUTES):
|
|
195
|
+
for location in self.heap.locations(value, field):
|
|
196
|
+
deps |= state.get(location, EMPTY)
|
|
197
|
+
return deps
|
|
198
|
+
|
|
199
|
+
def store(self, state: dict[Key, Dep], receiver: Value, field: str, deps: Dep) -> None:
|
|
200
|
+
for location in self.heap.locations(receiver, field):
|
|
201
|
+
state[location] = state.get(location, EMPTY) | deps
|
|
202
|
+
self.stores[location] = self.stores.get(location, EMPTY) | deps
|
|
203
|
+
if location.object.site.kind == "global":
|
|
204
|
+
self.mutated_globals.add(location.object.site.name)
|
|
205
|
+
|
|
206
|
+
def initial(self) -> State:
|
|
207
|
+
return MappingProxyType(
|
|
208
|
+
{p: Dep(frozenset({i})) for i, p in enumerate(self.function.parameters)}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def join(self, a: State, b: State) -> State:
|
|
212
|
+
merged = dict(a)
|
|
213
|
+
for value, deps in b.items():
|
|
214
|
+
merged[value] = merged[value] | deps if value in merged else deps
|
|
215
|
+
return MappingProxyType(merged)
|
|
216
|
+
|
|
217
|
+
def evaluate(self, block: BasicBlock, incoming: Mapping[BlockId, State]) -> State:
|
|
218
|
+
states = list(incoming.values())
|
|
219
|
+
state: dict[Key, Dep] = dict(states[0]) if states else {}
|
|
220
|
+
for other in states[1:]:
|
|
221
|
+
state = dict(self.join(state, other))
|
|
222
|
+
for instruction in block.instructions:
|
|
223
|
+
if isinstance(instruction, SetAttr):
|
|
224
|
+
self.store(state, instruction.object, ATTRIBUTES, state.get(instruction.value, EMPTY))
|
|
225
|
+
elif isinstance(instruction, SetItem):
|
|
226
|
+
self.store(state, instruction.object, ELEMENTS, state.get(instruction.value, EMPTY))
|
|
227
|
+
if instruction.result is None:
|
|
228
|
+
continue
|
|
229
|
+
if isinstance(instruction, Phi):
|
|
230
|
+
deps = EMPTY
|
|
231
|
+
for value, predecessor in instruction.incoming:
|
|
232
|
+
if predecessor in incoming:
|
|
233
|
+
deps |= incoming[predecessor].get(value, EMPTY)
|
|
234
|
+
elif isinstance(instruction, Call):
|
|
235
|
+
deps = self.call(instruction, state)
|
|
236
|
+
else:
|
|
237
|
+
deps = self.instruction(instruction, state)
|
|
238
|
+
if isinstance(instruction, GetAttr | GetItem | GetIter):
|
|
239
|
+
deps |= self.loaded(instruction, state)
|
|
240
|
+
state[instruction.result] = deps
|
|
241
|
+
terminator = block.terminator
|
|
242
|
+
if isinstance(terminator, ForNext) and terminator.result is not None:
|
|
243
|
+
state[terminator.result] = self.deep(terminator.iterator, state)
|
|
244
|
+
if isinstance(terminator, Return) and terminator.value is not None:
|
|
245
|
+
self.returns |= state.get(terminator.value, EMPTY)
|
|
246
|
+
return MappingProxyType(state)
|
|
247
|
+
|
|
248
|
+
def flow(self, cfg: CFG, block_id: BlockId, incoming: Mapping[BlockId, State]) -> Mapping[BlockId, State]:
|
|
249
|
+
block = self.blocks[block_id]
|
|
250
|
+
state = self.evaluate(block, incoming)
|
|
251
|
+
exits = {target: state for target in block.exception_targets}
|
|
252
|
+
terminator = block.terminator
|
|
253
|
+
if isinstance(terminator, Branch):
|
|
254
|
+
return {**exits, terminator.then_block: state, terminator.else_block: state}
|
|
255
|
+
if isinstance(terminator, Jump):
|
|
256
|
+
return {**exits, terminator.target: state}
|
|
257
|
+
if isinstance(terminator, ForNext):
|
|
258
|
+
return {**exits, terminator.body: state, terminator.exit: state}
|
|
259
|
+
return exits
|
|
260
|
+
|
|
261
|
+
# ------------------------------------------------------------------ transfer
|
|
262
|
+
|
|
263
|
+
def instruction(self, instruction: Instruction, state: Mapping[Key, Dep]) -> Dep:
|
|
264
|
+
if isinstance(instruction, Constant | Global | Symbol | Undefined):
|
|
265
|
+
return EMPTY
|
|
266
|
+
deps = EMPTY
|
|
267
|
+
for operand in instruction.operands():
|
|
268
|
+
deps |= state.get(operand, EMPTY)
|
|
269
|
+
if isinstance(instruction, BuildList | BuildTuple | BuildDict | BuildSet):
|
|
270
|
+
for unpacked in instruction.unpacked:
|
|
271
|
+
deps |= self.deep(unpacked, state)
|
|
272
|
+
return deps
|
|
273
|
+
|
|
274
|
+
def loaded(self, instruction: GetAttr | GetItem | GetIter, state: Mapping[Key, Dep]) -> Dep:
|
|
275
|
+
"""The contents a load reads from the objects it reads from."""
|
|
276
|
+
|
|
277
|
+
if isinstance(instruction, GetAttr):
|
|
278
|
+
receiver, field = instruction.object, ATTRIBUTES
|
|
279
|
+
elif isinstance(instruction, GetItem):
|
|
280
|
+
receiver, field = instruction.object, ELEMENTS
|
|
281
|
+
else:
|
|
282
|
+
receiver, field = instruction.iterable, ELEMENTS
|
|
283
|
+
deps = EMPTY
|
|
284
|
+
for location in self.heap.locations(receiver, field):
|
|
285
|
+
deps |= state.get(location, EMPTY)
|
|
286
|
+
return deps
|
|
287
|
+
|
|
288
|
+
def call(self, call: Call, state: dict[Key, Dep]) -> Dep:
|
|
289
|
+
arguments = tuple(self.deep(a, state) for a in call.arguments)
|
|
290
|
+
keywords = EMPTY
|
|
291
|
+
for value in (*call.starred, *(v for _, v in call.keywords)):
|
|
292
|
+
keywords |= self.deep(value, state)
|
|
293
|
+
everything = keywords
|
|
294
|
+
for deps in arguments:
|
|
295
|
+
everything |= deps
|
|
296
|
+
receiver = mutated_by(call, self.defs)
|
|
297
|
+
if receiver is not None:
|
|
298
|
+
self.store(state, receiver, ELEMENTS, everything)
|
|
299
|
+
target = self.graph.target_at(self.name, call.location)
|
|
300
|
+
|
|
301
|
+
if isinstance(target, ExternalSymbol):
|
|
302
|
+
project = self.project.get(target.symbol)
|
|
303
|
+
if project is None:
|
|
304
|
+
project = self.project.get(target.symbol.attribute("__init__"))
|
|
305
|
+
if project is not None:
|
|
306
|
+
bound = self.receiver(call, project.name)
|
|
307
|
+
arguments = (*(self.deep(r, state) for r in bound), *arguments)
|
|
308
|
+
return self.known(project, arguments, keywords, everything, call, state, bound)
|
|
309
|
+
self.record(
|
|
310
|
+
target.symbol,
|
|
311
|
+
tuple(a.parameters for a in arguments),
|
|
312
|
+
keywords.parameters,
|
|
313
|
+
call.location,
|
|
314
|
+
None,
|
|
315
|
+
)
|
|
316
|
+
return everything | Dep(externals=frozenset({target.symbol}))
|
|
317
|
+
if isinstance(target, KnownFunction):
|
|
318
|
+
made = self.defs.get(call.callee)
|
|
319
|
+
if isinstance(made, MakeFunction):
|
|
320
|
+
arguments = (*arguments, *(self.deep(v, state) for v in made.captured))
|
|
321
|
+
bound = self.receiver(call, target.name)
|
|
322
|
+
arguments = (*(self.deep(r, state) for r in bound), *arguments)
|
|
323
|
+
return self.known(self.table[target.name], arguments, keywords, everything, call, state, bound)
|
|
324
|
+
assert isinstance(target, UnknownTarget)
|
|
325
|
+
return everything | state.get(call.callee, EMPTY)
|
|
326
|
+
|
|
327
|
+
def receiver(self, call: Call, name: str) -> tuple[Value, ...]:
|
|
328
|
+
"""The object a method call runs on: the receiver of ``obj.method(...)`` or the
|
|
329
|
+
new object of ``Class(...)``, the method's implicit first parameter."""
|
|
330
|
+
|
|
331
|
+
if "." not in name:
|
|
332
|
+
return ()
|
|
333
|
+
callee = self.defs.get(call.callee)
|
|
334
|
+
if isinstance(callee, GetAttr):
|
|
335
|
+
return (callee.object,)
|
|
336
|
+
if name.endswith(".__init__") and isinstance(callee, Global | Symbol):
|
|
337
|
+
return (call.result,)
|
|
338
|
+
return ()
|
|
339
|
+
|
|
340
|
+
def known(
|
|
341
|
+
self,
|
|
342
|
+
callee: FunctionSummary,
|
|
343
|
+
arguments: tuple[Dep, ...],
|
|
344
|
+
keywords: Dep,
|
|
345
|
+
everything: Dep,
|
|
346
|
+
call: Call,
|
|
347
|
+
state: dict[Key, Dep],
|
|
348
|
+
receiver: tuple[Value, ...] = (),
|
|
349
|
+
) -> Dep:
|
|
350
|
+
"""Dependencies of a call to a function whose summary is known."""
|
|
351
|
+
|
|
352
|
+
if callee.unsupported:
|
|
353
|
+
return everything
|
|
354
|
+
|
|
355
|
+
def mapped(indices: Dependencies) -> Dep:
|
|
356
|
+
result = EMPTY
|
|
357
|
+
for index in indices:
|
|
358
|
+
result |= arguments[index] if index < len(arguments) else keywords
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
for reached in callee.external_calls:
|
|
362
|
+
self.record(
|
|
363
|
+
reached.symbol,
|
|
364
|
+
tuple(mapped(d).parameters for d in reached.argument_dependencies),
|
|
365
|
+
mapped(reached.keyword_dependencies).parameters,
|
|
366
|
+
reached.location,
|
|
367
|
+
call.location,
|
|
368
|
+
)
|
|
369
|
+
made = self.defs.get(call.callee)
|
|
370
|
+
values = (*receiver, *call.arguments, *(made.captured if isinstance(made, MakeFunction) else ()))
|
|
371
|
+
for mutation in callee.mutations:
|
|
372
|
+
if mutation.parameter < len(values):
|
|
373
|
+
deps = mapped(mutation.dependencies) | Dep(externals=mutation.externals)
|
|
374
|
+
self.store(state, values[mutation.parameter], mutation.field, deps)
|
|
375
|
+
return mapped(callee.return_dependencies) | Dep(externals=callee.return_externals)
|
|
376
|
+
|
|
377
|
+
def record(
|
|
378
|
+
self,
|
|
379
|
+
symbol: SymbolId,
|
|
380
|
+
arguments: tuple[Dependencies, ...],
|
|
381
|
+
keywords: Dependencies,
|
|
382
|
+
location: SourceSpan,
|
|
383
|
+
call_site: SourceSpan | None,
|
|
384
|
+
) -> None:
|
|
385
|
+
key = (symbol, location, call_site)
|
|
386
|
+
previous = self.external.get(key)
|
|
387
|
+
if previous is not None:
|
|
388
|
+
arguments = tuple(
|
|
389
|
+
a | b for a, b in zip(previous.argument_dependencies, arguments, strict=False)
|
|
390
|
+
)
|
|
391
|
+
keywords |= previous.keyword_dependencies
|
|
392
|
+
self.external[key] = ExternalCall(symbol, arguments, keywords, location, call_site)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def summarize(
|
|
396
|
+
name: str,
|
|
397
|
+
function: FunctionIR,
|
|
398
|
+
cfg: CFG,
|
|
399
|
+
graph: CallGraph,
|
|
400
|
+
table: Mapping[str, FunctionSummary],
|
|
401
|
+
project: Mapping[SymbolId, FunctionSummary] | None = None,
|
|
402
|
+
heap: HeapFacts | None = None,
|
|
403
|
+
) -> FunctionSummary:
|
|
404
|
+
problem = _DependenceProblem(name, function, graph, table, project, heap)
|
|
405
|
+
solution = solve(problem, cfg)
|
|
406
|
+
problem.external = {}
|
|
407
|
+
problem.returns = EMPTY
|
|
408
|
+
problem.stores = {}
|
|
409
|
+
problem.mutated_globals = set()
|
|
410
|
+
for block in function.blocks:
|
|
411
|
+
if solution.reached(block.id):
|
|
412
|
+
problem.evaluate(block, solution.incoming(block.id))
|
|
413
|
+
mutations: list[Mutation] = []
|
|
414
|
+
for index, parameter in enumerate(function.parameters):
|
|
415
|
+
for field in (ELEMENTS, ATTRIBUTES):
|
|
416
|
+
deps = EMPTY
|
|
417
|
+
for location in problem.heap.locations(parameter, field):
|
|
418
|
+
deps |= problem.stores.get(location, EMPTY)
|
|
419
|
+
if deps.parameters or deps.externals:
|
|
420
|
+
mutations.append(Mutation(index, field, deps.parameters, deps.externals))
|
|
421
|
+
return FunctionSummary(
|
|
422
|
+
name,
|
|
423
|
+
len(function.parameters),
|
|
424
|
+
problem.returns.parameters,
|
|
425
|
+
tuple(problem.external.values()),
|
|
426
|
+
return_externals=problem.returns.externals,
|
|
427
|
+
mutations=tuple(mutations),
|
|
428
|
+
side_effects=frozenset(problem.mutated_globals),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class SummaryAnalysis(Analysis[SummaryTable]):
|
|
433
|
+
name: ClassVar[str] = "interprocedural.summaries"
|
|
434
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
|
|
435
|
+
{CallGraphAnalysis, SSAAnalysis, CFGAnalysis, ProjectSummaries, HeapAnalysis}
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
@classmethod
|
|
439
|
+
def compute(cls, ctx: AnalysisContext) -> SummaryTable:
|
|
440
|
+
graph = ctx.get(CallGraphAnalysis)
|
|
441
|
+
project = ctx.get(ProjectSummaries).summaries
|
|
442
|
+
table: dict[str, FunctionSummary] = {}
|
|
443
|
+
supported: dict[str, tuple[FunctionIR, CFG, HeapFacts]] = {}
|
|
444
|
+
for name, function in graph.definitions.items():
|
|
445
|
+
if name in graph.unsupported:
|
|
446
|
+
count = len(function.parameters)
|
|
447
|
+
table[name] = FunctionSummary(name, count, frozenset(range(count)), (), True)
|
|
448
|
+
else:
|
|
449
|
+
supported[name] = (
|
|
450
|
+
ctx.get(SSAAnalysis, function),
|
|
451
|
+
ctx.get(CFGAnalysis, function),
|
|
452
|
+
ctx.get(HeapAnalysis, function),
|
|
453
|
+
)
|
|
454
|
+
table[name] = FunctionSummary(name, len(function.parameters), NONE, ())
|
|
455
|
+
changed = True
|
|
456
|
+
while changed:
|
|
457
|
+
changed = False
|
|
458
|
+
for name, (ssa, cfg, heap) in supported.items():
|
|
459
|
+
updated = summarize(name, ssa, cfg, graph, table, project, heap)
|
|
460
|
+
if updated != table[name]:
|
|
461
|
+
table[name] = updated
|
|
462
|
+
changed = True
|
|
463
|
+
return SummaryTable(table)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Def-use and use-def chains over SSA values (architecture §7)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from coretrace_python.analysis import AnalysisContext, AnyAnalysis, FunctionAnalysis
|
|
11
|
+
from coretrace_python.cfg import BlockId
|
|
12
|
+
from coretrace_python.hir import nodes
|
|
13
|
+
from coretrace_python.ir.model import ForNext, FunctionIR, Value
|
|
14
|
+
from coretrace_python.ir.ssa import SSAAnalysis
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Definition:
|
|
19
|
+
"""Where a value is defined: a parameter (no block), an instruction, or a terminator."""
|
|
20
|
+
|
|
21
|
+
block: BlockId | None
|
|
22
|
+
index: int | None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Use:
|
|
27
|
+
"""Where a value is used: an instruction index, or ``None`` for the terminator."""
|
|
28
|
+
|
|
29
|
+
block: BlockId
|
|
30
|
+
index: int | None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DefUse:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
definitions: Mapping[Value, Definition],
|
|
37
|
+
uses: Mapping[Value, tuple[Use, ...]],
|
|
38
|
+
) -> None:
|
|
39
|
+
self._definitions = MappingProxyType(dict(definitions))
|
|
40
|
+
self._uses = MappingProxyType(dict(uses))
|
|
41
|
+
|
|
42
|
+
def definition(self, value: Value) -> Definition:
|
|
43
|
+
return self._definitions[value]
|
|
44
|
+
|
|
45
|
+
def uses(self, value: Value) -> tuple[Use, ...]:
|
|
46
|
+
return self._uses.get(value, ())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def def_use(function: FunctionIR) -> DefUse:
|
|
50
|
+
definitions: dict[Value, Definition] = {}
|
|
51
|
+
uses: dict[Value, list[Use]] = {}
|
|
52
|
+
for parameter in function.parameters:
|
|
53
|
+
definitions[parameter] = Definition(None, None)
|
|
54
|
+
for block in function.blocks:
|
|
55
|
+
for index, instruction in enumerate(block.instructions):
|
|
56
|
+
if instruction.result is not None:
|
|
57
|
+
definitions[instruction.result] = Definition(block.id, index)
|
|
58
|
+
for operand in instruction.operands():
|
|
59
|
+
uses.setdefault(operand, []).append(Use(block.id, index))
|
|
60
|
+
terminator = block.terminator
|
|
61
|
+
if isinstance(terminator, ForNext) and terminator.result is not None:
|
|
62
|
+
definitions[terminator.result] = Definition(block.id, None)
|
|
63
|
+
for operand in terminator.operands():
|
|
64
|
+
uses.setdefault(operand, []).append(Use(block.id, None))
|
|
65
|
+
return DefUse(definitions, {value: tuple(found) for value, found in uses.items()})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DefUseAnalysis(FunctionAnalysis[DefUse]):
|
|
69
|
+
name: ClassVar[str] = "ir.defuse"
|
|
70
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis})
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def compute(cls, ctx: AnalysisContext, function: nodes.Function) -> DefUse:
|
|
74
|
+
return def_use(ctx.get(SSAAnalysis, function))
|