@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see
#include "memmodel.h"
#include "tm_p.h" /* For C_COMMON_OVERRIDE_OPTIONS. */
#include "diagnostic.h"
+#include "diagnostics/file-cache.h"
#include "c-pragma.h"
#include "flags.h"
#include "toplev.h"
@@ -535,6 +536,7 @@ c_common_handle_option (size_t scode, const char *arg, HOST_WIDE_INT value,
case OPT_fpreprocessed:
cpp_opts->preprocessed = value;
+ global_dc->get_file_cache ().set_preprocessed (value);
break;
case OPT_fdebug_cpp:
@@ -45,6 +45,126 @@ file_cache::initialize_input_context (diagnostic_input_charset_callback ccb,
m_input_context.should_skip_bom = should_skip_bom;
}
+/* Contains info about a single linemarker seen in a preprocessed file.
+ Is used by the linemarker_cache. */
+struct linemarker_cache_entry
+{
+ /* The source file name. */
+ char *source_name;
+
+ /* The line in the file containing the linemarker, counting from 1. */
+ int line;
+
+ /* The line the linemarker refers to. */
+ int source_line;
+
+ /* The entry's index in the sorted record of all entries. */
+ unsigned int record_idx;
+
+ linemarker_cache_entry (char_span sn, int l, int sl, unsigned int i)
+ : source_name (sn.xstrdup ()), line (l), source_line (sl), record_idx (i)
+ {}
+ ~linemarker_cache_entry ()
+ {
+ free (source_name);
+ }
+};
+
+/* A cache that is used by get_source_line_preprocessed to speed up
+ finding the correct line to read based on linemarker data.
+ The file_cache owns a single cache for the preprocessed input file.
+ Helps to avoid repeatedly scanning the whole file for linemarkers via
+ read_line_num. */
+struct linemarker_cache
+{
+ /* String portion of the hash is owned by linemarker_cache_entry,
+ so we use nofree for the linemarker_hash_map. */
+ typedef hash_map<nofree_string_hash, vec<linemarker_cache_entry *, va_heap>>
+ linemarker_hash_map;
+
+ /* For the memoization map, source names are copied so we use
+ free_string_hash. */
+ typedef pair_hash<free_string_hash, int_hash<int, -1, -2>>
+ source_file_line_hash;
+ typedef hash_map<source_file_line_hash, int> result_line_map;
+
+ /* The maximum line number of all linemarkers we've seen so far. */
+ int m_max_linemarker_line;
+
+ /* This hash map is the main entry point to the cache.
+ It maps source paths to a vector of linemarker_cache_entry pointers.
+ It allows efficient lookup of the closest linemarker.
+ For each source file, linemarkers are unique with both physical line
+ and referenced source line numbers in ascending order.
+ The same source line number can be referenced by multiple linemarkers.
+ */
+ linemarker_hash_map *m_linemarkers_by_source_name;
+
+ /* A sorted (by line number ascending) record of all unique linemarkers
+ we encounter. This is used for efficiently determining if a candidate
+ from the cache is the best candidate. */
+ vec<linemarker_cache_entry *, va_heap> m_linemarkers;
+
+ /* This map is used for plain memoization of linemarker to
+ physical source line mappings. This is an additional optimization used
+ to avoid repeated calls to get_source_line_preprocessed
+ for the same source_file and line.
+ Expects a copy of the source_file string and assumes ownership.
+ */
+ result_line_map *m_result_lines_by_source;
+
+ /* Default ctor. */
+ linemarker_cache ()
+ : m_max_linemarker_line (0),
+ m_linemarkers_by_source_name (new linemarker_hash_map),
+ m_result_lines_by_source (new result_line_map)
+ {
+ m_linemarkers.create (0);
+ }
+
+ /* Used in map traversal to clean up linemarker vectors. */
+ static bool
+ release_linemarker_cache_vector (ATTRIBUTE_UNUSED const char *const &,
+ vec<linemarker_cache_entry *> *vec,
+ ATTRIBUTE_UNUSED void *)
+ {
+ // linemarker_cache_entry is owned by m_linemarkers so
+ // we only need to release the vector.
+ vec->release ();
+ return true;
+ }
+
+ /* Destructor. */
+ ~linemarker_cache ()
+ {
+ m_linemarkers_by_source_name
+ ->traverse<void *, &release_linemarker_cache_vector> (NULL);
+ delete m_linemarkers_by_source_name;
+ delete m_result_lines_by_source;
+
+ for (unsigned i = 0; i < m_linemarkers.length (); ++i)
+ delete m_linemarkers[i];
+ m_linemarkers.release ();
+ }
+
+ int get_max_linemarker_line () const
+ {
+ return m_max_linemarker_line;
+ }
+ void set_max_linemarker_line (int l)
+ {
+ m_max_linemarker_line = l;
+ }
+
+ int get_result_line (const char *source_name, int source_line) const;
+ void add_result_line (const char *source_name, int source_line, int line);
+ bool get_closest_linemarker (const char *source_name, int source_line,
+ int *out_linemarker_line,
+ int *out_linemarker_source_line) const;
+ void add_linemarker_unique (char_span source_name, int line,
+ int source_line);
+};
+
/* This is a cache used by get_next_line to store the content of a
file to be searched for file lines. */
class file_cache_slot
@@ -191,7 +311,6 @@ public:
m_data += offset;
m_size -= offset;
}
-
};
size_t file_cache_slot::line_record_size = 0;
@@ -457,7 +576,9 @@ file_cache_slot::set_content (const char *buf, size_t sz)
/* file_cache's ctor. */
file_cache::file_cache ()
-: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots])
+: m_num_file_slots (16), m_file_slots (new file_cache_slot[m_num_file_slots]),
+ m_linemarker_cache (new linemarker_cache),
+ m_main_input_file_path (nullptr), m_is_preprocessed (false)
{
initialize_input_context (nullptr, false);
}
@@ -467,6 +588,7 @@ file_cache::file_cache ()
file_cache::~file_cache ()
{
delete[] m_file_slots;
+ delete m_linemarker_cache;
}
void
@@ -960,6 +1082,310 @@ file_cache::get_source_file_content (const char *file_path)
return c->get_full_file_content ();
}
+/* Find the closest cached linemarker for a given SOURCE_LINE and SOURCE_NAME.
+
+ Closest is defined by the smallest distance between the cached source line
+ and the given source line (i.e. the nearest left neighbor or exact match).
+ If we can safely determine that the cached linemarker is the best candidate,
+ return true, otherwise false. If we have a valid match, store the linemarker
+ physical line number in OUT_LINEMARKER_LINE and the referenced source line
+ in OUT_LINEMARKER_SOURCE_LINE.
+
+ If we are certain that the candidate not the best because we know
+ about more linemarkers after, set OUT_LINEMARKER_LINE to the line of the
+ last known linemarker and OUT_LINEMARKER_SOURCE_LINE to 0.
+ This way, the caller does not have to rescan lines. */
+
+bool
+linemarker_cache::get_closest_linemarker (
+ const char *source_name, int source_line, int *out_linemarker_line,
+ int *out_linemarker_source_line) const
+{
+ *out_linemarker_source_line = 0;
+ *out_linemarker_line = 0;
+
+ // get sorted list of linemarkers for the given source file from cache
+ vec<linemarker_cache_entry *, va_heap> *linemarkers
+ = m_linemarkers_by_source_name->get (source_name);
+ if (!linemarkers)
+ return false;
+ if (linemarkers->length () == 0)
+ return false;
+
+ // perform a binary search to find the nearest left neighbor or exact match
+ int left = 0, right = linemarkers->length () - 1;
+ while (left <= right)
+ {
+ int mid = left + (right - left) / 2;
+ if ((*linemarkers)[mid]->source_line > source_line)
+ {
+ right = mid - 1;
+ }
+ else
+ {
+ left = mid + 1;
+ }
+ }
+ if (right < 0)
+ return false;
+
+ *out_linemarker_source_line = (*linemarkers)[right]->source_line;
+ *out_linemarker_line = (*linemarkers)[right]->line;
+
+ // If there is a linemarker for any file above the candidate, we can
+ // check if the target source line falls in the range between.
+ // If so, the candidate is the best.
+ if (m_linemarkers.length () > (*linemarkers)[right]->record_idx + 1)
+ {
+ linemarker_cache_entry *next_lm
+ = m_linemarkers[(*linemarkers)[right]->record_idx + 1];
+ size_t lines_to_next_lm = next_lm->line - (*linemarkers)[right]->line - 1;
+ size_t offset = source_line - (*linemarkers)[right]->source_line;
+ if (offset < lines_to_next_lm)
+ return true;
+ else
+ {
+ // we know this candidate cannot be the right one
+ // because the space between the candidate and the next linemarker
+ // is not large enough for the offset.
+ *out_linemarker_source_line = 0;
+ *out_linemarker_line = 0;
+ }
+ }
+
+ return false;
+}
+
+/* Add a linemarker to the linemarker record in the file_cache_slot **/
+void
+linemarker_cache::add_linemarker_unique (char_span source_name,
+ int line,
+ int source_line)
+{
+ if (line <= get_max_linemarker_line ())
+ return; // seen already
+ set_max_linemarker_line (line);
+
+ // first, add the linemarker to the sorted record along with its index
+ linemarker_cache_entry *entry
+ = new linemarker_cache_entry{source_name, line, source_line,
+ m_linemarkers.length ()};
+
+ m_linemarkers.safe_push (entry);
+
+ // then, link the entry to the source_file based lookup table,
+ // creating the table entry if it doesn't exist yet
+ vec<linemarker_cache_entry *, va_heap> *linemarkers
+ = m_linemarkers_by_source_name->get (entry->source_name);
+ if (!linemarkers)
+ {
+ vec<linemarker_cache_entry *, va_heap> linemarkers_vec
+ = vec<linemarker_cache_entry *, va_heap> ();
+ linemarkers_vec.create (1);
+ linemarkers_vec.safe_push (entry);
+ m_linemarkers_by_source_name->put (entry->source_name, linemarkers_vec);
+ }
+ else
+ {
+ linemarkers->safe_push (entry);
+ }
+}
+
+/* Memoize a result line for get_source_line_preprocessed calls. */
+void
+linemarker_cache::add_result_line (const char *source_name,
+ int source_line,
+ int result_line)
+{
+ m_result_lines_by_source->put (string_int_pair (xstrdup (source_name),
+ source_line),
+ result_line);
+}
+
+/* Return a memoized line number for the given SOURCE_NAME and SOURCE_LINE.
+ If no result is cached, return 0.
+ The cached line number is the physical line which is referenced by
+ the cached linemarker for these inputs. */
+int
+linemarker_cache::get_result_line (const char *source_name,
+ int source_line) const
+{
+ int *cached_line = m_result_lines_by_source->get (
+ string_int_pair (source_name, source_line));
+ return cached_line ? *cached_line : 0;
+}
+
+/* Return true if LINE is a linemarker for the given SOURCE_NAME.
+ Linemarkers are of the form:
+ # LINENUM "SOURCE_NAME" [FLAGS]
+ Store the referenced line number in OUT_LINE_NUM.
+ If it is any linemarker, even for a different file, write the file name
+ out to OUT_LINEMARKER_SOURCE_NAME. */
+static bool
+is_linemarker_for_file (char_span line, const char *source_name,
+ int *out_line_num,
+ char_span *out_linemarker_source_name)
+{
+ // skip leading whitespace
+ size_t i = 0;
+ while (line.length () > i && ISBLANK (line[i]))
+ ++i;
+
+ // a linemarker starts with '# ' followed by a line number
+ if (line.length () <= i + 2 || line[i] != '#' || line[i + 1] != ' '
+ || !ISDIGIT (line[i + 2]))
+ return false;
+
+ *out_line_num = atoi (line.get_buffer () + i + 2);
+
+ // find the leading quote of the source name
+ for (i += 3; i < line.length () && line[i] != '"'; ++i)
+ ;
+ if (i >= line.length ())
+ return false;
+
+ // find the trailing quote of the source name
+ size_t source_name_start = i + 1;
+ size_t j = source_name_start;
+ for (; j < line.length () && line[j] != '"'; ++j)
+ ;
+ if (j >= line.length ())
+ return false;
+
+ size_t source_name_length = j - source_name_start;
+ *out_linemarker_source_name = line.subspan (source_name_start,
+ source_name_length);
+
+ // the linemarker is only for SOURCE_NAME if the names match exactly
+ if (strlen (source_name) != source_name_length)
+ return false;
+ return memcmp (out_linemarker_source_name->get_buffer (), source_name,
+ source_name_length) == 0;
+}
+
+/* Return the physical source line that corresponds to SOURCE_NAME/LINE.
+ Read the line from the preprocessed file at FILE_PATH.
+ The line is not nul-terminated. The returned pointer is only
+ valid until the next call of get_source_line.
+ Note that the line can contain several null characters,
+ so the returned value's length has the actual length of the line.
+ If the function fails, a NULL char_span is returned.
+
+ If we for example want to get line 7 of file.cpp from the preprocessed
+ output, the corresponding line marker might look like this:
+ # 5 "file.cpp" // line 2050 of the preprocessed file
+ int func ()
+ {
+ int a = 4;
+ int b = 5;
+
+ This means that line 2051 of the preprocessed file is line 5 of the
+ original file.cpp. So to get line 7 of file.cpp we have to read line
+ 2053 of the preprocessed file. */
+
+char_span
+file_cache::get_source_line_preprocessed (const char *source_name,
+ const char *file_path, int line)
+{
+ char *buffer = NULL;
+ ssize_t len;
+
+ if (line == 0)
+ return char_span (NULL, 0);
+
+ file_cache_slot *c = lookup_or_add_file (file_path);
+ if (c == NULL)
+ return char_span (NULL, 0);
+
+ // return memoized result if available
+ int cached_line = m_linemarker_cache->get_result_line (source_name, line);
+ if (cached_line != 0)
+ {
+ bool read = c->read_line_num (cached_line, &buffer, &len);
+ if (read)
+ return char_span (buffer, len);
+ }
+
+ // attempt to find a linemarker in the cache
+ int linemarker_line = 0; // the physical line num of the candidate
+ int linemarker_loc = 0; // the referenced source line num of the candidate
+ bool is_best
+ = m_linemarker_cache->get_closest_linemarker (source_name, line,
+ &linemarker_line,
+ &linemarker_loc);
+
+ // we continue reading from (highest) cached location if the potential
+ // candidate is not the best already
+ int current_line = m_linemarker_cache->get_max_linemarker_line () + 1;
+ char_span linemarker_source_name = char_span (NULL, 0);
+ int linemarker_source_line = 0;
+ bool is_match = false;
+ while (!is_best && c->read_line_num (current_line, &buffer, &len))
+ {
+ linemarker_source_name = char_span (NULL, 0);
+ linemarker_source_line = 0;
+ is_match = is_linemarker_for_file (char_span (buffer, len), source_name,
+ &linemarker_source_line,
+ &linemarker_source_name);
+
+ if (linemarker_source_name)
+ {
+ m_linemarker_cache->add_linemarker_unique (linemarker_source_name,
+ current_line,
+ linemarker_source_line);
+ if (linemarker_line > 0 && linemarker_loc > 0)
+ {
+ // if our last candidate fits the range to the next linemarker,
+ // we can stop searching
+ int lines_to_next_lm = current_line - linemarker_line - 1;
+ int offset = line - linemarker_loc;
+ if (offset < lines_to_next_lm)
+ break;
+ else
+ {
+ // reset the candidate if we have passed the range
+ linemarker_line = 0;
+ linemarker_loc = 0;
+ }
+ }
+ if (is_match)
+ {
+ // new candidate
+ linemarker_line = current_line;
+ linemarker_loc = linemarker_source_line;
+ }
+ }
+ ++current_line;
+ }
+
+ if (linemarker_line == 0 || linemarker_loc == 0)
+ return char_span (NULL, 0);
+
+ // we have a matching linemarker, read the indicated line
+ size_t physical_line = linemarker_line + (line - linemarker_loc) + 1;
+ bool read = c->read_line_num (physical_line, &buffer, &len);
+ if (!read)
+ return char_span (NULL, 0);
+
+ // memoize result line
+ m_linemarker_cache->add_result_line (source_name, line, physical_line);
+ return char_span (buffer, len);
+}
+
+/* Get the source line for FILE_PATH/LINE. When compiling preprocessed input,
+ read the line from the preprocessed input file instead, so that diagnostics
+ remain correct when the original source file is unavailable (e.g. within a
+ compile cluster) or has changed since preprocessing. */
+
+char_span
+file_cache::get_source_line_maybe_preprocessed (const char *file_path, int line)
+{
+ if (m_is_preprocessed && m_main_input_file_path != nullptr)
+ return get_source_line_preprocessed (file_path, m_main_input_file_path,
+ line);
+ return get_source_line (file_path, line);
+}
+
#if CHECKING_P
namespace selftest {
@@ -1079,6 +1505,268 @@ test_reading_source_buffer ()
ASSERT_TRUE (source_line.get_buffer () == NULL);
}
+/* Verify reading of preprocessed input files
+ (e.g. for caret-based diagnostics). */
+
+static void
+test_parsing_linemarker ()
+{
+ auto assert_is_linemarker_for_file = [] (const ::selftest::location &loc,
+ const char *buf,
+ const char *expected_file_name,
+ int expected_line_num)
+ {
+ int line_num;
+ char_span file_name (nullptr, 0);
+ bool result = is_linemarker_for_file (char_span (buf, strlen (buf)),
+ expected_file_name,
+ &line_num, &file_name);
+ ASSERT_TRUE_AT (loc, result);
+ ASSERT_EQ_AT (loc, line_num, expected_line_num);
+ ASSERT_TRUE_AT (loc, file_name);
+ char *actual_file_name = file_name.xstrdup ();
+ ASSERT_STREQ_AT (loc, expected_file_name, actual_file_name);
+ free (actual_file_name);
+ };
+#define ASSERT_IS_LINEMARKER_FOR_FILE(BUF, EXPECTED_FILENAME, EXPECTED_LINE_NUM) \
+ SELFTEST_BEGIN_STMT \
+ assert_is_linemarker_for_file (SELFTEST_LOCATION, (BUF), \
+ (EXPECTED_FILENAME), (EXPECTED_LINE_NUM)); \
+ SELFTEST_END_STMT
+
+ /* EXPECTED_PARSED_NAME asserts the source name parsed out of the linemarker
+ for cases where it does not match the file we search for; it is nullptr
+ when the input is not a linemarker at all. */
+ auto assert_is_not_linemarker_for_file
+ = [] (const ::selftest::location &loc, const char *buf,
+ const char *file_to_match, const char *expected_parsed_name)
+ {
+ int line_num;
+ char_span file_name (nullptr, 0);
+ bool result = is_linemarker_for_file (char_span (buf, strlen (buf)),
+ file_to_match, &line_num, &file_name);
+ ASSERT_FALSE_AT (loc, result);
+ if (expected_parsed_name)
+ {
+ ASSERT_TRUE_AT (loc, file_name);
+ char *actual_file_name = file_name.xstrdup ();
+ ASSERT_STREQ_AT (loc, expected_parsed_name, actual_file_name);
+ free (actual_file_name);
+ }
+ else
+ ASSERT_FALSE_AT (loc, file_name);
+ };
+#define ASSERT_IS_NOT_LINEMARKER_FOR_FILE(BUF, FILENAME, EXPECTED_PARSED_NAME) \
+ SELFTEST_BEGIN_STMT \
+ assert_is_not_linemarker_for_file (SELFTEST_LOCATION, (BUF), (FILENAME), \
+ (EXPECTED_PARSED_NAME)); \
+ SELFTEST_END_STMT
+
+ ASSERT_IS_LINEMARKER_FOR_FILE ("# 112 \"test.cpp\"\n", "test.cpp", 112);
+
+ /* Linemarker but for a different file. */
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.hpp\"\n", "test.h",
+ "test.hpp");
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.h\"\n", "test.hpp",
+ "test.h");
+
+ /* Not a linemarker. */
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("int main () \n", "test.cpp", nullptr);
+
+ /* Linemarker with flags. */
+ ASSERT_IS_LINEMARKER_FOR_FILE ("# 23 \"test.h\" 1\n", "test.h", 23);
+
+ /* Malformed linemarkers. */
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.h \n", "test.h", nullptr);
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 2 test.h\n", "test.h", nullptr);
+ ASSERT_IS_NOT_LINEMARKER_FOR_FILE ("# 23 \"test.h\"\n", "test.h", nullptr);
+
+ /* Linemarker not starting at beginning of line, spaces and tabs. */
+ ASSERT_IS_LINEMARKER_FOR_FILE (" # 23 \"test.hpp\"\n", "test.hpp", 23);
+
+#undef ASSERT_IS_LINEMARKER_FOR_FILE
+#undef ASSERT_IS_NOT_LINEMARKER_FOR_FILE
+}
+
+static void
+test_linemarker_cache ()
+{
+ linemarker_cache cache;
+
+ // Represents a file like
+ // # 1 "test.cpp" 1
+ // # 1 "test.cpp"
+ // content
+ // # 1 "test.h" 1
+ // # 2 "test.h"
+ // ...
+ // # 35 "test.h"
+ cache.add_linemarker_unique (char_span ("test.cpp", 8), 1, 1);
+ cache.add_linemarker_unique (char_span ("test.cpp", 8), 2, 1);
+
+ int line_num = 0;
+ int source_line_num = 0;
+
+ /* Matching linemarkers. */
+ bool best
+ = cache.get_closest_linemarker ("test.cpp", 4, &line_num,
+ &source_line_num);
+ // not surely the best but a match
+ // because there are no further linemarkers known yet
+ ASSERT_FALSE (best);
+ ASSERT_EQ (2, line_num);
+ ASSERT_EQ (1, source_line_num);
+
+ // add more linemarkers
+ cache.add_linemarker_unique (char_span ("test.h", 6), 5, 2);
+ cache.add_linemarker_unique (char_span ("test.h", 6), 15, 35);
+
+ // repeat previous test but with more cache entries
+ best
+ = cache.get_closest_linemarker ("test.cpp", 1, &line_num,
+ &source_line_num);
+ // best because there is room for one line before the next linemarker
+ ASSERT_TRUE (best);
+ // left-nearest neighbor, i.e. the last linemarker before the source line
+ ASSERT_EQ (2, line_num);
+ ASSERT_EQ (1, source_line_num);
+
+ best
+ = cache.get_closest_linemarker ("test.cpp", 4, &line_num,
+ &source_line_num);
+ // not the best because there are linemarkers after and the
+ // space between is not enough for the source line offset
+ ASSERT_FALSE (best);
+ ASSERT_EQ (0, line_num);
+ ASSERT_EQ (0, source_line_num);
+
+ best = cache.get_closest_linemarker ("test.h", 10, &line_num,
+ &source_line_num);
+ // best because there are 9 lines between the linemarker and the next
+ // We can target source line offsets between 0 and 8
+ // and the offset is 8
+ ASSERT_TRUE (best);
+ ASSERT_EQ (5, line_num);
+ ASSERT_EQ (2, source_line_num);
+ best = cache.get_closest_linemarker ("test.h", 11, &line_num,
+ &source_line_num);
+ // one source line further already falls on the next linemarker
+ ASSERT_FALSE (best);
+ ASSERT_EQ (0, line_num);
+ ASSERT_EQ (0, source_line_num);
+
+ best = cache.get_closest_linemarker ("test.h", 40, &line_num,
+ &source_line_num);
+ // no linemarker in the cache after this, so we would need to scan further
+ ASSERT_FALSE (best);
+ ASSERT_EQ (15, line_num);
+ ASSERT_EQ (35, source_line_num);
+
+ /* No match. */
+ best = cache.get_closest_linemarker ("test.h", 1, &line_num,
+ &source_line_num);
+ // no left-nearest neighbor
+ ASSERT_FALSE (best);
+ ASSERT_EQ (0, line_num);
+ ASSERT_EQ (0, source_line_num);
+
+ best = cache.get_closest_linemarker ("no_match.cpp", 3, &line_num,
+ &source_line_num);
+ ASSERT_FALSE (best);
+ ASSERT_EQ (0, line_num);
+ ASSERT_EQ (0, source_line_num);
+}
+
+static void
+test_reading_source_line_preprocessed ()
+{
+ /* Create a tempfile and write some valid preprocessor output. */
+ temp_source_file tmp (SELFTEST_LOCATION, ".cpp.ii",
+ "# 1 \"test.cpp\"\n"
+ "# 1 \"test.cpp\"\n"
+ "# 1 \"test.h\" 1\n"
+ "void test_func () \n"
+ "# 35 \"test.h\"\n"
+ " do_something_else ();\n"
+ "}\n"
+ "# 35 \"test.hpp\"\n"
+ " do_nothing ();\n"
+ "}\n"
+ "# 2 \"test.cpp\" 2\n"
+ "\n"
+ "int main () \n"
+ " do_something ();\n"
+ "\n"
+ " int i = 5;\n"
+ " unsigned j = 3;\n"
+ " if (i > j)\n"
+ " return 0;\n"
+ "\n"
+ "# 38 \"test.h\"\n"
+ " random_func ();\n"
+ " test_func ();\n"
+ "}\n");
+ file_cache fc;
+
+ // Perform all tests twice to verify memoized calls behave the same
+ for (int i = 0; i < 2; i++)
+ {
+ /* Read back a specific line from the tempfile. */
+ char_span source_line
+ = fc.get_source_line_preprocessed ("test.cpp", tmp.get_filename (), 4);
+ ASSERT_TRUE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () != NULL);
+ ASSERT_EQ (20, source_line.length ());
+ ASSERT_TRUE (!strncmp (" do_something ();", source_line.get_buffer (),
+ source_line.length ()));
+
+ source_line
+ = fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 35);
+ ASSERT_TRUE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () != NULL);
+ ASSERT_EQ (25, source_line.length ());
+ ASSERT_TRUE (!strncmp (" do_something_else ();",
+ source_line.get_buffer (), source_line.length ()));
+
+ source_line
+ = fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 39);
+ ASSERT_TRUE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () != NULL);
+ ASSERT_EQ (17, source_line.length ());
+ ASSERT_TRUE (!strncmp (" test_func ();", source_line.get_buffer (),
+ source_line.length ()));
+
+ source_line
+ = fc.get_source_line_preprocessed ("test.hpp", tmp.get_filename (), 35);
+ ASSERT_TRUE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () != NULL);
+ ASSERT_EQ (18, source_line.length ());
+ ASSERT_TRUE (!strncmp (" do_nothing ();", source_line.get_buffer (),
+ source_line.length ()));
+
+ // file not present in preprocessor output
+ source_line
+ = fc.get_source_line_preprocessed ("other.h", tmp.get_filename (), 4);
+ ASSERT_FALSE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () == NULL);
+
+ // empty lines omitted by preprocessor
+ source_line
+ = fc.get_source_line_preprocessed ("test.h", tmp.get_filename (), 2);
+ ASSERT_FALSE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () == NULL);
+ }
+
+ // Verify that the cache is working as expected
+ // line not previously read but all linemarkers are in the cache
+ char_span source_line
+ = fc.get_source_line_preprocessed ("test.cpp", tmp.get_filename (), 6);
+ ASSERT_TRUE (source_line);
+ ASSERT_TRUE (source_line.get_buffer () != NULL);
+ ASSERT_TRUE (!strncmp (" int i = 5;", source_line.get_buffer (),
+ source_line.length ()));
+}
+
/* Run all of the selftests within this file. */
void
@@ -1087,6 +1775,9 @@ file_cache_cc_tests ()
test_reading_source_line ();
test_reading_source_buffer ();
test_replacement ();
+ test_parsing_linemarker ();
+ test_linemarker_cache ();
+ test_reading_source_line_preprocessed ();
}
} // namespace selftest
@@ -69,6 +69,10 @@ class char_span
need to be in this header. */
class file_cache_slot;
+/* Forward decl of the linemarker cache used when reading source lines from
+ a preprocessed input file; its definition is private to file-cache.cc. */
+struct linemarker_cache;
+
/* A cache of source files for use when emitting diagnostics
(and in a few places in the C/C++ frontends).
@@ -101,6 +105,10 @@ class file_cache
char_span get_source_file_content (const char *file_path);
char_span get_source_line (const char *file_path, int line);
+ char_span get_source_line_preprocessed (const char *source_name,
+ const char *file_path, int line);
+ char_span get_source_line_maybe_preprocessed (const char *file_path,
+ int line);
bool missing_trailing_newline_p (const char *file_path);
void add_buffered_content (const char *file_path,
@@ -109,6 +117,13 @@ class file_cache
void tune (size_t num_file_slots, size_t lines);
+ /* Configure reading of source lines from a preprocessed input file. */
+ void set_preprocessed (bool preprocessed) { m_is_preprocessed = preprocessed; }
+ void set_main_input_file_path (const char *path)
+ {
+ m_main_input_file_path = path;
+ }
+
private:
file_cache_slot *evicted_cache_tab_entry (unsigned *highest_use_count);
file_cache_slot *add_file (const char *file_path);
@@ -118,6 +133,17 @@ class file_cache
size_t m_num_file_slots;
file_cache_slot *m_file_slots;
input_context m_input_context;
+
+ /* Cache of linemarkers seen in the preprocessed input file, used to speed
+ up get_source_line_preprocessed. */
+ linemarker_cache *m_linemarker_cache;
+
+ /* The original (preprocessed) input file to read source lines from when
+ m_is_preprocessed is true. */
+ const char *m_main_input_file_path;
+
+ /* True if the input file is treated as preprocessed. */
+ bool m_is_preprocessed;
};
} // namespace diagnostics
@@ -2202,9 +2202,9 @@ layout::calculate_x_offset_display ()
return;
}
- const diagnostics::char_span line
- = m_file_cache.get_source_line (m_exploc.file,
- m_exploc.line);
+ diagnostics::char_span line
+ = m_file_cache.get_source_line_maybe_preprocessed (m_exploc.file,
+ m_exploc.line);
if (!line)
{
/* Nothing to do, we couldn't find the source line. */
@@ -3365,7 +3365,8 @@ source_line::source_line (diagnostics::file_cache &fc,
const char *filename,
int line)
{
- diagnostics::char_span span = fc.get_source_line (filename, line);
+ diagnostics::char_span span
+ = fc.get_source_line_maybe_preprocessed (filename, line);
chars = span.get_buffer ();
width = span.length ();
}
@@ -3722,7 +3723,9 @@ layout_printer<TextOrHtml>::print_line (linenum_type row)
typename TextOrHtml::auto_check_tag_nesting sentinel (m_text_or_html);
diagnostics::char_span line
- = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row);
+ = m_layout.m_file_cache.get_source_line_maybe_preprocessed
+ (m_layout.m_exploc.file, row);
+
if (!line)
return;
@@ -26,6 +26,7 @@ along with GCC; see the file COPYING3. If not see
#include "tree.h"
#include "tree-pass.h"
#include "diagnostic.h"
+#include "diagnostics/file-cache.h"
#include "opts.h"
#include "flags.h"
#include "langhooks.h"
@@ -231,6 +232,11 @@ read_cmdline_options (struct gcc_options *opts, struct gcc_options *opts_set,
if (opts->x_main_input_filename == NULL)
{
opts->x_main_input_filename = decoded_options[i].arg;
+ // remember original input filename in the file cache
+ // because if a preprocessed file is compiled, this is
+ // changed to the original source file name later
+ dc->get_file_cache ().set_main_input_file_path
+ (opts->x_main_input_filename);
opts->x_main_input_baselength
= base_of_path (opts->x_main_input_filename,
&opts->x_main_input_basename);
@@ -40,4 +40,4 @@ int main ()
}
// { dg-additional-files "missing-std-include-10.h" }
// { dg-regexp {[^\n]*: error: 'strcmp' was not declared in this scope\n *return strcmp [^\n]*;\n *\^~*\n} }
-// { dg-regexp {[^\n]* note: 'strcmp' is defined in header[^\n]*\n #include "missing-std-include-10.h"\n\+#include <cstring>\n // HERE\n} }
+// { dg-regexp {[^\n]* note: 'strcmp' is defined in header[^\n]*\n} }