diff options
author | Sean Dewar <seandewar@users.noreply.github.com> | 2022-02-16 22:37:10 +0000 |
---|---|---|
committer | Sean Dewar <seandewar@users.noreply.github.com> | 2022-02-17 17:06:16 +0000 |
commit | 41d0e7af2097e0374ab16fb1567cf22d21aad180 (patch) | |
tree | b50f5f45ef192da6608f8f6c963a50c0d1e96271 | |
parent | 8170260bb35f3761d2008405289832b2620abc53 (diff) | |
download | rneovim-41d0e7af2097e0374ab16fb1567cf22d21aad180.tar.gz rneovim-41d0e7af2097e0374ab16fb1567cf22d21aad180.tar.bz2 rneovim-41d0e7af2097e0374ab16fb1567cf22d21aad180.zip |
vim-patch:8.2.3601: check for overflow in put count does not work well
Problem: Check for overflow in put count does not work well.
Solution: Improve the overflow check. (Ozaki Kiichi, closes vim/vim#9102)
https://github.com/vim/vim/commit/fa53722367c3793fda95dac665af74b8651065e9
Add some casts as Nvim uses size_t variables in some places.
We could technically adjust the logic to check for overflow outside of size_t's
range, but it's much easier to just port the patch exactly (also means we can
use the same tests).
v:sizeoflong is N/A, so convert the 64-bit tests to Lua and use the FFI to check
long's size.
-rw-r--r-- | src/nvim/ops.c | 51 | ||||
-rw-r--r-- | src/nvim/testdir/test_put.vim | 37 | ||||
-rw-r--r-- | test/functional/legacy/put_spec.lua | 45 |
3 files changed, 112 insertions, 21 deletions
diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 95573799e5..b5c7020dee 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3314,18 +3314,28 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - // insert the new text + // Insert the new text. + // First check for multiplication overflow. + if (yanklen + spaces != 0 + && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) { + emsg(_(e_resulting_text_too_long)); + break; + } + totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); int addcount = (int)totlen + lines_appended; newp = (char_u *)xmalloc(totlen + oldlen + 1); + // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; + // may insert some spaces before the new text memset(ptr, ' ', (size_t)bd.startspaces); ptr += bd.startspaces; + // insert the new text for (long j = 0; j < count; j++) { memmove(ptr, y_array[i], (size_t)yanklen); @@ -3339,9 +3349,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) addcount -= spaces; } } + // may insert some spaces after the new text memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; + // move the text after the cursor to the end of the line. int columns = (int)oldlen - bd.textcol - delcount + 1; assert(columns >= 0); @@ -3430,15 +3442,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - do { - const double multlen = (double)count * (double)yanklen; - - totlen = (size_t)(int)(count * yanklen); - if ((double)totlen != multlen) { - emsg(_(e_resulting_text_too_long)); - break; - } else if (totlen > 0) { + if (count == 0 || yanklen == 0) { + if (VIsual_active) { + lnum = end_lnum; + } + } else if (count > INT_MAX / yanklen) { + // multiplication overflow + emsg(_(e_resulting_text_too_long)); + } else { + totlen = (size_t)(count * yanklen); + do { oldp = ml_get(lnum); + oldlen = STRLEN(oldp); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, @@ -3449,11 +3464,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) col = MAXCOL; } } - if (VIsual_active && col > (int)STRLEN(oldp)) { + if (VIsual_active && col > (colnr_T)oldlen) { lnum++; continue; } - newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); + newp = (char_u *)xmalloc(totlen + oldlen + 1); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3475,14 +3490,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) changed_bytes(lnum, col); extmark_splice_cols(curbuf, (int)lnum-1, col, 0, (int)totlen, kExtmarkUndo); - } - if (VIsual_active) { - lnum++; - } - } while (VIsual_active && lnum <= end_lnum); + if (VIsual_active) { + lnum++; + } + } while (VIsual_active && lnum <= end_lnum); - if (VIsual_active) { // reset lnum to the last visual line - lnum--; + if (VIsual_active) { // reset lnum to the last visual line + lnum--; + } } // put '] at the first byte of the last character diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index a71224785a..9f2fc999a7 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -139,10 +139,18 @@ func Test_p_with_count_leaves_mark_at_end() endfunc func Test_very_large_count() - throw 'Skipped: v:sizeofint is N/A' + new + " total put-length (21474837 * 100) brings 32 bit int overflow + let @" = repeat('x', 100) + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead - if v:sizeofint != 8 - throw 'Skipped: only works with 64 bit ints' + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' endif new @@ -151,6 +159,29 @@ func Test_very_large_count() bwipe! endfunc +func Test_very_large_count_block() + new + " total put-length (21474837 * 100) brings 32 bit int overflow + call setline(1, repeat('x', 100)) + exe "norm \<C-V>99ly" + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_block_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + + new + call setline(1, 'x') + exe "norm \<C-V>y" + call assert_fails('norm 44444444444444p', 'E1240:') + bwipe! +endfunc + func Test_put_above_first_line() new let @" = 'text' diff --git a/test/functional/legacy/put_spec.lua b/test/functional/legacy/put_spec.lua new file mode 100644 index 0000000000..0b969dee55 --- /dev/null +++ b/test/functional/legacy/put_spec.lua @@ -0,0 +1,45 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local meths = helpers.meths +local source = helpers.source +local eq = helpers.eq + +local function sizeoflong() + if not exec_lua('return pcall(require, "ffi")') then + pending('missing LuaJIT FFI') + end + return exec_lua('return require("ffi").sizeof(require("ffi").typeof("long"))') +end + +describe('put', function() + before_each(clear) + after_each(function() eq({}, meths.get_vvar('errors')) end) + + it('very large count 64-bit', function() + if sizeoflong() < 8 then + pending('Skipped: only works with 64 bit long ints') + end + + source [[ + new + let @" = 'x' + call assert_fails('norm 44444444444444p', 'E1240:') + bwipe! + ]] + end) + + it('very large count (visual block) 64-bit', function() + if sizeoflong() < 8 then + pending('Skipped: only works with 64 bit long ints') + end + + source [[ + new + call setline(1, 'x') + exe "norm \<C-V>y" + call assert_fails('norm 44444444444444p', 'E1240:') + bwipe! + ]] + end) +end) |