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 fewer string scans and overall just less work. But until that
happens, don't edit it unless you know exactly what you are doing, happens, don't edit it unless you know exactly what you are doing,
and do proper testing afterwards. and do proper testing afterwards.
*/
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) This function operates on strings backwards, starting at last_idx.
*/
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()); // We permit last_idx to be beyond the end of the string if and only if the string is empty
int result = expand_variables_internal(parser, in, out, last_idx, errors); assert(instr.empty() || (last_idx >= 0 && (size_t)last_idx < instr.size()));
free(in);
return result; // Make this explicit
if (instr.empty())
{
append_completion(out, instr);
return true;
} }
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) bool is_ok = true;
{ bool empty = false;
int is_ok= 1; const size_t insize = instr.size();
int empty=0;
wcstring var_tmp; 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--) 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)) if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE))
{ {
long start_pos = i+1; 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; stop_pos = start_pos;
while (1) while (stop_pos < insize)
{ {
const wchar_t nc = in[stop_pos]; const wchar_t nc = instr.at(stop_pos);
if (!(nc))
break;
if (nc == VARIABLE_EXPAND_EMPTY) if (nc == VARIABLE_EXPAND_EMPTY)
{ {
stop_pos++; stop_pos++;
break; break;
} }
if (!(wcsvarchr(nc))) if (!wcsvarchr(nc))
break; break;
stop_pos++; stop_pos++;
@ -1109,13 +1110,13 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (var_len == 0) 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; break;
} }
var_tmp.append(in + start_pos, var_len); var_tmp.append(instr, start_pos, var_len);
env_var_t var_val; env_var_t var_val;
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY) 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) 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; 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; wchar_t *slice_end;
size_t bad_pos; size_t bad_pos;
all_vars=0; 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()); bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size());
if (bad_pos != 0) if (bad_pos != 0)
{ {
append_syntax_error(errors, append_syntax_error(errors,
stop_pos + bad_pos, stop_pos + bad_pos,
L"Invalid index value"); L"Invalid index value");
is_ok = 0; is_ok = false;
break; break;
} }
stop_pos = (slice_end-in); 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, append_syntax_error(errors,
slice_start + var_src_pos, slice_start + var_src_pos,
ARRAY_BOUNDS_ERR); ARRAY_BOUNDS_ERR);
is_ok=0; is_ok = false;
var_idx_list.resize(j); var_idx_list.resize(j);
break; break;
} }
@ -1187,14 +1188,12 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok) if (is_ok)
{ {
if (is_single) if (is_single)
{ {
in[i]=0; wcstring res(instr, 0, i);
wcstring res = in;
if (i > 0) if (i > 0)
{ {
if (in[i-1] != VARIABLE_EXPAND_SINGLE) if (instr.at(i-1) != VARIABLE_EXPAND_SINGLE)
{ {
res.push_back(INTERNAL_SEPARATOR); 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(next);
} }
} }
res.append(in + stop_pos); assert(stop_pos <= insize);
is_ok &= expand_variables2(parser, res, out, i, errors); res.append(instr, stop_pos, insize - stop_pos);
is_ok &= expand_variables(parser, res, out, i, errors);
} }
else else
{ {
for (size_t j=0; j<var_item_list.size(); j++) for (size_t j=0; j<var_item_list.size(); j++)
{ {
const wcstring &next = var_item_list.at(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); append_completion(out, next);
} }
@ -1233,11 +1233,11 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok) if (is_ok)
{ {
wcstring new_in; wcstring new_in;
new_in.append(in, i); new_in.append(instr, 0, i);
if (i > 0) if (i > 0)
{ {
if (in[i-1] != VARIABLE_EXPAND) if (instr.at(i-1) != VARIABLE_EXPAND)
{ {
new_in.push_back(INTERNAL_SEPARATOR); 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); new_in.push_back(VARIABLE_EXPAND_EMPTY);
} }
} }
assert(stop_pos <= insize);
new_in.append(next); new_in.append(next);
new_in.append(in + stop_pos); new_in.append(instr, stop_pos, insize - stop_pos);
is_ok &= expand_variables2(parser, new_in, out, i, errors); 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 // even with no value, we still need to parse out slice syntax
// Behave as though we had 1 value, so $foo[1] always works. // Behave as though we had 1 value, so $foo[1] always works.
const size_t slice_start = stop_pos; 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; wchar_t *slice_end;
size_t bad_pos; 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) if (c == VARIABLE_EXPAND)
{ {
/* /* Regular expansion, i.e. expand this argument to nothing */
Regular expansion, i.e. expand this argument to nothing empty = true;
*/
empty = 1;
} }
else else
{ {
/* /* Expansion to single argument. */
Expansion to single argument.
*/
wcstring res; wcstring res;
res.append(in, i); res.append(instr, 0, i);
if (i > 0 && in[i-1] == VARIABLE_EXPAND_SINGLE) if (i > 0 && instr.at(i-1) == VARIABLE_EXPAND_SINGLE)
{ {
res.push_back(VARIABLE_EXPAND_EMPTY); 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; return is_ok;
} }
} }
} }
} }
if (!empty) if (!empty)
{ {
append_completion(out, in); append_completion(out, instr);
} }
return is_ok; return is_ok;
@ -1814,7 +1809,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
} }
else else
{ {
if (!expand_variables2(parser, next, *out, next.size() - 1, errors)) if (!expand_variables(parser, next, *out, next.size() - 1, errors))
{ {
return EXPAND_ERROR; 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++) for (i=0; i < in->size(); i++)
{ {
wcstring next_str = in->at(i).completion; wcstring next = in->at(i).completion;
int wc_res; int wc_res;
remove_internal_separator(next_str, (EXPAND_SKIP_WILDCARDS & flags) ? true : false); remove_internal_separator(next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
const wchar_t *next = next_str.c_str();
const bool has_wildcard = wildcard_has(next, 1); const bool has_wildcard = wildcard_has(next, 1);
if (has_wildcard && (flags & EXECUTABLES_ONLY)) 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))) || else if (((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
has_wildcard) has_wildcard)
{ {
const wchar_t *start, *rest; wcstring start, rest;
if (next[0] == '/') if (next[0] == '/')
{ {
start = L"/"; start = L"/";
rest = &next[1]; rest = next.substr(1);
} }
else else
{ {
@ -1943,7 +1936,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
{ {
if (!(flags & ACCEPT_INCOMPLETE)) 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); va_end(va);
if (output.size() != i)
{
res = false;
}
return res; return res;
} }
@ -1368,6 +1373,11 @@ static void test_expand()
err(L"Cannot skip wildcard expansion"); 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("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/.foo")) err(L"touch failed");
if (system("touch /tmp/fish_expand_test/bar")) 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) static void test_complete(void)
{ {
say(L"Testing complete"); say(L"Testing complete");
const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"}; 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; size_t count = sizeof name_strs / sizeof *name_strs;
const wcstring_list_t names(name_strs, name_strs + count); 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); complete(L"echo \\$Foo", completions, COMPLETION_REQUEST_DEFAULT);
do_test(completions.empty()); 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); complete_set_variable_names(NULL);
/* Test wraps */ /* Test wraps */
@ -2004,7 +2055,7 @@ static void test_completion_insertions()
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^"); 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; wcstring suggestion;
bool success = autosuggest_suggest_special(command, wd, 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 if (system("mkdir -p ~/test_autosuggest_suggest_special/")) err(L"mkdir failed"); //make sure tilde is handled
const wcstring wd = L"/tmp/autosuggest_test/"; 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_special_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_special_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_special_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_special_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 \"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 '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_special_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_special_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_special_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_special_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 \"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 '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_special_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_special_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_special_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_special_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 \"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 '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_special_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_special_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_special_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_special_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 \"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 '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_special_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_special_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_special_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_special_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 \"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 '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_special_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_special_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_special_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_special_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 \"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 '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 // 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 '/tmp/autosuggest_test/'")) err(L"rm failed");
if (system("rm -Rf ~/test_autosuggest_suggest_special/")) 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() static void test_autosuggestion_combining()
{ {
say(L"Testing 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("universal")) test_universal_callbacks();
if (should_test_function("notifiers")) test_universal_notifiers(); if (should_test_function("notifiers")) test_universal_notifiers();
if (should_test_function("completion_insertions")) test_completion_insertions(); 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("autosuggestion_combining")) test_autosuggestion_combining();
if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special(); if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
if (should_test_function("history")) history_tests_t::test_history(); 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 */ /** Hashtable containing all descriptions that describe an executable */
static std::map<wcstring, wcstring> suffix_map; static std::map<wcstring, wcstring> suffix_map;
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
bool wildcard_has(const wchar_t *str, bool internal) static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal)
{ {
if (!str) assert(str != NULL);
{ const wchar_t *end = str + len;
debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__);
return false;
}
if (internal) if (internal)
{ {
for (; *str; str++) for (; str < end; str++)
{ {
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE)) if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
return true; return true;
@ -125,7 +121,7 @@ bool wildcard_has(const wchar_t *str, bool internal)
else else
{ {
wchar_t prev=0; wchar_t prev=0;
for (; *str; str++) for (; str < end; str++)
{ {
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\')) if (((*str == L'*') || (*str == L'?')) && (prev != L'\\'))
return true; return true;
@ -136,6 +132,18 @@ bool wildcard_has(const wchar_t *str, bool internal)
return false; 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. 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) 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 // PCA: not convinced this temporary variable is really necessary
std::vector<completion_t> lst; std::vector<completion_t> lst;
int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, 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); bool wildcard_match(const wcstring &str, const wcstring &wc, bool leading_dots_fail_to_match = false);
/** Check if the specified string contains wildcards */ /** 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 Test wildcard completion