Skip to content

Commit fe1f490

Browse files
Merge pull request #1769 from AxiomCodeAI/apps/csharp/framework-pipeline-registration-wraps-ho
csharp: a component registered into a framework pipeline reaches its hooks
2 parents 268de57 + e4afbc8 commit fe1f490

10 files changed

Lines changed: 467 additions & 1 deletion

File tree

‎graph/csharp/engine/framework-behavior/knobs.dl‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,51 @@ cs_framework_save_hook("SaveChangesCanceled"). cs_framework_save_hook("SaveChang
338338
cs_framework_save_hook("ThrowingConcurrencyException"). cs_framework_save_hook("ThrowingConcurrencyExceptionAsync").
339339
cs_framework_callback("SaveChangesInterceptor", n, "orm_hook") :- cs_framework_save_hook(n).
340340
cs_framework_callback("ISaveChangesInterceptor", n, "orm_hook") :- cs_framework_save_hook(n).
341+
// EF Core: a command interceptor runs around every command the context sends.
342+
cs_framework_command_hook("ReaderExecuting"). cs_framework_command_hook("ReaderExecutingAsync").
343+
cs_framework_command_hook("ReaderExecuted"). cs_framework_command_hook("ReaderExecutedAsync").
344+
cs_framework_command_hook("NonQueryExecuting"). cs_framework_command_hook("NonQueryExecutingAsync").
345+
cs_framework_command_hook("NonQueryExecuted"). cs_framework_command_hook("NonQueryExecutedAsync").
346+
cs_framework_command_hook("ScalarExecuting"). cs_framework_command_hook("ScalarExecutingAsync").
347+
cs_framework_command_hook("ScalarExecuted"). cs_framework_command_hook("ScalarExecutedAsync").
348+
cs_framework_command_hook("CommandFailed"). cs_framework_command_hook("CommandFailedAsync").
349+
cs_framework_callback("DbCommandInterceptor", n, "orm_hook") :- cs_framework_command_hook(n).
350+
cs_framework_callback("IDbCommandInterceptor", n, "orm_hook") :- cs_framework_command_hook(n).
351+
// ASP.NET Core: an endpoint filter runs around every endpoint it is added to, and a
352+
// factory-activated middleware around every request.
353+
cs_framework_callback("IEndpointFilter", "InvokeAsync", "framework_hook").
354+
cs_framework_callback("IMiddleware", "InvokeAsync", "framework_hook").
355+
356+
// ── PIPELINE REGISTRATION (pipelines.dl) ───────────────────────────────────
357+
// cs_pipeline_register(Method, ReceiverMember, Form): a call that puts a component into a
358+
// framework pipeline, whose hooks (cs_framework_callback above) the framework then runs.
359+
// ReceiverMember "*" is any receiver; otherwise the receiver must be a member access of
360+
// that name (`options.Interceptors.Add<T>()`: a bare `Add` is every list in the program).
361+
// Form "type" is the call's type argument, `AddEndpointFilter<T>()`; "value" is an
362+
// argument whose type is known, `AddEndpointFilter(new T(..))`,
363+
// `AddInterceptors(sp.GetRequiredService<T>())`.
364+
cs_pipeline_register("AddHostedService", "*", "type").
365+
cs_pipeline_register("AddEndpointFilter", "*", "type").
366+
cs_pipeline_register("AddEndpointFilter", "*", "value").
367+
cs_pipeline_register("UseMiddleware", "*", "type").
368+
cs_pipeline_register("AddInterceptor", "*", "type"). // a gRPC client's builder
369+
cs_pipeline_register("AddInterceptor", "*", "value").
370+
cs_pipeline_register("Add", "Interceptors", "type"). // a gRPC server's options
371+
cs_pipeline_register("Add", "Interceptors", "value").
372+
cs_pipeline_register("AddInterceptors", "*", "value"). // an EF Core context's options
373+
// What a registered component WRAPS, beyond being called: (Method, ReceiverMember, Scope).
374+
// "route": the endpoint it is added to, or every endpoint of the route group it is added
375+
// to (nested groups too). "grpc_host": every rpc of every service the same file maps
376+
// with cs_grpc_map_service, which is where a host both configures AddGrpc and maps.
377+
cs_pipeline_wraps("AddEndpointFilter", "*", "route").
378+
cs_pipeline_wraps("Add", "Interceptors", "grpc_host").
379+
cs_grpc_map_service("MapGrpcService").
380+
// EF Core runs every save interceptor added with AddInterceptors when a context saves:
381+
// the synchronous hooks on SaveChanges, the Async ones on SaveChangesAsync.
382+
cs_ef_interceptor_register("AddInterceptors").
383+
cs_ef_save_call("SaveChanges", "sync").
384+
cs_ef_save_call("SaveChangesAsync", "async").
385+
// the framework bases a DbContext is written against
386+
cs_ef_context_base("DbContext").
387+
cs_ef_context_base("IdentityDbContext").
388+
cs_ef_context_base("IdentityUserContext").
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// ============================================================================
2+
// FRAMEWORK-BEHAVIOR · PIPELINES (a component registered into a framework pipeline)
3+
//
4+
// group.AddEndpointFilter<ClinicFilter>(); // ASP.NET runs InvokeAsync
5+
// services.AddGrpc(o => o.Interceptors.Add<Errors>()); // gRPC runs UnaryServerHandler
6+
// options.AddInterceptors(new AuditInterceptor()); // EF runs SavingChangesAsync
7+
// await db.SaveChangesAsync(); // ...here
8+
//
9+
// The registration names the component's TYPE (or hands over an instance) and never
10+
// its hook, so no call site names InvokeAsync, UnaryServerHandler or SavingChangesAsync.
11+
// entry-points.dl already says the framework calls them; nothing said WHO put them where
12+
// they are called, so `impact` on the hook had no caller and `path` from a save to the
13+
// interceptor found no chain.
14+
//
15+
// Three facts, each ADDED beside what the resolution phases derived:
16+
// 1. the registration site calls every hook of the registered type. A call_chain_edge
17+
// of tier `callback_registered` (a component handed over and run by whoever holds
18+
// it): consumers rank it below a resolved call and print it as `registered`.
19+
// 2. an EF save calls the save hooks of every interceptor the project registers with
20+
// AddInterceptors. Tier `event_dispatch`, as a mediator's Send reaching its handler
21+
// (dispatch.dl): same call stack, no call site names the hook.
22+
// 3. a filter or a server interceptor WRAPS the endpoints it is added to: the endpoint
23+
// depends on it (a request to the endpoint runs it first). framework_edge(Endpoint,
24+
// Hook, Mechanism, Detail, Confidence), the relation python/ emits for the same kind
25+
// of hop, which impact reads as a `framework` dependent of the hook. Not a call.
26+
//
27+
// Every framework name is a knob (knobs.dl: cs_pipeline_register, cs_pipeline_wraps,
28+
// cs_ef_*), over the cs_framework_callback table that names each base's hooks.
29+
// ============================================================================
30+
31+
// ── the registration sites ──────────────────────────────────────────────────
32+
pl_site(e, mn, rm) :- csite("client", e), call_callee_name("client", e, mn), cs_pipeline_register(mn, rm, _),
33+
rm = "*".
34+
pl_site(e, mn, rm) :- csite("client", e), call_callee_name("client", e, mn), cs_pipeline_register(mn, rm, _),
35+
rm != "*", call_receiver_expr("client", e, r), expr_kind("client", r, "MEMBER_ACCESS"),
36+
expr_member_name_child("client", r, nm), expr_written_name("client", nm, rm).
37+
38+
// the registered type: the call's own type argument...
39+
pl_registers(e, gk) :- pl_site(e, mn, rm), cs_pipeline_register(mn, rm, "type"),
40+
type_ref_owner("client", tr, e, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
41+
!type_ref_parent("client", tr, _, _, _), type_ref_resolves("client", tr, gk).
42+
// ...or the type of an argument: `new T(..)`, a typed local, parameter or field, and
43+
// `sp.GetRequiredService<T>()`, whose type is its type argument.
44+
pl_registers(e, gk) :- pl_site(e, mn, rm), cs_pipeline_register(mn, rm, "value"), http_arg(e, _, a),
45+
expr_type("client", a, gk).
46+
pl_registers(e, gk) :- pl_site(e, mn, rm), cs_pipeline_register(mn, rm, "value"), http_arg(e, _, a),
47+
csite("client", a), call_callee_name("client", a, f), cs_di_resolve(f),
48+
type_ref_owner("client", tr, a, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
49+
!type_ref_parent("client", tr, _, _, _), type_ref_resolves("client", tr, gk).
50+
51+
// ── the hooks of a registered type ──────────────────────────────────────────
52+
// A method the framework calls on the type because of the base it derives from
53+
// (entry-points.dl, cs_framework_type), declared on the type or, when the type does not
54+
// declare one of that name, inherited from the project's own base.
55+
pl_hook_name(gk, mn) :- pl_registers(_, gk), cs_framework_type(gk, bn), cs_framework_callback(bn, mn, _),
56+
!contains("*", mn), !contains("<", mn).
57+
pl_own_hook(gk, mn, m) :- pl_hook_name(gk, mn), method_in_type("client", gk, m), method_name("client", m, mn).
58+
pl_has_own(gk, mn) :- pl_own_hook(gk, mn, _).
59+
pl_hook(gk, m) :- pl_own_hook(gk, _, m).
60+
pl_hook(gk, m) :- pl_hook_name(gk, mn), !pl_has_own(gk, mn), type_ancestor("client", gk, agk),
61+
method_in_type("client", agk, m), method_name("client", m, mn).
62+
63+
// 1. the registration site reaches the hooks
64+
call_chain_edge(e, from, "-", m, "client", "callback_registered", kind) :- pl_registers(e, gk), pl_hook(gk, m),
65+
call_from("client", e, from), from != m, invocation_site("client", e, kind).
66+
67+
// ── 2. an EF save runs the registered save interceptors ─────────────────────
68+
pl_ef_interceptor(gk) :- pl_site(e, mn, _), cs_ef_interceptor_register(mn), pl_registers(e, gk).
69+
pl_async_name(n) :- cs_framework_save_hook(n), strlen(n) > 5, substr(n, strlen(n) - 5, 5) = "Async".
70+
pl_save_hook(m, "async") :- pl_ef_interceptor(gk), pl_hook(gk, m), method_name("client", m, n),
71+
cs_framework_save_hook(n), pl_async_name(n).
72+
pl_save_hook(m, "sync") :- pl_ef_interceptor(gk), pl_hook(gk, m), method_name("client", m, n),
73+
cs_framework_save_hook(n), !pl_async_name(n).
74+
75+
// a context: a type deriving from a DbContext base, directly or through the project's own
76+
pl_context(gk) :- cs_framework_type(gk, bn), cs_ef_context_base(bn).
77+
// A save: SaveChanges(Async) on a context, `db.SaveChangesAsync()`, `SaveChanges()`
78+
// inside one, `base.SaveChangesAsync()` in its override...
79+
pl_save_call(e, how) :- call_callee_name("client", e, n), cs_ef_save_call(n, how), call_recv_type("client", e, gk),
80+
pl_context(gk).
81+
pl_save_call(e, how) :- call_callee_name("client", e, n), cs_ef_save_call(n, how), call_is_base("client", e),
82+
expr_ultimate_type_group("client", e, gk), pl_context(gk).
83+
// ...and through the project's own abstraction over it: `IUnitOfWork.SaveChangesAsync`,
84+
// which a context implements by inheriting DbContext's. The call resolves to the
85+
// abstraction's method, which is not a context's own: a context that overrides the save
86+
// is reached through the dispatch hop and its own `base.` call above.
87+
// (Read off the resolution relations, not call_chain_edge, which this adds to.)
88+
pl_save_target(e, mm) :- call_callee_name("client", e, n), cs_ef_save_call(n, _), client_calls_client("client", e, "client", mm).
89+
pl_save_target(e, mm) :- call_callee_name("client", e, n), cs_ef_save_call(n, _), call_names_bodiless(e, mm).
90+
pl_save_call(e, how) :- call_callee_name("client", e, n), cs_ef_save_call(n, how), pl_save_target(e, mm),
91+
method_name("client", mm, n), method_owner("client", ot, mm), type_group("client", ot, ogk), !pl_context(ogk),
92+
pl_context(cgk), type_ancestor("client", cgk, ogk).
93+
94+
call_chain_edge(e, from, "-", m, "client", "event_dispatch", kind) :- pl_save_call(e, how), pl_save_hook(m, how),
95+
call_from("client", e, from), from != m, invocation_site("client", e, kind).
96+
97+
// ── 3. what a filter or server interceptor wraps ────────────────────────────
98+
pl_wrap_site(e, sc) :- pl_site(e, mn, rm), cs_pipeline_wraps(mn, rm, sc).
99+
100+
// "route": walk back from a builder expression through locals and fluent calls to the
101+
// endpoint (MapGet..) or route group (MapGroup) it configures, as group_prefix does in
102+
// destinations.dl.
103+
pl_map_call(e) :- map_call(e, _).
104+
pl_up(x, x) :- pl_wrap_site(e, "route"), call_receiver_expr("client", e, x).
105+
pl_up(x, x) :- map_call(e, _), call_receiver_expr("client", e, x).
106+
pl_up(x, x) :- map_group_call(e), call_receiver_expr("client", e, x).
107+
pl_up(x, z) :- pl_up(x, y), ref_denotes("client", y, "LOCAL_VARIABLE", v), var_initializer("client", v, z).
108+
pl_up(x, z) :- pl_up(x, y), csite("client", y), !pl_map_call(y), !map_group_call(y), call_receiver_expr("client", y, z).
109+
pl_anchor(x, n) :- pl_up(x, n), map_call(n, _).
110+
pl_anchor(x, n) :- pl_up(x, n), map_group_call(n).
111+
112+
// the endpoints of a group, and of the groups nested in it
113+
pl_in_group(e, g) :- map_call(e, _), call_receiver_expr("client", e, r), pl_anchor(r, g), map_group_call(g).
114+
pl_in_group(e, g) :- pl_in_group(e, g2), call_receiver_expr("client", g2, r), pl_anchor(r, g), map_group_call(g).
115+
116+
pl_wraps(h, e) :- pl_wrap_site(e, "route"), call_receiver_expr("client", e, r), pl_anchor(r, n), map_call(n, _),
117+
map_handler(n, h).
118+
pl_wraps(h, e) :- pl_wrap_site(e, "route"), call_receiver_expr("client", e, r), pl_anchor(r, g), map_group_call(g),
119+
pl_in_group(n, g), map_handler(n, h).
120+
121+
// "grpc_host": every rpc of every service mapped in the file that registers it
122+
pl_grpc_mapped(mod, gk) :- csite("client", e), call_callee_name("client", e, mn), cs_grpc_map_service(mn),
123+
call_module("client", e, mod),
124+
type_ref_owner("client", tr, e, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
125+
!type_ref_parent("client", tr, _, _, _), type_ref_resolves("client", tr, gk).
126+
pl_wraps(h, e) :- pl_wrap_site(e, "grpc_host"), call_module("client", e, mod), pl_grpc_mapped(mod, gk),
127+
grpc_serves(h, _), method_owner("client", t, h), type_group("client", t, gk).
128+
129+
// the detail names the registration as written: `AddEndpointFilter`, `Interceptors.Add`
130+
pl_wrap_detail(e, mn) :- pl_wrap_site(e, _), pl_site(e, mn, "*").
131+
pl_wrap_detail(e, d) :- pl_wrap_site(e, _), pl_site(e, mn, rm), rm != "*", d = cat(rm, cat(".", mn)).
132+
framework_edge(h, m, "pipeline_wrap", d, "registered") :- pl_wraps(h, e), pl_registers(e, gk), pl_hook(gk, m),
133+
pl_wrap_detail(e, d), h != m.

‎graph/csharp/souffle/decls_all.dl‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,21 @@
142142
.decl cs_amqp_publish(c0:symbol)
143143
.decl cs_attr_suffix(c0:symbol,c1:symbol)
144144
.decl cs_di_resolve(c0:symbol)
145+
.decl cs_ef_context_base(c0:symbol)
146+
.decl cs_ef_interceptor_register(c0:symbol)
147+
.decl cs_ef_save_call(c0:symbol,c1:symbol)
145148
.decl cs_framework_base(c0:symbol)
146149
.decl cs_framework_callback(c0:symbol,c1:symbol,c2:symbol)
147150
.decl cs_framework_callback_prefix(c0:symbol,c1:symbol,c2:symbol)
151+
.decl cs_framework_command_hook(c0:symbol)
148152
.decl cs_framework_namespace_root(c0:symbol)
149153
.decl cs_framework_save_hook(c0:symbol)
150154
.decl cs_framework_type(c0:symbol,c1:symbol)
151155
.decl cs_framework_type_direct(c0:symbol,c1:symbol)
152156
.decl cs_grpc_async_suffix(c0:symbol)
153157
.decl cs_grpc_client_member_not_rpc(c0:symbol)
154158
.decl cs_grpc_client_suffix(c0:symbol)
159+
.decl cs_grpc_map_service(c0:symbol)
155160
.decl cs_grpc_server_suffix(c0:symbol)
156161
.decl cs_heritage_in_source(c0:symbol,c1:symbol)
157162
.decl cs_http_client_factory_method(c0:symbol)
@@ -181,6 +186,8 @@
181186
.decl cs_msg_name_serve(c0:symbol,c1:symbol,c2:number,c3:symbol)
182187
.decl cs_msg_request_client(c0:symbol,c1:symbol)
183188
.decl cs_msg_request_send(c0:symbol)
189+
.decl cs_pipeline_register(c0:symbol,c1:symbol,c2:symbol)
190+
.decl cs_pipeline_wraps(c0:symbol,c1:symbol,c2:symbol)
184191
.decl cs_predefined_alias(c0:symbol)
185192
.decl cs_remote_confidence_binding(c0:symbol)
186193
.decl cs_remote_confidence_exact(c0:symbol)
@@ -325,6 +332,7 @@
325332
.decl field_type(c0:symbol,c1:symbol,c2:symbol)
326333
.decl field_type_name(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
327334
.decl field_type_ref(c0:symbol,c1:symbol,c2:symbol)
335+
.decl framework_edge(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
328336
.decl framework_rooted(c0:symbol)
329337
.decl generated_accessor_read(c0:symbol,c1:symbol,c2:symbol)
330338
.decl generated_property(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -437,8 +445,8 @@
437445
.decl med_send_concrete(c0:symbol,c1:symbol)
438446
.decl med_send_declared(c0:symbol,c1:symbol)
439447
.decl med_send_has_named_type(c0:symbol,c1:symbol)
440-
.decl med_send_named_type(c0:symbol,c1:symbol,c2:symbol)
441448
.decl med_send_matcher(c0:symbol)
449+
.decl med_send_named_type(c0:symbol,c1:symbol,c2:symbol)
442450
.decl med_send_new_ref(c0:symbol,c1:symbol)
443451
.decl med_send_qualified(c0:symbol,c1:symbol)
444452
.decl med_send_sees_new(c0:symbol)
@@ -625,6 +633,26 @@
625633
.decl parse_gap_bytes(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
626634
.decl path_norm(c0:symbol,c1:symbol)
627635
.decl path_raw(c0:symbol)
636+
.decl pl_anchor(c0:symbol,c1:symbol)
637+
.decl pl_async_name(c0:symbol)
638+
.decl pl_context(c0:symbol)
639+
.decl pl_ef_interceptor(c0:symbol)
640+
.decl pl_grpc_mapped(c0:symbol,c1:symbol)
641+
.decl pl_has_own(c0:symbol,c1:symbol)
642+
.decl pl_hook(c0:symbol,c1:symbol)
643+
.decl pl_hook_name(c0:symbol,c1:symbol)
644+
.decl pl_in_group(c0:symbol,c1:symbol)
645+
.decl pl_map_call(c0:symbol)
646+
.decl pl_own_hook(c0:symbol,c1:symbol,c2:symbol)
647+
.decl pl_registers(c0:symbol,c1:symbol)
648+
.decl pl_save_call(c0:symbol,c1:symbol)
649+
.decl pl_save_hook(c0:symbol,c1:symbol)
650+
.decl pl_save_target(c0:symbol,c1:symbol)
651+
.decl pl_site(c0:symbol,c1:symbol,c2:symbol)
652+
.decl pl_up(c0:symbol,c1:symbol)
653+
.decl pl_wrap_detail(c0:symbol,c1:symbol)
654+
.decl pl_wrap_site(c0:symbol,c1:symbol)
655+
.decl pl_wraps(c0:symbol,c1:symbol)
628656
.decl pn(c0:symbol)
629657
.decl pn_count(c0:symbol,c1:number)
630658
.decl pn_has_literal(c0:symbol)

‎graph/csharp/souffle/export_manifest.tsv‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ entry_point entry-point.csv
3838
entry_reachable entry-reachable.csv
3939
type_ancestor_type resolution-type-ancestor-type.csv
4040
override_pair resolution-virtual-override.csv
41+
framework_edge framework-edge.csv
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
registered Shop.DataSetup.<lambda> Shop.AuditInterceptor.SavingChanges
2+
registered Shop.DataSetup.<lambda> Shop.AuditInterceptor.SavingChangesAsync
3+
registered Shop.DataSetup.<lambda> Shop.EventsInterceptor.SavingChangesAsync
4+
registered Shop.DataSetup.AddData Shop.Seeder.ExecuteAsync
5+
registered Shop.OrderEndpoints.MapOrders Shop.IdempotencyFilter.InvokeAsync
6+
registered Shop.OrderEndpoints.MapOrders Shop.NonEmptyFilter.InvokeAsync
7+
registered Shop.OrderEndpoints.MapOrders Shop.TenantFilter.InvokeAsync
8+
registered Shop.OrdersHost.<lambda> Shop.ErrorInterceptor.UnaryServerHandler
9+
registered Shop.OrdersHost.Configure Shop.CorrelationInterceptor.AsyncUnaryCall
10+
save Shop.Committer.Commit Shop.AuditInterceptor.SavingChangesAsync
11+
save Shop.Committer.Commit Shop.EventsInterceptor.SavingChangesAsync
12+
save Shop.OrderStore.Save Shop.AuditInterceptor.SavingChanges
13+
save Shop.OrderStore.SaveAsync Shop.AuditInterceptor.SavingChangesAsync
14+
save Shop.OrderStore.SaveAsync Shop.EventsInterceptor.SavingChangesAsync
15+
wraps Shop.OrderEndpoints.Create Shop.IdempotencyFilter.InvokeAsync AddEndpointFilter
16+
wraps Shop.OrderEndpoints.Create Shop.TenantFilter.InvokeAsync AddEndpointFilter
17+
wraps Shop.OrderEndpoints.Get Shop.NonEmptyFilter.InvokeAsync AddEndpointFilter
18+
wraps Shop.OrderEndpoints.Get Shop.TenantFilter.InvokeAsync AddEndpointFilter
19+
wraps Shop.OrderEndpoints.Lines Shop.TenantFilter.InvokeAsync AddEndpointFilter
20+
wraps Shop.StockService.Check Shop.ErrorInterceptor.UnaryServerHandler Interceptors.Add

0 commit comments

Comments
 (0)