Message ID | 1541979208-37968-1-git-send-email-dmalcolm@redhat.com |
---|---|
State | New |
Headers | show |
Series | RFC: C/C++: print help when a header can't be found | expand |
On 11/11/2018 04:33 PM, David Malcolm wrote: > When gcc can't find a header file, it's a hard error that stops the build, > typically requiring the user to mess around with compile flags, Makefiles, > dependencies, and so forth. > > Often the exact search paths aren't obvious to the user. Consider the > case where the include paths are injected via a tool such as pkg-config, > such as e.g.: > > gcc $(pkg-config --cflags glib-2.0) demo.c > > This patch is an attempt at being more helpful for such cases. Given that > the user can't proceed until the issue is resolved, I think it's reasonable > to default to telling the user as much as possible about what happened. > This patch list all of the search paths, and any close matches (e.g. for > misspellings). > > Without the patch, the current behavior is: > > misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or directory > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > compilation terminated. > > With the patch, the user gets this output: > > misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or directory > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > misspelled-header-1.c:1:10: note: paths searched: > misspelled-header-1.c:1:10: note: path: '' > misspelled-header-1.c:1:10: note: not found: 'test-header.hpp' > misspelled-header-1.c:1:10: note: close match: 'test-header.h' > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > | "test-header.h" > misspelled-header-1.c:1:10: note: path: '/usr/include/glib-2.0' (via '-I') > misspelled-header-1.c:1:10: note: not found: '/usr/include/glib-2.0/test-header.hpp' > misspelled-header-1.c:1:10: note: path: '/usr/lib64/glib-2.0/include' (via '-I') > misspelled-header-1.c:1:10: note: not found: '/usr/lib64/glib-2.0/include/test-header.hpp' > misspelled-header-1.c:1:10: note: path: './include' (system directory) > misspelled-header-1.c:1:10: note: not found: './include/test-header.hpp' > misspelled-header-1.c:1:10: note: path: './include-fixed' (system directory) > misspelled-header-1.c:1:10: note: not found: './include-fixed/test-header.hpp' > misspelled-header-1.c:1:10: note: path: '/usr/local/include' (system directory) > misspelled-header-1.c:1:10: note: not found: '/usr/local/include/test-header.hpp' > misspelled-header-1.c:1:10: note: path: '/usr/include' (system directory) > misspelled-header-1.c:1:10: note: not found: '/usr/include/test-header.hpp' > compilation terminated. > > showing the paths that were tried, and why (e.g. the -I paths injected by > the pkg-config invocation), and the .hpp vs .h issue (with a fix-it hint). > > It's verbose, but as I said above, the user can't proceed until they > resolve it, so I think being verbose is appropriate here. > > Thoughts? I think printing the directories and especially the near matches will be very helpful, especially for big projects with lots of -I options. The output could be made substantially shorter, less repetitive, and so easier to read -- basically cut in half -- by avoiding most of the duplication and collapsing two notes into one, e.g. like so: fatal error: test-header.hpp: No such file or directory 1 | #include "test-header.hpp" | ^~~~~~~~~~~~~~~~~ note: paths searched: note: -I '.' note: close match: 'test-header.h' 1 | #include "test-header.hpp" | ^~~~~~~~~~~~~~~~~ | "test-header.h" note: -I '/usr/include/glib-2.0' note: -I '/usr/lib64/glib-2.0/include' note: -isystem './include' note: -isystem './include-fixed' note: -isystem '/usr/local/include' note: -isystem '/usr/include' or by printing the directories in sections: note: -I paths searched: note: '.' note: close match: 'test-header.h' 1 | #include "test-header.hpp" | ^~~~~~~~~~~~~~~~~ | "test-header.h" note: '/usr/include/glib-2.0' note: '/usr/lib64/glib-2.0/include' note: -isystem paths searched: note: './include' note: './include-fixed' note: '/usr/local/include' note: '/usr/include' Martin
On Mon, Nov 12, 2018 at 4:01 PM Martin Sebor <msebor@gmail.com> wrote: > On 11/11/2018 04:33 PM, David Malcolm wrote: > > When gcc can't find a header file, it's a hard error that stops the build, > > typically requiring the user to mess around with compile flags, Makefiles, > > dependencies, and so forth. > > > > Often the exact search paths aren't obvious to the user. Consider the > > case where the include paths are injected via a tool such as pkg-config, > > such as e.g.: > > > > gcc $(pkg-config --cflags glib-2.0) demo.c > > > > This patch is an attempt at being more helpful for such cases. Given that > > the user can't proceed until the issue is resolved, I think it's reasonable > > to default to telling the user as much as possible about what happened. > > This patch list all of the search paths, and any close matches (e.g. for > > misspellings). > > > > Without the patch, the current behavior is: > > > > misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or directory > > 1 | #include "test-header.hpp" > > | ^~~~~~~~~~~~~~~~~ > > compilation terminated. > > > > With the patch, the user gets this output: > > > > misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or directory > > 1 | #include "test-header.hpp" > > | ^~~~~~~~~~~~~~~~~ > > misspelled-header-1.c:1:10: note: paths searched: > > misspelled-header-1.c:1:10: note: path: '' > > misspelled-header-1.c:1:10: note: not found: 'test-header.hpp' > > misspelled-header-1.c:1:10: note: close match: 'test-header.h' > > 1 | #include "test-header.hpp" > > | ^~~~~~~~~~~~~~~~~ > > | "test-header.h" > > misspelled-header-1.c:1:10: note: path: '/usr/include/glib-2.0' (via '-I') > > misspelled-header-1.c:1:10: note: not found: '/usr/include/glib-2.0/test-header.hpp' > > misspelled-header-1.c:1:10: note: path: '/usr/lib64/glib-2.0/include' (via '-I') > > misspelled-header-1.c:1:10: note: not found: '/usr/lib64/glib-2.0/include/test-header.hpp' > > misspelled-header-1.c:1:10: note: path: './include' (system directory) > > misspelled-header-1.c:1:10: note: not found: './include/test-header.hpp' > > misspelled-header-1.c:1:10: note: path: './include-fixed' (system directory) > > misspelled-header-1.c:1:10: note: not found: './include-fixed/test-header.hpp' > > misspelled-header-1.c:1:10: note: path: '/usr/local/include' (system directory) > > misspelled-header-1.c:1:10: note: not found: '/usr/local/include/test-header.hpp' > > misspelled-header-1.c:1:10: note: path: '/usr/include' (system directory) > > misspelled-header-1.c:1:10: note: not found: '/usr/include/test-header.hpp' > > compilation terminated. > > > > showing the paths that were tried, and why (e.g. the -I paths injected by > > the pkg-config invocation), and the .hpp vs .h issue (with a fix-it hint). > > > > It's verbose, but as I said above, the user can't proceed until they > > resolve it, so I think being verbose is appropriate here. > > > > Thoughts? > > I think printing the directories and especially the near matches > will be very helpful, especially for big projects with lots of -I > options. > > The output could be made substantially shorter, less repetitive, > and so easier to read -- basically cut in half -- by avoiding > most of the duplication and collapsing two notes into one, e.g. > like so: > > fatal error: test-header.hpp: No such file or directory > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > note: paths searched: > note: -I '.' > note: close match: 'test-header.h' > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > | "test-header.h" > note: -I '/usr/include/glib-2.0' > note: -I '/usr/lib64/glib-2.0/include' > note: -isystem './include' > note: -isystem './include-fixed' > note: -isystem '/usr/local/include' > note: -isystem '/usr/include' > > or by printing the directories in sections: > > note: -I paths searched: > note: '.' > note: close match: 'test-header.h' > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > | "test-header.h" > note: '/usr/include/glib-2.0' > note: '/usr/lib64/glib-2.0/include' > note: -isystem paths searched: > note: './include' > note: './include-fixed' > note: '/usr/local/include' > note: '/usr/include' I agree that the "not found" lines are unnecessary; I don't feel strongly about the other formatting. Jason
On 11/12/18, Martin Sebor <msebor@gmail.com> wrote: > On 11/11/2018 04:33 PM, David Malcolm wrote: >> When gcc can't find a header file, it's a hard error that stops the >> build, >> typically requiring the user to mess around with compile flags, >> Makefiles, >> dependencies, and so forth. >> >> Often the exact search paths aren't obvious to the user. Consider the >> case where the include paths are injected via a tool such as pkg-config, >> such as e.g.: >> >> gcc $(pkg-config --cflags glib-2.0) demo.c >> >> This patch is an attempt at being more helpful for such cases. Given >> that >> the user can't proceed until the issue is resolved, I think it's >> reasonable >> to default to telling the user as much as possible about what happened. >> This patch list all of the search paths, and any close matches (e.g. for >> misspellings). >> >> Without the patch, the current behavior is: >> >> misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or >> directory >> 1 | #include "test-header.hpp" >> | ^~~~~~~~~~~~~~~~~ >> compilation terminated. >> >> With the patch, the user gets this output: >> >> misspelled-header-1.c:1:10: fatal error: test-header.hpp: No such file or >> directory >> 1 | #include "test-header.hpp" >> | ^~~~~~~~~~~~~~~~~ >> misspelled-header-1.c:1:10: note: paths searched: >> misspelled-header-1.c:1:10: note: path: '' >> misspelled-header-1.c:1:10: note: not found: 'test-header.hpp' >> misspelled-header-1.c:1:10: note: close match: 'test-header.h' >> 1 | #include "test-header.hpp" >> | ^~~~~~~~~~~~~~~~~ >> | "test-header.h" >> misspelled-header-1.c:1:10: note: path: '/usr/include/glib-2.0' (via >> '-I') >> misspelled-header-1.c:1:10: note: not found: >> '/usr/include/glib-2.0/test-header.hpp' >> misspelled-header-1.c:1:10: note: path: '/usr/lib64/glib-2.0/include' >> (via '-I') >> misspelled-header-1.c:1:10: note: not found: >> '/usr/lib64/glib-2.0/include/test-header.hpp' >> misspelled-header-1.c:1:10: note: path: './include' (system directory) >> misspelled-header-1.c:1:10: note: not found: >> './include/test-header.hpp' >> misspelled-header-1.c:1:10: note: path: './include-fixed' (system >> directory) >> misspelled-header-1.c:1:10: note: not found: >> './include-fixed/test-header.hpp' >> misspelled-header-1.c:1:10: note: path: '/usr/local/include' (system >> directory) >> misspelled-header-1.c:1:10: note: not found: >> '/usr/local/include/test-header.hpp' >> misspelled-header-1.c:1:10: note: path: '/usr/include' (system >> directory) >> misspelled-header-1.c:1:10: note: not found: >> '/usr/include/test-header.hpp' >> compilation terminated. >> >> showing the paths that were tried, and why (e.g. the -I paths injected by >> the pkg-config invocation), and the .hpp vs .h issue (with a fix-it >> hint). >> >> It's verbose, but as I said above, the user can't proceed until they >> resolve it, so I think being verbose is appropriate here. >> >> Thoughts? > > I think printing the directories and especially the near matches > will be very helpful, especially for big projects with lots of -I > options. > > The output could be made substantially shorter, less repetitive, > and so easier to read -- basically cut in half -- by avoiding > most of the duplication and collapsing two notes into one, e.g. > like so: > > fatal error: test-header.hpp: No such file or directory > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > note: paths searched: > note: -I '.' > note: close match: 'test-header.h' > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > | "test-header.h" > note: -I '/usr/include/glib-2.0' > note: -I '/usr/lib64/glib-2.0/include' > note: -isystem './include' > note: -isystem './include-fixed' > note: -isystem '/usr/local/include' > note: -isystem '/usr/include' > > or by printing the directories in sections: > > note: -I paths searched: > note: '.' > note: close match: 'test-header.h' > 1 | #include "test-header.hpp" > | ^~~~~~~~~~~~~~~~~ > | "test-header.h" > note: '/usr/include/glib-2.0' > note: '/usr/lib64/glib-2.0/include' > note: -isystem paths searched: > note: './include' > note: './include-fixed' > note: '/usr/local/include' > note: '/usr/include' > > Martin > > How would this interact with -Wmissing-include-dirs?
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c index 534d928..fb37393 100644 --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -49,6 +49,9 @@ along with GCC; see the file COPYING3. If not see #include "substring-locations.h" #include "spellcheck.h" #include "selftest.h" +#include <libgen.h> +#include <dirent.h> +#include "gcc-rich-location.h" cpp_reader *parse_in; /* Declared in c-pragma.h. */ @@ -8142,6 +8145,88 @@ cb_get_suggestion (cpp_reader *, const char *goal, return bm.get_best_meaningful_candidate (); } +/* Given a bogus #include of FNAME at LOC, look within DIR for similar + looking filenames, and if any files there are close enough, issue + a note suggesting the closest one as a spell correction. */ + +static void +maybe_suggest_misspelled_headers (cpp_reader *pfile, source_location loc, + const char *fname, cpp_dir *dir, + int angle_brackets) +{ + /* Compare with find_file_in_dir. */ + /* FIXME: what to do if remapping is enabled? */ + if (cpp_get_options (pfile)->remap) + return; + if (dir->construct) + return; + char *path = cpp_append_file_to_dir (fname, dir); + gcc_assert (path); + inform (loc, " not found: %qs", path); + + /* dname's lifetime becomes bound up with "path". */ + char *dname = dirname (path); + + /* FIXME: what if fname embeds a path? We'd want to split it out into + path and filename, and search relative to the paths we have. */ + + /* Find the closest match within the directory. The candidate + string needs to outlive the iteration, so gather them all + into an auto_string_vec. */ + auto_string_vec candidates; + if (DIR *dstream = opendir (dname)) + { + while (struct dirent *entry = readdir (dstream)) + candidates.safe_push (xstrdup (entry->d_name)); + closedir (dstream); + } + + /* Which candidate is closest (if any are close enough to + be meaningful). */ + char *bm = get_copy_of_closest_string (fname, candidates); + if (bm) + { + gcc_rich_location richloc (loc); + char *replacement; + if (angle_brackets) + replacement = xasprintf ("<%s>", bm); + else + replacement = xasprintf ("\"%s\"", bm); + richloc.add_fixit_replace (replacement); + inform (&richloc, " close match: %qs", bm); + free (replacement); + free (bm); + } + + /* dname may be using "path"s buffer. */ + free (path); +} + +/* Callback for libcpp for reporting on missing header files. + Report on which paths were searched, and why. + Report on possible misspellings of files found in those paths. */ +/* FIXME: handle wrong kind of angle brackets, where the header would + have been found if the correct form of #include had been used. */ + +void +cb_on_missing_header (cpp_reader *pfile, source_location loc, + const char *fname, cpp_dir *start_dir, + int angle_brackets) +{ + inform (loc, "paths searched:"); + for (cpp_dir *dir = start_dir; dir; dir = dir->next) + { + if (dir->sysp > 0) + inform (loc, " path: %qs (system directory)", dir->name); + else if (dir->user_supplied_p) + inform (loc, " path: %qs (via %<-I%>)", dir->name); + else + inform (loc, " path: %qs", dir->name); + maybe_suggest_misspelled_headers (pfile, loc, fname, dir, + angle_brackets); + } +} + /* Return the latice point which is the wider of the two FLT_EVAL_METHOD modes X, Y. This isn't just >, as the FLT_EVAL_METHOD values added by C TS 18661-3 for interchange types that are computed in their diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index a218432..70cf5ce 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -1077,6 +1077,11 @@ extern time_t cb_get_source_date_epoch (cpp_reader *pfile); extern const char *cb_get_suggestion (cpp_reader *, const char *, const char *const *); +/* Callback for libcpp for reporting on missing header files. */ + +extern void cb_on_missing_header (cpp_reader *, source_location, const char *, + cpp_dir *, int); + extern GTY(()) string_concat_db *g_string_concat_db; class substring_loc; diff --git a/gcc/c-family/c-lex.c b/gcc/c-family/c-lex.c index 28a820a..d46cf80 100644 --- a/gcc/c-family/c-lex.c +++ b/gcc/c-family/c-lex.c @@ -84,6 +84,7 @@ init_c_lex (void) cb->get_source_date_epoch = cb_get_source_date_epoch; cb->get_suggestion = cb_get_suggestion; cb->remap_filename = remap_macro_filename; + cb->on_missing_header = cb_on_missing_header; /* Set the debug callbacks if we can use them. */ if ((debug_info_level == DINFO_LEVEL_VERBOSE diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c index a572c08..0e21925 100644 --- a/gcc/diagnostic.c +++ b/gcc/diagnostic.c @@ -493,7 +493,8 @@ diagnostic_check_max_errors (diagnostic_context *context, bool flush) is written out. This function does not always return. */ void diagnostic_action_after_output (diagnostic_context *context, - diagnostic_t diag_kind) + diagnostic_t diag_kind, + rich_location *richloc) { switch (diag_kind) { @@ -543,6 +544,10 @@ diagnostic_action_after_output (diagnostic_context *context, case DK_FATAL: if (context->abort_on_error) real_abort (); + /* Allow notes to be emitted from any on_fatal_error handler. */ + context->lock--; + if (richloc) + richloc->on_fatal_error (); diagnostic_finish (context); fnotice (stderr, "compilation terminated.\n"); exit (FATAL_EXIT_CODE); @@ -1012,7 +1017,8 @@ diagnostic_report_diagnostic (diagnostic_context *context, print_parseable_fixits (context->printer, diagnostic->richloc); pp_flush (context->printer); } - diagnostic_action_after_output (context, diagnostic->kind); + diagnostic_action_after_output (context, diagnostic->kind, + diagnostic->richloc); diagnostic->x_data = NULL; if (context->edit_context_ptr) diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 3498a9b..6fc5092 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -343,7 +343,8 @@ void default_diagnostic_start_span_fn (diagnostic_context *, expanded_location); void default_diagnostic_finalizer (diagnostic_context *, diagnostic_info *); void diagnostic_set_caret_max_width (diagnostic_context *context, int value); -void diagnostic_action_after_output (diagnostic_context *, diagnostic_t); +void diagnostic_action_after_output (diagnostic_context *, diagnostic_t, + rich_location * = NULL); void diagnostic_check_max_errors (diagnostic_context *, bool flush = false); void diagnostic_file_cache_fini (void); diff --git a/gcc/spellcheck.c b/gcc/spellcheck.c index 690e6fa..e856771 100644 --- a/gcc/spellcheck.c +++ b/gcc/spellcheck.c @@ -162,6 +162,31 @@ find_closest_string (const char *target, return bm.get_best_meaningful_candidate (); } +/* As find_closest_string above, but return a copy of the closest string + if a non-NULL string is found. */ + +char * +get_copy_of_closest_string (const char *target, + const auto_string_vec &candidates) +{ + gcc_assert (target); + + int i; + char *candidate; + best_match<const char *, const char *> bm (target); + FOR_EACH_VEC_ELT (candidates, i, candidate) + { + gcc_assert (candidate); + bm.consider (candidate); + } + + const char *result = bm.get_best_meaningful_candidate (); + if (result) + return xstrdup (result); + else + return NULL; +} + /* Generate the maximum edit distance for which we consider a suggestion to be meaningful, given a goal of length GOAL_LEN and a candidate of length CANDIDATE_LEN. diff --git a/gcc/spellcheck.h b/gcc/spellcheck.h index e8fa77c..e47df93 100644 --- a/gcc/spellcheck.h +++ b/gcc/spellcheck.h @@ -35,6 +35,10 @@ extern const char * find_closest_string (const char *target, const auto_vec<const char *> *candidates); +extern char * +get_copy_of_closest_string (const char *target, + const auto_string_vec &candidates); + /* A traits class for describing a string-like type usable by class best_match. Specializations should provide the implementations of the following: diff --git a/gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c b/gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c new file mode 100644 index 0000000..a63b62a --- /dev/null +++ b/gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c @@ -0,0 +1,2 @@ +#include "test-header.hpp" +/* TODO: how to express the expected output in DejaGnu form? */ diff --git a/gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c b/gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c new file mode 100644 index 0000000..392b084 --- /dev/null +++ b/gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c @@ -0,0 +1,5 @@ +#include <test-header.h> +/* TODO: presumably the most helpful suggestion is a fix-it suggesting + converting the '<' and '>' to quotes, so that we also look in the + non-system-header locations, and find it there. */ +/* TODO: how to express the expected output in DejaGnu form? */ diff --git a/gcc/testsuite/gcc.dg/cpp/misspelled-header-3.c b/gcc/testsuite/gcc.dg/cpp/misspelled-header-3.c new file mode 100644 index 0000000..846cc3a --- /dev/null +++ b/gcc/testsuite/gcc.dg/cpp/misspelled-header-3.c @@ -0,0 +1,3 @@ +#include "../test-header.h" + +/* TODO: handle bogus paths within a #include directive, etc. */ diff --git a/gcc/testsuite/gcc.dg/cpp/test-header.h b/gcc/testsuite/gcc.dg/cpp/test-header.h new file mode 100644 index 0000000..e69de29 diff --git a/libcpp/errors.c b/libcpp/errors.c index 2268fc4..4146252 100644 --- a/libcpp/errors.c +++ b/libcpp/errors.c @@ -306,3 +306,16 @@ cpp_errno_filename (cpp_reader *pfile, enum cpp_diagnostic_level level, return cpp_error_at (pfile, level, loc, "%s: %s", filename, xstrerror (errno)); } + +/* As above, but with a rich_location. */ + +bool +cpp_errno_filename (cpp_reader *pfile, enum cpp_diagnostic_level level, + const char *filename, rich_location *richloc) +{ + if (filename[0] == '\0') + filename = _("stdout"); + + return cpp_error_at (pfile, level, richloc, "%s: %s", filename, + xstrerror (errno)); +} diff --git a/libcpp/files.c b/libcpp/files.c index 0f0cc7b..2af4df9 100644 --- a/libcpp/files.c +++ b/libcpp/files.c @@ -182,7 +182,7 @@ static struct cpp_dir *search_path_head (cpp_reader *, const char *fname, int angle_brackets, enum include_type); static const char *dir_name_of_file (_cpp_file *file); static void open_file_failed (cpp_reader *pfile, _cpp_file *file, int, - source_location); + source_location, cpp_dir *start_dir = NULL); static struct cpp_file_hash_entry *search_cache (struct cpp_file_hash_entry *head, const cpp_dir *start_dir); static _cpp_file *make_cpp_file (cpp_reader *, cpp_dir *, const char *fname); @@ -196,7 +196,6 @@ static int file_hash_eq (const void *p, const void *q); static char *read_filename_string (int ch, FILE *f); static void read_name_map (cpp_dir *dir); static char *remap_filename (cpp_reader *pfile, _cpp_file *file); -static char *append_file_to_dir (const char *fname, cpp_dir *dir); static bool validate_pch (cpp_reader *, _cpp_file *file, const char *pchname); static int pchf_save_compare (const void *e1, const void *e2); static int pchf_compare (const void *d_p, const void *e_p); @@ -387,7 +386,7 @@ find_file_in_dir (cpp_reader *pfile, _cpp_file *file, bool *invalid_pch, if (file->dir->construct) path = file->dir->construct (file->name, file->dir); else - path = append_file_to_dir (file->name, file->dir); + path = cpp_append_file_to_dir (file->name, file->dir); if (path) { @@ -588,7 +587,7 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir, return NULL; } else - open_file_failed (pfile, file, angle_brackets, loc); + open_file_failed (pfile, file, angle_brackets, loc, start_dir); break; } @@ -1056,10 +1055,45 @@ _cpp_stack_include (cpp_reader *pfile, const char *fname, int angle_brackets, return stacked; } +/* A subclass of rich_location for reporting on failure to open a file. */ + +class open_file_failed_location : public rich_location +{ + public: + open_file_failed_location (cpp_reader *pfile, source_location loc, + const char *fname, cpp_dir *start_dir, + int angle_brackets) + : rich_location (pfile->line_table, loc), + m_pfile (pfile), + m_fname (fname), + m_start_dir (start_dir), + m_angle_brackets (angle_brackets) + {} + + void on_fatal_error () FINAL OVERRIDE; + + private: + cpp_reader *m_pfile; + const char *m_fname; + cpp_dir *m_start_dir; + int m_angle_brackets; +}; + +/* Implementation of rich_location::on_fatal_error vfunc for + open_file_failed_location. */ + +void +open_file_failed_location::on_fatal_error () +{ + if (m_pfile->cb.on_missing_header) + m_pfile->cb.on_missing_header (m_pfile, get_loc (), m_fname, m_start_dir, + m_angle_brackets); +} + /* Could not open FILE. The complication is dependency output. */ static void open_file_failed (cpp_reader *pfile, _cpp_file *file, int angle_brackets, - source_location loc) + source_location loc, cpp_dir *start_dir) { int sysp = pfile->line_table->highest_line > 1 && pfile->buffer ? pfile->buffer->sysp : 0; bool print_dep = CPP_OPTION (pfile, deps.style) > (angle_brackets || !!sysp); @@ -1090,9 +1124,23 @@ open_file_failed (cpp_reader *pfile, _cpp_file *file, int angle_brackets, if (CPP_OPTION (pfile, deps.style) == DEPS_NONE || print_dep || CPP_OPTION (pfile, deps.need_preprocessor_output)) - cpp_errno_filename (pfile, CPP_DL_FATAL, - file->path ? file->path : file->name, - loc); + { + if (CPP_OPTION (pfile, deps.style) == DEPS_NONE) + { + /* If we're not outputting dependencies, then provide hints to + the user on what the include paths were, and on possible + fixes for a mispelled include. */ + open_file_failed_location richloc (pfile, loc, file->name, + start_dir, angle_brackets); + cpp_errno_filename (pfile, CPP_DL_FATAL, + file->path ? file->path : file->name, + &richloc); + } + else + cpp_errno_filename (pfile, CPP_DL_FATAL, + file->path ? file->path : file->name, + loc); + } else cpp_errno_filename (pfile, CPP_DL_WARNING, file->path ? file->path : file->name, @@ -1557,8 +1605,8 @@ cpp_set_include_chains (cpp_reader *pfile, cpp_dir *quote, cpp_dir *bracket, /* Append the file name to the directory to create the path, but don't turn / into // or // into ///; // may be a namespace escape. */ -static char * -append_file_to_dir (const char *fname, cpp_dir *dir) +char * +cpp_append_file_to_dir (const char *fname, cpp_dir *dir) { size_t dlen, flen; char *path; @@ -1649,7 +1697,7 @@ read_name_map (cpp_dir *dir) dir->name_map[count + 1] = to; else { - dir->name_map[count + 1] = append_file_to_dir (to, dir); + dir->name_map[count + 1] = cpp_append_file_to_dir (to, dir); free (to); } diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index aad836d..c859a37 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -682,6 +682,10 @@ struct cpp_callbacks /* Callback for filename remapping in __FILE__ and __BASE_FILE__ macro expansions. */ const char *(*remap_filename) (const char*); + + /* Callback for when aborting due to a failure to open a header. */ + void (*on_missing_header) (cpp_reader *, source_location, const char *, + cpp_dir *, int); }; #ifdef VMS @@ -1166,6 +1170,8 @@ extern bool cpp_errno (cpp_reader *, enum cpp_diagnostic_level, the filename is not localized. */ extern bool cpp_errno_filename (cpp_reader *, enum cpp_diagnostic_level, const char *filename, source_location loc); +extern bool cpp_errno_filename (cpp_reader *, enum cpp_diagnostic_level, + const char *filename, rich_location *richloc); /* Same as cpp_error, except additionally specifies a position as a (translation unit) physical line and physical column. If the line is @@ -1269,6 +1275,7 @@ extern cpp_buffer *cpp_get_buffer (cpp_reader *); extern struct _cpp_file *cpp_get_file (cpp_buffer *); extern cpp_buffer *cpp_get_prev (cpp_buffer *); extern void cpp_clear_file_cache (cpp_reader *); +extern char *cpp_append_file_to_dir (const char *fname, cpp_dir *dir); /* In pch.c */ struct save_macro_data; diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h index ae9780e..70a4d04 100644 --- a/libcpp/include/line-map.h +++ b/libcpp/include/line-map.h @@ -1641,7 +1641,7 @@ class rich_location const range_label *label = NULL); /* Destructor. */ - ~rich_location (); + virtual ~rich_location (); /* Accessors. */ source_location get_loc () const { return get_loc (0); } @@ -1750,6 +1750,10 @@ class rich_location return !m_fixits_cannot_be_auto_applied; } + /* Vfunc to be called for a fatal error, immediately before exiting, + to allow fatal errors to emit supporting notes. */ + virtual void on_fatal_error () {} + private: bool reject_impossible_fixit (source_location where); void stop_supporting_fixits ();