[PATCH driver/36312] should refuse to overwrite input file with output file

Manuel López-Ibáñez lopezibanez@gmail.com
Sat Nov 8 10:23:00 GMT 2014


This patch is a minor variant of the one approved here:

https://gcc.gnu.org/ml/gcc-patches/2014-11/msg00246.html

fixing the problem with linker parameters (which are stored in infiles).

https://gcc.gnu.org/ml/gcc-patches/2014-11/msg00391.html

The only part that has changed, and thus requires approval, is in
gcc/gcc.c, in particular the condition:

+    if ((!infiles[i].language || infiles[i].language[0] != '*')
+        && canonical_filename_eq (infiles[i].name, output_file))

is new.

Boot&tested on x86_64-linux-gnu with
--enable-languages=c,c++,objc,fortran,ada,obj-c++

OK?

gcc/testsuite/ChangeLog:

2014-11-08  Anthony Brandon  <anthony.brandon@gmail.com>
        Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR driver/36312
    * gcc.misc-tests/output.exp: New test case for identical input and
    output files.

include/ChangeLog:

2014-11-08  Anthony Brandon  <anthony.brandon@gmail.com>
        Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR driver/36312
    * filenames.h: Add prototype for canonical_filename_eq.

gcc/ChangeLog:

2014-11-08  Anthony Brandon  <anthony.brandon@gmail.com>
        Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR driver/36312
    * diagnostic-core.h: Add prototype for fatal_error.
    * diagnostic.c (fatal_error): New function fatal_error.
    * gcc.c (store_arg): Remove have_o_argbuf_index.
    (process_command): Check if input and output files are the same.
    * toplev.c (init_asm_output): Check if input and output files are
    the same.

libiberty/ChangeLog:

2014-11-08  Anthony Brandon  <anthony.brandon@gmail.com>
        Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR driver/36312
    * filename_cmp.c (canonical_filename_eq): New function to check if
    file names are the same.
    * functions.texi: Updated with documentation for new function.
-------------- next part --------------
Index: include/filenames.h
===================================================================
--- include/filenames.h	(revision 217234)
+++ include/filenames.h	(working copy)
@@ -88,10 +88,12 @@ extern int filename_ncmp (const char *s1
 
 extern hashval_t filename_hash (const void *s);
 
 extern int filename_eq (const void *s1, const void *s2);
 
+extern int canonical_filename_eq (const char *a, const char *b);
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif /* FILENAMES_H */
Index: libiberty/functions.texi
===================================================================
--- libiberty/functions.texi	(revision 217234)
+++ libiberty/functions.texi	(working copy)
@@ -123,10 +123,20 @@ is deprecated in favor of @code{memset}.
 Uses @code{malloc} to allocate storage for @var{nelem} objects of
 @var{elsize} bytes each, then zeros the memory.
 
 @end deftypefn
 
+@c filename_cmp.c:201
+@deftypefn Extension int canonical_filename_eq (const char *@var{a}, const char *@var{b})
+
+Return non-zero if file names @var{a} and @var{b} are equivalent.
+This function compares the canonical versions of the filenames as returned by
+@code{lrealpath()}, so that so that different file names pointing to the same
+underlying file are treated as being identical.
+
+@end deftypefn
+
 @c choose-temp.c:45
 @deftypefn Extension char* choose_temp_base (void)
 
 Return a prefix for temporary file names or @code{NULL} if unable to
 find one.  The current directory is chosen if all else fails so the
@@ -284,11 +294,11 @@ Find the first (least significant) bit s
 numbered from right to left, starting with bit 1 (corresponding to the
 value 1).  If @var{valu} is zero, zero is returned.
 
 @end deftypefn
 
-@c filename_cmp.c:32
+@c filename_cmp.c:37
 @deftypefn Extension int filename_cmp (const char *@var{s1}, const char *@var{s2})
 
 Return zero if the two file names @var{s1} and @var{s2} are equivalent.
 If not equivalent, the returned value is similar to what @code{strcmp}
 would return.  In other words, it returns a negative value if @var{s1}
@@ -301,28 +311,28 @@ the case when the two filenames point to
 However, it does handle the fact that on DOS-like file systems, forward
 and backward slashes are equal.
 
 @end deftypefn
 
-@c filename_cmp.c:178
+@c filename_cmp.c:183
 @deftypefn Extension int filename_eq (const void *@var{s1}, const void *@var{s2})
 
 Return non-zero if file names @var{s1} and @var{s2} are equivalent.
 This function is for use with hashtab.c hash tables.
 
 @end deftypefn
 
-@c filename_cmp.c:147
+@c filename_cmp.c:152
 @deftypefn Extension hashval_t filename_hash (const void *@var{s})
 
 Return the hash value for file name @var{s} that will be compared
 using filename_cmp.
 This function is for use with hashtab.c hash tables.
 
 @end deftypefn
 
-@c filename_cmp.c:89
+@c filename_cmp.c:94
 @deftypefn Extension int filename_ncmp (const char *@var{s1}, const char *@var{s2}, size_t @var{n})
 
 Return zero if the two file names @var{s1} and @var{s2} are equivalent
 in range @var{n}.
 If not equivalent, the returned value is similar to what @code{strncmp}
Index: libiberty/filename_cmp.c
===================================================================
--- libiberty/filename_cmp.c	(revision 217234)
+++ libiberty/filename_cmp.c	(working copy)
@@ -22,12 +22,17 @@
 
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
 
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
 #include "filenames.h"
 #include "safe-ctype.h"
+#include "libiberty.h"
 
 /*
 
 @deftypefn Extension int filename_cmp (const char *@var{s1}, const char *@var{s2})
 
@@ -188,5 +193,29 @@ int
 filename_eq (const void *s1, const void *s2)
 {
   /* The casts are for -Wc++-compat.  */
   return filename_cmp ((const char *) s1, (const char *) s2) == 0;
 }
