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

Reply via email to