From 4fc7da62b4a9632ea19e1f485d6a384a3b24ed00 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 23 May 2019 14:59:44 +0930 Subject: [PATCH] json_out: pass through OOM failures. And fix escaping to work with addstr(), and assert in debug mode if they don't specify quotes. Signed-off-by: Rusty Russell --- ccan/json_out/json_out.c | 172 +++++++++++++++++++++++++++------------ ccan/json_out/json_out.h | 29 +++++-- 2 files changed, 138 insertions(+), 63 deletions(-) diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c index b8c798fc..87deaca8 100644 --- a/ccan/json_out/json_out.c +++ b/ccan/json_out/json_out.c @@ -11,7 +11,7 @@ struct json_out { void *cb_arg; #ifdef CCAN_JSON_OUT_DEBUG - /* tal_arr of types ( or [ we're enclosed in. */ + /* tal_arr of types ( or [ we're enclosed in. NULL if oom. */ char *wrapping; #endif /* True if we haven't yet put an element in current wrapping */ @@ -27,16 +27,23 @@ static void *membuf_tal_realloc(struct membuf *mb, { char *p = rawelems; - tal_resize(&p, newsize); + if (!tal_resize(&p, newsize)) + return NULL; return p; } struct json_out *json_out_new(const tal_t *ctx) { struct json_out *jout = tal(ctx, struct json_out); + char *pool; - membuf_init(&jout->outbuf, - tal_arr(jout, char, 64), 64, membuf_tal_realloc); + if (!jout) + return NULL; + pool = tal_arr(jout, char, 64); + if (!pool) + return tal_free(jout); + + membuf_init(&jout->outbuf, pool, tal_count(pool), membuf_tal_realloc); #ifdef CCAN_JSON_OUT_DEBUG jout->wrapping = tal_arr(jout, char, 0); #endif @@ -61,9 +68,14 @@ struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src) size_t num_elems = membuf_num_elems(&src->outbuf); char *elems = membuf_elems(&src->outbuf); struct json_out *jout = tal_dup(ctx, struct json_out, src); - membuf_init(&jout->outbuf, - tal_dup_arr(jout, char, elems, num_elems, 0), - num_elems, membuf_tal_realloc); + char *pool; + + if (!jout) + return NULL; + pool = tal_dup_arr(jout, char, elems, num_elems, 0); + if (!pool) + return tal_free(jout); + membuf_init(&jout->outbuf, pool, num_elems, membuf_tal_realloc); membuf_added(&jout->outbuf, num_elems); #ifdef CCAN_JSON_OUT_DEBUG jout->wrapping = tal_dup_arr(jout, char, @@ -76,9 +88,14 @@ struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src) static void indent(struct json_out *jout, char type) { #ifdef CCAN_JSON_OUT_DEBUG - size_t n = tal_count(jout->wrapping); - tal_resize(&jout->wrapping, n+1); - jout->wrapping[n] = type; + /* Can't check if we ran out of memory. */ + if (jout->wrapping) { + size_t n = tal_count(jout->wrapping); + if (!tal_resize(&jout->wrapping, n+1)) + jout->wrapping = tal_free(jout->wrapping); + else + jout->wrapping[n] = type; + } #endif jout->empty = true; } @@ -86,11 +103,14 @@ static void indent(struct json_out *jout, char type) static void unindent(struct json_out *jout, char type) { #ifdef CCAN_JSON_OUT_DEBUG - size_t indent = tal_count(jout->wrapping); - assert(indent > 0); - /* Both [ and ] and { and } are two apart in ASCII */ - assert(jout->wrapping[indent-1] == type - 2); - tal_resize(&jout->wrapping, indent-1); + /* Can't check if we ran out of memory. */ + if (jout->wrapping) { + size_t indent = tal_count(jout->wrapping); + assert(indent > 0); + /* Both [ and ] and { and } are two apart in ASCII */ + assert(jout->wrapping[indent-1] == type - 2); + tal_resize(&jout->wrapping, indent-1); + } #endif jout->empty = false; } @@ -110,18 +130,22 @@ static void check_fieldname(const struct json_out *jout, const char *fieldname) { #ifdef CCAN_JSON_OUT_DEBUG - size_t n = tal_count(jout->wrapping); - if (n == 0) - /* Can't have a fieldname if not in anything! */ - assert(!fieldname); - else if (jout->wrapping[n-1] == '[') - /* No fieldnames in arrays. */ - assert(!fieldname); - else { - /* Must have fieldnames in objects. */ - assert(fieldname); - /* We don't escape this for you */ - assert(!json_escape_needed(fieldname, strlen(fieldname))); + /* We don't escape this for you */ + assert(!fieldname || !json_escape_needed(fieldname, strlen(fieldname))); + + /* Can't check anything else if we ran out of memory. */ + if (jout->wrapping) { + size_t n = tal_count(jout->wrapping); + if (n == 0) + /* Can't have a fieldname if not in anything! */ + assert(!fieldname); + else if (jout->wrapping[n-1] == '[') + /* No fieldnames in arrays. */ + assert(!fieldname); + else { + /* Must have fieldnames in objects. */ + assert(fieldname); + } } #endif } @@ -139,12 +163,9 @@ char *json_out_member_direct(struct json_out *jout, if (fieldname) extra += 1 + strlen(fieldname) + 2; - if (!extra) { - dest = NULL; - goto out; - } - dest = mkroom(jout, extra); + if (!dest) + goto out; if (!jout->empty) *(dest++) = ','; @@ -162,21 +183,33 @@ out: return dest; } -void json_out_start(struct json_out *jout, const char *fieldname, char type) +bool json_out_start(struct json_out *jout, const char *fieldname, char type) { + char *p; + assert(type == '[' || type == '{'); - json_out_member_direct(jout, fieldname, 1)[0] = type; + p = json_out_member_direct(jout, fieldname, 1); + if (p) + p[0] = type; indent(jout, type); + + return p != NULL; } -void json_out_end(struct json_out *jout, char type) +bool json_out_end(struct json_out *jout, char type) { + char *p; + assert(type == '}' || type == ']'); - json_out_direct(jout, 1)[0] = type; + p = json_out_direct(jout, 1); + if (p) + p[0] = type; unindent(jout, type); + + return p != NULL; } -void json_out_addv(struct json_out *jout, +bool json_out_addv(struct json_out *jout, const char *fieldname, bool quote, const char *fmt, @@ -186,7 +219,8 @@ void json_out_addv(struct json_out *jout, va_list ap2; char *dst; - json_out_member_direct(jout, fieldname, 0); + if (!json_out_member_direct(jout, fieldname, 0)) + return false; /* Make a copy in case we need it below. */ va_copy(ap2, ap); @@ -210,52 +244,78 @@ void json_out_addv(struct json_out *jout, if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) { /* Make room for NUL terminator, even though we don't want it */ dst = mkroom(jout, fmtlen + 1 + (int)quote*2); + if (!dst) + goto out; vsprintf(dst + quote, fmt, ap2); } - /* Of course, if we need to escape it, we have to redo it all. */ - if (json_escape_needed(dst + quote, fmtlen)) { - struct json_escape *e; - e = json_escape_len(NULL, dst + quote, fmtlen); - fmtlen = strlen(e->s); - dst = mkroom(jout, fmtlen + (int)quote*2); - memcpy(dst + quote, e, fmtlen); - tal_free(e); - } +#ifdef CCAN_JSON_OUT_DEBUG + /* You're not inserting junk here, are you? */ + assert(quote || !json_escape_needed(dst, fmtlen)); +#endif + /* Of course, if we need to escape it, we have to redo it all. */ if (quote) { + if (json_escape_needed(dst + quote, fmtlen)) { + struct json_escape *e; + e = json_escape_len(NULL, dst + quote, fmtlen); + fmtlen = strlen(e->s); + dst = mkroom(jout, fmtlen + (int)quote*2); + if (!dst) + goto out; + memcpy(dst + quote, e, fmtlen); + tal_free(e); + } dst[0] = '"'; dst[fmtlen+1] = '"'; } membuf_added(&jout->outbuf, fmtlen + (int)quote*2); + +out: va_end(ap2); + return dst != NULL; } -void json_out_add(struct json_out *jout, +bool json_out_add(struct json_out *jout, const char *fieldname, bool quote, const char *fmt, ...) { va_list ap; + bool ret; va_start(ap, fmt); - json_out_addv(jout, fieldname, quote, fmt, ap); + ret = json_out_addv(jout, fieldname, quote, fmt, ap); va_end(ap); + return ret; } -void json_out_addstr(struct json_out *jout, +bool json_out_addstr(struct json_out *jout, const char *fieldname, const char *str) { size_t len = strlen(str); char *p; + struct json_escape *e; + + if (json_escape_needed(str, len)) { + e = json_escape(NULL, str); + str = e->s; + len = strlen(str); + } else + e = NULL; p = json_out_member_direct(jout, fieldname, len + 2); - p[0] = p[1+len] = '"'; - memcpy(p+1, str, len); + if (p) { + p[0] = p[1+len] = '"'; + memcpy(p+1, str, len); + } + tal_free(e); + + return p != NULL; } -void json_out_add_splice(struct json_out *jout, +bool json_out_add_splice(struct json_out *jout, const char *fieldname, const struct json_out *src) { @@ -263,13 +323,17 @@ void json_out_add_splice(struct json_out *jout, size_t len; p = json_out_contents(src, &len); + if (!p) + return false; memcpy(json_out_member_direct(jout, fieldname, len), p, len); + return true; } char *json_out_direct(struct json_out *jout, size_t len) { char *p = mkroom(jout, len); - membuf_added(&jout->outbuf, len); + if (p) + membuf_added(&jout->outbuf, len); return p; } diff --git a/ccan/json_out/json_out.h b/ccan/json_out/json_out.h index a3ebdb13..2911ff24 100644 --- a/ccan/json_out/json_out.h +++ b/ccan/json_out/json_out.h @@ -11,6 +11,8 @@ struct json_out; /** * json_out_new - allocate a json_out stream. * @ctx: the tal_context to allocate from, or NULL + * + * Returns NULL if tal allocation fails. */ struct json_out *json_out_new(const tal_t *ctx); @@ -50,20 +52,23 @@ struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src); * @fieldname: the fieldname, if inside an object, or NULL if inside an array. * @type: '[' or '{' to start an array or object, respectively. * + * Returns true unless tal_resize() fails. * Literally writes '"@fieldname": @type' or '@type ' if fieldname is NULL. * @fieldname must not need JSON escaping. */ -void json_out_start(struct json_out *jout, const char *fieldname, char type); +bool json_out_start(struct json_out *jout, const char *fieldname, char type); /** * json_out_end - end an array or object. * @jout: the json_out object to write into. * @type: '}' or ']' to end an array or object, respectively. * + * Returns true unless tal_resize() fails. + * * Literally writes ']' or '}', keeping track of whether we need to append * a comma. */ -void json_out_end(struct json_out *jout, char type); +bool json_out_end(struct json_out *jout, char type); /** * json_out_add - add a formatted member. @@ -72,15 +77,18 @@ void json_out_end(struct json_out *jout, char type); * @quote: if true, surround fmt by " and ". * @fmt...: the printf-style format * + * Returns true unless tal_resize() fails. + * * If you're in an array, @fieldname must be NULL. If you're in an * object, @fieldname must be non-NULL. This is checked if * CCAN_JSON_OUT_DEBUG is defined. * @fieldname must not need JSON escaping. * - * If the resulting string requires escaping, we call json_escape(). + * If the resulting string requires escaping, and @quote is true, we + * call json_escape(). */ PRINTF_FMT(4,5) -void json_out_add(struct json_out *jout, +bool json_out_add(struct json_out *jout, const char *fieldname, bool quote, const char *fmt, ...); @@ -95,7 +103,7 @@ void json_out_add(struct json_out *jout, * * See json_out_add() above. */ -void json_out_addv(struct json_out *jout, +bool json_out_addv(struct json_out *jout, const char *fieldname, bool quote, const char *fmt, @@ -109,7 +117,7 @@ void json_out_addv(struct json_out *jout, * * Equivalent to json_out_add(@jout, @fieldname, true, "%s", @str); */ -void json_out_addstr(struct json_out *jout, +bool json_out_addstr(struct json_out *jout, const char *fieldname, const char *str); @@ -120,7 +128,7 @@ void json_out_addstr(struct json_out *jout, * @extra: how many bytes to allocate. * * @fieldname must not need JSON escaping. Returns a direct pointer into - * the @extra bytes. + * the @extra bytes, or NULL if tal_resize() fails. * * This allows you to write your own efficient type-specific helpers. */ @@ -135,7 +143,8 @@ char *json_out_member_direct(struct json_out *jout, * This lets you access the json_out stream directly, to save a copy, * if you know exactly how much you will write. * - * Returns a pointer to @len bytes at the end of @jout. + * Returns a pointer to @len bytes at the end of @jout, or NULL if + * tal_resize() fails. * * This is dangerous, since it doesn't automatically prepend a "," * like the internal logic does, but can be used (carefully) to add @@ -156,8 +165,10 @@ char *json_out_direct(struct json_out *jout, size_t extra); * * Note that it will call json_out_contents(@src), so it expects that * object to be unconsumed. + * + * Returns false if tal_resize() fails. */ -void json_out_add_splice(struct json_out *jout, +bool json_out_add_splice(struct json_out *jout, const char *fieldname, const struct json_out *src); -- 2.39.2