]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/input.c
sh.c: Do not include algorithm.
[thirdparty/gcc.git] / gcc / input.c
index 1e01bd350b25b8d7e05022aba5fc63ec96498607..b6147f32e88906d0b91267c53beaf14d48e19823 100644 (file)
@@ -22,13 +22,98 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "intl.h"
 #include "input.h"
+#include "vec.h"
+
+/* This is a cache used by get_next_line to store the content of a
+   file to be searched for file lines.  */
+struct fcache
+{
+  /* These are information used to store a line boundary.  */
+  struct line_info
+  {
+    /* The line number.  It starts from 1.  */
+    size_t line_num;
+
+    /* The position (byte count) of the beginning of the line,
+       relative to the file data pointer.  This starts at zero.  */
+    size_t start_pos;
+
+    /* The position (byte count) of the last byte of the line.  This
+       normally points to the '\n' character, or to one byte after the
+       last byte of the file, if the file doesn't contain a '\n'
+       character.  */
+    size_t end_pos;
+
+    line_info (size_t l, size_t s, size_t e)
+      : line_num (l), start_pos (s), end_pos (e)
+    {}
+
+    line_info ()
+      :line_num (0), start_pos (0), end_pos (0)
+    {}
+  };
+
+  /* The number of time this file has been accessed.  This is used
+     to designate which file cache to evict from the cache
+     array.  */
+  unsigned use_count;
+
+  const char *file_path;
+
+  FILE *fp;
+
+  /* This points to the content of the file that we've read so
+     far.  */
+  char *data;
+
+  /*  The size of the DATA array above.*/
+  size_t size;
+
+  /* The number of bytes read from the underlying file so far.  This
+     must be less (or equal) than SIZE above.  */
+  size_t nb_read;
+
+  /* The index of the beginning of the current line.  */
+  size_t line_start_idx;
+
+  /* The number of the previous line read.  This starts at 1.  Zero
+     means we've read no line so far.  */
+  size_t line_num;
+
+  /* This is the total number of lines of the current file.  At the
+     moment, we try to get this information from the line map
+     subsystem.  Note that this is just a hint.  When using the C++
+     front-end, this hint is correct because the input file is then
+     completely tokenized before parsing starts; so the line map knows
+     the number of lines before compilation really starts.  For e.g,
+     the C front-end, it can happen that we start emitting diagnostics
+     before the line map has seen the end of the file.  */
+  size_t total_lines;
+
+  /* This is a record of the beginning and end of the lines we've seen
+     while reading the file.  This is useful to avoid walking the data
+     from the beginning when we are asked to read a line that is
+     before LINE_START_IDX above.  Note that the maximum size of this
+     record is fcache_line_record_size, so that the memory consumption
+     doesn't explode.  We thus scale total_lines down to
+     fcache_line_record_size.  */
+  vec<line_info, va_heap> line_record;
+
+  fcache ();
+  ~fcache ();
+};
 
 /* Current position in real source file.  */
 
-location_t input_location;
+location_t input_location = UNKNOWN_LOCATION;
 
 struct line_maps *line_table;
 
+static fcache *fcache_tab;
+static const size_t fcache_tab_size = 16;
+static const size_t fcache_buffer_size = 4 * 1024;
+static const size_t fcache_line_record_size = 100;
+
 /* Expand the source location LOC into a human readable location.  If
    LOC resolves to a builtin location, the file name of the readable
    location is set to the string "<built-in>". If EXPANSION_POINT_P is
@@ -87,56 +172,561 @@ expand_location_1 (source_location loc,
   return xloc;
 }
 
-/* Reads one line from file into a static buffer.  */
-static const char *
-read_line (FILE *file)
+/* Initialize the set of cache used for files accessed by caret
+   diagnostic.  */
+
+static void
+diagnostic_file_cache_init (void)
 {
-  static char *string;
-  static size_t string_len;
-  size_t pos = 0;
-  char *ptr;
+  if (fcache_tab == NULL)
+    fcache_tab = new fcache[fcache_tab_size];
+}
 
-  if (!string_len)
+/* Free the resources used by the set of cache used for files accessed
+   by caret diagnostic.  */
+
+void
+diagnostic_file_cache_fini (void)
+{
+  if (fcache_tab)
     {
-      string_len = 200;
-      string = XNEWVEC (char, string_len);
+      delete [] (fcache_tab);
+      fcache_tab = NULL;
     }
+}
 
-  while ((ptr = fgets (string + pos, string_len - pos, file)))
+/* Return the total lines number that have been read so far by the
+   line map (in the preprocessor) so far.  For languages like C++ that
+   entirely preprocess the input file before starting to parse, this
+   equals the actual number of lines of the file.  */
+
+static size_t
+total_lines_num (const char *file_path)
+{
+  size_t r = 0;
+  source_location l = 0;
+  if (linemap_get_file_highest_location (line_table, file_path, &l))
     {
-      size_t len = strlen (string + pos);
+      gcc_assert (l >= RESERVED_LOCATION_COUNT);
+      expanded_location xloc = expand_location (l);
+      r = xloc.line;
+    }
+  return r;
+}
+
+/* Lookup the cache used for the content of a given file accessed by
+   caret diagnostic.  Return the found cached file, or NULL if no
+   cached file was found.  */
+
+static fcache*
+lookup_file_in_cache_tab (const char *file_path)
+{
+  if (file_path == NULL)
+    return NULL;
 
-      if (string[pos + len - 1] == '\n')
+  diagnostic_file_cache_init ();
+
+  /* This will contain the found cached file.  */
+  fcache *r = NULL;
+  for (unsigned i = 0; i < fcache_tab_size; ++i)
+    {
+      fcache *c = &fcache_tab[i];
+      if (c->file_path && !strcmp (c->file_path, file_path))
        {
-         string[pos + len - 1] = 0;
-         return string;
+         ++c->use_count;
+         r = c;
        }
-      pos += len;
-      string = XRESIZEVEC (char, string, string_len * 2);
-      string_len *= 2;
     }
-      
-  return pos ? string : NULL;
+
+  if (r)
+    ++r->use_count;
+
+  return r;
+}
+
+/* Return the file cache that has been less used, recently, or the
+   first empty one.  If HIGHEST_USE_COUNT is non-null,
+   *HIGHEST_USE_COUNT is set to the highest use count of the entries
+   in the cache table.  */
+
+static fcache*
+evicted_cache_tab_entry (unsigned *highest_use_count)
+{
+  diagnostic_file_cache_init ();
+
+  fcache *to_evict = &fcache_tab[0];
+  unsigned huc = to_evict->use_count;
+  for (unsigned i = 1; i < fcache_tab_size; ++i)
+    {
+      fcache *c = &fcache_tab[i];
+      bool c_is_empty = (c->file_path == NULL);
+
+      if (c->use_count < to_evict->use_count
+         || (to_evict->file_path && c_is_empty))
+       /* We evict C because it's either an entry with a lower use
+          count or one that is empty.  */
+       to_evict = c;
+
+      if (huc < c->use_count)
+       huc = c->use_count;
+
+      if (c_is_empty)
+       /* We've reached the end of the cache; subsequent elements are
+          all empty.  */
+       break;
+    }
+
+  if (highest_use_count)
+    *highest_use_count = huc;
+
+  return to_evict;
+}
+
+/* Create the cache used for the content of a given file to be
+   accessed by caret diagnostic.  This cache is added to an array of
+   cache and can be retrieved by lookup_file_in_cache_tab.  This
+   function returns the created cache.  Note that only the last
+   fcache_tab_size files are cached.  */
+
+static fcache*
+add_file_to_cache_tab (const char *file_path)
+{
+
+  FILE *fp = fopen (file_path, "r");
+  if (fp == NULL)
+    return NULL;
+
+  unsigned highest_use_count = 0;
+  fcache *r = evicted_cache_tab_entry (&highest_use_count);
+  r->file_path = file_path;
+  if (r->fp)
+    fclose (r->fp);
+  r->fp = fp;
+  r->nb_read = 0;
+  r->line_start_idx = 0;
+  r->line_num = 0;
+  r->line_record.truncate (0);
+  /* Ensure that this cache entry doesn't get evicted next time
+     add_file_to_cache_tab is called.  */
+  r->use_count = ++highest_use_count;
+  r->total_lines = total_lines_num (file_path);
+
+  return r;
+}
+
+/* Lookup the cache used for the content of a given file accessed by
+   caret diagnostic.  If no cached file was found, create a new cache
+   for this file, add it to the array of cached file and return
+   it.  */
+
+static fcache*
+lookup_or_add_file_to_cache_tab (const char *file_path)
+{
+  fcache *r = lookup_file_in_cache_tab (file_path);
+  if (r == NULL)
+    r = add_file_to_cache_tab (file_path);
+  return r;
+}
+
+/* Default constructor for a cache of file used by caret
+   diagnostic.  */
+
+fcache::fcache ()
+: use_count (0), file_path (NULL), fp (NULL), data (0),
+  size (0), nb_read (0), line_start_idx (0), line_num (0),
+  total_lines (0)
+{
+  line_record.create (0);
+}
+
+/* Destructor for a cache of file used by caret diagnostic.  */
+
+fcache::~fcache ()
+{
+  if (fp)
+    {
+      fclose (fp);
+      fp = NULL;
+    }
+  if (data)
+    {
+      XDELETEVEC (data);
+      data = 0;
+    }
+  line_record.release ();
+}
+
+/* Returns TRUE iff the cache would need to be filled with data coming
+   from the file.  That is, either the cache is empty or full or the
+   current line is empty.  Note that if the cache is full, it would
+   need to be extended and filled again.  */
+
+static bool
+needs_read (fcache *c)
+{
+  return (c->nb_read == 0
+         || c->nb_read == c->size
+         || (c->line_start_idx >= c->nb_read - 1));
+}
+
+/*  Return TRUE iff the cache is full and thus needs to be
+    extended.  */
+
+static bool
+needs_grow (fcache *c)
+{
+  return c->nb_read == c->size;
+}
+
+/* Grow the cache if it needs to be extended.  */
+
+static void
+maybe_grow (fcache *c)
+{
+  if (!needs_grow (c))
+    return;
+
+  size_t size = c->size == 0 ? fcache_buffer_size : c->size * 2;
+  c->data = XRESIZEVEC (char, c->data, size + 1);
+  c->size = size;
+}
+
+/*  Read more data into the cache.  Extends the cache if need be.
+    Returns TRUE iff new data could be read.  */
+
+static bool
+read_data (fcache *c)
+{
+  if (feof (c->fp) || ferror (c->fp))
+    return false;
+
+  maybe_grow (c);
+
+  char * from = c->data + c->nb_read;
+  size_t to_read = c->size - c->nb_read;
+  size_t nb_read = fread (from, 1, to_read, c->fp);
+
+  if (ferror (c->fp))
+    return false;
+
+  c->nb_read += nb_read;
+  return !!nb_read;
+}
+
+/* Read new data iff the cache needs to be filled with more data
+   coming from the file FP.  Return TRUE iff the cache was filled with
+   mode data.  */
+
+static bool
+maybe_read_data (fcache *c)
+{
+  if (!needs_read (c))
+    return false;
+  return read_data (c);
+}
+
+/* Read a new line from file FP, using C as a cache for the data
+   coming from the file.  Upon successful completion, *LINE is set to
+   the beginning of the line found.  Space for that line has been
+   allocated in the cache thus *LINE has the same life time as C.
+   *LINE_LEN is set to the length of the line.  Note that the line
+   does not contain any terminal delimiter.  This function returns
+   true if some data was read or process from the cache, false
+   otherwise.  Note that subsequent calls to get_next_line return the
+   next lines of the file and might overwrite the content of
+   *LINE.  */
+
+static bool
+get_next_line (fcache *c, char **line, ssize_t *line_len)
+{
+  /* Fill the cache with data to process.  */
+  maybe_read_data (c);
+
+  size_t remaining_size = c->nb_read - c->line_start_idx;
+  if (remaining_size == 0)
+    /* There is no more data to process.  */
+    return false;
+
+  char *line_start = c->data + c->line_start_idx;
+
+  char *next_line_start = NULL;
+  size_t len = 0;
+  char *line_end = (char *) memchr (line_start, '\n', remaining_size);
+  if (line_end == NULL)
+    {
+      /* We haven't found the end-of-line delimiter in the cache.
+        Fill the cache with more data from the file and look for the
+        '\n'.  */
+      while (maybe_read_data (c))
+       {
+         line_start = c->data + c->line_start_idx;
+         remaining_size = c->nb_read - c->line_start_idx;
+         line_end = (char *) memchr (line_start, '\n', remaining_size);
+         if (line_end != NULL)
+           {
+             next_line_start = line_end + 1;
+             break;
+           }
+       }
+      if (line_end == NULL)
+       /* We've loadded all the file into the cache and still no
+          '\n'.  Let's say the line ends up at one byte passed the
+          end of the file.  This is to stay consistent with the case
+          of when the line ends up with a '\n' and line_end points to
+          that terminal '\n'.  That consistency is useful below in
+          the len calculation.  */
+       line_end = c->data + c->nb_read ;
+    }
+  else
+    next_line_start = line_end + 1;
+
+  if (ferror (c->fp))
+    return -1;
+
+  /* At this point, we've found the end of the of line.  It either
+     points to the '\n' or to one byte after the last byte of the
+     file.  */
+  gcc_assert (line_end != NULL);
+
+  len = line_end - line_start;
+
+  if (c->line_start_idx < c->nb_read)
+    *line = line_start;
+
+  ++c->line_num;
+
+  /* Before we update our line record, make sure the hint about the
+     total number of lines of the file is correct.  If it's not, then
+     we give up recording line boundaries from now on.  */
+  bool update_line_record = true;
+  if (c->line_num > c->total_lines)
+    update_line_record = false;
+
+    /* Now update our line record so that re-reading lines from the
+     before c->line_start_idx is faster.  */
+  if (update_line_record
+      && c->line_record.length () < fcache_line_record_size)
+    {
+      /* If the file lines fits in the line record, we just record all
+        its lines ...*/
+      if (c->total_lines <= fcache_line_record_size
+         && c->line_num > c->line_record.length ())
+       c->line_record.safe_push (fcache::line_info (c->line_num,
+                                                c->line_start_idx,
+                                                line_end - c->data));
+      else if (c->total_lines > fcache_line_record_size)
+       {
+         /* ... otherwise, we just scale total_lines down to
+            (fcache_line_record_size lines.  */
+         size_t n = (c->line_num * fcache_line_record_size) / c->total_lines;
+         if (c->line_record.length () == 0
+             || n >= c->line_record.length ())
+           c->line_record.safe_push (fcache::line_info (c->line_num,
+                                                    c->line_start_idx,
+                                                    line_end - c->data));
+       }
+    }
+
+  /* Update c->line_start_idx so that it points to the next line to be
+     read.  */
+  if (next_line_start)
+    c->line_start_idx = next_line_start - c->data;
+  else
+    /* We didn't find any terminal '\n'.  Let's consider that the end
+       of line is the end of the data in the cache.  The next
+       invocation of get_next_line will either read more data from the
+       underlying file or return false early because we've reached the
+       end of the file.  */
+    c->line_start_idx = c->nb_read;
+
+  *line_len = len;
+
+  return true;
+}
+
+/* Reads the next line from FILE into *LINE.  If *LINE is too small
+   (or NULL) it is allocated (or extended) to have enough space to
+   containe the line.  *LINE_LENGTH must contain the size of the
+   initial*LINE buffer.  It's then updated by this function to the
+   actual length of the returned line.  Note that the returned line
+   can contain several zero bytes.  Also note that the returned string
+   is allocated in static storage that is going to be re-used by
+   subsequent invocations of read_line.  */
+
+static bool
+read_next_line (fcache *cache, char ** line, ssize_t *line_len)
+{
+  char *l = NULL;
+  ssize_t len = 0;
+
+  if (!get_next_line (cache, &l, &len))
+    return false;
+
+  if (*line == NULL)
+    *line = XNEWVEC (char, len);
+  else
+    if (*line_len < len)
+       *line = XRESIZEVEC (char, *line, len);
+
+  memcpy (*line, l, len);
+  *line_len = len;
+
+  return true;
+}
+
+/* Consume the next bytes coming from the cache (or from its
+   underlying file if there are remaining unread bytes in the file)
+   until we reach the next end-of-line (or end-of-file).  There is no
+   copying from the cache involved.  Return TRUE upon successful
+   completion.  */
+
+static bool
+goto_next_line (fcache *cache)
+{
+  char *l;
+  ssize_t len;
+
+  return get_next_line (cache, &l, &len);
+}
+
+/* Read an arbitrary line number LINE_NUM from the file cached in C.
+   The line is copied into *LINE.  *LINE_LEN must have been set to the
+   length of *LINE.  If *LINE is too small (or NULL) it's extended (or
+   allocated) and *LINE_LEN is adjusted accordingly.  *LINE ends up
+   with a terminal zero byte and can contain additional zero bytes.
+   This function returns bool if a line was read.  */
+
+static bool
+read_line_num (fcache *c, size_t line_num,
+              char ** line, ssize_t *line_len)
+{
+  gcc_assert (line_num > 0);
+
+  if (line_num <= c->line_num)
+    {
+      /* We've been asked to read lines that are before c->line_num.
+        So lets use our line record (if it's not empty) to try to
+        avoid re-reading the file from the beginning again.  */
+
+      if (c->line_record.is_empty ())
+       {
+         c->line_start_idx = 0;
+         c->line_num = 0;
+       }
+      else
+       {
+         fcache::line_info *i = NULL;
+         if (c->total_lines <= fcache_line_record_size)
+           {
+             /* In languages where the input file is not totally
+                preprocessed up front, the c->total_lines hint
+                can be smaller than the number of lines of the
+                file.  In that case, only the first
+                c->total_lines have been recorded.
+
+                Otherwise, the first c->total_lines we've read have
+                their start/end recorded here.  */
+             i = (line_num <= c->total_lines)
+               ? &c->line_record[line_num - 1]
+               : &c->line_record[c->total_lines - 1];
+             gcc_assert (i->line_num <= line_num);
+           }
+         else
+           {
+             /*  So the file had more lines than our line record
+                 size.  Thus the number of lines we've recorded has
+                 been scaled down to fcache_line_reacord_size.  Let's
+                 pick the start/end of the recorded line that is
+                 closest to line_num.  */
+             size_t n = (line_num <= c->total_lines)
+               ? line_num * fcache_line_record_size / c->total_lines
+               : c ->line_record.length () - 1;
+             if (n < c->line_record.length ())
+               {
+                 i = &c->line_record[n];
+                 gcc_assert (i->line_num <= line_num);
+               }
+           }
+
+         if (i && i->line_num == line_num)
+           {
+             /* We have the start/end of the line.  Let's just copy
+                it again and we are done.  */
+             ssize_t len = i->end_pos - i->start_pos + 1;
+             if (*line_len < len)
+               *line = XRESIZEVEC (char, *line, len);
+             memmove (*line, c->data + i->start_pos, len);
+             (*line)[len - 1] = '\0';
+             *line_len = --len;
+             return true;
+           }
+
+         if (i)
+           {
+             c->line_start_idx = i->start_pos;
+             c->line_num = i->line_num - 1;
+           }
+         else
+           {
+             c->line_start_idx = 0;
+             c->line_num = 0;
+           }
+       }
+    }
+
+  /*  Let's walk from line c->line_num up to line_num - 1, without
+      copying any line.  */
+  while (c->line_num < line_num - 1)
+    if (!goto_next_line (c))
+      return false;
+
+  /* The line we want is the next one.  Let's read and copy it back to
+     the caller.  */
+  return read_next_line (c, line, line_len);
 }
 
 /* Return the physical source line that corresponds to xloc in a
    buffer that is statically allocated.  The newline is replaced by
-   the null character.  */
+   the null character.  Note that the line can contain several null
+   characters, so LINE_LEN, if non-null, points to the actual length
+   of the line.  */
 
 const char *
