diff mbox series

diagnostics: pch: Remember diagnostic pragmas in a PCH [PR64117]

Message ID 20231108031741.1320175-1-lhyatt@gmail.com
State New
Headers show
Series diagnostics: pch: Remember diagnostic pragmas in a PCH [PR64117] | expand

Commit Message

Lewis Hyatt Nov. 8, 2023, 3:17 a.m. UTC
Hello-

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64117

This fixes an old PR / enhancement request. Bootstrap + regtest all
languages on x86-64 Linux. Please let me know if it looks OK? Thanks!

-Lewis

-- >8 --

As the PR points out, we do not currently record in a PCH whether any
diagnostics were reclassified by pragmas, so a header that takes care to
adjust some diagnostics for downstream users does not work as designed when
it is precompiled.  Implement that feature by adding a new interface
diagnostic_context::save_to_pch() / restore_from_pch(), which is called on
the global diagnostic context object.

This feature also exposes the need for a small tweak to the C++ frontend.
That frontend processes `#pragma GCC diagnostic push' and `#pragma GCC
diagnostic pop' pragmas twice, once while preprocessing and once again while
compiling.  In case a translation unit contains an unbalanced set of pushes
and pops, that results in twice as many leftover states as there should be.
This does not cause any problems for a single translation unit, but if the
snapshot of the state is preserved in a PCH, then it becomes observable, for
example with a setup like in the new testcase pragma-diagnostic-3.C:

t.h:
----
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored...
 //no pop at end of the file

t.c
----
 #include "t.h"
 #pragma GCC diagnostic pop
 //expect to be at the initial state here

If t.h has been precompiled, and if the push had been processed twice at the
time the PCH was written, then the state will not be reset as expected in
t.c. Address that by having the C++ frontend reset the push/pop history
before starting its second pass.

gcc/ChangeLog:

	PR pch/64117
	* Makefile.in: Add tree-diagnostic.cc to GTFILES
	* diagnostic.cc (diagnostic_option_classifier::init): Handle the
	classification history members, which had been omitted here.
	(diagnostic_option_classifier::fini): Likewise.
	(diagnostic_option_classifier::classify_diagnostic): Refactor some
	logic to...
	(diagnostic_context::get_original_option_classification): ...this
	new function.
	* diagnostic.h (struct diagnostic_option_classifier::state): Declare.
	(diagnostic_option_classifier::save_state_to): Declare.
	(diagnostic_option_classifier::restore_from_pch): Declare.
	(diagnostic_option_classifier::get_state): Declare.
	(diagnostic_option_classifier::restore_state): Declare.
	(diagnostic_option_classifier::free_state): Declare.
	(diagnostic_context::get_original_option_classification): Declare.
	(diagnostic_context::get_classifier): New accessor functions.
	(diagnostic_context::save_to_pch): Declare.
	(diagnostic_context::restore_from_pch): Declare.
	* ggc-common.cc (gt_pch_save): Use the new PCH interface to handle
	the global diagnostic context.
	(gt_pch_restore): Likewise.
	* tree-diagnostic.cc (struct diagnostic_option_classifier::state):
	New struct.
	(struct diagnostic_pch_data): New struct.
	(pch): New GC root.
	(diagnostic_context::save_to_pch): New function.
	(diagnostic_context::restore_from_pch): New function.
	(diagnostic_option_classifier::save_state_to): New function.
	(diagnostic_option_classifier::restore_from_pch): New function.
	(diagnostic_option_classifier::get_state): New function.
	(diagnostic_option_classifier::restore_state): New function.
	(diagnostic_option_classifier::free_state): New function.

gcc/cp/ChangeLog:

	PR pch/64117
	* parser.cc (cp_lexer_new_main): Restore the diagnostics
	classifications to their original state after the first pass through
	the input.

