Clean up variable expansion, and properly handle embedded NULs

This commit is contained in:
ridiculousfish 2014-08-24 14:28:31 -07:00
parent f71b10df8c
commit 9419191aa6
4 changed files with 199 additions and 117 deletions

View file

@ -1049,21 +1049,24 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
fewer string scans and overall just less work. But until that
happens, don't edit it unless you know exactly what you are doing,
and do proper testing afterwards.
This function operates on strings backwards, starting at last_idx.
*/
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors);
static int expand_variables2(parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
static int expand_variables(parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
{
wchar_t *in = wcsdup(instr.c_str());
int result = expand_variables_internal(parser, in, out, last_idx, errors);
free(in);
return result;
}
// We permit last_idx to be beyond the end of the string if and only if the string is empty
assert(instr.empty() || (last_idx >= 0 && (size_t)last_idx < instr.size()));
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
{
int is_ok= 1;
int empty=0;
// Make this explicit
if (instr.empty())
{
append_completion(out, instr);
return true;
}
bool is_ok = true;
bool empty = false;
const size_t insize = instr.size();
wcstring var_tmp;
@ -1077,7 +1080,7 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
for (long i=last_idx; (i>=0) && is_ok && !empty; i--)
{
const wchar_t c = in[i];
const wchar_t c = instr.at(i);
if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE))
{
long start_pos = i+1;
@ -1087,17 +1090,15 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
stop_pos = start_pos;
while (1)
while (stop_pos < insize)
{
const wchar_t nc = in[stop_pos];
if (!(nc))
break;
const wchar_t nc = instr.at(stop_pos);
if (nc == VARIABLE_EXPAND_EMPTY)
{
stop_pos++;
break;
}
if (!(wcsvarchr(nc)))
if (!wcsvarchr(nc))
break;
stop_pos++;
@ -1109,13 +1110,13 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (var_len == 0)
{
expand_variable_error(parser, in, stop_pos-1, -1, errors);
expand_variable_error(parser, instr, stop_pos-1, -1, errors);
is_ok = 0;
is_ok = false;
break;
}
var_tmp.append(in + start_pos, var_len);
var_tmp.append(instr, start_pos, var_len);
env_var_t var_val;
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY)
{
@ -1133,22 +1134,22 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok)
{
tokenize_variable_array(var_val.c_str(), var_item_list);
tokenize_variable_array(var_val, var_item_list);
const size_t slice_start = stop_pos;
if (in[slice_start] == L'[')
if (slice_start < insize && instr.at(slice_start) == L'[')
{
wchar_t *slice_end;
size_t bad_pos;
all_vars=0;
const wchar_t *in = instr.c_str();
bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size());
if (bad_pos != 0)
{
append_syntax_error(errors,
stop_pos + bad_pos,
L"Invalid index value");
is_ok = 0;
is_ok = false;
break;
}
stop_pos = (slice_end-in);
@ -1168,7 +1169,7 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
append_syntax_error(errors,
slice_start + var_src_pos,
ARRAY_BOUNDS_ERR);
is_ok=0;
is_ok = false;
var_idx_list.resize(j);
break;
}
@ -1187,14 +1188,12 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok)
{
if (is_single)
{
in[i]=0;
wcstring res = in;
wcstring res(instr, 0, i);
if (i > 0)
{
if (in[i-1] != VARIABLE_EXPAND_SINGLE)
if (instr.at(i-1) != VARIABLE_EXPAND_SINGLE)
{
res.push_back(INTERNAL_SEPARATOR);
}
@ -1215,15 +1214,16 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
res.append(next);
}
}
res.append(in + stop_pos);
is_ok &= expand_variables2(parser, res, out, i, errors);
assert(stop_pos <= insize);
res.append(instr, stop_pos, insize - stop_pos);
is_ok &= expand_variables(parser, res, out, i, errors);
}
else
{
for (size_t j=0; j<var_item_list.size(); j++)
{
const wcstring &next = var_item_list.at(j);
if (is_ok && (i == 0) && (!in[stop_pos]))
if (is_ok && (i == 0) && stop_pos == insize)
{
append_completion(out, next);
}
@ -1233,11 +1233,11 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok)
{
wcstring new_in;
new_in.append(in, i);
new_in.append(instr, 0, i);
if (i > 0)
{
if (in[i-1] != VARIABLE_EXPAND)
if (instr.at(i-1) != VARIABLE_EXPAND)
{
new_in.push_back(INTERNAL_SEPARATOR);
}
@ -1246,9 +1246,10 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
new_in.push_back(VARIABLE_EXPAND_EMPTY);
}
}
assert(stop_pos <= insize);
new_in.append(next);
new_in.append(in + stop_pos);
is_ok &= expand_variables2(parser, new_in, out, i, errors);
new_in.append(instr, stop_pos, insize - stop_pos);
is_ok &= expand_variables(parser, new_in, out, i, errors);
}
}
@ -1263,8 +1264,9 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
// even with no value, we still need to parse out slice syntax
// Behave as though we had 1 value, so $foo[1] always works.
const size_t slice_start = stop_pos;
if (in[slice_start] == L'[')
if (slice_start < insize && instr.at(slice_start) == L'[')
{
const wchar_t *in = instr.c_str();
wchar_t *slice_end;
size_t bad_pos;
@ -1295,41 +1297,34 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
}
}
/*
Expand a non-existing variable
*/
/* Expand a non-existing variable */
if (c == VARIABLE_EXPAND)
{
/*
Regular expansion, i.e. expand this argument to nothing
*/
empty = 1;
/* Regular expansion, i.e. expand this argument to nothing */
empty = true;
}
else
{
/*
Expansion to single argument.
*/
/* Expansion to single argument. */
wcstring res;
res.append(in, i);
if (i > 0 && in[i-1] == VARIABLE_EXPAND_SINGLE)
res.append(instr, 0, i);
if (i > 0 && instr.at(i-1) == VARIABLE_EXPAND_SINGLE)
{
res.push_back(VARIABLE_EXPAND_EMPTY);
}
res.append(in + stop_pos);
assert(stop_pos <= insize);
res.append(instr, stop_pos, insize - stop_pos);
is_ok &= expand_variables2(parser, res, out, i, errors);
is_ok &= expand_variables(parser, res, out, i, errors);
return is_ok;
}
}
}
}
if (!empty)
{
append_completion(out, in);
append_completion(out, instr);
}
return is_ok;
@ -1814,7 +1809,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
}
else
{
if (!expand_variables2(parser, next, *out, next.size() - 1, errors))
if (!expand_variables(parser, next, *out, next.size() - 1, errors))
{
return EXPAND_ERROR;
}
@ -1878,12 +1873,10 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
for (i=0; i < in->size(); i++)
{
wcstring next_str = in->at(i).completion;
wcstring next = in->at(i).completion;
int wc_res;
remove_internal_separator(next_str, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
const wchar_t *next = next_str.c_str();
remove_internal_separator(next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
const bool has_wildcard = wildcard_has(next, 1);
if (has_wildcard && (flags & EXECUTABLES_ONLY))
@ -1893,12 +1886,12 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
else if (((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
has_wildcard)
{
const wchar_t *start, *rest;
wcstring start, rest;
if (next[0] == '/')
{
start = L"/";
rest = &next[1];
rest = next.substr(1);
}
else
{
@ -1943,7 +1936,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
{
if (!(flags & ACCEPT_INCOMPLETE))
{
append_completion(*out, next_str);
append_completion(*out, next);
}
}
}

View file

@ -1342,6 +1342,11 @@ static int expand_test(const wchar_t *in, int flags, ...)
}
va_end(va);
if (output.size() != i)
{
res = false;
}
return res;
}
@ -1368,6 +1373,11 @@ static void test_expand()
err(L"Cannot skip wildcard expansion");
}
if (!expand_test(L"/bin/l\\0", ACCEPT_INCOMPLETE, 0))
{
err(L"Failed to handle null escape in expansion");
}
if (system("mkdir -p /tmp/fish_expand_test/")) err(L"mkdir failed");
if (system("touch /tmp/fish_expand_test/.foo")) err(L"touch failed");
if (system("touch /tmp/fish_expand_test/bar")) err(L"touch failed");
@ -1858,6 +1868,7 @@ static void test_colors()
static void test_complete(void)
{
say(L"Testing complete");
const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"};
size_t count = sizeof name_strs / sizeof *name_strs;
const wcstring_list_t names(name_strs, name_strs + count);
@ -1938,6 +1949,46 @@ static void test_complete(void)
complete(L"echo \\$Foo", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty());
/* File completions */
char saved_wd[PATH_MAX + 1] = {};
getcwd(saved_wd, sizeof saved_wd);
if (system("mkdir -p '/tmp/complete_test/'")) err(L"mkdir failed");
if (system("touch '/tmp/complete_test/testfile'")) err(L"touch failed");
if (chdir("/tmp/complete_test/")) err(L"chdir failed");
complete(L"cat te", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.size() == 1);
do_test(completions.at(0).completion == L"stfile");
completions.clear();
complete(L"cat /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.size() == 1);
do_test(completions.at(0).completion == L"stfile");
completions.clear();
complete(L"echo sup > /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.size() == 1);
do_test(completions.at(0).completion == L"stfile");
completions.clear();
complete(L"echo sup > /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.size() == 1);
do_test(completions.at(0).completion == L"stfile");
completions.clear();
// Zero escapes can cause problems. See #1631
complete(L"cat foo\\0", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty());
completions.clear();
complete(L"cat foo\\0bar", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty());
completions.clear();
complete(L"cat \\0", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty());
completions.clear();
complete(L"cat te\\0", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty());
completions.clear();
if (chdir(saved_wd)) err(L"chdir failed");
if (system("rm -Rf '/tmp/complete_test/'")) err(L"rm failed");
complete_set_variable_names(NULL);
/* Test wraps */
@ -2004,7 +2055,7 @@ static void test_completion_insertions()
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
}
static void perform_one_autosuggestion_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line)
static void perform_one_autosuggestion_special_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line)
{
wcstring suggestion;
bool success = autosuggest_suggest_special(command, wd, suggestion);
@ -2034,57 +2085,81 @@ static void test_autosuggest_suggest_special()
if (system("mkdir -p ~/test_autosuggest_suggest_special/")) err(L"mkdir failed"); //make sure tilde is handled
const wcstring wd = L"/tmp/autosuggest_test/";
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/0", wd, L"cd /tmp/autosuggest_test/0foobar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/0", wd, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/0", wd, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 0", wd, L"cd 0foobar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"0", wd, L"cd \"0foobar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '0", wd, L"cd '0foobar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/0", wd, L"cd /tmp/autosuggest_test/0foobar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/0", wd, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/0", wd, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 0", wd, L"cd 0foobar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"0", wd, L"cd \"0foobar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '0", wd, L"cd '0foobar/'", __LINE__);
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/1", wd, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/1", wd, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/1", wd, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 1", wd, L"cd 1foo\\ bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"1", wd, L"cd \"1foo bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '1", wd, L"cd '1foo bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/1", wd, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/1", wd, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/1", wd, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 1", wd, L"cd 1foo\\ bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"1", wd, L"cd \"1foo bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '1", wd, L"cd '1foo bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/2", wd, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/2", wd, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/2", wd, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 2", wd, L"cd 2foo\\ \\ bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"2", wd, L"cd \"2foo bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '2", wd, L"cd '2foo bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/2", wd, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/2", wd, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/2", wd, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 2", wd, L"cd 2foo\\ \\ bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"2", wd, L"cd \"2foo bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '2", wd, L"cd '2foo bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/3", wd, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/3", wd, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/3", wd, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 3", wd, L"cd 3foo\\\\bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"3", wd, L"cd \"3foo\\bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '3", wd, L"cd '3foo\\bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/3", wd, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/3", wd, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/3", wd, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 3", wd, L"cd 3foo\\\\bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"3", wd, L"cd \"3foo\\bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '3", wd, L"cd '3foo\\bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/4", wd, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/4", wd, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/4", wd, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 4", wd, L"cd 4foo\\'bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"4", wd, L"cd \"4foo'bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '4", wd, L"cd '4foo\\'bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/4", wd, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/4", wd, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/4", wd, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 4", wd, L"cd 4foo\\'bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"4", wd, L"cd \"4foo'bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '4", wd, L"cd '4foo\\'bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/5", wd, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/5", wd, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/5", wd, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd 5", wd, L"cd 5foo\\\"bar/", __LINE__);
perform_one_autosuggestion_test(L"cd \"5", wd, L"cd \"5foo\\\"bar/\"", __LINE__);
perform_one_autosuggestion_test(L"cd '5", wd, L"cd '5foo\"bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/5", wd, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/5", wd, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/5", wd, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__);
perform_one_autosuggestion_special_test(L"cd 5", wd, L"cd 5foo\\\"bar/", __LINE__);
perform_one_autosuggestion_special_test(L"cd \"5", wd, L"cd \"5foo\\\"bar/\"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '5", wd, L"cd '5foo\"bar/'", __LINE__);
perform_one_autosuggestion_test(L"cd ~/test_autosuggest_suggest_specia", wd, L"cd ~/test_autosuggest_suggest_special/", __LINE__);
perform_one_autosuggestion_special_test(L"cd ~/test_autosuggest_suggest_specia", wd, L"cd ~/test_autosuggest_suggest_special/", __LINE__);
// A single quote should defeat tilde expansion
perform_one_autosuggestion_test(L"cd '~/test_autosuggest_suggest_specia'", wd, L"", __LINE__);
perform_one_autosuggestion_special_test(L"cd '~/test_autosuggest_suggest_specia'", wd, L"", __LINE__);
if (system("rm -Rf '/tmp/autosuggest_test/'")) err(L"rm failed");
if (system("rm -Rf ~/test_autosuggest_suggest_special/")) err(L"rm failed");
}
static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, const wcstring &wd, long line)
{
completion_list_t comps;
complete(command, comps, COMPLETION_REQUEST_AUTOSUGGESTION);
do_test(comps.empty());
if (! comps.empty())
{
const wcstring &suggestion = comps.front().completion;
printf("line %ld: complete() expected to return nothing for %ls\n", line, command.c_str());
printf(" instead got: %ls\n", suggestion.c_str());
}
}
static void test_autosuggestion_ignores()
{
say(L"Testing scenarios that should produce no autosuggestions");
const wcstring wd = L"/tmp/autosuggest_test/";
// Do not do file autosuggestions immediately after certain statement terminators - see #1631
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST|", wd, __LINE__);
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST&", wd, __LINE__);
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST#comment", wd, __LINE__);
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST;", wd, __LINE__);
}
static void test_autosuggestion_combining()
{
say(L"Testing autosuggestion combining");
@ -3631,6 +3706,7 @@ int main(int argc, char **argv)
if (should_test_function("universal")) test_universal_callbacks();
if (should_test_function("notifiers")) test_universal_notifiers();
if (should_test_function("completion_insertions")) test_completion_insertions();
if (should_test_function("autosuggestion_ignores")) test_autosuggestion_ignores();
if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining();
if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
if (should_test_function("history")) history_tests_t::test_history();

View file

@ -105,18 +105,14 @@ wildcards using **.
/** Hashtable containing all descriptions that describe an executable */
static std::map<wcstring, wcstring> suffix_map;
bool wildcard_has(const wchar_t *str, bool internal)
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal)
{
if (!str)
{
debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__);
return false;
}
assert(str != NULL);
const wchar_t *end = str + len;
if (internal)
{
for (; *str; str++)
for (; str < end; str++)
{
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
return true;
@ -125,7 +121,7 @@ bool wildcard_has(const wchar_t *str, bool internal)
else
{
wchar_t prev=0;
for (; *str; str++)
for (; str < end; str++)
{
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\'))
return true;
@ -136,6 +132,18 @@ bool wildcard_has(const wchar_t *str, bool internal)
return false;
}
bool wildcard_has(const wchar_t *str, bool internal)
{
assert(str != NULL);
return wildcard_has_impl(str, wcslen(str), internal);
}
bool wildcard_has(const wcstring &str, bool internal)
{
return wildcard_has_impl(str.data(), str.size(), internal);
}
/**
Check whether the string str matches the wildcard string wc.
@ -1084,6 +1092,11 @@ int wildcard_expand(const wchar_t *wc,
int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector<completion_t> &outputs)
{
/* Hackish fix for 1631. We are about to call c_str(), which will produce a string truncated at any embedded nulls. We could fix this by passing around the size, etc. However embedded nulls are never allowed in a filename, so we just check for them and return 0 (no matches) if there is an embedded null. This isn't quite right, e.g. it will fail for \0?, but that is an edge case. */
if (wc.find(L'\0') != wcstring::npos)
{
return 0;
}
// PCA: not convinced this temporary variable is really necessary
std::vector<completion_t> lst;
int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, lst);

View file

@ -79,9 +79,9 @@ int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_
*/
bool wildcard_match(const wcstring &str, const wcstring &wc, bool leading_dots_fail_to_match = false);
/** Check if the specified string contains wildcards */
bool wildcard_has(const wchar_t *str, bool internal);
bool wildcard_has(const wcstring &, bool internal);
bool wildcard_has(const wchar_t *, bool internal);
/**
Test wildcard completion