Skip to content

Commit 2d225ed

Browse files
committed
Merge branch 'ab/bug-if-bug' into seen
* 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 83cc4cd + 2c50062 commit 2d225ed

File tree

12 files changed

+217
-71
lines changed

12 files changed

+217
-71
lines changed

Documentation/technical/api-error-handling.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
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+
This is for the convenience of APIs who'd like to potentially report
24+
more than one "bug", such as the optbug() validation in
25+
parse-options.c.
26+
1027
- `die` is for fatal application errors. It prints a message to
1128
the user and exits with status 128.
1229

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 inmemory index");
733733
}
734734

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

common-main.c

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,35 @@ int main(int argc, const char **argv)
5656
result = cmd_main(argc, argv);
5757

5858
/*
59-
* We define exit() to call trace2_cmd_exit_fl() in
60-
* git-compat-util.h. Whether we reach this or exit()
59+
* We define exit() to call common_exit(), which will in turn
60+
* call trace2_cmd_exit_fl(). Whether we reach this or exit()
6161
* elsewhere we'll always run our trace2 exit handler.
6262
*/
6363
exit(result);
6464
}
65+
66+
static void check_bug_if_BUG(void)
67+
{
68+
if (!bug_called_must_BUG)
69+
return;
70+
71+
/* BUG_vfl() calls exit(), which calls us again */
72+
bug_called_must_BUG = 0;
73+
BUG("on exit(): had bug() call(s) in this process without explicit BUG_if_bug()");
74+
}
75+
76+
int common_exit(const char *file, int line, int code)
77+
{
78+
/*
79+
* For non-POSIX systems: Take the lowest 8 bits of the "code"
80+
* to e.g. turn -1 into 255. On a POSIX system this is
81+
* redundant, see exit(3) and wait(2), but as it doesn't harm
82+
* anything there we don't need to guard this with an "ifdef".
83+
*/
84+
code &= 0xff;
85+
86+
check_bug_if_BUG();
87+
trace2_cmd_exit_fl(file, line, code);
88+
89+
return code;
90+
}

git-compat-util.h

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

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

13391351
#ifndef FSYNC_METHOD_DEFAULT
13401352
#ifdef __APPLE__
@@ -1461,12 +1473,20 @@ static inline int is_missing_file_error(int errno_)
14611473

14621474
int cmd_main(int, const char **);
14631475

1476+
/**
1477+
* The exit() function is defined as common_exit() in
1478+
* git-compat-util.h.
1479+
*
1480+
* Intercepting the exit() allows us to hook in at-exit behavior, such
1481+
* emitting trace2 logging before calling the real exit().
1482+
*/
1483+
int common_exit(const char *file, int line, int code);
1484+
14641485
/*
14651486
* Intercept all calls to exit() and route them to trace2 to
14661487
* optionally emit a message before calling the real exit().
14671488
*/
1468-
int trace2_cmd_exit_fl(const char *file, int line, int code);
1469-
#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
1489+
#define exit(code) exit(common_exit(__FILE__, __LINE__, (code)))
14701490

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

parse-options.c

Lines changed: 37 additions & 30 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,41 @@ 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:
477-
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");
476+
if (!opts->callback && !opts->ll_callback) {
477+
optbug(opts, "OPTION_CALLBACK needs one callback");
478+
break;
479+
}
480+
if (opts->callback && opts->ll_callback) {
481+
optbug(opts, "OPTION_CALLBACK can't have two callbacks");
482+
break;
483+
}
481484
break;
482485
case OPTION_LOWLEVEL_CALLBACK:
483-
if (!opts->ll_callback)
484-
BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
485-
if (opts->callback)
486-
BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
486+
if (!opts->ll_callback) {
487+
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback");
488+
break;
489+
}
490+
if (opts->callback) {
491+
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback");
492+
break;
493+
}
487494
break;
488495
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.");
496+
optbug(opts, "OPT_ALIAS() should not remain at this point. "
497+
"Are you using parse_options_step() directly?\n"
498+
"That case is not supported yet.");
499+
break;
492500
default:
493501
; /* ok. (usually accepts an argument) */
494502
}
495503
if (opts->argh &&
496504
strcspn(opts->argh, " _") != strlen(opts->argh))
497-
err |= optbug(opts, "multi-word argh should use dash to separate words");
505+
optbug(opts, "multi-word argh should use dash to separate words");
498506
}
499-
if (err)
500-
exit(128);
507+
BUG_if_bug("invalid 'struct option'");
501508
}
502509

503510
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,

t/helper/test-trace2.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,30 @@ 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+
209225
/*
210226
* Usage:
211227
* test-tool trace2 <ut_name_1> <ut_usage_1>
@@ -222,7 +238,9 @@ static struct unit_test ut_table[] = {
222238
{ ut_004child, "004child", "[<child_command_line>]" },
223239
{ ut_005exec, "005exec", "<git_command_args>" },
224240
{ ut_006data, "006data", "[<category> <key> <value>]+" },
225-
{ ut_007bug, "007bug", "" },
241+
{ ut_007BUG, "007bug", "" },
242+
{ ut_008bug, "008bug", "" },
243+
{ ut_009bug_BUG, "009bug_BUG","" },
226244
};
227245
/* clang-format on */
228246

t/t0210-trace2-normal.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,58 @@ test_expect_success 'BUG messages are written to trace2' '
168168
test_cmp expect actual
169169
'
170170

171+
test_expect_success 'bug messages with BUG_if_bug() are written to trace2' '
172+
test_when_finished "rm trace.normal actual expect" &&
173+
test_expect_code 99 env GIT_TRACE2="$(pwd)/trace.normal" \
174+
test-tool trace2 008bug 2>err &&
175+
cat >expect <<-\EOF &&
176+
a bug message
177+
another bug message
178+
an explicit BUG_if_bug() following bug() call(s) is nice, but not required
179+
EOF
180+
sed "s/^.*: //" <err >actual &&
181+
test_cmp expect actual &&
182+
183+
perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
184+
cat >expect <<-EOF &&
185+
version $V
186+
start _EXE_ trace2 008bug
187+
cmd_name trace2 (trace2)
188+
error a bug message
189+
error another bug message
190+
error an explicit BUG_if_bug() following bug() call(s) is nice, but not required
191+
exit elapsed:_TIME_ code:99
192+
atexit elapsed:_TIME_ code:99
193+
EOF
194+
test_cmp expect actual
195+
'
196+
197+
test_expect_success 'bug messages without explicit BUG_if_bug() are written to trace2' '
198+
test_when_finished "rm trace.normal actual expect" &&
199+
test_expect_code 99 env GIT_TRACE2="$(pwd)/trace.normal" \
200+
test-tool trace2 009bug_BUG 2>err &&
201+
cat >expect <<-\EOF &&
202+
a bug message
203+
another bug message
204+
had bug() call(s) in this process without explicit BUG_if_bug()
205+
EOF
206+
sed "s/^.*: //" <err >actual &&
207+
test_cmp expect actual &&
208+
209+
perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
210+
cat >expect <<-EOF &&
211+
version $V
212+
start _EXE_ trace2 009bug_BUG
213+
cmd_name trace2 (trace2)
214+
error a bug message
215+
error another bug message
216+
error on exit(): had bug() call(s) in this process without explicit BUG_if_bug()
217+
exit elapsed:_TIME_ code:99
218+
atexit elapsed:_TIME_ code:99
219+
EOF
220+
test_cmp expect actual
221+
'
222+
171223
sane_unset GIT_TRACE2_BRIEF
172224

173225
# Now test without environment variables and get all Trace2 settings

0 commit comments

Comments
 (0)