mirror of
https://github.com/neovim/neovim
synced 2025-07-20 21:32:16 +00:00
fix(:source): copy curbuf lines to memory before sourcing #15111
It's possible for weirdness to happen if curbuf is modified while sourcing from it via :source (with no arguments). For example: - Deleting lines from or wiping curbuf can cause internal error E315 to be thrown from ml_get. - Changing the curbuf to another buffer while sourcing can cause lines from the new curbuf to then be sourced instead.
This commit is contained in:
@ -1860,72 +1860,11 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Concatenate (possibly many) VimL lines starting with line continuations into
|
|
||||||
/// a growarray. @see concat_continued_line
|
|
||||||
///
|
|
||||||
/// @note All parameters, excluding `ga`, accept expressions that are evaluated
|
|
||||||
/// once for each line; side-effects may be triggered many times!
|
|
||||||
///
|
|
||||||
/// @param ga the growarray to append to
|
|
||||||
/// @param cond should evaluate to true if a next line exists
|
|
||||||
/// @param line should evaluate to the current line
|
|
||||||
/// @param next should handle fetching the next line when evaluated
|
|
||||||
#define CONCAT_CONTINUED_LINES(ga, cond, line, next) \
|
|
||||||
do { \
|
|
||||||
garray_T *const ga_ = (ga); \
|
|
||||||
const int init_growsize_ = ga_->ga_growsize; \
|
|
||||||
for (; (cond); (next)) { \
|
|
||||||
const char_u *const line_ = (line); \
|
|
||||||
if (!concat_continued_line(ga_, init_growsize_, line_, STRLEN(line_))) { \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
linenr_T curr_lnum;
|
linenr_T curr_lnum;
|
||||||
const linenr_T final_lnum;
|
const linenr_T final_lnum;
|
||||||
} GetBufferLineCookie;
|
} GetBufferLineCookie;
|
||||||
|
|
||||||
/// Get one line from the current selection in the buffer.
|
|
||||||
/// Called by do_cmdline() when it's called from cmd_source_buffer().
|
|
||||||
///
|
|
||||||
/// @return pointer to allocated line, or NULL for end-of-file or
|
|
||||||
/// some error.
|
|
||||||
static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat)
|
|
||||||
{
|
|
||||||
GetBufferLineCookie *p = cookie;
|
|
||||||
if (p->curr_lnum > p->final_lnum) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
garray_T ga;
|
|
||||||
ga_init(&ga, sizeof(char_u), 400);
|
|
||||||
ga_concat(&ga, ml_get(p->curr_lnum++));
|
|
||||||
if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) {
|
|
||||||
CONCAT_CONTINUED_LINES(&ga, p->curr_lnum <= p->final_lnum,
|
|
||||||
ml_get(p->curr_lnum), p->curr_lnum++);
|
|
||||||
}
|
|
||||||
ga_append(&ga, NUL);
|
|
||||||
return ga.ga_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cmd_source_buffer(const exarg_T *eap)
|
|
||||||
FUNC_ATTR_NONNULL_ALL
|
|
||||||
{
|
|
||||||
GetBufferLineCookie cookie = {
|
|
||||||
.curr_lnum = eap->line1,
|
|
||||||
.final_lnum = eap->line2,
|
|
||||||
};
|
|
||||||
if (curbuf != NULL && curbuf->b_fname
|
|
||||||
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
|
|
||||||
nlua_source_using_linegetter(get_buffer_line, (void *)&cookie,
|
|
||||||
":source (no file)");
|
|
||||||
} else {
|
|
||||||
source_using_linegetter((void *)&cookie, get_buffer_line,
|
|
||||||
":source (no file)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ":source" and associated commands.
|
/// ":source" and associated commands.
|
||||||
///
|
///
|
||||||
/// @return address holding the next breakpoint line for a source cookie
|
/// @return address holding the next breakpoint line for a source cookie
|
||||||
@ -2033,6 +1972,40 @@ static int source_using_linegetter(void *cookie,
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cmd_source_buffer(const exarg_T *const eap)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
if (curbuf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
garray_T ga;
|
||||||
|
ga_init(&ga, sizeof(char_u), 400);
|
||||||
|
const linenr_T final_lnum = eap->line2;
|
||||||
|
// Copy the contents to be executed.
|
||||||
|
for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) {
|
||||||
|
// Adjust growsize to current length to speed up concatenating many lines.
|
||||||
|
if (ga.ga_len > 400) {
|
||||||
|
ga_set_growsize(&ga, MAX(ga.ga_len, 8000));
|
||||||
|
}
|
||||||
|
ga_concat(&ga, ml_get(curr_lnum));
|
||||||
|
ga_append(&ga, NL);
|
||||||
|
}
|
||||||
|
((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
|
||||||
|
const GetStrLineCookie cookie = {
|
||||||
|
.buf = ga.ga_data,
|
||||||
|
.offset = 0,
|
||||||
|
};
|
||||||
|
if (curbuf->b_fname
|
||||||
|
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
|
||||||
|
nlua_source_using_linegetter(get_str_line, (void *)&cookie,
|
||||||
|
":source (no file)");
|
||||||
|
} else {
|
||||||
|
source_using_linegetter((void *)&cookie, get_str_line,
|
||||||
|
":source (no file)");
|
||||||
|
}
|
||||||
|
ga_clear(&ga);
|
||||||
|
}
|
||||||
|
|
||||||
/// Executes lines in `src` as Ex commands.
|
/// Executes lines in `src` as Ex commands.
|
||||||
///
|
///
|
||||||
/// @see do_source()
|
/// @see do_source()
|
||||||
@ -2490,9 +2463,12 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat)
|
|||||||
|
|
||||||
ga_init(&ga, (int)sizeof(char_u), 400);
|
ga_init(&ga, (int)sizeof(char_u), 400);
|
||||||
ga_concat(&ga, line);
|
ga_concat(&ga, line);
|
||||||
CONCAT_CONTINUED_LINES(&ga, sp->nextline != NULL, sp->nextline,
|
while (sp->nextline != NULL
|
||||||
(xfree(sp->nextline),
|
&& concat_continued_line(&ga, 400, sp->nextline,
|
||||||
sp->nextline = get_one_sourceline(sp)));
|
STRLEN(sp->nextline))) {
|
||||||
|
xfree(sp->nextline);
|
||||||
|
sp->nextline = get_one_sourceline(sp);
|
||||||
|
}
|
||||||
ga_append(&ga, NUL);
|
ga_append(&ga, NUL);
|
||||||
xfree(line);
|
xfree(line);
|
||||||
line = ga.ga_data;
|
line = ga.ga_data;
|
||||||
|
@ -60,6 +60,15 @@ describe(':source', function()
|
|||||||
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
|
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('does not break if current buffer is modified while sourced', function()
|
||||||
|
insert [[
|
||||||
|
bwipeout!
|
||||||
|
let a = 123
|
||||||
|
]]
|
||||||
|
command('source')
|
||||||
|
eq('123', meths.exec('echo a', true))
|
||||||
|
end)
|
||||||
|
|
||||||
it('multiline heredoc command', function()
|
it('multiline heredoc command', function()
|
||||||
insert([[
|
insert([[
|
||||||
lua << EOF
|
lua << EOF
|
||||||
|
Reference in New Issue
Block a user