Skip to content

Commit 61daf6b

Browse files
committed
Merge branch 'ab/bug-if-bug' into seen
A new bug() and BUG_if_bug() API is introduced to make it easier to uniformly log "detect multiple bugs and abort in the end" pattern. * ab/bug-if-bug: cache-tree.c: use bug() and BUG_if_bug() receive-pack: use bug() and BUG_if_bug() parse-options.c: use optbug() instead of BUG() "opts" check parse-options.c: use new bug() API for optbug() usage.c: add a non-fatal bug() function to go with BUG() common-main.c: move non-trace2 exit() behavior out of trace2.c
2 parents 098d21e + 6d40f0a commit 61daf6b

File tree

12 files changed

+231
-70
lines changed

12 files changed

+231
-70
lines changed

Documentation/technical/api-error-handling.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
Error reporting in git
22
======================
33

4-
`BUG`, `die`, `usage`, `error`, and `warning` report errors of
4+
`BUG`, `bug`, `die`, `usage`, `error`, and `warning` report errors of
55
various kinds.
66

77
- `BUG` is for failed internal assertions that should never happen,
88
i.e. a bug in git itself.
99

10+
- `bug` (lower-case, not `BUG`) is supposed to be used like `BUG` but
11+
prints a "BUG" message instead of calling `abort()`.
12+
+
13+
A call to `bug()` will then result in a "real" call to the `BUG()`
14+
function, either explicitly by invoking `BUG_if_bug()` after call(s)
15+
to `bug()`, or implicitly at `exit()` time where we'll check if we
16+
encountered any outstanding `bug()` invocations.
17+
+
18+
If there were no prior calls to `bug()` before invoking `BUG_if_bug()`
19+
the latter is a NOOP. The `BUG_if_bug()` function takes the same
20+
arguments as `BUG()` itself. Calling `BUG_if_bug()` explicitly isn't
21+
necessary, but ensures that we die as soon as possible.
22+
+
23+
If you know you had prior calls to `bug()` then calling `BUG()` itself
24+
is equivalent to calling `BUG_if_bug()`, the latter being a wrapper
25+
calling `BUG()` if we've set a flag indicating that we've called
26+
`bug()`.
27+
+
28+
This is for the convenience of APIs who'd like to potentially report
29+
more than one "bug", such as the optbug() validation in
30+
parse-options.c.
31+
1032
- `die` is for fatal application errors. It prints a message to
1133
the user and exits with status 128.
1234

Documentation/technical/api-trace2.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,8 @@ completed.)
465465
------------
466466

467467
`"error"`::
468-
This event is emitted when one of the `BUG()`, `error()`, `die()`,
469-
`warning()`, or `usage()` functions are called.
468+
This event is emitted when one of the `BUG()`, `bug()`, `error()`,
469+
`die()`, `warning()`, or `usage()` functions are called.
470470
+
471471
------------
472472
{

builtin/receive-pack.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,21 +1810,17 @@ static int should_process_cmd(struct command *cmd)
18101810
return !cmd->error_string && !cmd->skip_update;
18111811
}
18121812

1813-
static void warn_if_skipped_connectivity_check(struct command *commands,
1813+
static void BUG_if_skipped_connectivity_check(struct command *commands,
18141814
struct shallow_info *si)
18151815
{
18161816
struct command *cmd;
1817-
int checked_connectivity = 1;
18181817

18191818
for (cmd = commands; cmd; cmd = cmd->next) {
1820-
if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
1821-
error("BUG: connectivity check has not been run on ref %s",
1822-
cmd->ref_name);
1823-
checked_connectivity = 0;
1824-
}
1819+
if (should_process_cmd(cmd) && si->shallow_ref[cmd->index])
1820+
bug("connectivity check has not been run on ref %s",
1821+
cmd->ref_name);
18251822
}
1826-
if (!checked_connectivity)
1827-
BUG("connectivity check skipped???");
1823+
BUG_if_bug("connectivity check skipped???");
18281824
}
18291825

