diff mbox series

[v2,01/11] bisect--helper: fix `cmd_*()` function switch default return

Message ID 20200321161020.22817-2-mirucam@gmail.com (mailing list archive)
State New, archived
Headers show
Series [v2,01/11] bisect--helper: fix `cmd_*()` function switch default return | expand

Commit Message

Miriam R. March 21, 2020, 4:10 p.m. UTC
In a `cmd_*()` function, return `error()` cannot be used
because that translates to `-1` and `cmd_*()` functions need
to return exit codes.

Let's fix switch default return.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miriam Rubio <mirucam@gmail.com>
---
 builtin/bisect--helper.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Comments

Junio C Hamano April 3, 2020, 4:58 a.m. UTC | #1
Miriam Rubio <mirucam@gmail.com> writes:

> In a `cmd_*()` function, return `error()` cannot be used
> because that translates to `-1` and `cmd_*()` functions need
> to return exit codes.
>
> Let's fix switch default return.
>
> Mentored-by: Christian Couder <chriscool@tuxfamily.org>
> Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Miriam Rubio <mirucam@gmail.com>
> ---
>  builtin/bisect--helper.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
> index c1c40b516d..1f81cff1d8 100644
> --- a/builtin/bisect--helper.c
> +++ b/builtin/bisect--helper.c
> @@ -711,7 +711,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
>  		res = bisect_start(&terms, no_checkout, argv, argc);
>  		break;
>  	default:
> -		return error("BUG: unknown subcommand '%d'", cmdmode);
> +		res = error(_("BUG: unknown subcommand."));

The return value from error() is *NOT* taken from "enum
bisect_error"; its value (-1) happens to be the same as
BISECT_FAILED, but that is by accident, and not by design.

So the above code is accident waiting to happen, while

	default:
		error(_("BUG: ..."));
		res = BISECT_FAILED;

would be a lot more correct (by design).

>  	}
>  	free_terms(&terms);

After this part, there is this code:

       if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
                       res = BISECT_OK;

        return abs(res);

This is not a problem with this patch, but the use of abs() is very
misleading, as res is always non-positive, as it is (after fixing
the patch I am responding to) taken from "enum bisect_error"
vocabulary.  "return -res;" would make the intent of the code
clearer, I think.

By the way, under what condition can the "BUG:" be reached?  Would
it only be reachable by a programming error?  If so, it would be
correct to use BUG("...") and force it die there.  If it can be
reached in some other way (e.g. an incorrect input by the user,
corruption in state files "git bisect" uses on the filesystem), then
it is *not* a "BUG".

I think "bisect--helper" is *not* called by end-user, so an unknown
command would be a BUG in the calling program, which is still part
of git, so it probably is more prudent to do something like the
following instead.

Thanks.

 builtin/bisect--helper.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index c1c40b516d..0fbd924aac 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -711,7 +711,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 		res = bisect_start(&terms, no_checkout, argv, argc);
 		break;
 	default:
-		return error("BUG: unknown subcommand '%d'", cmdmode);
+		BUG("unknown subcommand %d", (int)cmdmode);
 	}
 	free_terms(&terms);
 
@@ -722,5 +722,5 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 	if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
 		res = BISECT_OK;
 
-	return abs(res);
+	return -res;
 }
Christian Couder April 3, 2020, 1:17 p.m. UTC | #2
On Fri, Apr 3, 2020 at 6:58 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Miriam Rubio <mirucam@gmail.com> writes:
>
> > In a `cmd_*()` function, return `error()` cannot be used
> > because that translates to `-1` and `cmd_*()` functions need
> > to return exit codes.
> >
> > Let's fix switch default return.
> >
> > Mentored-by: Christian Couder <chriscool@tuxfamily.org>
> > Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> > Signed-off-by: Miriam Rubio <mirucam@gmail.com>
> > ---
> >  builtin/bisect--helper.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
> > index c1c40b516d..1f81cff1d8 100644
> > --- a/builtin/bisect--helper.c
> > +++ b/builtin/bisect--helper.c
> > @@ -711,7 +711,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
> >               res = bisect_start(&terms, no_checkout, argv, argc);
> >               break;
> >       default:
> > -             return error("BUG: unknown subcommand '%d'", cmdmode);
> > +             res = error(_("BUG: unknown subcommand."));
>
> The return value from error() is *NOT* taken from "enum
> bisect_error"; its value (-1) happens to be the same as
> BISECT_FAILED, but that is by accident, and not by design.

