aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2025-04-17 12:49:40 +0200
committerJunio C Hamano <gitster@pobox.com>2025-04-17 08:15:15 -0700
commit09705696f763bac370ac74926bef137eb712c0c8 (patch)
treecd767ab0d0436f3b2e8f59cb63faf871137dfb92 /t
parentparse-options: rename `OPT_MAGNITUDE()` to `OPT_UNSIGNED()` (diff)
downloadgit-09705696f763bac370ac74926bef137eb712c0c8.tar.gz
git-09705696f763bac370ac74926bef137eb712c0c8.zip
parse-options: introduce precision handling for `OPTION_INTEGER`
The `OPTION_INTEGER` option type accepts a signed integer. The type of the underlying integer is a simple `int`, which restricts the range of values accepted by such options. But there is a catch: because the caller provides a pointer to the value via the `.value` field, which is a simple void pointer. This has two consequences: - There is no check whether the passed value is sufficiently long to store the entire range of `int`. This can lead to integer wraparound in the best case and out-of-bounds writes in the worst case. - Even when a caller knows that they want to store a value larger than `INT_MAX` they don't have a way to do so. In practice this doesn't tend to be a huge issue because users typically don't end up passing huge values to most commands. But the parsing logic is demonstrably broken, and it is too easy to get the calling convention wrong. Improve the situation by introducing a new `precision` field into the structure. This field gets assigned automatically by `OPT_INTEGER_F()` and tracks the size of the passed value. Like this it becomes possible for the caller to pass arbitrarily-sized integers and the underlying logic knows to handle it correctly by doing range checks. Furthermore, convert the code to use `strtoimax()` intstead of `strtol()` so that we can also parse values larger than `LONG_MAX`. Note that we do not yet assert signedness of the passed variable, which is another source of bugs. This will be handled in a subsequent commit. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 't')
-rw-r--r--t/helper/test-parse-options.c3
-rwxr-xr-xt/t0040-parse-options.sh23
2 files changed, 25 insertions, 1 deletions
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index fc3e2861c2..3689aee831 100644
--- a/t/helper/test-parse-options.c
+++ b/t/helper/test-parse-options.c
@@ -120,6 +120,7 @@ int cmd__parse_options(int argc, const char **argv)
};
struct string_list expect = STRING_LIST_INIT_NODUP;
struct string_list list = STRING_LIST_INIT_NODUP;
+ int16_t i16 = 0;
struct option options[] = {
OPT_BOOL(0, "yes", &boolean, "get a boolean"),
@@ -139,6 +140,7 @@ int cmd__parse_options(int argc, const char **argv)
OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4),
OPT_GROUP(""),
OPT_INTEGER('i', "integer", &integer, "get a integer"),
+ OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"),
OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"),
OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
@@ -210,6 +212,7 @@ int cmd__parse_options(int argc, const char **argv)
}
show(&expect, &ret, "boolean: %d", boolean);
show(&expect, &ret, "integer: %d", integer);
+ show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16);
show(&expect, &ret, "unsigned: %lu", unsigned_integer);
show(&expect, &ret, "timestamp: %"PRItime, timestamp);
show(&expect, &ret, "string: %s", string ? string : "(not set)");
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 65a11c8dbc..be785547ea 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -22,6 +22,7 @@ usage: test-tool parse-options <options>
-i, --[no-]integer <n>
get a integer
+ --[no-]i16 <n> get a 16 bit integer
-j <n> get a integer, too
-u, --unsigned <n> get an unsigned integer
--[no-]set23 set integer to 23
@@ -138,6 +139,7 @@ test_expect_success 'OPT_UNSIGNED() 3giga' '
cat >expect <<\EOF
boolean: 2
integer: 1729
+i16: 0
unsigned: 16384
timestamp: 0
string: 123
@@ -158,6 +160,7 @@ test_expect_success 'short options' '
cat >expect <<\EOF
boolean: 2
integer: 1729
+i16: 9000
unsigned: 16384
timestamp: 0
string: 321
@@ -169,7 +172,7 @@ file: prefix/fi.le
EOF
test_expect_success 'long options' '
- test-tool parse-options --boolean --integer 1729 --unsigned 16k \
+ test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \
--boolean --string2=321 --verbose --verbose --no-dry-run \
--abbrev=10 --file fi.le --obsolete \
>output 2>output.err &&
@@ -181,6 +184,7 @@ test_expect_success 'abbreviate to something longer than SHA1 length' '
cat >expect <<-EOF &&
boolean: 0
integer: 0
+ i16: 0
unsigned: 0
timestamp: 0
string: (not set)
@@ -255,6 +259,7 @@ test_expect_success 'superfluous value provided: cmdmode' '
cat >expect <<\EOF
boolean: 1
integer: 13
+i16: 0
unsigned: 0
timestamp: 0
string: 123
@@ -278,6 +283,7 @@ test_expect_success 'intermingled arguments' '
cat >expect <<\EOF
boolean: 0
integer: 2
+i16: 0
unsigned: 0
timestamp: 0
string: (not set)
@@ -345,6 +351,7 @@ cat >expect <<\EOF
Callback: "four", 0
boolean: 5
integer: 4
+i16: 0
unsigned: 0
timestamp: 0
string: (not set)
@@ -370,6 +377,7 @@ test_expect_success 'OPT_CALLBACK() and callback errors work' '
cat >expect <<\EOF
boolean: 1
integer: 23
+i16: 0
unsigned: 0
timestamp: 0
string: (not set)
@@ -449,6 +457,7 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' '
cat >expect <<\EOF
boolean: 0
integer: 0
+i16: 0
unsigned: 0
timestamp: 0
string: (not set)
@@ -785,4 +794,16 @@ test_expect_success 'unsigned with units but no numbers' '
test_must_be_empty out
'
+test_expect_success 'i16 limits range' '
+ test-tool parse-options --i16 32767 >out &&
+ test_grep "i16: 32767" out &&
+ test_must_fail test-tool parse-options --i16 32768 2>err &&
+ test_grep "value 32768 for option .i16. not in range \[-32768,32767\]" err &&
+
+ test-tool parse-options --i16 -32768 >out &&
+ test_grep "i16: -32768" out &&
+ test_must_fail test-tool parse-options --i16 -32769 2>err &&
+ test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err
+'
+
test_done