+
+/*
+
+@deftypefn Extension int canonical_filename_eq (const char *@var{a}, const char *@var{b})
+
+Return non-zero if file names @var{a} and @var{b} are equivalent.
+This function compares the canonical versions of the filenames as returned by
+@code{lrealpath()}, so that so that different file names pointing to the same
+underlying file are treated as being identical.
+
+@end deftypefn
+
+*/
+
+int
+canonical_filename_eq (const char * a, const char * b)
+{
+  char * ca = lrealpath(a);
+  char * cb = lrealpath(b);
+  int res = filename_eq (ca, cb);
+  free (ca);
+  free (cb);
+  return res;
+}
Index: gcc/diagnostic.c
===================================================================
--- gcc/diagnostic.c	(revision 217234)
+++ gcc/diagnostic.c	(working copy)
@@ -1161,10 +1161,27 @@ fatal_error (const char *gmsgid, ...)
   va_end (ap);
 
   gcc_unreachable ();
 }
 
+/* An error which is severe enough that we make no attempt to
+   continue.  Do not use this for internal consistency checks; that's
+   internal_error.  Use of this function should be rare.  */
+void
+fatal_error (location_t loc, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, loc, DK_FATAL);
+  report_diagnostic (&diagnostic);
+  va_end (ap);
+
+  gcc_unreachable ();
+}
+
 /* An internal consistency check has failed.  We make no attempt to
    continue.  Note that unless there is debugging value to be had from
    a more specific message, or some other good reason, you should use
    abort () instead of calling this function directly.  */
 void
Index: gcc/gcc.c
===================================================================
--- gcc/gcc.c	(revision 217234)
+++ gcc/gcc.c	(working copy)
@@ -1700,21 +1700,19 @@ typedef const char *const_char_p; /* For
 
 /* Vector of pointers to arguments in the current line of specifications.  */
 
 static vec<const_char_p> argbuf;
 
-/* Position in the argbuf vector containing the name of the output file
-   (the value associated with the "-o" flag).  */
-
-static int have_o_argbuf_index = 0;
-
 /* Were the options -c, -S or -E passed.  */
 static int have_c = 0;
 
 /* Was the option -o passed.  */
 static int have_o = 0;
 
