+static bool
+should_warn_p (const call_info &info,
+ const result_range &avail, const result_range &result)
+{
+ if (result.max <= avail.min)
+ {
+ /* The least amount of space remaining in the destination is big
+ enough for the longest output. */
+ return false;
+ }
+
+ if (info.bounded)
+ {
+ if (warn_format_trunc == 1 && result.min <= avail.max
+ && info.retval_used ())
+ {
+ /* The likely amount of space remaining in the destination is big
+ enough for the least output and the return value is used. */
+ return false;
+ }
+
+ if (warn_format_trunc == 1 && result.likely <= avail.likely
+ && !info.retval_used ())
+ {
+ /* The likely amount of space remaining in the destination is big
+ enough for the likely output and the return value is unused. */
+ return false;
+ }
+
+ if (warn_format_trunc == 2
+ && result.likely <= avail.min
+ && (result.max <= avail.min
+ || result.max > HOST_WIDE_INT_MAX))
+ {
+ /* The minimum amount of space remaining in the destination is big
+ enough for the longest output. */
+ return false;
+ }
+ }
+ else
+ {
+ if (warn_level == 1 && result.likely <= avail.likely)
+ {
+ /* The likely amount of space remaining in the destination is big
+ enough for the likely output. */
+ return false;
+ }
+
+ if (warn_level == 2
+ && result.likely <= avail.min
+ && (result.max <= avail.min
+ || result.max > HOST_WIDE_INT_MAX))
+ {
+ /* The minimum amount of space remaining in the destination is big
+ enough for the longest output. */
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* At format string location describe by DIRLOC in a call described
+ by INFO, issue a warning for a directive DIR whose output may be
+ in excess of the available space AVAIL_RANGE in the destination
+ given the formatting result FMTRES. This function does nothing
+ except decide whether to issue a warning for a possible write
+ past the end or truncation and, if so, format the warning.
+ Return true if a warning has been issued. */
+
+static bool
+maybe_warn (substring_loc &dirloc, location_t argloc,
+ const call_info &info,
+ const result_range &avail_range, const result_range &res,
+ const directive &dir)
+{
+ if (!should_warn_p (info, avail_range, res))
+ return false;
+
+ /* A warning will definitely be issued below. */
+
+ /* The maximum byte count to reference in the warning. Larger counts
+ imply that the upper bound is unknown (and could be anywhere between
+ RES.MIN + 1 and SIZE_MAX / 2) are printed as "N or more bytes" rather
+ than "between N and X" where X is some huge number. */
+ unsigned HOST_WIDE_INT maxbytes = target_dir_max ();
+
+ /* True when there is enough room in the destination for the least
+ amount of a directive's output but not enough for its likely or
+ maximum output. */
+ bool maybe = (res.min <= avail_range.max
+ && (avail_range.min < res.likely
+ || (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 (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
+ past the end of the destination. */
+ if (navail < dir.len)
+ dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+ }
+
+ if (*dir.beg == '\0')
+ {
+ /* This is the terminating nul. */
+ gcc_assert (res.min == 1 && res.min == res.max);
+
+ return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%qE output may be truncated before the "
+ "last format character")
+ : G_("%qE output truncated before the last "
+ "format character"))
+ : (maybe
+ ? G_("%qE may write a terminating nul past the "
+ "end of the destination")
+ : G_("%qE writing a terminating nul past the "
+ "end of the destination")),
+ info.func);
+ }
+
+ if (res.min == res.max)
+ {
+ const char *d = target_to_host (hostdir, sizeof hostdir, dir.beg);
+ if (!info.bounded)
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive writing %wu byte into a "
+ "region of size %wu",
+ "%<%.*s%> directive writing %wu bytes into a "
+ "region of size %wu",
+ (int) dir.len, d, res.min, navail);
+ else if (maybe)
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive output may be truncated "
+ "writing %wu byte into a region of size %wu",
+ "%<%.*s%> directive output may be truncated "
+ "writing %wu bytes into a region of size %wu",
+ (int) dir.len, d, res.min, navail);
+ else
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive output truncated writing "
+ "%wu byte into a region of size %wu",
+ "%<%.*s%> directive output truncated writing "
+ "%wu bytes into a region of size %wu",
+ (int) dir.len, d, res.min, navail);
+ }
+ if (res.min == 0 && res.max < maxbytes)
+ return fmtwarn (dirloc, argloc, NULL,
+ info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing up to %wu bytes into a region of "
+ "size %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "up to %wu bytes into a region of size %wu"))
+ : G_("%<%.*s%> directive writing up to %wu bytes "
+ "into a region of size %wu"), (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.max, navail);
+
+ if (res.min == 0 && maxbytes <= res.max)
+ /* This is a special case to avoid issuing the potentially
+ confusing warning:
+ writing 0 or more bytes into a region of size 0. */
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing likely %wu or more bytes into a "
+ "region of size %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "likely %wu or more bytes into a region of "
+ "size %wu"))
+ : G_("%<%.*s%> directive writing likely %wu or more "
+ "bytes into a region of size %wu"), (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.likely, navail);
+
+ if (res.max < maxbytes)
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing between %wu and %wu bytes into a "
+ "region of size %wu")
+ : G_("%<%.*s%> directive output truncated "
+ "writing between %wu and %wu bytes into a "
+ "region of size %wu"))
+ : G_("%<%.*s%> directive writing between %wu and "
+ "%wu bytes into a region of size %wu"),
+ (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, res.max, navail);
+
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing %wu or more bytes into a region of "
+ "size %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "%wu or more bytes into a region of size %wu"))
+ : G_("%<%.*s%> directive writing %wu or more bytes "
+ "into a region of size %wu"), (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, navail);
+ }
+
+ /* The size of the destination region is a range. */
+
+ if (target_to_host (*dir.beg) != '%')
+ {
+ unsigned HOST_WIDE_INT navail = avail_range.max;
+
+ /* For plain character directives (i.e., the format string itself)
+ but not others, point the caret at the first character that's
+ past the end of the destination. */
+ if (navail < dir.len)
+ dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+ }
+
+ if (*dir.beg == '\0')
+ {
+ gcc_assert (res.min == 1 && res.min == res.max);
+
+ return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%qE output may be truncated before the last "
+ "format character")
+ : G_("%qE output truncated before the last format "
+ "character"))
+ : (maybe
+ ? G_("%qE may write a terminating nul past the end "
+ "of the destination")
+ : G_("%qE writing a terminating nul past the end "
+ "of the destination")), info.func);
+ }
+
+ if (res.min == res.max)
+ {
+ const char *d = target_to_host (hostdir, sizeof hostdir, dir.beg);
+ if (!info.bounded)
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive writing %wu byte into a region "
+ "of size between %wu and %wu",
+ "%<%.*s%> directive writing %wu bytes into a region "
+ "of size between %wu and %wu", (int) dir.len, d,
+ res.min, avail_range.min, avail_range.max);
+ else if (maybe)
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive output may be truncated writing "
+ "%wu byte into a region of size between %wu and %wu",
+ "%<%.*s%> directive output may be truncated writing "
+ "%wu bytes into a region of size between %wu and "
+ "%wu", (int) dir.len, d, res.min, avail_range.min,
+ avail_range.max);
+ else
+ return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+ "%<%.*s%> directive output truncated writing %wu "
+ "byte into a region of size between %wu and %wu",
+ "%<%.*s%> directive output truncated writing %wu "
+ "bytes into a region of size between %wu and %wu",
+ (int) dir.len, d, res.min, avail_range.min,
+ avail_range.max);
+ }
+
+ if (res.min == 0 && res.max < maxbytes)
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing up to %wu bytes into a region of size "
+ "between %wu and %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "up to %wu bytes into a region of size between "
+ "%wu and %wu"))
+ : G_("%<%.*s%> directive writing up to %wu bytes "
+ "into a region of size between %wu and %wu"),
+ (int) 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)
+ /* This is a special case to avoid issuing the potentially confusing
+ warning:
+ writing 0 or more bytes into a region of size between 0 and N. */
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing likely %wu or more bytes into a region "
+ "of size between %wu and %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "likely %wu or more bytes into a region of size "
+ "between %wu and %wu"))
+ : G_("%<%.*s%> directive writing likely %wu or more bytes "
+ "into a region of size between %wu and %wu"),
+ (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.likely, avail_range.min, avail_range.max);
+
+ if (res.max < maxbytes)
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated "
+ "writing between %wu and %wu bytes into a region "
+ "of size between %wu and %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "between %wu and %wu bytes into a region of size "
+ "between %wu and %wu"))
+ : G_("%<%.*s%> directive writing between %wu and "
+ "%wu bytes into a region of size between %wu and "
+ "%wu"), (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, res.max, avail_range.min, avail_range.max);
+
+ return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ info.bounded
+ ? (maybe
+ ? G_("%<%.*s%> directive output may be truncated writing "
+ "%wu or more bytes into a region of size between "
+ "%wu and %wu")
+ : G_("%<%.*s%> directive output truncated writing "
+ "%wu or more bytes into a region of size between "
+ "%wu and %wu"))
+ : G_("%<%.*s%> directive writing %wu or more bytes "
+ "into a region of size between %wu and %wu"),
+ (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ res.min, avail_range.min, avail_range.max);
+}
+
+/* Given the formatting result described by RES and NAVAIL, the number
+ of available in the destination, return the range of bytes remaining
+ in the destination. */
+
+static inline result_range
+bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+ result_range range;
+
+ if (HOST_WIDE_INT_MAX <= navail)
+ {
+ range.min = range.max = range.likely = range.unlikely = navail;
+ return range;
+ }
+
+ /* The lower bound of the available range is the available size
+ minus the maximum output size, and the upper bound is the size
+ minus the minimum. */
+ range.max = res.range.min < navail ? navail - res.range.min : 0;
+
+ range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
+
+ if (res.range.max < HOST_WIDE_INT_MAX)
+ range.min = res.range.max < navail ? navail - res.range.max : 0;
+ else
+ range.min = range.likely;
+
+ range.unlikely = (res.range.unlikely < navail
+ ? navail - res.range.unlikely : 0);
+
+ return range;
+}
+
+/* Compute the length of the output resulting from the directive DIR
+ in a call described by INFO and update the overall result of the call
+ in *RES. Return true if the directive has been handled. */
+
+static bool
+format_directive (const call_info &info,
+ format_result *res, const directive &dir,
+ const class vr_values *vr_values)