Skip to content

Receiver Traversal

JavaScript and TypeScript queries can expose Bifrost’s bounded, demand-driven receiver facts. The three terminal steps preserve uncertainty explicitly:

  • points_to describes the value denoted by an expression.
  • receiver_targets describes the possible receiver values at a call or member access.
  • member_targets returns exact indexed member declarations selected through those receiver values.

Every analyzed input produces a receiver_analysis row. Read its outcome before using its candidates: precise, ambiguous, unknown, unsupported, and exceeded_budget are distinct states. This is not whole-program points-to, general alias analysis, path-sensitive control flow, taint, or data-flow analysis.

Last verified end to end: 2026-07-15 (query_code schema version 2).

All examples on this page execute against this file.

class Service {
run() {}
}
class Other {
run() {}
}
function makeService() {
return new Service();
}
function consume(value: Service) {
value.run();
}
export function caller(flag: boolean) {
const direct = new Service();
direct.run();
const factory = makeService();
factory.run();
const ambiguous = flag ? new Service() : new Other();
ambiguous.run();
consume(new Service());
}

capture on a receiver step is valid only when the preceding domain is a structural match, and the name must identify a positive capture in the pattern. Every unique range bound to that capture is analyzed.

(points-to :capture receiver
(language typescript
(call :callee "run"
:receiver (identifier :name "direct" :capture "receiver"))))
{"languages":["typescript"],"match":{"kind":"call","callee":{"name":"run"},"receiver":{"kind":"identifier","name":"direct","capture":"receiver"}},"steps":[{"op":"points_to","capture":"receiver"}]}
{"results":[{"analysis_kind":"points_to","capture":"receiver","input_kind":"identifier","language":"typescript","outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":19,"kind":"call","path":"receiver.ts","result_type":"structural_match","start_line":19},"steps":[{"op":"points_to","result":{"analysis_kind":"points_to","capture":"receiver","outcome":"precise","path":"receiver.ts","range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis"}}]}],"range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis","text":"direct","values":[{"allocation_site":{"path":"receiver.ts","range":{"end_column":31,"end_line":18,"start_column":18,"start_line":18}},"receiver_value_kind":"allocation_site","type_declaration":{"end_line":3,"fq_name":"Service","kind":"class","language":"typescript","path":"receiver.ts","signature":"class Service {","start_line":1}}]}],"truncated":false}

A factory result retains both the exact factory declaration and the nested value it returned. Here that nested value terminates at the exact Service allocation site.

(points-to :capture receiver
(language typescript
(call :callee "run"
:receiver (identifier :name "factory" :capture "receiver"))))
{"languages":["typescript"],"match":{"kind":"call","callee":{"name":"run"},"receiver":{"kind":"identifier","name":"factory","capture":"receiver"}},"steps":[{"op":"points_to","capture":"receiver"}]}
{"results":[{"analysis_kind":"points_to","capture":"receiver","input_kind":"identifier","language":"typescript","outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":22,"kind":"call","path":"receiver.ts","result_type":"structural_match","start_line":22},"steps":[{"op":"points_to","result":{"analysis_kind":"points_to","capture":"receiver","outcome":"precise","path":"receiver.ts","range":{"end_column":10,"end_line":22,"start_column":3,"start_line":22},"result_type":"receiver_analysis"}}]}],"range":{"end_column":10,"end_line":22,"start_column":3,"start_line":22},"result_type":"receiver_analysis","text":"factory","values":[{"factory":{"end_line":11,"fq_name":"makeService","kind":"function","language":"typescript","path":"receiver.ts","signature":"function makeService() { ... }","start_line":9},"receiver_value_kind":"factory_return","returned_value":{"allocation_site":{"path":"receiver.ts","range":{"end_column":23,"end_line":10,"start_column":10,"start_line":10}},"receiver_value_kind":"allocation_site","type_declaration":{"end_line":3,"fq_name":"Service","kind":"class","language":"typescript","path":"receiver.ts","signature":"class Service {","start_line":1}}}]}],"truncated":false}

Exact Member Target, Not Same-Name Guessing

Section titled “Exact Member Target, Not Same-Name Guessing”

Both classes declare run, but the direct receiver is a Service. member_targets returns only that owner’s declaration; it never falls back to an unrelated same-name member.

