mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 03:05:11 -07: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:
parent
6188926e00
commit
afdc9e6150
@ -1860,72 +1860,11 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize,
|
||||
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 {
|
||||
linenr_T curr_lnum;
|
||||
const linenr_T final_lnum;
|
||||
} 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.
|
||||
///
|
||||
/// @return address holding the next breakpoint line for a source cookie
|
||||
@ -2033,6 +1972,40 @@ static int source_using_linegetter(void *cookie,
|
||||
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.
|
||||
///
|
||||
/// @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_concat(&ga, line);
|
||||
CONCAT_CONTINUED_LINES(&ga, sp->nextline != NULL, sp->nextline,
|
||||
(xfree(sp->nextline),
|
||||
sp->nextline = get_one_sourceline(sp)));
|
||||
while (sp->nextline != NULL
|
||||
&& concat_continued_line(&ga, 400, sp->nextline,
|
||||
STRLEN(sp->nextline))) {
|
||||
xfree(sp->nextline);
|
||||
sp->nextline = get_one_sourceline(sp);
|
||||
}
|
||||
ga_append(&ga, NUL);
|
||||
xfree(line);
|
||||
line = ga.ga_data;
|
||||
|
@ -60,6 +60,15 @@ describe(':source', function()
|
||||
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
|
||||
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()
|
||||
insert([[
|
||||
lua << EOF
|
||||
|
Loading…
Reference in New Issue
Block a user