changeset 19350:76cb39bf1871 v8.2.0233

patch 8.2.0233: crash when using garbagecollect() in between rand() Commit: https://github.com/vim/vim/commit/4f645c54efe33d7a11e314676e503118761f08a7 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Feb 8 16:40:39 2020 +0100 patch 8.2.0233: crash when using garbagecollect() in between rand() Problem: Crash when using garbagecollect() in between rand(). Solution: Redesign the rand() and srand() implementation. (Yasuhiro Matsumoto, closes #5587, closes #5588)
author Bram Moolenaar <Bram@vim.org>
date Sat, 08 Feb 2020 16:45:04 +0100
parents dd7206f7e1ea
children d943c3167fa5
files runtime/doc/eval.txt runtime/doc/testing.txt src/evalfunc.c src/testdir/test_random.vim src/version.c
diffstat 5 files changed, 163 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2867,6 +2867,7 @@ test_refcount({expr})		Number	get the re
 test_scrollbar({which}, {value}, {dragging})
 				none	scroll in the GUI for testing
 test_setmouse({row}, {col})	none	set the mouse position for testing
+test_srand_seed([seed])		none	set seed for testing srand()
 test_settime({expr})		none	set current time for testing
 timer_info([{id}])		List	information about timers
 timer_pause({id}, {pause})	none	pause or unpause a timer
--- a/runtime/doc/testing.txt
+++ b/runtime/doc/testing.txt
@@ -210,7 +210,6 @@ test_setmouse({row}, {col})				*test_set
 			call test_setmouse(4, 20)
 			call feedkeys("\<LeftMouse>", "xt")
 
-
 test_settime({expr})					*test_settime()*
 		Set the time Vim uses internally.  Currently only used for
 		timestamps in the history, as they are used in viminfo, and
@@ -223,6 +222,10 @@ test_settime({expr})					*test_settime()
 		Can also be used as a |method|: >
 			GetTime()->test_settime()
 
+test_srand_seed([seed])					*test_srand_seed()*
+		When [seed] is given this sets the seed value used by
+		`srand()`.  When omitted the test seed is removed.
+
 ==============================================================================
 3. Assert functions				*assert-functions-details*
 
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -168,6 +168,7 @@ static void f_pyeval(typval_T *argvars, 
 #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
 static void f_pyxeval(typval_T *argvars, typval_T *rettv);
 #endif
+static void f_test_srand_seed(typval_T *argvars, typval_T *rettv);
 static void f_rand(typval_T *argvars, typval_T *rettv);
 static void f_range(typval_T *argvars, typval_T *rettv);
 static void f_reg_executing(typval_T *argvars, typval_T *rettv);
@@ -835,6 +836,7 @@ static funcentry_T global_functions[] =
 #endif
     {"test_setmouse",	2, 2, 0,	  &t_void,	f_test_setmouse},
     {"test_settime",	1, 1, FEARG_1,	  &t_void,	f_test_settime},
+    {"test_srand_seed",	0, 1, FEARG_1,	  &t_void,	f_test_srand_seed},
 #ifdef FEAT_TIMERS
     {"timer_info",	0, 1, FEARG_1,	  &t_list_dict_any, f_timer_info},
     {"timer_pause",	2, 2, FEARG_1,	  &t_void,	f_timer_pause},
@@ -5225,6 +5227,83 @@ f_pyxeval(typval_T *argvars, typval_T *r
 }
 #endif
 
+static UINT32_T srand_seed_for_testing = 0;
+static int	srand_seed_for_testing_is_used = FALSE;
+
+    static void
+f_test_srand_seed(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    if (argvars[0].v_type == VAR_UNKNOWN)
+        srand_seed_for_testing_is_used = FALSE;
+    else
+    {
+        srand_seed_for_testing = (UINT32_T)tv_get_number(&argvars[0]);
+        srand_seed_for_testing_is_used = TRUE;
+    }
+}
+
+    static void
+init_srand(UINT32_T *x)
+{
+#ifndef MSWIN
+    static int dev_urandom_state = NOTDONE;  // FAIL or OK once tried
+#endif
+
+    if (srand_seed_for_testing_is_used)
+    {
+        *x = srand_seed_for_testing;
+	return;
+    }
+#ifndef MSWIN
+    if (dev_urandom_state != FAIL)
+    {
+	int  fd = open("/dev/urandom", O_RDONLY);
+	struct {
+	    union {
+		UINT32_T number;
+		char     bytes[sizeof(UINT32_T)];
+	    } contents;
+	} buf;
+
+	// Attempt reading /dev/urandom.
+	if (fd == -1)
+	    dev_urandom_state = FAIL;
+	else
+	{
+	    buf.contents.number = 0;
+	    if (read(fd, buf.contents.bytes, sizeof(UINT32_T))
+							   != sizeof(UINT32_T))
+		dev_urandom_state = FAIL;
+	    else
+	    {
+		dev_urandom_state = OK;
+		*x = buf.contents.number;
+	    }
+	    close(fd);
+	}
+    }
+    if (dev_urandom_state != OK)
+	// Reading /dev/urandom doesn't work, fall back to time().
+#endif
+	*x = vim_time();
+}
+
+#define ROTL(x, k) ((x << k) | (x >> (32 - k)))
+#define SPLITMIX32(x, z) ( \
+    z = (x += 0x9e3779b9), \
+    z = (z ^ (z >> 16)) * 0x85ebca6b, \
+    z = (z ^ (z >> 13)) * 0xc2b2ae35, \
+    z ^ (z >> 16) \
+    )
+#define SHUFFLE_XOSHIRO128STARSTAR(x, y, z, w) \
+    result = ROTL(y * 5, 7) * 9; \
+    t = y << 9; \
+    z ^= x; \
+    w ^= y; \
+    y ^= z, x ^= w; \
+    z ^= t; \
+    w = ROTL(w, 11);
+
 /*
  * "rand()" function
  */
