0
0
mirror of https://github.com/vim/vim.git synced 2025-08-27 20:13:38 -04:00

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)
This commit is contained in:
Bram Moolenaar 2020-02-08 16:40:39 +01:00
parent 165315584d
commit 4f645c54ef
5 changed files with 163 additions and 114 deletions

View File

@ -2867,6 +2867,7 @@ test_refcount({expr}) Number get the reference count of {expr}
test_scrollbar({which}, {value}, {dragging}) test_scrollbar({which}, {value}, {dragging})
none scroll in the GUI for testing none scroll in the GUI for testing
test_setmouse({row}, {col}) none set the mouse position 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 test_settime({expr}) none set current time for testing
timer_info([{id}]) List information about timers timer_info([{id}]) List information about timers
timer_pause({id}, {pause}) none pause or unpause a timer timer_pause({id}, {pause}) none pause or unpause a timer

View File

@ -210,7 +210,6 @@ test_setmouse({row}, {col}) *test_setmouse()*
call test_setmouse(4, 20) call test_setmouse(4, 20)
call feedkeys("\<LeftMouse>", "xt") call feedkeys("\<LeftMouse>", "xt")
test_settime({expr}) *test_settime()* test_settime({expr}) *test_settime()*
Set the time Vim uses internally. Currently only used for Set the time Vim uses internally. Currently only used for
timestamps in the history, as they are used in viminfo, and 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|: > Can also be used as a |method|: >
GetTime()->test_settime() 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* 3. Assert functions *assert-functions-details*

View File

@ -168,6 +168,7 @@ static void f_pyeval(typval_T *argvars, typval_T *rettv);
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
static void f_pyxeval(typval_T *argvars, typval_T *rettv); static void f_pyxeval(typval_T *argvars, typval_T *rettv);
#endif #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_rand(typval_T *argvars, typval_T *rettv);
static void f_range(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); static void f_reg_executing(typval_T *argvars, typval_T *rettv);
@ -835,6 +836,7 @@ static funcentry_T global_functions[] =
#endif #endif
{"test_setmouse", 2, 2, 0, &t_void, f_test_setmouse}, {"test_setmouse", 2, 2, 0, &t_void, f_test_setmouse},
{"test_settime", 1, 1, FEARG_1, &t_void, f_test_settime}, {"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 #ifdef FEAT_TIMERS
{"timer_info", 0, 1, FEARG_1, &t_list_dict_any, f_timer_info}, {"timer_info", 0, 1, FEARG_1, &t_list_dict_any, f_timer_info},
{"timer_pause", 2, 2, FEARG_1, &t_void, f_timer_pause}, {"timer_pause", 2, 2, FEARG_1, &t_void, f_timer_pause},
@ -5225,6 +5227,83 @@ f_pyxeval(typval_T *argvars, typval_T *rettv)
} }
#endif #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 * "rand()" function
*/ */
@ -5232,66 +5311,57 @@ f_pyxeval(typval_T *argvars, typval_T *rettv)
f_rand(typval_T *argvars, typval_T *rettv) f_rand(typval_T *argvars, typval_T *rettv)
{ {
list_T *l = NULL; list_T *l = NULL;
static list_T *globl = NULL; static UINT32_T gx, gy, gz, gw;
UINT32_T x, y, z, w, t, result; static int initialized = FALSE;
listitem_T *lx, *ly, *lz, *lw; listitem_T *lx, *ly, *lz, *lw;
UINT32_T x, y, z, w, t, result;
if (argvars[0].v_type == VAR_UNKNOWN) if (argvars[0].v_type == VAR_UNKNOWN)
{ {
// When no argument is given use the global seed list. // When no argument is given use the global seed list.
if (globl == NULL) if (initialized == FALSE)
{ {
// Initialize the global seed list. // Initialize the global seed list.
f_srand(argvars, rettv); init_srand(&x);
l = rettv->vval.v_list;
if (l == NULL || list_len(l) != 4) gx = SPLITMIX32(x, z);
{ gy = SPLITMIX32(x, z);
clear_tv(rettv); gz = SPLITMIX32(x, z);
goto theend; gw = SPLITMIX32(x, z);
} initialized = TRUE;
globl = l;
} }
else
l = globl; SHUFFLE_XOSHIRO128STARSTAR(gx, gy, gz, gw);
} }
else if (argvars[0].v_type == VAR_LIST) else if (argvars[0].v_type == VAR_LIST)
{ {
l = argvars[0].vval.v_list; l = argvars[0].vval.v_list;
if (l == NULL || list_len(l) != 4) if (l == NULL || list_len(l) != 4)
goto theend; 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 else
goto theend; 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->v_type = VAR_NUMBER;
rettv->vval.v_number = (varnumber_T)result; rettv->vval.v_number = (varnumber_T)result;
return; return;
@ -5302,6 +5372,39 @@ theend:
rettv->vval.v_number = -1; rettv->vval.v_number = -1;
} }
/*
* "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 * "range()" function
*/ */
@ -7217,73 +7320,6 @@ f_sqrt(typval_T *argvars, typval_T *rettv)
} }
#endif #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 #ifdef FEAT_FLOAT
/* /*
* "str2float()" function * "str2float()" function

View File

@ -11,7 +11,7 @@ func Test_Rand()
call test_settime(12341234) call test_settime(12341234)
let s = srand() let s = srand()
if filereadable('/dev/urandom') if !has('win32') && filereadable('/dev/urandom')
" using /dev/urandom " using /dev/urandom
call assert_notequal(s, srand()) call assert_notequal(s, srand())
else else
@ -21,9 +21,10 @@ func Test_Rand()
call assert_notequal(s, srand()) call assert_notequal(s, srand())
endif endif
call srand() call test_srand_seed(123456789)
let v = rand() call assert_equal(4284103975, rand())
call assert_notequal(v, rand()) call assert_equal(1001954530, rand())
call test_srand_seed()
if has('float') if has('float')
call assert_fails('echo srand(1.2)', 'E805:') call assert_fails('echo srand(1.2)', 'E805:')
@ -38,3 +39,9 @@ func Test_Rand()
call test_settime(0) call test_settime(0)
endfunc endfunc
func Test_issue_5587()
call rand()
call garbagecollect()
call rand()
endfunc

View File

@ -742,6 +742,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
233,
/**/ /**/
232, 232,
/**/ /**/