Escape control bytes in properties-JSON array items (raw newlines from multi-line C params)

The array appenders (param_types/param_names/decorators/base_classes) in both props-builder twins escaped only quote and backslash, so items sliced from multi-line C parameter declarations carried raw newline/tab bytes — invalid inside JSON strings (the remaining 118 malformed kernel rows after the truncation fix; e.g. param_types:["struct\n\t\t..."]). Array items now go through the full escape function, and both escapers degrade any other raw control byte (e.g. form feed) to a space. Reproduced via the prod CLI on a multi-line-param fixture (nl_fn json_valid=0 pre-fix); the sweep regression test gained that fixture (61st case).
This commit is contained in:
Martin Vogel
2026-06-09 23:54:25 +02:00
parent 9f465e5109
commit 359f37cd71
3 changed files with 32 additions and 17 deletions
+7 -8
View File
@@ -103,7 +103,9 @@ static int def_json_escape_char(char *buf, size_t avail, char ch) {
break;
default:
if (avail >= SKIP_ONE) {
buf[0] = ch;
/* Any other raw control byte (e.g. form feed) is invalid inside a
* JSON string — degrade to a space. */
buf[0] = ((unsigned char)ch < 0x20) ? ' ' : ch;
}
return SKIP_ONE;
}
@@ -194,14 +196,11 @@ static void append_json_str_array(char *buf, size_t bufsize, size_t *pos, const
if (p < bufsize - SKIP_ONE) {
buf[p++] = '"';
}
/* Full escaping (not just quote/backslash): items like C param types
* sliced from multi-line declarations carry raw \n/\t bytes, which are
* invalid inside JSON strings. */
for (const char *s = arr[i]; *s && p < bufsize - PD_ESC_SPACE; s++) {
if (*s == '"' || *s == '\\') {
buf[p++] = '\\';
if (p >= bufsize - PD_ESC_SPACE) {
break;
}
}
buf[p++] = *s;
p += (size_t)def_json_escape_char(buf + p, bufsize - p - PD_ESC_SPACE, *s);
}
if (p < bufsize - SKIP_ONE) {
buf[p++] = '"';
+7 -8
View File
@@ -145,7 +145,9 @@ static int json_escape_char(char *buf, size_t avail, char ch) {
break;
default:
if (avail >= SKIP_ONE) {
buf[0] = ch;
/* Any other raw control byte (e.g. form feed) is invalid inside a
* JSON string — degrade to a space. */
buf[0] = ((unsigned char)ch < 0x20) ? ' ' : ch;
}
return SKIP_ONE;
}
@@ -237,14 +239,11 @@ static void append_json_str_array(char *buf, size_t bufsize, size_t *pos, const
if (p < bufsize - SKIP_ONE) {
buf[p++] = '"';
}
/* Full escaping (not just quote/backslash): items like C param types
* sliced from multi-line declarations carry raw \n/\t bytes, which are
* invalid inside JSON strings. */
for (const char *s = arr[i]; *s && p < bufsize - PP_ESC_SPACE; s++) {
if (*s == '"' || *s == '\\') {
buf[p++] = '\\';
if (p >= bufsize - PP_ESC_SPACE) {
break;
}
}
buf[p++] = *s;
p += (size_t)json_escape_char(buf + p, bufsize - p - PP_ESC_SPACE, *s);
}
if (p < bufsize - SKIP_ONE) {
buf[p++] = '"';
+18 -1
View File
@@ -506,6 +506,23 @@ TEST(pipeline_def_props_valid_json_when_oversized) {
fclose(f);
}
/* Multi-line parameter declarations: param_types items then carry raw
* newline/tab bytes from the source slice. The array appender's inline
* escape loop only handled quote/backslash, so the raw control bytes made
* the JSON invalid (118 Linux-kernel rows of this shape). */
{
char path[512];
snprintf(path, sizeof(path), "%s/sweep_nl.c", g_tmpdir);
FILE *f = fopen(path, "w");
if (!f) {
teardown_test_repo();
FAIL("failed to write sweep_nl.c");
}
fprintf(f, "int sweep_fn_nl(struct\n\t\t\t\treally_long_struct_name *a,\n"
" enum\n\tweird_enum b) { return 0; }\n");
fclose(f);
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/test_huge_props.db", g_tmpdir);
cbm_pipeline_t *p = cbm_pipeline_new(g_tmpdir, db_path, CBM_MODE_FULL);
@@ -539,7 +556,7 @@ TEST(pipeline_def_props_valid_json_when_oversized) {
yyjson_doc_free(doc);
checked++;
}
ASSERT_EQ(checked, 60); /* all sweep functions present */
ASSERT_EQ(checked, 61); /* all sweep functions present (60 sizes + nl case) */
cbm_store_free_nodes(funcs, func_count);
cbm_store_close(s);