18301826
static void execute_commands_non_atomic(struct command *commands,
@@ -2005,7 +2001,7 @@ static void execute_commands(struct command *commands,
20052001
execute_commands_non_atomic(commands, si);
20062002

20072003
if (shallow_update)
2008-
warn_if_skipped_connectivity_check(commands, si);
2004+
BUG_if_skipped_connectivity_check(commands, si);
20092005
}
20102006

20112007
static struct command **queue_command(struct command **tail,

cache-tree.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -722,14 +722,14 @@ struct tree* write_in_core_index_as_tree(struct repository *repo) {
722722
ret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);
723723
if (ret == WRITE_TREE_UNMERGED_INDEX) {
724724
int i;
725-
fprintf(stderr, "BUG: There are unmerged index entries:\n");
725+
bug("there are unmerged index entries:");
726726
for (i = 0; i < index_state->cache_nr; i++) {
727727
const struct cache_entry *ce = index_state->cache[i];
728728
if (ce_stage(ce))
729-
fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
730-
(int)ce_namelen(ce), ce->name);
729+
bug("%d %.*s", ce_stage(ce),
730+
(int)ce_namelen(ce), ce->name);
731731
}
732-
BUG("unmerged index entries when writing inmemory index");
732+
BUG("unmerged index entries when writing in-core index");
733733
}
734734

735735
return lookup_tree(repo, &index_state->cache_tree->oid);

common-main.c

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,30 @@ int main(int argc, const char **argv)
5555

5656
result = cmd_main(argc, argv);
5757

58+
/* Not exit(3), but a wrapper calling our common_exit() */
59+
exit(result);
60+
}
61+
62+
static void check_bug_if_BUG(void)
63+
{
64+
if (!bug_called_must_BUG)
65+
return;
66+
BUG("on exit(): had bug() call(s) in this process without explicit BUG_if_bug()");
67+
}
68+
69+
/* We wrap exit() to call common_exit() in git-compat-util.h */
70+
int common_exit(const char *file, int line, int code)
71+
{
5872
/*
59-
* We define exit() to call trace2_cmd_exit_fl() in
60-
* git-compat-util.h. Whether we reach this or exit()
61-
* elsewhere we'll always run our trace2 exit handler.
73+
* For non-POSIX systems: Take the lowest 8 bits of the "code"
74+
* to e.g. turn -1 into 255. On a POSIX system this is
75+
* redundant, see exit(3) and wait(2), but as it doesn't harm
76+
* anything there we don't need to guard this with an "ifdef".
6277
*/
63-
exit(result);
78+
code &= 0xff;
79+
80+
check_bug_if_BUG();
81+
trace2_cmd_exit_fl(file, line, code);
82+
83+
return code;
6484
}

git-compat-util.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,9 +1331,19 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size,
13311331
/* usage.c: only to be used for testing BUG() implementation (see test-tool) */
13321332
extern int BUG_exit_code;
13331333

1334+
/* usage.c: if bug() is called we should have a BUG_if_bug() afterwards */
1335+
extern int bug_called_must_BUG;
1336+
13341337
__attribute__((format (printf, 3, 4))) NORETURN
13351338
void BUG_fl(const char *file, int line, const char *fmt, ...);
13361339
#define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__)
1340+
__attribute__((format (printf, 3, 4)))
1341+
void bug_fl(const char *file, int line, const char *fmt, ...);
1342+
#define bug(...) bug_fl(__FILE__, __LINE__, __VA_ARGS__)
1343+
#define BUG_if_bug(...) do { \
1344+
if (bug_called_must_BUG) \
1345+
BUG_fl(__FILE__, __LINE__, __VA_ARGS__); \
1346+
} while (0)
13371347

13381348
#ifndef FSYNC_METHOD_DEFAULT
13391349
#ifdef __APPLE__
@@ -1464,8 +1474,8 @@ int cmd_main(int, const char **);
14641474
* Intercept all calls to exit() and route them to trace2 to
14651475
* optionally emit a message before calling the real exit().
14661476
*/
1467-
int trace2_cmd_exit_fl(const char *file, int line, int code);
1468-
#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
1477+
int common_exit(const char *file, int line, int code);
1478+
#define exit(code) exit(common_exit(__FILE__, __LINE__, (code)))
14691479