gcc/testsuite/ChangeLog:

	PR pch/64117
	* g++.dg/pch/pragma-diagnostic-1.C: New test.
	* g++.dg/pch/pragma-diagnostic-1.Hs: New test.
	* g++.dg/pch/pragma-diagnostic-2.C: New test.
	* g++.dg/pch/pragma-diagnostic-2.Hs: New test.
	* g++.dg/pch/pragma-diagnostic-3.C: New test.
	* g++.dg/pch/pragma-diagnostic-3.Hs: New test.
	* g++.dg/pch/pragma-diagnostic-4.C: New test.
	* g++.dg/pch/pragma-diagnostic-4.Hs: New test.
	* g++.dg/pch/pragma-diagnostic-5.C: New test.
	* g++.dg/pch/pragma-diagnostic-5.Hs: New test.
	* gcc.dg/pch/pragma-diagnostic-1.c: New test.
	* gcc.dg/pch/pragma-diagnostic-1.hs: New test.
	* gcc.dg/pch/pragma-diagnostic-2.c: New test.
	* gcc.dg/pch/pragma-diagnostic-2.hs: New test.
	* gcc.dg/pch/pragma-diagnostic-3.c: New test.
	* gcc.dg/pch/pragma-diagnostic-3.hs: New test.
	* gcc.dg/pch/pragma-diagnostic-4.c: New test.
	* gcc.dg/pch/pragma-diagnostic-4.hs: New test.
---
 gcc/Makefile.in                               |   1 +
 gcc/cp/parser.cc                              |  11 ++
 gcc/diagnostic.cc                             |  26 ++-
 gcc/diagnostic.h                              |  34 ++++
 gcc/ggc-common.cc                             |   5 +-
 gcc/tree-diagnostic.cc                        | 151 ++++++++++++++++++
 .../g++.dg/pch/pragma-diagnostic-1.C          |  19 +++
 .../g++.dg/pch/pragma-diagnostic-1.Hs         |   6 +
 .../g++.dg/pch/pragma-diagnostic-2.C          |  19 +++
 .../g++.dg/pch/pragma-diagnostic-2.Hs         |   6 +
 .../g++.dg/pch/pragma-diagnostic-3.C          |   5 +
 .../g++.dg/pch/pragma-diagnostic-3.Hs         |   4 +
 .../g++.dg/pch/pragma-diagnostic-4.C          |  17 ++
 .../g++.dg/pch/pragma-diagnostic-4.Hs         |   6 +
 .../g++.dg/pch/pragma-diagnostic-5.C          |   5 +
 .../g++.dg/pch/pragma-diagnostic-5.Hs         |   8 +
 .../gcc.dg/pch/pragma-diagnostic-1.c          |  19 +++
 .../gcc.dg/pch/pragma-diagnostic-1.hs         |   6 +
 .../gcc.dg/pch/pragma-diagnostic-2.c          |  19 +++
 .../gcc.dg/pch/pragma-diagnostic-2.hs         |   6 +
 .../gcc.dg/pch/pragma-diagnostic-3.c          |   5 +
 .../gcc.dg/pch/pragma-diagnostic-3.hs         |   4 +
 .../gcc.dg/pch/pragma-diagnostic-4.c          |  17 ++
 .../gcc.dg/pch/pragma-diagnostic-4.hs         |   6 +
 24 files changed, 398 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.Hs
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.c
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.hs
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.c
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.hs
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.c
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.hs
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.c
 create mode 100644 gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.hs
diff mbox series

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 29cec21c825..dbc6403f383 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -2840,6 +2840,7 @@  GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/reg-stack.cc $(srcdir)/cfgrtl.cc \
   $(srcdir)/stor-layout.cc \
   $(srcdir)/stringpool.cc $(srcdir)/tree.cc $(srcdir)/varasm.cc \
+  $(srcdir)/tree-diagnostic.cc \
   $(srcdir)/gimple.h \
   $(srcdir)/gimple-ssa.h \
   $(srcdir)/tree-ssanames.cc $(srcdir)/tree-eh.cc $(srcdir)/tree-ssa-address.cc \
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 5116bcb78f6..9317e45b569 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -714,6 +714,11 @@  cp_lexer_new_main (void)
   cp_parser_initial_pragma (&token);
   c_common_no_more_pch ();
 
