diff mbox series

RFC: C/C++: print help when a header can't be found

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

Commit Message

David Malcolm Nov. 11, 2018, 11:33 p.m. UTC
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?
Dave

gcc/c-family/ChangeLog:
	* c-common.c: Include <libgen.h>, <dirent.h>, and
	"gcc-rich-location.h".
	(maybe_suggest_misspelled_headers): New function.
	(cb_on_missing_header): New function.
	* c-common.h (cb_on_missing_header): New decl.
	* c-lex.c (init_c_lex): Wire up cb_on_missing_header.

gcc/ChangeLog:
	* diagnostic.c (diagnostic_action_after_output): Add "rich_loc"
	param.
	(diagnostic_action_after_output): When handling a fatal error,
	call the rich_location's on_fatal_error vfunc.
	(diagnostic_report_diagnostic): Pass the rich_location to
	diagnostic_action_after_output.
	* diagnostic.h (diagnostic_action_after_output): Add optional
	rich_location * param.
	* spellcheck.c (get_copy_of_closest_string): New function.
	* spellcheck.h (get_copy_of_closest_string): New decl.

gcc/testsuite/ChangeLog:
	* gcc.dg/cpp/misspelled-header-1.c: New test.
	* gcc.dg/cpp/misspelled-header-2.c: New test.
	* gcc.dg/cpp/misspelled-header-3.c: New test.

libcpp/ChangeLog:
	* errors.c (cpp_errno_filename): Add overload taking a
	rich_location *.
	* files.c (open_file_failed): Add "start_dir" param.
	(find_file_in_dir): Update for renaming of append_file_to_dir.
	(_cpp_find_file): Pass start_dir to open_file_failed.
	(class open_file_failed_location): New subclass of rich_location.
	(open_file_failed): Add "start_dir" param.  If we're not
	outputting dependencies, then use open_file_failed_location to
	provide hints to the user on which paths were searched.
	(append_file_to_dir): Rename to...
	(cpp_append_file_to_dir): ...this.
	(read_name_map): Update for renaming.
	* include/cpplib.h (struct cpp_callbacks): Add on_missing_header.
	(cpp_errno_filename): Convert final patam from source_location
	to rich_location *.
	(cpp_append_file_to_dir): New decl.
	* include/line-map.h (rich_location::~rich_location): Make
	virtual.
	(rich_location::on_fatal_error): New vfunc.
---
 gcc/c-family/c-common.c                        | 85 ++++++++++++++++++++++++++
 gcc/c-family/c-common.h                        |  5 ++
 gcc/c-family/c-lex.c                           |  1 +
 gcc/diagnostic.c                               | 10 ++-
 gcc/diagnostic.h                               |  3 +-
 gcc/spellcheck.c                               | 25 ++++++++
 gcc/spellcheck.h                               |  4 ++
 gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c |  2 +
 gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c |  5 ++
 gcc/testsuite/gcc.dg/cpp/misspelled-header-3.c |  3 +
 gcc/testsuite/gcc.dg/cpp/test-header.h         |  0
 libcpp/errors.c                                | 13 ++++
 libcpp/files.c                                 | 70 +++++++++++++++++----
 libcpp/include/cpplib.h                        |  7 +++
 libcpp/include/line-map.h                      |  6 +-
 15 files changed, 224 insertions(+), 15 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/misspelled-header-3.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/test-header.h

Comments

Martin Sebor Nov. 12, 2018, 9:01 p.m. UTC | #1
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
Jason Merrill Nov. 13, 2018, 10:12 p.m. UTC | #2
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
Eric Gallager Nov. 16, 2018, 12:55 a.m. UTC | #3
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 mbox series

Patch

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 ();