14701480
/*
14711481
* You can mark a stack variable with UNLEAK(var) to avoid it being

parse-options.c

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ enum opt_parsed {
1414
OPT_UNSET = 1<<1,
1515
};
1616

17-
static int optbug(const struct option *opt, const char *reason)
17+
static void optbug(const struct option *opt, const char *reason)
1818
{
19-
if (opt->long_name) {
20-
if (opt->short_name)
21-
return error("BUG: switch '%c' (--%s) %s",
22-
opt->short_name, opt->long_name, reason);
23-
return error("BUG: option '%s' %s", opt->long_name, reason);
24-
}
25-
return error("BUG: switch '%c' %s", opt->short_name, reason);
19+
if (opt->long_name && opt->short_name)
20+
bug("switch '%c' (--%s) %s", opt->short_name,
21+
opt->long_name, reason);
22+
else if (opt->long_name)
23+
bug("option '%s' %s", opt->long_name, reason);
24+
else
25+
bug("switch '%c' %s", opt->short_name, reason);
2626
}
2727

2828
static const char *optname(const struct option *opt, enum opt_parsed flags)
@@ -441,28 +441,27 @@ static void check_typos(const char *arg, const struct option *options)
441441

442442
static void parse_options_check(const struct option *opts)
443443
{
444-
int err = 0;
445444
char short_opts[128];
446445

447446
memset(short_opts, '\0', sizeof(short_opts));
448447
for (; opts->type != OPTION_END; opts++) {
449448
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
450449
(opts->flags & PARSE_OPT_OPTARG))
451-
err |= optbug(opts, "uses incompatible flags "
452-
"LASTARG_DEFAULT and OPTARG");
450+
optbug(opts, "uses incompatible flags "
451+
"LASTARG_DEFAULT and OPTARG");
453452
if (opts->short_name) {
454453
if (0x7F <= opts->short_name)
455-
err |= optbug(opts, "invalid short name");
454+
optbug(opts, "invalid short name");
456455
else if (short_opts[opts->short_name]++)
457-
err |= optbug(opts, "short name already used");
456+
optbug(opts, "short name already used");
458457
}
459458
if (opts->flags & PARSE_OPT_NODASH &&
460459
((opts->flags & PARSE_OPT_OPTARG) ||
461460
!(opts->flags & PARSE_OPT_NOARG) ||
462461
!(opts->flags & PARSE_OPT_NONEG) ||
463462
opts->long_name))
464-
err |= optbug(opts, "uses feature "
465-
"not supported for dashless options");
463+
optbug(opts, "uses feature "
464+
"not supported for dashless options");
466465
switch (opts->type) {
467466
case OPTION_COUNTUP:
468467
case OPTION_BIT:
@@ -471,33 +470,33 @@ static void parse_options_check(const struct option *opts)
471470
case OPTION_NUMBER:
472471
if ((opts->flags & PARSE_OPT_OPTARG) ||
473472
!(opts->flags & PARSE_OPT_NOARG))
474-
err |= optbug(opts, "should not accept an argument");
473+
optbug(opts, "should not accept an argument");
475474
break;
476475
case OPTION_CALLBACK:
477476
if (!opts->callback && !opts->ll_callback)
478-
BUG("OPTION_CALLBACK needs one callback");
479-
if (opts->callback && opts->ll_callback)
480-
BUG("OPTION_CALLBACK can't have two callbacks");
477+
optbug(opts, "OPTION_CALLBACK needs one callback");
478+
else if (opts->callback && opts->ll_callback)
479+
optbug(opts, "OPTION_CALLBACK can't have two callbacks");
481480
break;
482481
case OPTION_LOWLEVEL_CALLBACK:
483482
if (!opts->ll_callback)
484-
BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
483+
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback");
485484
if (opts->callback)
486-
BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
485+
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback");
487486
break;
488487
case OPTION_ALIAS:
489-
BUG("OPT_ALIAS() should not remain at this point. "
490-
"Are you using parse_options_step() directly?\n"
491-
"That case is not supported yet.");
488+
optbug(opts, "OPT_ALIAS() should not remain at this point. "
489+
"Are you using parse_options_step() directly?\n"
490+
"That case is not supported yet.");
491+
break;
492492
default:
493493
; /* ok. (usually accepts an argument) */
494494
}
495495
if (opts->argh &&
496496
strcspn(opts->argh, " _") != strlen(opts->argh))
497-
err |= optbug(opts, "multi-word argh should use dash to separate words");
497+
optbug(opts, "multi-word argh should use dash to separate words");
498498
}
499-
if (err)
500-
exit(128);
499+
BUG_if_bug("invalid 'struct option'");
501500
}
502501

503502
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,

t/helper/test-trace2.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,36 @@ static int ut_006data(int argc, const char **argv)
198198
return 0;
199199
}
200200

201-
static int ut_007bug(int argc, const char **argv)
201+
static int ut_007BUG(int argc, const char **argv)
202202
{
203203
/*
204204
* Exercise BUG() to ensure that the message is printed to trace2.
205205
*/
206206
BUG("the bug message");
207207
}
208208

209+
static int ut_008bug(int argc, const char **argv)
210+
{
211+
bug("a bug message");
212+
bug("another bug message");
213+
BUG_if_bug("an explicit BUG_if_bug() following bug() call(s) is nice, but not required");
214+
return 0;
215+
}
216+
217+
static int ut_009bug_BUG(int argc, const char **argv)
218+
{
219+
bug("a bug message");
220+
bug("another bug message");
221+
/* The BUG_if_bug(...) isn't here, but we'll spot bug() calls on exit()! */
222+
return 0;
223+
}
224+
225+
static int ut_010bug_BUG(int argc, const char **argv)
226+
{
227+
bug("a bug message");
228+
BUG("a BUG message");
229+
}
230+
209231
/*
210232
* Usage:
211233
* test-tool trace2 <ut_name_1> <ut_usage_1>
@@ -222,7 +244,10 @@ static struct unit_test ut_table[] = {
222244
{ ut_004child, "004child", "[<child_command_line>]" },
223245
{ ut_005exec, "005exec", "<git_command_args>" },
224246
{ ut_006data, "006data", "[<category> <key> <value>]+" },
225-
{ ut_007bug, "007bug", "" },
247+
{ ut_007BUG, "007bug", "" },
248+
{ ut_008bug, "008bug", "" },
249+
{ ut_009bug_BUG, "009bug_BUG","" },
250+
{ ut_010bug_BUG, "010bug_BUG","" },
226251
};
227252
/* clang-format on */
228253

0 commit comments

Comments
 (0)