Skip to content

Commit a3d87b0

Browse files
pks-tgitster
authored andcommitted
parse-options: introduce bounded integer options
In the preceding commits we have introduced integer precisions. The precision merely tracks bounds of the underlying data types so that we don't try to for example write a `size_t` into an `unsigned`, which could otherwise cause out-of-bounds writes. Some options may have bounds that are stricter than the underlying data type. Right now, users of any such options would have to manually verify that the value passed to such an option is inside the expected bounds. This is rather tedious, and it leads to code duplication across sites that wish to perform such bounds checks. Introduce `OPT_*_BOUNDED()` options that alleviate this issue. Users can optionally specify both a lower and upper bound, and if set we will verify that the value passed by the user is in that range. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 56146f9 commit a3d87b0

File tree

4 files changed

+125
-5
lines changed

4 files changed

+125
-5
lines changed

parse-options.c

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,20 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
177177
intmax_t lower_bound = -upper_bound - 1;
178178
intmax_t value;
179179

180+
if (opt->lower_bound) {
181+
if (opt->lower_bound < lower_bound)
182+
BUG("invalid lower bound for option %s", optname(opt, flags));
183+
if (opt->lower_bound > lower_bound)
184+
lower_bound = opt->lower_bound;
185+
}
186+
187+
if (opt->upper_bound) {
188+
if (opt->upper_bound > (uintmax_t)upper_bound)
189+
BUG("invalid upper bound for option %s", optname(opt, flags));
190+
if (opt->upper_bound < (uintmax_t)upper_bound)
191+
upper_bound = opt->upper_bound;
192+
}
193+
180194
if (unset) {
181195
value = 0;
182196
} else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
@@ -225,8 +239,16 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
225239
case OPTION_UNSIGNED:
226240
{
227241
uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision);
242+
uintmax_t lower_bound = 0;
228243
uintmax_t value;
229244

245+
if (opt->lower_bound < 0)
246+
BUG("invalid lower bound for option %s", optname(opt, flags));
247+
if (opt->lower_bound > 0)
248+
lower_bound = opt->lower_bound;
249+
if (opt->upper_bound && opt->upper_bound < upper_bound)
250+
upper_bound = opt->upper_bound;
251+
230252
if (unset) {
231253
value = 0;
232254
} else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
@@ -247,16 +269,16 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
247269
optname(opt, flags));
248270
if (errno == ERANGE)
249271
return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
250-
arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
272+
arg, optname(opt, flags), (uintmax_t)lower_bound, (uintmax_t)upper_bound);
251273
if (errno)
252274
return error_errno(_("value %s for %s cannot be parsed"),
253275
arg, optname(opt, flags));
254276

255277
}
256278

257-
if (value > upper_bound)
279+
if (value < lower_bound || value > upper_bound)
258280
return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
259-
arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
281+
arg, optname(opt, flags), (uintmax_t)lower_bound, (uintmax_t)upper_bound);
260282

261283
switch (opt->precision) {
262284
case 1:
@@ -279,8 +301,16 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
279301
case OPTION_MAGNITUDE:
280302
{
281303
uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision);
304+
uintmax_t lower_bound = 0;
282305
unsigned long value;
283306

307+
if (opt->lower_bound < 0)
308+
BUG("invalid lower bound for option %s", optname(opt, flags));
309+
if (opt->lower_bound > 0)
310+
lower_bound = opt->lower_bound;
311+
if (opt->upper_bound && opt->upper_bound < upper_bound)
312+
upper_bound = opt->upper_bound;
313+
284314
if (unset) {
285315
value = 0;
286316
} else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
@@ -293,9 +323,9 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
293323
optname(opt, flags));
294324
}
295325

296-
if (value > upper_bound)
326+
if (value < lower_bound || value > upper_bound)
297327
return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
298-
arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
328+
arg, optname(opt, flags), (uintmax_t)lower_bound, (uintmax_t)upper_bound);
299329

