diff --git a/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c b/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c
index 9867c94a8ddc3683d420146b42c88ca8da923b31..6e37294fd2ba0df53a756193eb2f02b6d2b41269 100644
--- a/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c
+++ b/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c
@@ -3,9 +3,8 @@
 #pragma GCC warning "warn-a" // { dg-warning warn-a }
 #pragma GCC error "err-b" // { dg-error err-b }
 
-#define CONST1 _Pragma("GCC warning \"warn-c\"") 1
-#define CONST2 _Pragma("GCC error \"err-d\"") 2
-
-char a[CONST1]; // { dg-warning warn-c }
-char b[CONST2]; // { dg-error err-d }
+#define CONST1 _Pragma("GCC warning \"warn-c\"") 1 // { dg-warning warn-c }
+#define CONST2 _Pragma("GCC error \"err-d\"") 2 // { dg-error err-d }
 
+char a[CONST1]; // { dg-note "in expansion of macro 'CONST1'" }
+char b[CONST2]; // { dg-note "in expansion of macro 'CONST2'" }
diff --git a/gcc/testsuite/c-c++-common/cpp/pragma-diagnostic-loc.c b/gcc/testsuite/c-c++-common/cpp/pragma-diagnostic-loc.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ef40420cdd360a78f7695699c1927bf83d59752
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/cpp/pragma-diagnostic-loc.c
@@ -0,0 +1,17 @@
+/* { dg-do preprocess } */
+/* PR preprocessor/114423 */
+/* Check that we now issue diagnostics at the location of the _Pragma
+   instead of an invalid location.  If we someday manage to issue
+   diagnostics at better locations in the future, this will need
+   updating.  */
+_Pragma("GCC warning \"warning1\"") /* { dg-warning "1:warning1" } */
+#define P _Pragma("GCC warning \"warning2\"") /* { dg-warning "11:warning2" } */
+P /* { dg-note "in expansion of macro" } */
+#define S "GCC warning \"warning3\""
+/**/ _Pragma(S) /* { dg-warning "6:warning3" } */
+
+/* This diagnostic uses a different code path (cpp_diagnostic_at() rather
+   than cpp_error_with_line()).  Also make sure that the dg-note location
+   does not get overridden to the _Pragma location.  */
+#pragma GCC poison xyz /* { dg-note "poisoned here" } */
+/* */ _Pragma("xyz") /* { dg-error "7:attempt to use poisoned" } */
diff --git a/gcc/testsuite/g++.dg/pch/operator-1.C b/gcc/testsuite/g++.dg/pch/operator-1.C
index 290b5f7ab21044d226ac59f16e6c0a423baf678c..1138a96953613841dce51465fcfa12a5bdf4de5d 100644
--- a/gcc/testsuite/g++.dg/pch/operator-1.C
+++ b/gcc/testsuite/g++.dg/pch/operator-1.C
@@ -1,2 +1,8 @@
 #include "operator-1.H"
-int main(void){ major(0);} /* { dg-warning "Did not Work" } */
+int main(void){ major(0);} /* { dg-note "in expansion of macro 'major'" } */
+/* Line numbers below pertain to the header file.  */
+/* { dg-warning "Did not Work" "" { target *-*-* } 1 } */
+/* { dg-note "in expansion of macro '__glibc_macro_warning1'" "" { target *-*-* } 3 } */
+/* { dg-note "in expansion of macro '__glibc_macro_warning'" "" { target *-*-* } 4 } */
+/* { dg-note "in expansion of macro '__SYSMACROS_DM1'" "" { target *-*-* } 6 } */
+/* { dg-note "in expansion of macro '__SYSMACROS_DM'" "" { target *-*-* } 9 } */
diff --git a/libcpp/directives.cc b/libcpp/directives.cc
index 9d235fa1b05768734479bbf7988d4fe6450e9bb3..5706c28b83530ebb425972a9c423eee8d1849509 100644
--- a/libcpp/directives.cc
+++ b/libcpp/directives.cc
@@ -2430,6 +2430,12 @@ destringize_and_run (cpp_reader *pfile, const cpp_string *in,
   pfile->buffer->file = pfile->buffer->prev->file;
   pfile->buffer->sysp = pfile->buffer->prev->sysp;
 
+  /* See comment below regarding the use of expansion_loc as the location
+     for all tokens; arrange here that diagnostics issued during lexing
+     get the same treatment.  */
+  const auto prev_loc_override = pfile->diagnostic_override_loc;
+  pfile->diagnostic_override_loc = expansion_loc;
+
   start_directive (pfile);
   _cpp_clean_line (pfile);
   save_directive = pfile->directive;
@@ -2497,6 +2503,7 @@ destringize_and_run (cpp_reader *pfile, const cpp_string *in,
      make that applicable to the real buffer too.  */
   pfile->buffer->prev->sysp = pfile->buffer->sysp;
   _cpp_pop_buffer (pfile);
+  pfile->diagnostic_override_loc = prev_loc_override;
 
   /* Reset the old macro state before ...  */
   XDELETE (pfile->context);
diff --git a/libcpp/errors.cc b/libcpp/errors.cc
index ad45f61913c313ecd1b8553b6bc9ca4829e2c005..b644c36a9a03521e11af75711618598dab26e65c 100644
--- a/libcpp/errors.cc
+++ b/libcpp/errors.cc
@@ -60,13 +60,15 @@ cpp_diagnostic_at (cpp_reader * pfile, enum cpp_diagnostic_level level,
 		   enum cpp_warning_reason reason, rich_location *richloc,
 		   const char *msgid, va_list *ap)
 {
-  bool ret;
-
   if (!pfile->cb.diagnostic)
     abort ();
-  ret = pfile->cb.diagnostic (pfile, level, reason, richloc, _(msgid), ap);
-
-  return ret;
+  if (pfile->diagnostic_override_loc && level != CPP_DL_NOTE)
+    {
+      rich_location rc2{pfile->line_table, pfile->diagnostic_override_loc};
+      rc2.set_escape_on_output (richloc->escape_on_output_p ());
+      return pfile->cb.diagnostic (pfile, level, reason, &rc2, _(msgid), ap);
+    }
+  return pfile->cb.diagnostic (pfile, level, reason, richloc, _(msgid), ap);
 }
 
 /* Print a diagnostic at the location of the previously lexed token.  */
@@ -201,8 +203,14 @@ cpp_diagnostic_with_line (cpp_reader * pfile, enum cpp_diagnostic_level level,
   
   if (!pfile->cb.diagnostic)
     abort ();
+  /* Don't override note locations, which will likely make the note
+     more confusing.  */
+  const bool do_loc_override
+    = pfile->diagnostic_override_loc && level != CPP_DL_NOTE;
+  if (do_loc_override)
+    src_loc = pfile->diagnostic_override_loc;
   rich_location richloc (pfile->line_table, src_loc);
-  if (column)
+  if (column && !do_loc_override)
     richloc.override_column (column);
   ret = pfile->cb.diagnostic (pfile, level, reason, &richloc, _(msgid), ap);
 
diff --git a/libcpp/internal.h b/libcpp/internal.h
index a658a8c5739c1f1df5e45db13cd09bfe0c785dd9..13186c5a5b048bd1accdef755a852148a0bde50c 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -616,6 +616,10 @@ struct cpp_reader
      zero of said file.  */
   location_t main_loc;
 
+  /* If non-zero, override diagnostic locations (other than DK_NOTE
+     diagnostics) to this one.  */
+  location_t diagnostic_override_loc;
+
   /* Returns true iff we should warn about UTF-8 bidirectional control
      characters.  */
   bool warn_bidi_p () const