Skip to content

Framework-independent entrypoint detection (JackEE-style finders) #27

Open

Description

@rahlk

Is your feature request related to a problem? Please describe.

Nothing in the emitted schema says which callables a framework invokes from outside the application's own call graph. A call graph without identified roots cannot answer reachability, dead-code, or attack-surface questions, and every consumer re-derives entrypoints ad hoc.

The gap is load-bearing downstream. Java emits is_entrypoint and is_entrypoint_class (python-sdk/cldk/models/java/models.py:343,403), so JavaAnalysis.get_entry_point_* are real methods. The Python facade mirrors the same surface but all four — get_entry_point_classes, get_entry_point_methods, get_service_entry_point_classes, get_service_entry_point_methods — raise NotImplementedError (python-sdk/cldk/analysis/python/python_analysis.py:881-960), purely because the backend supplies no data.

Describe the solution you'd like

  • Schema, all defaulted: PyClass.is_entrypoint, PyClass.entrypoint_framework, PyCallable.is_entrypoint, PyCallable.entrypoint_framework
  • frameworks/_base.py: AbstractEntrypointFinder plus ModuleContext carrying per-project routing facts
  • Per-framework finders behind entrypoint_factory.py, OR-ing results (mirrors Java's EntrypointsFinderFactory)
  • Routing pre-pass in frameworks/routing/, one pass per project, emitting {qualified_name → route_metadata}
  • Wire into symbol_table_builder.py: routing pre-pass first, then set the fields during class/function construction
Finder Signals
flask @app.route, @bp.get|post|…, MethodView/View bases
fastapi @app|router.get|post|websocket, router mounts via ModuleContext
django CBV bases, DRF @api_view/@action, urls.py resolution
tornado RequestHandler subclass
celery @app.task, @shared_task, @periodic_task
aws_lambda handler(event, context) + SAM/serverless binding
cli @click.command, @app.command
grpc *Servicer subclass

Routing resolvers: django_url_resolver (path/re_path/url/include chains, .as_view()), fastapi_router_resolver (include_router(prefix=…), mount), flask_blueprint_resolver (Blueprint + register_blueprint).

Describe alternatives you've considered

Not stated in the original issue.

Additional context

Scope boundary

Entrypoints only. CRUD detection is specced separately. is_entrypoint_class covers inheritance-based entrypoints only, not a coarse "worth analyzing" filter as in Java — in Python the function-level predicate does the real work.

Caveats and known risks

  • The routing pre-pass is the only piece with no Java analog and carries most of the risk.
  • Decorator-only detection was rejected: it misses Django entirely, where views bind in urls.py.
  • Literal JackEE (Datalog/Doop fact ingestion) was rejected: this analyzer is AST-based, and Java already ported the same architecture to ASTs.
  • Resolving in the SDK was rejected: it sees only the serialized schema and cannot re-run AST passes.
  • Tree-sitter suffices for decorators and base classes; Jedi is needed only for cross-module base resolution.

Definition of done

  • A Flask @app.route view, a FastAPI @router.get handler, a Celery @shared_task, and a Click @cli.command each carry is_entrypoint=True and the right entrypoint_framework
  • A Django function view with no decorator, bound only via path('...', view) through one include(), is flagged — the routing pre-pass gate
  • An internally-called helper is not flagged
  • Pre-existing analysis.json files still load
  • PythonAnalysis.get_entry_point_* become thin readers over these fields, matching JavaAnalysis

Reference

JackEE — Antoniadis et al., "Static Analysis of Java Enterprise Applications", PLDI 2020. Java mirror: src/main/java/com/ibm/cldk/javaee/.

Activity

  1. added a commit that references this issue on Aug 20, 2026
  2. rahlk commented on Aug 20, 2026

    @rahlk
    ContributorAuthor

    Units 1-3 landed in #137 (d35541d). Issue stays open — this is 3 of 6 units.

    Landed: PyEntrypoint records on PyCallable/PyClass with derived is_entrypoint; a wrapped post-pass over the L1 symbol table; a shipped rules.yml extensible via --entrypoint-rules; framework gating on imports or manifest; decorator matching; inheritance matching with the class/method dispatch split and via back-pointers. Working detection for Flask, FastAPI, Celery, Click and DRF decorators.

    Design: docs/design/specs/2026-08-19-entrypoint-detection-design.md. Note it corrects this issue's premise in one place: the roadmap's claim that entrypoint vocabulary was "coined three ways" does not hold — Java has the only implementation, and TypeScript's TSApplication.entrypoints is declared in its SCHEMA_DECISIONS.md but TSEntrypoint appears nowhere in its source. Python is the second implementation and set the record shape.

    Remaining, per spec § 9 and § 10:

    • Unit 4 — declared readers: [project.scripts], [project.entry-points], setup.cfg, setup.py, Procfile, Dockerfile, SAM/serverless handlers. Exact, no heuristics; the highest value-per-effort work left.
    • Unit 5 — the Django routing engine. Django currently gates in and reports frameworks_detected: ["django"], but urls.py-bound views are not detected.
    • Unit 6 — structural passes (__main__ walk).
    • Deferred — the argument-position rule (a first-party callable passed to a call resolving outside the project). It generalises furthest of any rule in the itemisation, catching add_url_rule(view_func=h), scheduler.add_job(run) and any framework with no finder. Blocked on PyCallArgument carrying resolved identity — the same id-space work Structured decorator representation: PyDecorator on callable, class, attribute, parameter #128 did for decorators.

    Known gaps carried forward: multi-hop inheritance unresolved (BaseRule.transitive loaded but unhonoured); no record dedup on (framework, evidence, route); evidence carries a qualified name rather than a binding site, which unit 5 needs; Neo4j projects only is_entrypoint/entrypoint_frameworks, so via is not walkable in the graph; confidence typed str rather than Literal.

    Worth knowing before unit 4: matching depends on Jedi's full_name, which returns the definition path, not the public re-export path — @app.route resolves to flask.sansio.scaffold.Scaffold.route, not flask.Flask.route. All eight shipped patterns were initially written against the public paths and detected nothing on real code. test/test_entrypoint_frameworks_integration.py now pins the real paths against installed packages; keep that test honest when adding frameworks.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions