-
Notifications
You must be signed in to change notification settings - Fork 201
[RFC] Create a 'safe' strbuf API #2230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ds/trace2-tolerate-failed-timestamp
Are you sure you want to change the base?
Changes from all commits
b177970
8d30730
3b3c672
ebd91b9
6e654dc
dea925f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,17 +2,25 @@ | |
|
|
||
| #include "git-compat-util.h" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +int jw_release(struct json_writer *jw)
> {
> - strbuf_release(&jw->json);
> - strbuf_release(&jw->open_stack);
> + enum safe_result result = SUCCESS;
> +
> + /* attempt both removals without short-circuiting. */
> + result = sstrbuf_release(&jw->json) || result;
> + result = sstrbuf_release(&jw->open_stack) || result;
> +
> + return result;
> }
This is puzzling in a few ways.
"enum safe_result" so far has been SUCCESS==0 and MEMORY_ERROR==1.
Presumably in some future we would gain other kind of error symbols,
but when that happens is this meant to act as an enumeration of
different kinds errors? Or an enumeration of bitmasks that can
signal different kinds of errors?
If we mean "enum safe_result" is an enumeration of different kinds
of errors, then the "result" variable and the returned value from
here would be able to report a *single* kind of error, and it may
be common to report the first error we encounter, in which case
enum safe_result result = SUCCESS;
enum safe_result res;
res = sstrbuf_release(&jw->json);
if (!result && res)
result = res;
res = sstrbuf_release(&jw->open_stack);
if (!result && res)
result = res;
return result;
would be slightly longer, far easier to reason about, and is a lot
more futureproof. What you wrote, with "||", does not really allow
anything other than "is it still zero, or coalesce any non-zero
value to 1".
On the other hand, if we mean "enum safe_result" is an enumeration
of bitmasks, each bit representing different kind of error, then
enum safe_result result = 0;
result |= sstrbuf_release(&jw->json);
result |= sstrbuf_release(&jw->open_stack);
return result;
would probably be what you want. That way you can add different
functions that returns different bit to signal a different kind of
error and or it in.
result |= some_function();There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Junio C Hamano <gitster@pobox.com> writes:
> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> +int jw_release(struct json_writer *jw)
>> {
>> - strbuf_release(&jw->json);
>> - strbuf_release(&jw->open_stack);
>> + enum safe_result result = SUCCESS;
>> +
>> + /* attempt both removals without short-circuiting. */
>> + result = sstrbuf_release(&jw->json) || result;
>> + result = sstrbuf_release(&jw->open_stack) || result;
>> +
>> + return result;
>> }
>
> This is puzzling in a few ways.
> If we mean "enum safe_result" is an enumeration of different kinds
> of errors, then the "result" variable and the returned value from
> ...
> On the other hand, if we mean "enum safe_result" is an enumeration
> of bitmasks, each bit representing different kind of error, then
> ...
I forgot the third possibility. Regardless of which interpretation
of "enum safe_result" we use, if jw_release() is designed to say "0
for success, non-zero for failure", then almost as written but
declaring "result" as a plain "int"
int result = 0;
result = sstrbuf_release(&jw->json) || result;
result = sstrbuf_release(&jw->open_stack) || result;
return result;
would probably make sense, even though the "|| result" construct is
a bit unusual in C.
Thanks.
|
||
| #include "json-writer.h" | ||
| #include "strbuf.h" | ||
| /* banned-die must be last. */ | ||
| #include "banned-die.h" | ||
|
|
||
| void jw_init(struct json_writer *jw) | ||
| { | ||
| struct json_writer blank = JSON_WRITER_INIT; | ||
| memcpy(jw, &blank, sizeof(*jw));; | ||
| } | ||
|
|
||
| void jw_release(struct json_writer *jw) | ||
| int jw_release(struct json_writer *jw) | ||
| { | ||
| strbuf_release(&jw->json); | ||
| strbuf_release(&jw->open_stack); | ||
| enum safe_result result = SUCCESS; | ||
|
|
||
| /* attempt both removals without short-circuiting. */ | ||
| result = sstrbuf_release(&jw->json) || result; | ||
| result = sstrbuf_release(&jw->open_stack) || result; | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /* | ||
|
|
@@ -98,16 +106,17 @@ static void maybe_add_comma(struct json_writer *jw) | |
| jw->need_comma = 1; | ||
| } | ||
|
|
||
| static void fmt_double(struct json_writer *jw, int precision, | ||
| double value) | ||
| static int fmt_double(struct json_writer *jw, int precision, | ||
| double value) | ||
| { | ||
| if (precision < 0) { | ||
| strbuf_addf(&jw->json, "%f", value); | ||
| return 0; | ||
| } else { | ||
| struct strbuf fmt = STRBUF_INIT; | ||
| strbuf_addf(&fmt, "%%.%df", precision); | ||
| strbuf_addf(&jw->json, fmt.buf, value); | ||
| strbuf_release(&fmt); | ||
| return sstrbuf_release(&fmt); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -234,8 +243,8 @@ static void kill_indent(struct strbuf *sb, | |
| } | ||
| } | ||
|
|
||
| static void append_sub_jw(struct json_writer *jw, | ||
| const struct json_writer *value) | ||
| static int append_sub_jw(struct json_writer *jw, | ||
| const struct json_writer *value) | ||
| { | ||
| /* | ||
| * If both are pretty, increase the indentation of the sub_jw | ||
|
|
@@ -254,18 +263,17 @@ static void append_sub_jw(struct json_writer *jw, | |
| struct strbuf sb = STRBUF_INIT; | ||
| increase_indent(&sb, value, jw->open_stack.len * 2); | ||
| strbuf_addbuf(&jw->json, &sb); | ||
| strbuf_release(&sb); | ||
| return; | ||
| return sstrbuf_release(&sb); | ||
| } | ||
| if (!jw->pretty && value->pretty) { | ||
| struct strbuf sb = STRBUF_INIT; | ||
| kill_indent(&sb, value); | ||
| strbuf_addbuf(&jw->json, &sb); | ||
| strbuf_release(&sb); | ||
| return; | ||
| return sstrbuf_release(&sb); | ||
| } | ||
|
|
||
| strbuf_addbuf(&jw->json, &value->json); | ||
| return 0; | ||
| } | ||
|
|
||
| void jw_object_sub_jw(struct json_writer *jw, const char *key, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #include "git-compat-util.h" | ||
| #include "strbuf-safe.h" | ||
| #include "banned-die.h" | ||
|
|
||
| /* | ||
| * A safe version of ALLOC_GROW from git-compat-util.h and | ||
| * xrealloc() from wrapper.c. | ||
| */ | ||
| #define SAFE_ALLOC_GROW(x, nr, alloc) \ | ||
| do { \ | ||
| if ((nr) > alloc) { \ | ||
| if (alloc_nr(alloc) < (nr)) \ | ||
| alloc = (nr); \ | ||
| else \ | ||
| alloc = alloc_nr(alloc); \ | ||
| if (srealloc((void **)&(x), alloc)) \ | ||
| return MEMORY_ERROR; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| enum safe_result sstrbuf_grow(struct strbuf *sb, size_t extra) | ||
| { | ||
| int new_buf = !sb->alloc; | ||
| size_t new_len = st_add3(sb->len, extra, 1); | ||
| if (new_buf) | ||
| sb->buf = NULL; | ||
|
|
||
| SAFE_ALLOC_GROW(sb->buf, new_len, sb->alloc); | ||
|
|
||
| if (new_buf) | ||
| sb->buf[0] = '\0'; | ||
|
|
||
| return SUCCESS; | ||
| } | ||
|
|
||
| enum safe_result sstrbuf_init(struct strbuf *sb, size_t hint) | ||
| { | ||
| struct strbuf blank = STRBUF_INIT; | ||
| memcpy(sb, &blank, sizeof(*sb)); | ||
| if (!hint) | ||
| return 0; | ||
| return sstrbuf_grow(sb, hint); | ||
| } | ||
|
|
||
| enum safe_result sstrbuf_release(struct strbuf *sb) | ||
| { | ||
| if (sb->alloc) { | ||
| free(sb->buf); | ||
| return sstrbuf_init(sb, 0); | ||
| } | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| #ifndef STRBUF_SAFE_H | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +/*
> + * NOTE FOR STRBUF DEVELOPERS
> + *
> + * strbuf is a low-level primitive; as such it should interact only
> + * with other low-level primitives. Do not introduce new functions
> + * which interact with higher-level APIs.
> + *
> + * This header file specifically conatins the "safe" API surface for
> + * working with strbufs. The implementations of these methods avoid
> + * using die() and other exits. Thus, these methods are appropriate
> + * for use within lower-level APIs such as trace2.
> + */
I have to wonder if this is somewhat backwards, in that the longer
term goal for us should be to make most of the service routines like
strbuf, string_list, csum_file, etc., free of die() and be "safe".
A recent trend under the label "libification" is to make the use of
the_repository more explicit and pass a "struct repository *" as a
parameter instead more widely throughout the code flow, but it would
be equally if not more useful change to expand the "safe" API surface
so that callers of more service routines take responsibility to act
on errors.
And picking strbuf as the first instance of such generic service
library certainly is a good idea. Its interface is well defined.
We may want to rename functions that _happen_ to use a strbuf to
return their results but otherwise has nothing to do with strbuf
away from strbuf_ prefix (strbuf_realpath() etc. in abspath.h are
prime examples) as part of this first step, though. |
||
| #define STRBUF_SAFE_H | ||
|
|
||
| /* | ||
| * NOTE FOR STRBUF DEVELOPERS | ||
| * | ||
| * strbuf is a low-level primitive; as such it should interact only | ||
| * with other low-level primitives. Do not introduce new functions | ||
| * which interact with higher-level APIs. | ||
| * | ||
| * This header file specifically conatins the "safe" API surface for | ||
| * working with strbufs. The implementations of these methods avoid | ||
| * using die() and other exits. Thus, these methods are appropriate | ||
| * for use within lower-level APIs such as trace2. | ||
| */ | ||
|
|
||
| struct string_list; | ||
|
|
||
| /** | ||
| * strbufs are meant to be used with all the usual C string and memory | ||
| * APIs. Given that the length of the buffer is known, it's often better to | ||
| * use the mem* functions than a str* one (e.g., memchr vs. strchr). | ||
| * Though, one has to be careful about the fact that str* functions often | ||
| * stop on NULs and that strbufs may have embedded NULs. | ||
| * | ||
| * A strbuf is NUL terminated for convenience, but no function in the | ||
| * strbuf API actually relies on the string being free of NULs. | ||
| * | ||
| * strbufs have some invariants that are very important to keep in mind: | ||
| * | ||
| * - The `buf` member is never NULL, so it can be used in any usual C | ||
| * string operations safely. strbufs _have_ to be initialized either by | ||
| * `strbuf_init()` or by `= STRBUF_INIT` before the invariants, though. | ||
| * | ||
| * Do *not* assume anything on what `buf` really is (e.g. if it is | ||
| * allocated memory or not), use `strbuf_detach()` to unwrap a memory | ||
| * buffer from its strbuf shell in a safe way. That is the sole supported | ||
| * way. This will give you a malloced buffer that you can later `free()`. | ||
| * | ||
| * However, it is totally safe to modify anything in the string pointed by | ||
| * the `buf` member, between the indices `0` and `len-1` (inclusive). | ||
| * | ||
| * - The `buf` member is a byte array that has at least `len + 1` bytes | ||
| * allocated. The extra byte is used to store a `'\0'`, allowing the | ||
| * `buf` member to be a valid C-string. All strbuf functions ensure this | ||
| * invariant is preserved. | ||
| * | ||
| * NOTE: It is OK to "play" with the buffer directly if you work it this | ||
| * way: | ||
| * | ||
| * strbuf_grow(sb, SOME_SIZE); <1> | ||
| * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); | ||
| * | ||
| * <1> Here, the memory array starting at `sb->buf`, and of length | ||
| * `strbuf_avail(sb)` is all yours, and you can be sure that | ||
| * `strbuf_avail(sb)` is at least `SOME_SIZE`. | ||
| * | ||
| * NOTE: `SOME_OTHER_SIZE` must be smaller or equal to `strbuf_avail(sb)`. | ||
| * | ||
| * Doing so is safe, though if it has to be done in many places, adding the | ||
| * missing API to the strbuf module is the way to go. | ||
| * | ||
| * WARNING: Do _not_ assume that the area that is yours is of size `alloc | ||
| * - 1` even if it's true in the current implementation. Alloc is somehow a | ||
| * "private" member that should not be messed with. Use `strbuf_avail()` | ||
| * instead. | ||
| */ | ||
|
|
||
| /** | ||
| * Data Structures | ||
| * --------------- | ||
| */ | ||
|
|
||
| /** | ||
| * This is the string buffer structure. The `len` member can be used to | ||
| * determine the current length of the string, and `buf` member provides | ||
| * access to the string itself. | ||
| */ | ||
| struct strbuf { | ||
| size_t alloc; | ||
| size_t len; | ||
| char *buf; | ||
| }; | ||
|
|
||
| extern char strbuf_slopbuf[]; | ||
| #define STRBUF_INIT { .buf = strbuf_slopbuf } | ||
|
|
||
| enum safe_result { | ||
| SUCCESS = 0, | ||
| MEMORY_ERROR, | ||
| }; | ||
|
|
||
| enum safe_result sstrbuf_grow(struct strbuf *sb, size_t extra); | ||
| enum safe_result sstrbuf_init(struct strbuf *sb, size_t hint); | ||
| enum safe_result sstrbuf_release(struct strbuf *sb); | ||
|
|
||
| #endif /* STRBUF_SAFE_H */ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Junio C Hamano wrote on the Git mailing list (how to reply to this email):