aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dewar <seandewar@users.noreply.github.com>2022-02-16 22:37:10 +0000
committerSean Dewar <seandewar@users.noreply.github.com>2022-02-17 17:06:16 +0000
commit41d0e7af2097e0374ab16fb1567cf22d21aad180 (patch)
treeb50f5f45ef192da6608f8f6c963a50c0d1e96271
parent8170260bb35f3761d2008405289832b2620abc53 (diff)
downloadrneovim-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.c51
-rw-r--r--src/nvim/testdir/test_put.vim37
-rw-r--r--test/functional/legacy/put_spec.lua45
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)