+/* Pointer to output file name passed in with -o. */
+static const char *output_file = 0;
+
 /* This is the list of suffixes and codes (%g/%u/%U/%j) and the associated
    temp file.  If the HOST_BIT_BUCKET is used for %j, no entry is made for
    it here.  */
 
 static struct temp_name {
@@ -1760,12 +1758,10 @@ clear_args (void)
 static void
 store_arg (const char *arg, int delete_always, int delete_failure)
 {
   argbuf.safe_push (arg);
 
-  if (strcmp (arg, "-o") == 0)
-    have_o_argbuf_index = argbuf.length ();
   if (delete_always || delete_failure)
     {
       const char *p;
       /* If the temporary file we should delete is specified as
 	 part of a joined argument extract the filename.  */
@@ -3711,10 +3707,11 @@ driver_handle_option (struct gcc_options
     case OPT_o:
       have_o = 1;
 #if defined(HAVE_TARGET_EXECUTABLE_SUFFIX) || defined(HAVE_TARGET_OBJECT_SUFFIX)
       arg = convert_filename (arg, ! have_c, 0);
 #endif
+      output_file = arg;
       /* Save the output name in case -save-temps=obj was used.  */
       save_temps_prefix = xstrdup (arg);
       /* On some systems, ld cannot handle "-o" without a space.  So
 	 split the option from its argument.  */
       save_switch ("-o", 1, &arg, validated, true);
@@ -4050,10 +4047,20 @@ process_command (unsigned int decoded_op
       read_cmdline_option (&global_options, &global_options_set,
 			   decoded_options + j, UNKNOWN_LOCATION,
 			   CL_DRIVER, &handlers, global_dc);
     }
 
+  if (output_file && strcmp (output_file, "-"))
+    {
+      int i;
+      for (i = 0; i < n_infiles; i++)
+	if ((!infiles[i].language || infiles[i].language[0] != '*')
+	    && canonical_filename_eq (infiles[i].name, output_file))
+	  fatal_error ("input file %qs is the same as output file",
+		       output_file);
+    }
+
   /* If -save-temps=obj and -o name, create the prefix to use for %b.
      Otherwise just make -save-temps=obj the same as -save-temps=cwd.  */
   if (save_temps_flag == SAVE_TEMPS_OBJ && save_temps_prefix != NULL)
     {
       save_temps_length = strlen (save_temps_prefix);
Index: gcc/diagnostic-core.h
===================================================================
--- gcc/diagnostic-core.h	(revision 217234)
+++ gcc/diagnostic-core.h	(working copy)
@@ -66,10 +66,12 @@ extern void error (const char *, ...) AT
 extern void error_n (location_t, int, const char *, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,5) ATTRIBUTE_GCC_DIAG(4,5);
 extern void error_at (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void fatal_error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2)
      ATTRIBUTE_NORETURN;
+extern void fatal_error (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3)
+     ATTRIBUTE_NORETURN;
 /* Pass one of the OPT_W* from options.h as the second parameter.  */
 extern bool pedwarn (location_t, int, const char *, ...)
      ATTRIBUTE_GCC_DIAG(3,4);
 extern bool permerror (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void sorry (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
Index: gcc/toplev.c
===================================================================
--- gcc/toplev.c	(revision 217234)
+++ gcc/toplev.c	(working copy)
@@ -940,14 +940,21 @@ init_asm_output (const char *name)
 	  strcat (dumpname, ".s");
 	  asm_file_name = dumpname;
 	}
       if (!strcmp (asm_file_name, "-"))
 	asm_out_file = stdout;
-      else
+      else if (!canonical_filename_eq (asm_file_name, name))
 	asm_out_file = fopen (asm_file_name, "w");
+      else
+	/* Use fatal_error (UNKOWN_LOCATION) instead of just fatal_error to
+	   prevent gcc from printing the first line in the current file. */
+	fatal_error (UNKNOWN_LOCATION,
+		     "input file %qs is the same as output file",
+		     asm_file_name);
       if (asm_out_file == 0)
-	fatal_error ("can%'t open %s for writing: %m", asm_file_name);
+	fatal_error (UNKNOWN_LOCATION,
+		     "can%'t open %qs for writing: %m", asm_file_name);
     }
 
   if (!flag_syntax_only)
     {
       targetm.asm_out.file_start ();
Index: gcc/testsuite/gcc.misc-tests/output.exp
===================================================================
--- gcc/testsuite/gcc.misc-tests/output.exp	(revision 0)
+++ gcc/testsuite/gcc.misc-tests/output.exp	(revision 0)
@@ -0,0 +1,66 @@
+# Copyright (C) 2005-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Run GCC with the input file also specified as output file. Check that the
+# compiler prints an error message and does not overwrite the input file.
+
+load_lib gcc-defs.exp
+load_lib target-supports.exp
+
+# These tests don't run runtest_file_p consistently if it
+# doesn't return the same values, so disable parallelization
+# of this *.exp file.  The first parallel runtest to reach
+# this will run all the tests serially.
+if ![gcc_parallel_test_run_p output] {
+    return
+}
+
+# I'm not sure if this is needed here. It was in options.exp.
+gcc_parallel_test_enable 0
+
+proc check_gcc_overwrite_input {} {
+    set filename test-[pid]
+    set fd [open $filename.c w]
+    puts $fd "int main (void) \{ return 0; \}"
+    close $fd
+    remote_download host $filename.c
+    set test "input overwrite test"
+    set compiler cc1
+    set gcc_output [gcc_target_compile $filename.c $filename.c executable ""]
+    
+    # Is this right, or do I need to use something like remote_upload?
+    set fd [open $filename.c r]
+    set file_data [read $fd]
+    close $fd
+    remote_file build delete $filename.c
+
+    # check if the contents of the input file has changed
+    if {!($file_data eq "int main (void) \{ return 0; \}\n")} {
+	fail "$test (input overwritten)"
+	return
+    }
+    
+    # check if the error message was printed
+    if {![regexp -- "same as output" $gcc_output]} {
+	fail "$test (no error printed)"
+	return
+    }
+    pass $test
+}
+
+check_gcc_overwrite_input
+
+gcc_parallel_test_enable 1


More information about the Gcc-patches mailing list