-location_get_source_line (expanded_location xloc)
+location_get_source_line (expanded_location xloc,
+                         int *line_len)
 {
-  const char *buffer;
-  int lines = 1;
-  FILE *stream = xloc.file ? fopen (xloc.file, "r") : NULL;
-  if (!stream)
+  static char *buffer;
+  static ssize_t len;
+
+  if (xloc.line == 0)
+    return NULL;
+
+  fcache *c = lookup_or_add_file_to_cache_tab (xloc.file);
+  if (c == NULL)
     return NULL;
 
-  while ((buffer = read_line (stream)) && lines < xloc.line)
-    lines++;
+  bool read = read_line_num (c, xloc.line, &buffer, &len);
 
-  fclose (stream);
-  return buffer;
+  if (read && line_len)
+    *line_len = len;
+
+  return read ? buffer : NULL;
+}
+
+/* Test if the location originates from the spelling location of a
+   builtin-tokens.  That is, return TRUE if LOC is a (possibly
+   virtual) location of a built-in token that appears in the expansion
+   list of a macro.  Please note that this function also works on
+   tokens that result from built-in tokens.  For instance, the
+   function would return true if passed a token "4" that is the result
+   of the expansion of the built-in __LINE__ macro.  */
+bool
+is_location_from_builtin_token (source_location loc)
+{
+  const line_map *map = NULL;
+  loc = linemap_resolve_location (line_table, loc,
+                                 LRK_SPELLING_LOCATION, &map);
+  return loc == BUILTINS_LOCATION;
 }
 
 /* Expand the source location LOC into a human readable location.  If
@@ -159,19 +749,20 @@ expand_location (source_location loc)
 expanded_location
 expand_location_to_spelling_point (source_location loc)
 {
-  return expand_location_1 (loc, /*expansion_piont_p=*/false);
+  return expand_location_1 (loc, /*expansion_point_p=*/false);
 }
 
-/* If LOCATION is in a system header and if it's a virtual location for
-   a token coming from the expansion of a macro M, unwind it to the
-   location of the expansion point of M.  Otherwise, just return
+/* If LOCATION is in a system header and if it is a virtual location for
+   a token coming from the expansion of a macro, unwind it to the
+   location of the expansion point of the macro.  Otherwise, just return
    LOCATION.
 
    This is used for instance when we want to emit diagnostics about a
-   token that is located in a macro that is itself defined in a system
-   header -- e.g for the NULL macro.  In that case, if LOCATION is
-   passed to diagnostics emitting functions like warning_at as is, no
-   diagnostic won't be emitted.  */
+   token that may be located in a macro that is itself defined in a
+   system header, for example, for the NULL macro.  In such a case, if
+   LOCATION were passed directly to diagnostic functions such as
+   warning_at, the diagnostic would be suppressed (unless
+   -Wsystem-headers).  */
 
 source_location
 expansion_point_location_if_in_system_header (source_location location)