Below is a simple test script. Mainline GCC is expected to fail on
test5 and test7, both due to incorrect splitting of paths
containing whitespace characters.
On 2026/5/28 21:18, Wang Jinghao wrote:
> On Windows, GCC is very likely to be installed in a directory
> containing spaces. The patch from the original PR was used
> downstream in MSYS2, while this patch provides a more general
> way to fix the issue.
>
> Currently, there is no dedicated test suite for specs. I have a
> script for roughly checking the functionality of this part.
>
> Bootstrapped and regtested on x86_64-linux-gnu and x86_64-w64-mingw32.
>
> -- >8 --
>
> do_spec_1() treats whitespace characters (' ' and '\t') as argument
> terminators, causing splitting to occur. Therefore, we need to wrap
> all spec functions whose return values should not be split
> (e.g. file path) with the quote_spec() function. For example,
> "dir with space/something.o" will be split into
> "dir" "with" and "space/something.o".
>
> pass_through_libs_spec_func() is somewhat special because the
> multiple arguments it returns are expected to be split by ' ', but
> each individual argument should not be split. Therefore, each
> individual argument must be quoted first and then concatenated.
>
> PR driver/90692
>
> gcc/ChangeLog:
>
> * gcc.cc (if_exists_spec_function): Escape special characters.
> (if_exists_else_spec_function): Likewise.
> (if_exists_then_else_spec_function): Likewise.
> (version_compare_spec_function): Likewise.
> (find_file_spec_function): Likewise.
> (find_plugindir_spec_function): Likewise.
> (pass_through_libs_spec_func): Likewise.
> (find_fortran_preinclude_file): Likewise.
>
> Signed-off-by: Wang Jinghao <[email protected]>
> ---
> gcc/gcc.cc | 32 +++++++++++++++++++-------------
> 1 file changed, 19 insertions(+), 13 deletions(-)
>
> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
> index 43b0158ee02..82f1e23b500 100644
> --- a/gcc/gcc.cc
> +++ b/gcc/gcc.cc
> @@ -10532,7 +10532,7 @@ if_exists_spec_function (int argc, const char **argv)
> {
> /* Must have only one argument. */
> if (argc == 1 && IS_ABSOLUTE_PATH (argv[0]) && ! access (argv[0], R_OK))
> - return argv[0];
> + return quote_spec (xstrdup (argv[0]));
>
> return NULL;
> }
> @@ -10550,9 +10550,9 @@ if_exists_else_spec_function (int argc, const char
> **argv)
> return NULL;
>
> if (IS_ABSOLUTE_PATH (argv[0]) && ! access (argv[0], R_OK))
> - return argv[0];
> + return quote_spec (xstrdup (argv[0]));
>
> - return argv[1];
> + return quote_spec (xstrdup (argv[1]));
> }
>
> /* if-exists-then-else built-in spec function.
> @@ -10570,10 +10570,10 @@ if_exists_then_else_spec_function (int argc, const
> char **argv)
> return NULL;
>
> if (IS_ABSOLUTE_PATH (argv[0]) && ! access (argv[0], R_OK))
> - return argv[1];
> + return quote_spec (xstrdup (argv[1]));
>
> if (argc == 3)
> - return argv[2];
> + return quote_spec (xstrdup (argv[2]));
>
> return NULL;
> }
> @@ -10771,7 +10771,7 @@ version_compare_spec_function (int argc, const char
> **argv)
> if (! result)
> return NULL;
>
> - return argv[nargs + 2];
> + return quote_spec (xstrdup (argv[nargs + 2]));
> }
>
> /* %:include builtin spec function. This differs from %include in that it
> @@ -10805,7 +10805,7 @@ find_file_spec_function (int argc, const char **argv)
> abort ();
>
> file = find_file (argv[0]);
> - return file;
> + return quote_spec (xstrdup (file));
> }
>
>
> @@ -10815,13 +10815,13 @@ find_file_spec_function (int argc, const char
> **argv)
> static const char *
> find_plugindir_spec_function (int argc, const char **argv ATTRIBUTE_UNUSED)
> {
> - const char *option;
> + char *option;
>
> if (argc != 0)
> abort ();
>
> option = concat ("-iplugindir=", find_file ("plugin"), NULL);
> - return option;
> + return quote_spec (option);
> }
>
>
> @@ -10999,13 +10999,17 @@ pass_through_libs_spec_func (int argc, const char
> **argv)
> break;
> else if (!*lopt)
> lopt = argv[n];
> - prepended = concat (prepended, "-plugin-opt=-pass-through=-l",
> - lopt, " ", NULL);
> + char *arg = concat ("-plugin-opt=-pass-through=-l", lopt, NULL);
> + arg = quote_spec (arg);
> + prepended = concat (prepended, arg, " ", NULL);
> + free (arg);
> }
> else if (!strcmp (".a", argv[n] + strlen (argv[n]) - 2))
> {
> - prepended = concat (prepended, "-plugin-opt=-pass-through=",
> - argv[n], " ", NULL);
> + char *arg = concat ("-plugin-opt=-pass-through=", argv[n], NULL);
> + arg = quote_spec (arg);
> + prepended = concat (prepended, arg, " ", NULL);
> + free (arg);
> }
> if (prepended != old)
> free (old);
> @@ -11235,6 +11239,8 @@ find_fortran_preinclude_file (int argc, const char
> **argv)
> }
>
> path_prefix_reset (&prefixes);
> + if (result)
> + result = quote_spec (result);
> return result;
> }
>
#!/bin/bash
# make directory {test_dir, test_dir/normal_dir, test_dir/dir with spaces}
# make something.o to {test_dir/normal_dir/something.o, test_dir/dir with
spaces/something.o}
# write main.c to test_dir/main.c
# make {test_dir/total.log, test_dir/fail.log}
# dump default specs to test_dir/specs_default
prepare() {
local normal_dir="$1"
local dir_with_space="$2"
if [[ -d "test_dir" ]]; then
return
fi
mkdir -p "test_dir"
pushd "test_dir" > /dev/null
mkdir -p "${PWD}/${normal_dir}"
mkdir -p "${PWD}/${dir_with_space}"
printf "\n" > "something.c"
printf "int main() { return 0; }\n" > "main.c"
gcc --compile something.c
cp something.o "${normal_dir}/something.o"
mv something.o "${dir_with_space}/something.o"
touch {total,fail}.log
gcc -dumpspecs > specs_default
popd > /dev/null
}
# excute cmd,
# compare output with expected_out,
# print result and log details to total.log and fail.log
expected() {
local name="$1"
local cmd="$2"
local expected_out="$3"
local got
got="$(bash -c "$cmd")"
if [[ -z "$got" && -z "$expected_out" ]] || \
[[ -n "$got" && -n "$expected_out" && "$got" == *"$expected_out"* ]]; then
printf '\033[32m%s Success\033[0m\n' "$name"
{
printf '##### %s Success #####\n' "$name"
printf 'Command:\n%s\n' "$cmd"
printf 'Expected:\n'
printf '%s\n' "$expected_out"
printf 'Got:\n'
printf '%s\n\n' "$got"
} >> total.log
return 0
else
printf '\033[31m%s Failed\033[0m\n' "$name"
{
printf '##### %s Failed #####\n' "$name"
printf 'Command:\n%s\n' "$cmd"
printf 'Expected:\n'
printf '%s\n' "$expected_out"
printf 'Got:\n'
printf '%s\n\n' "$got"
} | tee -a total.log fail.log >/dev/null
return 1
fi
}
# generate specs by appending var to the line after *segment: in specs_default
generate_specs() {
local segment="$1"
local var="$2"
local output="$3"
sed "/^\*${segment}:$/ { n; s/$/ ${var}/ }" specs_default > "${output}"
}
test_wrap() {
local id="$1"
local var="$2"
local dir="$3"
local expected_compile_out="$4"
printf 'Test %s with %s in %s\n' "$id" "$var" "$dir"
generate_specs "endfile" "${var}" "specs_${id}"
expected "Test${id} compile" \
"gcc -specs=specs_${id} main.c -B\"${dir}\" -o a.out 2>&1" \
"${expected_compile_out}"
if [[ $? -eq 0 ]] && [[ -n "$5" ]]; then
local expected_check_out="$5"
expected "Test${id} check" \
"strings a.out | grep ${expected_check_out}" \
"${expected_check_out}"
fi
}
main() {
normal_dir="normal_dir"
dir_with_space="dir with spaces"
prepare "${normal_dir}" "${dir_with_space}"
pushd "test_dir" > /dev/null
normal_dir="$PWD/${normal_dir}"
dir_with_space="$PWD/${dir_with_space}"
printf "====================\n" | tee -a {total,fail}.log > /dev/null
gcc --version | tee -a {total,fail}.log
# append_to_specs, compile_include_dir, expected_compile_out,
[expected_check_out]
test_args=(
"" "${dir_with_space}" "" ""
"something.o%s" "${normal_dir}" ""
"something.c"
"something.o%s" "${dir_with_space}" ""
"something.c"
"%:if-exists(something.o%s)" "${normal_dir}" ""
"something.c"
"%:if-exists(something.o%s)" "${dir_with_space}" ""
"something.c"
"%:if-exists(%:if-exists(something.o%s))" "${normal_dir}" ""
"something.c"
"%:if-exists(%:if-exists(something.o%s))" "${dir_with_space}" ""
"something.c"
"non-existfile%s" "${normal_dir}" "cannot
find non-existfile" ""
"%:if-exists(non-existfile%s)" "${normal_dir}" "" ""
"%:if-exists(%:if-exists(non-existfile%s))" "${normal_dir}" "" ""
)
for ((i=0; i<${#test_args[@]}; i+=4)); do
id=$(((i+4)/4))
var="${test_args[i]}"
dir="${test_args[i+1]}"
expected_compile_out="${test_args[i+2]}"
expected_check_out="${test_args[i+3]}"
test_wrap "$id" "$var" "$dir" "$expected_compile_out" "$expected_check_out"
done
popd > /dev/null
}
#set -x
main