From 369e6d94b21d238a203ffb702605f97aca5704c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:44:09 +0200 Subject: [PATCH 1/7] parse-options: require PARSE_OPT_NOARG for OPTION_BITOP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OPTION_BITOP options don't take arguments. Make sure they are declared that way using the flag PARSE_OPT_NOARG. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 1 + 1 file changed, 1 insertion(+) diff --git a/parse-options.c b/parse-options.c index a9a39ecaef..68ff494492 100644 --- a/parse-options.c +++ b/parse-options.c @@ -591,6 +591,7 @@ static void parse_options_check(const struct option *opts) case OPTION_NEGBIT: case OPTION_SET_INT: case OPTION_NUMBER: + case OPTION_BITOP: if ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG)) optbug(opts, "should not accept an argument"); From 0d3e045b34f38d23e6160ce8aae363f358bd5cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:45:14 +0200 Subject: [PATCH 2/7] parse-options: add precision handling for PARSE_OPT_CMDMODE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build on 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) to support value variables of different sizes for PARSE_OPT_CMDMODE options. Do that by requiring their "precision" to be set and casting their "value" pointer accordingly. Call the function that does the raw casting do_get_int_value() to reserve the name get_int_value() for a more friendly wrapper we're going to introduce in one of the next patches. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- builtin/am.c | 1 + parse-options.c | 41 ++++++++++++++++++++++++++++++----- parse-options.h | 1 + t/helper/test-parse-options.c | 13 ++++++++--- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index e32a3b4c97..ce58abf2a2 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2406,6 +2406,7 @@ int cmd_am(int argc, .type = OPTION_CALLBACK, .long_name = "show-current-patch", .value = &resume_mode, + .precision = sizeof(resume_mode), .argh = "(diff|raw)", .help = N_("show the patch being applied"), .flags = PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, diff --git a/parse-options.c b/parse-options.c index 68ff494492..ddac008a5e 100644 --- a/parse-options.c +++ b/parse-options.c @@ -68,6 +68,26 @@ static char *fix_filename(const char *prefix, const char *file) return prefix_filename_except_for_dash(prefix, file); } +static int do_get_int_value(const void *value, size_t precision, intmax_t *ret) +{ + switch (precision) { + case sizeof(int8_t): + *ret = *(int8_t *)value; + return 0; + case sizeof(int16_t): + *ret = *(int16_t *)value; + return 0; + case sizeof(int32_t): + *ret = *(int32_t *)value; + return 0; + case sizeof(int64_t): + *ret = *(int64_t *)value; + return 0; + default: + return -1; + } +} + static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, const struct option *opt, enum opt_parsed flags, @@ -266,7 +286,9 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, } struct parse_opt_cmdmode_list { - int value, *value_ptr; + intmax_t value; + void *value_ptr; + size_t precision; const struct option *opt; const char *arg; enum opt_parsed flags; @@ -280,7 +302,7 @@ static void build_cmdmode_list(struct parse_opt_ctx_t *ctx, for (; opts->type != OPTION_END; opts++) { struct parse_opt_cmdmode_list *elem = ctx->cmdmode_list; - int *value_ptr = opts->value; + void *value_ptr = opts->value; if (!(opts->flags & PARSE_OPT_CMDMODE) || !value_ptr) continue; @@ -292,10 +314,13 @@ static void build_cmdmode_list(struct parse_opt_ctx_t *ctx, CALLOC_ARRAY(elem, 1); elem->value_ptr = value_ptr; - elem->value = *value_ptr; + elem->precision = opts->precision; + if (do_get_int_value(value_ptr, opts->precision, &elem->value)) + optbug(opts, "has invalid precision"); elem->next = ctx->cmdmode_list; ctx->cmdmode_list = elem; } + BUG_if_bug("invalid 'struct option'"); } static char *optnamearg(const struct option *opt, const char *arg, @@ -317,7 +342,13 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, char *opt_name, *other_opt_name; for (; elem; elem = elem->next) { - if (*elem->value_ptr == elem->value) + intmax_t new_value; + + if (do_get_int_value(elem->value_ptr, elem->precision, + &new_value)) + BUG("impossible: invalid precision"); + + if (new_value == elem->value) continue; if (elem->opt && @@ -327,7 +358,7 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, elem->opt = opt; elem->arg = arg; elem->flags = flags; - elem->value = *elem->value_ptr; + elem->value = new_value; } if (result || !elem) diff --git a/parse-options.h b/parse-options.h index 91c3e3c29b..c75a473c9e 100644 --- a/parse-options.h +++ b/parse-options.h @@ -269,6 +269,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), \ .defval = (i), \ diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index f2663dd0c0..1e03ff88f6 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -148,9 +148,16 @@ int cmd__parse_options(int argc, const char **argv) OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), - OPT_CALLBACK_F(0, "mode34", &integer, "(3|4)", - "set integer to 3 or 4 (cmdmode option)", - PARSE_OPT_CMDMODE, mode34_callback), + { + .type = OPTION_CALLBACK, + .long_name = "mode34", + .value = &integer, + .precision = sizeof(integer), + .argh = "(3|4)", + .help = "set integer to 3 or 4 (cmdmode option)", + .flags = PARSE_OPT_CMDMODE, + .callback = mode34_callback, + }, OPT_CALLBACK('L', "length", &integer, "str", "get length of ", length_callback), OPT_FILENAME('F', "file", &file, "set file to "), From c898bbc5e4b582c28379bc64b7f9c9ec96106993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:45:24 +0200 Subject: [PATCH 3/7] parse-options: add precision handling for OPTION_SET_INT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) support value variables of different sizes for OPTION_SET_INT. Do that by requiring their "precision" to be set, casting their "value" pointer accordingly and checking whether the value fits. Factor out the casting code from the part of do_get_value() that handles OPTION_INTEGER to avoid code duplication. We're going to use it in the next patches as well. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- builtin/update-index.c | 6 ++++ parse-options.c | 56 ++++++++++++++++++++++------------- parse-options.h | 2 ++ t/helper/test-parse-options.c | 1 + 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/builtin/update-index.c b/builtin/update-index.c index 538b619ba4..0c1d4ed55b 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -981,6 +981,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "assume-unchanged", .value = &mark_valid_only, + .precision = sizeof(mark_valid_only), .help = N_("mark files as \"not changing\""), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = MARK_FLAG, @@ -989,6 +990,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "no-assume-unchanged", .value = &mark_valid_only, + .precision = sizeof(mark_valid_only), .help = N_("clear assumed-unchanged bit"), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = UNMARK_FLAG, @@ -997,6 +999,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "skip-worktree", .value = &mark_skip_worktree_only, + .precision = sizeof(mark_skip_worktree_only), .help = N_("mark files as \"index-only\""), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = MARK_FLAG, @@ -1005,6 +1008,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "no-skip-worktree", .value = &mark_skip_worktree_only, + .precision = sizeof(mark_skip_worktree_only), .help = N_("clear skip-worktree bit"), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = UNMARK_FLAG, @@ -1079,6 +1083,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "fsmonitor-valid", .value = &mark_fsmonitor_only, + .precision = sizeof(mark_fsmonitor_only), .help = N_("mark files as fsmonitor valid"), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = MARK_FLAG, @@ -1087,6 +1092,7 @@ int cmd_update_index(int argc, .type = OPTION_SET_INT, .long_name = "no-fsmonitor-valid", .value = &mark_fsmonitor_only, + .precision = sizeof(mark_fsmonitor_only), .help = N_("clear fsmonitor valid bit"), .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = UNMARK_FLAG, diff --git a/parse-options.c b/parse-options.c index ddac008a5e..639f41b83b 100644 --- a/parse-options.c +++ b/parse-options.c @@ -88,6 +88,36 @@ static int do_get_int_value(const void *value, size_t precision, intmax_t *ret) } } +static enum parse_opt_result set_int_value(const struct option *opt, + enum opt_parsed flags, + intmax_t value) +{ + switch (opt->precision) { + case sizeof(int8_t): + *(int8_t *)opt->value = value; + return 0; + case sizeof(int16_t): + *(int16_t *)opt->value = value; + return 0; + case sizeof(int32_t): + *(int32_t *)opt->value = value; + return 0; + case sizeof(int64_t): + *(int64_t *)opt->value = value; + return 0; + default: + BUG("invalid precision for option %s", optname(opt, flags)); + } +} + +static int signed_int_fits(intmax_t value, size_t precision) +{ + size_t bits = precision * CHAR_BIT; + intmax_t upper_bound = INTMAX_MAX >> (bitsizeof(intmax_t) - bits); + intmax_t lower_bound = -upper_bound - 1; + return lower_bound <= value && value <= upper_bound; +} + static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, const struct option *opt, enum opt_parsed flags, @@ -136,8 +166,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, return 0; case OPTION_SET_INT: - *(int *)opt->value = unset ? 0 : opt->defval; - return 0; + return set_int_value(opt, flags, unset ? 0 : opt->defval); case OPTION_STRING: if (unset) @@ -219,23 +248,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound); - switch (opt->precision) { - case 1: - *(int8_t *)opt->value = value; - return 0; - case 2: - *(int16_t *)opt->value = value; - return 0; - case 4: - *(int32_t *)opt->value = value; - return 0; - case 8: - *(int64_t *)opt->value = value; - return 0; - default: - BUG("invalid precision for option %s", - optname(opt, flags)); - } + return set_int_value(opt, flags, value); } case OPTION_UNSIGNED: { @@ -617,10 +630,13 @@ static void parse_options_check(const struct option *opts) opts->long_name && !(opts->flags & PARSE_OPT_NONEG)) optbug(opts, "OPTION_SET_INT 0 should not be negatable"); switch (opts->type) { + case OPTION_SET_INT: + if (!signed_int_fits(opts->defval, opts->precision)) + optbug(opts, "has invalid defval"); + /* fallthru */ case OPTION_COUNTUP: case OPTION_BIT: case OPTION_NEGBIT: - case OPTION_SET_INT: case OPTION_NUMBER: case OPTION_BITOP: if ((opts->flags & PARSE_OPT_OPTARG) || diff --git a/parse-options.h b/parse-options.h index c75a473c9e..71516e4b5b 100644 --- a/parse-options.h +++ b/parse-options.h @@ -190,6 +190,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG | (f), \ .defval = (i), \ @@ -260,6 +261,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, \ .defval = 1, \ diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 1e03ff88f6..2ba2546d70 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -131,6 +131,7 @@ int cmd__parse_options(int argc, const char **argv) .short_name = 'B', .long_name = "no-fear", .value = &boolean, + .precision = sizeof(boolean), .help = "be brave", .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, .defval = 1, From 5228211c4b92052c0a38f2ab67cd0b87a7baec30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:45:33 +0200 Subject: [PATCH 4/7] parse-options: add precision handling for OPTION_BIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) support value variables of different sizes for OPTION_BIT. Do that by requiring their "precision" to be set, casting their "value" pointer accordingly and checking whether the value fits. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- builtin/write-tree.c | 1 + parse-options.c | 19 +++++++++++++++---- parse-options.h | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/builtin/write-tree.c b/builtin/write-tree.c index 5a8dc377ec..cfec044710 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -35,6 +35,7 @@ int cmd_write_tree(int argc, .type = OPTION_BIT, .long_name = "ignore-cache-tree", .value = &flags, + .precision = sizeof(flags), .help = N_("only useful for debugging"), .flags = PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, .defval = WRITE_TREE_IGNORE_CACHE_TREE, diff --git a/parse-options.c b/parse-options.c index 639f41b83b..b5c877d5e1 100644 --- a/parse-options.c +++ b/parse-options.c @@ -88,6 +88,14 @@ static int do_get_int_value(const void *value, size_t precision, intmax_t *ret) } } +static intmax_t get_int_value(const struct option *opt, enum opt_parsed flags) +{ + intmax_t ret; + if (do_get_int_value(opt->value, opt->precision, &ret)) + BUG("invalid precision for option %s", optname(opt, flags)); + return ret; +} + static enum parse_opt_result set_int_value(const struct option *opt, enum opt_parsed flags, intmax_t value) @@ -139,11 +147,14 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, return opt->ll_callback(p, opt, NULL, unset); case OPTION_BIT: + { + intmax_t value = get_int_value(opt, flags); if (unset) - *(int *)opt->value &= ~opt->defval; + value &= ~opt->defval; else - *(int *)opt->value |= opt->defval; - return 0; + value |= opt->defval; + return set_int_value(opt, flags, value); + } case OPTION_NEGBIT: if (unset) @@ -631,11 +642,11 @@ static void parse_options_check(const struct option *opts) optbug(opts, "OPTION_SET_INT 0 should not be negatable"); switch (opts->type) { case OPTION_SET_INT: + case OPTION_BIT: if (!signed_int_fits(opts->defval, opts->precision)) optbug(opts, "has invalid defval"); /* fallthru */ case OPTION_COUNTUP: - case OPTION_BIT: case OPTION_NEGBIT: case OPTION_NUMBER: case OPTION_BITOP: diff --git a/parse-options.h b/parse-options.h index 71516e4b5b..6501ca3c27 100644 --- a/parse-options.h +++ b/parse-options.h @@ -172,6 +172,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG|(f), \ .callback = NULL, \ From feeebbf1b7d5ed8761355d354e9529c791b77e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:45:53 +0200 Subject: [PATCH 5/7] parse-options: add precision handling for OPTION_NEGBIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) support value variables of different sizes for OPTION_NEGBIT. Do that by requiring their "precision" to be set, casting their "value" pointer accordingly and checking whether the value fits. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- builtin/rebase.c | 1 + parse-options.c | 11 +++++++---- parse-options.h | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2e8c4ee678..e90562a3b8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1128,6 +1128,7 @@ int cmd_rebase(int argc, .short_name = 'n', .long_name = "no-stat", .value = &options.flags, + .precision = sizeof(options.flags), .help = N_("do not show diffstat of what changed upstream"), .flags = PARSE_OPT_NOARG, .defval = REBASE_DIFFSTAT, diff --git a/parse-options.c b/parse-options.c index b5c877d5e1..ba89dc4d09 100644 --- a/parse-options.c +++ b/parse-options.c @@ -157,11 +157,14 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, } case OPTION_NEGBIT: + { + intmax_t value = get_int_value(opt, flags); if (unset) - *(int *)opt->value |= opt->defval; + value |= opt->defval; else - *(int *)opt->value &= ~opt->defval; - return 0; + value &= ~opt->defval; + return set_int_value(opt, flags, value); + } case OPTION_BITOP: if (unset) @@ -643,11 +646,11 @@ static void parse_options_check(const struct option *opts) switch (opts->type) { case OPTION_SET_INT: case OPTION_BIT: + case OPTION_NEGBIT: if (!signed_int_fits(opts->defval, opts->precision)) optbug(opts, "has invalid defval"); /* fallthru */ case OPTION_COUNTUP: - case OPTION_NEGBIT: case OPTION_NUMBER: case OPTION_BITOP: if ((opts->flags & PARSE_OPT_OPTARG) || diff --git a/parse-options.h b/parse-options.h index 6501ca3c27..076f88b384 100644 --- a/parse-options.h +++ b/parse-options.h @@ -250,6 +250,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG, \ .defval = (b), \ From 1d918bf2a5eb9d860df1dd115ef2641d7b5870e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:46:20 +0200 Subject: [PATCH 6/7] parse-options: add precision handling for OPTION_BITOP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) support value variables of different sizes for OPTION_BITOP. Do that by requiring their "precision" to be set, casting their "value" pointer accordingly and checking whether the value fits. Check if "devfal" fits into an integer variable with the given "precision", but don't check "extra", as its value is only used to clear bits, so cannot lead to an overflow. Not checking continues to allow e.g., using -1 to clear all bits even if the value variable has a narrower type than intptr_t. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 11 +++++++---- parse-options.h | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/parse-options.c b/parse-options.c index ba89dc4d09..a813511b1b 100644 --- a/parse-options.c +++ b/parse-options.c @@ -167,11 +167,14 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, } case OPTION_BITOP: + { + intmax_t value = get_int_value(opt, flags); if (unset) BUG("BITOP can't have unset form"); - *(int *)opt->value &= ~opt->extra; - *(int *)opt->value |= opt->defval; - return 0; + value &= ~opt->extra; + value |= opt->defval; + return set_int_value(opt, flags, value); + } case OPTION_COUNTUP: if (*(int *)opt->value < 0) @@ -647,12 +650,12 @@ static void parse_options_check(const struct option *opts) case OPTION_SET_INT: case OPTION_BIT: case OPTION_NEGBIT: + case OPTION_BITOP: if (!signed_int_fits(opts->defval, opts->precision)) optbug(opts, "has invalid defval"); /* fallthru */ case OPTION_COUNTUP: case OPTION_NUMBER: - case OPTION_BITOP: if ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG)) optbug(opts, "should not accept an argument"); diff --git a/parse-options.h b/parse-options.h index 076f88b384..8bdf469ae9 100644 --- a/parse-options.h +++ b/parse-options.h @@ -240,6 +240,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG|PARSE_OPT_NONEG, \ .defval = (set), \ From c1e616c39b31e78acc595790bf3a9553a022a19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 9 Jul 2025 11:46:28 +0200 Subject: [PATCH 7/7] parse-options: add precision handling for OPTION_COUNTUP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to 09705696f7 (parse-options: introduce precision handling for `OPTION_INTEGER`, 2025-04-17) support value variables of different sizes for OPTION_COUNTUP. Do that by requiring their "precision" to be set, casting their "value" pointer accordingly and checking whether the value fits. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 22 +++++++++++++++++----- parse-options.h | 1 + t/helper/test-parse-options.c | 3 +++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/parse-options.c b/parse-options.c index a813511b1b..5224203ffe 100644 --- a/parse-options.c +++ b/parse-options.c @@ -177,10 +177,22 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, } case OPTION_COUNTUP: - if (*(int *)opt->value < 0) - *(int *)opt->value = 0; - *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; - return 0; + { + size_t bits = CHAR_BIT * opt->precision; + intmax_t upper_bound = INTMAX_MAX >> (bitsizeof(intmax_t) - bits); + intmax_t value = get_int_value(opt, flags); + + if (value < 0) + value = 0; + if (unset) + value = 0; + else if (value < upper_bound) + value++; + else + return error(_("value for %s exceeds %"PRIdMAX), + optname(opt, flags), upper_bound); + return set_int_value(opt, flags, value); + } case OPTION_SET_INT: return set_int_value(opt, flags, unset ? 0 : opt->defval); @@ -651,10 +663,10 @@ static void parse_options_check(const struct option *opts) case OPTION_BIT: case OPTION_NEGBIT: case OPTION_BITOP: + case OPTION_COUNTUP: if (!signed_int_fits(opts->defval, opts->precision)) optbug(opts, "has invalid defval"); /* fallthru */ - case OPTION_COUNTUP: case OPTION_NUMBER: if ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG)) diff --git a/parse-options.h b/parse-options.h index 8bdf469ae9..312045604d 100644 --- a/parse-options.h +++ b/parse-options.h @@ -183,6 +183,7 @@ struct option { .short_name = (s), \ .long_name = (l), \ .value = (v), \ + .precision = sizeof(*v), \ .help = (h), \ .flags = PARSE_OPT_NOARG|(f), \ } diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 2ba2546d70..68579d83f3 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -178,6 +178,7 @@ int cmd__parse_options(int argc, const char **argv) .type = OPTION_COUNTUP, .short_name = '+', .value = &boolean, + .precision = sizeof(boolean), .help = "same as -b", .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, }, @@ -185,6 +186,7 @@ int cmd__parse_options(int argc, const char **argv) .type = OPTION_COUNTUP, .long_name = "ambiguous", .value = &ambiguous, + .precision = sizeof(ambiguous), .help = "positive ambiguity", .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, }, @@ -192,6 +194,7 @@ int cmd__parse_options(int argc, const char **argv) .type = OPTION_COUNTUP, .long_name = "no-ambiguous", .value = &ambiguous, + .precision = sizeof(ambiguous), .help = "negative ambiguity", .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, },