Skip to content

Commit

Permalink
fix(:source): copy curbuf lines to memory before sourcing #15111
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seandewar committed Sep 15, 2021
1 parent 6188926 commit afdc9e6
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 64 deletions.
104 changes: 40 additions & 64 deletions src/nvim/ex_cmds2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions test/functional/ex_cmds/source_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit afdc9e6

Please sign in to comment.
-