Skip to content

Refactor codeanalyzer-python #223

Open

Description

@rahlk

Is your feature request related to a problem? Please describe.
The analysis code is super hard to follow and brittle as all hell. Each algorithm builds its own graph structures, results come back as bare dicts and tuples, and the order of steps is fixed in one large orchestrator. Small changes keep causing bugs where two parts disagree about the same data.

Describe the solution you'd like

I propose three changes:

  • Use networkx as the one graph type. Control-flow, call, dependence and system-dependence graphs become typed wrappers around networkx graphs. Dominators, SCCs and reachability come from networkx. We keep one small worklist solver for the dataflow analyses, such as reaching definitions and function summaries.
  • Build the analysis as named passes. Each pass has a name, a scope (application, module or callable) and the lowest analysis level (-a) at which it runs. The pipeline is assembled in one place with add_pass and add_edge. Compiling it for a given level checks the wiring, orders the passes and rejects cycles.
  • Keep the analysis separate from the output. Passes work on the graph and know nothing about analysis.json or Neo4j. Serializers turn the finished graph into each format.

Not changing: the schema, the CLI, the analysis levels, or the meaning of any output.

It'd be considered done when analysis.json is byte-for-byte identical at every level on the test fixtures.

Each hand-written kernel is replaced one at a time and compared against the old version before the old one is deleted.

Additional context

Sketch

@dataclass(frozen=True)
class AnalysisPass:
    name: str
    fn: Callable[[Dict[str, Fragment]], Fragment]   # sees earlier results, returns only additions
    scope: str = "application"                      # "application" | "module" | "callable"
    min_level: int = 1                              # lowest -a level that runs this pass


class AnalysisPipeline:
    """Builder. Nothing runs until compile()."""

    def add_pass(self, name, fn, *, scope="application", min_level=1) -> "AnalysisPipeline": ...
    def add_edge(self, before: str, after: str) -> "AnalysisPipeline": ...

    def compile(self, level: int) -> "CompiledPipeline":
        # keep passes with min_level <= level, check every edge, then order them
        # with nx.lexicographical_topological_sort (deterministic; a cycle raises)
        ...


@dataclass(frozen=True)
class CompiledPipeline:
    plan: Tuple[AnalysisPass, ...]
    def execute(self, results): ...      # run each pass in order and store its result

The whole pipeline is wired in one place:

def build_pipeline() -> AnalysisPipeline:
    ap = AnalysisPipeline()
    ap.add_pass("symbols", build_symbols)
    ap.add_pass("calls",   resolve_calls, min_level=2)
    ap.add_pass("cfg",     build_cfg,     scope="callable", min_level=3)
    ap.add_edge("symbols", "calls").add_edge("symbols", "cfg")
    return ap

compiled = build_pipeline().compile(level=opts.level)
graph = compiled.execute(ctx)

Compiling for -a 1, -a 2 and -a 3 gives symbols, then symbols -> calls, then symbols -> calls -> cfg. This matches the existing rule that each level only adds to the one before.

Activity

  1. changed the title [-]Refactor codeanalyser-python[/-] [+]Refactor codeanalyzer-python[/+] on Oct 11, 2026
  2. rahlk commented on Oct 11, 2026

    @rahlk
    ContributorAuthor

    Some cli flags also need clean up...

    1. Single file analysis is not needed/supported. Drop.
    file_name: Annotated[
        Optional[Path],
        typer.Option(
            "--file-name",
            help="Analyze only the specified file (relative to input directory).",
        ),
    ] = None,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions