diff mbox series

[v2,3/4] diagnostics: libcpp: Assign real locations to the tokens inside _Pragma strings

Message ID 9a9ae598bfcde9676d41d592273b3b71a30b0ee4.1672867272.git.lhyatt@gmail.com
State New
Headers show
Series diagnostics: libcpp: Overhaul locations for _Pragma tokens | expand

Commit Message

Lewis Hyatt Jan. 5, 2023, 10:36 p.m. UTC
Currently, the tokens obtained from a destringified _Pragma string do not get
assigned proper locations while they are being lexed.  After the tokens have
been obtained, they are reassigned the same location as the _Pragma token,
which is sufficient to make things like _Pragma("GCC diagnostic ignored...")
operate correctly, but this still results in inferior diagnostics, since the
diagnostics do not point to the problematic tokens.  Further, if a diagnostic
is issued by libcpp during the lexing of the tokens, as opposed to being
issued by the frontend during the processing of the pragma, then the
patched-up location is not yet in place, and the user rather sees an invalid
location that is near to the location of the _Pragma string in some cases, or
potentially very far away, depending on the macro expansion history.  For
example:

=====
_Pragma("GCC diagnostic ignored \"oops")
=====

produces the diagnostic:

file.cpp:1:24: warning: missing terminating " character
    1 | _Pragma("GCC diagnostic ignored \"oops")
      |                        ^

with the caret in a nonsensical location, while this one:

=====
 #define S "GCC diagnostic ignored \"oops"
_Pragma(S)
=====

produces:

file.cpp:2:24: warning: missing terminating " character
    2 | _Pragma(S)
      |                        ^

with both the caret in a nonsensical location, and the actual relevant context
completely absent.

Fix this by assigning proper locations using the new LC_GEN type of linemap.
Now the tokens are given locations inside a generated content buffer, and the
macro expansion stack is modified to be aware that these tokens logically
belong to the "expansion" of the _Pragma directive. For the above examples we
now output:

======
In buffer generated from file.cpp:1:
<generated>:1:24: warning: missing terminating " character
    1 | GCC diagnostic ignored "oops
      |                        ^
file.cpp:1:1: note: in <_Pragma directive>
    1 | _Pragma("GCC diagnostic ignored \"oops")
      | ^~~~~~~
======

and

======
<generated>:1:24: warning: missing terminating " character
    1 | GCC diagnostic ignored "oops
      |                        ^
file.cpp:2:1: note: in <_Pragma directive>
    2 | _Pragma(S)
      | ^~~~~~~
======

So that carets are pointing to something meaningful and all relevant context
appears in the diagnostic.  For the second example, it would be nice if the
macro expansion also output "in expansion of macro S", however doing that for
a general case of macro expansions makes the logic very complicated, since it
has to be done after the fact when the macro maps have already been
constructed.  It doesn't seem worth it for this case, given that the _Pragma
string has already been output once on the first line.

gcc/ChangeLog:

	* tree-diagnostic.cc (maybe_unwind_expanded_macro_loc): Add awareness
	of _Pragma directive to the macro expansion trace.

libcpp/ChangeLog:

	* directives.cc (get_token_no_padding): Add argument to receive the
	virtual location of the token.
	(get__Pragma_string): Likewise.
	(do_pragma): Set pfile->directive_result->src_loc properly, it should
	not be a virtual location.
	(destringize_and_run): Update to provide proper locations for the
	_Pragma string tokens.  Support raw strings.
	(_cpp_do__Pragma): Adapt to changes to the helper functions.
	* errors.cc (cpp_diagnostic_at): Support
	cpp_reader::diagnostic_rebase_loc.
	(cpp_diagnostic_with_line): Likewise.
	* include/line-map.h (class rich_location): Add new member
	forget_cached_expanded_locations().
	* internal.h (struct _cpp__Pragma_state): Define new struct.
	(_cpp_rebase_diagnostic_location): Declare new function.
	(struct cpp_reader): Add diagnostic_rebase_loc member.
	(_cpp_push__Pragma_token_context): Declare new function.
	(_cpp_do__Pragma): Adjust prototype.
	* macro.cc (pragma_str): New static var.
	(builtin_macro): Adapt to new implementation of _Pragma processing.
	(_cpp_pop_context): Fix the logic for resetting
	pfile->top_most_macro_node, which previously was never triggered,
	although the error seems to have been harmless.
	(_cpp_push__Pragma_token_context): New function.
	(_cpp_rebase_diagnostic_location): New function.

gcc/c-family/ChangeLog:

	* c-ppoutput.cc (token_streamer::stream): Pass the virtual location of
	the _Pragma token to maybe_print_line(), not the spelling location.

libgomp/ChangeLog:

	* testsuite/libgomp.oacc-c-c++-common/reduction-5.c: Adjust for new
	macro tracking output for _Pragma directives.
	* testsuite/libgomp.oacc-c-c++-common/vred2d-128.c: Likewise.

gcc/testsuite/ChangeLog:

	* c-c++-common/cpp/diagnostic-pragma-1.c: Adjust for new macro
	tracking output for _Pragma directives.
	* c-c++-common/cpp/pr57580.c: Likewise.
	* c-c++-common/gomp/pragma-3.c: Likewise.
	* c-c++-common/gomp/pragma-5.c: Likewise.
	* g++.dg/pch/operator-1.C: Likewise.
	* gcc.dg/cpp/pr28165.c: Likewise.
	* gcc.dg/cpp/pr35322.c: Likewise.
	* gcc.dg/dfp/pragma-float-const-decimal64-4.c: Likewise.
	* gcc.dg/dfp/pragma-float-const-decimal64-5.c: Likewise.
	* gcc.dg/dfp/pragma-float-const-decimal64-6.c: Likewise.
	* gcc.dg/gomp/macro-4.c: Likewise.
	* gcc.dg/pragma-message.c: Likewise.
	* c-c++-common/pragma-diag-17.c: New test.
	* c-c++-common/pragma-diag-18.c: New test.
	* g++.dg/cpp/pragma-raw-string.C: New test.
	* g++.dg/pch/LC_GEN-maps.C: New test.
	* g++.dg/pch/LC_GEN-maps.Hs: New test.
	* lib/prune.exp: Support pruning new _Pragma include trace.
---
 gcc/c-family/c-ppoutput.cc                    |   2 +-
 .../c-c++-common/cpp/diagnostic-pragma-1.c    |   1 +
 gcc/testsuite/c-c++-common/cpp/pr57580.c      |   2 +-
 gcc/testsuite/c-c++-common/gomp/pragma-3.c    |   3 +-
 gcc/testsuite/c-c++-common/gomp/pragma-5.c    |   3 +-
 gcc/testsuite/c-c++-common/pragma-diag-17.c   |  35 +++
 gcc/testsuite/c-c++-common/pragma-diag-18.c   |  18 ++
 gcc/testsuite/g++.dg/cpp/pragma-raw-string.C  |  16 +
 gcc/testsuite/g++.dg/pch/LC_GEN-maps.C        |  20 ++
 gcc/testsuite/g++.dg/pch/LC_GEN-maps.Hs       |   5 +
 gcc/testsuite/g++.dg/pch/operator-1.C         |   1 +
 gcc/testsuite/gcc.dg/cpp/pr28165.c            |   1 +
 gcc/testsuite/gcc.dg/cpp/pr35322.c            |   1 +
 .../dfp/pragma-float-const-decimal64-4.c      |   1 +
 .../dfp/pragma-float-const-decimal64-5.c      |   2 +-
 .../dfp/pragma-float-const-decimal64-6.c      |   2 +-
 gcc/testsuite/gcc.dg/gomp/macro-4.c           |   2 +-
 gcc/testsuite/gcc.dg/pragma-message.c         |   3 +-
 gcc/testsuite/lib/prune.exp                   |   1 +
 gcc/tree-diagnostic.cc                        |  18 +-
 libcpp/directives.cc                          | 279 ++++++++++++------
 libcpp/errors.cc                              |  16 +-
 libcpp/include/line-map.h                     |   1 +
 libcpp/internal.h                             |  32 +-
 libcpp/macro.cc                               | 126 +++++++-
 .../libgomp.oacc-c-c++-common/reduction-5.c   |   3 +-
 .../libgomp.oacc-c-c++-common/vred2d-128.c    |  40 ++-
 27 files changed, 492 insertions(+), 142 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/pragma-diag-17.c
 create mode 100644 gcc/testsuite/c-c++-common/pragma-diag-18.c
 create mode 100644 gcc/testsuite/g++.dg/cpp/pragma-raw-string.C
 create mode 100644 gcc/testsuite/g++.dg/pch/LC_GEN-maps.C
 create mode 100644 gcc/testsuite/g++.dg/pch/LC_GEN-maps.Hs