+  /* Remember the current state of diagnostics classifications, so we can
+     reset it after preprocessing.  */
+  auto &classifier = global_dc->get_classifier ();
+  const auto class_state = classifier.get_state ();
+
   cp_lexer *lexer = cp_lexer_alloc ();
   /* Put the first token in the buffer.  */
   cp_token *tok = lexer->buffer->quick_push (token);
@@ -772,6 +777,12 @@  cp_lexer_new_main (void)
      process them again at the correct time as needed.  */
   c_reset_target_pragmas ();
 
+  /* Reset the state of any diagnostic pragmas to the initial state, so that
+     e.g. an unbalanced set of push/pop pragmas would not be observable in
+     a precompiled header.  */
+  classifier.restore_state (class_state);
+  classifier.free_state (class_state);
+
   gcc_assert (!lexer->next_token->purged_p);
   return lexer;
 }
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index addd6606eaa..7b0dc39cee3 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -156,6 +156,8 @@  diagnostic_option_classifier::init (int n_opts)
   m_classify_diagnostic = XNEWVEC (diagnostic_t, n_opts);
   for (int i = 0; i < n_opts; i++)
     m_classify_diagnostic[i] = DK_UNSPECIFIED;
+  m_classification_history = nullptr;
+  m_n_classification_history = 0;
   m_push_list = nullptr;
   m_n_push = 0;
 }
@@ -165,7 +167,11 @@  diagnostic_option_classifier::fini ()
 {
   XDELETEVEC (m_classify_diagnostic);
   m_classify_diagnostic = nullptr;
-  free (m_push_list);
+  XDELETEVEC (m_classification_history);
+  m_classification_history = nullptr;
+  m_n_classification_history = 0;
+  XDELETEVEC (m_push_list);
+  m_push_list = nullptr;
   m_n_push = 0;
 }
 
@@ -1123,11 +1129,7 @@  classify_diagnostic (const diagnostic_context *context,
       /* Record the command-line status, so we can reset it back on DK_POP. */
       if (old_kind == DK_UNSPECIFIED)
 	{
-	  old_kind = !context->m_option_enabled (option_index,
-						 context->m_lang_mask,
-						 context->m_option_state)
-	    ? DK_IGNORED : (context->warning_as_error_requested_p ()
-			    ? DK_ERROR : DK_WARNING);
+	  old_kind = context->get_original_option_classification (option_index);
 	  m_classify_diagnostic[option_index] = old_kind;
 	}
 
@@ -2471,6 +2473,18 @@  set_text_art_charset (enum diagnostic_text_art_charset charset)
     }
 }
 
+/* Return the original state of an option prior to any reclassification that may
+   have been done via diagnostic pragmas.  */
+diagnostic_t
+diagnostic_context::get_original_option_classification (int option_index) const
+{
+  if (!m_option_enabled)
+    return DK_UNSPECIFIED;
+  if (!m_option_enabled (option_index, m_lang_mask, m_option_state))
+    return DK_IGNORED;
+  return warning_as_error_requested_p () ? DK_ERROR : DK_WARNING;
+}
+
 /* class simple_diagnostic_path : public diagnostic_path.  */
 
 simple_diagnostic_path::simple_diagnostic_path (pretty_printer *event_pp)
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 58341cec308..c1d5c8388aa 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -267,6 +267,20 @@  public:
   diagnostic_t
   update_effective_level_from_pragmas (diagnostic_info *diagnostic) const;
 
+  /* See comments in class diagnostic_context regarding PCH support.  */
+  struct state;
+  void save_state_to (state *st, bool is_ggc) const;
+  void restore_from_pch (const diagnostic_context *context);
+
+  /* These are helper routines for PCH support, but they may also be useful on
+     their own, e.g. to save and restore the state of all classifications.  The
+     returned state should be passed to free_state () when it is no longer
+     needed; otherwise it may be passed to restore_state () as many times as
+     needed.  */
+  state * get_state () const;
+  bool restore_state (const state *st);
+  static void free_state (state *st);
+
 private:
   /* Each time a diagnostic's classification is changed with a pragma,
      we record the change and the location of the change in an array of
@@ -401,6 +415,8 @@  public:
 						    where);
   }
 
+  diagnostic_t get_original_option_classification (int option_index) const;
+
   void push_diagnostics (location_t where ATTRIBUTE_UNUSED)
   {
     m_option_classifier.push ();
@@ -463,6 +479,18 @@  public:
     return m_file_cache;
   }
 
+  const diagnostic_option_classifier &
+  get_classifier () const
+  {
+    return m_option_classifier;
+  }
+
+  diagnostic_option_classifier &
+  get_classifier ()
+  {
+    return m_option_classifier;
+  }
+
   edit_context *get_edit_context () const
   {
     return m_edit_context_ptr;
@@ -666,6 +694,12 @@  public:
   /* The size of the tabstop for tab expansion.  */
   int m_tabstop;
 
