#include "calls.h"
#include "cfgloop.h"
#include "intl.h"
+#include "langhooks.h"
#include "builtins.h"
#include "stor-layout.h"
return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
}
+/* A straightforward mapping from the execution character set to the host
+ character set indexed by execution character. */
+
+static char target_to_host_charmap[256];
+
+/* Initialize a mapping from the execution character set to the host
+ character set. */
+
+static bool
+init_target_to_host_charmap ()
+{
+ /* If the percent sign is non-zero the mapping has already been
+ initialized. */
+ if (target_to_host_charmap['%'])
+ return true;
+
+ /* Initialize the target_percent character (done elsewhere). */
+ if (!init_target_chars ())
+ return false;
+
+ /* The subset of the source character set used by printf conversion
+ specifications (strictly speaking, not all letters are used but
+ they are included here for the sake of simplicity). The dollar
+ sign must be included even though it's not in the basic source
+ character set. */
+ const char srcset[] = " 0123456789!\"#%&'()*+,-./:;<=>?[\\]^_{|}~$"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ /* Set the mapping for all characters to some ordinary value (i,e.,
+ not none used in printf conversion specifications) and overwrite
+ those that are used by conversion specifications with their
+ corresponding values. */
+ memset (target_to_host_charmap + 1, '?', sizeof target_to_host_charmap - 1);
+
+ /* Are the two sets of characters the same? */
+ bool all_same_p = true;
+
+ for (const char *pc = srcset; *pc; ++pc)
+ {
+ /* Slice off the high end bits in case target characters are
+ signed. All values are expected to be non-nul, otherwise
+ there's a problem. */
+ if (unsigned char tc = lang_hooks.to_target_charset (*pc))
+ {
+ target_to_host_charmap[tc] = *pc;
+ if (tc != *pc)
+ all_same_p = false;
+ }
+ else
+ return false;
+
+ }
+
+ /* Set the first element to a non-zero value if the mapping
+ is 1-to-1, otherwise leave it clear (NUL is assumed to be
+ the same in both character sets). */
+ target_to_host_charmap[0] = all_same_p;
+
+ return true;
+}
+
+/* Return the host source character corresponding to the character
+ CH in the execution character set if one exists, or some innocuous
+ (non-special, non-nul) source character otherwise. */
+
+static inline unsigned char
+target_to_host (unsigned char ch)
+{
+ return target_to_host_charmap[ch];
+}
+
+/* Convert an initial substring of the string TARGSTR consisting of
+ characters in the execution character set into a string in the
+ source character set on the host and store up to HOSTSZ characters
+ in the buffer pointed to by HOSTR. Return HOSTR. */
+
+static const char*
+target_to_host (char *hostr, size_t hostsz, const char *targstr)
+{
+ /* Make sure the buffer is reasonably big. */
+ gcc_assert (hostsz > 4);
+
+ /* The interesting subset of source and execution characters are
+ the same so no conversion is necessary. However, truncate
+ overlong strings just like the translated strings are. */
+ if (target_to_host_charmap['\0'] == 1)
+ {
+ strncpy (hostr, targstr, hostsz - 4);
+ if (strlen (targstr) >= hostsz)
+ strcpy (hostr + hostsz - 4, "...");
+ return hostr;
+ }
+
+ /* Convert the initial substring of TARGSTR to the corresponding
+ characters in the host set, appending "..." if TARGSTR is too
+ long to fit. Using the static buffer assumes the function is
+ not called in between sequence points (which it isn't). */
+ for (char *ph = hostr; ; ++targstr)
+ {
+ *ph++ = target_to_host (*targstr);
+ if (!*targstr)
+ break;
+
+ if (size_t (ph - hostr) == hostsz - 4)
+ {
+ *ph = '\0';
+ strcat (ph, "...");
+ break;
+ }
+ }
+
+ return hostr;
+}
+
+/* Convert the sequence of decimal digits in the execution character
+ starting at S to a long, just like strtol does. Return the result
+ and set *END to one past the last converted character. On range
+ error set ERANGE to the digit that caused it. */
+
+static inline long
+target_strtol10 (const char **ps, const char **erange)
+{
+ unsigned HOST_WIDE_INT val = 0;
+ for ( ; ; ++*ps)
+ {
+ unsigned char c = target_to_host (**ps);
+ if (ISDIGIT (c))
+ {
+ c -= '0';
+
+ /* Check for overflow. */
+ if (val > (LONG_MAX - c) / 10LU)
+ {
+ val = LONG_MAX;
+ *erange = *ps;
+
+ /* Skip the remaining digits. */
+ do
+ c = target_to_host (*++*ps);
+ while (ISDIGIT (c));
+ break;
+ }
+ else
+ val = val * 10 + c;
+ }
+ else
+ break;
+ }
+
+ return val;
+}
+
/* Return the constant initial value of DECL if available or DECL
otherwise. Same as the synonymous function in c/c-typeck.c. */
|| (res.max < HOST_WIDE_INT_MAX
&& avail_range.min < res.max)));
+ /* Buffer for the directive in the host character set (used when
+ the source character set is different). */
+ char hostdir[32];
+
if (avail_range.min == avail_range.max)
{
/* The size of the destination region is exact. */
unsigned HOST_WIDE_INT navail = avail_range.max;
- if (*dir.beg != '%')
+ if (target_to_host (*dir.beg) != '%')
{
/* For plain character directives (i.e., the format string itself)
but not others, point the caret at the first character that's
: G_("%<%.*s%> directive writing %wu bytes "
"into a region of size %wu")));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg, res.min,
- navail);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, navail);
}
if (res.min == 0 && res.max < maxbytes)
: G_("%<%.*s%> directive writing up to %wu bytes "
"into a region of size %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
res.max, navail);
}
: G_("%<%.*s%> directive writing likely %wu or more bytes "
"into a region of size %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
res.likely, navail);
}
: G_("%<%.*s%> directive writing between %wu and "
"%wu bytes into a region of size %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
- res.min, res.max,
- navail);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, res.max, navail);
}
const char* fmtstr
: G_("%<%.*s%> directive writing %wu or more bytes "
"into a region of size %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
res.min, navail);
}
/* The size of the destination region is a range. */
- if (*dir.beg != '%')
+ if (target_to_host (*dir.beg) != '%')
{
unsigned HOST_WIDE_INT navail = avail_range.max;
"into a region of size between %wu and %wu")));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg, res.min,
- avail_range.min, avail_range.max);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, avail_range.min, avail_range.max);
}
if (res.min == 0 && res.max < maxbytes)
: G_("%<%.*s%> directive writing up to %wu bytes "
"into a region of size between %wu and %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg, res.max,
- avail_range.min, avail_range.max);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.max, avail_range.min, avail_range.max);
}
if (res.min == 0 && maxbytes <= res.max)
: G_("%<%.*s%> directive writing likely %wu or more bytes "
"into a region of size between %wu and %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg, res.likely,
- avail_range.min, avail_range.max);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.likely, avail_range.min, avail_range.max);
}
if (res.max < maxbytes)
: G_("%<%.*s%> directive writing between %wu and "
"%wu bytes into a region of size between %wu and %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
- res.min, res.max,
- avail_range.min, avail_range.max);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, res.max, avail_range.min, avail_range.max);
}
const char* fmtstr
: G_("%<%.*s%> directive writing %wu or more bytes "
"into a region of size between %wu and %wu"));
return fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dir.len, dir.beg,
- res.min,
- avail_range.min, avail_range.max);
+ info.warnopt (), fmtstr, dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, avail_range.min, avail_range.max);
}
/* Compute the length of the output resulting from the directive DIR
}
}
+ /* Buffer for the directive in the host character set (used when
+ the source character set is different). */
+ char hostdir[32];
+
int dirlen = dir.len;
if (fmtres.nullp)
{
fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
"%<%.*s%> directive argument is null",
- dirlen, dir.beg);
+ dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
/* Don't bother processing the rest of the format string. */
res->warned = true;
info.warnopt (),
"%<%.*s%> directive output of %wu bytes exceeds "
"minimum required size of 4095",
- dirlen, dir.beg, fmtres.range.min);
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min);
else
{
const char *fmtstr
"bytes exceeds minimum required size of 4095"));
warned = fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dirlen, dir.beg,
+ info.warnopt (), fmtstr, dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
fmtres.range.min, fmtres.range.max);
}
}
warned = fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
"%<%.*s%> directive output of %wu bytes causes "
"result to exceed %<INT_MAX%>",
- dirlen, dir.beg, fmtres.range.min);
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min);
else
{
const char *fmtstr
: G_ ("%<%.*s%> directive output between %wu and %wu "
"bytes may cause result to exceed %<INT_MAX%>"));
warned = fmtwarn (dirloc, pargrange, NULL,
- info.warnopt (), fmtstr,
- dirlen, dir.beg,
+ info.warnopt (), fmtstr, dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
fmtres.range.min, fmtres.range.max);
}
}
directive &dir, format_result *res,
const char *str, unsigned *argno)
{
- const char *pcnt = strchr (str, '%');
+ const char *pcnt = strchr (str, target_percent);
dir.beg = str;
if (size_t len = pcnt ? pcnt - str : *str ? strlen (str) : 1)
const char *pf = pcnt + 1;
/* POSIX numbered argument index or zero when none. */
- unsigned dollar = 0;
+ HOST_WIDE_INT dollar = 0;
/* With and precision. -1 when not specified, HOST_WIDE_INT_MIN
when given by a va_list argument, and a non-negative value
HOST_WIDE_INT width = -1;
HOST_WIDE_INT precision = -1;
+ /* Pointers to the beginning of the width and precision decimal
+ string (if any) within the directive. */
+ const char *pwidth = 0;
+ const char *pprec = 0;
+
+ /* When the value of the decimal string that specifies width or
+ precision is out of range, points to the digit that causes
+ the value to exceed the limit. */
+ const char *werange = NULL;
+ const char *perange = NULL;
+
/* Width specified via the asterisk. Need not be INTEGER_CST.
For vararg functions set to void_node. */
tree star_width = NULL_TREE;
For vararg functions set to void_node. */
tree star_precision = NULL_TREE;
- if (ISDIGIT (*pf))
+ if (ISDIGIT (target_to_host (*pf)))
{
/* This could be either a POSIX positional argument, the '0'
flag, or a width, depending on what follows. Store it as
width and sort it out later after the next character has
been seen. */
- char *end;
- width = strtol (pf, &end, 10);
- pf = end;
+ pwidth = pf;
+ width = target_strtol10 (&pf, &werange);
}
- else if ('*' == *pf)
+ else if (target_to_host (*pf) == '*')
{
/* Similarly to the block above, this could be either a POSIX
positional argument or a width, depending on what follows. */
++pf;
}
- if (*pf == '$')
+ if (target_to_host (*pf) == '$')
{
/* Handle the POSIX dollar sign which references the 1-based
positional argument number. */
/* Bail when the numbered argument is out of range (it will
have already been diagnosed by -Wformat). */
if (dollar == 0
- || dollar == info.argidx
+ || dollar == (int)info.argidx
|| dollar > gimple_call_num_args (info.callstmt))
return false;
the next field is the optional flags followed by an optional
width. */
for ( ; ; ) {
- switch (*pf)
+ switch (target_to_host (*pf))
{
case ' ':
case '0':
case '+':
case '-':
case '#':
- dir.set_flag (*pf++);
+ dir.set_flag (target_to_host (*pf++));
break;
default:
}
start_width:
- if (ISDIGIT (*pf))
+ if (ISDIGIT (target_to_host (*pf)))
{
- char *end;
- width = strtol (pf, &end, 10);
- pf = end;
+ werange = 0;
+ pwidth = pf;
+ width = target_strtol10 (&pf, &werange);
}
- else if ('*' == *pf)
+ else if (target_to_host (*pf) == '*')
{
if (*argno < gimple_call_num_args (info.callstmt))
star_width = gimple_call_arg (info.callstmt, (*argno)++);
}
++pf;
}
- else if ('\'' == *pf)
+ else if (target_to_host (*pf) == '\'')
{
/* The POSIX apostrophe indicating a numeric grouping
in the current locale. Even though it's possible to
}
start_precision:
- if ('.' == *pf)
+ if (target_to_host (*pf) == '.')
{
++pf;
- if (ISDIGIT (*pf))
+ if (ISDIGIT (target_to_host (*pf)))
{
- char *end;
- precision = strtol (pf, &end, 10);
- pf = end;
+ pprec = pf;
+ precision = target_strtol10 (&pf, &perange);
}
- else if ('*' == *pf)
+ else if (target_to_host (*pf) == '*')
{
if (*argno < gimple_call_num_args (info.callstmt))
star_precision = gimple_call_arg (info.callstmt, (*argno)++);
}
}
- switch (*pf)
+ switch (target_to_host (*pf))
{
case 'h':
- if (pf[1] == 'h')
+ if (target_to_host (pf[1]) == 'h')
{
++pf;
dir.modifier = FMT_LEN_hh;
break;
case 'l':
- if (pf[1] == 'l')
+ if (target_to_host (pf[1]) == 'l')
{
++pf;
dir.modifier = FMT_LEN_ll;
break;
}
- switch (*pf)
+ switch (target_to_host (*pf))
{
/* Handle a sole '%' character the same as "%%" but since it's
undefined prevent the result from being folded. */
return 0;
}
- dir.specifier = *pf++;
+ dir.specifier = target_to_host (*pf++);
+
+ /* Store the length of the format directive. */
+ dir.len = pf - pcnt;
+
+ /* Buffer for the directive in the host character set (used when
+ the source character set is different). */
+ char hostdir[32];
if (star_width)
{
}
}
else
- dir.set_width (width);
+ {
+ if (width == LONG_MAX && werange)
+ {
+ size_t begin = dir.beg - info.fmtstr + (pwidth - pcnt);
+ size_t caret = begin + (werange - pcnt);
+ size_t end = pf - info.fmtstr - 1;
+
+ /* Create a location for the width part of the directive,
+ pointing the caret at the first out-of-range digit. */
+ substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+ caret, begin, end);
+
+ fmtwarn (dirloc, NULL, NULL,
+ info.warnopt (), "%<%.*s%> directive width out of range",
+ dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+ }
+
+ dir.set_width (width);
+ }
if (star_precision)
{
}
}
else
- dir.set_precision (precision);
+ {
+ if (precision == LONG_MAX && perange)
+ {
+ size_t begin = dir.beg - info.fmtstr + (pprec - pcnt) - 1;
+ size_t caret = dir.beg - info.fmtstr + (perange - pcnt) - 1;
+ size_t end = pf - info.fmtstr - 2;
+
+ /* Create a location for the precision part of the directive,
+ including the leading period, pointing the caret at the first
+ out-of-range digit . */
+ substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+ caret, begin, end);
+
+ fmtwarn (dirloc, NULL, NULL,
+ info.warnopt (), "%<%.*s%> directive precision out of range",
+ dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+ }
+
+ dir.set_precision (precision);
+ }
/* Extract the argument if the directive takes one and if it's
available (e.g., the function doesn't take a va_list). Treat
&& *argno < gimple_call_num_args (info.callstmt))
dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
- /* Return the length of the format directive. */
- dir.len = pf - pcnt;
-
if (dump_file)
{
fprintf (dump_file, " Directive %u at offset %llu: \"%.*s\"",
unsigned int
pass_sprintf_length::execute (function *fun)
{
+ init_target_to_host_charmap ();
+
basic_block bb;
FOR_EACH_BB_FN (bb, fun)
{
--- /dev/null
+/* PR tree-optimization/80523 - -Wformat-overflow doesn't consider
+ -fexec-charset
+ { dg-do compile }
+ { dg-require-iconv "IBM1047" }
+ { dg-options "-O2 -Wall -Wno-format -Wformat-overflow -fexec-charset=IBM1047 -ftrack-macro-expansion=0" } */
+
+char buf[1];
+void sink (void*);
+
+#define T(...) (__builtin_sprintf (buf + 1, __VA_ARGS__), sink (buf))
+
+/* Exercise all special C and POSIX characters. */
+
+void test_characters ()
+{
+ T ("%%"); /* { dg-warning ".%%. directive writing 1 byte" } */
+
+ T ("%A", 0.0); /* { dg-warning ".%A. directive writing between 6 and 20 " } */
+ T ("%a", 0.0); /* { dg-warning ".%a. directive writing between 6 and 20 " } */
+
+ T ("%C", 'a'); /* { dg-warning ".%C. directive writing 1 byte" "bug 80537" { xfail *-*-* } } */
+ T ("%c", 'a'); /* { dg-warning ".%c. directive writing 1 byte" } */
+
+ T ("%d", 12); /* { dg-warning ".%d. directive writing 2 bytes" } */
+ T ("% d", 12); /* { dg-warning ".% d. directive writing 3 bytes" } */
+ T ("%-d", 123); /* { dg-warning ".%-d. directive writing 3 bytes" } */
+ T ("%+d", 1234); /* { dg-warning ".%\\+d. directive writing 5 bytes" } */
+ T ("%'d", 1234); /* { dg-warning ".%'d. directive writing 5 bytes" "bug 80535" { xfail *-*-* } } */
+ T ("%1$d", 2345); /* { dg-warning ".%1\\\$d. directive writing 4 bytes" } */
+
+ /* Verify that digits are correctly interpreted as width and precision. */
+ T ("%0d", 12345); /* { dg-warning ".%0d. directive writing 5 bytes" } */
+ T ("%1d", 12345); /* { dg-warning ".%1d. directive writing 5 bytes" } */
+ T ("%2d", 12345); /* { dg-warning ".%2d. directive writing 5 bytes" } */
+ T ("%3d", 12345); /* { dg-warning ".%3d. directive writing 5 bytes" } */
+ T ("%4d", 12345); /* { dg-warning ".%4d. directive writing 5 bytes" } */
+ T ("%5d", 12345); /* { dg-warning ".%5d. directive writing 5 bytes" } */
+ T ("%6d", 12345); /* { dg-warning ".%6d. directive writing 6 bytes" } */
+ T ("%7d", 12345); /* { dg-warning ".%7d. directive writing 7 bytes" } */
+ T ("%8d", 12345); /* { dg-warning ".%8d. directive writing 8 bytes" } */
+ T ("%9d", 12345); /* { dg-warning ".%9d. directive writing 9 bytes" } */
+
+ T ("%.0d", 12345); /* { dg-warning ".%.0d. directive writing 5 bytes" } */
+ T ("%.1d", 12345); /* { dg-warning ".%.1d. directive writing 5 bytes" } */
+ T ("%.2d", 12345); /* { dg-warning ".%.2d. directive writing 5 bytes" } */
+ T ("%.3d", 12345); /* { dg-warning ".%.3d. directive writing 5 bytes" } */
+ T ("%.4d", 12345); /* { dg-warning ".%.4d. directive writing 5 bytes" } */
+ T ("%.5d", 12345); /* { dg-warning ".%.5d. directive writing 5 bytes" } */
+ T ("%.6d", 12345); /* { dg-warning ".%.6d. directive writing 6 bytes" } */
+ T ("%.7d", 12345); /* { dg-warning ".%.7d. directive writing 7 bytes" } */
+ T ("%.8d", 12345); /* { dg-warning ".%.8d. directive writing 8 bytes" } */
+ T ("%.9d", 12345); /* { dg-warning ".%.9d. directive writing 9 bytes" } */
+
+ T ("%hhd", 12); /* { dg-warning ".%hhd. directive writing 2 bytes" } */
+ T ("%hd", 234); /* { dg-warning ".%hd. directive writing 3 bytes" } */
+
+ {
+ const __PTRDIFF_TYPE__ i = 3456;
+ T ("%jd", i); /* { dg-warning ".%jd. directive writing 4 bytes" } */
+ }
+
+ T ("%ld", 45678L); /* { dg-warning ".%ld. directive writing 5 bytes" } */
+
+ {
+ const __PTRDIFF_TYPE__ i = 56789;
+ T ("%td", i); /* { dg-warning ".%td. directive writing 5 bytes" } */
+ }
+
+ {
+ const __SIZE_TYPE__ i = 67890;
+ T ("%zd", i); /* { dg-warning ".%zd. directive writing 5 bytes" } */
+ }
+
+ T ("%E", 0.0); /* { dg-warning ".%E. directive writing 12 bytes" } */
+ T ("%e", 0.0); /* { dg-warning ".%e. directive writing 12 bytes" } */
+ T ("%F", 0.0); /* { dg-warning ".%F. directive writing 8 bytes" } */
+ T ("%f", 0.0); /* { dg-warning ".%f. directive writing 8 bytes" } */
+ T ("%G", 0.0); /* { dg-warning ".%G. directive writing 1 byte" } */
+ T ("%g", 0.0); /* { dg-warning ".%g. directive writing 1 byte" } */
+
+ T ("%i", 123); /* { dg-warning ".%i. directive writing 3 bytes" } */
+
+ {
+ int n;
+
+ T ("%n", &n); /* { dg-warning "writing a terminating nul" } */
+ T ("%nH", &n); /* { dg-warning ".H. directive writing 1 byte" } */
+ }
+
+ T ("%o", 999); /* { dg-warning ".%o. directive writing 4 bytes" } */
+ T ("%#o", 999); /* { dg-warning ".%#o. directive writing 5 bytes" } */
+
+ T ("%x", 1234); /* { dg-warning ".%x. directive writing 3 bytes" } */
+ T ("%#X", 1235); /* { dg-warning ".%#X. directive writing 5 bytes" } */
+
+ T ("%S", L"1"); /* { dg-warning ".%S. directive writing 1 byte" } */
+ T ("%-s", "1"); /* { dg-warning ".%-s. directive writing 1 byte" } */
+
+ /* Verify that characters in the source character set appear in
+ the text of the warning unchanged (i.e., not as their equivalents
+ in the execution character set on the target). The trailing %%
+ disables sprintf->strcpy optimization. */
+ T ("ABCDEFGHIJ%%"); /* { dg-warning ".ABCDEFGHIJ. directive writing 10 bytes" } */
+ T ("KLMNOPQRST%%"); /* { dg-warning ".KLMNOPQRST. directive writing 10 bytes" } */
+ T ("UVWXYZ%%"); /* { dg-warning ".UVWXYZ. directive writing 6 bytes" } */
+
+ T ("abcdefghij%%"); /* { dg-warning ".abcdefghij. directive writing 10 bytes" } */
+ T ("klmnopqrst%%"); /* { dg-warning ".klmnopqrst. directive writing 10 bytes" } */
+ T ("uvwxyz%%"); /* { dg-warning ".uvwxyz. directive writing 6 bytes" } */
+}
+
+#undef T
+#define T(...) (__builtin_sprintf (d, __VA_ARGS__), sink (d))
+
+void test_width_and_precision_out_of_range (char *d)
+{
+#if __LONG_MAX__ == 2147483647
+# define MAX_P1_STR "2147483648"
+#elif __LONG_MAX__ == 9223372036854775807
+# define MAX_P1_STR "9223372036854775808"
+#endif
+
+ T ("%" MAX_P1_STR "i", 0); /* { dg-warning "width out of range" } */
+ /* { dg-warning "result to exceed .INT_MAX. " "" { target *-*-* } .-1 } */
+ T ("%." MAX_P1_STR "i", 0); /* { dg-warning "precision out of range" } */
+
+ /* The following is diagnosed by -Wformat (disabled here). */
+ /* T ("%" MAX_P1_STR "$i", 0); */
+}
+
+/* Verify that an excessively long directive is truncated and the truncation
+ is indicated by three trailing dots in the text of the warning. */
+
+void test_overlong_plain_string ()
+{
+ static const char longfmtstr[] =
+ "0123456789012345678901234567890123456789012345678901234567890123456789%%";
+
+ char d[1];
+ T (longfmtstr); /* { dg-warning ".0123\[0-9\]\*\.\.\.. directive writing 70 bytes" } */
+}