summaryrefslogtreecommitdiff
path: root/lib/argp-parse.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2017-05-19 15:39:06 -0700
committerPaul Eggert <eggert@cs.ucla.edu>2017-05-19 15:39:44 -0700
commit06094e390b058f1318e7885b13c3b5c2aaa42927 (patch)
tree869d7c95a412ebcc767551a5c43172e59f2de3b3 /lib/argp-parse.c
parenta50e906c0c046c57938f786ffb82b36beea75043 (diff)
downloadgnulib-06094e390b058f1318e7885b13c3b5c2aaa42927.tar.gz
argp: fix shift bug
* lib/argp-parse.c (parser_parse_opt): Rework to avoid undefined behavior on shift overflow, caught by gcc -fsanitize=undefined.
Diffstat (limited to 'lib/argp-parse.c')
-rw-r--r--lib/argp-parse.c22
1 files changed, 16 insertions, 6 deletions
diff --git a/lib/argp-parse.c b/lib/argp-parse.c
index 3f723bc83f..4723a2b29d 100644
--- a/lib/argp-parse.c
+++ b/lib/argp-parse.c
@@ -740,12 +740,22 @@ parser_parse_opt (struct parser *parser, int opt, char *val)
}
}
else
- /* A long option. We use shifts instead of masking for extracting
- the user value in order to preserve the sign. */
- err =
- group_parse (&parser->groups[group_key - 1], &parser->state,
- (opt << GROUP_BITS) >> GROUP_BITS,
- parser->opt_data.optarg);
+ /* A long option. Preserve the sign in the user key, without
+ invoking undefined behavior. Assume two's complement. */
+ {
+ unsigned uopt = opt;
+ unsigned ushifted_user_key = uopt << GROUP_BITS;
+ int shifted_user_key = ushifted_user_key;
+ int user_key;
+ if (-1 >> 1 == -1)
+ user_key = shifted_user_key >> GROUP_BITS;
+ else
+ user_key = ((ushifted_user_key >> GROUP_BITS)
+ - (shifted_user_key < 0 ? 1 << USER_BITS : 0));
+ err =
+ group_parse (&parser->groups[group_key - 1], &parser->state,
+ user_key, parser->opt_data.optarg);
+ }
if (err == EBADKEY)
/* At least currently, an option not recognized is an error in the