From 51187ec777b0ab57061d60e987cfdb0bdf544d6d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 10 Oct 2018 16:11:45 -0400
Subject: [PATCH 05/42] FIXME: WIP experiment to print help when a header can't
 be found

---
 gcc/c-family/c-common.c                        | 76 ++++++++++++++++++++++++++
 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                               | 20 +++++++
 gcc/spellcheck.h                               |  4 ++
 gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c |  1 +
 gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c |  1 +
 gcc/testsuite/gcc.dg/cpp/test-header.h         |  0
 libcpp/errors.c                                | 13 +++++
 libcpp/files.c                                 | 45 ++++++++++++---
 libcpp/include/cpplib.h                        |  9 +++
 libcpp/include/line-map.h                      |  4 ++
 14 files changed, 182 insertions(+), 10 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/test-header.h

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 534d928..81346c7 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> // FIXME
+#include <dirent.h> // FIXME
+#include "gcc-rich-location.h" // FIXME
 
 cpp_reader *parse_in;		/* Declared in c-pragma.h.  */
 
@@ -8142,6 +8145,79 @@ cb_get_suggestion (cpp_reader *, const char *goal,
   return bm.get_best_meaningful_candidate ();
 }
 
+/* FIXME.  */
+
+static void
+maybe_suggest_misspellings (cpp_reader *pfile, source_location loc,
+			    const char *fname, cpp_dir *dir)
+{
+  // FIXME: see find_file_in_dir
+#if 0
+  if (CPP_OPTION (pfile, remap))
+    return;
+#endif
+  if (dir->construct)
+    return;
+  char *path = 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);
+  inform (loc, "   dirname: %qs", dname);
+
+  // FIXME: get basename of fname
+  
+  /* 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);
+    }
+
+  const char *bm = find_closest_string (fname, // FIXME
+					candidates);
+  // FIXME: does bm really outlive "candidates"?
+  // is this a use-after-free?
+  if (bm)
+    {
+      gcc_rich_location richloc (loc);
+      char *replacement = xasprintf ("\"%s\"", bm); // FIXME
+      richloc.add_fixit_replace (replacement);
+      inform (&richloc, "close match: %qs", bm);
+      free (replacement);
+    }
+
+  /* dname may be using "path"s buffer.  */
+  free (path);
+}
+
+/* Callback for libcpp for reporting on missing header files.  */
+
+void
+cb_on_missing_header (cpp_reader *pfile, source_location loc,
+		      const char *fname, cpp_dir *start_dir)
+{
+  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);
+      /* FIXME: show close matches
+	 (which would require getting at edit-distance code from libcpp)
+	 FIXME: handle wrong kind of angle brackets.  */
+      maybe_suggest_misspellings (pfile, loc, fname, dir);
+    }
+}
+
 /* 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 641fe57..bb2e885 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 *);
+
 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..d2b7843 100644
--- a/gcc/spellcheck.c
+++ b/gcc/spellcheck.c
@@ -162,6 +162,26 @@ find_closest_string (const char *target,
   return bm.get_best_meaningful_candidate ();
 }
 
+/* FIXME.  */
+
+const char *
+find_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);
+    }
+
+  return bm.get_best_meaningful_candidate ();
+}
+
 /* 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..a0cb7c2 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 const char *
+find_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..3c736ee
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/misspelled-header-1.c
@@ -0,0 +1 @@
+#include "toast-header.h"
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..0c5af63
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/misspelled-header-2.c
@@ -0,0 +1 @@
+#include <toast-header.h>
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 08b7c64..16c884a 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);
@@ -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,40 @@ _cpp_stack_include (cpp_reader *pfile, const char *fname, int angle_brackets,
   return stacked;
 }
 
+/* FIXME.  */
+
+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)
+  : rich_location (pfile->line_table, loc),
+    m_pfile (pfile),
+    m_fname (fname),
+    m_start_dir (start_dir)
+  {}
+
+  void on_fatal_error () FINAL OVERRIDE;
+
+ private:
+  cpp_reader *m_pfile;
+  const char *m_fname;
+  cpp_dir *m_start_dir;
+};
+
+/* FIXME.  */
+
+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);
+}
+
 /* 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);
@@ -1067,6 +1096,8 @@ open_file_failed (cpp_reader *pfile, _cpp_file *file, int angle_brackets,
   if (pfile->state.in__has_include__)
     return;
 
+  open_file_failed_location richloc (pfile, loc, file->name, start_dir);
+
   errno = file->err_no;
   if (print_dep && CPP_OPTION (pfile, deps.missing_files) && errno == ENOENT)
     {
@@ -1076,7 +1107,7 @@ open_file_failed (cpp_reader *pfile, _cpp_file *file, int angle_brackets,
       if (CPP_OPTION (pfile, deps.need_preprocessor_output))
 	cpp_errno_filename (pfile, CPP_DL_FATAL,
 			    file->path ? file->path : file->name,
-			    loc);
+			    &richloc);
     }
   else
     {
@@ -1092,7 +1123,7 @@ open_file_failed (cpp_reader *pfile, _cpp_file *file, int angle_brackets,
           || CPP_OPTION (pfile, deps.need_preprocessor_output))
 	cpp_errno_filename (pfile, CPP_DL_FATAL,
 			    file->path ? file->path : file->name,
-			    loc);
+			    &richloc);
       else
 	cpp_errno_filename (pfile, CPP_DL_WARNING,
 			    file->path ? file->path : file->name,
@@ -1557,7 +1588,7 @@ 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 *
+char *
 append_file_to_dir (const char *fname, cpp_dir *dir)
 {
   size_t dlen, flen;
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index a9b4fb3..f7f7bb2 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*);
+
+  // FIXME
+  void (*on_missing_header) (cpp_reader *, source_location, const char *,
+			     cpp_dir *);
 };
 
 #ifdef VMS
@@ -1163,6 +1167,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
@@ -1297,4 +1303,7 @@ extern bool cpp_userdef_char_p
 extern const char * cpp_get_userdef_suffix
   (const cpp_token *);
 
+// FIXME (files.c):
+extern char *append_file_to_dir (const char *fname, cpp_dir *dir);
+
 #endif /* ! LIBCPP_CPPLIB_H */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 23f0e4a..2a4d46b 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1738,6 +1738,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 ();
-- 
1.8.5.3