+  /* Support to remember a subset of the configuration in a PCH.  Implemented in
+     tree-diagnostic.cc, since diagnostic.cc may be used in contexts (via
+     libcommon.a) where GGC routines are not available.  */
+  void save_to_pch () const;
+  void restore_from_pch ();
+
 private:
   /* How should non-ASCII/non-printable bytes be escaped when
      a diagnostic suggests escaping the source code on output.  */
diff --git a/gcc/ggc-common.cc b/gcc/ggc-common.cc
index 39e2581affd..a6ddffabc47 100644
--- a/gcc/ggc-common.cc
+++ b/gcc/ggc-common.cc
@@ -25,7 +25,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "timevar.h"
-#include "diagnostic-core.h"
+#include "diagnostic.h"
 #include "ggc-internal.h"
 #include "hosthooks.h"
 #include "plugin.h"
@@ -520,6 +520,7 @@  gt_pch_save (FILE *f)
   const size_t mmap_offset_alignment = host_hooks.gt_pch_alloc_granularity ();
 
   gt_pch_save_stringpool ();
+  global_dc->save_to_pch ();
 
   timevar_push (TV_PCH_PTR_REALLOC);
   saving_htab = new hash_table<saving_hasher> (50000);
@@ -916,6 +917,8 @@  gt_pch_restore (FILE *f)
   /* Barring corruption of the PCH file, the restored line table should be
      complete and usable.  */
   line_table = new_line_table;
+
+  global_dc->restore_from_pch ();
 }
 
 /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS when mmap is not present.
diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc
index de853bc4fd7..c1489613f71 100644
--- a/gcc/tree-diagnostic.cc
+++ b/gcc/tree-diagnostic.cc
@@ -379,3 +379,154 @@  tree_diagnostics_defaults (diagnostic_context *context)
   context->set_set_locations_callback (set_inlining_locations);
   context->set_client_data_hooks (make_compiler_data_hooks ());
 }
