Skip to content

feat(artifacts): may-dispatch over a static string table — J_DISPATCHES_TO with prov: ["table"] (DayTrader's TradeConfig.getPage) #261

Description

@rahlk

Plan (optional)

Spec: codellm-devkit/.github docs/design/specs/2026-09-11-java-view-templates-and-dispatch.md § 4.5 (amendment on PR codellm-devkit/.github#80). Follows #259 / codeanalyzer-java 3.3.0.

Problem

Running the released 3.3.0 on daytrader8 resolves 3 of its view dispatches and leaves 17 non-literal. The real mechanism is not a literal on any path: TradeServletAction.requestDispatch(page) and TradeConfigServlet dispatch to TradeConfig.getPage(N), which is return webUI[webInterface][pageNumber] over a static String[][] table of 3 × N page paths (/welcome.jsp / /welcomeImg.jsp / …) indexed by a runtime-selected interface (TradeConfig.java:169-208, 481, 730). Neither the literal tier nor the DDG / call-graph tiers can close it, correctly — no single target exists. The remaining 20 JSPs are therefore unreachable in the graph even though the set of pages the code can reach is fully static.

This issue adds a table tier: a target expression that is a call to a method whose body returns an element of a static String[] / String[][] field with a literal initializer closes on every string literal in that initializer, emitting one J_DISPATCHES_TO per matching artifact with prov: ["table"] — a may-dispatch edge, the first deliberate over-approximation in this pass.

Scope boundary

The table shape only: T.m(args) where every return in m is an array access rooted at a static String[]/String[][] field of T with an array-initializer of string literals, either directly at the dispatch site or as a caller's argument to a parameter the interprocedural tier binds. Not arbitrary constant propagation, not maps, not enums, not switch-returned literals. Not routes: table entries that name no artifact (/app?action=…) are dropped from the edge set, and a table with no matching entry is no-such-artifact. The exactly-one rule stays for literal and dataflow edges; only table edges are many-per-site, and prov says so.

Goals

  • ViewDispatches table tier at every level, after the literal tier: parse the target expression, resolve the callee by declaring-type simple name + method name within the tree (unique match, else give up), slice the callee's body_span from the module source, require every return to be an array access rooted at one static field, read that field's initializer, collect its string literals.
  • Interprocedural tier accepts a table-closed argument per caller: a parameter closes when every caller's argument is a literal or a table; the union is the candidate set; prov is ["table"] when any table contributed, else ["dataflow"].
  • Emit one edge per matched artifact; unresolved when none matches (no-such-artifact, target absent, prov lists the attempted tiers including table).
  • prov vocabulary on J_DISPATCHES_TO / view_dispatches[] documented as literal | table | dataflow; JSON oracle unchanged (stringList); README and SCHEMA_DECISIONS.md D33.

Caveats and known risks

  • May-dispatch is an over-approximation by construction: on DayTrader every table site reaches all three interface variants of every page. A consumer wanting "the page this build serves" must filter on prov; the edge does not claim a single path.
  • The callee is found by simple type name + method name, not by L2 callee (so it works at -a 1); a project with two types of the same simple name both declaring the method gives up rather than picks one.
  • Body slicing depends on body_span (3.2.0+) and on the module source being captured.
  • Levels: the tier itself is level-independent; the parameter case still needs -a 4 for the call-graph tier that reaches the callers.

Definition of done

  • Fixture: Pages.page(i) over {{"/a.jsp","/b.jsp","/x"},{"/aImg.jsp","/bImg.jsp","/x"}} with a.jsp b.jsp aImg.jsp bImg.jsp on disk: req.getRequestDispatcher(Pages.page(1)).include(req,res) at -a 1 yields exactly the four edges, all prov: ["table"], /x contributes nothing; a table naming no file yields one no-such-artifact record with prov containing table.
  • Parameter case: show(Pages.page(0)) and show("/c.jsp") calling show(String page) at -a 4 yields the four table pages plus c.jsp, prov: ["table"]; at -a 3 the site stays non-literal.
  • A return that is not an array access, or a field without a literal initializer, does not close (test each).
  • daytrader8 with the built jar: view_dispatches grows from 3 to the table's reachable pages, every .jsp in webUI that exists under src/main/webapp has an incoming J_DISPATCHES_TO, and the 3 literal edges are unchanged.
  • Full suite green; -a 1 ⊆ -a 3 ⊆ -a 4 still holds on the fixture.

Activity

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