diff mbox series

[v4,6/7] parse-options: introduce precision handling for `OPTION_UNSIGNED`

Message ID 20250417-b4-pks-parse-options-integers-v4-6-9cbc76b61cfe@pks.im (mailing list archive)
State Accepted
Commit bc288c59298f199348418ca08322046c67c9a0a2
Headers show
Series parse-options: harden handling of integer values | expand

Commit Message

Patrick Steinhardt April 17, 2025, 10:49 a.m. UTC
This commit is the equivalent to the preceding commit, but instead of
introducing precision handling for `OPTION_INTEGER` we introduce it for
`OPTION_UNSIGNED`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.c               | 48 +++++++++++++++++++++++++++++++++----------
 parse-options.h               |  1 +
 parse.c                       |  2 +-
 parse.h                       |  1 +
 t/helper/test-parse-options.c |  3 +++
 t/t0040-parse-options.sh      | 18 +++++++++++++++-
 6 files changed, 60 insertions(+), 13 deletions(-)
diff mbox series

Patch

diff --git a/parse-options.c b/parse-options.c
index 768718a3972..a9a39ecaef6 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -197,7 +197,7 @@  static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 
 		if (value < lower_bound)
 			return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
-				     arg, optname(opt, flags), lower_bound, upper_bound);
+				     arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound);
 
 		switch (opt->precision) {
 		case 1:
@@ -218,21 +218,47 @@  static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 		}
 	}
 	case OPTION_UNSIGNED:
+	{
+		uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision);
+		uintmax_t value;
+
 		if (unset) {
-			*(unsigned long *)opt->value = 0;
-			return 0;
-		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
-			*(unsigned long *)opt->value = opt->defval;
-			return 0;
-		}
-		if (get_arg(p, opt, flags, &arg))
+			value = 0;
+		} else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+			value = opt->defval;
+		} else if (get_arg(p, opt, flags, &arg)) {
 			return -1;
-		if (!git_parse_ulong(arg, opt->value))
+		} else if (!*arg) {
+			return error(_("%s expects a numerical value"),
+				     optname(opt, flags));
+		} else if (!git_parse_unsigned(arg, &value, upper_bound)) {
+			if (errno == ERANGE)
+				return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
+					     arg, optname(opt, flags), (uintmax_t) 0, upper_bound);
+
 			return error(_("%s expects a non-negative integer value"
 				       " with an optional k/m/g suffix"),
 				     optname(opt, flags));
-		return 0;
+		}
+
+		switch (opt->precision) {
+		case 1:
+			*(uint8_t *)opt->value = value;
+			return 0;
+		case 2:
+			*(uint16_t *)opt->value = value;
+			return 0;
+		case 4:
+			*(uint32_t *)opt->value = value;
+			return 0;
+		case 8:
+			*(uint64_t *)opt->value = value;
+			return 0;
+		default:
+			BUG("invalid precision for option %s",
+			    optname(opt, flags));
+		}
+	}
 
 	default:
 		BUG("opt->type %d should not happen", opt->type);
diff --git a/parse-options.h b/parse-options.h
index 4c430c7273c..dc460a26ff1 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -281,6 +281,7 @@  struct option {
 	.short_name = (s), \
 	.long_name = (l), \
 	.value = (v), \
+	.precision = sizeof(*v), \
 	.argh = N_("n"), \
 	.help = (h), \
 	.flags = PARSE_OPT_NONEG, \
diff --git a/parse.c b/parse.c
index 3c47448ca67..48313571aab 100644
--- a/parse.c
+++ b/parse.c
@@ -51,7 +51,7 @@  int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
 	return 0;
 }
 
-static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
+int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
 {
 	if (value && *value) {
 		char *end;
diff --git a/parse.h b/parse.h
index 6bb9a54d9ac..ea32de9a91f 100644
--- a/parse.h
+++ b/parse.h
@@ -2,6 +2,7 @@ 
 #define PARSE_H
 
 int git_parse_signed(const char *value, intmax_t *ret, intmax_t max);
+int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max);
 int git_parse_ssize_t(const char *, ssize_t *);
 int git_parse_ulong(const char *, unsigned long *);
 int git_parse_int(const char *value, int *ret);
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index 3689aee8315..f2663dd0c07 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;
+	uint16_t u16 = 0;
 	int16_t i16 = 0;
 
 	struct option options[] = {
@@ -143,6 +144,7 @@  int cmd__parse_options(int argc, const char **argv)
 		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_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"),
 		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),
@@ -214,6 +216,7 @@  int cmd__parse_options(int argc, const char **argv)
 	show(&expect, &ret, "integer: %d", integer);
 	show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16);
 	show(&expect, &ret, "unsigned: %lu", unsigned_integer);
+	show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16);
 	show(&expect, &ret, "timestamp: %"PRItime, timestamp);
 	show(&expect, &ret, "string: %s", string ? string : "(not set)");
 	show(&expect, &ret, "abbrev: %d", abbrev);
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index be785547ead..ca55ea8228c 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -25,6 +25,7 @@  usage: test-tool parse-options <options>
     --[no-]i16 <n>        get a 16 bit integer
     -j <n>                get a integer, too
     -u, --unsigned <n>    get an unsigned integer
+    --u16 <n>             get a 16 bit unsigned integer
     --[no-]set23          set integer to 23
     --mode1               set integer to 1 (cmdmode option)
     --mode2               set integer to 2 (cmdmode option)
@@ -141,6 +142,7 @@  boolean: 2
 integer: 1729
 i16: 0
 unsigned: 16384
+u16: 0
 timestamp: 0
 string: 123
 abbrev: 7
@@ -162,6 +164,7 @@  boolean: 2
 integer: 1729
 i16: 9000
 unsigned: 16384
+u16: 32768
 timestamp: 0
 string: 321
 abbrev: 10
@@ -173,7 +176,7 @@  EOF
 
 test_expect_success 'long options' '
 	test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \
-		--boolean --string2=321 --verbose --verbose --no-dry-run \
+		--u16 32k --boolean --string2=321 --verbose --verbose --no-dry-run \
 		--abbrev=10 --file fi.le --obsolete \
 		>output 2>output.err &&
 	test_must_be_empty output.err &&
@@ -186,6 +189,7 @@  test_expect_success 'abbreviate to something longer than SHA1 length' '
 	integer: 0
 	i16: 0
 	unsigned: 0
+	u16: 0
 	timestamp: 0
 	string: (not set)
 	abbrev: 100
@@ -261,6 +265,7 @@  boolean: 1
 integer: 13
 i16: 0
 unsigned: 0
+u16: 0
 timestamp: 0
 string: 123
 abbrev: 7
@@ -285,6 +290,7 @@  boolean: 0
 integer: 2
 i16: 0
 unsigned: 0
+u16: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -353,6 +359,7 @@  boolean: 5
 integer: 4
 i16: 0
 unsigned: 0
+u16: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -379,6 +386,7 @@  boolean: 1
 integer: 23
 i16: 0
 unsigned: 0
+u16: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -459,6 +467,7 @@  boolean: 0
 integer: 0
 i16: 0
 unsigned: 0
+u16: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -806,4 +815,11 @@  test_expect_success 'i16 limits range' '
 	test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err
 '
 
+test_expect_success 'u16 limits range' '
+	test-tool parse-options --u16 65535 >out &&
+	test_grep "u16: 65535" out &&
+	test_must_fail test-tool parse-options --u16 65536 2>err &&
+	test_grep "value 65536 for option .u16. not in range \[0,65535\]" err
+'
+
 test_done