+
+/* Plumbing to remember diagnostic classification history in a PCH, so that
+   `#pragma GCC diagnostic' directives from the PCH will take effect after
+   restoring the PCH.  */
+
+struct GTY (()) diagnostic_option_classifier::state
+{
+  int n_opts;
+  int n_classification_history;
+  int n_push;
+  void * GTY((atomic)) classification_history;
+  int * GTY((atomic)) push_list;
+};
+
+struct GTY (()) diagnostic_pch_data
+{
+  diagnostic_option_classifier::state class_state;
+};
+
+static GTY(()) diagnostic_pch_data *pch;
+
+void
+diagnostic_context::save_to_pch () const
+{
+  pch = ggc_cleared_alloc<diagnostic_pch_data> ();
+  m_option_classifier.save_state_to (&pch->class_state, true);
+}
+
+void
+diagnostic_context::restore_from_pch ()
+{
+  if (!pch)
+    return;
+  m_option_classifier.restore_from_pch (this);
+  pch = nullptr;
+}
+
+void
+diagnostic_option_classifier::save_state_to (state *st, bool is_ggc) const
+{
+  st->n_opts = m_n_opts;
+
+#define DC_ALLOC(T, N) (is_ggc ? ggc_vec_alloc<T> (N) : XNEWVEC (T, N))
+
+  if (m_n_classification_history)
+    {
+      using dcc = diagnostic_classification_change_t;
+      st->n_classification_history = m_n_classification_history;
+      st->classification_history = DC_ALLOC (dcc, m_n_classification_history);
+      memcpy (st->classification_history, m_classification_history,
+	      m_n_classification_history * sizeof (dcc));
+    }
+
+  if (m_n_push)
+    {
+      st->n_push = m_n_push;
+      st->push_list = DC_ALLOC (int, m_n_push);
+      memcpy (st->push_list, m_push_list, m_n_push * sizeof (int));
+    }
+
+#undef DC_ALLOC
+
+}
+
+diagnostic_option_classifier::state *
+diagnostic_option_classifier::get_state () const
+{
+  const auto st = XCNEW (diagnostic_option_classifier::state);
+  save_state_to (st, false);
+  return st;
+}
+
+bool
+diagnostic_option_classifier::restore_state (const state *st)
+{
+  if (st->n_opts != m_n_opts)
+    return false;
+
+  m_n_classification_history = st->n_classification_history;
+  if (m_n_classification_history)
+    {
+      using dcc = diagnostic_classification_change_t;
+      m_classification_history = XRESIZEVEC (dcc, m_classification_history,
+					     m_n_classification_history);
+      memcpy (m_classification_history, st->classification_history,
+	      m_n_classification_history * sizeof (dcc));
+    }
+
+  m_n_push = st->n_push;
+  if (m_n_push)
+    {
+      m_push_list = XRESIZEVEC (int, m_push_list, m_n_push);
+      memcpy (m_push_list, st->push_list, m_n_push * sizeof (int));
+    }
+
+  return true;
+}
+
+void
+diagnostic_option_classifier::
+restore_from_pch (const diagnostic_context *context)
+{
+  /* N.B.  We do not need to worry about preserving whatever exists already in
+     this object, because we would have declined to read a PCH if a `#pragma GCC
+     diagnostic' had been processed already.  If it should be supported in the
+     future, then any location_t values stored in the classification_history are
+     invalid (being valid only for the old line_table, which has now been
+     replaced by a new one from the PCH), so we would need to go through and
+     create new location_t values pointing to some valid location in the new
+     line_table.  */
+  if (!restore_state (&pch->class_state))
+    return;
+
+  /* m_classify_diagnostic is used to record the state of each option as
+     specified on the command line prior to processing any diagnostic pragmas.
+     This makes it possible for the outermost `#pragma GCC diagnostic pop' to
+     restore that initial state.  We do not automatically populate
+     m_classify_diagnostic for all options, rather we make the necessary memo
+     about the initial state only when we change the state via a diagnostic
+     pragma.  So if the state has been changed not by a diagnostic pragma in
+     *this* translation unit, but rather by a diagnostic pragma recorded in the
+     PCH, we need to record the initial state now so that we can pop to it
+     later.  (It is permitted to have an unbalanced `#pragma GCC diagnostic
+     push' in a header--be it PCH or regular--and then to have the corresponding
+     `#pragma GCC diagnostic pop' say in another header, or in the main source
+     file.)  Since the state to which a pop directive needs to move is the
+     initial state from the current compilation, not the initial state from the
+     compilation of the PCH file, we can't read this data from the PCH but
+     rather need to figure it out now.  */
+
+  for (int i = 0; i != m_n_classification_history; ++i)
+    {
+      const auto &hist = m_classification_history[i];
+      if (hist.kind != (int) DK_POP)
+	{
+	  auto &c = m_classify_diagnostic[hist.option];
+	  if (c == DK_UNSPECIFIED)
+	    c = context->get_original_option_classification (hist.option);
+	}
+    }
+}
+
+void
+diagnostic_option_classifier::free_state (state *st)
+{
+  XDELETEVEC (st->classification_history);
+  XDELETEVEC (st->push_list);
+  XDELETE (st);
+}
+
+#include "gt-tree-diagnostic.h"
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.C b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.C
new file mode 100644
index 00000000000..c9b31c03471
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.C
@@ -0,0 +1,19 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Werror=uninitialized -ftrack-macro-expansion=0" } */
+#include "pragma-diagnostic-1.H"
+#define UNINIT1(x) void f ## x () { int i; int j = i; }
+#define UNINIT2(x) UNINIT1 (x) _Pragma("GCC diagnostic pop")
+#define UNINIT UNINIT2(__LINE__)
+
+/* Diagnostic should be ignored in the innermost two contexts.  */
+UNINIT /* { dg-bogus "uninitialized" } */
+UNINIT /* { dg-bogus "uninitialized" } */
+
+/* Diagnostic should be a warning now.  */
+UNINIT /* { dg-warning "uninitialized" } */
+
+/* Make sure the final pop took it to an error, as requested by -Werror,
+   not to a warning, as was the case when the PCH was compiled.  */
+UNINIT /* { dg-error "uninitialized" } */
+
+/* { dg-regexp {[^[:blank:]]*: some warnings being treated as errors} } */
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.Hs b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.Hs
new file mode 100644
index 00000000000..22ea699e809
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-1.Hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#pragma GCC diagnostic push
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.C b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.C
new file mode 100644
index 00000000000..282b86487d1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.C
@@ -0,0 +1,19 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Werror -Wuninitialized -ftrack-macro-expansion=0" } */
+#include "pragma-diagnostic-2.H"
+#define UNINIT1(x) void f ## x () { int i; int j = i; }
+#define UNINIT2(x) UNINIT1 (x) _Pragma("GCC diagnostic pop")
+#define UNINIT UNINIT2(__LINE__)
+
+/* Diagnostic should be ignored in the innermost two contexts.  */
+UNINIT /* { dg-bogus "uninitialized" } */
+UNINIT /* { dg-bogus "uninitialized" } */
+
+/* Diagnostic should be a warning now.  */
+UNINIT /* { dg-warning "uninitialized" } */
+
+/* Make sure the final pop took it to an error, as requested by -Werror,
+   not to a warning, as was the case when the PCH was compiled.  */
+UNINIT /* { dg-error "uninitialized" } */
+
+/* { dg-regexp {[^[:blank:]]*: all warnings being treated as errors} } */
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.Hs b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.Hs
new file mode 100644
index 00000000000..22ea699e809
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-2.Hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#pragma GCC diagnostic push
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.C b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.C
new file mode 100644
index 00000000000..12991b3953a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.C
@@ -0,0 +1,5 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Wdate-time" } */
+#include "pragma-diagnostic-3.H"
+#pragma GCC diagnostic pop
+const char *s2 = __DATE__; /* { dg-warning "-Wdate-time" } */
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.Hs b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.Hs
new file mode 100644
index 00000000000..bd112301a3f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-3.Hs
@@ -0,0 +1,4 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdate-time"
+const char *s1 = __DATE__;
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.C b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.C
new file mode 100644
index 00000000000..a58e8b6c864
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.C
@@ -0,0 +1,17 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Wpragmas -Wuninitialized" } */
+#include "pragma-diagnostic-4.H"
+#pragma GCC diagnostic ignored "oops" /* { dg-bogus "pragmas" } */
+void f1 ()
+{
+  int i;
+  int j = i; /* { dg-bogus "uninitialized" } */
+}
+#pragma GCC diagnostic pop
+_Pragma("GCC diagnostic ignored \"oops\"") /* { dg-error "pragmas" } */
+void f2 ()
+{
+  int i;
+  int j = i; /* { dg-error "uninitialized" } */
+}
+/* { dg-regexp {[^[:blank:]]*: some warnings being treated as errors} } */
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.Hs b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.Hs
new file mode 100644
index 00000000000..da4990baec7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-4.Hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic error "-Wpragmas"
+#pragma GCC diagnostic error "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wuninitialized"
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.C b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.C
new file mode 100644
index 00000000000..b72242b679a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.C
@@ -0,0 +1,5 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Weffc++" } */
+#include "pragma-diagnostic-5.H"
+Struct<int> global;
+/* { dg-bogus "should be initialized in the member initialization list" "" { target *-*-* } 6 } */
diff --git a/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.Hs b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.Hs
new file mode 100644
index 00000000000..5cee20f05e9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pragma-diagnostic-5.Hs
@@ -0,0 +1,8 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic ignored "-Weffc++"
+template<typename T>
+struct Struct {
+  T *pointer;
+  Struct() {}
+};
+#pragma GCC diagnostic error "-Weffc++"
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.c b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.c
new file mode 100644
index 00000000000..32e832f5291
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.c
@@ -0,0 +1,19 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Werror=uninitialized -ftrack-macro-expansion=0" } */
+#include "pragma-diagnostic-1.h"
+#define UNINIT1(x) void f ## x () { int i; int j = i; }
+#define UNINIT2(x) UNINIT1 (x) _Pragma("GCC diagnostic pop")
+#define UNINIT UNINIT2(__LINE__)
+
+/* Diagnostic should be ignored in the innermost two contexts.  */
+UNINIT /* { dg-bogus "uninitialized" } */
+UNINIT /* { dg-bogus "uninitialized" } */
+
+/* Diagnostic should be a warning now.  */
+UNINIT /* { dg-warning "uninitialized" } */
+
+/* Make sure the final pop took it to an error, as requested by -Werror,
+   not to a warning, as was the case when the PCH was compiled.  */
+UNINIT /* { dg-error "uninitialized" } */
+
+/* { dg-regexp {[^[:blank:]]*: some warnings being treated as errors} } */
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.hs b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.hs
new file mode 100644
index 00000000000..22ea699e809
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-1.hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#pragma GCC diagnostic push
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.c b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.c
new file mode 100644
index 00000000000..484b7a611e2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.c
@@ -0,0 +1,19 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Werror -Wuninitialized -ftrack-macro-expansion=0" } */
+#include "pragma-diagnostic-2.h"
+#define UNINIT1(x) void f ## x () { int i; int j = i; }
+#define UNINIT2(x) UNINIT1 (x) _Pragma("GCC diagnostic pop")
+#define UNINIT UNINIT2(__LINE__)
+
+/* Diagnostic should be ignored in the innermost two contexts.  */
+UNINIT /* { dg-bogus "uninitialized" } */
+UNINIT /* { dg-bogus "uninitialized" } */
+
+/* Diagnostic should be a warning now.  */
+UNINIT /* { dg-warning "uninitialized" } */
+
+/* Make sure the final pop took it to an error, as requested by -Werror,
+   not to a warning, as was the case when the PCH was compiled.  */
+UNINIT /* { dg-error "uninitialized" } */
+
+/* { dg-regexp {[^[:blank:]]*: all warnings being treated as errors} } */
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.hs b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.hs
new file mode 100644
index 00000000000..22ea699e809
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-2.hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#pragma GCC diagnostic push
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.c b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.c
new file mode 100644
index 00000000000..483f7f33797
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.c
@@ -0,0 +1,5 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Wdate-time" } */
+#include "pragma-diagnostic-3.h"
+#pragma GCC diagnostic pop
+const char *s2 = __DATE__; /* { dg-warning "-Wdate-time" } */
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.hs b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.hs
new file mode 100644
index 00000000000..bd112301a3f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-3.hs
@@ -0,0 +1,4 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdate-time"
+const char *s1 = __DATE__;
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.c b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.c
new file mode 100644
index 00000000000..dd539cfac29
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.c
@@ -0,0 +1,17 @@ 
+/* PR pch/64117 */
+/* { dg-additional-options "-Wpragmas -Wuninitialized" } */
+#include "pragma-diagnostic-4.h"
+#pragma GCC diagnostic ignored "oops" /* { dg-bogus "pragmas" } */
+void f1 ()
+{
+  int i;
+  int j = i; /* { dg-bogus "uninitialized" } */
+}
+#pragma GCC diagnostic pop
+_Pragma("GCC diagnostic ignored \"oops\"") /* { dg-error "pragmas" } */
+void f2 ()
+{
+  int i;
+  int j = i; /* { dg-error "uninitialized" } */
+}
+/* { dg-regexp {[^[:blank:]]*: some warnings being treated as errors} } */
diff --git a/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.hs b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.hs
new file mode 100644
index 00000000000..da4990baec7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pch/pragma-diagnostic-4.hs
@@ -0,0 +1,6 @@ 
+/* PR pch/64117 */
+#pragma GCC diagnostic error "-Wpragmas"
+#pragma GCC diagnostic error "-Wuninitialized"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wuninitialized"