Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion graph/bundle/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
| `boundary_generated` | csharp | A read of a property the compiler generated (a positional record's property). There is no written accessor to point at, so callee_method_id is NULL, callee_label names the member and callee_provenance is `generated`. A correct end, not a blind spot. |
| `runtime_observed` | csharp | Only when a runtime trace is supplied: an edge the trace saw and the static pass did not name. Real by construction, and added only, never used to remove an edge. The site is the CALLER METHOD, not an expression (the tracer sees method entry, not the call site), and kind is `runtime`. |
| `known_implicit_ctor` | csharp | `new Foo()` where Foo declares no constructor. The compiler supplies a parameterless one, so there is no user code to call and nothing resolving is the right answer. |
| `event_dispatch` | csharp | A mediator's `Send(request)` reaching the `Handle` of the handler registered for the request's type (`IRequestHandler<TRequest, TResponse>`, with a generic request's type arguments), or `Publish(notification)` reaching every `INotificationHandler<T>` for its type and each of its bases. Not the site's own callee: the site also keeps its row for the library method. The container picks the handler in process, before the call returns, so this is a call edge and not a remote_edge. A request declared as a base (not an interface) fans to the handler of each type in its family. |
| `known_edge` | all | Exactly one target resolved. The strongest claim. |
| `multi_inferred` | all | A sound SET of possible targets; each member is one row. The set over-approximates — every member is a real possibility, but not every member runs. HOW WIDE the set is differs by language: see the per-language notes on this table for whether the fan is narrowed by the instantiation set. |
| `boundary_lib` | all | The target is outside the client (library, builtin, or unstaged external). The chain is not expanded past it here. |
Expand Down Expand Up @@ -569,7 +570,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
- **python** — A `boundary_lib` edge may point at a builtin (callee_provenance builtin, callee_label `builtin:NAME`) or at an unstaged import path (callee_provenance external) — neither has a methods row.
- **java** — A `boundary_lib` edge with callee_provenance external names a method of an ancestor type no staged IR declares (callee_label `external:<type>.<name>`, no methods row). A site whose receiver is declared as such a type is multi_inferred even with one client override: the platform method itself, and the platform's own subclasses, are the other possible targets. Stage the library to replace the label with the real method.
- **python** — The reason a site is ambiguous_unknown is exported per site in ext_call_site_unresolved (site, caller, reason, detail).
- **all** — THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.
- **all** — THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript; event_dispatch in C# too, where a mediator Send or Publish runs the handler for the request type, beside the row for the site itself. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.
- **java** — A multi_inferred fan is CHA-wide: it is every override the hierarchy admits, bounded only by the dispatch cap. type_instantiated is computed and exported but NOT read by any rule, so the fan is not narrowed to types the program constructs. Narrow it yourself by joining dispatch_candidates to type_instantiated — see the dispatch_envelope_of query. A receiver is ALSO typed by what flows into it (a local's initializer, the arguments callers pass to a parameter, the receivers callers invoke a method on for its `this`), and each flow-in type resolves its member directly, outside the fan: that is why a `fan_capped` site still carries edges, and why they are the types the program was seen to hand over, not the whole hierarchy.
- **typescript** — A multi_inferred fan is CHA-wide, as in Java: type_instantiated is computed and exported but NOT read by any rule. The fan also has sources that are not virtual dispatch at all — an overload set or a union-typed receiver produces one too.
- **python** — A multi_inferred fan IS narrowed by the instantiation set: type_instantiated_reachable (the constructed classes and their bases) bounds dispatch in resolution/dispatch.dl. Python is the only front end where that narrowing is applied, so a fan here is tighter than the same shape would be in Java or TypeScript.
Expand Down
3 changes: 2 additions & 1 deletion graph/bundle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ export const VOCAB: readonly VocabSpec[] = [
{ table: 'call_edges', column: 'tier', value: 'boundary_generated', languages: C, meaning: 'A read of a property the compiler generated (a positional record\'s property). There is no written accessor to point at, so callee_method_id is NULL, callee_label names the member and callee_provenance is `generated`. A correct end, not a blind spot.' },
{ table: 'call_edges', column: 'tier', value: 'runtime_observed', languages: C, meaning: 'Only when a runtime trace is supplied: an edge the trace saw and the static pass did not name. Real by construction, and added only, never used to remove an edge. The site is the CALLER METHOD, not an expression (the tracer sees method entry, not the call site), and kind is `runtime`.' },
{ table: 'call_edges', column: 'tier', value: 'known_implicit_ctor', languages: C, meaning: '`new Foo()` where Foo declares no constructor. The compiler supplies a parameterless one, so there is no user code to call and nothing resolving is the right answer.' },
{ table: 'call_edges', column: 'tier', value: 'event_dispatch', languages: C, meaning: 'A mediator\'s `Send(request)` reaching the `Handle` of the handler registered for the request\'s type (`IRequestHandler<TRequest, TResponse>`, with a generic request\'s type arguments), or `Publish(notification)` reaching every `INotificationHandler<T>` for its type and each of its bases. Not the site\'s own callee: the site also keeps its row for the library method. The container picks the handler in process, before the call returns, so this is a call edge and not a remote_edge. A request declared as a base (not an interface) fans to the handler of each type in its family.' },
{ table: 'call_edges', column: 'callee_provenance', value: 'external', languages: 'all', meaning: 'The target is outside every staged IR. callee_method_id is NULL and callee_label names it, as `external:<Type>.<Member>`. A client-only run of a C# project reaches the BCL this way for most of its calls.' },

// run.key
Expand Down Expand Up @@ -747,7 +748,7 @@ export const NOTES: readonly NoteSpec[] = [
{ language: 'python', table: 'entry_points', note: 'EMPTY. The Python rule set does not derive entry points; entry_reachable is therefore empty too.' },
{ language: 'python', table: 'overrides', note: 'EMPTY — this table is Java-shaped. The Python dispatch envelope is in dispatch_candidates with basis `mro`, and `value` for a function assigned onto an instance\'s member; the raw linearisation is in ext_mro_position.' },
{ language: 'python', table: 'type_instantiated', note: 'Every row has how = `new`: the rule set records that some client call constructs the class, not which form.' },
{ language: 'all', table: 'call_edges', note: 'THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.' },
{ language: 'all', table: 'call_edges', note: 'THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript; event_dispatch in C# too, where a mediator Send or Publish runs the handler for the request type, beside the row for the site itself. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.' },
{ language: 'java', table: 'call_edges', note: 'A multi_inferred fan is CHA-wide: it is every override the hierarchy admits, bounded only by the dispatch cap. type_instantiated is computed and exported but NOT read by any rule, so the fan is not narrowed to types the program constructs. Narrow it yourself by joining dispatch_candidates to type_instantiated — see the dispatch_envelope_of query. A receiver is ALSO typed by what flows into it (a local\'s initializer, the arguments callers pass to a parameter, the receivers callers invoke a method on for its `this`), and each flow-in type resolves its member directly, outside the fan: that is why a `fan_capped` site still carries edges, and why they are the types the program was seen to hand over, not the whole hierarchy.' },
{ language: 'typescript', table: 'call_edges', note: 'A multi_inferred fan is CHA-wide, as in Java: type_instantiated is computed and exported but NOT read by any rule. The fan also has sources that are not virtual dispatch at all — an overload set or a union-typed receiver produces one too.' },
{ language: 'python', table: 'call_edges', note: 'A multi_inferred fan IS narrowed by the instantiation set: type_instantiated_reachable (the constructed classes and their bases) bounds dispatch in resolution/dispatch.dl. Python is the only front end where that narrowing is applied, so a fan here is tighter than the same shape would be in Java or TypeScript.' },
Expand Down
4 changes: 4 additions & 0 deletions graph/csharp/engine/call-edge-generation/call_chain.dl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
// ambiguous_dynamic a call through `dynamic`. Unresolvable BY DESIGN: the DLR
// decides at runtime. Separated from ambiguous_unknown so a
// known-undecidable site is never counted as an engine failure.
// event_dispatch NOT the site's own target: a framework runs this method for the
// site in process (a mediator's Send reaching the handler for the
// request type). Added beside the site's row, by
// framework-behavior/dispatch.dl.
// ============================================================================

// ── THE SITE UNIVERSE ───────────────────────────────────────────────────────
Expand Down
26 changes: 17 additions & 9 deletions graph/csharp/engine/framework-behavior/destinations.dl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ call_recv_written_name(e, n) :-
ref_denotes("client", r, "LOCAL_VARIABLE", v), var_is_implicit("client", v),
var_value_initializer("client", v, init), expr_new_type_ref("client", init, tr),
type_ref_name("client", tr, n).
// …and a property read off another value, `services.Mediator.Send(..)`: a minimal-API
// endpoint takes its services as one parameter object, and the receiver is that
// object's property, whose declared type name is what the project wrote.
call_recv_written_name(e, n) :-
call_receiver_expr("client", e, r), property_access("client", r, p, _),
property_type_name("client", p, n, _), n != "".
// …and a `var` from the service locator, which holds its type argument
call_recv_written_name(e, n) :-
call_receiver_expr("client", e, r),
Expand Down Expand Up @@ -706,24 +712,26 @@ msg_bus_arg(e, a) :- msg_bus_send_site(e, _, _), http_arg(e, 0, a), !msg_first_i
msg_bus_arg(e, a) :- msg_bus_send_site(e, _, _), msg_first_is_address(e), http_arg(e, 1, a).
msg_first_is_address(e) :- msg_bus_send_site(e, _, _), http_arg(e, 0, a0), http_arg(e, 1, _),
expr_literal("client", a0, k, _), k != "NULL".
// the message's type NAME, however it is written
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), expr_new_type_ref("client", a, tr), type_ref_name("client", tr, n0), last_segment(n0, n).
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), expr_kind("client", a, "OBJECT_CREATION"),
// the message's type NAME, however it is written. Read for a bus send here and for an
// in-process mediator send (dispatch.dl), which carries its request the same way.
msg_typed_arg(a) :- msg_bus_arg(_, a).
msg_arg_type_name(a, n) :- msg_typed_arg(a), expr_new_type_ref("client", a, tr), type_ref_name("client", tr, n0), last_segment(n0, n).
msg_arg_type_name(a, n) :- msg_typed_arg(a), expr_kind("client", a, "OBJECT_CREATION"),
call_callee_name("client", a, n0), last_segment(n0, n).
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), expr_type("client", a, gk), type_group("client", t, gk),
msg_arg_type_name(a, n) :- msg_typed_arg(a), expr_type("client", a, gk), type_group("client", t, gk),
type_decl("client", n, _, _, _, t).
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
msg_arg_type_name(a, n) :- msg_typed_arg(a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
var_type_name("client", v, n0, _), n0 != "", last_segment(n0, n).
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), ref_denotes("client", a, "PARAMETER", p),
msg_arg_type_name(a, n) :- msg_typed_arg(a), ref_denotes("client", a, "PARAMETER", p),
param_type_name("client", p, n0, _), n0 != "", last_segment(n0, n).
// A local declared as a BASE is usually assigned a concrete message on each branch,
// Notice evt; if (ok) evt = new Accepted(..); else evt = new Rejected(..);
// and those constructions, in the same method, are what it can hold.
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
msg_arg_type_name(a, n) :- msg_typed_arg(a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
msg_local_assigned(v, c), msg_creation_name(c, n).
msg_arg_type_name(a, n) :- msg_bus_arg(_, a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
msg_arg_type_name(a, n) :- msg_typed_arg(a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
var_value_initializer("client", v, c), msg_creation_name(c, n).
msg_local_assigned(v, c) :- msg_bus_arg(_, a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
msg_local_assigned(v, c) :- msg_typed_arg(a), ref_denotes("client", a, "LOCAL_VARIABLE", v),
expr_kind("client", asg, "ASSIGNMENT"), expr_child("client", asg, "ASSIGNMENT_TARGET", _, t),
ref_denotes("client", t, "LOCAL_VARIABLE", v), expr_child("client", asg, "ASSIGNMENT_VALUE", _, c).
msg_creation_name(c, n) :- expr_kind("client", c, "OBJECT_CREATION"), expr_new_type_ref("client", c, tr),
Expand Down
Loading
Loading