diff mbox

[1/7] enhance -Wformat to detect quoting problems (PR 80280 et al.)

Message ID e11821d0-9a7d-bc1b-d546-c73f81e17ed6@gmail.com
State New
Headers show

Commit Message

Martin Sebor May 8, 2017, 7:55 p.m. UTC
On 05/05/2017 11:49 AM, Joseph Myers wrote:
> On Thu, 4 May 2017, Martin Sebor wrote:
>
>> I like the flags2 idea.  I split up the initialization array to also
>> detect quoted %K, and unquoted %R and %r.  With that I ran into test
>> failures that took me a bit to debug.  It turns out that there's code
>> (a nasty hack, really) that makes assumptions about some of
>> the conversion specifiers.  I dealt with the failures by simplifying
>> the initialization code and removing the hack.
>
> This patch is OK.

Better testing exposed a couple of issues with the patch:

1) The init_dynamic_diag_info function depends on the order in which
    the identifiers it looks (e.g. tree_node) for are declared in
    the translation unit being compiled and in some instances wound
    up initializing the local_tree_type_node with void_type_node even
    though tree would subsequently be found to be declared.

2) The warning on %R/%r when used outside a quoted sequence was
    overfly restrictive.  There is code in cp/error.c that relies
    on that when printing the inlining context of a function.

I fixed both of these problems in the attached revision.  I've
also been running a script to build the targets in config-list.mk.
So far it's built just over 40 of the 199 targets and found one
quoting problem in a build of sparcv9-solaris2.11.  The build
fails due to an unrelated error (I opened bug 80673 for it), so
I can't say I've found anything that should block the patch from
checking in.

I'll keep running the script but unless you or someone else would
prefer I hold off until they all finish I'll go ahead and commit
the attached patch, without waiting for the build of the remaining
targets to complete, probably sometime in the second half of
the week.

Martin
diff mbox

Patch

PR translation/80280 - Missing closing quote (%>) c/semantics.c and c/c-typeck.c

gcc/c-family/ChangeLog:

	PR translation/80280
	* c-format.h (struct format_flag_spec): Add new member.
	(T89_T): New macro.
	* c-format.c (local_tree_type_node): New global.
	(printf_flag_specs, asm_fprintf_flag_spec): Initialize new data.
	(gcc_diag_flag_specs, scanf_flag_specs, strftime_flag_specs): Ditto.
	(strfmon_flag_specs): Likewise.
	(gcc_diag_char_table, gcc_cdiag_char_table): Split up specifiers
	with distinct quoting properties.
	(gcc_tdiag_char_table, gcc_cxxdiag_char_table): Same.
	(flag_chars_t::validate): Add argument and handle bad quoting.
	(check_format_info_main): Handle quoting problems.
	(init_dynamic_diag_info): Simplify.

gcc/testsuite/ChangeLog:

	PR translation/80280
	* gcc.dg/format/gcc_diag-10.c: New test.

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 400eb66..2dba062 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -53,6 +53,9 @@  struct function_format_info
   unsigned HOST_WIDE_INT first_arg_num;	/* number of first arg (zero for varargs) */
 };
 
+/* Initialized in init_dynamic_diag_info.  */
+static tree local_tree_type_node;
+
 static bool decode_format_attr (tree, function_format_info *, int);
 static int decode_format_type (const char *);
 
