gcc/ChangeLog:
PR middle-end/77696
* gimple-ssa-sprintf.c: Include "gcc-rich-location.h".
(struct directive_state): New struct.
(directive_state::get_text): New function.
(format_result::add_directive): New member function.
(format_result::should_warn): New field.
(format_result::m_directives): New field.
(fmtwarn_n): Delete.
(maybe_warn): Delete.
(format_directive): Add param "is_nul_terminator". Rename locals
"start" and "length" to "start_idx" and "end_idx" respectively.
Call res->add_directive. Eliminate call to maybe_warn in favor of
calling should_warn_p and updating res->should_warn.
Eliminate notes.
(class rich_format_location): New class.
(rich_format_location::flyweight_range_label::get_text): New
function.
(rich_format_location::get_label_for_buffer): New function.
(rich_format_location::get_label_for_directive): New function.
(sprintf_dom_walker::compute_format_length): Add param "dst_ptr".
Retain param "callloc". Initialize res->should_warn. Add local
"is_nul_terminator" and pass to format_directive. Rather than
returning at the end of the format string, check for res->should_warn
and emit warnings using rich_format_location.
(sprintf_dom_walker::handle_gimple_call): Pass in dst_ptr to
compute_format_length.
gcc/testsuite/ChangeLog:
PR middle-end/77696
* gcc.dg/sprintf-diagnostics-1.c: New test.
* gcc.dg/sprintf-diagnostics-2.c: New test.
---
gcc/gimple-ssa-sprintf.c | 646 ++++++++++++---------------
gcc/testsuite/gcc.dg/sprintf-diagnostics-1.c | 252 +++++++++++
gcc/testsuite/gcc.dg/sprintf-diagnostics-2.c | 57 +++
3 files changed, 597 insertions(+), 358 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/sprintf-diagnostics-1.c
create mode 100644 gcc/testsuite/gcc.dg/sprintf-diagnostics-2.c
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index ab430fe..fb6268c 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -83,6 +83,7 @@ along with GCC; see the file COPYING3. If not see
#include "alloc-pool.h"
#include "vr-values.h"
#include "gimple-ssa-evrp-analyze.h"
+#include "gcc-rich-location.h"
/* The likely worst case value of MB_LEN_MAX for the target, large enough
for UTF-8. Ideally, this would be obtained by a target hook if it were
@@ -128,7 +129,7 @@ class sprintf_dom_walker : public dom_walker
void after_dom_children (basic_block) FINAL OVERRIDE;
bool handle_gimple_call (gimple_stmt_iterator *);
- bool compute_format_length (call_info &, format_result *);
+ bool compute_format_length (call_info &, format_result *, tree);
class evrp_range_analyzer evrp_range_analyzer;
};
@@ -193,10 +194,67 @@ struct result_range
unsigned HOST_WIDE_INT unlikely;
};
+/* Information about a particular directive (or the NUL terminator)
+ within a format string, for use when emitting warnings. */
+
+struct directive_state
+{
+ directive_state (size_t fmt_start_idx, size_t fmt_end_idx,
+ bool is_nul_terminator,
+ unsigned HOST_WIDE_INT min_size,
+ unsigned HOST_WIDE_INT max_size)
+ : m_fmt_start_idx (fmt_start_idx), m_fmt_end_idx (fmt_end_idx),
+ m_is_nul_terminator (is_nul_terminator),
+ m_min_size (min_size), m_max_size (max_size)
+ {}
+
+ label_text get_text () const;
+
+ size_t m_fmt_start_idx;
+ size_t m_fmt_end_idx;
+ bool m_is_nul_terminator;
+ unsigned HOST_WIDE_INT m_min_size;
+ unsigned HOST_WIDE_INT m_max_size;
+};
+
+/* Generate a label for the directive within the format string, describing
+ the size consumed by the output, and highlighting the NUL terminator. */
+
+label_text
+directive_state::get_text () const
+{
+ pretty_printer pp_range;
+ bool singular = pp_humanized_range (&pp_range, m_min_size, m_max_size);
+ pretty_printer pp;
+ // FIXME: i18n for singular/plural
+ if (singular)
+ pp_printf (&pp, G_("%s byte"),
+ pp_formatted_text (&pp_range));
+ else
+ pp_printf (&pp, G_("%s bytes"),
+ pp_formatted_text (&pp_range));
+ if (m_is_nul_terminator)
+ pp_string (&pp, G_(" (for NUL terminator)"));
+ return label_text (xstrdup (pp_formatted_text (&pp)), true);
+}
+
/* The result of a call to a formatted function. */
struct format_result
{
+ /* Record a directive from FMT_START_IDX through FMT_END_IDX,
+ and whether it's the nul terminator, consuming between
+ MIN_SIZE and MAX_SIZE bytes when output. */
+ void add_directive (size_t fmt_start_idx, size_t fmt_end_idx,
+ bool is_nul_terminator,
+ unsigned HOST_WIDE_INT min_size,
+ unsigned HOST_WIDE_INT max_size)
+ {
+ m_directives.safe_push (directive_state (fmt_start_idx, fmt_end_idx,
+ is_nul_terminator,
+ min_size, max_size));
+ }
+
/* Range of characters written by the formatted function.
Setting the minimum to HOST_WIDE_INT_MAX disables all
length tracking for the remainder of the format string. */
@@ -229,6 +287,13 @@ struct format_result
of a call. WARNED also disables the return value optimization. */
bool warned;
+ /* True when we have an overflow or truncation. */
+ bool should_warn;
+
+ /* The directives we've seen: locations and sizes of output, in case
+ we need to emit a warning. */
+ auto_vec<directive_state> m_directives;
+
/* Preincrement the number of output characters by 1. */
format_result& operator++ ()
{
@@ -475,23 +540,6 @@ fmtwarn (const substring_loc &fmt_loc, location_t
param_loc,
return warned;
}
-static bool
-ATTRIBUTE_GCC_DIAG (6, 8) ATTRIBUTE_GCC_DIAG (7, 8)
-fmtwarn_n (const substring_loc &fmt_loc, location_t param_loc,
- const char *corrected_substring, int opt, unsigned HOST_WIDE_INT n,
- const char *singular_gmsgid, const char *plural_gmsgid, ...)
-{
- format_string_diagnostic_t diag (fmt_loc, NULL, param_loc, NULL,
- corrected_substring);
- va_list ap;
- va_start (ap, plural_gmsgid);
- bool warned = diag.emit_warning_n_va (opt, n, singular_gmsgid, plural_gmsgid,
- &ap);
- va_end (ap);
-
- return warned;
-}
-
/* Format length modifiers. */
enum format_lengths
@@ -2394,310 +2442,27 @@ should_warn_p (const call_info &info,
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);
-}
-
/* 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. */
+ in *RES. Return true if the directive has been handled.
+ IS_NUL_TERMINATOR is true iff the "directive" is the NUL terminator. */
static bool
format_directive (const call_info &info,
format_result *res, const directive &dir,
- class vr_values *vr_values)
+ class vr_values *vr_values,
+ bool is_nul_terminator)
{
/* Offset of the beginning of the directive from the beginning
of the format string. */
size_t offset = dir.beg - info.fmtstr;
- size_t start = offset;
- size_t length = offset + dir.len - !!dir.len;
+ size_t start_idx = offset;
+ size_t end_idx = offset + dir.len - !!dir.len;
/* Create a location for the whole directive from the % to the format
specifier. */
substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
- offset, start, length);
+ offset, start_idx, end_idx);
/* Also get the location of the argument if possible.
This doesn't work for integer literals or function calls. */
@@ -2713,6 +2478,10 @@ format_directive (const call_info &info,
/* Compute the range of lengths of the formatted output. */
fmtresult fmtres = dir.fmtfunc (dir, dir.arg, vr_values);
+ // (FIXME: or maybe record the whole of fmtres?)
+ res->add_directive (start_idx, end_idx, is_nul_terminator,
+ fmtres.range.min, fmtres.range.max);
+
/* Record whether the output of all directives is known to be
bounded by some maximum, implying that their arguments are
either known exactly or determined to be in a known range
@@ -2776,11 +2545,8 @@ format_directive (const call_info &info,
NUL that's appended after the format string has been processed. */
result_range avail_range = bytes_remaining (info.objsize, *res);
- bool warned = res->warned;
-
- if (!warned)
- warned = maybe_warn (dirloc, argloc, info, avail_range,
- fmtres.range, dir);
+ if (should_warn_p (info, avail_range, fmtres.range))
+ res->should_warn = true;
/* Bump up the total maximum if it isn't too big. */
if (res->range.max < HOST_WIDE_INT_MAX
@@ -2814,6 +2580,8 @@ format_directive (const call_info &info,
if (fmtres.mayfail)
res->posunder4k = false;
+ bool warned = res->warned;
+
if (!warned
/* Only warn at level 2. */
&& warn_level > 1
@@ -2907,39 +2675,6 @@ format_directive (const call_info &info,
res->warned |= warned;
- if (!dir.beg[0] && res->warned && info.objsize < HOST_WIDE_INT_MAX)
- {
- /* If a warning has been issued for buffer overflow or truncation
- (but not otherwise) help the user figure out how big a buffer
- they need. */
-
- location_t callloc = gimple_location (info.callstmt);
-
- unsigned HOST_WIDE_INT min = res->range.min;
- unsigned HOST_WIDE_INT max = res->range.max;
-
- if (min == max)
- inform (callloc,
- (min == 1
- ? G_("%qE output %wu byte into a destination of size %wu")
- : G_("%qE output %wu bytes into a destination of size %wu")),
- info.func, min, info.objsize);
- else if (max < HOST_WIDE_INT_MAX)
- inform (callloc,
- "%qE output between %wu and %wu bytes into "
- "a destination of size %wu",
- info.func, min, max, info.objsize);
- else if (min < res->range.likely && res->range.likely < max)
- inform (callloc,
- "%qE output %wu or more bytes (assuming %wu) into "
- "a destination of size %wu",
- info.func, min, res->range.likely, info.objsize);
- else
- inform (callloc,
- "%qE output %wu or more bytes into a destination of size %wu",
- info.func, min, info.objsize);
- }
-
if (dump_file && *dir.beg)
{
fprintf (dump_file,
@@ -3396,20 +3131,109 @@ parse_directive (call_info &info,
return dir.len;
}
+/* Subclass of gcc_rich_location for emitting warnings about sprintf and
+ snprintf. */
+
+class rich_format_location : public gcc_rich_location
+{
+ public:
+ rich_format_location (location_t dst_loc, const call_info &info,
+ const format_result &res)
+ : gcc_rich_location (dst_loc, &m_label),
+ m_info (info), m_res (res),
+ m_label (*this)
+ {
+ unsigned i;
+ directive_state *dir_state;
+ FOR_EACH_VEC_ELT (m_res.m_directives, i, dir_state)
+ {
+ substring_loc dirloc (m_info.fmtloc, TREE_TYPE (m_info.format),
+ dir_state->m_fmt_start_idx,
+ dir_state->m_fmt_start_idx,
+ dir_state->m_fmt_end_idx);
+ location_t loc = UNKNOWN_LOCATION;
+ dirloc.get_location (&loc);
+ add_range (loc, SHOW_RANGE_WITH_CARET, &m_label);
+ }
+ }
+
+ private:
+ /* Subclass of range_label for labelling all of the various ranges. */
+ class flyweight_range_label : public range_label
+ {
+ public:
+ flyweight_range_label (const rich_format_location &rich_loc)
+ : m_rich_loc (rich_loc) {}
+
+ label_text get_text (unsigned range_idx) const FINAL OVERRIDE;
+ private:
+ const rich_format_location &m_rich_loc;
+ };
+ friend class flyweight_range_label;
+
+ label_text get_label_for_buffer () const;
+ label_text get_label_for_directive (unsigned dir_idx) const;
+
+ const call_info &m_info;
+ const format_result &m_res;
+ flyweight_range_label m_label;
+};
+
+/* Implementation of range_label::get_text for
+ rich_format_location::flyweight_range_label.
+ Handle all of the various labels with one instance by dispatching based on
+ RANGE_IDX. */
+
+label_text
+rich_format_location::flyweight_range_label::get_text (unsigned range_idx)
const
+{
+ if (range_idx == 0)
+ return m_rich_loc.get_label_for_buffer ();
+ else
+ return m_rich_loc.get_label_for_directive (range_idx - 1);
+}
+
+/* Get a label for the underlined destination buffer, showing the capacity of
+ the buffer. */
+
+label_text
+rich_format_location::get_label_for_buffer () const
+{
+ pretty_printer pp;
+ // FIXME: i18n for singular/plural
+ if (m_info.objsize == 1)
+ pp_printf (&pp, G_("capacity: %wu byte"), m_info.objsize);
+ else
+ pp_printf (&pp, G_("capacity: %wu bytes"), m_info.objsize);
+ return label_text (xstrdup (pp_formatted_text (&pp)), true);
+}
+
+/* Get a label for the underlined directive, showing the size of the output
+ from that directive. */
+
+label_text
+rich_format_location::get_label_for_directive (unsigned dir_idx) const
+{
+ const directive_state &dir_state = m_res.m_directives[dir_idx];
+ return dir_state.get_text ();
+}
+
/* Compute the length of the output resulting from the call to a formatted
output function described by INFO and store the result of the call in
*RES. Issue warnings for detected past the end writes. Return true
if the complete format string has been processed and *RES can be relied
on, false otherwise (e.g., when a unknown or unhandled directive was seen
- that caused the processing to be terminated early). */
+ that caused the processing to be terminated early). DST_PTR is the
destination
+ of the output. */
bool
sprintf_dom_walker::compute_format_length (call_info &info,
- format_result *res)
+ format_result *res,
+ tree dst_ptr)
{
+ location_t callloc = gimple_location (info.callstmt);
if (dump_file)
{
- location_t callloc = gimple_location (info.callstmt);
fprintf (dump_file, "%s:%i: ",
LOCATION_FILE (callloc), LOCATION_LINE (callloc));
print_generic_expr (dump_file, info.func, dump_flags);
@@ -3430,6 +3254,7 @@ sprintf_dom_walker::compute_format_length (call_info
&info,
res->posunder4k = true;
res->floating = false;
res->warned = false;
+ res->should_warn = false;
/* 1-based directive counter. */
unsigned dirno = 1;
@@ -3437,6 +3262,8 @@ sprintf_dom_walker::compute_format_length (call_info
&info,
/* The variadic argument counter. */
unsigned argno = info.argidx;
+ bool is_nul_terminator = false;
+
for (const char *pf = info.fmtstr; ; ++dirno)
{
directive dir = directive ();
@@ -3445,23 +3272,126 @@ sprintf_dom_walker::compute_format_length (call_info
&info,
size_t n = parse_directive (info, dir, res, pf, &argno,
evrp_range_analyzer.get_vr_values ());
+ is_nul_terminator = !n && *pf == '\0';
+
+ fmtresult fmtres;
+
/* Return failure if the format function fails. */
if (!format_directive (info, res, dir,
- evrp_range_analyzer.get_vr_values ()))
+ evrp_range_analyzer.get_vr_values (),
+ is_nul_terminator))
return false;
- /* Return success the directive is zero bytes long and it's
- the last think in the format string (i.e., it's the terminating
- nul, which isn't really a directive but handling it as one makes
- things simpler). */
+ /* Stop at the end of the format string. */
if (!n)
- return *pf == '\0';
+ break;
pf += n;
}
- /* The complete format string was processed (with or without warnings). */
- return true;
+ if (res->should_warn && info.objsize < HOST_WIDE_INT_MAX)
+ {
+ unsigned HOST_WIDE_INT min = res->range.min;
+ unsigned HOST_WIDE_INT max = res->range.max;
+ bool warned;
+
+ /* Build the rich location for diagnostics. */
+ location_t dst_loc = UNKNOWN_LOCATION;
+ if (dst_ptr != NULL_TREE && EXPR_HAS_LOCATION (dst_ptr))
+ dst_loc = EXPR_LOCATION (dst_ptr);
+ location_t primary_loc;
+ if (dst_loc != UNKNOWN_LOCATION)
+ primary_loc = dst_loc;
+ else
+ primary_loc = get_start (callloc);
+ rich_format_location rich_loc (primary_loc, info, *res);
+
+ /* Truncation vs buffer overflow. */
+ if (info.bounded)
+ {
+ /* Emit a "truncated output" warning. */
+ if (min == max)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ (min == 1
+ ? G_("%qE will truncate the output (%wu byte) to size %wu")
+ : G_("%qE will truncate the output (%wu bytes) to size %wu")),
+ info.func, min, info.objsize);
+ else if (max < HOST_WIDE_INT_MAX)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "%qE will truncate the output (between %wu and %wu bytes) "
+ "to size %wu",
+ info.func, min, max, info.objsize);
+ else if (min < res->range.likely && res->range.likely < max)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "%qE will truncate the output (%wu or more bytes, assuming %wu) "
+ "to size %wu",
+ info.func, min, res->range.likely, info.objsize);
+ else
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "FIXME: the other truncation case for %qE) ",
+ info.func);
+ // FIXME: what's the other case here?
+
+ // FIXME: what about wrong size limit?
+ // FIXME: should we highlight where the size comes for sizeof exprs?
+ }
+ else
+ {
+ /* Emit a "buffer overflow" warning. */
+ auto_diagnostic_group d;
+ if (min == max)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ (min == 1
+ ? G_("buffer overflow: %qE will write %wu byte"
+ " into a destination of size %wu")
+ : G_("buffer overflow: %qE will write %wu bytes"
+ " into a destination of size %wu")),
+ info.func, min, info.objsize);
+ else if (max < HOST_WIDE_INT_MAX)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "buffer overflow: %qE will write between %wu and %wu bytes into "
+ "a destination of size %wu",
+ info.func, min, max, info.objsize);
+ else if (min < res->range.likely && res->range.likely < max)
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "buffer overflow: %qE will write %wu or more bytes"
+ " (assuming %wu) into a destination of size %wu",
+ info.func, min, res->range.likely, info.objsize);
+ else
+ warned = warning_at
+ (&rich_loc, info.warnopt (),
+ "buffer overflow: %qE will write %wu or more bytes"
+ " into a destination of size %wu",
+ info.func, min, info.objsize);
+ }
+ if (warned)
+ {
+ /* If possible, emit a note showing the declaration of the
+ destination buffer. */
+ // FIXME: possibly use "if nearby" to add as another location to main
warning?
+ if (TREE_CODE (dst_ptr) == ADDR_EXPR)
+ {
+ tree pt_var = TREE_OPERAND (dst_ptr, 0);
+ if (DECL_P (pt_var))
+ inform (DECL_SOURCE_LOCATION (pt_var),
+ "destination declared here");
+ }
+ }
+ }
+
+ /* Return success if the final "directive" is zero bytes long and it's
+ the last think in the format string (i.e., it's the terminating
+ nul, which isn't really a directive but handling it as one makes
+ things simpler).
+ This isn't affected by whether we emitted warnings. */
+ return is_nul_terminator;
}
/* Return the size of the object referenced by the expression DEST if
@@ -3932,7 +3862,7 @@ sprintf_dom_walker::handle_gimple_call
(gimple_stmt_iterator *gsi)
including the terminating NUL. */
format_result res = format_result ();
- bool success = compute_format_length (info, &res);
+ bool success = compute_format_length (info, &res, dstptr);
/* When optimizing and the printf return value optimization is enabled,
attempt to substitute the computed result for the return value of
diff --git a/gcc/testsuite/gcc.dg/sprintf-diagnostics-1.c
b/gcc/testsuite/gcc.dg/sprintf-diagnostics-1.c
new file mode 100644
index 0000000..c807b2d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sprintf-diagnostics-1.c
@@ -0,0 +1,252 @@
+/* { dg-do compile } */
+/* { dg-options "-Wformat-overflow=2 -Wformat-truncation=2 -O2
-fdiagnostics-show-caret" } */
+
+typedef __SIZE_TYPE__ size_t;
+extern int sprintf (char*, const char*, ...);
+extern int snprintf (char*, size_t, const char*, ...);
+
+/* Bounded, definite truncation in a directive. */
+
+void test_1 (int i)
+{
+ char buf_1[4]; /* { dg-message "destination declared here" } */
+ snprintf (buf_1, sizeof buf_1, "%i", 1235); /* { dg-warning "'snprintf' will
truncate the output \\(5 bytes\\) to size 4" } */
+ /* { dg-begin-multiline-output "" }
+ snprintf (buf_1, sizeof buf_1, "%i", 1235);
+ ^~~~~ ^~^
+ | | |
+ | | 1 byte (for NUL terminator)
+ capacity: 4 bytes 4 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_1[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* Bounded, definite truncation copying format string. */
+
+void test_2 (int i)
+{
+ char buf_2[4]; /* { dg-message "destination declared here" } */
+ snprintf (buf_2, sizeof buf_2, "%iAB", 123); /* { dg-warning "'snprintf'
will truncate the output \\(6 bytes\\) to size 4" } */
+ /* { dg-begin-multiline-output "" }
+ snprintf (buf_2, sizeof buf_2, "%iAB", 123);
+ ^~~~~ ^~^~^
+ | | | |
+ | | | 1 byte (for NUL terminator)
+ | | 2 bytes
+ capacity: 4 bytes 3 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_2[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* unbounded, definite overflow in a directive. */
+
+void test_3 (int i)
+{
+ char buf_3[4]; /* { dg-message "destination declared here" } */
+ sprintf (buf_3, "%i", 1235); /* { dg-warning "buffer overflow: 'sprintf'
will write 5 bytes into a destination of size 4" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_3, "%i", 1235);
+ ^~~~~ ^~^
+ | | |
+ | | 1 byte (for NUL terminator)
+ | 4 bytes
+ capacity: 4 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_3[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* unbounded, definite overflow copying format string. */
+
+void test_4 (int i)
+{
+ char buf_4[4]; /* { dg-message "destination declared here" } */
+ sprintf (buf_4, "%iAB", 123); /* { dg-warning "buffer overflow: 'sprintf'
will write 6 bytes into a destination of size 4" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_4, "%iAB", 123);
+ ^~~~~ ^~^~^
+ | | | |
+ | | | 1 byte (for NUL terminator)
+ | | 2 bytes
+ | 3 bytes
+ capacity: 4 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_4[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* bounded, possible truncation a directve. */
+
+void test_5 (int i)
+{
+ char buf_5[4]; /* { dg-message "destination declared here" } */
+ snprintf (buf_5, sizeof buf_5, "%i", i); /* { dg-warning "'snprintf' will
truncate the output \\(between 2 and 12 bytes\\) to size 4" } */
+ /* { dg-begin-multiline-output "" }
+ snprintf (buf_5, sizeof buf_5, "%i", i);
+ ^~~~~ ^~^
+ | | |
+ | | 1 byte (for NUL terminator)
+ capacity: 4 bytes 1...11 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_5[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* bounded, possible overflow copying format string. */
+
+void test_6 (int i)
+{
+ char buf_6[4]; /* { dg-message "destination declared here" } */
+ snprintf (buf_6, sizeof buf_6, "%iAB", i); /* { dg-warning "'snprintf' will
truncate the output \\(between 4 and 14 bytes\\) to size 4" } */
+ /* { dg-begin-multiline-output "" }
+ snprintf (buf_6, sizeof buf_6, "%iAB", i);
+ ^~~~~ ^~^~^
+ | | | |
+ | | | 1 byte (for NUL terminator)
+ | | 2 bytes
+ capacity: 4 bytes 1...11 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_6[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* unbounded, possible overflow in a directve. */
+
+void test_7 (int i)
+{
+ char buf_7[4]; /* { dg-message "destination declared here" } */
+ sprintf (buf_7, "%i", i); /* { dg-warning "buffer overflow: 'sprintf' will
write between 2 and 12 bytes into a destination of size 4" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_7, "%i", i);
+ ^~~~~ ^~^
+ | | |
+ | | 1 byte (for NUL terminator)
+ | 1...11 bytes
+ capacity: 4 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_7[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+/* unbounded, possible overflow copying format string. */
+
+void test_8 (int i)
+{
+ char buf_8[4];
+ sprintf (buf_8, "%iAB", 123);
+ /* FIXME: why isn't this generating a warning? */
+}
+
+/* unbounded, possible overflow copying format string. */
+
+void test_9 (int i)
+{
+ char buf_9[4]; /* { dg-message "destination declared here" } */
+ const char *s = i ? "123" : "1234";
+ sprintf (buf_9, "%sAB", s); /* { dg-warning "buffer overflow: 'sprintf' will
write between 6 and 7 bytes into a destination of size 4" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_9, "%sAB", s);
+ ^~~~~ ^~^~^
+ | | | |
+ | | | 1 byte (for NUL terminator)
+ | | 2 bytes
+ | 3...4 bytes
+ capacity: 4 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_9[4];
+ ^~~~~
+ { dg-end-multiline-output "" } */
+}
+
+extern char buf_10[80];
+extern char tmpdir[80];
+extern char fname[8];
+
+void test_10 (int num)
+{
+ sprintf (buf_10, "/%s/%s-%i.tmp", tmpdir, fname, num); /* { dg-warning
"buffer overflow: 'sprintf' will write between 9 and 105 bytes into a
destination of size 80" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_10, "/%s/%s-%i.tmp", tmpdir, fname, num);
+ ^~~~~~ ^^~^^~^^~^~~~^
+ | || || || | |
+ | || || || | 1 byte (for NUL terminator)
+ | || || || 4 bytes
+ | || || |1...11 bytes
+ | || || 1 byte
+ | || |0...7 bytes
+ | || 1 byte
+ | |0...79 bytes
+ | 1 byte
+ capacity: 80 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ extern char buf_10[80];
+ ^~~~~~
+ { dg-end-multiline-output "" } */
+}
+
+struct MyStrings {
+ char a[8], b[20];
+};
+
+const struct MyStrings ms[] = {
+ { "foo", "bar" }, { "abcd", "klmno" }
+};
+
+void test_11 (void)
+{
+ char buf_11[6];
+ sprintf (buf_11, "msg: %s\n", ms[1].b); /* { dg-warning "buffer overflow:
'sprintf' will write 12 bytes into a destination of size 6" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_11, "msg: %s\n", ms[1].b);
+ ^~~~~~ ^~~~~^~^~^
+ | | | | |
+ | | | | 1 byte (for NUL terminator)
+ | | | 1 byte
+ | | 5 bytes
+ | 5 bytes
+ capacity: 6 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_11[6];
+ ^~~~~~
+ { dg-end-multiline-output "" } */
+}
+
+void test_12 (int idx)
+{
+ char buf_12[6];
+ sprintf (buf_12, "msg: %s\n", ms[idx].b); /* { dg-warning "buffer overflow:
'sprintf' will write 7 or more bytes \\(assuming 8\\) into a destination of
size 6" } */
+ // FIXME: this has a 64-bit assumption
+ /* { dg-begin-multiline-output "" }
+ sprintf (buf_12, "msg: %s\n", ms[idx].b);
+ ^~~~~~ ^~~~~^~^~^
+ | | | | |
+ | | | | 1 byte (for NUL terminator)
+ | | | 1 byte
+ | | 0...(1<<63)-1 bytes
+ | 5 bytes
+ capacity: 6 bytes
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ char buf_12[6];
+ ^~~~~~
+ { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/sprintf-diagnostics-2.c
b/gcc/testsuite/gcc.dg/sprintf-diagnostics-2.c
new file mode 100644
index 0000000..69482ee
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sprintf-diagnostics-2.c
@@ -0,0 +1,57 @@
+/* TODO: finish these test cases. */
+
+/* { dg-do compile } */
+/* { dg-options "-Wformat-overflow=2 -Wformat-truncation=2 -O2
-fdiagnostics-show-caret" } */
+
+typedef __SIZE_TYPE__ size_t;
+extern int sprintf (char*, const char*, ...);
+extern int snprintf (char*, size_t, const char*, ...);
+
+void test_13 (const char *msg)
+{
+ char buf[16];
+
+ const char *fmt = "msg: %s\n";
+ sprintf (buf, fmt, msg);
+ /* { dg-begin-multiline-output "" }
+ { dg-end-multiline-output "" } */
+}
+
+void test_14 (void)
+{
+#define INT_FMT "%i"
+ char buf_14[4]; /* { dg-message "destination declared here" } */
+ sprintf (buf_14, INT_FMT "AB", 123);
+ /* { dg-begin-multiline-output "" }
+ { dg-end-multiline-output "" } */
+}
+
+void test_15 (void)
+{
+#define FMT "%i"
+ char buf[16];
+ sprintf (buf, "i: " FMT " j: " FMT " k: " FMT "\n", 1066, 1215, 1649);
+ /* { dg-begin-multiline-output "" }
+ { dg-end-multiline-output "" } */
+}
+
+void test_non_contiguous_strings (void)
+{
+ char buf[4]; /* { dg-message "destination declared here" } */
+ sprintf(buf, " %" "i ", 65536);
+ /* { dg-begin-multiline-output "" }
+ { dg-end-multiline-output "" } */
+}
+
+void test_non_contiguous_strings_2 (void)
+{
+ char buf[4]; /* { dg-message "destination declared here" } */
+ sprintf(buf, " %"
+ "i ", 65536);
+ /* FIXME: looks like ICF merges this with the above and gets the wrong
location. */
+ /* { dg-begin-multiline-output "" }
+ { dg-end-multiline-output "" } */
+}
+
+/* FIXME: see gcc.dg/format/diagnostic-ranges.c
+ FIXME: try with C++. */
--
1.8.5.3