diff mbox series

Patch

diff --git a/gcc/c-family/c-ppoutput.cc b/gcc/c-family/c-ppoutput.cc
index 6e054358e9e..ec607f67a75 100644
--- a/gcc/c-family/c-ppoutput.cc
+++ b/gcc/c-family/c-ppoutput.cc
@@ -280,7 +280,7 @@  token_streamer::stream (cpp_reader *pfile, const cpp_token *token,
 	  const char *space;
 	  const char *name;
 
-	  line_marker_emitted = maybe_print_line (token->src_loc);
+	  line_marker_emitted = maybe_print_line (loc);
 	  fputs ("#pragma ", print.outf);
 	  c_pp_lookup_pragma (token->val.pragma, &space, &name);
 	  if (space)
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 9867c94a8dd..801c93935b8 100644
--- a/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c
+++ b/gcc/testsuite/c-c++-common/cpp/diagnostic-pragma-1.c
@@ -1,4 +1,5 @@ 
 // { dg-do compile }
+// { dg-additional-options "-ftrack-macro-expansion=0" }
 
 #pragma GCC warning "warn-a" // { dg-warning warn-a }
 #pragma GCC error "err-b" // { dg-error err-b }
diff --git a/gcc/testsuite/c-c++-common/cpp/pr57580.c b/gcc/testsuite/c-c++-common/cpp/pr57580.c
index e77462b20de..b0e54d876d6 100644
--- a/gcc/testsuite/c-c++-common/cpp/pr57580.c
+++ b/gcc/testsuite/c-c++-common/cpp/pr57580.c
@@ -1,6 +1,6 @@ 
 /* PR preprocessor/57580 */
 /* { dg-do compile } */
-/* { dg-options "-save-temps" } */
+/* { dg-options "-save-temps -ftrack-macro-expansion=0" } */
 
 #define MSG 	\
   _Pragma("message(\"message0\")")	\
diff --git a/gcc/testsuite/c-c++-common/gomp/pragma-3.c b/gcc/testsuite/c-c++-common/gomp/pragma-3.c
index 3e1b2111c3d..e0cffb8aeea 100644
--- a/gcc/testsuite/c-c++-common/gomp/pragma-3.c
+++ b/gcc/testsuite/c-c++-common/gomp/pragma-3.c
@@ -8,7 +8,8 @@  void
 f (void)
 {
   const char *str = outer(inner(1,2)); /* { dg-line str_location } */
-  /* { dg-warning "35:'pragma omp error' encountered: Test" "" { target *-*-* } inner_location }
+  /* { dg-warning "1:'pragma omp error' encountered: Test" "" { target *-*-* } 1 }
+     { dg-note "35: in <_Pragma directive>" "" { target *-*-* } inner_location }
      { dg-note "20:in expansion of macro 'inner'" "" { target *-*-* } outer_location }
      { dg-note "21:in expansion of macro 'outer'" "" { target *-*-* } str_location } */
 }
diff --git a/gcc/testsuite/c-c++-common/gomp/pragma-5.c b/gcc/testsuite/c-c++-common/gomp/pragma-5.c
index 173c25e803a..787a334882d 100644
--- a/gcc/testsuite/c-c++-common/gomp/pragma-5.c
+++ b/gcc/testsuite/c-c++-common/gomp/pragma-5.c
@@ -8,7 +8,8 @@  void
 f (void)
 {
   const char *str = outer(inner(1,2)); /* { dg-line str_location } */
-  /* { dg-warning "35:'pragma omp error' encountered: Test" "" { target *-*-* } inner_location }
+  /* { dg-warning "4:'pragma omp error' encountered: Test" "" { target *-*-* } 1 }
+     { dg-note "35:in <_Pragma directive>" "" { target *-*-*} inner_location }
      { dg-note "20:in expansion of macro 'inner'" "" { target *-*-* } outer_location }
      { dg-note "21:in expansion of macro 'outer'" "" { target *-*-* } str_location } */
 }
diff --git a/gcc/testsuite/c-c++-common/pragma-diag-17.c b/gcc/testsuite/c-c++-common/pragma-diag-17.c
new file mode 100644
index 00000000000..b9539c9598b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/pragma-diag-17.c
@@ -0,0 +1,35 @@ 
+/* Test virtual location aspects of _Pragmas, when an error is reported after
+   lexing the tokens from the _Pragma string.  */
+/* { dg-additional-options "-Wpragmas -Wunknown-pragmas" } */
+
+_Pragma("GCC diagnostic ignored \"oops1\"") /* { dg-note {1:in <_Pragma directive>} } */
+/* { dg-warning {24:'oops1' is not an option} "" { target *-*-* } 1 } */
+
+#define S2 "GCC diagnostic ignored \"oops2\""
+_Pragma(S2) /* { dg-note {1:in <_Pragma directive>} } */
+/* { dg-warning {24:'oops2' is not an option} "" { target *-*-* } 1 } */
+
+#define PP(x) _Pragma(x) /* { dg-note {15:in <_Pragma directive>} } */
+PP("GCC diagnostic ignored \"oops3\"") /* { dg-note {1:in expansion of macro 'PP'} } */
+/* { dg-warning {24:'oops3' is not an option} "" { target *-*-* } 1 } */
+
+#define X4 _Pragma("GCC diagnostic ignored \"oops4\"") /* { dg-note {12:in <_Pragma directive>} } */
+#define Y4 X4 /* { dg-note {12:in expansion of macro 'X4'} } */
+Y4 /* { dg-note {1:in expansion of macro 'Y4'} } */
+/* { dg-warning {24:'oops4' is not an option} "" { target *-*-* } 1 } */
+
+#define P5 _Pragma /* { dg-note {12:in <_Pragma directive>} } */
+#define S5 "GCC diagnostic ignored \"oops5\""
+#define Y5 P5(S5) /* { dg-note {12:in expansion of macro 'P5'} } */
+Y5 /* { dg-note {1:in expansion of macro 'Y5'} } */
+/* { dg-warning {24:'oops5' is not an option} "" { target *-*-* } 1 } */
+
+#define P6 _Pragma /* { dg-note {12:in <_Pragma directive>} } */
+#define X6 P6("GCC diagnostic ignored \"oops6\"") /* { dg-note {12:in expansion of macro 'P6'} } */
+X6 /* { dg-note {1:in expansion of macro 'X6'} } */
+/* { dg-warning {24:'oops6' is not an option} "" { target *-*-* } 1 } */
+
+_Pragma(__DATE__) /* { dg-warning {-:[-Wunknown-pragmas]} } */
+
+_Pragma("once") /* { dg-note {1:in <_Pragma directive>} } */
+/* { dg-warning {#pragma once in main file} "" { target *-*-*} 1 } */
diff --git a/gcc/testsuite/c-c++-common/pragma-diag-18.c b/gcc/testsuite/c-c++-common/pragma-diag-18.c
new file mode 100644
index 00000000000..5de0fbcb8f1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/pragma-diag-18.c
@@ -0,0 +1,18 @@ 
+/* Test virtual location aspects of _Pragmas, when an error is reported during
+   lexing of the _Pragma string itself or of the tokens within it.  */
+/* { dg-additional-options "-Wpragmas" } */
+
+#define X1 "\""
+_Pragma(X1) /* { dg-note {1:in <_Pragma directive>} } */
+/* { dg-warning {1:missing terminating " character} "" { target *-*-* } 1 } */
+
+#define X2a _Pragma("GCC warning \"hello\"") ( /* { dg-note {13:in <_Pragma directive>} } */
+#define X2b "GCC warning \"goodbye\"" )
+_Pragma X2a X2b /* { dg-note {9:in expansion of macro 'X2a'} } */
+/* { dg-note {1:in <_Pragma directive>} "" { target *-*-* } .-1 } */
+/* { dg-warning {13:hello} "" { target *-*-* } 1 } */
+/* { dg-warning {13:goodbye} "" { target *-*-* } 1 } */
+
+_Pragma() /* { dg-error {9:_Pragma takes a parenthesized string literal} } */
+/* { dg-note {1:in <_Pragma directive>} "" { target *-*-* } .-1 } */
+/* { dg-error {at end of input|'_Pragma' does not name a type} "" { target *-*-* } .-2 } */
diff --git a/gcc/testsuite/g++.dg/cpp/pragma-raw-string.C b/gcc/testsuite/g++.dg/cpp/pragma-raw-string.C
new file mode 100644
index 00000000000..5a495aadeec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp/pragma-raw-string.C
@@ -0,0 +1,16 @@ 
+/* Test that _Pragma with a raw string works correctly.  */
+/* { dg-do compile { target c++11 } } */
+/* { dg-additional-options "-Wunused-variable -Wpragmas" } */
+
+_Pragma(R"delim(GCC diagnostic push)delim")
+_Pragma(R"(GCC diagnostic ignored "-Wunused-variable")")
+void f1 () { int i; }
+_Pragma(R"(GCC diagnostic pop)")
+void f2 () { int i; } /* { dg-warning {18:-Wunused-variable} } */
+
+/* Make sure lines stay in sync if there is an embedded newline too.  */
+_Pragma(R"xyz(GCC diagnostic ignored R"(two
+line option?)")xyz")
+/* { dg-note {1:in <_Pragma directive>} "" { target *-*-* } .-2 } */
+/* { dg-warning {24:unknown option} "" { target *-*-* } 1 } */
+void f3 () { int i; } /* { dg-warning {18:-Wunused-variable} } */
diff --git a/gcc/testsuite/g++.dg/pch/LC_GEN-maps.C b/gcc/testsuite/g++.dg/pch/LC_GEN-maps.C
new file mode 100644
index 00000000000..c21bce29bd2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/LC_GEN-maps.C
@@ -0,0 +1,20 @@ 
+#include "LC_GEN-maps.H"
+
+/* The LC_GEN map was written to the PCH, but there is not currently a way to
+   observe that fact in normal user code.  Let's try to test it anyway, using
+   -fdump-internal-locations to inspect the line_maps object we received from
+   the PCH.  */
+
+/* { dg-additional-options -fdump-internal-locations } */
+/* { dg-allow-blank-lines-in-output "" } */
+
+/* These regexps themselves will also appear in the output of
+   -fdump-internal-locations, so we need to make sure they contain at least
+   some regexp special characters, even if not strictly necessary, so they
+   match the intended text only, and not themselves.  Also, we make the second
+   one intentionally match the whole output if it maches anything.  We could
+   use dg-excess-errors instead, but that outputs XFAILS which are not really
+   helpful for this test.  */
+
+/* { dg-regexp {reason: . \(LC_GEN\)} } */
+/* { dg-regexp {(.|[\n\r])*data: this string should end up in the "PCH"(.|[\n\r])*} } */
diff --git a/gcc/testsuite/g++.dg/pch/LC_GEN-maps.Hs b/gcc/testsuite/g++.dg/pch/LC_GEN-maps.Hs
new file mode 100644
index 00000000000..76eefa7d1ae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/LC_GEN-maps.Hs
@@ -0,0 +1,5 @@ 
+/* Evaluating the _Pragma directive here creates an LC_GEN map in the
+   line_maps object that will be stored in the PCH.  The test will make sure
+   that the buffer holding the de-stringified _Pragma string contents makes
+   its way there.  */
+_Pragma("this string should end up in the \"PCH\"")
diff --git a/gcc/testsuite/g++.dg/pch/operator-1.C b/gcc/testsuite/g++.dg/pch/operator-1.C
index 290b5f7ab21..bf1c8b07bdb 100644
--- a/gcc/testsuite/g++.dg/pch/operator-1.C
+++ b/gcc/testsuite/g++.dg/pch/operator-1.C
@@ -1,2 +1,3 @@ 
+/* { dg-additional-options "-ftrack-macro-expansion=0" } */
 #include "operator-1.H"
 int main(void){ major(0);} /* { dg-warning "Did not Work" } */
diff --git a/gcc/testsuite/gcc.dg/cpp/pr28165.c b/gcc/testsuite/gcc.dg/cpp/pr28165.c
index 71c7c1dba46..3e5e49ffa01 100644
--- a/gcc/testsuite/gcc.dg/cpp/pr28165.c
+++ b/gcc/testsuite/gcc.dg/cpp/pr28165.c
@@ -2,5 +2,6 @@ 
 /* PR preprocessor/28165 */
 
 /* { dg-do preprocess } */
+/* { dg-additional-options "-ftrack-macro-expansion=0" } */
 #pragma GCC system_header   /* { dg-warning "system_header" "ignored" } */
 _Pragma ("GCC system_header")   /* { dg-warning "system_header" "ignored" } */
diff --git a/gcc/testsuite/gcc.dg/cpp/pr35322.c b/gcc/testsuite/gcc.dg/cpp/pr35322.c
index 1af9605eac6..5bd5f69b73d 100644
--- a/gcc/testsuite/gcc.dg/cpp/pr35322.c
+++ b/gcc/testsuite/gcc.dg/cpp/pr35322.c
@@ -1,4 +1,5 @@ 
 /* Test case for PR 35322 -- _Pragma ICE.  */
 
 /* { dg-do preprocess } */
+/* { dg-additional-options "-ftrack-macro-expansion=0" } */
 _Pragma("GCC dependency") /* { dg-error "#pragma dependency expects" } */
diff --git a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-4.c b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-4.c
index af0398daf79..42fc28a4384 100644
--- a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-4.c
+++ b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-4.c
@@ -1,4 +1,5 @@ 
 /* { dg-do compile } */
+/* { dg-additional-options -ftrack-macro-expansion=0 } */
 
 /* N1312 7.1.1: The FLOAT_CONST_DECIMAL64 pragma.
    C99 6.4.4.2a (New).
diff --git a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-5.c b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-5.c
index 75e9525dda0..3aefede7b5d 100644
--- a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-5.c
+++ b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-5.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-std=c99 -pedantic" } */
+/* { dg-options "-std=c99 -pedantic -ftrack-macro-expansion=0" } */
 
 /* N1312 7.1.1: The FLOAT_CONST_DECIMAL64 pragma.
    C99 6.4.4.2a (New).
diff --git a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-6.c b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-6.c
index 03c1715bee6..6d70ce2bb8d 100644
--- a/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-6.c
+++ b/gcc/testsuite/gcc.dg/dfp/pragma-float-const-decimal64-6.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-std=c99 -pedantic-errors" } */
+/* { dg-options "-std=c99 -pedantic-errors -ftrack-macro-expansion=0" } */
 
 /* N1312 7.1.1: The FLOAT_CONST_DECIMAL64 pragma.
    C99 6.4.4.2a (New).
diff --git a/gcc/testsuite/gcc.dg/gomp/macro-4.c b/gcc/testsuite/gcc.dg/gomp/macro-4.c
index a4ed9a3980a..c6817d40125 100644
--- a/gcc/testsuite/gcc.dg/gomp/macro-4.c
+++ b/gcc/testsuite/gcc.dg/gomp/macro-4.c
@@ -1,6 +1,6 @@ 
 /* PR preprocessor/27746 */
 /* { dg-do compile } */
-/* { dg-options "-fopenmp -Wunknown-pragmas" } */
+/* { dg-options "-fopenmp -Wunknown-pragmas -ftrack-macro-expansion=0" } */
 
 #define p		_Pragma ("omp parallel")
 #define omp_p		_Pragma ("omp p")
diff --git a/gcc/testsuite/gcc.dg/pragma-message.c b/gcc/testsuite/gcc.dg/pragma-message.c
index 1b7cf09de0a..72fb0da6f44 100644
--- a/gcc/testsuite/gcc.dg/pragma-message.c
+++ b/gcc/testsuite/gcc.dg/pragma-message.c
@@ -45,8 +45,9 @@ 
 #define DO_PRAGMA(x) _Pragma (#x) /* { dg-line pragma_loc1 } */
 #define TODO(x) DO_PRAGMA(message ("TODO - " #x)) /* { dg-line pragma_loc2 } */
 TODO(Okay 4) /* { dg-message "in expansion of macro 'TODO'" } */
-/* { dg-message "TODO - Okay 4" "test4.1" { target *-*-* } pragma_loc1 } */
+/* { dg-message "1:TODO - Okay 4" "test4.1" { target *-*-* } 1 } */
 /* { dg-message "in expansion of macro 'DO_PRAGMA'" "test4.2" { target *-*-* } pragma_loc2 } */
+/* { dg-note {in <_Pragma directive>} "test4.3" { target *-*-* } pragma_loc1 } */
 
 #if 0
 #pragma message ("Not printed")
diff --git a/gcc/testsuite/lib/prune.exp b/gcc/testsuite/lib/prune.exp
index 04c6a1dd7a1..a5788842652 100644
--- a/gcc/testsuite/lib/prune.exp
+++ b/gcc/testsuite/lib/prune.exp
@@ -54,6 +54,7 @@  proc prune_gcc_output { text } {
 
     # Diagnostic inclusion stack
     regsub -all "(^|\n)(In file)?\[ \]+included from \[^\n\]*" $text "" text
+    regsub -all "(^|\n)In buffer generated from \[^\n\]*" $text "" text
     regsub -all "(^|\n)\[ \]+from \[^\n\]*" $text "" text
     regsub -all "(^|\n)(In|of) module( \[^\n \]*,)? imported at \[^\n\]*" $text "" text
 
diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc
index 731e3559cd8..fd2773f3d8a 100644
--- a/gcc/tree-diagnostic.cc
+++ b/gcc/tree-diagnostic.cc
@@ -203,9 +203,12 @@  maybe_unwind_expanded_macro_loc (diagnostic_context *context,
 	const int resolved_def_loc_line = SOURCE_LINE (m, l0);
         if (ix == 0 && saved_location_line != resolved_def_loc_line)
           {
-            diagnostic_append_note (context, resolved_def_loc, 
-                                    "in definition of macro %qs",
-                                    linemap_map_get_macro_name (iter->map));
+	    const char *name = linemap_map_get_macro_name (iter->map);
+	    if (*name == '<')
+	      diagnostic_append_note (context, resolved_def_loc, "in %s", name);
+	    else
+	      diagnostic_append_note (context, resolved_def_loc,
+				      "in definition of macro %qs", name);
             /* At this step, as we've printed the context of the macro
                definition, we don't want to print the context of its
                expansion, otherwise, it'd be redundant.  */
@@ -220,9 +223,12 @@  maybe_unwind_expanded_macro_loc (diagnostic_context *context,
                                     MACRO_MAP_EXPANSION_POINT_LOCATION (iter->map),
                                     LRK_MACRO_DEFINITION_LOCATION, NULL);
 
-        diagnostic_append_note (context, resolved_exp_loc, 
-                                "in expansion of macro %qs",
-                                linemap_map_get_macro_name (iter->map));
+	const char *name = linemap_map_get_macro_name (iter->map);
+	if (*name == '<')
+	  diagnostic_append_note (context, resolved_exp_loc, "in %s", name);
+	else
+	  diagnostic_append_note (context, resolved_exp_loc,
+				  "in expansion of macro %qs", name);
       }
 }
 
diff --git a/libcpp/directives.cc b/libcpp/directives.cc
index a29f4abc3cc..c9cb8ad7e6b 100644
--- a/libcpp/directives.cc
+++ b/libcpp/directives.cc
@@ -127,10 +127,10 @@  static void do_pragma_warning_or_error (cpp_reader *, bool error);
 static void do_pragma_warning (cpp_reader *);
 static void do_pragma_error (cpp_reader *);
 static void do_linemarker (cpp_reader *);
-static const cpp_token *get_token_no_padding (cpp_reader *);
-static const cpp_token *get__Pragma_string (cpp_reader *);
-static void destringize_and_run (cpp_reader *, const cpp_string *,
-				 location_t);
+static const cpp_token *get_token_no_padding (cpp_reader *,
+					      location_t * = nullptr);
+static const cpp_token *get__Pragma_string (cpp_reader *,
+					    location_t * = nullptr);
 static bool parse_answer (cpp_reader *, int, location_t, cpp_macro **);
 static cpp_hashnode *parse_assertion (cpp_reader *, int, cpp_macro **);
 static cpp_macro **find_answer (cpp_hashnode *, const cpp_macro *);
@@ -1502,14 +1502,12 @@  do_pragma (cpp_reader *pfile)
 {
   const struct pragma_entry *p = NULL;
   const cpp_token *token, *pragma_token;
-  location_t pragma_token_virt_loc = 0;
   cpp_token ns_token;
   unsigned int count = 1;
 
   pfile->state.prevent_expansion++;
 
-  pragma_token = token = cpp_get_token_with_location (pfile,
-						      &pragma_token_virt_loc);
+  pragma_token = token = cpp_get_token (pfile);
   ns_token = *token;
   if (token->type == CPP_NAME)
     {
@@ -1535,7 +1533,7 @@  do_pragma (cpp_reader *pfile)
     {
       if (p->is_deferred)
 	{
-	  pfile->directive_result.src_loc = pragma_token_virt_loc;
+	  pfile->directive_result.src_loc = pragma_token->src_loc;
 	  pfile->directive_result.type = CPP_PRAGMA;
 	  pfile->directive_result.flags = pragma_token->flags;
 	  pfile->directive_result.val.pragma = p->u.ident;
@@ -1828,11 +1826,11 @@  do_pragma_error (cpp_reader *pfile)
 
 /* Get a token but skip padding.  */
 static const cpp_token *
-get_token_no_padding (cpp_reader *pfile)
+get_token_no_padding (cpp_reader *pfile, location_t *virt_loc)
 {
   for (;;)
     {
-      const cpp_token *result = cpp_get_token (pfile);
+      const cpp_token *result = cpp_get_token_with_location (pfile, virt_loc);
       if (result->type != CPP_PADDING)
 	return result;
     }
@@ -1841,7 +1839,7 @@  get_token_no_padding (cpp_reader *pfile)
 /* Check syntax is "(string-literal)".  Returns the string on success,
    or NULL on failure.  */
 static const cpp_token *
-get__Pragma_string (cpp_reader *pfile)
+get__Pragma_string (cpp_reader *pfile, location_t *string_virt_loc)
 {
   const cpp_token *string;
   const cpp_token *paren;
@@ -1852,7 +1850,7 @@  get__Pragma_string (cpp_reader *pfile)
   if (paren->type != CPP_OPEN_PAREN)
     return NULL;
 
-  string = get_token_no_padding (pfile);
+  string = get_token_no_padding (pfile, string_virt_loc);
   if (string->type == CPP_EOF)
     _cpp_backup_tokens (pfile, 1);
   if (string->type != CPP_STRING && string->type != CPP_WSTRING
@@ -1872,55 +1870,105 @@  get__Pragma_string (cpp_reader *pfile)
 /* Destringize IN into a temporary buffer, by removing the first \ of
    \" and \\ sequences, and process the result as a #pragma directive.  */
 static void
-destringize_and_run (cpp_reader *pfile, const cpp_string *in,
-		     location_t expansion_loc)
-{
-  const unsigned char *src, *limit;
-  char *dest, *result;
-  cpp_context *saved_context;
-  cpp_token *saved_cur_token;
-  tokenrun *saved_cur_run;
-  cpp_token *toks;
-  int count;
-  const struct directive *save_directive;
-
-  dest = result = (char *) alloca (in->len - 1);
-  src = in->text + 1 + (in->text[0] == 'L');
-  limit = in->text + in->len - 1;
-  while (src < limit)
+destringize_and_run (cpp_reader *pfile, _cpp__Pragma_state *pstate)
+{
+  uchar *dest, *result;
+
+  /* Determine where the data starts, and what kind of string it is.  */
+  const cpp_string *const in = &pstate->string_tok->val.str;
+  const uchar *src = in->text;
+  bool is_raw_string = false;
+  for (;;)
     {
-      /* We know there is a character following the backslash.  */
-      if (*src == '\\' && (src[1] == '\\' || src[1] == '"'))
-	src++;
-      *dest++ = *src++;
+      switch (*src++)
+	{
+	case '\"': break;
+	case 'R': is_raw_string = true; continue;
+	case '\0': gcc_assert (false);
+	default: continue;
+	}
+      break;
     }
-  *dest = '\n';
 
-  /* Ugh; an awful kludge.  We are really not set up to be lexing
-     tokens when in the middle of a macro expansion.  Use a new
-     context to force cpp_get_token to lex, and so skip_rest_of_line
-     doesn't go beyond the end of the text.  Also, remember the
-     current lexing position so we can return to it later.
+  /* If we were given a raw string literal, we don't need to destringize it,
+     but we do need to strip off the prefix and the suffix.  */
+  if (is_raw_string)
+    {
+      cpp_string buf;
+      const bool ok
+	= cpp_interpret_string_notranslate (pfile, in, 1, &buf, CPP_STRING);
+      gcc_assert (ok);
 
-     Something like line-at-a-time lexing should remove the need for
-     this.  */
-  saved_context = pfile->context;
-  saved_cur_token = pfile->cur_token;
-  saved_cur_run = pfile->cur_run;
+      /* BUF.TEXT ends with a terminating null (which is counted in BUF.LEN).
+	 We want to end with a newline as required by cpp_push_buffer.  While it
+	 is not strictly necessary to null terminate our buffer, it is useful to
+	 do so for safety, so we reserve one extra byte.  The \n\0 sequence is
+	 appended after the else block.  */
+      result = _cpp_unaligned_alloc (pfile, buf.len + 1);
+      memcpy (result, buf.text, buf.len - 1);
+      dest = result + (buf.len - 1);
+      XDELETEVEC (buf.text);
+    }
+  else
+    {
+      const auto last_ptr = in->text + in->len - 1;
+      /* +2 for the trailing \n\0 as above.  */
+      dest = result = _cpp_unaligned_alloc (pfile, last_ptr - src + 1 + 2);
+      while (src < last_ptr)
+	{
+	  /* We know there is a character following the backslash.  */
+	  if (*src == '\\' && (src[1] == '\\' || src[1] == '"'))
+	    src++;
+	  *dest++ = *src++;
+	}
+    }
+  *dest++ = '\n';
+  *dest++ = '\0';
 
-  pfile->context = XCNEW (cpp_context);
+  /* We will now ask PFILE to interrupt what it was doing (obtaining tokens
+     either from the main context via lexing, or from a macro context), and get
+     tokens from the string argument instead.  We create a new isolated
+     cpp_context so that cpp_get_token will think it is working on the main
+     buffer and call cpp_lex_token accordingly.  Save all the relevant state so
+     we can return to the previous task once that is completed.
 
-  /* Inline run_directive, since we need to delay the _cpp_pop_buffer
-     until we've read all of the tokens that we want.  */
-  cpp_push_buffer (pfile, (const uchar *) result, dest - result,
-		   /* from_stage3 */ true);
-  /* ??? Antique Disgusting Hack.  What does this do?  */
-  if (pfile->buffer->prev)
-    pfile->buffer->file = pfile->buffer->prev->file;
+     Doing things this way is a bit of a kludge, but the alternative would be
+     to create a new context type to support lexing from a string, and that
+     would add overhead to every token parse, while _Pragma is relatively rarely
+     needed.  */
 
+  const auto saved_context = pfile->context;
+  const auto saved_cur_token = pfile->cur_token;
+  const auto saved_cur_run = pfile->cur_run;
+  pfile->context = XCNEW (cpp_context);
   start_directive (pfile);
+
+  /* Set up an LC_GEN line map to get valid locations for the tokens we are
+     about to lex.  We need to do this after calling start_directive, because
+     historically pfile->directive_line is what's been passed to
+     pfile->cb.def_pragma, and we are not proposing to change that now.  To
+     decide if we are in a system header or not, look at the location of the
+     _Pragma token.  So for instance if we have _Pragma(S) in the main file,
+     where S is a macro defined in a system header, we will decide we are not in
+     a system location.  */
+  const unsigned int buf_len = dest - result;
+  const int sysp = linemap_location_in_system_header_p (pfile->line_table,
+							pstate->pragma_loc);
+  linemap_add (pfile->line_table, LC_GEN, sysp, (const char *)result, 1,
+	       buf_len);
+  const auto col_hint = (uchar *) memchr (result, '\n', buf_len) - result;
+  linemap_line_start (pfile->line_table, 1, col_hint);
+
+  /* Push the buffer.  */
+  cpp_push_buffer (pfile, result, buf_len - 2, true);
+
+  /* This is needed to make _Pragma("once") work correctly, as it needs
+     pfile->buffer->file to be set to the current source file.  */
+  pfile->buffer->file = pfile->buffer->prev->file;
+
+  /* We are ready to start handling the directive as normal.  */
   _cpp_clean_line (pfile);
-  save_directive = pfile->directive;
+  const auto save_directive = pfile->directive;
   pfile->directive = &dtable[T_PRAGMA];
   do_pragma (pfile);
   if (pfile->directive_result.type == CPP_PRAGMA)
@@ -1929,80 +1977,123 @@  destringize_and_run (cpp_reader *pfile, const cpp_string *in,
   pfile->directive = save_directive;
 
   /* We always insert at least one token, the directive result.  It'll
-     either be a CPP_PADDING or a CPP_PRAGMA.  In the later case, we 
+     either be a CPP_PADDING or a CPP_PRAGMA.  In the latter case, we
      need to insert *all* of the tokens, including the CPP_PRAGMA_EOL.  */
 
   /* If we're not handling the pragma internally, read all of the tokens from
-     the string buffer now, while the string buffer is still installed.  */
-  /* ??? Note that the token buffer allocated here is leaked.  It's not clear
-     to me what the true lifespan of the tokens are.  It would appear that
-     the lifespan is the entire parse of the main input stream, in which case
-     this may not be wrong.  */
-  if (pfile->directive_result.type == CPP_PRAGMA)
-    {
-      int maxcount;
-
-      count = 1;
-      maxcount = 50;
-      toks = XNEWVEC (cpp_token, maxcount);
-      toks[0] = pfile->directive_result;
-      toks[0].src_loc = expansion_loc;
-
-      do
+     the string buffer now, while the string buffer is still installed, and then
+     push them as a new token context after.  This way, we can clean up the
+     temporarily modified state of the lexer now.  */
+
+  const bool is_deferred = (pfile->directive_result.type == CPP_PRAGMA);
+  if (is_deferred)
+    {
+      /* Using _cpp_buff allows us to arrange for this buffer to be freed when
+	 the new token context is popped, without adding any additional space
+	 overhead to the cpp_context structure.  In order to support
+	 track_macro_expansion==0, we need to store the cpp_token objects
+	 contiguously, and the virt locs separately.  (Note that these tokens
+	 may acquire a virtual loc here, in case the pragma allows macro
+	 expansion.  But they will not yet have virtual locs representing them
+	 as part of the expansion of the _Pragma directive; this will be handled
+	 later in _cpp_push__Pragma_token_context.  */
+      const size_t init_count = 50;
+      _cpp_buff *tok_buff
+	= _cpp_get_buff (pfile, init_count * sizeof (cpp_token));
+      _cpp_buff *loc_buff
+	= _cpp_get_buff (pfile, init_count * sizeof (location_t));
+
+      /* Remember the base buffs so we can chain the final loc buff after it
+	 once we are done collecting tokens.  */
+      const auto tok_buff0 = tok_buff;
+      pstate->buff_chain = &loc_buff->next;
+
+      /* DIRECTIVE_RESULT is the first token we return (a CPP_PRAGMA).  This
+	 location cannot result from macro expansion, so there is no virtual
+	 location to worry about.  */
+      auto tok_out = (cpp_token *) tok_buff->base;
+      *tok_out++ = pfile->directive_result;
+      auto loc_out = (location_t *) loc_buff->base;
+      *loc_out++ = pfile->directive_result.src_loc;
+      unsigned int ntoks = 1;
+
+      /* Finally get all the tokens.  */
+      for (;;)
 	{
-	  if (count == maxcount)
+	  if (tok_buff->limit - (uchar *)tok_out < (int)sizeof (cpp_token))
 	    {
-	      maxcount = maxcount * 3 / 2;
-	      toks = XRESIZEVEC (cpp_token, toks, maxcount);
+	      _cpp_extend_buff (pfile, &tok_buff,
+				tok_buff->limit - tok_buff->base);
+	      tok_out = ((cpp_token *)tok_buff->base) + ntoks;
 	    }
-	  toks[count] = *cpp_get_token (pfile);
-	  /* _Pragma is a builtin, so we're not within a macro-map, and so
-	     the token locations are set to bogus ordinary locations
-	     near to, but after that of the "_Pragma".
-	     Paper over this by setting them equal to the location of the
-	     _Pragma itself (PR preprocessor/69126).  */
-	  toks[count].src_loc = expansion_loc;
+
+	  if (loc_buff->limit - (uchar *)loc_out < (int)sizeof (location_t))
+	    {
+	      _cpp_extend_buff (pfile, &loc_buff,
+				loc_buff->limit - loc_buff->base);
+	      loc_out = ((location_t *)loc_buff->base) + ntoks;
+	    }
+
+	  const auto this_tok = tok_out;
+	  *tok_out++ = *cpp_get_token_with_location (pfile, loc_out++);
+	  ++ntoks;
+
 	  /* Macros have been already expanded by cpp_get_token
 	     if the pragma allowed expansion.  */
-	  toks[count++].flags |= NO_EXPAND;
+	  this_tok->flags |= NO_EXPAND;
+	  if (this_tok->type == CPP_PRAGMA_EOL)
+	    break;
 	}
-      while (toks[count-1].type != CPP_PRAGMA_EOL);
+
+      /* Finalize the buffers so they can be stored as one chain in a
+	 cpp_context and freed when that context is popped.  */
+      tok_buff0->next = loc_buff;
+      pstate->ntoks = ntoks;
+      pstate->tok_buff = tok_buff;
+      pstate->loc_buff = loc_buff;
     }
   else
     {
-      count = 1;
-      toks = &pfile->avoid_paste;
-
       /* If we handled the entire pragma internally, make sure we get the
 	 line number correct for the next token.  */
       if (pfile->cb.line_change)
 	pfile->cb.line_change (pfile, pfile->cur_token, false);
     }
 
-  /* Finish inlining run_directive.  */
+  /* Reset the old state before...  */
+  const auto map = linemap_add (pfile->line_table, LC_LEAVE, 0, nullptr, 0);
+  linemap_line_start
+    (pfile->line_table,
+     ORDINARY_MAP_STARTING_LINE_NUMBER (linemap_check_ordinary (map)),
+     127);
   pfile->buffer->file = NULL;
   _cpp_pop_buffer (pfile);
-
-  /* Reset the old macro state before ...  */
   XDELETE (pfile->context);
   pfile->context = saved_context;
   pfile->cur_token = saved_cur_token;
   pfile->cur_run = saved_cur_run;
 
-  /* ... inserting the new tokens we collected.  */
-  _cpp_push_token_context (pfile, NULL, toks, count);
+  /* ...inserting the new tokens we collected.  This is not a simple call to
+     _cpp_push_token_context, because we need to create virtual locations
+     for the tokens and push an extended token context to return them.  */
+  if (is_deferred)
+    _cpp_push__Pragma_token_context (pfile, pstate);
+  else
+    _cpp_push_token_context (pfile, nullptr, &pfile->avoid_paste, 1);
 }
 
+
 /* Handle the _Pragma operator.  Return 0 on error, 1 if ok.  */
+
 int
-_cpp_do__Pragma (cpp_reader *pfile, location_t expansion_loc)
+_cpp_do__Pragma (cpp_reader *pfile, _cpp__Pragma_state *pstate)
 {
-  const cpp_token *string = get__Pragma_string (pfile);
-  pfile->directive_result.type = CPP_PADDING;
+  pstate->string_tok = get__Pragma_string (pfile, &pstate->string_loc);
 
-  if (string)
+  pfile->directive_result.type = CPP_PADDING;
+  if (pstate->string_tok)
     {
-      destringize_and_run (pfile, &string->val.str, expansion_loc);
+      destringize_and_run (pfile, pstate);
       return 1;
     }
   cpp_error (pfile, CPP_DL_ERROR,
diff --git a/libcpp/errors.cc b/libcpp/errors.cc
index df5f8d6fa32..351c7bc17e8 100644
--- a/libcpp/errors.cc
+++ b/libcpp/errors.cc
@@ -60,13 +60,11 @@  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_rebase_loc)
+    _cpp_rebase_diagnostic_location (pfile, richloc);
+  return pfile->cb.diagnostic (pfile, level, reason, richloc, _(msgid), ap);
 }
 
 /* Print a diagnostic at the location of the previously lexed token.  */
@@ -197,16 +195,14 @@  cpp_diagnostic_with_line (cpp_reader * pfile, enum cpp_diagnostic_level level,
 			  location_t src_loc, unsigned int column,
 			  const char *msgid, va_list *ap)
 {
-  bool ret;
-  
   if (!pfile->cb.diagnostic)
     abort ();
   rich_location richloc (pfile->line_table, src_loc);
   if (column)
     richloc.override_column (column);
-  ret = pfile->cb.diagnostic (pfile, level, reason, &richloc, _(msgid), ap);
-
-  return ret;
+  if (pfile->diagnostic_rebase_loc)
+    _cpp_rebase_diagnostic_location (pfile, &richloc);
+  return pfile->cb.diagnostic (pfile, level, reason, &richloc, _(msgid), ap);
 }
 
 /* Print a warning or error, depending on the value of LEVEL.  */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 7bed55548c7..ff93d529e67 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1758,6 +1758,7 @@  class rich_location
   location_range *get_range (unsigned int idx);
 
   expanded_location get_expanded_location (unsigned int idx);
+  void forget_cached_expanded_location () { m_have_expanded_location = false; }
 
   void
   override_column (int column);
diff --git a/libcpp/internal.h b/libcpp/internal.h
index badfd1b40da..fd0de49e302 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -292,6 +292,28 @@  struct lexer_state
   unsigned char ignore__Pragma;
 };
 
+/* Because handling of _Pragma bounces back and forth between macro.cc and
+   directives.cc, it is useful to keep the needed state in one place.  */
+struct _cpp__Pragma_state
+{
+  const cpp_token *string_tok; /* The token for the argument string.  */
+
+  /* These locations are the virtual locations returned by
+     cpp_get_token_with_location, if the relevant tokens came from macro
+     expansions.  */
+  location_t pragma_loc; /* Location of the _Pragma token.  */
+  location_t string_loc; /* Location of the string arg.  */
+
+  /* The tokens lexed from the _Pragma string.  */
+  unsigned int ntoks;
+  _cpp_buff *tok_buff;
+  _cpp_buff *loc_buff;
+  _cpp_buff **buff_chain;
+};
+
+/* In macro.cc, implements pstate->diagnostic_rebase_loc handling.  */
+void _cpp_rebase_diagnostic_location (cpp_reader *, rich_location *);
+
 /* Special nodes - identifiers with predefined significance.  */
 struct spec_nodes
 {
@@ -601,6 +623,12 @@  struct cpp_reader
      zero of said file.  */
   location_t main_loc;
 
+  /* Location from which we would like to pretend a given token was
+     macro-expanded, if a diagnostic is issued.  Useful for improving
+     _Pragma diagnostics.  */
+  location_t diagnostic_rebase_loc;
+  cpp_hashnode *diagnostic_rebase_node;
+
   /* Returns true iff we should warn about UTF-8 bidirectional control
      characters.  */
   bool warn_bidi_p () const
@@ -701,6 +729,8 @@  extern const unsigned char *_cpp_builtin_macro_text (cpp_reader *,
 extern int _cpp_warn_if_unused_macro (cpp_reader *, cpp_hashnode *, void *);
 extern void _cpp_push_token_context (cpp_reader *, cpp_hashnode *,
 				     const cpp_token *, unsigned int);
+extern void _cpp_push__Pragma_token_context (cpp_reader *,
+					     _cpp__Pragma_state *);
 extern void _cpp_backup_tokens_direct (cpp_reader *, unsigned int);
 
 /* In identifiers.cc */
@@ -772,7 +802,7 @@  extern int _cpp_handle_directive (cpp_reader *, bool);
 extern void _cpp_define_builtin (cpp_reader *, const char *);
 extern char ** _cpp_save_pragma_names (cpp_reader *);
 extern void _cpp_restore_pragma_names (cpp_reader *, char **);
-extern int _cpp_do__Pragma (cpp_reader *, location_t);
+extern int _cpp_do__Pragma (cpp_reader *, _cpp__Pragma_state *);
 extern void _cpp_init_directives (cpp_reader *);
 extern void _cpp_init_internal_pragmas (cpp_reader *);
 extern void _cpp_do_file_change (cpp_reader *, enum lc_reason, const char *,
diff --git a/libcpp/macro.cc b/libcpp/macro.cc
index 452e14a1e66..7591fab8632 100644
--- a/libcpp/macro.cc
+++ b/libcpp/macro.cc
@@ -93,6 +93,8 @@  struct macro_arg_saved_data {
 static const char *vaopt_paste_error =
   N_("'##' cannot appear at either end of __VA_OPT__");
 
+static const uchar pragma_str[] = N_("<_Pragma directive>");
+
 static void expand_arg (cpp_reader *, macro_arg *);
 
 /* A class for tracking __VA_OPT__ state while iterating over a
@@ -756,7 +758,31 @@  builtin_macro (cpp_reader *pfile, cpp_hashnode *node,
       if (pfile->state.in_directive || pfile->state.ignore__Pragma)
 	return 0;
 
-      return _cpp_do__Pragma (pfile, loc);
+      _cpp__Pragma_state pstate = {};
+      pstate.pragma_loc = loc;
+
+      /* The diagnostic_rebase stuff arranges that any diagnostics issued during
+	 lexing will point the user back to the _Pragma location.  */
+      const auto prev_rloc = pfile->diagnostic_rebase_loc;
+      const auto prev_rnode = pfile->diagnostic_rebase_node;
+      pfile->diagnostic_rebase_loc = loc;
+      pfile->diagnostic_rebase_node
+	= cpp_lookup (pfile, pragma_str, (sizeof pragma_str) - 1);
+
+      /* While lexing tokens, if we end up expanding some macros, we would
+	 like not to override top_most_macro_node; preserving it pointing
+	 to the _Pragma helps out the case of -ftrack-macro-expansion=0.
+	 Setting this flag causes in_macro_expansion_p to return TRUE,
+	 even though we are not technically in a macro context.  */
+      const bool prev_expand = pfile->about_to_expand_macro_p;
+      pfile->about_to_expand_macro_p = true;
+
+      /* Get the tokens, then reset everything back how it was.  */
+      const int res = _cpp_do__Pragma (pfile, &pstate);
+      pfile->about_to_expand_macro_p = prev_expand;
+      pfile->diagnostic_rebase_loc = prev_rloc;
+      pfile->diagnostic_rebase_node = prev_rnode;
+      return res;
     }
 
   buf = _cpp_builtin_macro_text (pfile, node, expand_loc);
@@ -2802,7 +2828,8 @@  _cpp_pop_context (cpp_reader *pfile)
 	  && macro_of_context (context->prev) != macro)
 	macro->flags &= ~NODE_DISABLED;
 
-      if (macro == pfile->top_most_macro_node && context->prev == NULL)
+      if (!pfile->about_to_expand_macro_p
+	  && context->prev == &pfile->base_context)
 	/* We are popping the context of the top-most macro node.  */
 	pfile->top_most_macro_node = NULL;
     }
@@ -2836,10 +2863,10 @@  reached_end_of_context (cpp_context *context)
 
 /* Consume the next token contained in the current context of PFILE,
    and return it in *TOKEN. It's "full location" is returned in
-   *LOCATION. If -ftrack-macro-location is in effeect, fFull location"
-   means the location encoding the locus of the token across macro
-   expansion; otherwise it's just is the "normal" location of the
-   token which (*TOKEN)->src_loc.  */
+   *LOCATION.  If -ftrack-macro-location is in effect, "full location"
+   means the virtual location encoding the locus of the token across macro
+   expansion; otherwise it's just the "normal" (spelling) location of the
+   token, which is (*TOKEN)->src_loc.  */
 static inline void
 consume_next_token_from_context (cpp_reader *pfile,
 				 const cpp_token ** token,
@@ -4129,3 +4156,90 @@  cpp_macro_definition (cpp_reader *pfile, cpp_hashnode *node,
   *buffer = '\0';
   return pfile->macro_buffer;
 }
+
+/* Handle the list of tokens lexed from a _Pragma string.  We need to create
+   virtual locations (reflecting the fact that these tokens are logically
+   within the expansion of the _Pragma string), and push an extended token
+   context.  */
+
+void
+_cpp_push__Pragma_token_context (cpp_reader *pfile,
+				 _cpp__Pragma_state *pstate)
+{
+  const auto node = cpp_lookup (pfile, pragma_str, (sizeof pragma_str) - 1);
+  const auto toks = (const cpp_token *) pstate->tok_buff->base;
+
+  /* If not tracking macro expansions, then just push a normal token context.
+     cpp_get_token () will return the user the location of the _Pragma
+     directive, so they will have a valid location for the _Pragma which is
+     outside the LC_GEN map.  */
+  if (!CPP_OPTION (pfile, track_macro_expansion))
+    {
+      _cpp_push_token_context (pfile, node, toks, pstate->ntoks);
+      /* Arrange to free the buffers when the context is popped.  */
+      pfile->context->buff = pstate->tok_buff;
+      return;
+    }
+
+  location_t *virt_locs = nullptr;
+  _cpp_buff *const macro_tokens = tokens_buff_new (pfile, pstate->ntoks,
+						   &virt_locs);
+  const auto map = linemap_enter_macro (pfile->line_table, node,
+					pstate->pragma_loc, pstate->ntoks);
+  const auto locs = (location_t *)pstate->loc_buff->base;
+  for (unsigned int i = 0; i != pstate->ntoks; ++i)
+    {
+      tokens_buff_add_token (macro_tokens, virt_locs, toks + i,
+			     locs[i], locs[i], map, i);
+    }
+
+  /* Chain tok_buff ahead of macro_tokens so both are freed together
+     when the context is popped.  pstate->buff_chain is the NEXT pointer
+     of the last buffer in the LOC_BUFF chain, so it looks like:
+     TOK_BUFF_1 -> ... -> TOK_BUFF_N -> ... -> LOC_BUFF_1 -> ... ->
+     LOC_BUFF_N -> MACRO_TOKENS_1 -> ... -> MACRO_TOKENS_N.  */
+  *pstate->buff_chain = macro_tokens;
+  push_extended_tokens_context (pfile, node, pstate->tok_buff, virt_locs,
+				(const cpp_token **) macro_tokens->base,
+				pstate->ntoks);
+}
+
+void
+_cpp_rebase_diagnostic_location (cpp_reader *pfile, rich_location *richloc)
+{
+  /* If we are here, it means a diagnostic is being generated while lexing
+     tokens outside a macro context, but pfile->diagnostic_rebase_loc indicates
+     a location from which we would like to pretend we are actually expanding a
+     macro.  This works around the fact that a macro map can only be generated
+     once we know how many tokens it will contain, but the number of tokens to
+     be lexed from, say, a _Pragma string, is not known ahead of time.  In the
+     case of _Pragma, _cpp_push__Pragma_token_context above handles creating the
+     proper macro map once all the tokens are available.  This function runs
+     earlier than that, while in the middle of lexing tokens, so it creates a
+     temporary macro map which serves only to improve the information content of
+     the diagnostic that's about to be generated.  */
+
+  const int nlocs = richloc->get_num_locations ();
+
+  if (CPP_OPTION (pfile, track_macro_expansion))
+    {
+      const auto map
+	= linemap_enter_macro (pfile->line_table, pfile->diagnostic_rebase_node,
+			       pfile->diagnostic_rebase_loc, nlocs);
+      for (int i = 0; i != nlocs; ++i)
+	{
+	  location_range& r = *richloc->get_range (i);
+	  r.m_loc = linemap_add_macro_token (map, i, r.m_loc, r.m_loc);
+	}
+    }
+  else
+    {
+      /* When not tracking macro expansion, then set the location to the
+	 expansion point for all tokens, which is what would be returned
+	 by cpp_get_token in the normal case.  */
+      for (int i = 0; i != nlocs; ++i)
+	richloc->get_range (i)->m_loc = pfile->invocation_location;
+    }
+
+  richloc->forget_cached_expanded_location ();
+}
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/reduction-5.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/reduction-5.c
index ddccfe89e73..f518915492d 100644
--- a/libgomp/testsuite/libgomp.oacc-c-c++-common/reduction-5.c
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/reduction-5.c
@@ -46,7 +46,8 @@  main (void)
   /* Nvptx targets require a vector_length or 32 in to allow spinlocks with
      gangs.  */
   check_reduction (num_workers (nw) vector_length (vl), worker); /* { dg-line check_reduction_loc } */
-  /* { dg-warning "22:region is vector partitioned but does not contain vector partitioned code" "" { target *-*-* } pragma_loc }
+  /* { dg-warning "1:region is vector partitioned but does not contain vector partitioned code" "" { target *-*-* } 1 }
+     { dg-note "22:in <_Pragma directive>" "" { target *-*-* xfail offloading_enabled} pragma_loc }
      { dg-note "1:in expansion of macro 'DO_PRAGMA'" "" { target *-*-* xfail offloading_enabled } DO_PRAGMA_loc }
      { dg-note "3:in expansion of macro 'check_reduction'" "" { target *-*-* xfail offloading_enabled } check_reduction_loc }
      TODO See PR101551 for 'offloading_enabled' XFAILs.  */
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/vred2d-128.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/vred2d-128.c
index 84e6d51670b..bd2567d96f8 100644
--- a/libgomp/testsuite/libgomp.oacc-c-c++-common/vred2d-128.c
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/vred2d-128.c
@@ -40,46 +40,54 @@  int a1[n], a2[n];
 
 gentest (test1, "acc parallel loop gang vector_length (128) firstprivate (t1, t2)",
 	 "acc loop vector reduction(+:t1) reduction(-:t2)")
-/* { dg-warning {'t1' is used uninitialized} {} { target *-*-* } outer }
+/* { dg-warning {'t1' is used uninitialized} {} { target *-*-* } 1 }
+   { dg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { dg-note {'t1' was declared here} {} { target *-*-* } vars }
-   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-4 }
+   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-5 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
-/* { dg-warning {'t2' is used uninitialized} {} { target *-*-* } outer }
+/* { dg-warning {'t2' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { dg-note {'t2' was declared here} {} { target *-*-* } vars }
-   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-8 }
+   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-10 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
 
 gentest (test2, "acc parallel loop gang vector_length (128) firstprivate (t1, t2)",
 	 "acc loop worker vector reduction(+:t1) reduction(-:t2)")
-/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t1' was declared here} {} { target *-*-* } vars }
-   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-4 }
+   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-5 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
-/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t2' was declared here} {} { target *-*-* } vars }
-   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-8 }
+   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-10 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
 
 gentest (test3, "acc parallel loop gang worker vector_length (128) firstprivate (t1, t2)",
 	 "acc loop vector reduction(+:t1) reduction(-:t2)")
-/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t1' was declared here} {} { target *-*-* } vars }
-   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-4 }
+   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-5 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
-/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t2' was declared here} {} { target *-*-* } vars }
-   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-8 }
+   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-10 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
 
 gentest (test4, "acc parallel loop firstprivate (t1, t2)",
 	 "acc loop reduction(+:t1) reduction(-:t2)")
-/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t1' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t1' was declared here} {} { target *-*-* } vars }
-   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-4 }
+   { dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-5 }
      TODO See PR101551 for 'offloading_enabled' differences.  */
-/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } outer }
+/* { DUPdg-warning {'t2' is used uninitialized} {} { target *-*-* } 1 }
+   { DUPdg-note {in <_Pragma directive>} {} { target { ! offloading_enabled } } outer }
    { DUP_dg-note {'t2' was declared here} {} { target *-*-* } vars }
-   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-8 }
+   { DUP_dg-note {in expansion of macro 'gentest'} {} { target { ! offloading_enabled } } .-10 }
      TODO See PR101551 for 'offloading_enabled' differences.  */