codeanalyzer-python 1.1.0__py3-none-any.whl → 1.2.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.
- codeanalyzer/__main__.py +95 -123
- codeanalyzer/core.py +21 -45
- codeanalyzer/dataflow/access_paths.py +26 -4
- codeanalyzer/dataflow/builder.py +65 -1
- codeanalyzer/dataflow/identity.py +1 -1
- codeanalyzer/dataflow/pdg.py +7 -2
- codeanalyzer/dataflow/scc.py +1 -1
- codeanalyzer/entrypoints/__init__.py +3 -0
- codeanalyzer/entrypoints/detect.py +124 -0
- codeanalyzer/entrypoints/matching.py +182 -0
- codeanalyzer/entrypoints/pipeline.py +131 -0
- codeanalyzer/entrypoints/rules.py +159 -0
- codeanalyzer/entrypoints/rules.yml +88 -0
- codeanalyzer/neo4j/bolt.py +1 -1
- codeanalyzer/neo4j/project.py +85 -60
- codeanalyzer/neo4j/schema.py +35 -34
- codeanalyzer/options/__init__.py +2 -2
- codeanalyzer/options/options.py +2 -26
- codeanalyzer/schema/__init__.py +48 -0
- codeanalyzer/schema/l1_body.py +11 -1
- codeanalyzer/schema/l2_callees.py +29 -13
- codeanalyzer/schema/py_schema.py +95 -103
- codeanalyzer/semantic_analysis/call_graph.py +20 -4
- codeanalyzer/semantic_analysis/defuse_linker.py +1499 -0
- codeanalyzer/syntactic_analysis/symbol_table_builder.py +88 -3
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/METADATA +36 -161
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/RECORD +31 -30
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/WHEEL +1 -1
- codeanalyzer/config/__init__.py +0 -3
- codeanalyzer/config/config.py +0 -8
- codeanalyzer/semantic_analysis/pycg/__init__.py +0 -20
- codeanalyzer/semantic_analysis/pycg/pycg_analysis.py +0 -1115
- codeanalyzer/semantic_analysis/pycg/pycg_exceptions.py +0 -23
- codeanalyzer/semantic_analysis/pycg/shard_planner.py +0 -401
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/entry_points.txt +0 -0
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -16,6 +16,7 @@ from codeanalyzer.schema.py_schema import (
|
|
|
16
16
|
PyCallableParameter,
|
|
17
17
|
PyCallArgument,
|
|
18
18
|
PyCallsite,
|
|
19
|
+
PyDecorator,
|
|
19
20
|
PyClass,
|
|
20
21
|
PyClassAttribute,
|
|
21
22
|
PyComment,
|
|
@@ -258,10 +259,26 @@ class SymbolTableBuilder:
|
|
|
258
259
|
|
|
259
260
|
return imports
|
|
260
261
|
|
|
262
|
+
@staticmethod
|
|
263
|
+
def _scope_definitions(node: AST):
|
|
264
|
+
"""Yield the def/class statements belonging to *node*'s own scope.
|
|
265
|
+
|
|
266
|
+
Sees through compound statements — a ``def`` under a module-level
|
|
267
|
+
``if sys.platform == ...:``, inside a ``try:`` import guard, or
|
|
268
|
+
conditionally defined within a method body is still a definition of
|
|
269
|
+
that scope (#148 reference comparison caught these missing). Never
|
|
270
|
+
descends into a yielded def/class: nested scopes recurse separately.
|
|
271
|
+
"""
|
|
272
|
+
for child in ast.iter_child_nodes(node):
|
|
273
|
+
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
274
|
+
yield child
|
|
275
|
+
else:
|
|
276
|
+
yield from SymbolTableBuilder._scope_definitions(child)
|
|
277
|
+
|
|
261
278
|
def _add_class(self, node: AST, script: Script, source: str, prefix: str = "") -> Dict[str, PyClass]:
|
|
262
279
|
classes: Dict[str, PyClass] = {}
|
|
263
280
|
|
|
264
|
-
for child in
|
|
281
|
+
for child in self._scope_definitions(node):
|
|
265
282
|
if not isinstance(child, ast.ClassDef):
|
|
266
283
|
continue
|
|
267
284
|
|
|
@@ -295,6 +312,7 @@ class SymbolTableBuilder:
|
|
|
295
312
|
.name(class_name)
|
|
296
313
|
.signature(signature)
|
|
297
314
|
.span(span)
|
|
315
|
+
.decorators(self._decorators(child, script, source))
|
|
298
316
|
.start_line(start_line)
|
|
299
317
|
.end_line(end_line)
|
|
300
318
|
.comments(self._pycomments(child, code))
|
|
@@ -317,7 +335,7 @@ class SymbolTableBuilder:
|
|
|
317
335
|
def _callables(self, node: AST, script: Script, source: str, prefix: str = "") -> Dict[str, PyCallable]:
|
|
318
336
|
callables: Dict[str, PyCallable] = {}
|
|
319
337
|
|
|
320
|
-
for child in
|
|
338
|
+
for child in self._scope_definitions(node):
|
|
321
339
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
322
340
|
method_name = child.name # Keep the actual method name unchanged
|
|
323
341
|
start_line = child.lineno
|
|
@@ -331,7 +349,11 @@ class SymbolTableBuilder:
|
|
|
331
349
|
getattr(child, "end_lineno", child.lineno),
|
|
332
350
|
getattr(child, "end_col_offset", child.col_offset)),
|
|
333
351
|
)
|
|
334
|
-
decorators =
|
|
352
|
+
decorators = self._decorators(child, script, source)
|
|
353
|
+
# `async def` is a declaration modifier, not a distinct kind (#130).
|
|
354
|
+
modifiers = (
|
|
355
|
+
["async"] if isinstance(child, ast.AsyncFunctionDef) else []
|
|
356
|
+
)
|
|
335
357
|
|
|
336
358
|
if prefix:
|
|
337
359
|
# We're in a nested context - build signature with prefix
|
|
@@ -357,6 +379,7 @@ class SymbolTableBuilder:
|
|
|
357
379
|
.signature(signature) # Use the full signature here
|
|
358
380
|
.span(span)
|
|
359
381
|
.decorators(decorators)
|
|
382
|
+
.modifiers(modifiers)
|
|
360
383
|
.start_line(start_line)
|
|
361
384
|
.end_line(end_line)
|
|
362
385
|
.code_start_line(child.body[0].lineno if child.body else start_line)
|
|
@@ -590,6 +613,68 @@ class SymbolTableBuilder:
|
|
|
590
613
|
|
|
591
614
|
return params
|
|
592
615
|
|
|
616
|
+
def _decorators(
|
|
617
|
+
self, node: ast.AST, script: Optional[Script], source: str = ""
|
|
618
|
+
) -> List[PyDecorator]:
|
|
619
|
+
"""Structure each entry of ``node.decorator_list`` (#128).
|
|
620
|
+
|
|
621
|
+
``name`` is the spelling as written and ``qualified_name`` is Jedi's
|
|
622
|
+
resolution of it, inferred at the last identifier of the callee so that
|
|
623
|
+
``@a.b.c`` resolves ``c`` rather than ``a``. Resolution is best-effort:
|
|
624
|
+
dynamic, conditional and re-exported decorators stay unresolved, and a
|
|
625
|
+
failure here must never abort the symbol table.
|
|
626
|
+
"""
|
|
627
|
+
out: List[PyDecorator] = []
|
|
628
|
+
for dec in getattr(node, "decorator_list", []) or []:
|
|
629
|
+
callee = dec.func if isinstance(dec, ast.Call) else dec
|
|
630
|
+
positional: List[str] = []
|
|
631
|
+
keyword: Dict[str, str] = {}
|
|
632
|
+
if isinstance(dec, ast.Call):
|
|
633
|
+
positional = [ast.unparse(a) for a in dec.args]
|
|
634
|
+
for kw in dec.keywords:
|
|
635
|
+
# ``**kwargs`` has no arg name; keep it addressable rather
|
|
636
|
+
# than dropping it.
|
|
637
|
+
key = kw.arg if kw.arg is not None else f"**{ast.unparse(kw.value)}"
|
|
638
|
+
keyword[key] = ast.unparse(kw.value)
|
|
639
|
+
span = Span(
|
|
640
|
+
start=(dec.lineno, dec.col_offset),
|
|
641
|
+
end=(getattr(dec, "end_lineno", dec.lineno),
|
|
642
|
+
getattr(dec, "end_col_offset", dec.col_offset)),
|
|
643
|
+
bytes=byte_offsets(source, dec.lineno, dec.col_offset,
|
|
644
|
+
getattr(dec, "end_lineno", dec.lineno),
|
|
645
|
+
getattr(dec, "end_col_offset", dec.col_offset)),
|
|
646
|
+
) if source else None
|
|
647
|
+
out.append(
|
|
648
|
+
PyDecorator.builder()
|
|
649
|
+
.name(ast.unparse(callee))
|
|
650
|
+
.qualified_name(self._decorator_qualified_name(callee, script))
|
|
651
|
+
.positional_arguments(positional)
|
|
652
|
+
.keyword_arguments(keyword)
|
|
653
|
+
.expression(ast.unparse(dec))
|
|
654
|
+
.span(span)
|
|
655
|
+
.build()
|
|
656
|
+
)
|
|
657
|
+
return out
|
|
658
|
+
|
|
659
|
+
@staticmethod
|
|
660
|
+
def _decorator_qualified_name(
|
|
661
|
+
callee: ast.AST, script: Optional[Script]
|
|
662
|
+
) -> Optional[str]:
|
|
663
|
+
"""Jedi's full name for a decorator's callee, or ``None``."""
|
|
664
|
+
if script is None:
|
|
665
|
+
return None
|
|
666
|
+
line = getattr(callee, "end_lineno", getattr(callee, "lineno", None))
|
|
667
|
+
col = getattr(callee, "end_col_offset", None)
|
|
668
|
+
if line is None or col is None:
|
|
669
|
+
return None
|
|
670
|
+
try:
|
|
671
|
+
d = SymbolTableBuilder._first_definition(
|
|
672
|
+
script.infer(line=line, column=max(col - 1, 0))
|
|
673
|
+
)
|
|
674
|
+
except Exception:
|
|
675
|
+
return None
|
|
676
|
+
return getattr(d, "full_name", None) if d is not None else None
|
|
677
|
+
|
|
593
678
|
def _accessed_symbols(
|
|
594
679
|
self, fn_node: ast.FunctionDef, script: Script
|
|
595
680
|
) -> List[PySymbol]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: codeanalyzer-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Static analysis for Python — canonical schema v2 (symbol table, call graph, and native CFG/PDG/SDG dataflow) as analysis.json or a Neo4j property graph.
|
|
5
5
|
Author-email: Rahul Krishna <i.m.ralk@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -9,20 +9,13 @@ Requires-Python: >=3.9
|
|
|
9
9
|
Requires-Dist: astor<0.9.0,>=0.8.1
|
|
10
10
|
Requires-Dist: jedi<0.20.0,>=0.18.0; python_version < '3.11'
|
|
11
11
|
Requires-Dist: jedi<=0.19.2; python_version >= '3.11'
|
|
12
|
-
Requires-Dist: msgpack<1.0.7,>=1.0.0; python_version < '3.11'
|
|
13
|
-
Requires-Dist: msgpack<2.0.0,>=1.0.7; python_version >= '3.11'
|
|
14
12
|
Requires-Dist: networkx<3.2.0,>=2.6.0; python_version < '3.11'
|
|
15
13
|
Requires-Dist: networkx<4.0.0,>=3.0.0; python_version >= '3.11'
|
|
16
|
-
Requires-Dist: numpy<1.24.0,>=1.21.0; python_version < '3.11'
|
|
17
|
-
Requires-Dist: numpy<2.0.0,>=1.24.0; python_version >= '3.11' and python_version < '3.12'
|
|
18
|
-
Requires-Dist: numpy<2.0.0,>=1.26.0; python_version >= '3.12'
|
|
19
14
|
Requires-Dist: packaging>=25.0
|
|
20
|
-
Requires-Dist: pandas<2.0.0,>=1.3.0; python_version < '3.11'
|
|
21
|
-
Requires-Dist: pandas<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
22
15
|
Requires-Dist: parso>=0.8.5
|
|
23
|
-
Requires-Dist: pycg>=0.0.6
|
|
24
16
|
Requires-Dist: pydantic<2.0.0,>=1.8.0; python_version < '3.11'
|
|
25
17
|
Requires-Dist: pydantic<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
18
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
26
19
|
Requires-Dist: ray<3.0.0,>=2.10.0; python_version >= '3.11'
|
|
27
20
|
Requires-Dist: ray==2.0.0; python_version < '3.11'
|
|
28
21
|
Requires-Dist: requests<3.0.0,>=2.20.0; python_version >= '3.11'
|
|
@@ -55,7 +48,7 @@ Description-Content-Type: text/markdown
|
|
|
55
48
|
---
|
|
56
49
|
|
|
57
50
|
`canpy` is a static analyzer for Python built on [Jedi](https://jedi.readthedocs.io/),
|
|
58
|
-
|
|
51
|
+
and [Tree-sitter](https://tree-sitter.github.io/). It
|
|
59
52
|
emits the **canonical CodeLLM-DevKit (CLDK) schema v2** — a single, additive Code Property Graph
|
|
60
53
|
tree — either as `analysis.json` or projected into a **Neo4j property graph**. It is the Python
|
|
61
54
|
backend behind [CLDK](https://github.com/codellm-devkit/python-sdk), mirroring its
|
|
@@ -97,8 +90,9 @@ needs.
|
|
|
97
90
|
at a single `application` node with durable `can://` ids on every callable and above.
|
|
98
91
|
- **Symbol table** — modules, classes, functions, methods, variables, decorators, imports, and
|
|
99
92
|
docstrings, with precise byte-offset source spans; each module carries its `source` once.
|
|
100
|
-
- **Call graph** — Jedi's lexical resolver at level 1, enriched
|
|
101
|
-
|
|
93
|
+
- **Call graph** — Jedi's lexical resolver at level 1, enriched at level 2 by a per-callable
|
|
94
|
+
**defuse linker** that resolves call sites through local def-use chains and module-scope
|
|
95
|
+
bindings (provenance-tagged, deterministic, no global fixpoint).
|
|
102
96
|
- **Dataflow graphs** — native, per-callable exceptional **CFG** plus **control-** and
|
|
103
97
|
**data-dependence** edges (`cfg`/`cdg`/`ddg`) at level 3, stitched into a whole-program
|
|
104
98
|
**interprocedural SDG** (synthetic parameter vertices, `param_in`/`param_out`/`summary`,
|
|
@@ -109,7 +103,7 @@ needs.
|
|
|
109
103
|
checked in as `schema.neo4j.json` (`2.0.0`) and shipped with every release.
|
|
110
104
|
- **Incremental cache** — per-file results are cached under `.codeanalyzer`; `--lazy` (default)
|
|
111
105
|
reuses them, `--eager` forces a clean rebuild. `--ray` distributes the work across cores.
|
|
112
|
-
- **Compact output** — canonical `analysis.json
|
|
106
|
+
- **Compact output** — one canonical `analysis.json` per run.
|
|
113
107
|
|
|
114
108
|
## Installation
|
|
115
109
|
|
|
@@ -182,8 +176,7 @@ canpy --input /path/to/python/project
|
|
|
182
176
|
```
|
|
183
177
|
|
|
184
178
|
With no `--output`, the analysis is printed to stdout as compact JSON; with `--output <dir>` it is
|
|
185
|
-
written to `analysis.json` (or `graph.cypher` for `--emit neo4j
|
|
186
|
-
`--format msgpack`) in that directory.
|
|
179
|
+
written to `analysis.json` (or `graph.cypher` for `--emit neo4j`) in that directory.
|
|
187
180
|
|
|
188
181
|
### Options
|
|
189
182
|
|
|
@@ -194,7 +187,7 @@ $ canpy --help
|
|
|
194
187
|
|
|
195
188
|
Usage: canpy [OPTIONS] COMMAND [ARGS]...
|
|
196
189
|
|
|
197
|
-
Static Analysis on Python source code using Jedi
|
|
190
|
+
Static Analysis on Python source code using Jedi and Tree sitter.
|
|
198
191
|
|
|
199
192
|
╭─ Options ────────────────────────────────────────────────────────────────────╮
|
|
200
193
|
│ --version Show the canpy │
|
|
@@ -207,10 +200,6 @@ $ canpy --help
|
|
|
207
200
|
│ --emit schema). │
|
|
208
201
|
│ --output -o <path> Output directory │
|
|
209
202
|
│ for artifacts. │
|
|
210
|
-
│ --format -f <json|msgpack> Output format │
|
|
211
|
-
│ for --emit json: │
|
|
212
|
-
│ json or msgpack. │
|
|
213
|
-
│ [default: json] │
|
|
214
203
|
│ --emit <json|neo4j|sche Output target: │
|
|
215
204
|
│ ma> json │
|
|
216
205
|
│ (analysis.json, │
|
|
@@ -258,7 +247,8 @@ $ canpy --help
|
|
|
258
247
|
│ --analysis-level -a <int range> Analysis depth: │
|
|
259
248
|
│ [1<=x<=4] 1=symbol │
|
|
260
249
|
│ table+Jedi call │
|
|
261
|
-
│ graph,
|
|
250
|
+
│ graph, │
|
|
251
|
+
│ 2=+defuse-linker │
|
|
262
252
|
│ call graph, │
|
|
263
253
|
│ 3=+native │
|
|
264
254
|
│ intraprocedural │
|
|
@@ -270,7 +260,7 @@ $ canpy --help
|
|
|
270
260
|
│ edges, │
|
|
271
261
|
│ alias-aware │
|
|
272
262
|
│ DDG). │
|
|
273
|
-
│ [default: 1]
|
|
263
|
+
│ [default: (1)] │
|
|
274
264
|
│ --graphs <str> Level 3+ only: │
|
|
275
265
|
│ comma-separated │
|
|
276
266
|
│ program-graph │
|
|
@@ -282,8 +272,12 @@ $ canpy --help
|
|
|
282
272
|
│ PDG's data edges │
|
|
283
273
|
│ only; `sdg` │
|
|
284
274
|
│ requires -a 4. │
|
|
275
|
+
│ Incompatible │
|
|
276
|
+
│ with --emit │
|
|
277
|
+
│ neo4j (always │
|
|
278
|
+
│ full-depth). │
|
|
285
279
|
│ [default: │
|
|
286
|
-
│ cfg,dfg,pdg]
|
|
280
|
+
│ (cfg,dfg,pdg)] │
|
|
287
281
|
│ --graph-field-de… <int range> Level 3 only: │
|
|
288
282
|
│ [x>=1] k-limit on │
|
|
289
283
|
│ access-path │
|
|
@@ -344,134 +338,14 @@ $ canpy --help
|
|
|
344
338
|
│ verbosity: -v, │
|
|
345
339
|
│ -vv, -vvv │
|
|
346
340
|
│ [default: 0] │
|
|
347
|
-
│ --
|
|
348
|
-
│
|
|
349
|
-
│
|
|
350
|
-
│
|
|
351
|
-
│
|
|
352
|
-
│
|
|
353
|
-
│
|
|
354
|
-
│
|
|
355
|
-
│ ceiling, PyCG is │
|
|
356
|
-
│ run │
|
|
357
|
-
│ independently │
|
|
358
|
-
│ per top-level │
|
|
359
|
-
│ package with │
|
|
360
|
-
│ cross-package │
|
|
361
|
-
│ imports treated │
|
|
362
|
-
│ as ghost nodes. │
|
|
363
|
-
│ Without this │
|
|
364
|
-
│ flag, projects │
|
|
365
|
-
│ over the ceiling │
|
|
366
|
-
│ fall back to │
|
|
367
|
-
│ Jedi-only edges. │
|
|
368
|
-
│ [default: │
|
|
369
|
-
│ no-pycg-shard] │
|
|
370
|
-
│ --pycg-shard-cei… <int range> Maximum files │
|
|
371
|
-
│ [x>=1] per shard when │
|
|
372
|
-
│ --pycg-shard is │
|
|
373
|
-
│ active (default │
|
|
374
|
-
│ 100). Shards │
|
|
375
|
-
│ exceeding this │
|
|
376
|
-
│ limit are │
|
|
377
|
-
│ skipped; their │
|
|
378
|
-
│ call edges are │
|
|
379
|
-
│ omitted from the │
|
|
380
|
-
│ call graph (Jedi │
|
|
381
|
-
│ edges for those │
|
|
382
|
-
│ packages are │
|
|
383
|
-
│ still included). │
|
|
384
|
-
│ Lower values are │
|
|
385
|
-
│ safer for │
|
|
386
|
-
│ packages with │
|
|
387
|
-
│ deep class │
|
|
388
|
-
│ hierarchies or │
|
|
389
|
-
│ heavy import │
|
|
390
|
-
│ graphs. │
|
|
391
|
-
│ [default: 100] │
|
|
392
|
-
│ --pycg-shard-tim… <int range> Per-shard │
|
|
393
|
-
│ [x>=0] wall-clock │
|
|
394
|
-
│ timeout in │
|
|
395
|
-
│ seconds when │
|
|
396
|
-
│ --pycg-shard is │
|
|
397
|
-
│ active (default │
|
|
398
|
-
│ 120). A shard │
|
|
399
|
-
│ that exceeds │
|
|
400
|
-
│ this limit is │
|
|
401
|
-
│ skipped │
|
|
402
|
-
│ gracefully. │
|
|
403
|
-
│ PyCG's fixpoint │
|
|
404
|
-
│ is bimodal: it │
|
|
405
|
-
│ either converges │
|
|
406
|
-
│ quickly or │
|
|
407
|
-
│ diverges │
|
|
408
|
-
│ indefinitely, so │
|
|
409
|
-
│ the timeout acts │
|
|
410
|
-
│ as a final │
|
|
411
|
-
│ safety net after │
|
|
412
|
-
│ the file-count │
|
|
413
|
-
│ ceiling. Set to │
|
|
414
|
-
│ 0 to disable. │
|
|
415
|
-
│ POSIX only │
|
|
416
|
-
│ (macOS / Linux); │
|
|
417
|
-
│ ignored on │
|
|
418
|
-
│ Windows. │
|
|
419
|
-
│ [default: 120] │
|
|
420
|
-
│ --pycg-shard-str… <jedi|package> How --pycg-shard │
|
|
421
|
-
│ groups files │
|
|
422
|
-
│ (level 2 only). │
|
|
423
|
-
│ 'jedi' (default) │
|
|
424
|
-
│ partitions the │
|
|
425
|
-
│ Jedi │
|
|
426
|
-
│ module-dependen… │
|
|
427
|
-
│ graph (SCC + │
|
|
428
|
-
│ Louvain) so │
|
|
429
|
-
│ tightly-coupled │
|
|
430
|
-
│ modules │
|
|
431
|
-
│ co-compute and │
|
|
432
|
-
│ few call edges │
|
|
433
|
-
│ are severed │
|
|
434
|
-
│ between shards; │
|
|
435
|
-
│ import cycles │
|
|
436
|
-
│ are never split. │
|
|
437
|
-
│ 'package' uses │
|
|
438
|
-
│ the legacy │
|
|
439
|
-
│ one-shard-per-p… │
|
|
440
|
-
│ grouping. │
|
|
441
|
-
│ [default: jedi] │
|
|
442
|
-
│ --pycg-max-iter <int range> Cap on PyCG's │
|
|
443
|
-
│ [x>=-1] fixpoint passes │
|
|
444
|
-
│ per │
|
|
445
|
-
│ shard/project │
|
|
446
|
-
│ (level 2; │
|
|
447
|
-
│ default 50). │
|
|
448
|
-
│ PyCG iterates │
|
|
449
|
-
│ until its │
|
|
450
|
-
│ points-to state │
|
|
451
|
-
│ stops changing, │
|
|
452
|
-
│ but its │
|
|
453
|
-
│ access-path │
|
|
454
|
-
│ domain has no │
|
|
455
|
-
│ convergence │
|
|
456
|
-
│ bound, so heavy │
|
|
457
|
-
│ metaclass/mixin │
|
|
458
|
-
│ code (e.g. an │
|
|
459
|
-
│ ORM) can loop │
|
|
460
|
-
│ with each pass │
|
|
461
|
-
│ costing seconds. │
|
|
462
|
-
│ The cap returns │
|
|
463
|
-
│ a │
|
|
464
|
-
│ sound-but-incom… │
|
|
465
|
-
│ call graph │
|
|
466
|
-
│ instead of │
|
|
467
|
-
│ looping until │
|
|
468
|
-
│ the timeout │
|
|
469
|
-
│ kills it. Set to │
|
|
470
|
-
│ -1 for PyCG's │
|
|
471
|
-
│ unbounded │
|
|
472
|
-
│ run-to-converge… │
|
|
473
|
-
│ behaviour. │
|
|
474
|
-
│ [default: 50] │
|
|
341
|
+
│ --entrypoint-rul… <path> Extra entrypoint │
|
|
342
|
+
│ rules file │
|
|
343
|
+
│ (YAML). │
|
|
344
|
+
│ Repeatable; │
|
|
345
|
+
│ merges with the │
|
|
346
|
+
│ shipped rules. A │
|
|
347
|
+
│ malformed file │
|
|
348
|
+
│ is an error. │
|
|
475
349
|
│ --help Show this │
|
|
476
350
|
│ message and │
|
|
477
351
|
│ exit. │
|
|
@@ -493,13 +367,14 @@ $ canpy --help
|
|
|
493
367
|
canpy --input ./my-python-project --output ./out --format msgpack # → ./out/analysis.msgpack
|
|
494
368
|
```
|
|
495
369
|
|
|
496
|
-
3. **Enrich the call graph with
|
|
370
|
+
3. **Enrich the call graph with the defuse linker (level 2):**
|
|
497
371
|
```sh
|
|
498
372
|
canpy --input ./my-python-project -a 2
|
|
499
373
|
```
|
|
500
|
-
Level 1 edges come from Jedi's lexical resolution. `-a 2` runs **
|
|
501
|
-
|
|
502
|
-
|
|
374
|
+
Level 1 edges come from Jedi's lexical resolution. `-a 2` runs the **defuse linker** —
|
|
375
|
+
per-callable resolution over lexical scopes, import bindings, class hierarchies, and a
|
|
376
|
+
bounded type-propagation round — and merges its edges with Jedi's, backfilling the
|
|
377
|
+
callees Jedi could not resolve. Every edge is provenance-tagged (`jedi`, `defuse`).
|
|
503
378
|
|
|
504
379
|
4. **Emit a Neo4j snapshot, or push to a live database:**
|
|
505
380
|
```sh
|
|
@@ -528,7 +403,7 @@ $ canpy --help
|
|
|
528
403
|
canpy --input ./my-python-project -a 3 --graph-field-depth 2 # tighter access-path k-limit
|
|
529
404
|
```
|
|
530
405
|
Levels 3 and 4 also enrich the Neo4j projection (`--emit neo4j`) with the CPG overlay
|
|
531
|
-
(`:
|
|
406
|
+
(`:PyBodyNode` nodes wired by `PY_CFG_NEXT`/`PY_CDG`/`PY_DDG`, plus the level-4
|
|
532
407
|
`PY_PARAM_IN`/`PY_PARAM_OUT`/`PY_SUMMARY` edges — the cross-language dataflow vocabulary,
|
|
533
408
|
PY_-namespaced like every other row family so multi-language databases never mingle
|
|
534
409
|
analyzers' edges).
|
|
@@ -541,7 +416,7 @@ levels are cumulative and additive — `analysis.json(-a 1) ⊆ … ⊆ analysis
|
|
|
541
416
|
| Level | Flag | What it adds | Where it lands |
|
|
542
417
|
| --- | --- | --- | --- |
|
|
543
418
|
| **1** | `-a 1` (default) | Symbol table, Jedi call graph, and `call` nodes in each callable's `body` | `body` calls (`callee: null`) |
|
|
544
|
-
| **2** | `-a 2` |
|
|
419
|
+
| **2** | `-a 2` | Defuse-linker call-graph enrichment; each call's `callee` backfilled to a `can://` id | `call_graph`, `body` callees |
|
|
545
420
|
| **3** | `-a 3` | Native **intraprocedural** CFG/CDG/DDG (syntactic, name-equality, `prov: ["ssa"]`) | `cfg`, `cdg`, `ddg`, `@entry`/`@exit` on each callable |
|
|
546
421
|
| **4** | `-a 4` | **Interprocedural** SDG: synthetic param vertices, alias-aware DDG (`prov: ["points-to"]`) | `param_in`, `param_out`, `summary`, semantic `ddg` |
|
|
547
422
|
|
|
@@ -570,7 +445,7 @@ symbol-table signature by construction
|
|
|
570
445
|
external dependency to install; the analyzer falls back to the built-in `TypeBasedAliasOracle`
|
|
571
446
|
(Jedi-inferred types; unknown types conservatively alias) only when Scalpel can't resolve a
|
|
572
447
|
construct or a per-callable build fails, keeping the `may_alias` interface total. Call dispatch
|
|
573
|
-
comes from the merged Jedi
|
|
448
|
+
comes from the merged Jedi + defuse-linker call graph, treated as a frozen oracle.
|
|
574
449
|
- **Summaries:** relational formal-in → formal-out flows composed bottom-up over the Tarjan SCC
|
|
575
450
|
condensation of the call graph, a monotone fixpoint within SCCs; globals ride as extra formals,
|
|
576
451
|
closure captures bind at definition sites.
|
|
@@ -609,7 +484,7 @@ just populate more of the same tree:
|
|
|
609
484
|
}
|
|
610
485
|
},
|
|
611
486
|
"call_graph": [ { "src": "can://…/main(a)", "dst": "can://…/helper(x)",
|
|
612
|
-
"weight": 1, "prov": ["
|
|
487
|
+
"weight": 1, "prov": ["defuse", "jedi"] } ],
|
|
613
488
|
"external_symbols": { // imported/builtin call targets, keyed by id
|
|
614
489
|
"can://python/<app>/@external/os/getcwd":
|
|
615
490
|
{ "id": "can://python/<app>/@external/os/getcwd", "kind": "external",
|
|
@@ -675,7 +550,7 @@ label is `Py`-prefixed and every relationship type is `PY_`-prefixed (e.g. `:PyC
|
|
|
675
550
|
so multiple language analyzers can share one database without label or relationship-type collisions.
|
|
676
551
|
Declarations are keyed by their **`can://` id** under a shared `:PySymbol` label; calls, imports,
|
|
677
552
|
inheritance, decorators, and call sites are relationships. At `-a 3`/`-a 4` the projection gains the
|
|
678
|
-
**CPG overlay** — `:
|
|
553
|
+
**CPG overlay** — `:PyBodyNode` nodes (statements, and at level 4 the parameter vertices) wired by
|
|
679
554
|
`PY_CFG_NEXT`/`PY_CDG`/`PY_DDG`, plus the level-4 `PY_PARAM_IN`/`PY_PARAM_OUT`/`PY_SUMMARY` edges:
|
|
680
555
|
|
|
681
556
|
- **Without `--neo4j-uri`** — writes a self-contained `graph.cypher` (constraints + indexes, a scoped
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
codeanalyzer/__init__.py,sha256=BZ3Kuwl-F_F-8H8cepLnVJ4Ku4NNUjjqg0Y6ujPQSsI,108
|
|
2
|
-
codeanalyzer/__main__.py,sha256=
|
|
3
|
-
codeanalyzer/core.py,sha256=
|
|
2
|
+
codeanalyzer/__main__.py,sha256=hmuIKxpW7TFfGaBGb1HiHCFS02dCQxVhG7FkruXECWQ,14954
|
|
3
|
+
codeanalyzer/core.py,sha256=vkU8djqKmyX4mXmb5rOz3oRjlMWYjMB7dWB_toIHnu4,42140
|
|
4
4
|
codeanalyzer/provenance.py,sha256=DT-DqwdVwO7g-GyK3sC0_4vDNCUjZYEY1fyYCt-7VRM,2306
|
|
5
5
|
codeanalyzer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
codeanalyzer/config/__init__.py,sha256=9XBxAn1oWGRuhg3bEBUuVGs3hFNXEAKrr-Ce7tq9a2k,61
|
|
7
|
-
codeanalyzer/config/config.py,sha256=ZiKzc5uEUCIvih58-6BDtLLI1hPij41wGQjBcj9KNQM,188
|
|
8
6
|
codeanalyzer/dataflow/__init__.py,sha256=HCNOH24rAGLUMG94N9aC-2SsAWSntk3uys7e39ipU5o,1725
|
|
9
|
-
codeanalyzer/dataflow/access_paths.py,sha256=
|
|
7
|
+
codeanalyzer/dataflow/access_paths.py,sha256=wC8Q9qD-RZzkoFWMVvu_6uNNmYP8z48OGp9h9v3F1d4,23623
|
|
10
8
|
codeanalyzer/dataflow/alias.py,sha256=ZYKY0DiR3GHv6YJr7C001Lode9rZsk6-gxdomUyIvwg,3928
|
|
11
|
-
codeanalyzer/dataflow/builder.py,sha256=
|
|
9
|
+
codeanalyzer/dataflow/builder.py,sha256=sCngN3MWfCbHteSHiue8l7AUVQNttETJInsgVMy_xkY,31317
|
|
12
10
|
codeanalyzer/dataflow/cfg.py,sha256=u5YXJjAaDshdgv-9kWWITbBJFP-MwXKq6hbNh0fhDm8,25307
|
|
13
11
|
codeanalyzer/dataflow/defuse.py,sha256=LrX1ToOZzR79NlAGg5T647UAFkpiEqEnT7t0K5RXOiI,4377
|
|
14
12
|
codeanalyzer/dataflow/dominance.py,sha256=X7Ki1QRdVqaseYsJtiUL7m9MbcC2WHEv9b8bczSLFUA,5086
|
|
15
|
-
codeanalyzer/dataflow/identity.py,sha256=
|
|
16
|
-
codeanalyzer/dataflow/pdg.py,sha256=
|
|
13
|
+
codeanalyzer/dataflow/identity.py,sha256=WAIal6XchmQqdnXbvbEgu8J6vJdXNRdie1KHz1vJBl8,3906
|
|
14
|
+
codeanalyzer/dataflow/pdg.py,sha256=1Bm7AnoRqXBryZulII2idPw0feV8eZQ1VqqprQ8PW-g,3731
|
|
17
15
|
codeanalyzer/dataflow/scalpel_oracle.py,sha256=FxRadKrTWar0VKHyQJM40n3cq25sld3Sj4lMdOm5NFk,11494
|
|
18
|
-
codeanalyzer/dataflow/scc.py,sha256=
|
|
16
|
+
codeanalyzer/dataflow/scc.py,sha256=Doa_0-5f3agCu_5UmeEDlCUErg34TtniKIv8Uqd7Jiw,3493
|
|
19
17
|
codeanalyzer/dataflow/sdg.py,sha256=sTUUlYMB9uTKg9Yxiw-ScTBXogaQKcrSP6vksKXG14M,17841
|
|
20
18
|
codeanalyzer/dataflow/slicing.py,sha256=lWZ7jHhlR8m7rECWJQXvwfs5GNZeJY55Cud3Ji5UbNU,3654
|
|
21
19
|
codeanalyzer/dataflow/summaries.py,sha256=DOgesiymL6WgePrtkQBiS_Rd7SuCr3McD4Eq05Msb50,8023
|
|
@@ -31,40 +29,43 @@ codeanalyzer/dataflow/scalpel/cfg/model.py,sha256=5t_3jQgYwbkSvUUCKkOjX0pBzVUyDC
|
|
|
31
29
|
codeanalyzer/dataflow/scalpel/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
30
|
codeanalyzer/dataflow/scalpel/core/func_call_visitor.py,sha256=ps0snjTchBXilhor3ijrx3ZqUuhCZYT94b5s5lFC5Z8,7313
|
|
33
31
|
codeanalyzer/dataflow/scalpel/core/vars_visitor.py,sha256=gE5fNyJS6jslD6vsMRbFLoy3n1xTwH-b54mBcNYO72M,5660
|
|
32
|
+
codeanalyzer/entrypoints/__init__.py,sha256=VaMd4mSEPLuPlRxxAve_nTJ9ZH62stXZGTPNwU_BQ50,99
|
|
33
|
+
codeanalyzer/entrypoints/detect.py,sha256=fsWRQz1njvB9d3LIJEHxsNFE1GrB7q52KCvozw_UIUQ,4594
|
|
34
|
+
codeanalyzer/entrypoints/matching.py,sha256=vXrhCsPOeaYHp8tYyILk5kNh9_3rwV_1wi6Z47PqG80,6427
|
|
35
|
+
codeanalyzer/entrypoints/pipeline.py,sha256=PDptL_o105BpofpnQHWZYOtFYVelqTB4zktp0PVPbEc,5540
|
|
36
|
+
codeanalyzer/entrypoints/rules.py,sha256=aYwCiX-J3tvj8qJrrb-tph5zovin5wIdmzc49iR3h2U,5318
|
|
37
|
+
codeanalyzer/entrypoints/rules.yml,sha256=rgDglVOcUNXnQ5FXLMfnJbfI7xHRiRmegTP5bmiemSQ,3112
|
|
34
38
|
codeanalyzer/jedi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
39
|
codeanalyzer/jedi/jedi.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
40
|
codeanalyzer/neo4j/__init__.py,sha256=8Ei6uThTOqmul6GhnKoGOjLXJZbyDwF5Jo-8I8NxnAk,1574
|
|
37
|
-
codeanalyzer/neo4j/bolt.py,sha256=
|
|
41
|
+
codeanalyzer/neo4j/bolt.py,sha256=qEBtQlBjaMPpLQPUCOAR9jEqLxpXYJG5mtxdYAhjtMI,10134
|
|
38
42
|
codeanalyzer/neo4j/cypher.py,sha256=a7YSmxxDbPcYc5gI4TW9sM6_WOE3RgLXTGBR9K8W8kw,5321
|
|
39
43
|
codeanalyzer/neo4j/emit.py,sha256=QdrZWG3_IQHMcKCX4bFINpXvqZU2Qfsi9beIJovC2p4,3493
|
|
40
|
-
codeanalyzer/neo4j/project.py,sha256=
|
|
44
|
+
codeanalyzer/neo4j/project.py,sha256=LFj8O_2DYA_dvMTbSYK_PYxmnP2UJ4h_3J8rCIoK1w8,25927
|
|
41
45
|
codeanalyzer/neo4j/rows.py,sha256=med0XFa63PPUVUhJMq1TtVcJxgaVW39sgxBRZJDIJ_w,7811
|
|
42
|
-
codeanalyzer/neo4j/schema.py,sha256=
|
|
43
|
-
codeanalyzer/options/__init__.py,sha256=
|
|
44
|
-
codeanalyzer/options/options.py,sha256=
|
|
45
|
-
codeanalyzer/schema/__init__.py,sha256=
|
|
46
|
+
codeanalyzer/neo4j/schema.py,sha256=kx134hxG_Uv6b5PkHFfkX7L1STpxSaW4pGTDe_Wruf0,10768
|
|
47
|
+
codeanalyzer/options/__init__.py,sha256=6NewN0a-1HAEgKcxQjYyVn2WEDLrhax8h3WLgZddeqI,94
|
|
48
|
+
codeanalyzer/options/options.py,sha256=5gmKNxb51vWmIknE4e8kyT9nJRchL0VYspZxe_PAsws,1389
|
|
49
|
+
codeanalyzer/schema/__init__.py,sha256=hIyz02abUJNPW8yUgguaeKflZjTmOZWbDiKICNBD7yk,4141
|
|
46
50
|
codeanalyzer/schema/assign_ids.py,sha256=pQHluLaO5a6hFREp3vjVRsUeAfTYSJ95RXS9TZvnoys,1590
|
|
47
51
|
codeanalyzer/schema/call_graph_ids.py,sha256=mWAhJDBsW8i-siJiINOHCXEm4qM6F_5se9-HuHWIFqU,568
|
|
48
52
|
codeanalyzer/schema/ids.py,sha256=G5NGPjJ3UdJ1k0opy2MrvgWRWNBZgKU1PwY1MR6hDT4,829
|
|
49
|
-
codeanalyzer/schema/l1_body.py,sha256=
|
|
50
|
-
codeanalyzer/schema/l2_callees.py,sha256=
|
|
51
|
-
codeanalyzer/schema/py_schema.py,sha256=
|
|
53
|
+
codeanalyzer/schema/l1_body.py,sha256=5Su347kwAPNflJDf7SvBR3sNXx9PVdGEml2pOpQCwo0,1684
|
|
54
|
+
codeanalyzer/schema/l2_callees.py,sha256=CTcBeGoO04DstDC6h-1sHMbbIrHDEOMxP01U9VLBcfc,2327
|
|
55
|
+
codeanalyzer/schema/py_schema.py,sha256=VwwRqr_jFolkbRKi8ytiwUScKZmscJBZvh16Y2tDbII,18449
|
|
52
56
|
codeanalyzer/semantic_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
codeanalyzer/semantic_analysis/call_graph.py,sha256=
|
|
54
|
-
codeanalyzer/semantic_analysis/
|
|
55
|
-
codeanalyzer/semantic_analysis/pycg/pycg_analysis.py,sha256=u22ZbicZ8_uTBNhbO4WepiO4ZcKB4OnarcOFh5xMido,48081
|
|
56
|
-
codeanalyzer/semantic_analysis/pycg/pycg_exceptions.py,sha256=n4tRderrSYw9cUYgR2wsn64UqozY56nD1YfDNnaVcAk,968
|
|
57
|
-
codeanalyzer/semantic_analysis/pycg/shard_planner.py,sha256=7wz821fv18vojzOHFYhXB6MLE1KdU4kFaldbd2kZHt0,15837
|
|
57
|
+
codeanalyzer/semantic_analysis/call_graph.py,sha256=6YEB_wTn5-oQYLrbIhJYE0BsHl4fpWPoy5Hwd9mTnGc,11918
|
|
58
|
+
codeanalyzer/semantic_analysis/defuse_linker.py,sha256=bSn1rCun28OQqSd2NOc9aVBSm47Gt9Iul4r_gwfqO1w,64930
|
|
58
59
|
codeanalyzer/syntactic_analysis/__init__.py,sha256=EUQkJEh6wHjWx2qTTKbTbUgwSbfKeNieKHNy7RknVXA,476
|
|
59
60
|
codeanalyzer/syntactic_analysis/exceptions.py,sha256=whs_n0vIu655Jkk1a7iOoXY6iIca4pZqJnU40V9Ejaw,537
|
|
60
61
|
codeanalyzer/syntactic_analysis/import_resolver.py,sha256=Q8noZwSdDNt4P4NMqDTB9FaS-M0_d4ASK9GnJop9MDI,2751
|
|
61
|
-
codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=
|
|
62
|
+
codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=2bp9S5Z9Atx0Ovd70nMzAbutoNx_FD0VYBY397GAAVo,47343
|
|
62
63
|
codeanalyzer/utils/__init__.py,sha256=hC6VWdR5rerSqBxzu9KQHTASWqwrrYJv-CMDwrTlzkc,137
|
|
63
64
|
codeanalyzer/utils/logging.py,sha256=Fw0tattPAOMs3o0JMjjXhRVLIF64f-SCcygUXF9jqeg,904
|
|
64
65
|
codeanalyzer/utils/progress_bar.py,sha256=C9JtzVdd10lIxTv-KA6PebqjKWueC_vMGwVzAtHuHIw,2818
|
|
65
|
-
codeanalyzer_python-1.
|
|
66
|
-
codeanalyzer_python-1.
|
|
67
|
-
codeanalyzer_python-1.
|
|
68
|
-
codeanalyzer_python-1.
|
|
69
|
-
codeanalyzer_python-1.
|
|
70
|
-
codeanalyzer_python-1.
|
|
66
|
+
codeanalyzer_python-1.2.0.dist-info/METADATA,sha256=Un9ZuyLWDpzfB_CNuAXiXGvM8eN0HQmr_0r2jGsBqqA,36319
|
|
67
|
+
codeanalyzer_python-1.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
68
|
+
codeanalyzer_python-1.2.0.dist-info/entry_points.txt,sha256=v4Vux0Nnx7sOntVk_CH7W9RX6SkIkvR1FQYq73oVlCQ,105
|
|
69
|
+
codeanalyzer_python-1.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
70
|
+
codeanalyzer_python-1.2.0.dist-info/licenses/NOTICE,sha256=MdVkNYqHJ20on2FmWvgD4WpMsX5mir33xdyPvTXd0A0,1223
|
|
71
|
+
codeanalyzer_python-1.2.0.dist-info/RECORD,,
|
codeanalyzer/config/__init__.py
DELETED
codeanalyzer/config/config.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
################################################################################
|
|
2
|
-
# Copyright IBM Corporation 2025
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
################################################################################
|
|
16
|
-
|
|
17
|
-
from codeanalyzer.semantic_analysis.pycg.pycg_analysis import PyCG
|
|
18
|
-
from codeanalyzer.semantic_analysis.pycg.pycg_exceptions import PyCGExceptions
|
|
19
|
-
|
|
20
|
-
__all__ = ["PyCG", "PyCGExceptions"]
|