@@ -5232,66 +5311,57 @@ f_pyxeval(typval_T *argvars, typval_T *r
 f_rand(typval_T *argvars, typval_T *rettv)
 {
     list_T	*l = NULL;
-    static list_T *globl = NULL;
+    static UINT32_T	gx, gy, gz, gw;
+    static int	initialized = FALSE;
+    listitem_T	*lx, *ly, *lz, *lw;
     UINT32_T	x, y, z, w, t, result;
-    listitem_T	*lx, *ly, *lz, *lw;
 
     if (argvars[0].v_type == VAR_UNKNOWN)
     {
 	// When no argument is given use the global seed list.
-	if (globl == NULL)
+	if (initialized == FALSE)
 	{
 	    // Initialize the global seed list.
-	    f_srand(argvars, rettv);
-	    l = rettv->vval.v_list;
-	    if (l == NULL || list_len(l) != 4)
-	    {
-		clear_tv(rettv);
-		goto theend;
-	    }
-	    globl = l;
+	    init_srand(&x);
+
+	    gx = SPLITMIX32(x, z);
+	    gy = SPLITMIX32(x, z);
+	    gz = SPLITMIX32(x, z);
+	    gw = SPLITMIX32(x, z);
+	    initialized = TRUE;
 	}
-	else
-	    l = globl;
+
+	SHUFFLE_XOSHIRO128STARSTAR(gx, gy, gz, gw);
     }
     else if (argvars[0].v_type == VAR_LIST)
     {
 	l = argvars[0].vval.v_list;
 	if (l == NULL || list_len(l) != 4)
 	    goto theend;
+
+	lx = list_find(l, 0L);
+	ly = list_find(l, 1L);
+	lz = list_find(l, 2L);
+	lw = list_find(l, 3L);
+	if (lx->li_tv.v_type != VAR_NUMBER) goto theend;
+	if (ly->li_tv.v_type != VAR_NUMBER) goto theend;
+	if (lz->li_tv.v_type != VAR_NUMBER) goto theend;
+	if (lw->li_tv.v_type != VAR_NUMBER) goto theend;
+	x = (UINT32_T)lx->li_tv.vval.v_number;
+	y = (UINT32_T)ly->li_tv.vval.v_number;
+	z = (UINT32_T)lz->li_tv.vval.v_number;
+	w = (UINT32_T)lw->li_tv.vval.v_number;
+
+	SHUFFLE_XOSHIRO128STARSTAR(x, y, z, w);
+
+	lx->li_tv.vval.v_number = (varnumber_T)x;
+	ly->li_tv.vval.v_number = (varnumber_T)y;
+	lz->li_tv.vval.v_number = (varnumber_T)z;
+	lw->li_tv.vval.v_number = (varnumber_T)w;
     }
     else
 	goto theend;
 
-    lx = list_find(l, 0L);
-    ly = list_find(l, 1L);
-    lz = list_find(l, 2L);
-    lw = list_find(l, 3L);
-    if (lx->li_tv.v_type != VAR_NUMBER) goto theend;
-    if (ly->li_tv.v_type != VAR_NUMBER) goto theend;
-    if (lz->li_tv.v_type != VAR_NUMBER) goto theend;
-    if (lw->li_tv.v_type != VAR_NUMBER) goto theend;
-    x = (UINT32_T)lx->li_tv.vval.v_number;
-    y = (UINT32_T)ly->li_tv.vval.v_number;
-    z = (UINT32_T)lz->li_tv.vval.v_number;
-    w = (UINT32_T)lw->li_tv.vval.v_number;
-
-    // SHUFFLE_XOSHIRO128STARSTAR
-#define ROTL(x, k) ((x << k) | (x >> (32 - k)))
-    result = ROTL(y * 5, 7) * 9;
-    t = y << 9;
-    z ^= x;
-    w ^= y;
-    y ^= z, x ^= w;
-    z ^= t;
-    w = ROTL(w, 11);
-#undef ROTL
-
-    lx->li_tv.vval.v_number = (varnumber_T)x;
-    ly->li_tv.vval.v_number = (varnumber_T)y;
-    lz->li_tv.vval.v_number = (varnumber_T)z;
-    lw->li_tv.vval.v_number = (varnumber_T)w;
-
     rettv->v_type = VAR_NUMBER;
     rettv->vval.v_number = (varnumber_T)result;
     return;
@@ -5303,6 +5373,39 @@ theend:
 }
 
 /*
+ * "srand()" function
+ */
+    static void
+f_srand(typval_T *argvars, typval_T *rettv)
+{
+    UINT32_T x = 0, z;
+
+    if (rettv_list_alloc(rettv) == FAIL)
+	return;
+    if (argvars[0].v_type == VAR_UNKNOWN)
+    {
+	init_srand(&x);
+    }
+    else
+    {
+	int	    error = FALSE;
+
+	x = (UINT32_T)tv_get_number_chk(&argvars[0], &error);
+	if (error)
+	    return;
+    }
+
+    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
+    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
+    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
+    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
+}
+
+#undef ROTL
+#undef SPLITMIX32
+#undef SHUFFLE_XOSHIRO128STARSTAR
+
+/*
  * "range()" function
  */
     static void
@@ -7217,73 +7320,6 @@ f_sqrt(typval_T *argvars, typval_T *rett
 }
 #endif
 
-/*
- * "srand()" function
- */
-    static void
-f_srand(typval_T *argvars, typval_T *rettv)
-{
-    static int dev_urandom_state = -1;  // FAIL or OK once tried
-    UINT32_T x = 0, z;
-
-    if (rettv_list_alloc(rettv) == FAIL)
-	return;
-    if (argvars[0].v_type == VAR_UNKNOWN)
-    {
-	if (dev_urandom_state != FAIL)
-	{
-	    int  fd = open("/dev/urandom", O_RDONLY);
-	    struct {
-		union {
-		    UINT32_T number;
-		    char     bytes[sizeof(UINT32_T)];
-		} cont;
-	    } buf;
-
-	    // Attempt reading /dev/urandom.
-	    if (fd == -1)
-		dev_urandom_state = FAIL;
-	    else
-	    {
-		buf.cont.number = 0;
-		if (read(fd, buf.cont.bytes, sizeof(UINT32_T))
-							   != sizeof(UINT32_T))
-		    dev_urandom_state = FAIL;
-		else
-		{
-		    dev_urandom_state = OK;
-		    x = buf.cont.number;
-		}
-		close(fd);
-	    }
-
-	}
-	if (dev_urandom_state != OK)
-	    // Reading /dev/urandom doesn't work, fall back to time().
-	    x = vim_time();
-    }
-    else
-    {
-	int	    error = FALSE;
-
-	x = (UINT32_T)tv_get_number_chk(&argvars[0], &error);
-	if (error)
-	    return;
-    }
-
-#define SPLITMIX32 ( \
-    z = (x += 0x9e3779b9), \
-    z = (z ^ (z >> 16)) * 0x85ebca6b, \
-    z = (z ^ (z >> 13)) * 0xc2b2ae35, \
-    z ^ (z >> 16) \
-    )
-
-    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32);
-    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32);
-    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32);
-    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32);
-}
-
 #ifdef FEAT_FLOAT
 /*
  * "str2float()" function
--- a/src/testdir/test_random.vim
+++ b/src/testdir/test_random.vim
@@ -11,7 +11,7 @@ func Test_Rand()
 
   call test_settime(12341234)
   let s = srand()
-  if filereadable('/dev/urandom')
+  if !has('win32') && filereadable('/dev/urandom')
     " using /dev/urandom
     call assert_notequal(s, srand())
   else
@@ -21,9 +21,10 @@ func Test_Rand()
     call assert_notequal(s, srand())
   endif
 
-  call srand()
-  let v = rand()
-  call assert_notequal(v, rand())
+  call test_srand_seed(123456789)
+  call assert_equal(4284103975, rand())
+  call assert_equal(1001954530, rand())
+  call test_srand_seed()
 
   if has('float')
     call assert_fails('echo srand(1.2)', 'E805:')
@@ -38,3 +39,9 @@ func Test_Rand()
 
   call test_settime(0)
 endfunc
+
+func Test_issue_5587()
+  call rand()
+  call garbagecollect()
+  call rand()
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    233,
+/**/
     232,
 /**/
     231,