To: vim_dev@googlegroups.com Subject: Patch 8.2.0233 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0233 Problem: Crash when using garbagecollect() in between rand(). Solution: Redesign the rand() and srand() implementation. (Yasuhiro Matsumoto, closes #5587, closes #5588) Files: src/evalfunc.c, src/testdir/test_random.vim, runtime/doc/testing.txt, runtime/doc/eval.txt *** ../vim-8.2.0232/src/evalfunc.c 2020-02-05 20:38:15.931599558 +0100 --- src/evalfunc.c 2020-02-08 16:36:59.028550580 +0100 *************** *** 168,173 **** --- 168,174 ---- #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,840 **** --- 836,842 ---- #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,5230 **** --- 5227,5309 ---- } #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,5297 **** f_rand(typval_T *argvars, typval_T *rettv) { list_T *l = NULL; ! static list_T *globl = NULL; ! 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) { // 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; } ! else ! l = globl; } else if (argvars[0].v_type == VAR_LIST) { l = argvars[0].vval.v_list; if (l == NULL || list_len(l) != 4) goto theend; } 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; --- 5311,5367 ---- f_rand(typval_T *argvars, typval_T *rettv) { list_T *l = 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; if (argvars[0].v_type == VAR_UNKNOWN) { // When no argument is given use the global seed list. ! if (initialized == FALSE) { // Initialize the global seed list. ! init_srand(&x); ! ! gx = SPLITMIX32(x, z); ! gy = SPLITMIX32(x, z); ! gz = SPLITMIX32(x, z); ! gw = SPLITMIX32(x, z); ! initialized = TRUE; } ! ! 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; rettv->v_type = VAR_NUMBER; rettv->vval.v_number = (varnumber_T)result; return; *************** *** 5303,5308 **** --- 5373,5411 ---- } /* + * "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,7289 **** } #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 --- 7320,7325 ---- *** ../vim-8.2.0232/src/testdir/test_random.vim 2019-11-28 22:55:18.000000000 +0100 --- src/testdir/test_random.vim 2020-02-08 16:07:00.083822690 +0100 *************** *** 11,17 **** call test_settime(12341234) let s = srand() ! if filereadable('/dev/urandom') " using /dev/urandom call assert_notequal(s, srand()) else --- 11,17 ---- call test_settime(12341234) let s = srand() ! if !has('win32') && filereadable('/dev/urandom') " using /dev/urandom call assert_notequal(s, srand()) else *************** *** 21,29 **** call assert_notequal(s, srand()) endif ! call srand() ! let v = rand() ! call assert_notequal(v, rand()) if has('float') call assert_fails('echo srand(1.2)', 'E805:') --- 21,30 ---- call assert_notequal(s, srand()) endif ! 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,40 **** --- 39,47 ---- call test_settime(0) endfunc + + func Test_issue_5587() + call rand() + call garbagecollect() + call rand() + endfunc *** ../vim-8.2.0232/runtime/doc/testing.txt 2020-02-05 20:38:15.927599576 +0100 --- runtime/doc/testing.txt 2020-02-08 16:16:08.797606207 +0100 *************** *** 210,216 **** call test_setmouse(4, 20) call feedkeys("\", "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 --- 210,215 ---- *************** *** 223,228 **** --- 222,231 ---- 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* *** ../vim-8.2.0232/runtime/doc/eval.txt 2020-02-05 20:38:15.927599576 +0100 --- runtime/doc/eval.txt 2020-02-08 16:11:09.918810771 +0100 *************** *** 2865,2870 **** --- 2867,2873 ---- 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 *** ../vim-8.2.0232/src/version.c 2020-02-08 16:00:43.225362846 +0100 --- src/version.c 2020-02-08 16:09:11.663289031 +0100 *************** *** 744,745 **** --- 744,747 ---- { /* Add new patch number below this line */ + /**/ + 233, /**/ -- It was recently discovered that research causes cancer in rats. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///