@@ -492,17 +495,17 @@  static const format_length_info gcc_gfc_length_specs[] =
 
 static const format_flag_spec printf_flag_specs[] =
 {
-  { ' ',  0, 0, N_("' ' flag"),        N_("the ' ' printf flag"),              STD_C89 },
-  { '+',  0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
-  { '#',  0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
-  { '0',  0, 0, N_("'0' flag"),        N_("the '0' printf flag"),              STD_C89 },
-  { '-',  0, 0, N_("'-' flag"),        N_("the '-' printf flag"),              STD_C89 },
-  { '\'', 0, 0, N_("''' flag"),        N_("the ''' printf flag"),              STD_EXT },
-  { 'I',  0, 0, N_("'I' flag"),        N_("the 'I' printf flag"),              STD_EXT },
-  { 'w',  0, 0, N_("field width"),     N_("field width in printf format"),     STD_C89 },
-  { 'p',  0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
-  { 'L',  0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { ' ',  0, 0, 0, N_("' ' flag"),        N_("the ' ' printf flag"),              STD_C89 },
+  { '+',  0, 0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
+  { '#',  0, 0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
+  { '0',  0, 0, 0, N_("'0' flag"),        N_("the '0' printf flag"),              STD_C89 },
+  { '-',  0, 0, 0, N_("'-' flag"),        N_("the '-' printf flag"),              STD_C89 },
+  { '\'', 0, 0, 0, N_("''' flag"),        N_("the ''' printf flag"),              STD_EXT },
+  { 'I',  0, 0, 0, N_("'I' flag"),        N_("the 'I' printf flag"),              STD_EXT },
+  { 'w',  0, 0, 0, N_("field width"),     N_("field width in printf format"),     STD_C89 },
+  { 'p',  0, 0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
+  { 'L',  0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 
@@ -516,15 +519,15 @@  static const format_flag_pair printf_flag_pairs[] =
 
 static const format_flag_spec asm_fprintf_flag_specs[] =
 {
-  { ' ',  0, 0, N_("' ' flag"),        N_("the ' ' printf flag"),              STD_C89 },
-  { '+',  0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
-  { '#',  0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
-  { '0',  0, 0, N_("'0' flag"),        N_("the '0' printf flag"),              STD_C89 },
-  { '-',  0, 0, N_("'-' flag"),        N_("the '-' printf flag"),              STD_C89 },
-  { 'w',  0, 0, N_("field width"),     N_("field width in printf format"),     STD_C89 },
-  { 'p',  0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
-  { 'L',  0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { ' ',  0, 0, 0, N_("' ' flag"),        N_("the ' ' printf flag"),              STD_C89 },
+  { '+',  0, 0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
+  { '#',  0, 0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
+  { '0',  0, 0, 0, N_("'0' flag"),        N_("the '0' printf flag"),              STD_C89 },
+  { '-',  0, 0, 0, N_("'-' flag"),        N_("the '-' printf flag"),              STD_C89 },
+  { 'w',  0, 0, 0, N_("field width"),     N_("field width in printf format"),     STD_C89 },
+  { 'p',  0, 0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
+  { 'L',  0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 static const format_flag_pair asm_fprintf_flag_pairs[] =
@@ -547,12 +550,12 @@  static const format_flag_pair gcc_diag_flag_pairs[] =
 
 static const format_flag_spec gcc_diag_flag_specs[] =
 {
-  { '+',  0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
-  { '#',  0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
-  { 'q',  0, 0, N_("'q' flag"),        N_("the 'q' diagnostic flag"),          STD_C89 },
-  { 'p',  0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
-  { 'L',  0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { '+',  0, 0, 0, N_("'+' flag"),        N_("the '+' printf flag"),              STD_C89 },
+  { '#',  0, 0, 0, N_("'#' flag"),        N_("the '#' printf flag"),              STD_C89 },
+  { 'q',  0, 0, 1, N_("'q' flag"),        N_("the 'q' diagnostic flag"),          STD_C89 },
+  { 'p',  0, 0, 0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
+  { 'L',  0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 #define gcc_tdiag_flag_specs gcc_diag_flag_specs
@@ -562,14 +565,14 @@  static const format_flag_spec gcc_diag_flag_specs[] =
 
 static const format_flag_spec scanf_flag_specs[] =
 {
-  { '*',  0, 0, N_("assignment suppression"), N_("the assignment suppression scanf feature"), STD_C89 },
-  { 'a',  0, 0, N_("'a' flag"),               N_("the 'a' scanf flag"),                       STD_EXT },
-  { 'm',  0, 0, N_("'m' flag"),               N_("the 'm' scanf flag"),                       STD_EXT },
-  { 'w',  0, 0, N_("field width"),            N_("field width in scanf format"),              STD_C89 },
-  { 'L',  0, 0, N_("length modifier"),        N_("length modifier in scanf format"),          STD_C89 },
-  { '\'', 0, 0, N_("''' flag"),               N_("the ''' scanf flag"),                       STD_EXT },
-  { 'I',  0, 0, N_("'I' flag"),               N_("the 'I' scanf flag"),                       STD_EXT },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { '*',  0, 0, 0, N_("assignment suppression"), N_("the assignment suppression scanf feature"), STD_C89 },
+  { 'a',  0, 0, 0, N_("'a' flag"),               N_("the 'a' scanf flag"),                       STD_EXT },
+  { 'm',  0, 0, 0, N_("'m' flag"),               N_("the 'm' scanf flag"),                       STD_EXT },
+  { 'w',  0, 0, 0, N_("field width"),            N_("field width in scanf format"),              STD_C89 },
+  { 'L',  0, 0, 0, N_("length modifier"),        N_("length modifier in scanf format"),          STD_C89 },
+  { '\'', 0, 0, 0, N_("''' flag"),               N_("the ''' scanf flag"),                       STD_EXT },
+  { 'I',  0, 0, 0, N_("'I' flag"),               N_("the 'I' scanf flag"),                       STD_EXT },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 
@@ -583,16 +586,16 @@  static const format_flag_pair scanf_flag_pairs[] =
 
 static const format_flag_spec strftime_flag_specs[] =
 {
-  { '_', 0,   0, N_("'_' flag"),     N_("the '_' strftime flag"),          STD_EXT },
-  { '-', 0,   0, N_("'-' flag"),     N_("the '-' strftime flag"),          STD_EXT },
-  { '0', 0,   0, N_("'0' flag"),     N_("the '0' strftime flag"),          STD_EXT },
-  { '^', 0,   0, N_("'^' flag"),     N_("the '^' strftime flag"),          STD_EXT },
-  { '#', 0,   0, N_("'#' flag"),     N_("the '#' strftime flag"),          STD_EXT },
-  { 'w', 0,   0, N_("field width"),  N_("field width in strftime format"), STD_EXT },
-  { 'E', 0,   0, N_("'E' modifier"), N_("the 'E' strftime modifier"),      STD_C99 },
-  { 'O', 0,   0, N_("'O' modifier"), N_("the 'O' strftime modifier"),      STD_C99 },
-  { 'O', 'o', 0, NULL,               N_("the 'O' modifier"),               STD_EXT },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { '_', 0,   0, 0, N_("'_' flag"),     N_("the '_' strftime flag"),          STD_EXT },
+  { '-', 0,   0, 0, N_("'-' flag"),     N_("the '-' strftime flag"),          STD_EXT },
+  { '0', 0,   0, 0, N_("'0' flag"),     N_("the '0' strftime flag"),          STD_EXT },
+  { '^', 0,   0, 0, N_("'^' flag"),     N_("the '^' strftime flag"),          STD_EXT },
+  { '#', 0,   0, 0, N_("'#' flag"),     N_("the '#' strftime flag"),          STD_EXT },
+  { 'w', 0,   0, 0, N_("field width"),  N_("field width in strftime format"), STD_EXT },
+  { 'E', 0,   0, 0, N_("'E' modifier"), N_("the 'E' strftime modifier"),      STD_C99 },
+  { 'O', 0,   0, 0, N_("'O' modifier"), N_("the 'O' strftime modifier"),      STD_C99 },
+  { 'O', 'o', 0, 0, NULL,               N_("the 'O' modifier"),               STD_EXT },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 
@@ -609,17 +612,17 @@  static const format_flag_pair strftime_flag_pairs[] =
 
 static const format_flag_spec strfmon_flag_specs[] =
 {
-  { '=',  0, 1, N_("fill character"),  N_("fill character in strfmon format"),  STD_C89 },
-  { '^',  0, 0, N_("'^' flag"),        N_("the '^' strfmon flag"),              STD_C89 },
-  { '+',  0, 0, N_("'+' flag"),        N_("the '+' strfmon flag"),              STD_C89 },
-  { '(',  0, 0, N_("'(' flag"),        N_("the '(' strfmon flag"),              STD_C89 },
-  { '!',  0, 0, N_("'!' flag"),        N_("the '!' strfmon flag"),              STD_C89 },
-  { '-',  0, 0, N_("'-' flag"),        N_("the '-' strfmon flag"),              STD_C89 },
-  { 'w',  0, 0, N_("field width"),     N_("field width in strfmon format"),     STD_C89 },
-  { '#',  0, 0, N_("left precision"),  N_("left precision in strfmon format"),  STD_C89 },
-  { 'p',  0, 0, N_("right precision"), N_("right precision in strfmon format"), STD_C89 },
-  { 'L',  0, 0, N_("length modifier"), N_("length modifier in strfmon format"), STD_C89 },
-  { 0, 0, 0, NULL, NULL, STD_C89 }
+  { '=',  0, 1, 0, N_("fill character"),  N_("fill character in strfmon format"),  STD_C89 },
+  { '^',  0, 0, 0, N_("'^' flag"),        N_("the '^' strfmon flag"),              STD_C89 },
+  { '+',  0, 0, 0, N_("'+' flag"),        N_("the '+' strfmon flag"),              STD_C89 },
+  { '(',  0, 0, 0, N_("'(' flag"),        N_("the '(' strfmon flag"),              STD_C89 },
+  { '!',  0, 0, 0, N_("'!' flag"),        N_("the '!' strfmon flag"),              STD_C89 },
+  { '-',  0, 0, 0, N_("'-' flag"),        N_("the '-' strfmon flag"),              STD_C89 },
+  { 'w',  0, 0, 0, N_("field width"),     N_("field width in strfmon format"),     STD_C89 },
+  { '#',  0, 0, 0, N_("left precision"),  N_("left precision in strfmon format"),  STD_C89 },
+  { 'p',  0, 0, 0, N_("right precision"), N_("right precision in strfmon format"), STD_C89 },
+  { 'L',  0, 0, 0, N_("length modifier"), N_("length modifier in strfmon format"), STD_C89 },
+  { 0, 0, 0, 0, NULL, NULL, STD_C89 }
 };
 
 static const format_flag_pair strfmon_flag_pairs[] =
@@ -685,10 +688,13 @@  static const format_char_info gcc_diag_char_table[] =
   /* Custom conversion specifiers.  */
 
   /* These will require a "tree" at runtime.  */
-  { "K",   0, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",    "",   NULL },
+  { "K",   1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "\"",   NULL },
 
-  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "cR",   NULL },
-  { "<>'R",0, STD_C89, NOARGUMENTS, "",      "",   NULL },
+  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "//cR",   NULL },
+  { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL },
+  { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL },
+  { "'" ,  0, STD_C89, NOARGUMENTS, "",      "",    NULL },
+  { "R",   0, STD_C89, NOARGUMENTS, "",     "\\",   NULL },
   { "m",   0, STD_C89, NOARGUMENTS, "q",     "",   NULL },
   { NULL,  0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
 };
@@ -706,12 +712,17 @@  static const format_char_info gcc_tdiag_char_table[] =
   /* Custom conversion specifiers.  */
 
   /* These will require a "tree" at runtime.  */
-  { "DFKTEV", 0, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "",   NULL },
+  { "DFTV", 1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "'",   NULL },
+  { "E", 1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "",   NULL },
+  { "K", 1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL },
 
   { "v",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q#",  "",   NULL },
 
-  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "cR",   NULL },
-  { "<>'R",0, STD_C89, NOARGUMENTS, "",      "",   NULL },
+  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "/cR",   NULL },
+  { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL },
+  { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL },
+  { "'",   0, STD_C89, NOARGUMENTS, "",      "",    NULL },
+  { "R",   0, STD_C89, NOARGUMENTS, "",     "\\",   NULL },
   { "m",   0, STD_C89, NOARGUMENTS, "q",     "",   NULL },
   { "Z",   1, STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "", &gcc_tdiag_char_table[0] },
   { NULL,  0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
@@ -730,12 +741,17 @@  static const format_char_info gcc_cdiag_char_table[] =
   /* Custom conversion specifiers.  */
 
   /* These will require a "tree" at runtime.  */
-  { "DEFKTV", 0, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "",   NULL },
+  { "DFTV", 1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "'",   NULL },
+  { "E",   1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+", "",   NULL },
+  { "K",   1, STD_C89, { T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL },
 
   { "v",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q#",  "",   NULL },
 
-  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "cR",   NULL },
-  { "<>'R",0, STD_C89, NOARGUMENTS, "",      "",   NULL },
+  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "/cR",   NULL },
+  { "<",   0, STD_C89, NOARGUMENTS, "",      "<",  NULL },
+  { ">",   0, STD_C89, NOARGUMENTS, "",      ">",  NULL },
+  { "'",   0, STD_C89, NOARGUMENTS, "",      "",   NULL },
+  { "R",   0, STD_C89, NOARGUMENTS, "",     "\\",  NULL },
   { "m",   0, STD_C89, NOARGUMENTS, "q",     "",   NULL },
   { "Z",   1, STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "", &gcc_tdiag_char_table[0] },
   { NULL,  0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
@@ -754,15 +770,19 @@  static const format_char_info gcc_cxxdiag_char_table[] =
   /* Custom conversion specifiers.  */
 
   /* These will require a "tree" at runtime.  */
-  { "ADEFKSTVX",0,STD_C89,{ T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+#",   "",   NULL },
-
+  { "ADFSTVX",1,STD_C89,{ T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+#",   "'",   NULL },
+  { "E", 1,STD_C89,{ T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q+#",   "",   NULL },
+  { "K", 1, STD_C89,{ T89_T,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",   "\"",   NULL },
   { "v", 0,STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q#",  "",   NULL },
 
   /* These accept either an 'int' or an 'enum tree_code' (which is handled as an 'int'.)  */
   { "CLOPQ",0,STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",  "",   NULL },
 
-  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "cR",   NULL },
-  { "<>'R",0, STD_C89, NOARGUMENTS, "",      "",   NULL },
+  { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "/cR",   NULL },
+  { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL },
+  { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL },
+  { "'",   0, STD_C89, NOARGUMENTS, "",      "",    NULL },
+  { "R",   0, STD_C89, NOARGUMENTS, "",      "\\",  NULL },
   { "m",   0, STD_C89, NOARGUMENTS, "q",     "",   NULL },
   { "Z",   1, STD_C89, { T89_I,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "", &gcc_tdiag_char_table[0] },
   { NULL,  0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
@@ -1689,7 +1709,8 @@  class flag_chars_t
 		 tree format_string_cst,
 		 location_t format_string_loc,
 		 const char * const orig_format_chars,
-		 char format_char);
+		 char format_char,
+		 bool quoted);
   int get_alloc_flag (const format_kind_info *fki);
   int assignment_suppression_p (const format_kind_info *fki);
 
@@ -1849,10 +1870,13 @@  flag_chars_t::validate (const format_kind_info *fki,
 			tree format_string_cst,
 			location_t format_string_loc,
 			const char * const orig_format_chars,
-			char format_char)
+			char format_char,
+			bool quoted)
 {
   int i;
   int d = 0;
+  bool quotflag = false;
+
   for (i = 0; m_flag_chars[i] != 0; i++)
     {
       const format_flag_spec *s = get_flag_spec (flag_specs,
@@ -1860,6 +1884,10 @@  flag_chars_t::validate (const format_kind_info *fki,
       m_flag_chars[i - d] = m_flag_chars[i];
       if (m_flag_chars[i] == fki->length_code_char)
 	continue;
+
+      /* Remember if a quoting flag is seen.  */
+      quotflag |= s->quoting;
+
       if (strchr (fci->flag_chars, m_flag_chars[i]) == 0)
 	{
 	  format_warning_at_char (format_string_loc, format_string_cst,
@@ -1891,8 +1919,30 @@  flag_chars_t::validate (const format_kind_info *fki,
 			    format_char, fki->name);
 	    }
 	}
+
+      /* Detect quoting directives used within a quoted sequence, such
+	 as GCC's "%<...%qE".  */
+      if (quoted && s->quoting)
+	{
+	  format_warning_at_char (format_string_loc, format_string_cst,
+				  format_chars - orig_format_chars - 1,
+				  OPT_Wformat_,
+				  "%s used within a quoted sequence",
+				  _(s->name));
+	}
     }
   m_flag_chars[i - d] = 0;
+
+  if (!quoted
+      && !quotflag
+      && strchr (fci->flags2, '\''))
+    {
+      format_warning_at_char (format_string_loc, format_string_cst,
+			      format_chars - orig_format_chars,
+			      OPT_Wformat_,
+			      "%qc conversion used unquoted",
+			      format_char);
+    }
 }
 
 /* Determine if an assignment-allocation has been set, requiring
@@ -2704,6 +2754,16 @@  check_format_info_main (format_check_results *res,
      and it didn't use $; 1 if $ formats are in use.  */
   int has_operand_number = -1;
 
+  /* Vector of pointers to opening quoting directives (like GCC "%<").  */
+  auto_vec<const char*> quotdirs;
+
+  /* Pointers to the most recent color directives (like GCC's "%r or %R").
+     A starting color directive much be terminated before the end of
+     the format string.  A terminating directive makes no sense without
+     a prior starting directive.  */
+  const char *color_begin = NULL;
+  const char *color_end = NULL;
+
   init_dollar_format_checking (info->first_arg_num, first_fillin_param);
 
   while (*format_chars != 0)
@@ -2785,11 +2845,72 @@  check_format_info_main (format_check_results *res,
 
       flag_chars.validate (fki, fci, flag_specs, format_chars,
 			   format_string_cst,
-			   format_string_loc, orig_format_chars, format_char);
+			   format_string_loc, orig_format_chars, format_char,
+			   quotdirs.length () > 0);
 
       const int alloc_flag = flag_chars.get_alloc_flag (fki);
       const bool suppressed = flag_chars.assignment_suppression_p (fki);
 
+      /* Diagnose nested or unmatched quoting directives such as GCC's
+	 "%<...%<" and "%>...%>".  */
+      bool quot_begin_p = strchr (fci->flags2, '<');
+      bool quot_end_p = strchr (fci->flags2, '>');
+
+      if (quot_begin_p && !quot_end_p)
+	{
+	  if (quotdirs.length ())
+	    format_warning_at_char (format_string_loc, format_string_cst,
+				    format_chars - orig_format_chars,
+				    OPT_Wformat_,
+				    "nested quoting directive");
+	  quotdirs.safe_push (format_chars);
+	}
+      else if (!quot_begin_p && quot_end_p)
+	{
+	  if (quotdirs.length ())
+	    quotdirs.pop ();
+	  else
+	    format_warning_at_char (format_string_loc, format_string_cst,
+				    format_chars - orig_format_chars,
+				    OPT_Wformat_,
+				    "unmatched quoting directive");
+	}
+
+      bool color_begin_p = strchr (fci->flags2, '/');
+      if (color_begin_p)
+	{
+	  color_begin = format_chars;
+	  color_end = NULL;
+	}
+      else if (strchr (fci->flags2, '\\'))
+	{
+	  if (color_end)
+	    format_warning_at_char (format_string_loc, format_string_cst,
+				    format_chars - orig_format_chars,
+				    OPT_Wformat_,
+				    "%qc directive redundant after prior "
+				    "occurence of the same", format_char);
+	  else if (!color_begin)
+	    format_warning_at_char (format_string_loc, format_string_cst,
+				    format_chars - orig_format_chars,
+				    OPT_Wformat_,
+				    "unmatched color reset directive");
+	  color_end = format_chars;
+	}
+
+      /* Diagnose directives that shouldn't appear in a quoted sequence.
+	 (They are denoted by a double quote in FLAGS2.)  */
+      if (quotdirs.length ())
+	{
+	  if (strchr (fci->flags2, '"'))
+	    format_warning_at_char (format_string_loc, format_string_cst,
+				    format_chars - orig_format_chars,
+				    OPT_Wformat_,
+				    "%qc conversion used within a quoted "
+				    "sequence",
+				    format_char);
+	}
+
       /* Validate the pairs of flags used.  */
       arg_parser.validate_flag_pairs (fci, format_char);
 
@@ -2834,6 +2955,15 @@  check_format_info_main (format_check_results *res,
     }
   if (has_operand_number > 0)
     finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+  if (quotdirs.length ())
+    format_warning_at_char (format_string_loc, format_string_cst,
+			    quotdirs.pop () - orig_format_chars,
+			    OPT_Wformat_, "unterminated quoting directive");
+  if (color_begin && !color_end)
+    format_warning_at_char (format_string_loc, format_string_cst,
+			    color_begin - orig_format_chars,
+			    OPT_Wformat_, "unterminated color directive");
 }
 
 /* Check the argument types from a single format conversion (possibly
@@ -3654,58 +3784,58 @@  init_dynamic_gfc_info (void)
 static void
 init_dynamic_diag_info (void)
 {
-  static tree t, loc, hwi;
-
-  if (!loc || !t || !hwi)
+  /* For the GCC-diagnostics custom format specifiers to work, one
+     must have declared 'tree' and 'location_t' prior to using those
+     attributes.  If we haven't seen these declarations then
+     the specifiers requiring these types shouldn't be used.
+     However we don't force a hard ICE because we may see only one
+     or the other type.  */
+  if (tree loc = maybe_get_identifier ("location_t"))
     {
-      static format_char_info *diag_fci, *tdiag_fci, *cdiag_fci, *cxxdiag_fci;
-      static format_length_info *diag_ls;
-      unsigned int i;
-
-      /* For the GCC-diagnostics custom format specifiers to work, one
-	 must have declared 'tree' and/or 'location_t' prior to using
-	 those attributes.  If we haven't seen these declarations then
-	 you shouldn't use the specifiers requiring these types.
-	 However we don't force a hard ICE because we may see only one
-	 or the other type.  */
-      if ((loc = maybe_get_identifier ("location_t")))
-	{
-	  loc = identifier_global_value (loc);
-	  if (loc)
-	    {
-	      if (TREE_CODE (loc) != TYPE_DECL)
-		{
-		  error ("%<location_t%> is not defined as a type");
-		  loc = 0;
-		}
-	      else
-		loc = TREE_TYPE (loc);
-	    }
-	}
+      loc = identifier_global_value (loc);
+      if (loc && TREE_CODE (loc) != TYPE_DECL)
+	error ("%<location_t%> is not defined as a type");
+    }
 
+  /* Initialize the global tree node type local to this file.  */
+  if (!local_tree_type_node
+      || local_tree_type_node == void_type_node)
+    {
       /* We need to grab the underlying 'union tree_node' so peek into
 	 an extra type level.  */
-      if ((t = maybe_get_identifier ("tree")))
+      if ((local_tree_type_node = maybe_get_identifier ("tree")))
 	{
-	  t = identifier_global_value (t);
-	  if (t)
+	  local_tree_type_node = identifier_global_value (local_tree_type_node);
+	  if (local_tree_type_node)
 	    {
-	      if (TREE_CODE (t) != TYPE_DECL)
+	      if (TREE_CODE (local_tree_type_node) != TYPE_DECL)
 		{
 		  error ("%<tree%> is not defined as a type");
-		  t = 0;
+		  local_tree_type_node = 0;
 		}
-	      else if (TREE_CODE (TREE_TYPE (t)) != POINTER_TYPE)
+	      else if (TREE_CODE (TREE_TYPE (local_tree_type_node))
+		       != POINTER_TYPE)
 		{
 		  error ("%<tree%> is not defined as a pointer type");
-		  t = 0;
+		  local_tree_type_node = 0;
 		}
 	      else
-		t = TREE_TYPE (TREE_TYPE (t));
+		local_tree_type_node =
+		  TREE_TYPE (TREE_TYPE (local_tree_type_node));
 	    }
 	}
+      else
+	local_tree_type_node = void_type_node;
+    }
 
-      /* Find the underlying type for HOST_WIDE_INT.  For the %w
+  static tree hwi;
+
+  if (!hwi)
+    {
+      static format_length_info *diag_ls;
+      unsigned int i;
+
+      /* Find the underlying type for HOST_WIDE_INT.  For the 'w'
 	 length modifier to work, one must have issued: "typedef
 	 HOST_WIDE_INT __gcc_host_wide_int__;" in one's source code
 	 prior to using that modifier.  */
@@ -3757,75 +3887,17 @@  init_dynamic_diag_info (void)
 	  else
 	    gcc_unreachable ();
 	}
-
-      /* Handle the __gcc_diag__ format specifics.  */
-      if (!diag_fci)
-	dynamic_format_types[gcc_diag_format_type].conversion_specs =
-	  diag_fci = (format_char_info *)
-		     xmemdup (gcc_diag_char_table,
-			      sizeof (gcc_diag_char_table),
-			      sizeof (gcc_diag_char_table));
-      if (t)
-	{
-	  i = find_char_info_specifier_index (diag_fci, 'K');
-	  diag_fci[i].types[0].type = &t;
-	  diag_fci[i].pointer_count = 1;
-	}
-
-      /* Handle the __gcc_tdiag__ format specifics.  */
-      if (!tdiag_fci)
-	dynamic_format_types[gcc_tdiag_format_type].conversion_specs =
-	  tdiag_fci = (format_char_info *)
-		      xmemdup (gcc_tdiag_char_table,
-			       sizeof (gcc_tdiag_char_table),
-			       sizeof (gcc_tdiag_char_table));
-      if (t)
-	{
-	  /* All specifiers taking a tree share the same struct.  */
-	  i = find_char_info_specifier_index (tdiag_fci, 'D');
-	  tdiag_fci[i].types[0].type = &t;
-	  tdiag_fci[i].pointer_count = 1;
-	  i = find_char_info_specifier_index (tdiag_fci, 'K');
-	  tdiag_fci[i].types[0].type = &t;
-	  tdiag_fci[i].pointer_count = 1;
-	}
-
-      /* Handle the __gcc_cdiag__ format specifics.  */
-      if (!cdiag_fci)
-	dynamic_format_types[gcc_cdiag_format_type].conversion_specs =
-	  cdiag_fci = (format_char_info *)
-		      xmemdup (gcc_cdiag_char_table,
-			       sizeof (gcc_cdiag_char_table),
-			       sizeof (gcc_cdiag_char_table));
-      if (t)
-	{
-	  /* All specifiers taking a tree share the same struct.  */
-	  i = find_char_info_specifier_index (cdiag_fci, 'D');
-	  cdiag_fci[i].types[0].type = &t;
-	  cdiag_fci[i].pointer_count = 1;
-	  i = find_char_info_specifier_index (cdiag_fci, 'K');
-	  cdiag_fci[i].types[0].type = &t;
-	  cdiag_fci[i].pointer_count = 1;
-	}
-
-      /* Handle the __gcc_cxxdiag__ format specifics.  */
-      if (!cxxdiag_fci)
-	dynamic_format_types[gcc_cxxdiag_format_type].conversion_specs =
-	  cxxdiag_fci = (format_char_info *)
-			xmemdup (gcc_cxxdiag_char_table,
-				 sizeof (gcc_cxxdiag_char_table),
-				 sizeof (gcc_cxxdiag_char_table));
-      if (t)
-	{
-	  /* All specifiers taking a tree share the same struct.  */
-	  i = find_char_info_specifier_index (cxxdiag_fci, 'D');
-	  cxxdiag_fci[i].types[0].type = &t;
-	  cxxdiag_fci[i].pointer_count = 1;
-	  i = find_char_info_specifier_index (cxxdiag_fci, 'K');
-	  cxxdiag_fci[i].types[0].type = &t;
-	  cxxdiag_fci[i].pointer_count = 1;
-	}
     }
+
+  /* It's safe to "re-initialize these to the same values.  */
+  dynamic_format_types[gcc_diag_format_type].conversion_specs =
+    gcc_diag_char_table;
+  dynamic_format_types[gcc_tdiag_format_type].conversion_specs =
+    gcc_tdiag_char_table;
+  dynamic_format_types[gcc_cdiag_format_type].conversion_specs =
+    gcc_cdiag_char_table;
+  dynamic_format_types[gcc_cxxdiag_format_type].conversion_specs =
+    gcc_cxxdiag_char_table;
 }
 
 #ifdef TARGET_FORMAT_TYPES
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 13ca8ea..37fa382 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -151,7 +151,16 @@  struct format_char_info
      "W" if the argument is a pointer which is dereferenced and written into,
      "R" if the argument is a pointer which is dereferenced and read from,
      "i" for printf integer formats where the '0' flag is ignored with
-     precision, and "[" for the starting character of a scanf scanset.  */
+     precision, and "[" for the starting character of a scanf scanset,
+     "<" if the specifier introduces a quoted sequence (such as "%<"),
+     ">" if the specifier terminates a quoted sequence (such as "%>"),
+     "[" if the specifier introduces a color sequence (such as "%r"),
+     "]" if the specifier terminates a color sequence (such as "%R"),
+     "'" (single quote) if the specifier is expected to be quoted when
+     it appears outside a quoted sequence and unquoted otherwise (such
+     as the GCC internal printf format directive "%T"), and
+     "\"" (double quote) if the specifier is not expected to appear in
+     a quoted sequence (such as the GCC internal format directive "%K".  */
   const char *flags2;
   /* If this format conversion character consumes more than one argument,
      CHAIN points to information about the next argument.  For later
@@ -178,6 +187,8 @@  struct format_flag_spec
   /* Nonzero if the next character after this flag in the format should
      be skipped ('=' in strfmon), zero otherwise.  */
   int skip_next_char;
+  /* True if the flag introduces quoting (as in GCC's %qE).  */
+  bool quoting;
   /* The name to use for this flag in diagnostic messages.  For example,
      N_("'0' flag"), N_("field width").  */
   const char *name;
@@ -287,6 +298,7 @@  struct format_kind_info
 #define T_UC	&unsigned_char_type_node
 #define T99_UC	{ STD_C99, NULL, T_UC }
 #define T_V	&void_type_node
+#define T89_T   { STD_C89, NULL, &local_tree_type_node }
 #define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
 #define T94_W	{ STD_C94, "wchar_t", T_W }
diff --git a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
new file mode 100644
index 0000000..b3be277
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
@@ -0,0 +1,151 @@ 
+/* Test for GCC internal format directives.
+   { dg-do compile }
+   { dg-options "-std=gnu99 -Wformat" } */
+
+/* Magic identifiers must be set before the attribute is used.  */
+
+typedef long long __gcc_host_wide_int__;
+
+typedef struct location_s
+{
+  const char *file;
+  int line;
+} location_t;
+
+union tree_node;
+typedef union tree_node *tree;
+
+
+#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
+
+void diag (const char*, ...) FORMAT (diag);
+void cdiag (const char*, ...) FORMAT (cdiag);
+void tdiag (const char*, ...) FORMAT (tdiag);
+void cxxdiag (const char*, ...) FORMAT (cxxdiag);
+
+void test_diag (tree t)
+{
+  diag ("%<");   /* { dg-warning "unterminated quoting directive" } */
+  diag ("%>");   /* { dg-warning "unmatched quoting directive " } */
+  diag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  diag ("%K", t);
+
+  diag ("%R");       /* { dg-warning "unmatched color reset directive" } */
+  diag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  diag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  diag ("%r%R", "");
+  diag ("%r%r%R", "", "");
+  diag ("%r%R%r%R", "", "");
+
+  diag ("%<%K%>", t);   /* { dg-warning ".K. conversion used within a quoted sequence" } */
+
+  diag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  diag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  diag ("%<%r%R%>", "");
+}
+
+void test_cdiag (tree t)
+{
+  cdiag ("%<");   /* { dg-warning "unterminated quoting directive" } */
+  cdiag ("%>");   /* { dg-warning "unmatched quoting directive " } */
+  cdiag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  cdiag ("%D", t);       /* { dg-warning ".D. conversion used unquoted" } */
+  cdiag ("%E", t);
+  cdiag ("%F", t);       /* { dg-warning ".F. conversion used unquoted" } */
+  cdiag ("%K", t);
+
+  cdiag ("%R");       /* { dg-warning "unmatched color reset directive" } */
+  cdiag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  cdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  cdiag ("%r%R", "");
+  cdiag ("%r%r%R", "", "");
+  cdiag ("%r%R%r%R", "", "");
+
+  cdiag ("%T", t);       /* { dg-warning ".T. conversion used unquoted" } */
+  cdiag ("%V", t);       /* { dg-warning ".V. conversion used unquoted" } */
+
+  cdiag ("%<%D%>", t);
+  cdiag ("%<%E%>", t);
+  cdiag ("%<%F%>", t);
+  cdiag ("%<%K%>", t);   /* { dg-warning ".K. conversion used within a quoted sequence" } */
+
+  cdiag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  cdiag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  cdiag ("%<%r%R%>", "");
+
+  cdiag ("%<%T%>", t);
+  cdiag ("%<%V%>", t);
+
+  cdiag ("%<%qD%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  cdiag ("%<%qE%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  cdiag ("%<%qT%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+}
+
+void test_tdiag (tree t)
+{
+  tdiag ("%<");       /* { dg-warning "unterminated quoting directive" } */
+  tdiag ("%>");       /* { dg-warning "unmatched quoting directive " } */
+  tdiag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  tdiag ("%D", t);       /* { dg-warning ".D. conversion used unquoted" } */
+  tdiag ("%E", t);
+  tdiag ("%K", t);
+
+  tdiag ("%R");          /* { dg-warning "unmatched color reset directive" } */
+  tdiag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  tdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  tdiag ("%r%R", "");
+  tdiag ("%r%R", "");
+  tdiag ("%r%r%R", "", "");
+  tdiag ("%r%R%r%R", "", "");
+
+  tdiag ("%T", t);       /* { dg-warning ".T. conversion used unquoted" } */
+
+  tdiag ("%<%D%>", t);
+  tdiag ("%<%E%>", t);
+  tdiag ("%<%K%>", t);   /* { dg-warning ".K. conversion used within a quoted sequence" } */
+
+  tdiag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  tdiag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  tdiag ("%<%r%R%>", "");
+
+  tdiag ("%<%T%>", t);
+
+  tdiag ("%<%qD%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  tdiag ("%<%qE%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  tdiag ("%<%qT%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+}
+
+void test_cxxdiag (tree t)
+{
+  cxxdiag ("%A", t);     /* { dg-warning ".A. conversion used unquoted" } */
+  cxxdiag ("%D", t);     /* { dg-warning ".D. conversion used unquoted" } */
+  cxxdiag ("%E", t);
+  cxxdiag ("%F", t);     /* { dg-warning ".F. conversion used unquoted" } */
+
+  cxxdiag ("%R");        /* { dg-warning "unmatched color reset directive" } */
+  cxxdiag ("%r", "");    /* { dg-warning "unterminated color directive" } */
+  cxxdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  cxxdiag ("%r%R", "");
+  cxxdiag ("%r%R", "");
+  cxxdiag ("%r%r%R", "", "");
+  cxxdiag ("%r%R%r%R", "", "");
+
+  cxxdiag ("%S", t);     /* { dg-warning ".S. conversion used unquoted" } */
+  cxxdiag ("%T", t);     /* { dg-warning ".T. conversion used unquoted" } */
+  cxxdiag ("%V", t);     /* { dg-warning ".V. conversion used unquoted" } */
+  cxxdiag ("%X", t);     /* { dg-warning ".X. conversion used unquoted" } */
+
+  cxxdiag ("%<%A%>", t);
+  cxxdiag ("%<%D%>", t);
+  cxxdiag ("%<%E%>", t);
+  cxxdiag ("%<%F%>", t);
+  cxxdiag ("%<%R%>");    /* { dg-warning "unmatched color reset" } */
+  cxxdiag ("%<%r%R%>", "");
+  cxxdiag ("%<%S%>", t);
+  cxxdiag ("%<%T%>", t);
+  cxxdiag ("%<%V%>", t);
+  cxxdiag ("%<%X%>", t);
+}