mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
fix(api): limit depth of nvim_cmd (#27225)
This commit is contained in:
parent
38bb0e1da8
commit
7367838359
@ -151,7 +151,7 @@ bool try_end(Error *err)
|
|||||||
if (should_free) {
|
if (should_free) {
|
||||||
xfree(msg);
|
xfree(msg);
|
||||||
}
|
}
|
||||||
} else if (did_throw) {
|
} else if (did_throw || need_rethrow) {
|
||||||
if (*current_exception->throw_name != NUL) {
|
if (*current_exception->throw_name != NUL) {
|
||||||
if (current_exception->throw_lnum != 0) {
|
if (current_exception->throw_lnum != 0) {
|
||||||
api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s",
|
api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s",
|
||||||
|
@ -309,6 +309,33 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd)
|
|||||||
no_wait_return--;
|
no_wait_return--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int cmdline_call_depth = 0; ///< recursiveness
|
||||||
|
|
||||||
|
/// Start executing an Ex command line.
|
||||||
|
///
|
||||||
|
/// @return FAIL if too recursive, OK otherwise.
|
||||||
|
static int do_cmdline_start(void)
|
||||||
|
{
|
||||||
|
assert(cmdline_call_depth >= 0);
|
||||||
|
// It's possible to create an endless loop with ":execute", catch that
|
||||||
|
// here. The value of 200 allows nested function calls, ":source", etc.
|
||||||
|
// Allow 200 or 'maxfuncdepth', whatever is larger.
|
||||||
|
if (cmdline_call_depth >= 200 && cmdline_call_depth >= p_mfd) {
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
cmdline_call_depth++;
|
||||||
|
start_batch_changes();
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End executing an Ex command line.
|
||||||
|
static void do_cmdline_end(void)
|
||||||
|
{
|
||||||
|
cmdline_call_depth--;
|
||||||
|
assert(cmdline_call_depth >= 0);
|
||||||
|
end_batch_changes();
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a simple command line. Used for translated commands like "*".
|
/// Execute a simple command line. Used for translated commands like "*".
|
||||||
int do_cmdline_cmd(const char *cmd)
|
int do_cmdline_cmd(const char *cmd)
|
||||||
{
|
{
|
||||||
@ -359,7 +386,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
|
|||||||
char *(*cmd_getline)(int, void *, int, bool);
|
char *(*cmd_getline)(int, void *, int, bool);
|
||||||
void *cmd_cookie;
|
void *cmd_cookie;
|
||||||
struct loop_cookie cmd_loop_cookie;
|
struct loop_cookie cmd_loop_cookie;
|
||||||
static int call_depth = 0; // recursiveness
|
|
||||||
|
|
||||||
// For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory
|
// For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory
|
||||||
// location for storing error messages to be converted to an exception.
|
// location for storing error messages to be converted to an exception.
|
||||||
@ -371,10 +397,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
|
|||||||
msg_list = &private_msg_list;
|
msg_list = &private_msg_list;
|
||||||
private_msg_list = NULL;
|
private_msg_list = NULL;
|
||||||
|
|
||||||
// It's possible to create an endless loop with ":execute", catch that
|
if (do_cmdline_start() == FAIL) {
|
||||||
// here. The value of 200 allows nested function calls, ":source", etc.
|
|
||||||
// Allow 200 or 'maxfuncdepth', whatever is larger.
|
|
||||||
if (call_depth >= 200 && call_depth >= p_mfd) {
|
|
||||||
emsg(_(e_command_too_recursive));
|
emsg(_(e_command_too_recursive));
|
||||||
// When converting to an exception, we do not include the command name
|
// When converting to an exception, we do not include the command name
|
||||||
// since this is not an error of the specific command.
|
// since this is not an error of the specific command.
|
||||||
@ -382,8 +405,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
|
|||||||
msg_list = saved_msg_list;
|
msg_list = saved_msg_list;
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
call_depth++;
|
|
||||||
start_batch_changes();
|
|
||||||
|
|
||||||
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
|
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
|
||||||
|
|
||||||
@ -884,8 +905,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
|
|||||||
|
|
||||||
did_endif = false; // in case do_cmdline used recursively
|
did_endif = false; // in case do_cmdline used recursively
|
||||||
|
|
||||||
call_depth--;
|
do_cmdline_end();
|
||||||
end_batch_changes();
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1669,9 +1689,13 @@ static int execute_cmd0(int *retv, exarg_T *eap, const char **errormsg, bool pre
|
|||||||
/// @param preview Execute command preview callback instead of actual command
|
/// @param preview Execute command preview callback instead of actual command
|
||||||
int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
|
int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
|
||||||
{
|
{
|
||||||
const char *errormsg = NULL;
|
|
||||||
int retv = 0;
|
int retv = 0;
|
||||||
|
if (do_cmdline_start() == FAIL) {
|
||||||
|
emsg(_(e_command_too_recursive));
|
||||||
|
return retv;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *errormsg = NULL;
|
||||||
#undef ERROR
|
#undef ERROR
|
||||||
#define ERROR(msg) \
|
#define ERROR(msg) \
|
||||||
do { \
|
do { \
|
||||||
@ -1738,9 +1762,12 @@ end:
|
|||||||
if (errormsg != NULL && *errormsg != NUL) {
|
if (errormsg != NULL && *errormsg != NUL) {
|
||||||
emsg(errormsg);
|
emsg(errormsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo command modifiers
|
// Undo command modifiers
|
||||||
undo_cmdmod(&cmdmod);
|
undo_cmdmod(&cmdmod);
|
||||||
cmdmod = save_cmdmod;
|
cmdmod = save_cmdmod;
|
||||||
|
|
||||||
|
do_cmdline_end();
|
||||||
return retv;
|
return retv;
|
||||||
#undef ERROR
|
#undef ERROR
|
||||||
}
|
}
|
||||||
|
@ -399,6 +399,19 @@ describe('API', function()
|
|||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('errors properly when command too recursive', function()
|
||||||
|
exec_lua([[
|
||||||
|
_G.success = false
|
||||||
|
vim.api.nvim_create_user_command('Test', function()
|
||||||
|
vim.api.nvim_exec2('Test', {})
|
||||||
|
_G.success = true
|
||||||
|
end, {})
|
||||||
|
]])
|
||||||
|
pcall_err(command, 'Test')
|
||||||
|
assert_alive()
|
||||||
|
eq(false, exec_lua('return _G.success'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('nvim_command', function()
|
describe('nvim_command', function()
|
||||||
@ -4560,6 +4573,7 @@ describe('API', function()
|
|||||||
line6
|
line6
|
||||||
]]
|
]]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with count', function()
|
it('works with count', function()
|
||||||
insert [[
|
insert [[
|
||||||
line1
|
line1
|
||||||
@ -4577,6 +4591,7 @@ describe('API', function()
|
|||||||
line6
|
line6
|
||||||
]]
|
]]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with register', function()
|
it('works with register', function()
|
||||||
insert [[
|
insert [[
|
||||||
line1
|
line1
|
||||||
@ -4599,11 +4614,13 @@ describe('API', function()
|
|||||||
line6
|
line6
|
||||||
]]
|
]]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with bang', function()
|
it('works with bang', function()
|
||||||
api.nvim_create_user_command('Foo', 'echo "<bang>"', { bang = true })
|
api.nvim_create_user_command('Foo', 'echo "<bang>"', { bang = true })
|
||||||
eq('!', api.nvim_cmd({ cmd = 'Foo', bang = true }, { output = true }))
|
eq('!', api.nvim_cmd({ cmd = 'Foo', bang = true }, { output = true }))
|
||||||
eq('', api.nvim_cmd({ cmd = 'Foo', bang = false }, { output = true }))
|
eq('', api.nvim_cmd({ cmd = 'Foo', bang = false }, { output = true }))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with modifiers', function()
|
it('works with modifiers', function()
|
||||||
-- with silent = true output is still captured
|
-- with silent = true output is still captured
|
||||||
eq(
|
eq(
|
||||||
@ -4659,6 +4676,7 @@ describe('API', function()
|
|||||||
feed(':call<CR><CR>')
|
feed(':call<CR><CR>')
|
||||||
eq('E471: Argument required', api.nvim_cmd({ cmd = 'messages' }, { output = true }))
|
eq('E471: Argument required', api.nvim_cmd({ cmd = 'messages' }, { output = true }))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with magic.file', function()
|
it('works with magic.file', function()
|
||||||
exec_lua([[
|
exec_lua([[
|
||||||
vim.api.nvim_create_user_command("Foo", function(opts)
|
vim.api.nvim_create_user_command("Foo", function(opts)
|
||||||
@ -4673,6 +4691,7 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('splits arguments correctly', function()
|
it('splits arguments correctly', function()
|
||||||
exec([[
|
exec([[
|
||||||
function! FooFunc(...)
|
function! FooFunc(...)
|
||||||
@ -4695,6 +4714,7 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('splits arguments correctly for Lua callback', function()
|
it('splits arguments correctly for Lua callback', function()
|
||||||
api.nvim_exec_lua(
|
api.nvim_exec_lua(
|
||||||
[[
|
[[
|
||||||
@ -4721,6 +4741,7 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with buffer names', function()
|
it('works with buffer names', function()
|
||||||
command('edit foo.txt | edit bar.txt')
|
command('edit foo.txt | edit bar.txt')
|
||||||
api.nvim_cmd({ cmd = 'buffer', args = { 'foo.txt' } }, {})
|
api.nvim_cmd({ cmd = 'buffer', args = { 'foo.txt' } }, {})
|
||||||
@ -4728,6 +4749,7 @@ describe('API', function()
|
|||||||
api.nvim_cmd({ cmd = 'buffer', args = { 'bar.txt' } }, {})
|
api.nvim_cmd({ cmd = 'buffer', args = { 'bar.txt' } }, {})
|
||||||
eq('bar.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
|
eq('bar.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('triggers CmdUndefined event if command is not found', function()
|
it('triggers CmdUndefined event if command is not found', function()
|
||||||
api.nvim_exec_lua(
|
api.nvim_exec_lua(
|
||||||
[[
|
[[
|
||||||
@ -4742,13 +4764,16 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
eq('foo', api.nvim_cmd({ cmd = 'Foo' }, { output = true }))
|
eq('foo', api.nvim_cmd({ cmd = 'Foo' }, { output = true }))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('errors if command is not implemented', function()
|
it('errors if command is not implemented', function()
|
||||||
eq('Command not implemented: winpos', pcall_err(api.nvim_cmd, { cmd = 'winpos' }, {}))
|
eq('Command not implemented: winpos', pcall_err(api.nvim_cmd, { cmd = 'winpos' }, {}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with empty arguments list', function()
|
it('works with empty arguments list', function()
|
||||||
api.nvim_cmd({ cmd = 'update' }, {})
|
api.nvim_cmd({ cmd = 'update' }, {})
|
||||||
api.nvim_cmd({ cmd = 'buffer', count = 0 }, {})
|
api.nvim_cmd({ cmd = 'buffer', count = 0 }, {})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("doesn't suppress errors when used in keymapping", function()
|
it("doesn't suppress errors when used in keymapping", function()
|
||||||
api.nvim_exec_lua(
|
api.nvim_exec_lua(
|
||||||
[[
|
[[
|
||||||
@ -4760,6 +4785,7 @@ describe('API', function()
|
|||||||
feed('[l')
|
feed('[l')
|
||||||
neq(nil, string.find(eval('v:errmsg'), 'E5108:'))
|
neq(nil, string.find(eval('v:errmsg'), 'E5108:'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('handles 0 range #19608', function()
|
it('handles 0 range #19608', function()
|
||||||
api.nvim_buf_set_lines(0, 0, -1, false, { 'aa' })
|
api.nvim_buf_set_lines(0, 0, -1, false, { 'aa' })
|
||||||
api.nvim_cmd({ cmd = 'delete', range = { 0 } }, {})
|
api.nvim_cmd({ cmd = 'delete', range = { 0 } }, {})
|
||||||
@ -4767,12 +4793,14 @@ describe('API', function()
|
|||||||
eq({ 'aa' }, api.nvim_buf_get_lines(0, 0, 1, false))
|
eq({ 'aa' }, api.nvim_buf_get_lines(0, 0, 1, false))
|
||||||
assert_alive()
|
assert_alive()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('supports filename expansion', function()
|
it('supports filename expansion', function()
|
||||||
api.nvim_cmd({ cmd = 'argadd', args = { '%:p:h:t', '%:p:h:t' } }, {})
|
api.nvim_cmd({ cmd = 'argadd', args = { '%:p:h:t', '%:p:h:t' } }, {})
|
||||||
local arg = fn.expand('%:p:h:t')
|
local arg = fn.expand('%:p:h:t')
|
||||||
eq({ arg, arg }, fn.argv())
|
eq({ arg, arg }, fn.argv())
|
||||||
end)
|
end)
|
||||||
it("'make' command works when argument count isn't 1 #19696", function()
|
|
||||||
|
it(":make command works when argument count isn't 1 #19696", function()
|
||||||
command('set makeprg=echo')
|
command('set makeprg=echo')
|
||||||
command('set shellquote=')
|
command('set shellquote=')
|
||||||
matches('^:!echo ', api.nvim_cmd({ cmd = 'make' }, { output = true }))
|
matches('^:!echo ', api.nvim_cmd({ cmd = 'make' }, { output = true }))
|
||||||
@ -4789,6 +4817,7 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
assert_alive()
|
assert_alive()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("doesn't display messages when output=true", function()
|
it("doesn't display messages when output=true", function()
|
||||||
local screen = Screen.new(40, 6)
|
local screen = Screen.new(40, 6)
|
||||||
screen:attach()
|
screen:attach()
|
||||||
@ -4817,31 +4846,37 @@ describe('API', function()
|
|||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with non-String args', function()
|
it('works with non-String args', function()
|
||||||
eq('2', api.nvim_cmd({ cmd = 'echo', args = { 2 } }, { output = true }))
|
eq('2', api.nvim_cmd({ cmd = 'echo', args = { 2 } }, { output = true }))
|
||||||
eq('1', api.nvim_cmd({ cmd = 'echo', args = { true } }, { output = true }))
|
eq('1', api.nvim_cmd({ cmd = 'echo', args = { true } }, { output = true }))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('first argument as count', function()
|
describe('first argument as count', function()
|
||||||
it('works', function()
|
it('works', function()
|
||||||
command('vsplit | enew')
|
command('vsplit | enew')
|
||||||
api.nvim_cmd({ cmd = 'bdelete', args = { api.nvim_get_current_buf() } }, {})
|
api.nvim_cmd({ cmd = 'bdelete', args = { api.nvim_get_current_buf() } }, {})
|
||||||
eq(1, api.nvim_get_current_buf())
|
eq(1, api.nvim_get_current_buf())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('works with :sleep using milliseconds', function()
|
it('works with :sleep using milliseconds', function()
|
||||||
local start = uv.now()
|
local start = uv.now()
|
||||||
api.nvim_cmd({ cmd = 'sleep', args = { '100m' } }, {})
|
api.nvim_cmd({ cmd = 'sleep', args = { '100m' } }, {})
|
||||||
ok(uv.now() - start <= 300)
|
ok(uv.now() - start <= 300)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it(':call with unknown function does not crash #26289', function()
|
it(':call with unknown function does not crash #26289', function()
|
||||||
eq(
|
eq(
|
||||||
'Vim:E117: Unknown function: UnknownFunc',
|
'Vim:E117: Unknown function: UnknownFunc',
|
||||||
pcall_err(api.nvim_cmd, { cmd = 'call', args = { 'UnknownFunc()' } }, {})
|
pcall_err(api.nvim_cmd, { cmd = 'call', args = { 'UnknownFunc()' } }, {})
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it(':throw does not crash #24556', function()
|
it(':throw does not crash #24556', function()
|
||||||
eq('42', pcall_err(api.nvim_cmd, { cmd = 'throw', args = { '42' } }, {}))
|
eq('42', pcall_err(api.nvim_cmd, { cmd = 'throw', args = { '42' } }, {}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('can use :return #24556', function()
|
it('can use :return #24556', function()
|
||||||
exec([[
|
exec([[
|
||||||
func Foo()
|
func Foo()
|
||||||
@ -4854,5 +4889,18 @@ describe('API', function()
|
|||||||
eq('before', api.nvim_get_var('pos'))
|
eq('before', api.nvim_get_var('pos'))
|
||||||
eq({ 1, 2, 3 }, api.nvim_get_var('result'))
|
eq({ 1, 2, 3 }, api.nvim_get_var('result'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('errors properly when command too recursive #27210', function()
|
||||||
|
exec_lua([[
|
||||||
|
_G.success = false
|
||||||
|
vim.api.nvim_create_user_command('Test', function()
|
||||||
|
vim.api.nvim_cmd({ cmd = 'Test' }, {})
|
||||||
|
_G.success = true
|
||||||
|
end, {})
|
||||||
|
]])
|
||||||
|
pcall_err(command, 'Test')
|
||||||
|
assert_alive()
|
||||||
|
eq(false, exec_lua('return _G.success'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user