300330
switch (opt->precision) {
301331
case 1:

parse-options.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,
9797
* precision of the integer pointed to by `value` in number of bytes. Should
9898
* typically be its `sizeof()`.
9999
*
100+
* `lower_bound`,`upper_bound`::
101+
* lower and upper bound of the integer to further restrict the accepted
102+
* range of integer values. `0` will use the minimum and maximum values for
103+
* the integer type of the specified precision. Specifying a bound that does
104+
* not fit into an integer type of the specified precision will trigger a
105+
* bug.
106+
*
100107
* `argh`::
101108
* token to explain the kind of argument this option wants. Does not
102109
* begin in capital letter, and does not end with a full stop.
@@ -157,6 +164,8 @@ struct option {
157164
const char *long_name;
158165
void *value;
159166
size_t precision;
167+
intmax_t lower_bound;
168+
uintmax_t upper_bound;
160169
const char *argh;
161170
const char *help;
162171

@@ -225,6 +234,19 @@ struct option {
225234
.help = (h), \
226235
.flags = (f), \
227236
}
237+
#define OPT_INTEGER_BOUNDED_F(s, l, v, lower, upper, h, f) { \
238+
.type = OPTION_INTEGER, \
239+
.short_name = (s), \
240+
.long_name = (l), \
241+
.value = (v) + BARF_UNLESS_SIGNED(*(v)), \
242+
.precision = sizeof(*v), \
243+
.lower_bound = (lower), \
244+
.upper_bound = (upper), \
245+
.argh = N_("n"), \
246+
.help = (h), \
247+
.flags = (f), \
248+
}
249+
228250
#define OPT_UNSIGNED_F(s, l, v, h, f) { \
229251
.type = OPTION_UNSIGNED, \
230252
.short_name = (s), \
@@ -235,6 +257,18 @@ struct option {
235257
.help = (h), \
236258
.flags = (f), \
237259
}
260+
#define OPT_UNSIGNED_BOUNDED_F(s, l, v, lower, upper, h, f) { \
261+
.type = OPTION_UNSIGNED, \
262+
.short_name = (s), \
263+
.long_name = (l), \
264+
.value = (v) + BARF_UNLESS_UNSIGNED(*(v)), \
265+
.precision = sizeof(*v), \
266+
.lower_bound = (lower), \
267+
.upper_bound = (upper), \
268+
.argh = N_("n"), \
269+
.help = (h), \
270+
.flags = (f), \
271+
}
238272

239273
#define OPT_END() { \
240274
.type = OPTION_END, \
@@ -287,7 +321,12 @@ struct option {
287321
#define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0)
288322

289323
#define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0)
324+
#define OPT_INTEGER_BOUNDED(s, l, v, lower, upper, h) \
325+
OPT_INTEGER_BOUNDED_F(s, l, v, lower, upper, h, 0)
290326
#define OPT_UNSIGNED(s, l, v, h) OPT_UNSIGNED_F(s, l, v, h, 0)
327+
#define OPT_UNSIGNED_BOUNDED(s, l, v, lower, upper, h) \
328+
OPT_UNSIGNED_BOUNDED_F(s, l, v, lower, upper, h, 0)
329+
291330
#define OPT_MAGNITUDE(s, l, v, h) { \
292331
.type = OPTION_MAGNITUDE, \
293332
.short_name = (s), \
@@ -298,6 +337,19 @@ struct option {
298337
.help = (h), \
299338
.flags = PARSE_OPT_NONEG, \
300339
}
340+
#define OPT_MAGNITUDE_BOUNDED(s, l, v, lower, upper, h) { \
341+
.type = OPTION_MAGNITUDE, \
342+
.short_name = (s), \
343+
.long_name = (l), \
344+
.value = (v) + BARF_UNLESS_UNSIGNED(*(v)), \
345+
.precision = sizeof(*v), \
346+
.lower_bound = (lower), \
347+
.upper_bound = (upper), \
348+
.argh = N_("n"), \
349+
.help = (h), \
350+
.flags = PARSE_OPT_NONEG, \
351+
}
352+
301353
#define OPT_STRING(s, l, v, a, h) OPT_STRING_F(s, l, v, a, h, 0)
302354
#define OPT_STRING_LIST(s, l, v, a, h) { \
303355
.type = OPTION_CALLBACK, \