In bisect.h we have made sure that BISECT_FAILED would be -1, so it is
not by accident:

enum bisect_error {
        BISECT_OK = 0,
        BISECT_FAILED = -1,
        BISECT_ONLY_SKIPPED_LEFT = -2,
        BISECT_MERGE_BASE_CHECK = -3,
        BISECT_NO_TESTABLE_COMMIT = -4,
        BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND = -10,
        BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11
};

> So the above code is accident waiting to happen, while
>
>         default:
>                 error(_("BUG: ..."));
>                 res = BISECT_FAILED;
>
> would be a lot more correct (by design).

I think it is very unlikely that we will ever change the value
returned by error(), so I don't think there is an accident waiting to
happen.

Maybe we should make it clearer though in bisect.h in the comment
before the enum, that we chose -1 for BISECT_FAILED so that it is the
same as what error() returns. Maybe something like "BISECT_FAILED
error code: default error code, should be the same value as what
error() returns."

> After this part, there is this code:
>
>        if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
>                        res = BISECT_OK;
>
>         return abs(res);
>
> This is not a problem with this patch, but the use of abs() is very
> misleading, as res is always non-positive, as it is (after fixing
> the patch I am responding to) taken from "enum bisect_error"
> vocabulary.  "return -res;" would make the intent of the code
> clearer, I think.

I am ok with using "-res" here. There are other places where
"abs(res)" is needed though, so code could look a bit more consistent
if "abs(res)" was used here too.

> By the way, under what condition can the "BUG:" be reached?  Would
> it only be reachable by a programming error?

It could happen if a user would try to directly use `git
bisect--helper <cmd> ...` with an unsupported <cmd>. Users are not
supposed to directly use bisect--helper though.

It could also happen if a developer uses `git bisect--helper <cmd>
...` in a script, program or alias if <cmd> is not properly spelled or
is unavailable for some reason.

> If so, it would be
> correct to use BUG("...") and force it die there.  If it can be
> reached in some other way (e.g. an incorrect input by the user,
> corruption in state files "git bisect" uses on the filesystem), then
> it is *not* a "BUG".

In this case I think it's difficult to tell if it will be a bug or not.

> I think "bisect--helper" is *not* called by end-user, so an unknown
> command would be a BUG in the calling program, which is still part
> of git, so it probably is more prudent to do something like the
> following instead.

I am ok with both ways.

Thanks,
Christian.
Junio C Hamano April 3, 2020, 6:30 p.m. UTC | #3
Christian Couder <christian.couder@gmail.com> writes:

>> The return value from error() is *NOT* taken from "enum
>> bisect_error"; its value (-1) happens to be the same as
>> BISECT_FAILED, but that is by accident, and not by design.
>
> In bisect.h we have made sure that BISECT_FAILED would be -1, so it is
> not by accident:

It *is* accident waiting to happen, unless you have a comment to
tell future developers that they are forbidden from changing the
assignment of values; "We've made sure" alone is not a good excuse.

> enum bisect_error {
>         BISECT_OK = 0,
>         BISECT_FAILED = -1,
>         BISECT_ONLY_SKIPPED_LEFT = -2,
>         BISECT_MERGE_BASE_CHECK = -3,
>         BISECT_NO_TESTABLE_COMMIT = -4,
>         BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND = -10,
>         BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11
> };
>
>> So the above code is accident waiting to happen, while
>>
>>         default:
>>                 error(_("BUG: ..."));
>>                 res = BISECT_FAILED;
>>
>> would be a lot more correct (by design).
>
> I think it is very unlikely that we will ever change the value
> returned by error(), so I don't think there is an accident waiting to
> happen.
>
> Maybe we should make it clearer though in bisect.h in the comment
> before the enum, that we chose -1 for BISECT_FAILED so that it is the
> same as what error() returns....

