Escape sliced source text in edge properties JSON
Edge properties interpolated raw source-text slices into JSON via snprintf without escaping: decorator text (quotes + raw newlines, e.g. @register.tag("block") or multi-line @override_settings) in DECORATES props - django produced 3826 malformed edges - plus usage ref_name and route-registration callee/url in USAGE/CALLS props. Malformed edge JSON aborts every json_extract consumer, including the url_path_gen generated-column evaluation that runs during PRAGMA integrity_check. All such sites now route through cbm_json_escape (DECORATES twins in pass_parallel.c/pass_semantic.c, usage emit twins, route-registration calls). Regression: pipeline_edge_props_valid_json (register.tag fixture; real-repo red recorded on the django index).
This commit is contained in:
@@ -193,10 +193,14 @@ static void handle_route_registration(cbm_pipeline_ctx_t *ctx, const CBMCall *ca
|
||||
snprintf(route_props, sizeof(route_props), "{\"method\":\"%s\"}", method ? method : "ANY");
|
||||
int64_t route_id = cbm_gbuf_upsert_node(ctx->gbuf, "Route", call->first_string_arg, route_qn,
|
||||
"", 0, 0, route_props);
|
||||
char esc_cn[CBM_SZ_256]; /* sliced source text: escape quotes/newlines */
|
||||
char esc_fa[CBM_SZ_256];
|
||||
cbm_json_escape(esc_cn, sizeof(esc_cn), call->callee_name);
|
||||
cbm_json_escape(esc_fa, sizeof(esc_fa), call->first_string_arg);
|
||||
char props[CBM_SZ_512];
|
||||
snprintf(props, sizeof(props),
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"route_registration\"}",
|
||||
call->callee_name, call->first_string_arg);
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"route_registration\"}", esc_cn,
|
||||
esc_fa);
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, route_id, "CALLS", props);
|
||||
if (call->second_arg_name != NULL && call->second_arg_name[0] != '\0') {
|
||||
cbm_resolution_t hres = cbm_registry_resolve(ctx->registry, call->second_arg_name,
|
||||
|
||||
@@ -1858,7 +1858,9 @@ static void resolve_file_usages(resolve_ctx_t *rc, resolve_worker_state_t *ws,
|
||||
continue;
|
||||
}
|
||||
char uprops[CBM_SZ_256];
|
||||
snprintf(uprops, sizeof(uprops), "{\"callee\":\"%s\"}", usage->ref_name);
|
||||
char esc_ref[CBM_SZ_256]; /* sliced source text: escape quotes/newlines */
|
||||
cbm_json_escape(esc_ref, sizeof(esc_ref), usage->ref_name);
|
||||
snprintf(uprops, sizeof(uprops), "{\"callee\":\"%s\"}", esc_ref);
|
||||
cbm_gbuf_insert_edge(ws->local_edge_buf, src->id, tgt->id, "USAGE", uprops);
|
||||
ws->usages_resolved++;
|
||||
}
|
||||
@@ -1984,8 +1986,14 @@ static void resolve_def_decorators(resolve_ctx_t *rc, resolve_worker_state_t *ws
|
||||
cbm_gbuf_upsert_node(ws->local_edge_buf, "Decorator", fn, syn_qn, "", 0, 0, "{}");
|
||||
}
|
||||
if (dn_id != 0 && node->id != dn_id) {
|
||||
char dp[CBM_SZ_256];
|
||||
snprintf(dp, sizeof(dp), "{\"decorator\":\"%s\"}", def->decorators[dc]);
|
||||
/* Decorator SOURCE TEXT can contain quotes and raw newlines
|
||||
* (e.g. @register.tag("block"), multi-line @override_settings) —
|
||||
* interpolating it raw produced malformed properties JSON that
|
||||
* aborts every json_extract consumer (django: 3826 such edges). */
|
||||
char esc_dec[CBM_SZ_256];
|
||||
cbm_json_escape(esc_dec, sizeof(esc_dec), def->decorators[dc]);
|
||||
char dp[CBM_SZ_512];
|
||||
snprintf(dp, sizeof(dp), "{\"decorator\":\"%s\"}", esc_dec);
|
||||
cbm_gbuf_insert_edge(ws->local_edge_buf, node->id, dn_id, "DECORATES", dp);
|
||||
/* Ensure a reference-style edge exists so the decorator appears in queries
|
||||
* without being misclassified as a real call by downstream passes. */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* Depends on: pass_definitions having populated the registry and graph buffer
|
||||
*/
|
||||
#include "foundation/constants.h"
|
||||
#include "foundation/str_util.h" // cbm_json_escape
|
||||
#include "pipeline/pipeline.h"
|
||||
#include <stdint.h>
|
||||
#include "pipeline/pipeline_internal.h"
|
||||
@@ -398,8 +399,13 @@ static void resolve_decorator(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *no
|
||||
}
|
||||
}
|
||||
if (dec && node->id != dec->id) {
|
||||
char props[CBM_SZ_256];
|
||||
snprintf(props, sizeof(props), "{\"decorator\":\"%s\"}", decorator);
|
||||
/* Decorator source text can contain quotes and raw newlines — escape
|
||||
* it or the edge properties JSON is malformed (twin of the parallel
|
||||
* path in pass_parallel.c). */
|
||||
char esc_dec[CBM_SZ_256];
|
||||
cbm_json_escape(esc_dec, sizeof(esc_dec), decorator);
|
||||
char props[CBM_SZ_512];
|
||||
snprintf(props, sizeof(props), "{\"decorator\":\"%s\"}", esc_dec);
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, node->id, dec->id, "DECORATES", props);
|
||||
/* Ensure a reference edge exists so the decorator appears in usage queries
|
||||
* without being misclassified as a real call by downstream passes. */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* Depends on: pass_definitions having populated the registry and graph buffer
|
||||
*/
|
||||
#include "foundation/constants.h"
|
||||
#include "foundation/str_util.h" // cbm_json_escape
|
||||
#include "pipeline/pipeline.h"
|
||||
#include "pipeline/pipeline_internal.h"
|
||||
#include "graph_buffer/graph_buffer.h"
|
||||
@@ -223,8 +224,12 @@ static int resolve_usage_edges(cbm_pipeline_ctx_t *ctx, const CBMFileResult *res
|
||||
continue;
|
||||
}
|
||||
|
||||
char uprops[CBM_SZ_256];
|
||||
snprintf(uprops, sizeof(uprops), "{\"callee\":\"%s\"}", usage->ref_name);
|
||||
/* ref_name is sliced source text and can contain quotes/newlines —
|
||||
* escape it or the edge properties JSON is malformed. */
|
||||
char esc_ref[CBM_SZ_256];
|
||||
cbm_json_escape(esc_ref, sizeof(esc_ref), usage->ref_name);
|
||||
char uprops[CBM_SZ_512];
|
||||
snprintf(uprops, sizeof(uprops), "{\"callee\":\"%s\"}", esc_ref);
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, src->id, tgt->id, "USAGE", uprops);
|
||||
resolved++;
|
||||
}
|
||||
|
||||
@@ -565,6 +565,68 @@ TEST(pipeline_def_props_valid_json_when_oversized) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Edge properties must be VALID JSON. Decorator source text (quotes, raw
|
||||
* newlines: @register.tag("block"), multi-line @override_settings) was
|
||||
* interpolated raw into the DECORATES properties — django produced 3826
|
||||
* malformed edges, and any json_extract-based consumer (including the
|
||||
* url_path_gen generated-column evaluation during PRAGMA integrity_check)
|
||||
* aborts on them. Usage/call emit sites had the same hole for sliced source
|
||||
* text. */
|
||||
TEST(pipeline_edge_props_valid_json) {
|
||||
if (setup_test_repo() != 0) {
|
||||
FAIL("failed to create temp dir");
|
||||
}
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/deco.py", g_tmpdir);
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) {
|
||||
teardown_test_repo();
|
||||
FAIL("failed to write deco.py");
|
||||
}
|
||||
fprintf(f, "from x import register\n"
|
||||
"@register.tag(\"block\")\n"
|
||||
"def do_block(parser):\n"
|
||||
" return parser\n"
|
||||
"@register.tag(\"extends\")\n"
|
||||
"def do_extends(parser):\n"
|
||||
" return parser\n");
|
||||
fclose(f);
|
||||
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/test_edge_props.db", g_tmpdir);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(g_tmpdir, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
|
||||
cbm_edge_t *edges = NULL;
|
||||
int edge_count = 0;
|
||||
ASSERT_EQ(cbm_store_find_edges_by_type(s, project, "DECORATES", &edges, &edge_count),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_GT(edge_count, 0); /* the decorators must produce DECORATES edges */
|
||||
for (int i = 0; i < edge_count; i++) {
|
||||
const char *pj = edges[i].properties_json;
|
||||
if (!pj) {
|
||||
continue;
|
||||
}
|
||||
yyjson_doc *doc = yyjson_read(pj, strlen(pj), 0);
|
||||
if (!doc) {
|
||||
printf(" INVALID edge properties JSON: %.80s\n", pj);
|
||||
}
|
||||
ASSERT_NOT_NULL(doc);
|
||||
yyjson_doc_free(doc);
|
||||
}
|
||||
|
||||
cbm_store_free_edges(edges, edge_count);
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
teardown_test_repo();
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Calls pass tests ──────────────────────────────────────────── */
|
||||
|
||||
TEST(pipeline_calls_resolution) {
|
||||
@@ -5630,6 +5692,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_definitions_defines_edges);
|
||||
RUN_TEST(pipeline_definitions_properties);
|
||||
RUN_TEST(pipeline_def_props_valid_json_when_oversized);
|
||||
RUN_TEST(pipeline_edge_props_valid_json);
|
||||
/* Complexity propagation pass (Tier B) */
|
||||
RUN_TEST(pipeline_complexity_transitive_loop_depth);
|
||||
/* Calls pass */
|
||||
|
||||
Reference in New Issue
Block a user