t/helper/test-parse-options.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ int cmd__parse_options(int argc, const char **argv)
120120
};
121121
struct string_list expect = STRING_LIST_INIT_NODUP;
122122
struct string_list list = STRING_LIST_INIT_NODUP;
123+
uint32_t mbounded = 0, ubounded = 0;
123124
uint16_t m16 = 0, u16 = 0;
125+
int32_t ibounded = 0;
124126
int16_t i16 = 0;
125127

126128
struct option options[] = {
@@ -142,10 +144,13 @@ int cmd__parse_options(int argc, const char **argv)
142144
OPT_GROUP(""),
143145
OPT_INTEGER('i', "integer", &integer, "get a integer"),
144146
OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"),
147+
OPT_INTEGER_BOUNDED(0, "ibounded", &ibounded, -10, 10, "get a bounded integer between [-10,10]"),
145148
OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"),
149+
OPT_UNSIGNED_BOUNDED(0, "ubounded", &ubounded, 10, 100, "get a bounded unsigned integer between [10,100]"),
146150
OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
147151
OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"),
148152
OPT_MAGNITUDE(0, "m16", &m16, "get a 16 bit magnitude"),
153+
OPT_MAGNITUDE_BOUNDED(0, "mbounded", &mbounded, 10, 100, "get a bounded magnitude between [10,100]"),
149154
OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
150155
OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1),
151156
OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2),

t/t0040-parse-options.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ usage: test-tool parse-options <options>
2323
-i, --[no-]integer <n>
2424
get a integer
2525
--[no-]i16 <n> get a 16 bit integer
26+
--[no-]ibounded <n> get a bounded integer between [-10,10]
2627
--[no-]u16 <n> get a 16 bit unsigned integer
28+
--[no-]ubounded <n> get a bounded unsigned integer between [10,100]
2729
-j <n> get a integer, too
2830
-m, --magnitude <n> get a magnitude
2931
--m16 <n> get a 16 bit magnitude
32+
--mbounded <n> get a bounded magnitude between [10,100]
3033
--[no-]set23 set integer to 23
3134
--mode1 set integer to 1 (cmdmode option)
3235
--mode2 set integer to 2 (cmdmode option)
@@ -848,4 +851,34 @@ test_expect_success 'u16 does not accept negative value' '
848851
test_must_be_empty out
849852
'
850853

854+
test_expect_success 'ibounded does not accept outside range' '
855+
test_must_fail test-tool parse-options --ibounded -11 >out 2>err &&
856+
test_grep "value -11 for option .ibounded. not in range \[-10,10\]" err &&
857+
test_must_fail test-tool parse-options --ibounded 11 >out 2>err &&
858+
test_grep "value 11 for option .ibounded. not in range \[-10,10\]" err &&
859+
test-tool parse-options --ibounded -10 &&
860+
test-tool parse-options --ibounded 0 &&
861+
test-tool parse-options --ibounded 10
862+
'
863+
864+
test_expect_success 'ubounded does not accept outside range' '
865+
test_must_fail test-tool parse-options --ubounded 9 >out 2>err &&
866+
test_grep "value 9 for option .ubounded. not in range \[10,100\]" err &&
867+
test_must_fail test-tool parse-options --ubounded 101 >out 2>err &&
868+
test_grep "value 101 for option .ubounded. not in range \[10,100\]" err &&
869+
test-tool parse-options --ubounded 10 &&
870+
test-tool parse-options --ubounded 50 &&
871+
test-tool parse-options --ubounded 100
872+
'
873+
874+
test_expect_success 'mbounded does not accept outside range' '
875+
test_must_fail test-tool parse-options --mbounded 9 >out 2>err &&
876+
test_grep "value 9 for option .mbounded. not in range \[10,100\]" err &&
877+
test_must_fail test-tool parse-options --mbounded 101 >out 2>err &&
878+
test_grep "value 101 for option .mbounded. not in range \[10,100\]" err &&
879+
test-tool parse-options --mbounded 10 &&
880+
test-tool parse-options --mbounded 50 &&
881+
test-tool parse-options --mbounded 100
882+
'
883+
851884
test_done

0 commit comments

Comments
 (0)