Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

204 lines
8.7 KiB
C

/*
* repro_issue514.c -- Reproduce-first case for OPEN bug #514.
*
* Issue: #514 -- "trace_path data_flow mode doesn't surface arg expressions;
* NestJS DI patterns defeat ~70% of caller resolution"
*
* Sub-claim reproduced: (A) data_flow mode omits argument expressions.
*
* Why sub-claim A over sub-claim B (NestJS DI caller resolution):
* (A) has a crisp binary assertion: the "e" field either appears in the JSON
* output or it does not. (B) is a statistical claim (~70% failure rate) that
* requires a NestJS-specific fixture and a headcount of resolved callers across
* many call sites -- impossible to assert precisely in a unit test. (A) can
* be reproduced with a small two-function Python fixture and one strstr check.
*
* Root cause:
* The MCP schema for trace_path documents data_flow mode as "follow CALLS +
* DATA_FLOWS with arg expressions" (mcp.c line 356-357 and 363-364). Argument
* expressions at each call site ARE stored in the graph: pass_parallel.c::
* append_args_json serializes each CBMCallArg as {"i":<index>,"e":"<expr>,...}
* into the CALLS edge properties_json column. However,
* bfs_to_json_array() (mcp.c ~line 2283) only emits the node fields (name,
* qualified_name, hop, risk, is_test) from cbm_node_hop_t. The edge that
* carried the arg expressions is NOT propagated by cbm_store_bfs() into the
* cbm_traverse_result_t (cbm_edge_info_t carries only from_name, to_name,
* type, confidence -- no properties_json). So even if the user requests
* mode="data_flow", every hop in the response lacks the "args" field and the
* individual arg expression text ("e") is permanently absent from the output.
*
* Expected (correct) behaviour:
* After indexing a two-function Python file where caller() passes a compound
* expression (payload_info + 1) to callee(), a trace_path call with
* mode="data_flow" and direction="outbound" on "caller" must include the
* argument expression text "payload_info" in the response JSON -- either in an
* "args" array inside the hop object, or as a standalone "e" field.
*
* Actual (buggy) behaviour:
* The response is:
* {"function":"caller","direction":"outbound","mode":"data_flow",
* "callees":[{"name":"callee","qualified_name":"...","hop":1}]}
* The hop object contains NO "args" and NO "e"/"arg_expr" field.
* strstr(resp, "payload_info") returns NULL.
*
* Why RED on current code:
* The precondition assertion (CALLS edges >= 1) passes -- edge creation
* and arg serialisation in pass_parallel.c are correct. The final
* ASSERT_NOT_NULL(strstr(resp, "payload_info")) FAILS because
* bfs_to_json_array() never reads or re-emits edge properties_json, so the
* arg expression "payload_info" stored in the CALLS edge is permanently
* discarded before it reaches the MCP JSON output.
*
* Fix location (not implemented here):
* cbm_store_bfs() in src/store/store.c must propagate edge properties_json
* into the cbm_traverse_result_t (extend cbm_edge_info_t or cbm_node_hop_t).
* bfs_to_json_array() in src/mcp/mcp.c must then emit an "args" field when
* mode == "data_flow" and the incoming edge has a non-empty args array.
*/
#include <foundation/compat.h>
#include "test_framework.h"
#include "repro_harness.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/*
* Fixture: two Python functions in one file.
*
* def callee(x):
* return x * 2
*
* def caller():
* result = callee(payload_info + 1)
* return result
*
* caller() passes the compound expression (payload_info + 1) as the first
* positional argument to callee(). The extractor captures this as a CBMCallArg
* with .expr == "payload_info + 1" (or a prefix thereof after sanitization).
* append_args_json serializes it into the CALLS edge as:
* {"args":[{"i":0,"e":"payload_info + 1"}]}
*
* The expression token "payload_info" is unique enough to identify in the
* output: strstr(resp, "payload_info") is the assertion anchor.
*
* Python is used here because its CALLS extraction (including arg expressions)
* is proven reliable -- see repro_issue480.c for the same fixture approach.
*/
static const RFile k_files[] = {
{
"service.py",
"def callee(x):\n"
" return x * 2\n"
"\n"
"def caller():\n"
" result = callee(payload_info + 1)\n"
" return result\n"
}
};
/*
* TEST: repro_issue514_data_flow_surfaces_arg_expr
*
* Precondition (must be GREEN to prove this is a data_flow surfacing bug):
* rh_count_edges(store, project, "CALLS") >= 1
* If this fires RED, the extractor has a regression unrelated to #514.
*
* Failing assertion (RED on current code):
* strstr(resp, "payload_info") != NULL
* i.e. the argument expression text must appear somewhere in the response.
*/
TEST(repro_issue514_data_flow_surfaces_arg_expr) {
RProj lp;
cbm_store_t *store = rh_index_files(&lp, k_files,
(int)(sizeof(k_files) / sizeof(k_files[0])));
ASSERT_NOT_NULL(store);
/*
* Precondition: at least one CALLS edge must exist after indexing.
* If this fires RED the fixture is broken, not data_flow mode.
* The caller() -> callee(payload_info + 1) call must produce one edge.
*/
int calls_count = rh_count_edges(store, lp.project, "CALLS");
fprintf(stderr,
" [514] CALLS edges=%d (expected>=1; 0=extraction regression)\n",
calls_count);
ASSERT_GT(calls_count, 0);
/*
* Invoke trace_path with mode="data_flow", direction="outbound" on "caller".
*
* Args (matching the trace_path JSON schema in mcp.c ~line 355-374):
* function_name -- "caller": the function that passes the argument
* project -- lp.project: derived from the temp dir
* direction -- "outbound": follow callees (caller -> callee)
* depth -- 2: one hop is enough
* mode -- "data_flow": the mode that promises arg expressions
*
* Expected response (correct):
* {"function":"caller","direction":"outbound","mode":"data_flow",
* "callees":[{"name":"callee","qualified_name":"...","hop":1,
* "args":[{"i":0,"e":"payload_info + 1"}]}]}
* -- or any JSON structure that includes the string "payload_info".
*
* Buggy response:
* {"function":"caller","direction":"outbound","mode":"data_flow",
* "callees":[{"name":"callee","qualified_name":"...","hop":1}]}
* -- no "args", no "e", no "payload_info" anywhere.
*/
char args[512];
snprintf(args, sizeof(args),
"{\"function_name\":\"caller\","
"\"project\":\"%s\","
"\"direction\":\"outbound\","
"\"depth\":2,"
"\"mode\":\"data_flow\"}",
lp.project);
char *resp = cbm_mcp_handle_tool(lp.srv, "trace_path", args);
ASSERT_NOT_NULL(resp);
fprintf(stderr, " [514] trace_path data_flow response: %.400s\n", resp);
/* The response must not be an error -- the node must be found. */
ASSERT_NULL(strstr(resp, "function not found"));
/* The response is the MCP tool-result envelope (inner json embedded as an
* escaped string value), so the "callees" key appears as \"callees\".
* Match the escaped form (see repro_issue480 / test_incremental's
* resp_has_key idiom). */
ASSERT_NOT_NULL(strstr(resp, "\\\"callees\\\""));
/* The callees array must be non-empty: the callee's QN tail "service.callee"
* must appear as a hop (unambiguous + escaping-proof). RED if the CALLS
* traversal is broken (separate from #514). */
ASSERT_NULL(strstr(resp, "\\\"callees\\\":[]"));
ASSERT_NOT_NULL(strstr(resp, "service.callee"));
/*
* THE CORE ASSERTION FOR BUG #514:
*
* The argument expression "payload_info" (part of "payload_info + 1" passed
* to callee()) must appear in the response JSON when mode="data_flow".
*
* WHY RED on current code:
* bfs_to_json_array() (mcp.c ~line 2283) only emits cbm_node_hop_t fields
* (name, qualified_name, hop). cbm_edge_info_t (store.h ~line 146) does
* not carry properties_json, so the "e":"payload_info + 1" stored in the
* CALLS edge never reaches the JSON output. strstr returns NULL.
*
* This assertion is the canonical RED line for bug #514.
*/
ASSERT_NOT_NULL(strstr(resp, "payload_info"));
free(resp);
rh_cleanup(&lp, store);
PASS();
}
/* ── Suite ─────────────────────────────────────────────────────────────────── */
SUITE(repro_issue514) {
RUN_TEST(repro_issue514_data_flow_surfaces_arg_expr);
}