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