Files
ligent/src/json.c
T
2026-10-04 13:50:33 +08:00

959 lines
30 KiB
C

/* json.c - 手写递归下降 JSON 解析器 + 构造器 + 序列化器 */
#include "json.h"
#include "i18n.h"
#include "util.h"
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define JSON_MAX_DEPTH 200
/* ------------------------------------------------------------------ */
/* 构造 */
/* ------------------------------------------------------------------ */
static JsonValue *jv_new(JsonType t) {
JsonValue *v = xcalloc(1, sizeof(JsonValue));
v->type = t;
return v;
}
JsonValue *json_new_null(void) { return jv_new(JSON_NULL); }
JsonValue *json_new_array(void) { return jv_new(JSON_ARRAY); }
JsonValue *json_new_object(void) { return jv_new(JSON_OBJECT); }
JsonValue *json_new_bool(int b) {
JsonValue *v = jv_new(JSON_BOOL);
v->u.boolean = b ? 1 : 0;
return v;
}
JsonValue *json_new_number(double n) {
JsonValue *v = jv_new(JSON_NUMBER);
v->u.number = n;
return v;
}
JsonValue *json_new_string(const char *s) {
JsonValue *v = jv_new(JSON_STRING);
v->u.string = xstrdup(s ? s : "");
return v;
}
void json_array_insert(JsonValue *arr, size_t i, JsonValue *item) {
if (!arr || arr->type != JSON_ARRAY) {
json_free(item);
return;
}
if (i > arr->u.array.count)
i = arr->u.array.count;
if (arr->u.array.count == arr->u.array.cap) {
arr->u.array.cap = arr->u.array.cap ? arr->u.array.cap * 2 : 8;
arr->u.array.items = xrealloc(arr->u.array.items,
arr->u.array.cap * sizeof(JsonValue *));
}
memmove(arr->u.array.items + i + 1, arr->u.array.items + i,
(arr->u.array.count - i) * sizeof(JsonValue *));
arr->u.array.items[i] = item;
arr->u.array.count++;
}
void json_array_steal(JsonValue *dst, JsonValue *src) {
if (!dst || dst->type != JSON_ARRAY || !src || src->type != JSON_ARRAY)
return;
if (dst == src)
return;
for (size_t i = 0; i < src->u.array.count; i++) {
if (dst->u.array.count == dst->u.array.cap) {
dst->u.array.cap = dst->u.array.cap ? dst->u.array.cap * 2 : 8;
dst->u.array.items = xrealloc(
dst->u.array.items, dst->u.array.cap * sizeof(JsonValue *));
}
dst->u.array.items[dst->u.array.count++] = src->u.array.items[i];
}
free(src->u.array.items);
src->u.array.items = NULL;
src->u.array.count = 0;
src->u.array.cap = 0;
}
void json_array_remove(JsonValue *arr, size_t i) {
if (!arr || arr->type != JSON_ARRAY || i >= arr->u.array.count)
return;
json_free(arr->u.array.items[i]);
memmove(arr->u.array.items + i, arr->u.array.items + i + 1,
(arr->u.array.count - i - 1) * sizeof(JsonValue *));
arr->u.array.count--;
}
void json_array_push(JsonValue *arr, JsonValue *item) {
if (!arr || arr->type != JSON_ARRAY) {
json_free(item);
return;
}
if (arr->u.array.count == arr->u.array.cap) {
arr->u.array.cap = arr->u.array.cap ? arr->u.array.cap * 2 : 8;
arr->u.array.items = xrealloc(arr->u.array.items,
arr->u.array.cap * sizeof(JsonValue *));
}
arr->u.array.items[arr->u.array.count++] = item;
}
void json_object_remove(JsonValue *obj, const char *key) {
if (!obj || obj->type != JSON_OBJECT)
return;
for (size_t i = 0; i < obj->u.object.count; i++) {
if (strcmp(obj->u.object.members[i].key, key) == 0) {
free(obj->u.object.members[i].key);
json_free(obj->u.object.members[i].value);
memmove(&obj->u.object.members[i], &obj->u.object.members[i + 1],
(obj->u.object.count - i - 1) * sizeof(JsonMember));
obj->u.object.count--;
return;
}
}
}
void json_object_set(JsonValue *obj, const char *key, JsonValue *value) {
if (!obj || obj->type != JSON_OBJECT) {
json_free(value);
return;
}
for (size_t i = 0; i < obj->u.object.count; i++) {
if (strcmp(obj->u.object.members[i].key, key) == 0) {
json_free(obj->u.object.members[i].value);
obj->u.object.members[i].value = value;
return;
}
}
if (obj->u.object.count == obj->u.object.cap) {
obj->u.object.cap = obj->u.object.cap ? obj->u.object.cap * 2 : 8;
obj->u.object.members = xrealloc(
obj->u.object.members, obj->u.object.cap * sizeof(JsonMember));
}
obj->u.object.members[obj->u.object.count].key = xstrdup(key);
obj->u.object.members[obj->u.object.count].value = value;
obj->u.object.count++;
}
void json_object_set_string(JsonValue *obj, const char *key,
const char *value) {
json_object_set(obj, key, value ? json_new_string(value) : json_new_null());
}
void json_object_set_number(JsonValue *obj, const char *key, double value) {
json_object_set(obj, key, json_new_number(value));
}
void json_object_set_bool(JsonValue *obj, const char *key, int value) {
json_object_set(obj, key, json_new_bool(value));
}
void json_object_set_null(JsonValue *obj, const char *key) {
json_object_set(obj, key, json_new_null());
}
/* ------------------------------------------------------------------ */
/* 释放 */
/* ------------------------------------------------------------------ */
void json_free(JsonValue *v) {
if (!v)
return;
switch (v->type) {
case JSON_STRING:
free(v->u.string);
break;
case JSON_ARRAY:
for (size_t i = 0; i < v->u.array.count; i++)
json_free(v->u.array.items[i]);
free(v->u.array.items);
break;
case JSON_OBJECT:
for (size_t i = 0; i < v->u.object.count; i++) {
free(v->u.object.members[i].key);
json_free(v->u.object.members[i].value);
}
free(v->u.object.members);
break;
default:
break;
}
free(v);
}
JsonValue *json_clone(const JsonValue *v) {
if (!v)
return NULL;
switch (v->type) {
case JSON_NULL:
return json_new_null();
case JSON_BOOL:
return json_new_bool(v->u.boolean);
case JSON_NUMBER:
return json_new_number(v->u.number);
case JSON_STRING:
return json_new_string(v->u.string);
case JSON_ARRAY: {
JsonValue *a = json_new_array();
for (size_t i = 0; i < v->u.array.count; i++)
json_array_push(a, json_clone(v->u.array.items[i]));
return a;
}
case JSON_OBJECT: {
JsonValue *o = json_new_object();
for (size_t i = 0; i < v->u.object.count; i++)
json_object_set(o, v->u.object.members[i].key,
json_clone(v->u.object.members[i].value));
return o;
}
}
return json_new_null();
}
/* ------------------------------------------------------------------ */
/* 读取 */
/* ------------------------------------------------------------------ */
JsonValue *json_object_get(const JsonValue *obj, const char *key) {
if (!obj || obj->type != JSON_OBJECT || !key)
return NULL;
for (size_t i = 0; i < obj->u.object.count; i++)
if (strcmp(obj->u.object.members[i].key, key) == 0)
return obj->u.object.members[i].value;
return NULL;
}
const char *json_string_value(const JsonValue *v) {
if (!v || v->type != JSON_STRING)
return NULL;
return v->u.string;
}
double json_number_value(const JsonValue *v, double def) {
if (!v || v->type != JSON_NUMBER)
return def;
return v->u.number;
}
int json_bool_value(const JsonValue *v, int def) {
if (!v)
return def;
if (v->type == JSON_BOOL)
return v->u.boolean;
if (v->type == JSON_NUMBER)
return v->u.number != 0;
return def;
}
size_t json_array_size(const JsonValue *v) {
if (!v || v->type != JSON_ARRAY)
return 0;
return v->u.array.count;
}
JsonValue *json_array_get(const JsonValue *v, size_t i) {
if (!v || v->type != JSON_ARRAY || i >= v->u.array.count)
return NULL;
return v->u.array.items[i];
}
const char *json_get_string(const JsonValue *obj, const char *key) {
return json_string_value(json_object_get(obj, key));
}
double json_get_number(const JsonValue *obj, const char *key, double def) {
return json_number_value(json_object_get(obj, key), def);
}
int json_get_bool(const JsonValue *obj, const char *key, int def) {
return json_bool_value(json_object_get(obj, key), def);
}
/* ------------------------------------------------------------------ */
/* 解析器 */
/* ------------------------------------------------------------------ */
typedef struct {
const char *start; /* 原文起点, 用来算出行列号 */
const char *p;
const char *end;
char *err;
size_t errlen;
int depth;
int ui; /* 1 = 报错跟随界面语言 (见 jfmt) */
} Parser;
/* 取一条诊断的模板. ui 为 0 时固定用中文那份 —— 解析器的"母语"是中文,
* 而它同时出现在**给模型看的**工具参数报错里 (agent.c 把 json_parse 的
* 结论原样回填给模型), 那些不该随界面语言变, 见 README「界面语言」.
*
* 表里的 json.* 用的是 printf 转换 (%s/%c/%zu…)而不是 {name} —— 这里是
* 格式化转发, 不是占位符替换. 两种语言的转换符序列必须逐字一致,
* tests/test_i18n.c 会机械地比一遍. */
static const char *jfmt(int ui, const char *key) {
if (ui)
return t(key);
const char *zh = i18n_lookup(LIGENT_LANG_ZH, key);
return zh ? zh : key;
}
/* 只记录第一个错误; 带上行列号和出错处的原文片段,
* 否则用户只知道"语法错了"却不知道错在哪 —— 手写 JSON 时这几乎是必然的. */
LIGENT_PRINTF(2, 3) static void perr(Parser *ps, const char *fmt, ...) {
if (!ps->err || ps->errlen == 0 || ps->err[0] != '\0')
return;
size_t line = 1, col = 1;
for (const char *q = ps->start; q < ps->p && q < ps->end; q++) {
if (*q == '\n') {
line++;
col = 1;
} else
col++;
}
char detail[192];
va_list ap;
va_start(ap, fmt);
vsnprintf(detail, sizeof detail, fmt, ap);
va_end(ap);
/* 出错位置开始的一小段原文 (控制字符替换掉, 避免把终端搞乱) */
char excerpt[40];
size_t n = 0;
for (const char *q = ps->p; q < ps->end && n < 24; q++) {
unsigned char c = (unsigned char)*q;
if (c == '\n')
break;
excerpt[n++] = c < 0x20 || c == 0x7f ? '?' : (char)c;
}
excerpt[n] = '\0';
/* 模板里是同序的 %zu/%zu/%s (+%s)—— 两种语言必须完全一致 */
if (n > 0)
snprintf(ps->err, ps->errlen, jfmt(ps->ui, "json.err.at_got"), line,
col, detail, excerpt);
else
snprintf(ps->err, ps->errlen, jfmt(ps->ui, "json.err.at_end"), line,
col, detail);
/* 最后统一净化: 错误信息里不允许出现原始控制字符.
* 像 "意外的字符 '%c'" 这种格式化会直接把控制字符带进消息本体,
* 原样打到终端上会把用户的屏幕搞乱. */
for (char *q = ps->err; *q; q++) {
unsigned char c = (unsigned char)*q;
if (c < 0x20 || c == 0x7f)
*q = '?';
}
}
static void skip_ws(Parser *ps) {
while (ps->p < ps->end) {
char c = *ps->p;
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
ps->p++;
else
break;
}
}
static JsonValue *parse_value(Parser *ps);
/* 解析一个 JSON 字符串内容 (ps->p 指向起始双引号之后), 写入 sb */
static int parse_string_raw(Parser *ps, StrBuf *sb, char *errb, size_t errlen) {
(void)errb;
(void)errlen;
while (ps->p < ps->end) {
unsigned char c = (unsigned char)*ps->p++;
if (c == '"')
return 0;
if (c == '\\') {
if (ps->p >= ps->end) {
perr(ps, "%s",
jfmt(ps->ui, "json.err.string_escape_truncated"));
return -1;
}
char e = *ps->p++;
switch (e) {
case '"':
sb_putc(sb, '"');
break;
case '\\':
sb_putc(sb, '\\');
break;
case '/':
sb_putc(sb, '/');
break;
case 'b':
sb_putc(sb, '\b');
break;
case 'f':
sb_putc(sb, '\f');
break;
case 'n':
sb_putc(sb, '\n');
break;
case 'r':
sb_putc(sb, '\r');
break;
case 't':
sb_putc(sb, '\t');
break;
case 'u': {
unsigned int cp = 0;
if (ps->end - ps->p < 4) {
perr(ps, "%s", jfmt(ps->ui, "json.err.u_incomplete"));
return -1;
}
for (int i = 0; i < 4; i++) {
char h = ps->p[i];
cp <<= 4;
if (h >= '0' && h <= '9')
cp |= (unsigned)(h - '0');
else if (h >= 'a' && h <= 'f')
cp |= (unsigned)(h - 'a' + 10);
else if (h >= 'A' && h <= 'F')
cp |= (unsigned)(h - 'A' + 10);
else {
perr(ps, jfmt(ps->ui, "json.err.u_bad_hex"), h);
return -1;
}
}
ps->p += 4;
/* 代理对 */
if (cp >= 0xD800 && cp <= 0xDBFF && ps->end - ps->p >= 6 &&
ps->p[0] == '\\' && ps->p[1] == 'u') {
unsigned int lo = 0;
int ok = 1;
for (int i = 0; i < 4; i++) {
char h = ps->p[2 + i];
lo <<= 4;
if (h >= '0' && h <= '9')
lo |= (unsigned)(h - '0');
else if (h >= 'a' && h <= 'f')
lo |= (unsigned)(h - 'a' + 10);
else if (h >= 'A' && h <= 'F')
lo |= (unsigned)(h - 'A' + 10);
else {
ok = 0;
break;
}
}
if (ok && lo >= 0xDC00 && lo <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
ps->p += 6;
}
}
/* 编码为 UTF-8 */
if (cp < 0x80) {
sb_putc(sb, (char)cp);
} else if (cp < 0x800) {
sb_putc(sb, (char)(0xC0 | (cp >> 6)));
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
} else if (cp < 0x10000) {
sb_putc(sb, (char)(0xE0 | (cp >> 12)));
sb_putc(sb, (char)(0x80 | ((cp >> 6) & 0x3F)));
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
} else {
sb_putc(sb, (char)(0xF0 | (cp >> 18)));
sb_putc(sb, (char)(0x80 | ((cp >> 12) & 0x3F)));
sb_putc(sb, (char)(0x80 | ((cp >> 6) & 0x3F)));
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
}
break;
}
default:
perr(ps, jfmt(ps->ui, "json.err.unknown_escape"), e);
return -1;
}
} else if (c < 0x20) {
perr(ps, jfmt(ps->ui, "json.err.raw_control"), c);
return -1;
} else {
sb_putc(sb, (char)c);
}
}
perr(ps, "%s", jfmt(ps->ui, "json.err.string_unterminated"));
return -1;
}
static JsonValue *parse_string(Parser *ps) {
StrBuf sb;
sb_init(&sb);
if (parse_string_raw(ps, &sb, NULL, 0) != 0) {
sb_free(&sb);
return NULL;
}
JsonValue *v = jv_new(JSON_STRING);
v->u.string = sb_detach(&sb);
return v;
}
/* 严格按 RFC 8259 扫描一个数字:
* -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?
*
* 以前这里是"能吃多少吃多少", 于是 01、5.、1e、1e+ 全都被当成合法数字 ——
* 而 strtod("1e") 会老老实实返回 1, 那个残缺的指数就被静默吞掉了.
* 配置文件和存档都是手写的, 把写错的数字当成另一个数继续用下去, 用户只会在
* 很久之后从一个完全不相干的现象里发现它. 所以语法必须自己严查一遍:
* strtod 只负责最后那步文本 -> double 的转换. */
static JsonValue *parse_number(Parser *ps) {
const char *start = ps->p;
if (ps->p < ps->end && *ps->p == '-')
ps->p++;
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
goto bad;
if (*ps->p == '0')
ps->p++; /* '0' 后面不能再跟数字 (01 非法) */
else
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
ps->p++;
if (ps->p < ps->end && *ps->p == '.') {
ps->p++;
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
goto bad;
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
ps->p++;
}
if (ps->p < ps->end && (*ps->p == 'e' || *ps->p == 'E')) {
ps->p++;
if (ps->p < ps->end && (*ps->p == '+' || *ps->p == '-'))
ps->p++;
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
goto bad;
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
ps->p++;
}
{
char *tmp = xstrndup(start, (size_t)(ps->p - start));
char *endp = NULL;
double d = strtod(tmp, &endp);
int all = (endp && *endp == '\0');
free(tmp);
if (!all)
goto bad; /* 语法扫过了 strtod 却不认: 不该发生 */
return json_new_number(d);
}
bad:
/* 回退到数字的起点再报错: 这样行列号指向"这个数字从哪开始",
* 而不是指向我们自己刚消费掉的那个字节. */
{
char *tmp = xstrndup(start, (size_t)(ps->p - start));
ps->p = start;
perr(ps, jfmt(ps->ui, "json.err.bad_number_got"), tmp);
free(tmp);
}
return NULL;
}
/* 解析 ps->p 指向的 "key" */
static char *parse_key(Parser *ps) {
if (ps->p >= ps->end || *ps->p != '"') {
perr(ps, "%s", jfmt(ps->ui, "json.err.want_key"));
return NULL;
}
ps->p++;
StrBuf sb;
sb_init(&sb);
if (parse_string_raw(ps, &sb, NULL, 0) != 0) {
sb_free(&sb);
return NULL;
}
return sb_detach(&sb);
}
static JsonValue *parse_array(Parser *ps) {
JsonValue *arr = json_new_array();
ps->p++; /* '[' */
skip_ws(ps);
if (ps->p < ps->end && *ps->p == ']') {
ps->p++;
return arr;
}
for (;;) {
skip_ws(ps);
JsonValue *item = parse_value(ps);
if (!item) {
json_free(arr);
return NULL;
}
json_array_push(arr, item);
skip_ws(ps);
if (ps->p >= ps->end) {
perr(ps, "%s", jfmt(ps->ui, "json.err.array_unterminated"));
json_free(arr);
return NULL;
}
if (*ps->p == ',') {
ps->p++;
continue;
}
if (*ps->p == ']') {
ps->p++;
return arr;
}
perr(ps, jfmt(ps->ui, "json.err.want_comma_in_array"), *ps->p);
json_free(arr);
return NULL;
}
}
static JsonValue *parse_object(Parser *ps) {
JsonValue *obj = json_new_object();
ps->p++; /* '{' */
skip_ws(ps);
if (ps->p < ps->end && *ps->p == '}') {
ps->p++;
return obj;
}
for (;;) {
skip_ws(ps);
char *key = parse_key(ps);
if (!key) {
json_free(obj);
return NULL;
}
skip_ws(ps);
if (ps->p >= ps->end || *ps->p != ':') {
perr(ps, jfmt(ps->ui, "json.err.want_colon"), key);
free(key);
json_free(obj);
return NULL;
}
ps->p++;
skip_ws(ps);
JsonValue *val = parse_value(ps);
if (!val) {
free(key);
json_free(obj);
return NULL;
}
json_object_set(obj, key, val);
free(key);
skip_ws(ps);
if (ps->p >= ps->end) {
perr(ps, "%s", jfmt(ps->ui, "json.err.object_unterminated"));
json_free(obj);
return NULL;
}
if (*ps->p == ',') {
ps->p++;
continue;
}
if (*ps->p == '}') {
ps->p++;
return obj;
}
perr(ps, jfmt(ps->ui, "json.err.want_comma_in_object"), *ps->p);
json_free(obj);
return NULL;
}
}
static JsonValue *parse_value(Parser *ps) {
if (++ps->depth > JSON_MAX_DEPTH) {
perr(ps, jfmt(ps->ui, "json.err.too_deep"), JSON_MAX_DEPTH);
ps->depth--;
return NULL;
}
skip_ws(ps);
JsonValue *result = NULL;
if (ps->p >= ps->end) {
perr(ps, "%s", jfmt(ps->ui, "json.err.unexpected_eof"));
goto done;
}
switch (*ps->p) {
case '{':
result = parse_object(ps);
break;
case '[':
result = parse_array(ps);
break;
case '"':
ps->p++;
result = parse_string(ps);
break;
case 't':
if (ps->end - ps->p >= 4 && memcmp(ps->p, "true", 4) == 0) {
ps->p += 4;
result = json_new_bool(1);
} else
perr(ps, "%s", jfmt(ps->ui, "json.err.want_true"));
break;
case 'f':
if (ps->end - ps->p >= 5 && memcmp(ps->p, "false", 5) == 0) {
ps->p += 5;
result = json_new_bool(0);
} else
perr(ps, "%s", jfmt(ps->ui, "json.err.want_false"));
break;
case 'n':
if (ps->end - ps->p >= 4 && memcmp(ps->p, "null", 4) == 0) {
ps->p += 4;
result = json_new_null();
} else
perr(ps, "%s", jfmt(ps->ui, "json.err.want_null"));
break;
default:
if (*ps->p == '-' || isdigit((unsigned char)*ps->p))
result = parse_number(ps);
else
perr(ps, jfmt(ps->ui, "json.err.unexpected_char"), *ps->p);
break;
}
done:
ps->depth--;
return result;
}
/* ui != 0: 报错跟随界面语言; = 0: 固定中文 (给模型看的那份).
* 见上面 jfmt 的说明与 README「界面语言」. */
static JsonValue *parse_impl(const char *text, char *errbuf, size_t errlen,
int ui) {
if (errbuf && errlen)
errbuf[0] = '\0';
if (!text) {
if (errbuf && errlen)
snprintf(errbuf, errlen, "%s", jfmt(ui, "json.err.empty_input"));
return NULL;
}
Parser ps;
ps.start = text;
ps.p = text;
ps.end = text + strlen(text);
ps.err = errbuf;
ps.errlen = errlen;
ps.depth = 0;
ps.ui = ui;
JsonValue *v = parse_value(&ps);
if (!v)
return NULL;
skip_ws(&ps);
if (ps.p != ps.end) {
perr(&ps, jfmt(ps.ui, "json.err.trailing"), ps.p);
json_free(v);
return NULL;
}
return v;
}
JsonValue *json_parse(const char *text, char *errbuf, size_t errlen) {
return parse_impl(text, errbuf, errlen, 0);
}
JsonValue *json_parse_ui(const char *text, char *errbuf, size_t errlen) {
return parse_impl(text, errbuf, errlen, 1);
}
/* ------------------------------------------------------------------ */
/* 序列化 */
/* ------------------------------------------------------------------ */
static void dump_string(StrBuf *sb, const char *s) {
size_t len = strlen(s), i = 0;
sb_putc(sb, '"');
while (i < len) {
unsigned char c = (unsigned char)s[i];
const char *esc = NULL;
switch (c) {
case '"':
esc = "\\\"";
break;
case '\\':
esc = "\\\\";
break;
case '\b':
esc = "\\b";
break;
case '\f':
esc = "\\f";
break;
case '\n':
esc = "\\n";
break;
case '\r':
esc = "\\r";
break;
case '\t':
esc = "\\t";
break;
default:
break;
}
if (esc) {
sb_puts(sb, esc);
i++;
continue;
}
if (c < 0x20) {
sb_printf(sb, "\\u%04x", c);
i++;
continue;
}
if (c < 0x80) {
sb_putc(sb, (char)c);
i++;
continue;
}
/* 非 ASCII 字节: 只有构成**合法** UTF-8 序列时才能原样进 JSON 字符串.
* 以前这里是无条件 sb_putc, 于是工具输出里一个坏字节 (例如某条命令在
* 非 UTF-8 locale 下打出来的路径, 或者被截断的多字节字符)就会让**整个
* 请求体**不是合法 UTF-8, 服务端直接回
* 400 ... invalid unicode code point
* 更糟的是那段坏数据已经进了对话历史, 之后每次请求都会失败 ——
* 除非 /reset 把整轮对话丢掉.
*
* 兜在出口而不是只在入口清理, 是因为出口才是能保证"发出去的永远合法"
* 的那一层: 对话内容还可能来自存档、粘贴、/include 进来的文件……
* 这里统一把非法字节换成 U+FFFD, 既让请求成立, 也让模型看到"这里有个
* 无法解码的字符", 而不是收到一段乱码. */
size_t n = utf8_seq_len(s + i, len - i);
if (n == 0) {
sb_puts(sb, "\\ufffd");
i++;
continue;
}
/* U+FFFD 一律写成 \ufffd 转义. 它是上面替换坏字节时用的字符, 如果这里
* 让 EF BF BD 原样输出, dump→parse→dump 就不稳定了: 第一次是 \ufffd,
* 解析回来变成真的 U+FFFD, 第二次就原样吐三个字节.
* fuzz 的"dump 幂等"这条不变量就是被这个当场抓住的 —— 幂等是有价值的
* 性质, 它保证存档反复读写在字节层面也是收敛的. 给这一个字符定死一个
* 规范形式, 代价是真正合法的 U+FFFD 会多占 3 个字节, 可以忽略. */
if (n == 3 && (unsigned char)s[i] == 0xEF &&
(unsigned char)s[i + 1] == 0xBF &&
(unsigned char)s[i + 2] == 0xBD) {
sb_puts(sb, "\\ufffd");
i += 3;
continue;
}
sb_append(sb, s + i, n);
i += n;
}
sb_putc(sb, '"');
}
static void dump_number(StrBuf *sb, double n) {
if (isnan(n) || isinf(n)) {
sb_puts(sb, "null");
return;
}
char buf[64];
/* 先判范围, 再转换. double -> long long 在值超出范围时是 UB, 而 && 的
* 求值顺序保不住这个转换: 原来它写在条件最左边, 服务端回一个 1e300
* 就会踩到 (clang 的 UBSan 报 float-cast-overflow; gcc 不插桩这条转换,
* 所以 make SAN=1 抓不到). */
if (fabs(n) < 1e15 && n == (double)(long long)n)
snprintf(buf, sizeof buf, "%lld", (long long)n);
else
snprintf(buf, sizeof buf, "%.17g", n);
sb_puts(sb, buf);
}
static void dump_value(StrBuf *sb, const JsonValue *v, int indent, int pretty,
int level) {
if (!v) {
sb_puts(sb, "null");
return;
}
switch (v->type) {
case JSON_NULL:
sb_puts(sb, "null");
break;
case JSON_BOOL:
sb_puts(sb, v->u.boolean ? "true" : "false");
break;
case JSON_NUMBER:
dump_number(sb, v->u.number);
break;
case JSON_STRING:
dump_string(sb, v->u.string);
break;
case JSON_ARRAY:
if (v->u.array.count == 0) {
sb_puts(sb, "[]");
break;
}
sb_putc(sb, '[');
for (size_t i = 0; i < v->u.array.count; i++) {
if (i)
sb_putc(sb, ',');
if (pretty) {
sb_putc(sb, '\n');
for (int k = 0; k < (level + 1) * indent; k++)
sb_putc(sb, ' ');
}
dump_value(sb, v->u.array.items[i], indent, pretty, level + 1);
}
if (pretty) {
sb_putc(sb, '\n');
for (int k = 0; k < level * indent; k++)
sb_putc(sb, ' ');
}
sb_putc(sb, ']');
break;
case JSON_OBJECT:
if (v->u.object.count == 0) {
sb_puts(sb, "{}");
break;
}
sb_putc(sb, '{');
for (size_t i = 0; i < v->u.object.count; i++) {
if (i)
sb_putc(sb, ',');
if (pretty) {
sb_putc(sb, '\n');
for (int k = 0; k < (level + 1) * indent; k++)
sb_putc(sb, ' ');
}
dump_string(sb, v->u.object.members[i].key);
sb_putc(sb, ':');
if (pretty)
sb_putc(sb, ' ');
dump_value(sb, v->u.object.members[i].value, indent, pretty,
level + 1);
}
if (pretty) {
sb_putc(sb, '\n');
for (int k = 0; k < level * indent; k++)
sb_putc(sb, ' ');
}
sb_putc(sb, '}');
break;
}
}
char *json_dump(const JsonValue *v) {
StrBuf sb;
sb_init(&sb);
dump_value(&sb, v, 0, 0, 0);
return sb_detach(&sb);
}
char *json_dump_pretty(const JsonValue *v) {
StrBuf sb;
sb_init(&sb);
dump_value(&sb, v, 2, 1, 0);
return sb_detach(&sb);
}
char *json_quote_string(const char *s) {
StrBuf sb;
sb_init(&sb);
dump_string(&sb, s ? s : "");
return sb_detach(&sb);
}