parse-options: add precision handling for OPTION_SET_INT

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 <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Jul 9, 2025 at 11:45 UTC c898bbc5e4b582c28379bc64b7f9c9ec96106993
4 files changed +45 -20
builtin/update-index.c
+6
@@ -981,6 +981,7 @@ int cmd_update_index(int argc,
981 .type = OPTION_SET_INT,
982 .long_name = "assume-unchanged",
983 .value = &mark_valid_only,
984 + .precision = sizeof(mark_valid_only),
985 .help = N_("mark files as \"not changing\""),
986 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
987 .defval = MARK_FLAG,
@@ -989,6 +990,7 @@ int cmd_update_index(int argc,
990 .type = OPTION_SET_INT,
991 .long_name = "no-assume-unchanged",
992 .value = &mark_valid_only,
993 + .precision = sizeof(mark_valid_only),
994 .help = N_("clear assumed-unchanged bit"),
995 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
996 .defval = UNMARK_FLAG,
@@ -997,6 +999,7 @@ int cmd_update_index(int argc,
999 .type = OPTION_SET_INT,
1000 .long_name = "skip-worktree",
1001 .value = &mark_skip_worktree_only,
1002 + .precision = sizeof(mark_skip_worktree_only),
1003 .help = N_("mark files as \"index-only\""),
1004 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
1005 .defval = MARK_FLAG,
@@ -1005,6 +1008,7 @@ int cmd_update_index(int argc,
1008 .type = OPTION_SET_INT,
1009 .long_name = "no-skip-worktree",
1010 .value = &mark_skip_worktree_only,
1011 + .precision = sizeof(mark_skip_worktree_only),
1012 .help = N_("clear skip-worktree bit"),
1013 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
1014 .defval = UNMARK_FLAG,
@@ -1079,6 +1083,7 @@ int cmd_update_index(int argc,
1083 .type = OPTION_SET_INT,
1084 .long_name = "fsmonitor-valid",
1085 .value = &mark_fsmonitor_only,
1086 + .precision = sizeof(mark_fsmonitor_only),
1087 .help = N_("mark files as fsmonitor valid"),
1088 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
1089 .defval = MARK_FLAG,
@@ -1087,6 +1092,7 @@ int cmd_update_index(int argc,
1092 .type = OPTION_SET_INT,
1093 .long_name = "no-fsmonitor-valid",
1094 .value = &mark_fsmonitor_only,
1095 + .precision = sizeof(mark_fsmonitor_only),
1096 .help = N_("clear fsmonitor valid bit"),
1097 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
1098 .defval = UNMARK_FLAG,
parse-options.c
+36 -20
@@ -88,6 +88,36 @@ static int do_get_int_value(const void *value, size_t precision, intmax_t *ret)
88 }
89 }
90
91 +static enum parse_opt_result set_int_value(const struct option *opt,
92 + enum opt_parsed flags,
93 + intmax_t value)
94 +{
95 + switch (opt->precision) {
96 + case sizeof(int8_t):
97 + *(int8_t *)opt->value = value;
98 + return 0;
99 + case sizeof(int16_t):
100 + *(int16_t *)opt->value = value;
101 + return 0;
102 + case sizeof(int32_t):
103 + *(int32_t *)opt->value = value;
104 + return 0;
105 + case sizeof(int64_t):
106 + *(int64_t *)opt->value = value;
107 + return 0;
108 + default:
109 + BUG("invalid precision for option %s", optname(opt, flags));
110 + }
111 +}
112 +
113 +static int signed_int_fits(intmax_t value, size_t precision)
114 +{
115 + size_t bits = precision * CHAR_BIT;
116 + intmax_t upper_bound = INTMAX_MAX >> (bitsizeof(intmax_t) - bits);
117 + intmax_t lower_bound = -upper_bound - 1;
118 + return lower_bound <= value && value <= upper_bound;
119 +}
120 +
121 static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
122 const struct option *opt,
123 enum opt_parsed flags,
@@ -136,8 +166,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
166 return 0;
167
168 case OPTION_SET_INT:
139 - *(int *)opt->value = unset ? 0 : opt->defval;
140 - return 0;
169 + return set_int_value(opt, flags, unset ? 0 : opt->defval);
170
171 case OPTION_STRING:
172 if (unset)
@@ -219,23 +248,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
248 return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
249 arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound);
250
222 - switch (opt->precision) {
223 - case 1:
224 - *(int8_t *)opt->value = value;
225 - return 0;
226 - case 2:
227 - *(int16_t *)opt->value = value;
228 - return 0;
229 - case 4:
230 - *(int32_t *)opt->value = value;
231 - return 0;
232 - case 8:
233 - *(int64_t *)opt->value = value;
234 - return 0;
235 - default:
236 - BUG("invalid precision for option %s",
237 - optname(opt, flags));
238 - }
251 + return set_int_value(opt, flags, value);
252 }
253 case OPTION_UNSIGNED:
254 {
@@ -617,10 +630,13 @@ static void parse_options_check(const struct option *opts)
630 opts->long_name && !(opts->flags & PARSE_OPT_NONEG))
631 optbug(opts, "OPTION_SET_INT 0 should not be negatable");
632 switch (opts->type) {
633 + case OPTION_SET_INT:
634 + if (!signed_int_fits(opts->defval, opts->precision))
635 + optbug(opts, "has invalid defval");
636 + /* fallthru */
637 case OPTION_COUNTUP:
638 case OPTION_BIT:
639 case OPTION_NEGBIT:
623 - case OPTION_SET_INT:
640 case OPTION_NUMBER:
641 case OPTION_BITOP:
642 if ((opts->flags & PARSE_OPT_OPTARG) ||
parse-options.h
+2
@@ -190,6 +190,7 @@ struct option {
190 .short_name = (s), \
191 .long_name = (l), \
192 .value = (v), \
193 + .precision = sizeof(*v), \
194 .help = (h), \
195 .flags = PARSE_OPT_NOARG | (f), \
196 .defval = (i), \
@@ -260,6 +261,7 @@ struct option {
261 .short_name = (s), \
262 .long_name = (l), \
263 .value = (v), \
264 + .precision = sizeof(*v), \
265 .help = (h), \
266 .flags = PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, \
267 .defval = 1, \
t/helper/test-parse-options.c
+1
@@ -131,6 +131,7 @@ int cmd__parse_options(int argc, const char **argv)
131 .short_name = 'B',
132 .long_name = "no-fear",
133 .value = &boolean,
134 + .precision = sizeof(boolean),
135 .help = "be brave",
136 .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
137 .defval = 1,