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.
Files changed (37) hide show
  1. codeanalyzer/__main__.py +95 -123
  2. codeanalyzer/core.py +21 -45
  3. codeanalyzer/dataflow/access_paths.py +26 -4
  4. codeanalyzer/dataflow/builder.py +65 -1
  5. codeanalyzer/dataflow/identity.py +1 -1
  6. codeanalyzer/dataflow/pdg.py +7 -2
  7. codeanalyzer/dataflow/scc.py +1 -1
  8. codeanalyzer/entrypoints/__init__.py +3 -0
  9. codeanalyzer/entrypoints/detect.py +124 -0
  10. codeanalyzer/entrypoints/matching.py +182 -0
  11. codeanalyzer/entrypoints/pipeline.py +131 -0
  12. codeanalyzer/entrypoints/rules.py +159 -0
  13. codeanalyzer/entrypoints/rules.yml +88 -0
  14. codeanalyzer/neo4j/bolt.py +1 -1
  15. codeanalyzer/neo4j/project.py +85 -60
  16. codeanalyzer/neo4j/schema.py +35 -34
  17. codeanalyzer/options/__init__.py +2 -2
  18. codeanalyzer/options/options.py +2 -26
  19. codeanalyzer/schema/__init__.py +48 -0
  20. codeanalyzer/schema/l1_body.py +11 -1
  21. codeanalyzer/schema/l2_callees.py +29 -13
  22. codeanalyzer/schema/py_schema.py +95 -103
  23. codeanalyzer/semantic_analysis/call_graph.py +20 -4
  24. codeanalyzer/semantic_analysis/defuse_linker.py +1499 -0
  25. codeanalyzer/syntactic_analysis/symbol_table_builder.py +88 -3
  26. {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/METADATA +36 -161
  27. {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/RECORD +31 -30
  28. {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/WHEEL +1 -1
  29. codeanalyzer/config/__init__.py +0 -3
  30. codeanalyzer/config/config.py +0 -8
  31. codeanalyzer/semantic_analysis/pycg/__init__.py +0 -20
  32. codeanalyzer/semantic_analysis/pycg/pycg_analysis.py +0 -1115
  33. codeanalyzer/semantic_analysis/pycg/pycg_exceptions.py +0 -23
  34. codeanalyzer/semantic_analysis/pycg/shard_planner.py +0 -401
  35. {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/entry_points.txt +0 -0
  36. {codeanalyzer_python-1.1.0.dist-info → codeanalyzer_python-1.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {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 ast.iter_child_nodes(node):
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 ast.iter_child_nodes(node):
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 = [ast.unparse(d) for d in child.decorator_list]
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: codeanalyzer-python
3
- Version: 1.1.0
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
- [PyCG](https://github.com/vitsalis/PyCG), and [Tree-sitter](https://tree-sitter.github.io/). It
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 with **PyCG**-resolved edges at
101
- level 2 (provenance-tagged, coupling-aware sharding for large apps).
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`, or binary `analysis.msgpack` for smaller artifacts.
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`, or `analysis.msgpack` with
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, PyCG and Tree sitter.
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, 2=+PyCG
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
- │ --pycg-shard --no-pycg-shard Shard PyCG
348
- call-graph
349
- analysis by
350
- Python package
351
- (level 2 only).
352
- When the project
353
- exceeds the
354
- 500-file
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 PyCG (level 2):**
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 **PyCG** and merges its
501
- flow-sensitive edges in (RPC / third-party / dynamically-dispatched targets), backfilling
502
- callees Jedi could not resolve. Every edge is provenance-tagged (e.g. `jedi`, `pycg`).
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
- (`:PyCFGNode` nodes wired by `PY_CFG_NEXT`/`PY_CDG`/`PY_DDG`, plus the level-4
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` | PyCG call-graph enrichment; each call's `callee` backfilled to a `can://` id | `call_graph`, `body` callees |
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(+PyCG) call graph, treated as a frozen oracle.
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": ["jedi", "pycg"] } ],
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** — `:PyCFGNode` nodes (statements, and at level 4 the parameter vertices) wired by
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=fukx4MhfahAdwN1oHRDkbOaAZZanXrHpHwWV4FG6esg,16275
3
- codeanalyzer/core.py,sha256=ZBPXgRNWlSW2utzaII686TsppIvbFCPUz5337OxaKvg,43095
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=9BUaupb9_jev3pYcRSBroKt5IDCRhpfssxWUWxwcEDM,22821
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=QhsezCYCEDw_hdFudD6aUoVah9nE0B7M4dQC3YU1agE,28248
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=jGuyPrtRL5jTfHBVr1aLA7SvJAAS9PyZhb5zouVv2yA,3905
16
- codeanalyzer/dataflow/pdg.py,sha256=vVrdUrdKB-3Czf9N0wuuA1x_Bfj1KnyEvXhW6UefGcA,3488
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=AfnUCJ2-ZMkdoGL-ucUyb6903u26dfDkliF51KdP_3s,3501
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=0JHv6bHp2bsKwpFPT7CDzjDaz2GvGyoEaIKVtk_ayuA,10133
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=HbrehiVmVxLtoDCZkUVFpCOEWQuZR1ezr3mrp1EFnyo,24332
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=SyjaME5z9vgBC70Aswksct8Z0Tiau1PNvbHoeT3JLxE,10582
43
- codeanalyzer/options/__init__.py,sha256=Ki4qhHFqpyuUWVsntO-NYJMVWrkeFOzPW4nQ7oxiUVI,155
44
- codeanalyzer/options/options.py,sha256=RLJ298wSnK0FdaN3xJ5Ha7Lj62jaV9UDlhhtb5vh4Ro,2162
45
- codeanalyzer/schema/__init__.py,sha256=5bdJFV8icBDlTVHEoPumCFHBcq7O98-YNvuKs0qsqEs,2420
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=9dRyrhHU277MVWAEjumvATbIai2YkuRE8kXyjHD-evo,1365
50
- codeanalyzer/schema/l2_callees.py,sha256=A4kX4Sm07O06-2CskqshP4QThdaTgCFHB6x3BarRvs8,1438
51
- codeanalyzer/schema/py_schema.py,sha256=KesvWIODJTl-kVMt1fYvw9yVQNWA0tMsLApqmb9ZkLk,17479
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=PuR7dFTVrKanPatomYDJgkBaJq1Fd9920KspQpHQl1U,10957
54
- codeanalyzer/semantic_analysis/pycg/__init__.py,sha256=Lsgz25iFM_RGGu_i2psY-LN-KwvYIZQPnKgRPL1JsjU,928
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=US09mQgyaCbaF72YOtZF5NkJv1UM881c0dT9DSBpqXY,43398
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.1.0.dist-info/METADATA,sha256=vZi9sYUtyn9x9IFsoUYsxSxm6bTmDOlA3eBwTR3VCrw,46835
66
- codeanalyzer_python-1.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
67
- codeanalyzer_python-1.1.0.dist-info/entry_points.txt,sha256=v4Vux0Nnx7sOntVk_CH7W9RX6SkIkvR1FQYq73oVlCQ,105
68
- codeanalyzer_python-1.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
- codeanalyzer_python-1.1.0.dist-info/licenses/NOTICE,sha256=MdVkNYqHJ20on2FmWvgD4WpMsX5mir33xdyPvTXd0A0,1223
70
- codeanalyzer_python-1.1.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,3 +0,0 @@
1
- from .config import OutputFormat
2
-
3
- __all__ = ["OutputFormat"]
@@ -1,8 +0,0 @@
1
- from enum import Enum
2
-
3
-
4
- class OutputFormat(str, Enum):
5
- """String-based enum for output formats to support typer case-insensitive options."""
6
-
7
- JSON = "json"
8
- MSGPACK = "msgpack"
@@ -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"]