(member-targets
(language typescript
(call :callee "run" :receiver "direct")))
{"languages":["typescript"],"match":{"kind":"call","callee":{"name":"run"},"receiver":{"name":"direct"}},"steps":[{"op":"member_targets"}]}
{"results":[{"analysis_kind":"member_targets","input_kind":"receiver","language":"typescript","member_targets":[{"end_line":2,"fq_name":"Service.run","kind":"function","language":"typescript","path":"receiver.ts","signature":"run() { ... }","start_line":2}],"outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":19,"kind":"call","path":"receiver.ts","result_type":"structural_match","start_line":19},"steps":[{"op":"member_targets","result":{"analysis_kind":"member_targets","outcome":"precise","path":"receiver.ts","range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis"}}]}],"range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis","text":"direct"}],"truncated":false}

The conditional initializer has two bounded candidates. The row remains ambiguous and retains both allocation/type candidates; neither is silently upgraded to precise.

(receiver-targets
(language typescript
(call :callee "run" :receiver "ambiguous")))
{"languages":["typescript"],"match":{"kind":"call","callee":{"name":"run"},"receiver":{"name":"ambiguous"}},"steps":[{"op":"receiver_targets"}]}
{"results":[{"analysis_kind":"receiver_targets","input_kind":"identifier","language":"typescript","outcome":"ambiguous","path":"receiver.ts","provenance":[{"seed":{"end_line":25,"kind":"call","path":"receiver.ts","result_type":"structural_match","start_line":25},"steps":[{"op":"receiver_targets","result":{"analysis_kind":"receiver_targets","outcome":"ambiguous","path":"receiver.ts","range":{"end_column":12,"end_line":25,"start_column":3,"start_line":25},"result_type":"receiver_analysis"}}]}],"range":{"end_column":12,"end_line":25,"start_column":3,"start_line":25},"result_type":"receiver_analysis","text":"ambiguous","values":[{"allocation_site":{"path":"receiver.ts","range":{"end_column":41,"end_line":24,"start_column":28,"start_line":24}},"receiver_value_kind":"allocation_site","type_declaration":{"end_line":3,"fq_name":"Service","kind":"class","language":"typescript","path":"receiver.ts","signature":"class Service {","start_line":1}},{"allocation_site":{"path":"receiver.ts","range":{"end_column":55,"end_line":24,"start_column":44,"start_line":24}},"receiver_value_kind":"allocation_site","type_declaration":{"end_line":7,"fq_name":"Other","kind":"class","language":"typescript","path":"receiver.ts","signature":"class Other {","start_line":5}}]}],"truncated":false}

references_of produces exact reference-site rows. member_targets can consume them and reuses the same receiver-qualified member resolution used by definition and usage analysis.

(member-targets
(references-of :proof proven
(enclosing-decl
(language typescript
(inside (class :name "Service") (method :name "run"))))))
{"languages":["typescript"],"match":{"kind":"method","name":"run"},"inside":{"kind":"class","name":"Service"},"steps":[{"op":"enclosing_decl"},{"op":"references_of","proof":"proven"},{"op":"member_targets"}]}
{"results":[{"analysis_kind":"member_targets","input_kind":"receiver","language":"typescript","member_targets":[{"end_line":2,"fq_name":"Service.run","kind":"function","language":"typescript","path":"receiver.ts","signature":"run() { ... }","start_line":2}],"outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":2,"kind":"method","path":"receiver.ts","result_type":"structural_match","start_line":2},"steps":[{"op":"enclosing_decl","result":{"end_line":2,"fq_name":"Service.run","kind":"function","path":"receiver.ts","result_type":"declaration","start_line":2}},{"op":"references_of","result":{"path":"receiver.ts","proof":"proven","range":{"end_column":12,"end_line":14,"start_column":9,"start_line":14},"reference_kind":"method_call","result_type":"reference_site","target_fq_name":"Service.run"}},{"op":"member_targets","result":{"analysis_kind":"member_targets","outcome":"precise","path":"receiver.ts","range":{"end_column":8,"end_line":14,"start_column":3,"start_line":14},"result_type":"receiver_analysis"}}]}],"range":{"end_column":8,"end_line":14,"start_column":3,"start_line":14},"result_type":"receiver_analysis","text":"value"},{"analysis_kind":"member_targets","input_kind":"receiver","language":"typescript","member_targets":[{"end_line":2,"fq_name":"Service.run","kind":"function","language":"typescript","path":"receiver.ts","signature":"run() { ... }","start_line":2}],"outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":2,"kind":"method","path":"receiver.ts","result_type":"structural_match","start_line":2},"steps":[{"op":"enclosing_decl","result":{"end_line":2,"fq_name":"Service.run","kind":"function","path":"receiver.ts","result_type":"declaration","start_line":2}},{"op":"references_of","result":{"path":"receiver.ts","proof":"proven","range":{"end_column":13,"end_line":19,"start_column":10,"start_line":19},"reference_kind":"method_call","result_type":"reference_site","target_fq_name":"Service.run"}},{"op":"member_targets","result":{"analysis_kind":"member_targets","outcome":"precise","path":"receiver.ts","range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis"}}]}],"range":{"end_column":9,"end_line":19,"start_column":3,"start_line":19},"result_type":"receiver_analysis","text":"direct"},{"analysis_kind":"member_targets","input_kind":"receiver","language":"typescript","member_targets":[{"end_line":2,"fq_name":"Service.run","kind":"function","language":"typescript","path":"receiver.ts","signature":"run() { ... }","start_line":2}],"outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":2,"kind":"method","path":"receiver.ts","result_type":"structural_match","start_line":2},"steps":[{"op":"enclosing_decl","result":{"end_line":2,"fq_name":"Service.run","kind":"function","path":"receiver.ts","result_type":"declaration","start_line":2}},{"op":"references_of","result":{"path":"receiver.ts","proof":"proven","range":{"end_column":14,"end_line":22,"start_column":11,"start_line":22},"reference_kind":"method_call","result_type":"reference_site","target_fq_name":"Service.run"}},{"op":"member_targets","result":{"analysis_kind":"member_targets","outcome":"precise","path":"receiver.ts","range":{"end_column":10,"end_line":22,"start_column":3,"start_line":22},"result_type":"receiver_analysis"}}]}],"range":{"end_column":10,"end_line":22,"start_column":3,"start_line":22},"result_type":"receiver_analysis","text":"factory"}],"truncated":false}

