changeset 32098:39f4126d2a0d v9.0.1380

patch 9.0.1380: CTRL-X on 2**64 subtracts two Commit: https://github.com/vim/vim/commit/5fb78c3fa5c996c08a65431d698bd2c251eef5c7 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Mar 4 20:47:39 2023 +0000 patch 9.0.1380: CTRL-X on 2**64 subtracts two Problem: CTRL-X on 2**64 subtracts two. (James McCoy) Solution: Correct computation for large number. (closes https://github.com/vim/vim/issues/12103)
author Bram Moolenaar <Bram@vim.org>
date Sat, 04 Mar 2023 22:00:03 +0100
parents 79439f353fa0
children 9b58bd826012
files src/charset.c src/ex_cmds.c src/ex_getln.c src/json.c src/misc2.c src/ops.c src/option.c src/proto/charset.pro src/strings.c src/testdir/test_increment.vim src/typval.c src/version.c
diffstat 12 files changed, 69 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/charset.c
+++ b/src/charset.c
@@ -2138,7 +2138,8 @@ vim_str2nr(
     varnumber_T		*nptr,	    // return: signed result
     uvarnumber_T	*unptr,	    // return: unsigned result
     int			maxlen,     // max length of string to check
-    int			strict)     // check strictly
+    int			strict,     // check strictly
+    int			*overflow)  // when not NULL set to TRUE for overflow
 {
     char_u	    *ptr = start;
     int		    pre = 0;		// default is decimal
@@ -2209,7 +2210,11 @@ vim_str2nr(
 	    if (un <= UVARNUM_MAX / 2)
 		un = 2 * un + (uvarnumber_T)(*ptr - '0');
 	    else
+	    {
 		un = UVARNUM_MAX;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    ++ptr;
 	    if (n++ == maxlen)
 		break;
@@ -2234,7 +2239,11 @@ vim_str2nr(
 	    if (un <= UVARNUM_MAX / 8)
 		un = 8 * un + (uvarnumber_T)(*ptr - '0');
 	    else
+	    {
 		un = UVARNUM_MAX;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    ++ptr;
 	    if (n++ == maxlen)
 		break;
@@ -2258,7 +2267,11 @@ vim_str2nr(
 	    if (un <= UVARNUM_MAX / 16)
 		un = 16 * un + (uvarnumber_T)hex2nr(*ptr);
 	    else
+	    {
 		un = UVARNUM_MAX;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    ++ptr;
 	    if (n++ == maxlen)
 		break;
@@ -2282,7 +2295,11 @@ vim_str2nr(
 		    || (un == UVARNUM_MAX / 10 && digit <= UVARNUM_MAX % 10))
 		un = 10 * un + digit;
 	    else
+	    {
 		un = UVARNUM_MAX;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    ++ptr;
 	    if (n++ == maxlen)
 		break;
@@ -2310,7 +2327,11 @@ vim_str2nr(
 	{
 	    // avoid ubsan error for overflow
 	    if (un > VARNUM_MAX)
+	    {
 		*nptr = VARNUM_MIN;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    else
 		*nptr = -(varnumber_T)un;
 	}
@@ -2318,7 +2339,11 @@ vim_str2nr(
 	{
 	    // prevent a large unsigned number to become negative
 	    if (un > VARNUM_MAX)
+	    {
 		un = VARNUM_MAX;
+		if (overflow != NULL)
+		    *overflow = TRUE;
+	    }
 	    *nptr = (varnumber_T)un;
 	}
     }
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -511,7 +511,7 @@ ex_sort(exarg_T *eap)
 
 	if (sort_nr || sort_flt)
 	{
-	    // Make sure vim_str2nr doesn't read any digits past the end
+	    // Make sure vim_str2nr() doesn't read any digits past the end
 	    // of the match, by temporarily terminating the string there
 	    s2 = s + end_col;
 	    c = *s2;
@@ -539,7 +539,7 @@ ex_sort(exarg_T *eap)
 		    nrs[lnum - eap->line1].st_u.num.is_number = TRUE;
 		    vim_str2nr(s, NULL, NULL, sort_what,
 			&nrs[lnum - eap->line1].st_u.num.value,
-			NULL, 0, FALSE);
+			NULL, 0, FALSE, NULL);
 		}
 	    }
 	    else
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -4338,7 +4338,7 @@ get_list_range(char_u **str, int *num1, 
     *str = skipwhite(*str);
     if (**str == '-' || vim_isdigit(**str))  // parse "from" part of range
     {
-	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE);
+	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE, NULL);
 	*str += len;
 	*num1 = (int)num;
 	first = TRUE;
@@ -4347,7 +4347,7 @@ get_list_range(char_u **str, int *num1, 
     if (**str == ',')			// parse "to" part of range
     {
 	*str = skipwhite(*str + 1);
-	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE);
+	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE, NULL);
 	if (len > 0)
 	{
 	    *num2 = (int)num;
--- a/src/json.c
+++ b/src/json.c
@@ -540,7 +540,7 @@ json_decode_string(js_read_T *reader, ty
 		    nr = 0;
 		    len = 0;
 		    vim_str2nr(p + 2, NULL, &len,
-			     STR2NR_HEX + STR2NR_FORCE, &nr, NULL, 4, TRUE);
+			     STR2NR_HEX + STR2NR_FORCE, &nr, NULL, 4, TRUE, NULL);
 		    if (len == 0)
 		    {
 			if (res != NULL)
@@ -556,8 +556,8 @@ json_decode_string(js_read_T *reader, ty
 
 			// decode surrogate pair: \ud812\u3456
 			len = 0;
-			vim_str2nr(p + 2, NULL, &len,
-			     STR2NR_HEX + STR2NR_FORCE, &nr2, NULL, 4, TRUE);
+			vim_str2nr(p + 2, NULL, &len, STR2NR_HEX + STR2NR_FORCE,
+						    &nr2, NULL, 4, TRUE, NULL);
 			if (len == 0)
 			{
 			    if (res != NULL)
@@ -882,7 +882,7 @@ json_decode_item(js_read_T *reader, typv
 
 			    vim_str2nr(reader->js_buf + reader->js_used,
 				    NULL, &len, 0, // what
-				    &nr, NULL, 0, TRUE);
+				    &nr, NULL, 0, TRUE, NULL);
 			    if (len == 0)
 			    {
 				semsg(_(e_json_decode_error_at_str), p);
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1410,7 +1410,7 @@ find_special_key(
 	    bp += 3;	// skip t_xx, xx may be '-' or '>'
 	else if (STRNICMP(bp, "char-", 5) == 0)
 	{
-	    vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, TRUE);
+	    vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, TRUE, NULL);
 	    if (l == 0)
 	    {
 		emsg(_(e_invalid_argument));
@@ -1448,7 +1448,7 @@ find_special_key(
 	    {
 		// <Char-123> or <Char-033> or <Char-0x33>
 		vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL,
-								  &n, 0, TRUE);
+							    &n, 0, TRUE, NULL);
 		if (l == 0)
 		{
 		    emsg(_(e_invalid_argument));
--- a/src/ops.c
+++ b/src/ops.c
@@ -2781,11 +2781,12 @@ do_addsub(
 		    ? (int)STRLEN(ptr) - col
 		    : length);
 
+	int overflow = FALSE;
 	vim_str2nr(ptr + col, &pre, &length,
 		0 + (do_bin ? STR2NR_BIN : 0)
 		    + (do_oct ? STR2NR_OCT : 0)
 		    + (do_hex ? STR2NR_HEX : 0),
-		NULL, &n, maxlen, FALSE);
+		NULL, &n, maxlen, FALSE, &overflow);
 
 	// ignore leading '-' for hex and octal and bin numbers
 	if (pre && negative)
@@ -2802,10 +2803,14 @@ do_addsub(
 	    subtract ^= TRUE;
 
 	oldn = n;
-	if (subtract)
-	    n -= (uvarnumber_T)Prenum1;
-	else
-	    n += (uvarnumber_T)Prenum1;
+	if (!overflow)  // if number is too big don't add/subtract
+	{
+	    if (subtract)
+		n -= (uvarnumber_T)Prenum1;
+	    else
+		n += (uvarnumber_T)Prenum1;
+	}
+
 	// handle wraparound for decimal numbers
 	if (!pre)
 	{
--- a/src/option.c
+++ b/src/option.c
@@ -2157,7 +2157,7 @@ do_set_option_numeric(
     else if (*arg == '-' || VIM_ISDIGIT(*arg))
     {
 	// Allow negative (for 'undolevels'), octal and hex numbers.
-	vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, TRUE);
+	vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, TRUE, NULL);
 	if (i == 0 || (arg[i] != NUL && !VIM_ISWHITE(arg[i])))
 	{
 	    errmsg = e_number_required_after_equal;
--- a/src/proto/charset.pro
+++ b/src/proto/charset.pro
@@ -64,7 +64,7 @@ char_u *skiptowhite_esc(char_u *p);
 long getdigits(char_u **pp);
 long getdigits_quoted(char_u **pp);
 int vim_isblankline(char_u *lbuf);
-void vim_str2nr(char_u *start, int *prep, int *len, int what, varnumber_T *nptr, uvarnumber_T *unptr, int maxlen, int strict);
+void vim_str2nr(char_u *start, int *prep, int *len, int what, varnumber_T *nptr, uvarnumber_T *unptr, int maxlen, int strict, int *overflow);
 int hex2nr(int c);
 int hexhex2nr(char_u *p);
 int rem_backslash(char_u *str);
--- a/src/strings.c
+++ b/src/strings.c
@@ -1188,7 +1188,7 @@ f_str2nr(typval_T *argvars, typval_T *re
 	case 8: what |= STR2NR_OCT + STR2NR_OOCT + STR2NR_FORCE; break;
 	case 16: what |= STR2NR_HEX + STR2NR_FORCE; break;
     }
-    vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, FALSE);
+    vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, FALSE, NULL);
     // Text after the number is silently ignored.
     if (isneg)
 	rettv->vval.v_number = -n;
--- a/src/testdir/test_increment.vim
+++ b/src/testdir/test_increment.vim
@@ -840,6 +840,22 @@ func Test_increment_unsigned()
   set nrformats-=unsigned
 endfunc
 
+func Test_in_decrement_large_number()
+  " NOTE: 18446744073709551616 == 2^64
+  call setline(1, '18446744073709551616')
+  exec "norm! gg0\<C-X>"
+  call assert_equal('18446744073709551615', getline(1))
+
+  exec "norm! gg0\<C-X>"
+  call assert_equal('18446744073709551614', getline(1))
+
+  exec "norm! gg0\<C-A>"
+  call assert_equal('18446744073709551615', getline(1))
+
+  exec "norm! gg0\<C-A>"
+  call assert_equal('-18446744073709551615', getline(1))
+endfunc
+
 func Test_normal_increment_with_virtualedit()
   set virtualedit=all
 
--- a/src/typval.c
+++ b/src/typval.c
@@ -217,7 +217,7 @@ tv_get_bool_or_number_chk(typval_T *varp
 	    }
 	    if (varp->vval.v_string != NULL)
 		vim_str2nr(varp->vval.v_string, NULL, NULL,
-					    STR2NR_ALL, &n, NULL, 0, FALSE);
+					 STR2NR_ALL, &n, NULL, 0, FALSE, NULL);
 	    return n;
 	case VAR_LIST:
 	    emsg(_(e_using_list_as_number));
@@ -2230,7 +2230,7 @@ eval_number(
 	// decimal, hex or octal number
 	vim_str2nr(*arg, NULL, &len, skip_quotes
 		      ? STR2NR_NO_OCT + STR2NR_QUOTE
-		      : STR2NR_ALL, &n, NULL, 0, TRUE);
+		      : STR2NR_ALL, &n, NULL, 0, TRUE, NULL);
 	if (len == 0)
 	{
 	    if (evaluate)
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1380,
+/**/
     1379,
 /**/
     1378,