In this particular case, you do not even need to rely on such a
comment to tie hands of future developers' needs (e.g. they may need
to add a new enum value that must come between OK and FAILED because
they will find "if (err < FAILED)" is an easy way to do something
they need to do; an ordering requirement similar to how "enum
todo_command" in sequencer.h wants to enforce certain ordering of
values is not uncommon, and they will find it awkward if they are
told that they cannot move FAILED to some value other than -1).  You
were even shown a better way to separate "res" from the value
error() returns (which will always be -1) and BISECT_FAILED (which
may be -1 right now, but future developers may want to change it,
and you have the power to allow it).

I do not see why you are still giving a lame excuse after that.

I even do not like the fact that you are doing so in the context of
being a mentor---please do not spoil the opportunity to educate good
developers of our future; instead please lead them by showing a good
example.

> I am ok with using "-res" here. There are other places where
> "abs(res)" is needed though, so code could look a bit more consistent
> if "abs(res)" was used here too.

If there are two kinds of codepaths, some *need* to deal with both
positive and negative for good reasons, and others only need to deal
with non-positive values, it would make it easier to understand the
code by consistently using -res for the latter while using abs() for
the former.

This is a tangent, but a codepath that needs abs(res) may need to be
reexaimined for correctness, as it is likely that it is a sign that
a sloppy developer swept a deeper underlying problem under the rug.

Imagine that a function A, in one if() statement in it, returns
error() whose value is -1, and in some other if() statement returns
BAD_XYZZY whose value is 1.  The function A also returns BAD_FROTZ
whose value is 2.  The only guarantee the caller gets from the
function A is that an error is signaled by non-zero value, and zero
means success.

And if you use abs() to squash an error and BAD_XYZZY into 1 in your
function B that calls A, what good are you doing to the callers of
your B?  They cannot tell between error and BAD_XYZZY, but they can
tell them from BAD_FROTZ, but does such an arrangement make any
sense?  It would be far more rational to make your B either (1)
return -1 for any error, if B thinks callers do not have to care
(which could be a valid stance to take, depending on the nature of
B), or (2) add an error code to BAD_{XYZZY,FROTZ} family and map -1
that comes from an error to that value, so that the callers can tell
them apart, or (3) do the equivalent of (2) but do so inside A (not
in B), and update call the callers of A.

Any of the above is more sensible and future-proof, compared to
blindly using abs(res) and claim that you are safe because you are
not returning a negative value.

>> By the way, under what condition can the "BUG:" be reached?  Would
>> it only be reachable by a programming error?
>
> It could happen if a user would try to directly use `git
> bisect--helper <cmd> ...` with an unsupported <cmd>. Users are not
> supposed to directly use bisect--helper though.
>
> It could also happen if a developer uses `git bisect--helper <cmd>
> ...` in a script, program or alias if <cmd> is not properly spelled or
> is unavailable for some reason.

If the user can legitimately trigger it, it is not a "BUG:".  Let's
make sure we use "BUG:" (whether it comes from BUG("...") or handcrafted
message like this one here) only when there is a bug in our program.
In other words, when a user sees "BUG:" emitted from our program and
reports it to us, there shouldn't be a room for us to say, "eh,
thanks for reporting, but it is an intended behaviour---you are just
holding it wrong".

If I did not know bisect--helper is its way out (which would be the
endgame of making "git bisect" fully converted to C), I would say
that we should just mark it as an error, but in the endgame state,
there won't be any end-user visible bisect--helper, so I am OK to
label it as a "BUG:" in this case.  It will be in the endgame state.

Thanks.
diff mbox series

Patch

diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index c1c40b516d..1f81cff1d8 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -711,7 +711,7 @@  int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 		res = bisect_start(&terms, no_checkout, argv, argc);
 		break;
 	default:
-		return error("BUG: unknown subcommand '%d'", cmdmode);
+		res = error(_("BUG: unknown subcommand."));
 	}
 	free_terms(&terms);