call_input preserves the exact expression written for a resolved formal parameter. points_to can analyze that expression without pretending it followed assignments or general interprocedural data flow.

(points-to
(call-input :parameter-index 0
(call-sites-to :proof proven
(enclosing-decl
(language typescript (function :name "consume"))))))
{"languages":["typescript"],"match":{"kind":"function","name":"consume"},"steps":[{"op":"enclosing_decl"},{"op":"call_sites_to","proof":"proven"},{"op":"call_input","parameter_index":0},{"op":"points_to"}]}
{"results":[{"analysis_kind":"points_to","input_kind":"new_expression","language":"typescript","outcome":"precise","path":"receiver.ts","provenance":[{"seed":{"end_line":15,"kind":"function","path":"receiver.ts","result_type":"structural_match","start_line":13},"steps":[{"op":"enclosing_decl","result":{"end_line":15,"fq_name":"consume","kind":"function","path":"receiver.ts","result_type":"declaration","start_line":13}},{"op":"call_sites_to","result":{"callee_fq_name":"consume","caller_fq_name":"caller","path":"receiver.ts","proof":"proven","range":{"end_column":25,"end_line":27,"start_column":3,"start_line":27},"result_type":"call_site"}},{"op":"call_input","result":{"input_kind":"parameter","parameter_index":0,"parameter_name":"value","path":"receiver.ts","range":{"end_column":24,"end_line":27,"start_column":11,"start_line":27},"result_type":"expression_site"}},{"op":"points_to","result":{"analysis_kind":"points_to","outcome":"precise","path":"receiver.ts","range":{"end_column":24,"end_line":27,"start_column":11,"start_line":27},"result_type":"receiver_analysis"}}]}],"range":{"end_column":24,"end_line":27,"start_column":11,"start_line":27},"result_type":"receiver_analysis","text":"new Service()","values":[{"allocation_site":{"path":"receiver.ts","range":{"end_column":24,"end_line":27,"start_column":11,"start_line":27}},"receiver_value_kind":"allocation_site","type_declaration":{"end_line":3,"fq_name":"Service","kind":"class","language":"typescript","path":"receiver.ts","signature":"class Service {","start_line":1}}]}],"truncated":false}

Only JavaScript and TypeScript currently have a precise receiver provider. Other languages return receiver_analysis rows with outcome: "unsupported" plus aggregated capability diagnostics; they do not masquerade as zero matches.

Candidate-cap truncation and receiver budget exits set top-level truncated, identify the exhausted limit, and emit a diagnostic. Ordinary bounded ambiguity does not set truncated. For a completeness-sensitive decision, require truncated: false, inspect every outcome, and check provenance_truncated as described in Agent Result Safety.