-/* Copyright (C) 2016-2017 Free Software Foundation, Inc.
+/* Copyright (C) 2016-2020 Free Software Foundation, Inc.
Contributed by Martin Sebor <msebor@redhat.com>.
This file is part of GCC.
#include "gimple-iterator.h"
#include "tree-ssa.h"
#include "tree-object-size.h"
-#include "params.h"
#include "tree-cfg.h"
#include "tree-ssa-propagate.h"
#include "calls.h"
#include "cfgloop.h"
+#include "tree-scalar-evolution.h"
+#include "tree-ssa-loop.h"
#include "intl.h"
#include "langhooks.h"
+#include "attribs.h"
#include "builtins.h"
#include "stor-layout.h"
#include "substring-locations.h"
#include "diagnostic.h"
#include "domwalk.h"
+#include "alloc-pool.h"
+#include "vr-values.h"
+#include "tree-ssa-strlen.h"
+#include "tree-dfa.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
namespace {
-const pass_data pass_data_sprintf_length = {
- GIMPLE_PASS, // pass type
- "printf-return-value", // pass name
- OPTGROUP_NONE, // optinfo_flags
- TV_NONE, // tv_id
- PROP_cfg, // properties_required
- 0, // properties_provided
- 0, // properties_destroyed
- 0, // properties_start
- 0, // properties_finish
-};
-
/* Set to the warning level for the current function which is equal
either to warn_format_trunc for bounded functions or to
warn_format_overflow otherwise. */
static int warn_level;
-struct format_result;
-
-class sprintf_dom_walker : public dom_walker
-{
- public:
- sprintf_dom_walker () : dom_walker (CDI_DOMINATORS) {}
- ~sprintf_dom_walker () {}
-
- edge before_dom_children (basic_block) FINAL OVERRIDE;
- bool handle_gimple_call (gimple_stmt_iterator *);
-
- struct call_info;
- bool compute_format_length (call_info &, format_result *);
-};
-
-class pass_sprintf_length : public gimple_opt_pass
-{
- bool fold_return_value;
-
-public:
- pass_sprintf_length (gcc::context *ctxt)
- : gimple_opt_pass (pass_data_sprintf_length, ctxt),
- fold_return_value (false)
- { }
-
- opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
-
- virtual bool gate (function *);
-
- virtual unsigned int execute (function *);
-
- void set_pass_param (unsigned int n, bool param)
- {
- gcc_assert (n == 0);
- fold_return_value = param;
- }
-
-};
-
-bool
-pass_sprintf_length::gate (function *)
-{
- /* Run the pass iff -Warn-format-overflow or -Warn-format-truncation
- is specified and either not optimizing and the pass is being invoked
- early, or when optimizing and the pass is being invoked during
- optimization (i.e., "late"). */
- return ((warn_format_overflow > 0
- || warn_format_trunc > 0
- || flag_printf_return_value)
- && (optimize > 0) == fold_return_value);
-}
-
/* The minimum, maximum, likely, and unlikely maximum number of bytes
of output either a formatting function or an individual directive
can result in. */
unsigned HOST_WIDE_INT unlikely;
};
-/* The result of a call to a formatted function. */
-
-struct format_result
-{
- /* 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. */
- result_range range;
-
- /* True when the range above is obtained from known values of
- directive arguments, or bounds on the amount of output such
- as width and precision, and not the result of heuristics that
- depend on warning levels. It's used to issue stricter diagnostics
- in cases where strings of unknown lengths are bounded by the arrays
- they are determined to refer to. KNOWNRANGE must not be used for
- the return value optimization. */
- bool knownrange;
-
- /* True if no individual directive resulted in more than 4095 bytes
- of output (the total NUMBER_CHARS_{MIN,MAX} might be greater).
- Implementations are not required to handle directives that produce
- more than 4K bytes (leading to undefined behavior) and so when one
- is found it disables the return value optimization. */
- bool under4k;
-
- /* True when a floating point directive has been seen in the format
- string. */
- bool floating;
-
- /* True when an intermediate result has caused a warning. Used to
- avoid issuing duplicate warnings while finishing the processing
- of a call. WARNED also disables the return value optimization. */
- bool warned;
-
- /* Preincrement the number of output characters by 1. */
- format_result& operator++ ()
- {
- return *this += 1;
- }
-
- /* Postincrement the number of output characters by 1. */
- format_result operator++ (int)
- {
- format_result prev (*this);
- *this += 1;
- return prev;
- }
-
- /* Increment the number of output characters by N. */
- format_result& operator+= (unsigned HOST_WIDE_INT);
-};
-
-format_result&
-format_result::operator+= (unsigned HOST_WIDE_INT n)
-{
- gcc_assert (n < HOST_WIDE_INT_MAX);
-
- if (range.min < HOST_WIDE_INT_MAX)
- range.min += n;
-
- if (range.max < HOST_WIDE_INT_MAX)
- range.max += n;
-
- if (range.likely < HOST_WIDE_INT_MAX)
- range.likely += n;
-
- if (range.unlikely < HOST_WIDE_INT_MAX)
- range.unlikely += n;
-
- return *this;
-}
-
/* Return the value of INT_MIN for the target. */
static inline HOST_WIDE_INT
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, "...");
+ size_t len = strlen (targstr);
+ if (len >= hostsz)
+ {
+ memcpy (hostr, targstr, hostsz - 4);
+ strcpy (hostr + hostsz - 4, "...");
+ }
+ else
+ memcpy (hostr, targstr, len + 1);
return hostr;
}
if (!*targstr)
break;
- if (size_t (ph - hostr) == hostsz - 4)
+ if (size_t (ph - hostr) == hostsz)
{
- *ph = '\0';
- strcat (ph, "...");
+ strcpy (ph - 4, "...");
break;
}
}
}
/* 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. */
+ starting at *PS to a HOST_WIDE_INT, analogously to strtol. Return
+ the result and set *PS 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)
+static inline HOST_WIDE_INT
+target_strtowi (const char **ps, const char **erange)
{
unsigned HOST_WIDE_INT val = 0;
for ( ; ; ++*ps)
c -= '0';
/* Check for overflow. */
- if (val > (LONG_MAX - c) / 10LU)
+ if (val > ((unsigned HOST_WIDE_INT) HOST_WIDE_INT_MAX - c) / 10LU)
{
- val = LONG_MAX;
+ val = HOST_WIDE_INT_MAX;
*erange = *ps;
/* Skip the remaining digits. */
return val;
}
-/* Return the constant initial value of DECL if available or DECL
- otherwise. Same as the synonymous function in c/c-typeck.c. */
-
-static tree
-decl_constant_value (tree decl)
-{
- if (/* Don't change a variable array bound or initial value to a constant
- in a place where a variable is invalid. Note that DECL_INITIAL
- isn't valid for a PARM_DECL. */
- current_function_decl != 0
- && TREE_CODE (decl) != PARM_DECL
- && !TREE_THIS_VOLATILE (decl)
- && TREE_READONLY (decl)
- && DECL_INITIAL (decl) != 0
- && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK
- /* This is invalid if initial value is not constant.
- If it has either a function call, a memory reference,
- or a variable, then re-evaluating it could give different results. */
- && TREE_CONSTANT (DECL_INITIAL (decl))
- /* Check for cases where this is sub-optimal, even though valid. */
- && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
- return DECL_INITIAL (decl);
- return decl;
-}
-
/* Given FORMAT, set *PLOC to the source location of the format string
and return the format string if it is known or null otherwise. */
static const char*
get_format_string (tree format, location_t *ploc)
{
- if (VAR_P (format))
- {
- /* Pull out a constant value if the front end didn't. */
- format = decl_constant_value (format);
- STRIP_NOPS (format);
- }
-
- if (integer_zerop (format))
- {
- /* FIXME: Diagnose null format string if it hasn't been diagnosed
- by -Wformat (the latter diagnoses only nul pointer constants,
- this pass can do better). */
- return NULL;
- }
-
- HOST_WIDE_INT offset = 0;
-
- if (TREE_CODE (format) == POINTER_PLUS_EXPR)
- {
- tree arg0 = TREE_OPERAND (format, 0);
- tree arg1 = TREE_OPERAND (format, 1);
- STRIP_NOPS (arg0);
- STRIP_NOPS (arg1);
-
- if (TREE_CODE (arg1) != INTEGER_CST)
- return NULL;
-
- format = arg0;
-
- /* POINTER_PLUS_EXPR offsets are to be interpreted signed. */
- if (!cst_and_fits_in_hwi (arg1))
- return NULL;
-
- offset = int_cst_value (arg1);
- }
-
- if (TREE_CODE (format) != ADDR_EXPR)
- return NULL;
-
*ploc = EXPR_LOC_OR_LOC (format, input_location);
- format = TREE_OPERAND (format, 0);
-
- if (TREE_CODE (format) == ARRAY_REF
- && tree_fits_shwi_p (TREE_OPERAND (format, 1))
- && (offset += tree_to_shwi (TREE_OPERAND (format, 1))) >= 0)
- format = TREE_OPERAND (format, 0);
-
- if (offset < 0)
- return NULL;
-
- tree array_init;
- tree array_size = NULL_TREE;
-
- if (VAR_P (format)
- && TREE_CODE (TREE_TYPE (format)) == ARRAY_TYPE
- && (array_init = decl_constant_value (format)) != format
- && TREE_CODE (array_init) == STRING_CST)
- {
- /* Extract the string constant initializer. Note that this may
- include a trailing NUL character that is not in the array (e.g.
- const char a[3] = "foo";). */
- array_size = DECL_SIZE_UNIT (format);
- format = array_init;
- }
-
- if (TREE_CODE (format) != STRING_CST)
- return NULL;
-
- tree type = TREE_TYPE (format);
-
- scalar_int_mode char_mode;
- if (!is_int_mode (TYPE_MODE (TREE_TYPE (type)), &char_mode)
- || GET_MODE_SIZE (char_mode) != 1)
- {
- /* Wide format string. */
- return NULL;
- }
-
- const char *fmtstr = TREE_STRING_POINTER (format);
- unsigned fmtlen = TREE_STRING_LENGTH (format);
-
- if (array_size)
- {
- /* Variable length arrays can't be initialized. */
- gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
-
- if (tree_fits_shwi_p (array_size))
- {
- HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
- if (array_size_value > 0
- && array_size_value == (int) array_size_value
- && fmtlen > array_size_value)
- fmtlen = array_size_value;
- }
- }
- if (offset)
- {
- if (offset >= fmtlen)
- return NULL;
-
- fmtstr += offset;
- fmtlen -= offset;
- }
-
- if (fmtlen < 1 || fmtstr[--fmtlen] != 0)
- {
- /* FIXME: Diagnose an unterminated format string if it hasn't been
- diagnosed by -Wformat. Similarly to a null format pointer,
- -Wformay diagnoses only nul pointer constants, this pass can
- do better). */
- return NULL;
- }
-
- return fmtstr;
+ return c_getstr (format);
}
-/* The format_warning_at_substring function is not used here in a way
- that makes using attribute format viable. Suppress the warning. */
+/* For convenience and brevity, shorter named entrypoints of
+ format_string_diagnostic_t::emit_warning_va and
+ format_string_diagnostic_t::emit_warning_n_va.
+ These have to be functions with the attribute so that exgettext
+ works properly. */
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
-
-/* For convenience and brevity. */
+static bool
+ATTRIBUTE_GCC_DIAG (5, 6)
+fmtwarn (const substring_loc &fmt_loc, location_t param_loc,
+ const char *corrected_substring, int opt, const char *gmsgid, ...)
+{
+ format_string_diagnostic_t diag (fmt_loc, NULL, param_loc, NULL,
+ corrected_substring);
+ va_list ap;
+ va_start (ap, gmsgid);
+ bool warned = diag.emit_warning_va (opt, gmsgid, &ap);
+ va_end (ap);
+
+ return warned;
+}
static bool
- (* const fmtwarn) (const substring_loc &, location_t,
- const char *, int, const char *, ...)
- = format_warning_at_substring;
+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. */
/* Description of the result of conversion either of a single directive
or the whole format string. */
-struct fmtresult
+class fmtresult
{
+public:
/* Construct a FMTRESULT object with all counters initialized
to MIN. KNOWNRANGE is set when MIN is valid. */
fmtresult (unsigned HOST_WIDE_INT min = HOST_WIDE_INT_MAX)
- : argmin (), argmax (),
+ : argmin (), argmax (), dst_offset (HOST_WIDE_INT_MIN), nonstr (),
knownrange (min < HOST_WIDE_INT_MAX),
- nullp ()
+ mayfail (), nullp ()
{
range.min = min;
range.max = min;
KNOWNRANGE is set when both MIN and MAX are valid. */
fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max,
unsigned HOST_WIDE_INT likely = HOST_WIDE_INT_MAX)
- : argmin (), argmax (),
+ : argmin (), argmax (), dst_offset (HOST_WIDE_INT_MIN), nonstr (),
knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
- nullp ()
+ mayfail (), nullp ()
{
range.min = min;
range.max = max;
/* The range a directive's argument is in. */
tree argmin, argmax;
+ /* The starting offset into the destination of the formatted function
+ call of the %s argument that points into (aliases with) the same
+ destination array. */
+ HOST_WIDE_INT dst_offset;
+
/* The minimum and maximum number of bytes that a directive
results in on output for an argument in the range above. */
result_range range;
+ /* Non-nul when the argument of a string directive is not a nul
+ terminated string. */
+ tree nonstr;
+
/* True when the range above is obtained from a known value of
a directive's argument or its bounds and not the result of
heuristics that depend on warning levels. */
bool knownrange;
+ /* True for a directive that may fail (such as wide character
+ directives). */
+ bool mayfail;
+
/* True when the argument is a null pointer. */
bool nullp;
};
fmtresult::type_max_digits (tree type, int base)
{
unsigned prec = TYPE_PRECISION (type);
- if (base == 8)
- return (prec + 2) / 3;
-
- if (base == 16)
- return prec / 4;
+ switch (base)
+ {
+ case 8:
+ return (prec + 2) / 3;
+ case 10:
+ /* Decimal approximation: yields 3, 5, 10, and 20 for precision
+ of 8, 16, 32, and 64 bits. */
+ return prec * 301 / 1000 + 1;
+ case 16:
+ return prec / 4;
+ }
- /* Decimal approximation: yields 3, 5, 10, and 20 for precision
- of 8, 16, 32, and 64 bits. */
- return prec * 301 / 1000 + 1;
+ gcc_unreachable ();
}
static bool
-get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT);
+get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT,
+ const vr_values *);
+
+struct call_info;
/* Description of a format directive. A directive is either a plain
string or a conversion specification that starts with '%'. */
struct directive
{
+ directive (const call_info *inf, unsigned dno)
+ : info (inf), dirno (dno), argno (), beg (), len (), flags (),
+ width (), prec (), modifier (), specifier (), arg (), fmtfunc ()
+ { }
+
+ /* Reference to the info structure describing the call that this
+ directive is a part of. */
+ const call_info *info;
+
/* The 1-based directive number (for debugging). */
unsigned dirno;
+ /* The zero-based argument number of the directive's argument ARG in
+ the function's argument list. */
+ unsigned argno;
+
/* The first character of the directive and its length. */
const char *beg;
size_t len;
/* Format conversion function that given a directive and an argument
returns the formatting result. */
- fmtresult (*fmtfunc) (const directive &, tree);
+ fmtresult (*fmtfunc) (const directive &, tree, const vr_values *);
/* Return True when a the format flag CHR has been used. */
bool get_flag (char chr) const
or 0, whichever is greater. For a non-constant ARG in some range
set width to its range adjusting each bound to -1 if it's less.
For an indeterminate ARG set width to [0, INT_MAX]. */
- void set_width (tree arg)
+ void set_width (tree arg, const vr_values *vr)
{
- get_int_range (arg, width, width + 1, true, 0);
+ get_int_range (arg, width, width + 1, true, 0, vr);
}
/* Set both bounds of the precision range to VAL. */
or -1 whichever is greater. For a non-constant ARG in some range
set precision to its range adjusting each bound to -1 if it's less.
For an indeterminate ARG set precision to [-1, INT_MAX]. */
- void set_precision (tree arg)
+ void set_precision (tree arg, const vr_values *vr)
{
- get_int_range (arg, prec, prec + 1, false, -1);
+ get_int_range (arg, prec, prec + 1, false, -1, vr);
}
/* Return true if both width and precision are known to be
}
};
+/* The result of a call to a formatted function. */
+
+struct format_result
+{
+ format_result ()
+ : range (), aliases (), alias_count (), knownrange (), posunder4k (),
+ floating (), warned () { /* No-op. */ }
+
+ ~format_result ()
+ {
+ XDELETEVEC (aliases);
+ }
+
+ /* 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. */
+ result_range range;
+
+ struct alias_info
+ {
+ directive dir; /* The directive that aliases the destination. */
+ HOST_WIDE_INT offset; /* The offset at which it aliases it. */
+ result_range range; /* The raw result of the directive. */
+ };
+
+ /* An array of directives whose pointer argument aliases a part
+ of the destination object of the formatted function. */
+ alias_info *aliases;
+ unsigned alias_count;
+
+ /* True when the range above is obtained from known values of
+ directive arguments, or bounds on the amount of output such
+ as width and precision, and not the result of heuristics that
+ depend on warning levels. It's used to issue stricter diagnostics
+ in cases where strings of unknown lengths are bounded by the arrays
+ they are determined to refer to. KNOWNRANGE must not be used for
+ the return value optimization. */
+ bool knownrange;
+
+ /* True if no individual directive could fail or result in more than
+ 4095 bytes of output (the total NUMBER_CHARS_{MIN,MAX} might be
+ greater). Implementations are not required to handle directives
+ that produce more than 4K bytes (leading to undefined behavior)
+ and so when one is found it disables the return value optimization.
+ Similarly, directives that can fail (such as wide character
+ directives) disable the optimization. */
+ bool posunder4k;
+
+ /* True when a floating point directive has been seen in the format
+ string. */
+ bool floating;
+
+ /* True when an intermediate result has caused a warning. Used to
+ avoid issuing duplicate warnings while finishing the processing
+ of a call. WARNED also disables the return value optimization. */
+ bool warned;
+
+ /* Preincrement the number of output characters by 1. */
+ format_result& operator++ ()
+ {
+ return *this += 1;
+ }
+
+ /* Postincrement the number of output characters by 1. */
+ format_result operator++ (int)
+ {
+ format_result prev (*this);
+ *this += 1;
+ return prev;
+ }
+
+ /* Increment the number of output characters by N. */
+ format_result& operator+= (unsigned HOST_WIDE_INT);
+
+ /* Add a directive to the sequence of those with potentially aliasing
+ arguments. */
+ void append_alias (const directive &, HOST_WIDE_INT, const result_range &);
+
+private:
+ /* Not copyable or assignable. */
+ format_result (format_result&);
+ void operator= (format_result&);
+};
+
+format_result&
+format_result::operator+= (unsigned HOST_WIDE_INT n)
+{
+ gcc_assert (n < HOST_WIDE_INT_MAX);
+
+ if (range.min < HOST_WIDE_INT_MAX)
+ range.min += n;
+
+ if (range.max < HOST_WIDE_INT_MAX)
+ range.max += n;
+
+ if (range.likely < HOST_WIDE_INT_MAX)
+ range.likely += n;
+
+ if (range.unlikely < HOST_WIDE_INT_MAX)
+ range.unlikely += n;
+
+ return *this;
+}
+
+void
+format_result::append_alias (const directive &d, HOST_WIDE_INT off,
+ const result_range &resrng)
+{
+ unsigned cnt = alias_count + 1;
+ alias_info *ar = XNEWVEC (alias_info, cnt);
+
+ for (unsigned i = 0; i != alias_count; ++i)
+ ar[i] = aliases[i];
+
+ ar[alias_count].dir = d;
+ ar[alias_count].offset = off;
+ ar[alias_count].range = resrng;
+
+ XDELETEVEC (aliases);
+
+ alias_count = cnt;
+ aliases = ar;
+}
+
/* Return the logarithm of X in BASE. */
static int
return res;
}
-/* 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;
-}
-
/* Description of a call to a formatted function. */
-struct sprintf_dom_walker::call_info
+struct call_info
{
/* Function call statement. */
gimple *callstmt;
/* Called built-in function code. */
built_in_function fncode;
+ /* The "origin" of the destination pointer argument, which is either
+ the DECL of the destination buffer being written into or a pointer
+ that points to it, plus some offset. */
+ tree dst_origin;
+
+ /* For a destination pointing to a struct array member, the offset of
+ the member. */
+ HOST_WIDE_INT dst_field;
+
+ /* The offset into the destination buffer. */
+ HOST_WIDE_INT dst_offset;
+
/* Format argument and format string extracted from it. */
tree format;
const char *fmtstr;
{
return bounded ? OPT_Wformat_truncation_ : OPT_Wformat_overflow_;
}
+
+ /* Return true for calls to file formatted functions. */
+ bool is_file_func () const
+ {
+ return (fncode == BUILT_IN_FPRINTF
+ || fncode == BUILT_IN_FPRINTF_CHK
+ || fncode == BUILT_IN_FPRINTF_UNLOCKED
+ || fncode == BUILT_IN_VFPRINTF
+ || fncode == BUILT_IN_VFPRINTF_CHK);
+ }
+
+ /* Return true for calls to string formatted functions. */
+ bool is_string_func () const
+ {
+ return (fncode == BUILT_IN_SPRINTF
+ || fncode == BUILT_IN_SPRINTF_CHK
+ || fncode == BUILT_IN_SNPRINTF
+ || fncode == BUILT_IN_SNPRINTF_CHK
+ || fncode == BUILT_IN_VSPRINTF
+ || fncode == BUILT_IN_VSPRINTF_CHK
+ || fncode == BUILT_IN_VSNPRINTF
+ || fncode == BUILT_IN_VSNPRINTF_CHK);
+ }
};
/* Return the result of formatting a no-op directive (such as '%n'). */
static fmtresult
-format_none (const directive &, tree)
+format_none (const directive &, tree, const vr_values *)
{
fmtresult res (0);
return res;
/* Return the result of formatting the '%%' directive. */
static fmtresult
-format_percent (const directive &, tree)
+format_percent (const directive &, tree, const vr_values *)
{
fmtresult res (1);
return res;
for (int i = 0; i < NUM_INT_N_ENTS; i++)
if (int_n_enabled_p[i])
{
- char name[50];
+ char name[50], altname[50];
sprintf (name, "__int%d unsigned", int_n_data[i].bitsize);
+ sprintf (altname, "__int%d__ unsigned", int_n_data[i].bitsize);
- if (strcmp (name, UINTMAX_TYPE) == 0)
+ if (strcmp (name, UINTMAX_TYPE) == 0
+ || strcmp (altname, UINTMAX_TYPE) == 0)
{
*pintmax = int_n_trees[i].signed_type;
*puintmax = int_n_trees[i].unsigned_type;
static bool
get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
- bool absolute, HOST_WIDE_INT negbound)
+ bool absolute, HOST_WIDE_INT negbound,
+ const class vr_values *vr_values)
{
/* The type of the result. */
const_tree type = integer_type_node;
&& TYPE_PRECISION (argtype) <= TYPE_PRECISION (type))
{
/* Try to determine the range of values of the integer argument. */
- wide_int min, max;
- enum value_range_type range_type = get_range_info (arg, &min, &max);
- if (range_type == VR_RANGE)
+ const value_range_equiv *vr
+ = CONST_CAST (class vr_values *, vr_values)->get_value_range (arg);
+
+ if (range_int_cst_p (vr))
{
HOST_WIDE_INT type_min
= (TYPE_UNSIGNED (argtype)
HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (argtype));
- *pmin = min.to_shwi ();
- *pmax = max.to_shwi ();
+ *pmin = TREE_INT_CST_LOW (vr->min ());
+ *pmax = TREE_INT_CST_LOW (vr->max ());
if (*pmin < *pmax)
{
/* Handle an argument with an unknown range as if none had been
provided. */
if (unknown)
- return get_int_range (NULL_TREE, pmin, pmax, absolute, negbound);
+ return get_int_range (NULL_TREE, pmin, pmax, absolute,
+ negbound, vr_values);
}
/* Adjust each bound as specified by ABSOLUTE and NEGBOUND. */
used when the directive argument or its value isn't known. */
static fmtresult
-format_integer (const directive &dir, tree arg)
+format_integer (const directive &dir, tree arg, const vr_values *vr_values)
{
tree intmax_type_node;
tree uintmax_type_node;
{
/* Try to determine the range of values of the integer argument
(range information is not available for pointers). */
- wide_int min, max;
- enum value_range_type range_type = get_range_info (arg, &min, &max);
- if (range_type == VR_RANGE)
+ const value_range_equiv *vr
+ = CONST_CAST (class vr_values *, vr_values)->get_value_range (arg);
+
+ if (range_int_cst_p (vr))
{
- argmin = wide_int_to_tree (argtype, min);
- argmax = wide_int_to_tree (argtype, max);
+ argmin = vr->min ();
+ argmax = vr->max ();
/* Set KNOWNRANGE if the argument is in a known subrange
of the directive's type and neither width nor precision
res.argmin = argmin;
res.argmax = argmax;
}
- else if (range_type == VR_ANTI_RANGE)
+ else if (vr->kind () == VR_ANTI_RANGE)
{
/* Handle anti-ranges if/when bug 71690 is resolved. */
}
- else if (range_type == VR_VARYING)
+ else if (vr->varying_p () || vr->undefined_p ())
{
/* The argument here may be the result of promoting the actual
argument to int. Try to determine the type of the actual
if (code == INTEGER_CST)
{
arg = gimple_assign_rhs1 (def);
- return format_integer (dir, arg);
+ return format_integer (dir, arg, vr_values);
}
if (code == NOP_EXPR)
/* For unsigned conversions/directives or signed when
the minimum is positive, use the minimum and maximum to compute
the shortest and longest output, respectively. */
- res.range.min = format_integer (dir, argmin).range.min;
- res.range.max = format_integer (dir, argmax).range.max;
+ res.range.min = format_integer (dir, argmin, vr_values).range.min;
+ res.range.max = format_integer (dir, argmax, vr_values).range.max;
}
else if (tree_int_cst_sgn (argmax) < 0)
{
/* For signed conversions/directives if maximum is negative,
use the minimum as the longest output and maximum as the
shortest output. */
- res.range.min = format_integer (dir, argmax).range.min;
- res.range.max = format_integer (dir, argmin).range.max;
+ res.range.min = format_integer (dir, argmax, vr_values).range.min;
+ res.range.max = format_integer (dir, argmin, vr_values).range.max;
}
else
{
as the shortest output and for the longest output compute the
length of the output of both minimum and maximum and pick the
longer. */
- unsigned HOST_WIDE_INT max1 = format_integer (dir, argmin).range.max;
- unsigned HOST_WIDE_INT max2 = format_integer (dir, argmax).range.max;
- res.range.min = format_integer (dir, integer_zero_node).range.min;
+ unsigned HOST_WIDE_INT max1
+ = format_integer (dir, argmin, vr_values).range.max;
+ unsigned HOST_WIDE_INT max2
+ = format_integer (dir, argmax, vr_values).range.max;
+ res.range.min
+ = format_integer (dir, integer_zero_node, vr_values).range.min;
res.range.max = MAX (max1, max2);
}
round-to-nearest mode. */
mpfr_t x;
mpfr_init2 (x, rfmt->p);
- mpfr_from_real (x, &rv, GMP_RNDN);
+ mpfr_from_real (x, &rv, MPFR_RNDN);
/* Return a value one greater to account for the leading minus sign. */
unsigned HOST_WIDE_INT r
unsigned flagmin = (1 /* for the first digit */
+ (dir.get_flag ('+') | dir.get_flag (' ')));
+ /* The minimum is 3 for "inf" and "nan" for all specifiers, plus 1
+ for the plus sign/space with the '+' and ' ' flags, respectively,
+ unless reduced below. */
+ res.range.min = 2 + flagmin;
+
/* When the pound flag is set the decimal point is included in output
regardless of precision. Whether or not a decimal point is included
otherwise depends on the specification and precision. */
else if (dir.prec[0] > 0)
minprec = dir.prec[0] + !radix /* decimal point */;
- res.range.min = (2 /* 0x */
- + flagmin
- + radix
- + minprec
- + 3 /* p+0 */);
+ res.range.likely = (2 /* 0x */
+ + flagmin
+ + radix
+ + minprec
+ + 3 /* p+0 */);
res.range.max = format_floating_max (type, 'a', prec[1]);
- res.range.likely = res.range.min;
/* The unlikely maximum accounts for the longest multibyte
decimal point character. */
non-zero, decimal point. */
HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
- /* The minimum output is "[-+]1.234567e+00" regardless
+ /* The likely minimum output is "[-+]1.234567e+00" regardless
of the value of the actual argument. */
- res.range.min = (flagmin
- + radix
- + minprec
- + 2 /* e+ */ + 2);
+ res.range.likely = (flagmin
+ + radix
+ + minprec
+ + 2 /* e+ */ + 2);
res.range.max = format_floating_max (type, 'e', prec[1]);
- res.range.likely = res.range.min;
/* The unlikely maximum accounts for the longest multibyte
decimal point character. */
decimal point. */
HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
- /* The lower bound when precision isn't specified is 8 bytes
- ("1.23456" since precision is taken to be 6). When precision
- is zero, the lower bound is 1 byte (e.g., "1"). Otherwise,
- when precision is greater than zero, then the lower bound
- is 2 plus precision (plus flags). */
- res.range.min = flagmin + radix + minprec;
+ /* For finite numbers (i.e., not infinity or NaN) the lower bound
+ when precision isn't specified is 8 bytes ("1.23456" since
+ precision is taken to be 6). When precision is zero, the lower
+ bound is 1 byte (e.g., "1"). Otherwise, when precision is greater
+ than zero, then the lower bound is 2 plus precision (plus flags).
+ But in all cases, the lower bound is no greater than 3. */
+ unsigned HOST_WIDE_INT min = flagmin + radix + minprec;
+ if (min < res.range.min)
+ res.range.min = min;
/* Compute the upper bound for -TYPE_MAX. */
res.range.max = format_floating_max (type, 'f', prec[1]);
if (dir.prec[0] < 0 && dir.prec[1] > 0)
res.range.likely = 3;
else
- res.range.likely = res.range.min;
+ res.range.likely = min;
/* The unlikely maximum accounts for the longest multibyte
decimal point character. */
the lower bound on the range of bytes (not counting flags
or width) is 1 plus radix (i.e., either "0" or "0." for
"%g" and "%#g", respectively, with a zero argument). */
- res.range.min = flagmin + radix;
+ unsigned HOST_WIDE_INT min = flagmin + radix;
+ if (min < res.range.min)
+ res.range.min = min;
char spec = 'g';
HOST_WIDE_INT maxprec = dir.prec[1];
ARG. */
static fmtresult
-format_floating (const directive &dir, tree arg)
+format_floating (const directive &dir, tree arg, const vr_values *)
{
HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
+ tree type = (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
+ ? long_double_type_node : double_type_node);
/* For an indeterminate precision the lower bound must be assumed
to be zero. */
{
/* Get the number of fractional decimal digits needed to represent
the argument without a loss of accuracy. */
- tree type = arg ? TREE_TYPE (arg) :
- (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
- ? long_double_type_node : double_type_node);
-
unsigned fmtprec
= REAL_MODE_FORMAT (TYPE_MODE (type))->p;
}
}
- if (!arg || TREE_CODE (arg) != REAL_CST)
+ if (!arg
+ || TREE_CODE (arg) != REAL_CST
+ || !useless_type_conversion_p (type, TREE_TYPE (arg)))
return format_floating (dir, prec);
/* The minimum and maximum number of bytes produced by the directive. */
const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)));
+ if (!real_isfinite (rvp))
+ {
+ /* The format for Infinity and NaN is "[-]inf"/"[-]infinity"
+ and "[-]nan" with the choice being implementation-defined
+ but not locale dependent. */
+ bool sign = dir.get_flag ('+') || real_isneg (rvp);
+ res.range.min = 3 + sign;
+
+ res.range.likely = res.range.min;
+ res.range.max = res.range.min;
+ /* The unlikely maximum is "[-/+]infinity" or "[-/+][qs]nan".
+ For NaN, the C/POSIX standards specify two formats:
+ "[-/+]nan"
+ and
+ "[-/+]nan(n-char-sequence)"
+ No known printf implementation outputs the latter format but AIX
+ outputs QNaN and SNaN for quiet and signalling NaN, respectively,
+ so the unlikely maximum reflects that. */
+ res.range.unlikely = sign + (real_isinf (rvp) ? 8 : 4);
+
+ /* The range for infinity and NaN is known unless either width
+ or precision is unknown. Width has the same effect regardless
+ of whether the argument is finite. Precision is either ignored
+ (e.g., Glibc) or can have an effect on the short vs long format
+ such as inf/infinity (e.g., Solaris). */
+ res.knownrange = dir.known_width_and_precision ();
+
+ /* Adjust the range for width but ignore precision. */
+ res.adjust_for_width_or_precision (dir.width);
+
+ return res;
+ }
+
char fmtstr [40];
char *pfmt = fmtstr;
rounding in either direction can result in longer output. */
mpfr_t mpfrval;
mpfr_init2 (mpfrval, rfmt->p);
- mpfr_from_real (mpfrval, rvp, i ? GMP_RNDU : GMP_RNDD);
+ mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
/* Use the MPFR rounding specifier to round down in the first
iteration and then up. In most but not all cases this will
Used by the format_string function below. */
static fmtresult
-get_string_length (tree str)
+get_string_length (tree str, unsigned eltsize, const vr_values *vr)
{
if (!str)
return fmtresult ();
- if (tree slen = c_strlen (str, 1))
+ /* Try to determine the dynamic string length first.
+ Set MAXBOUND to an arbitrary non-null non-integer node as a request
+ to have it set to the length of the longest string in a PHI. */
+ c_strlen_data lendata = { };
+ lendata.maxbound = str;
+ if (eltsize == 1)
+ get_range_strlen_dynamic (str, &lendata, vr);
+ else
{
- /* Simply return the length of the string. */
- fmtresult res (tree_to_shwi (slen));
- return res;
+ /* Determine the length of the shortest and longest string referenced
+ by STR. Strings of unknown lengths are bounded by the sizes of
+ arrays that subexpressions of STR may refer to. Pointers that
+ aren't known to point any such arrays result in LENDATA.MAXLEN
+ set to SIZE_MAX. */
+ get_range_strlen (str, &lendata, eltsize);
}
- /* Determine the length of the shortest and longest string referenced
- by STR. Strings of unknown lengths are bounded by the sizes of
- arrays that subexpressions of STR may refer to. Pointers that
- aren't known to point any such arrays result in LENRANGE[1] set
- to SIZE_MAX. */
- tree lenrange[2];
- bool flexarray = get_range_strlen (str, lenrange);
+ /* If LENDATA.MAXBOUND is not equal to .MINLEN it corresponds to the bound
+ of the largest array STR refers to, if known, or it's set to SIZE_MAX
+ otherwise. */
- if (lenrange [0] || lenrange [1])
+ /* Return the default result when nothing is known about the string. */
+ if ((lendata.maxbound && !tree_fits_uhwi_p (lendata.maxbound))
+ || !tree_fits_uhwi_p (lendata.maxlen))
{
- HOST_WIDE_INT min
- = (tree_fits_uhwi_p (lenrange[0])
- ? tree_to_uhwi (lenrange[0])
- : 0);
-
- HOST_WIDE_INT max
- = (tree_fits_uhwi_p (lenrange[1])
- ? tree_to_uhwi (lenrange[1])
- : HOST_WIDE_INT_M1U);
-
- /* get_range_strlen() returns the target value of SIZE_MAX for
- strings of unknown length. Bump it up to HOST_WIDE_INT_M1U
- which may be bigger. */
- if ((unsigned HOST_WIDE_INT)min == target_size_max ())
- min = HOST_WIDE_INT_M1U;
- if ((unsigned HOST_WIDE_INT)max == target_size_max ())
- max = HOST_WIDE_INT_M1U;
-
- fmtresult res (min, max);
-
- /* Set RES.KNOWNRANGE to true if and only if all strings referenced
- by STR are known to be bounded (though not necessarily by their
- actual length but perhaps by their maximum possible length). */
- if (res.range.max < target_int_max ())
- {
- res.knownrange = true;
- /* When the the length of the longest string is known and not
- excessive use it as the likely length of the string(s). */
- res.range.likely = res.range.max;
- }
- else
- {
- /* When the upper bound is unknown (it can be zero or excessive)
- set the likely length to the greater of 1 and the length of
- the shortest string and reset the lower bound to zero. */
- res.range.likely = res.range.min ? res.range.min : warn_level > 1;
- res.range.min = 0;
- }
-
- /* If the range of string length has been estimated from the size
- of an array at the end of a struct assume that it's longer than
- the array bound says it is in case it's used as a poor man's
- flexible array member, such as in struct S { char a[4]; }; */
- res.range.unlikely = flexarray ? HOST_WIDE_INT_MAX : res.range.max;
+ fmtresult res;
+ res.nonstr = lendata.decl;
+ return res;
+ }
+ unsigned HOST_WIDE_INT lenmax = tree_to_uhwi (max_object_size ()) - 2;
+ if (integer_zerop (lendata.minlen)
+ && (!lendata.maxbound || lenmax <= tree_to_uhwi (lendata.maxbound))
+ && lenmax <= tree_to_uhwi (lendata.maxlen))
+ {
+ fmtresult res;
+ res.nonstr = lendata.decl;
return res;
}
- return get_string_length (NULL_TREE);
+ HOST_WIDE_INT min
+ = (tree_fits_uhwi_p (lendata.minlen)
+ ? tree_to_uhwi (lendata.minlen)
+ : 0);
+
+ HOST_WIDE_INT max
+ = (lendata.maxbound && tree_fits_uhwi_p (lendata.maxbound)
+ ? tree_to_uhwi (lendata.maxbound)
+ : HOST_WIDE_INT_M1U);
+
+ const bool unbounded = integer_all_onesp (lendata.maxlen);
+
+ /* Set the max/likely counters to unbounded when a minimum is known
+ but the maximum length isn't bounded. This implies that STR is
+ a conditional expression involving a string of known length and
+ and an expression of unknown/unbounded length. */
+ if (min
+ && (unsigned HOST_WIDE_INT)min < HOST_WIDE_INT_M1U
+ && unbounded)
+ max = HOST_WIDE_INT_M1U;
+
+ /* get_range_strlen() returns the target value of SIZE_MAX for
+ strings of unknown length. Bump it up to HOST_WIDE_INT_M1U
+ which may be bigger. */
+ if ((unsigned HOST_WIDE_INT)min == target_size_max ())
+ min = HOST_WIDE_INT_M1U;
+ if ((unsigned HOST_WIDE_INT)max == target_size_max ())
+ max = HOST_WIDE_INT_M1U;
+
+ fmtresult res (min, max);
+ res.nonstr = lendata.decl;
+
+ /* Set RES.KNOWNRANGE to true if and only if all strings referenced
+ by STR are known to be bounded (though not necessarily by their
+ actual length but perhaps by their maximum possible length). */
+ if (res.range.max < target_int_max ())
+ {
+ res.knownrange = true;
+ /* When the length of the longest string is known and not
+ excessive use it as the likely length of the string(s). */
+ res.range.likely = res.range.max;
+ }
+ else
+ {
+ /* When the upper bound is unknown (it can be zero or excessive)
+ set the likely length to the greater of 1. If MAXBOUND is
+ known, also reset the length of the lower bound to zero. */
+ res.range.likely = res.range.min ? res.range.min : warn_level > 1;
+ if (lendata.maxbound && !integer_all_onesp (lendata.maxbound))
+ res.range.min = 0;
+ }
+
+ res.range.unlikely = unbounded ? HOST_WIDE_INT_MAX : res.range.max;
+
+ return res;
}
/* Return the minimum and maximum number of characters formatted
vsprinf). */
static fmtresult
-format_character (const directive &dir, tree arg)
+format_character (const directive &dir, tree arg, const vr_values *vr_values)
{
fmtresult res;
res.knownrange = true;
- if (dir.modifier == FMT_LEN_l)
+ if (dir.specifier == 'C'
+ || dir.modifier == FMT_LEN_l)
{
/* A wide character can result in as few as zero bytes. */
res.range.min = 0;
HOST_WIDE_INT min, max;
- if (get_int_range (arg, &min, &max, false, 0))
+ if (get_int_range (arg, &min, &max, false, 0, vr_values))
{
if (min == 0 && max == 0)
{
res.range.likely = 0;
res.range.unlikely = 0;
}
- else if (min > 0 && min < 128)
+ else if (min >= 0 && min < 128)
{
+ /* Be conservative if the target execution character set
+ is not a 1-to-1 mapping to the source character set or
+ if the source set is not ASCII. */
+ bool one_2_one_ascii
+ = (target_to_host_charmap[0] == 1 && target_to_host ('a') == 97);
+
/* A wide character in the ASCII range most likely results
in a single byte, and only unlikely in up to MB_LEN_MAX. */
- res.range.max = 1;
+ res.range.max = one_2_one_ascii ? 1 : target_mb_len_max ();;
res.range.likely = 1;
res.range.unlikely = target_mb_len_max ();
+ res.mayfail = !one_2_one_ascii;
}
else
{
res.range.max = target_mb_len_max ();
res.range.likely = 2;
res.range.unlikely = res.range.max;
+ /* Converting such a character may fail. */
+ res.mayfail = true;
}
}
else
res.range.max = target_mb_len_max ();
res.range.likely = 2;
res.range.unlikely = res.range.max;
+ res.mayfail = true;
}
}
else
return res.adjust_for_width_or_precision (dir.width);
}
+/* Determine the offset *INDEX of the first byte of an array element of
+ TYPE (possibly recursively) into which the byte offset OFF points.
+ On success set *INDEX to the offset of the first byte and return type.
+ Otherwise, if no such element can be found, return null. */
+
+static tree
+array_elt_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+ gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+
+ tree eltype = type;
+ while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
+ eltype = TREE_TYPE (eltype);
+
+ if (TYPE_MODE (TREE_TYPE (eltype)) != TYPE_MODE (char_type_node))
+ eltype = TREE_TYPE (eltype);
+
+ if (eltype == type)
+ {
+ *index = 0;
+ return type;
+ }
+
+ HOST_WIDE_INT typsz = int_size_in_bytes (type);
+ HOST_WIDE_INT eltsz = int_size_in_bytes (eltype);
+ if (off < typsz * eltsz)
+ {
+ *index = (off / eltsz) * eltsz;
+ return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+ }
+
+ return NULL_TREE;
+}
+
+/* Determine the offset *INDEX of the first byte of a struct member of TYPE
+ (possibly recursively) into which the byte offset OFF points. On success
+ set *INDEX to the offset of the first byte and return true. Otherwise,
+ if no such member can be found, return false. */
+
+static bool
+field_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+ gcc_assert (RECORD_OR_UNION_TYPE_P (type));
+
+ for (tree fld = TYPE_FIELDS (type); fld; fld = TREE_CHAIN (fld))
+ {
+ if (TREE_CODE (fld) != FIELD_DECL || DECL_ARTIFICIAL (fld))
+ continue;
+
+ tree fldtype = TREE_TYPE (fld);
+ HOST_WIDE_INT fldoff = int_byte_position (fld);
+
+ /* If the size is not available the field is a flexible array
+ member. Treat this case as success. */
+ tree typesize = TYPE_SIZE_UNIT (fldtype);
+ HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (typesize)
+ ? tree_to_uhwi (typesize)
+ : off);
+
+ if (fldoff + fldsize < off)
+ continue;
+
+ if (TREE_CODE (fldtype) == ARRAY_TYPE)
+ {
+ HOST_WIDE_INT idx = 0;
+ if (tree ft = array_elt_at_offset (fldtype, off, &idx))
+ fldtype = ft;
+ else
+ break;
+
+ *index += idx;
+ fldoff -= idx;
+ off -= idx;
+ }
+
+ if (RECORD_OR_UNION_TYPE_P (fldtype))
+ {
+ *index += fldoff;
+ return field_at_offset (fldtype, off - fldoff, index);
+ }
+
+ *index += fldoff;
+ return true;
+ }
+
+ return false;
+}
+
+/* For an expression X of pointer type, recursively try to find the same
+ origin (object or pointer) as Y it references and return such an X.
+ When X refers to a struct member, set *FLDOFF to the offset of the
+ member from the beginning of the "most derived" object. */
+
+static tree
+get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
+{
+ if (!x)
+ return NULL_TREE;
+
+ switch (TREE_CODE (x))
+ {
+ case ADDR_EXPR:
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, off);
+
+ case ARRAY_REF:
+ {
+ tree offset = TREE_OPERAND (x, 1);
+ HOST_WIDE_INT idx = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+ tree eltype = TREE_TYPE (x);
+ if (TREE_CODE (eltype) == INTEGER_TYPE)
+ {
+ if (off)
+ *off = idx;
+ }
+ else if (idx < HOST_WIDE_INT_MAX)
+ *fldoff += idx * int_size_in_bytes (eltype);
+ else
+ *fldoff = idx;
+
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, NULL);
+ }
+
+ case MEM_REF:
+ if (off)
+ {
+ tree offset = TREE_OPERAND (x, 1);
+ *off = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+ }
+
+ x = TREE_OPERAND (x, 0);
+
+ if (off)
+ {
+ tree xtype
+ = (TREE_CODE (x) == ADDR_EXPR
+ ? TREE_TYPE (TREE_OPERAND (x, 0)) : TREE_TYPE (TREE_TYPE (x)));
+
+ /* The byte offset of the most basic struct member the byte
+ offset *OFF corresponds to, or for a (multidimensional)
+ array member, the byte offset of the array element. */
+ HOST_WIDE_INT index = 0;
+
+ if ((RECORD_OR_UNION_TYPE_P (xtype)
+ && field_at_offset (xtype, *off, &index))
+ || (TREE_CODE (xtype) == ARRAY_TYPE
+ && TREE_CODE (TREE_TYPE (xtype)) == ARRAY_TYPE
+ && array_elt_at_offset (xtype, *off, &index)))
+ {
+ *fldoff += index;
+ *off -= index;
+ }
+ }
+
+ return get_origin_and_offset (x, fldoff, NULL);
+
+ case COMPONENT_REF:
+ {
+ tree fld = TREE_OPERAND (x, 1);
+ *fldoff += int_byte_position (fld);
+
+ get_origin_and_offset (fld, fldoff, off);
+ x = TREE_OPERAND (x, 0);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+
+ case SSA_NAME:
+ {
+ gimple *def = SSA_NAME_DEF_STMT (x);
+ if (is_gimple_assign (def))
+ {
+ tree_code code = gimple_assign_rhs_code (def);
+ if (code == ADDR_EXPR)
+ {
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+
+ if (code == POINTER_PLUS_EXPR)
+ {
+ tree offset = gimple_assign_rhs2 (def);
+ if (off)
+ *off = (tree_fits_uhwi_p (offset)
+ ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, NULL);
+ }
+ else if (code == VAR_DECL)
+ {
+ x = gimple_assign_rhs1 (def);
+ return get_origin_and_offset (x, fldoff, off);
+ }
+ }
+ else if (gimple_nop_p (def) && SSA_NAME_VAR (x))
+ x = SSA_NAME_VAR (x);
+ }
+
+ default:
+ break;
+ }
+
+ return x;
+}
+
+/* If ARG refers to the same (sub)object or array element as described
+ by DST and DST_FLD, return the byte offset into the struct member or
+ array element referenced by ARG. Otherwise return HOST_WIDE_INT_MIN
+ to indicate that ARG and DST do not refer to the same object. */
+
+static HOST_WIDE_INT
+alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
+{
+ /* See if the argument refers to the same base object as the destination
+ of the formatted function call, and if so, try to determine if they
+ can alias. */
+ if (!arg || !dst || !ptr_derefs_may_alias_p (arg, dst))
+ return HOST_WIDE_INT_MIN;
+
+ /* The two arguments may refer to the same object. If they both refer
+ to a struct member, see if the members are one and the same. */
+ HOST_WIDE_INT arg_off = 0, arg_fld = 0;
+
+ tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off);
+
+ if (arg_orig == dst && arg_fld == dst_fld)
+ return arg_off;
+
+ return HOST_WIDE_INT_MIN;
+}
+
/* Return the minimum and maximum number of characters formatted
by the '%s' format directive and its wide character form for
the argument ARG. ARG can be null (for functions such as
vsprinf). */
static fmtresult
-format_string (const directive &dir, tree arg)
+format_string (const directive &dir, tree arg, const vr_values *vr_values)
{
fmtresult res;
+ if (warn_restrict)
+ {
+ /* See if ARG might alias the destination of the call with
+ DST_ORIGIN and DST_FIELD. If so, store the starting offset
+ so that the overlap can be determined for certain later,
+ when the amount of output of the call (including subsequent
+ directives) has been computed. Otherwise, store HWI_MIN. */
+ res.dst_offset = alias_offset (arg, dir.info->dst_origin,
+ dir.info->dst_field);
+ }
+
/* Compute the range the argument's length can be in. */
- fmtresult slen = get_string_length (arg);
+ int count_by = 1;
+ if (dir.specifier == 'S' || dir.modifier == FMT_LEN_l)
+ {
+ /* Get a node for a C type that will be the same size
+ as a wchar_t on the target. */
+ tree node = get_typenode_from_name (MODIFIED_WCHAR_TYPE);
+
+ /* Now that we have a suitable node, get the number of
+ bytes it occupies. */
+ count_by = int_size_in_bytes (node);
+ gcc_checking_assert (count_by == 2 || count_by == 4);
+ }
+
+ fmtresult slen = get_string_length (arg, count_by, vr_values);
if (slen.range.min == slen.range.max
&& slen.range.min < HOST_WIDE_INT_MAX)
{
/* A '%s' directive with a string argument with constant length. */
res.range = slen.range;
- if (dir.modifier == FMT_LEN_l)
+ if (dir.specifier == 'S'
+ || dir.modifier == FMT_LEN_l)
{
/* In the worst case the length of output of a wide string S
is bounded by MB_LEN_MAX * wcslen (S). */
res.range.max *= target_mb_len_max ();
res.range.unlikely = res.range.max;
- /* It's likely that the the total length is not more that
+ /* It's likely that the total length is not more that
2 * wcslen (S).*/
res.range.likely = res.range.min * 2;
/* Even a non-empty wide character string need not convert into
any bytes. */
res.range.min = 0;
+
+ /* A non-empty wide character conversion may fail. */
+ if (slen.range.max > 0)
+ res.mayfail = true;
}
else
{
at level 2. This result is adjust upward for width (if it's
specified). */
- if (dir.modifier == FMT_LEN_l)
+ if (dir.specifier == 'S'
+ || dir.modifier == FMT_LEN_l)
{
/* A wide character converts to as few as zero bytes. */
slen.range.min = 0;
if (slen.range.likely < target_int_max ())
slen.range.unlikely *= target_mb_len_max ();
+
+ /* A non-empty wide character conversion may fail. */
+ if (slen.range.max > 0)
+ res.mayfail = true;
}
res.range = slen.range;
if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max)
res.range.max = dir.prec[1];
res.range.likely = dir.prec[1] ? warn_level > 1 : 0;
+ if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.unlikely)
+ res.range.unlikely = dir.prec[1];
}
else if (slen.range.min >= target_int_max ())
{
empty, while at level 1 they are assumed to be one byte
long. */
res.range.likely = warn_level > 1;
+ res.range.unlikely = HOST_WIDE_INT_MAX;
}
else
{
if (res.range.likely >= target_int_max ())
res.range.likely = warn_level > 1;
}
-
- res.range.unlikely = res.range.max;
}
+ /* If the argument isn't a nul-terminated string and the number
+ of bytes on output isn't bounded by precision, set NONSTR. */
+ if (slen.nonstr && slen.range.min < (unsigned HOST_WIDE_INT)dir.prec[0])
+ res.nonstr = slen.nonstr;
+
/* Bump up the byte counters if WIDTH is greater. */
return res.adjust_for_width_or_precision (dir.width);
}
/* Format plain string (part of the format string itself). */
static fmtresult
-format_plain (const directive &dir, tree)
+format_plain (const directive &dir, tree, const vr_values *)
{
fmtresult res (dir.len);
return res;
should be diagnosed given the AVAILable space in the destination. */
static bool
-should_warn_p (const sprintf_dom_walker::call_info &info,
+should_warn_p (const call_info &info,
const result_range &avail, const result_range &result)
{
if (result.max <= avail.min)
static bool
maybe_warn (substring_loc &dirloc, location_t argloc,
- const sprintf_dom_walker::call_info &info,
+ const call_info &info,
const result_range &avail_range, const result_range &res,
const directive &dir)
{
/* 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. */
- dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+ 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);
-
- const char *fmtstr
- = (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")));
-
- return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
- fmtstr, info.func);
- }
-
- if (res.min == res.max)
- {
- const char* fmtstr
- = (res.min == 1
- ? (info.bounded
- ? (maybe
- ? G_("%<%.*s%> directive output may be truncated writing "
- "%wu byte into a region of size %wu")
- : G_("%<%.*s%> directive output truncated writing "
- "%wu byte into a region of size %wu"))
- : G_("%<%.*s%> directive writing %wu byte "
- "into a region of size %wu"))
- : (info.bounded
- ? (maybe
- ? G_("%<%.*s%> directive output may be truncated writing "
- "%wu bytes into a region of size %wu")
- : G_("%<%.*s%> directive output truncated writing "
- "%wu bytes into a region of size %wu"))
- : G_("%<%.*s%> directive writing %wu bytes "
- "into a region of size %wu")));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.min, navail);
+ {
+ /* 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 == 0 && res.max < maxbytes)
+ if (res.min == res.max)
{
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.max, navail);
+ 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. */
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.likely, navail);
- }
+ /* 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)
- {
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.min, res.max, navail);
- }
-
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
+ 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);
}
/* 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. */
- dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+ 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);
- const char *fmtstr
- = (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")));
-
- return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (), fmtstr,
- info.func);
+ 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* fmtstr
- = (res.min == 1
- ? (info.bounded
- ? (maybe
- ? G_("%<%.*s%> directive output may be truncated writing "
- "%wu byte into a region of size between %wu and %wu")
- : G_("%<%.*s%> directive output truncated writing "
- "%wu byte into a region of size between %wu and %wu"))
- : G_("%<%.*s%> directive writing %wu byte "
- "into a region of size between %wu and %wu"))
- : (info.bounded
- ? (maybe
- ? G_("%<%.*s%> directive output may be truncated writing "
- "%wu bytes into a region of size between %wu and %wu")
- : G_("%<%.*s%> directive output truncated writing "
- "%wu bytes into a region of size between %wu and %wu"))
- : G_("%<%.*s%> directive writing %wu bytes "
- "into a region of size between %wu and %wu")));
-
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.min, avail_range.min, avail_range.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)
- {
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- 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 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. */
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- res.likely, avail_range.min, avail_range.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)
- {
- const char* fmtstr
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- 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
- = (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"));
- return fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dir.len,
+ 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 sprintf_dom_walker::call_info &info,
- format_result *res, const directive &dir)
+format_directive (const call_info &info,
+ format_result *res, const directive &dir,
+ const class vr_values *vr_values)
{
/* Offset of the beginning of the directive from the beginning
of the format string. */
return false;
/* Compute the range of lengths of the formatted output. */
- fmtresult fmtres = dir.fmtfunc (dir, dir.arg);
+ fmtresult fmtres = dir.fmtfunc (dir, dir.arg, vr_values);
/* Record whether the output of all directives is known to be
bounded by some maximum, implying that their arguments are
if (fmtres.nullp)
{
fmtwarn (dirloc, argloc, NULL, info.warnopt (),
- "%<%.*s%> directive argument is null",
- dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
+ "%G%<%.*s%> directive argument is null",
+ info.callstmt, dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg));
/* Don't bother processing the rest of the format string. */
res->warned = true;
NUL that's appended after the format string has been processed. */
result_range avail_range = bytes_remaining (info.objsize, *res);
+ /* If the argument aliases a part of the destination of the formatted
+ call at offset FMTRES.DST_OFFSET append the directive and its result
+ to the set of aliases for later processing. */
+ if (fmtres.dst_offset != HOST_WIDE_INT_MIN)
+ res->append_alias (dir, fmtres.dst_offset, fmtres.range);
+
bool warned = res->warned;
if (!warned)
of 4095 bytes required to be supported? */
bool minunder4k = fmtres.range.min < 4096;
bool maxunder4k = fmtres.range.max < 4096;
- /* Clear UNDER4K in the overall result if the maximum has exceeded
- the 4k (this is necessary to avoid the return valuye optimization
+ /* Clear POSUNDER4K in the overall result if the maximum has exceeded
+ the 4k (this is necessary to avoid the return value optimization
that may not be safe in the maximum case). */
if (!maxunder4k)
- res->under4k = false;
+ res->posunder4k = false;
+ /* Also clear POSUNDER4K if the directive may fail. */
+ if (fmtres.mayfail)
+ res->posunder4k = false;
if (!warned
/* Only warn at level 2. */
- && 1 < warn_level
+ && warn_level > 1
+ /* Only warn for string functions. */
+ && info.is_string_func ()
&& (!minunder4k
|| (!maxunder4k && fmtres.range.max < HOST_WIDE_INT_MAX)))
{
of C11. Warn on this only at level 2 but remember this and
prevent folding the return value when done. This allows for
the possibility of the actual libc call failing due to ENOMEM
- (like Glibc does under some conditions). */
+ (like Glibc does with very large precision or width).
+ Issue the "may exceed" warning only for string functions and
+ not for fprintf or printf. */
if (fmtres.range.min == fmtres.range.max)
- warned = fmtwarn (dirloc, argloc, NULL,
- info.warnopt (),
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
"%<%.*s%> directive output of %wu bytes exceeds "
- "minimum required size of 4095",
- dirlen,
+ "minimum required size of 4095", dirlen,
target_to_host (hostdir, sizeof hostdir, dir.beg),
fmtres.range.min);
- else
- {
- const char *fmtstr
- = (minunder4k
- ? G_("%<%.*s%> directive output between %wu and %wu "
- "bytes may exceed minimum required size of 4095")
- : G_("%<%.*s%> directive output between %wu and %wu "
- "bytes exceeds minimum required size of 4095"));
-
- warned = fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dirlen,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- fmtres.range.min, fmtres.range.max);
- }
+ else if (!minunder4k)
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output between %wu and %wu "
+ "bytes exceeds minimum required size of 4095",
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min, fmtres.range.max);
+ else if (!info.retval_used () && info.is_string_func ())
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output between %wu and %wu "
+ "bytes may exceed minimum required size of "
+ "4095",
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min, fmtres.range.max);
}
/* Has the likely and maximum directive output exceeded INT_MAX? */
/* Warn for the likely output size at level 1. */
&& (likelyximax
/* But only warn for the maximum at level 2. */
- || (1 < warn_level
+ || (warn_level > 1
&& maxximax
&& fmtres.range.max < HOST_WIDE_INT_MAX)))
{
- /* The directive output causes the total length of output
- to exceed INT_MAX bytes. */
-
- if (fmtres.range.min == fmtres.range.max)
- warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
- "%<%.*s%> directive output of %wu bytes causes "
- "result to exceed %<INT_MAX%>",
- dirlen,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- fmtres.range.min);
- else
+ if (fmtres.range.min > target_int_max ())
+ {
+ /* The directive output exceeds INT_MAX bytes. */
+ if (fmtres.range.min == fmtres.range.max)
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output of %wu bytes exceeds "
+ "%<INT_MAX%>", dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min);
+ else
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output between %wu and "
+ "%wu bytes exceeds %<INT_MAX%>", dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min, fmtres.range.max);
+ }
+ else if (res->range.min > target_int_max ())
{
- const char *fmtstr
- = (fmtres.range.min > target_int_max ()
- ? G_ ("%<%.*s%> directive output between %wu and %wu "
- "bytes causes result to exceed %<INT_MAX%>")
- : G_ ("%<%.*s%> directive output between %wu and %wu "
- "bytes may cause result to exceed %<INT_MAX%>"));
- warned = fmtwarn (dirloc, argloc, NULL,
- info.warnopt (), fmtstr, dirlen,
- target_to_host (hostdir, sizeof hostdir, dir.beg),
- fmtres.range.min, fmtres.range.max);
+ /* The directive output is under INT_MAX but causes the result
+ to exceed INT_MAX bytes. */
+ if (fmtres.range.min == fmtres.range.max)
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output of %wu bytes causes "
+ "result to exceed %<INT_MAX%>", dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min);
+ else
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output between %wu and "
+ "%wu bytes causes result to exceed %<INT_MAX%>",
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min, fmtres.range.max);
}
+ else if ((!info.retval_used () || !info.bounded)
+ && (info.is_string_func ()))
+ /* Warn for calls to string functions that either aren't bounded
+ (sprintf) or whose return value isn't used. */
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive output between %wu and "
+ "%wu bytes may cause result to exceed "
+ "%<INT_MAX%>", dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg),
+ fmtres.range.min, fmtres.range.max);
+ }
+
+ if (!warned && fmtres.nonstr)
+ {
+ warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+ "%<%.*s%> directive argument is not a nul-terminated "
+ "string",
+ dirlen,
+ target_to_host (hostdir, sizeof hostdir, dir.beg));
+ if (warned && DECL_P (fmtres.nonstr))
+ inform (DECL_SOURCE_LOCATION (fmtres.nonstr),
+ "referenced argument declared here");
+ return false;
}
if (warned && fmtres.range.min < fmtres.range.likely
&& fmtres.range.likely < fmtres.range.max)
- {
- inform (info.fmtloc,
- (1 == fmtres.range.likely
- ? G_("assuming directive output of %wu byte")
- : G_("assuming directive output of %wu bytes")),
+ inform_n (info.fmtloc, fmtres.range.likely,
+ "assuming directive output of %wu byte",
+ "assuming directive output of %wu bytes",
fmtres.range.likely);
- }
if (warned && fmtres.argmin)
{
res->warned |= warned;
- if (!dir.beg[0] && res->warned && info.objsize < HOST_WIDE_INT_MAX)
+ if (!dir.beg[0] && res->warned)
{
- /* 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 (info.objsize < HOST_WIDE_INT_MAX)
+ {
+ /* If a warning has been issued for buffer overflow or truncation
+ help the user figure out how big a buffer they need. */
+
+ if (min == max)
+ inform_n (callloc, min,
+ "%qE output %wu byte into a destination of size %wu",
+ "%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);
+ }
+ else if (!info.is_string_func ())
+ {
+ /* If the warning is for a file function like fprintf
+ of printf with no destination size just print the computed
+ result. */
+ if (min == max)
+ inform_n (callloc, min,
+ "%qE output %wu byte", "%qE output %wu bytes",
+ info.func, min);
+ else if (max < HOST_WIDE_INT_MAX)
+ inform (callloc,
+ "%qE output between %wu and %wu bytes",
+ info.func, min, max);
+ else if (min < res->range.likely && res->range.likely < max)
+ inform (callloc,
+ "%qE output %wu or more bytes (assuming %wu)",
+ info.func, min, res->range.likely);
+ else
+ inform (callloc,
+ "%qE output %wu or more bytes",
+ info.func, min);
+ }
}
if (dump_file && *dir.beg)
{
- fprintf (dump_file, " Result: %lli, %lli, %lli, %lli "
- "(%lli, %lli, %lli, %lli)\n",
- (long long)fmtres.range.min,
- (long long)fmtres.range.likely,
- (long long)fmtres.range.max,
- (long long)fmtres.range.unlikely,
- (long long)res->range.min,
- (long long)res->range.likely,
- (long long)res->range.max,
- (long long)res->range.unlikely);
+ fprintf (dump_file,
+ " Result: "
+ HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ", "
+ HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC " ("
+ HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ", "
+ HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ")\n",
+ fmtres.range.min, fmtres.range.likely,
+ fmtres.range.max, fmtres.range.unlikely,
+ res->range.min, res->range.likely,
+ res->range.max, res->range.unlikely);
}
return true;
}
-#pragma GCC diagnostic pop
-
/* Parse a format directive in function call described by INFO starting
at STR and populate DIR structure. Bump up *ARGNO by the number of
arguments extracted for the directive. Return the length of
the directive. */
static size_t
-parse_directive (sprintf_dom_walker::call_info &info,
+parse_directive (call_info &info,
directive &dir, format_result *res,
- const char *str, unsigned *argno)
+ const char *str, unsigned *argno,
+ const vr_values *vr_values)
{
const char *pcnt = strchr (str, target_percent);
dir.beg = str;
if (dump_file)
{
- fprintf (dump_file, " Directive %u at offset %llu: \"%.*s\", "
- "length = %llu\n",
+ fprintf (dump_file, " Directive %u at offset "
+ HOST_WIDE_INT_PRINT_UNSIGNED ": \"%.*s\", "
+ "length = " HOST_WIDE_INT_PRINT_UNSIGNED "\n",
dir.dirno,
- (unsigned long long)(size_t)(dir.beg - info.fmtstr),
- (int)dir.len, dir.beg, (unsigned long long)dir.len);
+ (unsigned HOST_WIDE_INT)(size_t)(dir.beg - info.fmtstr),
+ (int)dir.len, dir.beg, (unsigned HOST_WIDE_INT) dir.len);
}
return len - !*str;
}
+ /* Set the directive argument's number to correspond to its position
+ in the formatted function call's argument list. */
+ dir.argno = *argno;
+
const char *pf = pcnt + 1;
/* POSIX numbered argument index or zero when none. */
width and sort it out later after the next character has
been seen. */
pwidth = pf;
- width = target_strtol10 (&pf, &werange);
+ width = target_strtowi (&pf, &werange);
}
else if (target_to_host (*pf) == '*')
{
{
werange = 0;
pwidth = pf;
- width = target_strtol10 (&pf, &werange);
+ width = target_strtowi (&pf, &werange);
}
else if (target_to_host (*pf) == '*')
{
if (ISDIGIT (target_to_host (*pf)))
{
pprec = pf;
- precision = target_strtol10 (&pf, &perange);
+ precision = target_strtowi (&pf, &perange);
}
else if (target_to_host (*pf) == '*')
{
dir.fmtfunc = format_none;
break;
+ case 'C':
case 'c':
+ /* POSIX wide character and C/POSIX narrow character. */
dir.fmtfunc = format_character;
break;
case 'S':
case 's':
+ /* POSIX wide string and C/POSIX narrow character string. */
dir.fmtfunc = format_string;
break;
if (star_width)
{
if (INTEGRAL_TYPE_P (TREE_TYPE (star_width)))
- dir.set_width (star_width);
+ dir.set_width (star_width, vr_values);
else
{
/* Width specified by a va_list takes on the range [0, -INT_MIN]
}
else
{
- if (width == LONG_MAX && werange)
+ if (width == HOST_WIDE_INT_MAX && werange)
{
size_t begin = dir.beg - info.fmtstr + (pwidth - pcnt);
size_t caret = begin + (werange - pcnt);
substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
caret, begin, end);
- fmtwarn (dirloc, UNKNOWN_LOCATION, NULL,
- info.warnopt (), "%<%.*s%> directive width out of range",
- dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+ fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+ "%<%.*s%> directive width out of range", (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg));
}
dir.set_width (width);
if (star_precision)
{
if (INTEGRAL_TYPE_P (TREE_TYPE (star_precision)))
- dir.set_precision (star_precision);
+ dir.set_precision (star_precision, vr_values);
else
{
/* Precision specified by a va_list takes on the range [-1, INT_MAX]
}
else
{
- if (precision == LONG_MAX && perange)
+ if (precision == HOST_WIDE_INT_MAX && perange)
{
size_t begin = dir.beg - info.fmtstr + (pprec - pcnt) - 1;
size_t caret = dir.beg - info.fmtstr + (perange - pcnt) - 1;
substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
caret, begin, end);
- fmtwarn (dirloc, UNKNOWN_LOCATION, NULL,
- info.warnopt (), "%<%.*s%> directive precision out of range",
- dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+ fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+ "%<%.*s%> directive precision out of range", (int) dir.len,
+ target_to_host (hostdir, sizeof hostdir, dir.beg));
}
dir.set_precision (precision);
if (dump_file)
{
- fprintf (dump_file, " Directive %u at offset %llu: \"%.*s\"",
- dir.dirno, (unsigned long long)(size_t)(dir.beg - info.fmtstr),
+ fprintf (dump_file,
+ " Directive %u at offset " HOST_WIDE_INT_PRINT_UNSIGNED
+ ": \"%.*s\"",
+ dir.dirno,
+ (unsigned HOST_WIDE_INT)(size_t)(dir.beg - info.fmtstr),
(int)dir.len, dir.beg);
if (star_width)
{
if (dir.width[0] == dir.width[1])
- fprintf (dump_file, ", width = %lli", (long long)dir.width[0]);
+ fprintf (dump_file, ", width = " HOST_WIDE_INT_PRINT_DEC,
+ dir.width[0]);
else
- fprintf (dump_file, ", width in range [%lli, %lli]",
- (long long)dir.width[0], (long long)dir.width[1]);
+ fprintf (dump_file,
+ ", width in range [" HOST_WIDE_INT_PRINT_DEC
+ ", " HOST_WIDE_INT_PRINT_DEC "]",
+ dir.width[0], dir.width[1]);
}
if (star_precision)
{
if (dir.prec[0] == dir.prec[1])
- fprintf (dump_file, ", precision = %lli", (long long)dir.prec[0]);
+ fprintf (dump_file, ", precision = " HOST_WIDE_INT_PRINT_DEC,
+ dir.prec[0]);
else
- fprintf (dump_file, ", precision in range [%lli, %lli]",
- (long long)dir.prec[0], (long long)dir.prec[1]);
+ fprintf (dump_file,
+ ", precision in range [" HOST_WIDE_INT_PRINT_DEC
+ HOST_WIDE_INT_PRINT_DEC "]",
+ dir.prec[0], dir.prec[1]);
}
fputc ('\n', dump_file);
}
return dir.len;
}
+/* Diagnose overlap between destination and %s directive arguments. */
+
+static void
+maybe_warn_overlap (call_info &info, format_result *res)
+{
+ /* Two vectors of 1-based indices corresponding to either certainly
+ or possibly aliasing arguments. */
+ auto_vec<int, 16> aliasarg[2];
+
+ /* Go through the array of potentially aliasing directives and collect
+ argument numbers of those that do or may overlap the destination
+ object given the full result. */
+ for (unsigned i = 0; i != res->alias_count; ++i)
+ {
+ const format_result::alias_info &alias = res->aliases[i];
+
+ enum { possible = -1, none = 0, certain = 1 } overlap = none;
+
+ /* If the precision is zero there is no overlap. (This only
+ considers %s directives and ignores %n.) */
+ if (alias.dir.prec[0] == 0 && alias.dir.prec[1] == 0)
+ continue;
+
+ if (alias.offset == HOST_WIDE_INT_MAX
+ || info.dst_offset == HOST_WIDE_INT_MAX)
+ overlap = possible;
+ else if (alias.offset == info.dst_offset)
+ overlap = alias.dir.prec[0] == 0 ? possible : certain;
+ else
+ {
+ /* Determine overlap from the range of output and offsets
+ into the same destination as the source, and rule out
+ impossible overlap. */
+ unsigned HOST_WIDE_INT albeg = alias.offset;
+ unsigned HOST_WIDE_INT dstbeg = info.dst_offset;
+
+ unsigned HOST_WIDE_INT alend = albeg + alias.range.min;
+ unsigned HOST_WIDE_INT dstend = dstbeg + res->range.min - 1;
+
+ if ((albeg <= dstbeg && alend > dstbeg)
+ || (albeg >= dstbeg && albeg < dstend))
+ overlap = certain;
+ else
+ {
+ alend = albeg + alias.range.max;
+ if (alend < albeg)
+ alend = HOST_WIDE_INT_M1U;
+
+ dstend = dstbeg + res->range.max - 1;
+ if (dstend < dstbeg)
+ dstend = HOST_WIDE_INT_M1U;
+
+ if ((albeg >= dstbeg && albeg <= dstend)
+ || (alend >= dstbeg && alend <= dstend))
+ overlap = possible;
+ }
+ }
+
+ if (overlap == none)
+ continue;
+
+ /* Append the 1-based argument number. */
+ aliasarg[overlap != certain].safe_push (alias.dir.argno + 1);
+
+ /* Disable any kind of optimization. */
+ res->range.unlikely = HOST_WIDE_INT_M1U;
+ }
+
+ tree arg0 = gimple_call_arg (info.callstmt, 0);
+ location_t loc = gimple_location (info.callstmt);
+
+ bool aliaswarn = false;
+
+ unsigned ncertain = aliasarg[0].length ();
+ unsigned npossible = aliasarg[1].length ();
+ if (ncertain && npossible)
+ {
+ /* If there are multiple arguments that overlap, some certainly
+ and some possibly, handle both sets in a single diagnostic. */
+ aliaswarn
+ = warning_at (loc, OPT_Wrestrict,
+ "%qE arguments %Z and maybe %Z overlap destination "
+ "object %qE",
+ info.func, aliasarg[0].address (), ncertain,
+ aliasarg[1].address (), npossible,
+ info.dst_origin);
+ }
+ else if (ncertain)
+ {
+ /* There is only one set of two or more arguments and they all
+ certainly overlap the destination. */
+ aliaswarn
+ = warning_n (loc, OPT_Wrestrict, ncertain,
+ "%qE argument %Z overlaps destination object %qE",
+ "%qE arguments %Z overlap destination object %qE",
+ info.func, aliasarg[0].address (), ncertain,
+ info.dst_origin);
+ }
+ else if (npossible)
+ {
+ /* There is only one set of two or more arguments and they all
+ may overlap (but need not). */
+ aliaswarn
+ = warning_n (loc, OPT_Wrestrict, npossible,
+ "%qE argument %Z may overlap destination object %qE",
+ "%qE arguments %Z may overlap destination object %qE",
+ info.func, aliasarg[1].address (), npossible,
+ info.dst_origin);
+ }
+
+ if (aliaswarn)
+ {
+ res->warned = true;
+
+ if (info.dst_origin != arg0)
+ {
+ /* If its location is different from the first argument of the call
+ point either at the destination object itself or at the expression
+ that was used to determine the overlap. */
+ loc = (DECL_P (info.dst_origin)
+ ? DECL_SOURCE_LOCATION (info.dst_origin)
+ : EXPR_LOCATION (info.dst_origin));
+ if (loc != UNKNOWN_LOCATION)
+ inform (loc,
+ "destination object referenced by %<restrict%>-qualified "
+ "argument 1 was declared here");
+ }
+ }
+}
+
/* 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
on, false otherwise (e.g., when a unknown or unhandled directive was seen
that caused the processing to be terminated early). */
-bool
-sprintf_dom_walker::compute_format_length (call_info &info,
- format_result *res)
+static bool
+compute_format_length (call_info &info, format_result *res, const vr_values *vr)
{
if (dump_file)
{
LOCATION_FILE (callloc), LOCATION_LINE (callloc));
print_generic_expr (dump_file, info.func, dump_flags);
- fprintf (dump_file, ": objsize = %llu, fmtstr = \"%s\"\n",
- (unsigned long long)info.objsize, info.fmtstr);
+ fprintf (dump_file,
+ ": objsize = " HOST_WIDE_INT_PRINT_UNSIGNED
+ ", fmtstr = \"%s\"\n",
+ info.objsize, info.fmtstr);
}
/* Reset the minimum and maximum byte counters. */
res->range.min = res->range.max = 0;
/* No directive has been seen yet so the length of output is bounded
- by the known range [0, 0] (with no conversion producing more than
- 4K bytes) until determined otherwise. */
+ by the known range [0, 0] (with no conversion resulting in a failure
+ or producing more than 4K bytes) until determined otherwise. */
res->knownrange = true;
- res->under4k = true;
res->floating = false;
res->warned = false;
/* The variadic argument counter. */
unsigned argno = info.argidx;
+ bool success = true;
+
for (const char *pf = info.fmtstr; ; ++dirno)
{
- directive dir = directive ();
- dir.dirno = dirno;
+ directive dir (&info, dirno);
- size_t n = parse_directive (info, dir, res, pf, &argno);
+ size_t n = parse_directive (info, dir, res, pf, &argno, vr);
/* Return failure if the format function fails. */
- if (!format_directive (info, res, dir))
+ if (!format_directive (info, res, dir, vr))
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
+ /* Return success when the directive is zero bytes long and it's
+ the last thing 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). */
if (!n)
- return *pf == '\0';
+ {
+ success = *pf == '\0';
+ break;
+ }
pf += n;
}
+ maybe_warn_overlap (info, res);
+
/* The complete format string was processed (with or without warnings). */
- return true;
+ return success;
}
/* Return the size of the object referenced by the expression DEST if
- available, or -1 otherwise. */
+ available, or the maximum possible size otherwise. */
static unsigned HOST_WIDE_INT
get_destination_size (tree dest)
{
+ /* When there is no destination return the maximum. */
+ if (!dest)
+ return HOST_WIDE_INT_MAX;
+
/* Initialize object size info before trying to compute it. */
init_object_sizes ();
if (compute_builtin_object_size (dest, ost, &size))
return size;
- return HOST_WIDE_INT_M1U;
+ return HOST_WIDE_INT_MAX;
}
/* Return true if the call described by INFO with result RES safe to
of its return values. */
static bool
-is_call_safe (const sprintf_dom_walker::call_info &info,
+is_call_safe (const call_info &info,
const format_result &res, bool under4k,
unsigned HOST_WIDE_INT retval[2])
{
- if (under4k && !res.under4k)
+ if (under4k && !res.posunder4k)
return false;
/* The minimum return value. */
static bool
try_substitute_return_value (gimple_stmt_iterator *gsi,
- const sprintf_dom_walker::call_info &info,
+ const call_info &info,
const format_result &res)
{
tree lhs = gimple_get_lhs (info.callstmt);
bool removed = false;
/* The minimum and maximum return value. */
- unsigned HOST_WIDE_INT retval[2];
+ unsigned HOST_WIDE_INT retval[2] = {0};
bool safe = is_call_safe (info, res, true, retval);
if (safe
are badly declared. */
&& !stmt_ends_bb_p (info.callstmt))
{
- tree cst = build_int_cst (integer_type_node, retval[0]);
+ tree cst = build_int_cst (lhs ? TREE_TYPE (lhs) : integer_type_node,
+ retval[0]);
- if (lhs == NULL_TREE
- && info.nowrite)
+ if (lhs == NULL_TREE && info.nowrite)
{
/* Remove the call to the bounded function with a zero size
(e.g., snprintf(0, 0, "%i", 123)) if there is no lhs. */
}
}
}
- else if (lhs)
+ else if (lhs && types_compatible_p (TREE_TYPE (lhs), integer_type_node))
{
bool setrange = false;
const char *what = setrange ? "Setting" : "Discarding";
if (retval[0] != retval[1])
fprintf (dump_file,
- " %s %s-bounds return value range [%llu, %llu].\n",
- what, inbounds,
- (unsigned long long)retval[0],
- (unsigned long long)retval[1]);
+ " %s %s-bounds return value range ["
+ HOST_WIDE_INT_PRINT_UNSIGNED ", "
+ HOST_WIDE_INT_PRINT_UNSIGNED "].\n",
+ what, inbounds, retval[0], retval[1]);
else
- fprintf (dump_file, " %s %s-bounds return value %llu.\n",
- what, inbounds, (unsigned long long)retval[0]);
+ fprintf (dump_file, " %s %s-bounds return value "
+ HOST_WIDE_INT_PRINT_UNSIGNED ".\n",
+ what, inbounds, retval[0]);
}
}
static bool
try_simplify_call (gimple_stmt_iterator *gsi,
- const sprintf_dom_walker::call_info &info,
+ const call_info &info,
const format_result &res)
{
unsigned HOST_WIDE_INT dummy[2];
return false;
}
-/* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
- functions and if so, handle it. Return true if the call is removed
- and gsi_next should not be performed in the caller. */
+/* Return the zero-based index of the format string argument of a printf
+ like function and set *IDX_ARGS to the first format argument. When
+ no such index exists return UINT_MAX. */
+
+static unsigned
+get_user_idx_format (tree fndecl, unsigned *idx_args)
+{
+ tree attrs = lookup_attribute ("format", DECL_ATTRIBUTES (fndecl));
+ if (!attrs)
+ attrs = lookup_attribute ("format", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
+
+ if (!attrs)
+ return UINT_MAX;
+
+ attrs = TREE_VALUE (attrs);
+
+ tree archetype = TREE_VALUE (attrs);
+ if (strcmp ("printf", IDENTIFIER_POINTER (archetype)))
+ return UINT_MAX;
+
+ attrs = TREE_CHAIN (attrs);
+ tree fmtarg = TREE_VALUE (attrs);
+
+ attrs = TREE_CHAIN (attrs);
+ tree elliparg = TREE_VALUE (attrs);
+
+ /* Attribute argument indices are 1-based but we use zero-based. */
+ *idx_args = tree_to_uhwi (elliparg) - 1;
+ return tree_to_uhwi (fmtarg) - 1;
+}
+
+} /* Unnamed namespace. */
+
+/* Determine if a GIMPLE call at *GSI is to one of the sprintf-like built-in
+ functions and if so, handle it. Return true if the call is removed and
+ gsi_next should not be performed in the caller. */
bool
-sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
+handle_printf_call (gimple_stmt_iterator *gsi, const vr_values *vr_values)
{
+ init_target_to_host_charmap ();
+
call_info info = call_info ();
info.callstmt = gsi_stmt (*gsi);
- if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+ info.func = gimple_call_fndecl (info.callstmt);
+ if (!info.func)
return false;
- info.func = gimple_call_fndecl (info.callstmt);
- info.fncode = DECL_FUNCTION_CODE (info.func);
+ /* Format string argument number (valid for all functions). */
+ unsigned idx_format = UINT_MAX;
+ if (gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+ info.fncode = DECL_FUNCTION_CODE (info.func);
+ else
+ {
+ unsigned idx_args;
+ idx_format = get_user_idx_format (info.func, &idx_args);
+ if (idx_format == UINT_MAX
+ || idx_format >= gimple_call_num_args (info.callstmt)
+ || idx_args > gimple_call_num_args (info.callstmt)
+ || !POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (info.callstmt,
+ idx_format))))
+ return false;
+ info.fncode = BUILT_IN_NONE;
+ info.argidx = idx_args;
+ }
/* The size of the destination as in snprintf(dest, size, ...). */
unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
/* The size of the destination determined by __builtin_object_size. */
unsigned HOST_WIDE_INT objsize = HOST_WIDE_INT_M1U;
- /* Buffer size argument number (snprintf and vsnprintf). */
- unsigned HOST_WIDE_INT idx_dstsize = HOST_WIDE_INT_M1U;
+ /* Zero-based buffer size argument number (snprintf and vsnprintf). */
+ unsigned idx_dstsize = UINT_MAX;
/* Object size argument number (snprintf_chk and vsnprintf_chk). */
- unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
+ unsigned idx_objsize = UINT_MAX;
- /* Format string argument number (valid for all functions). */
- unsigned idx_format;
+ /* Destinaton argument number (valid for sprintf functions only). */
+ unsigned idx_dstptr = 0;
switch (info.fncode)
{
+ case BUILT_IN_NONE:
+ // User-defined function with attribute format (printf).
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_FPRINTF:
+ // Signature:
+ // __builtin_fprintf (FILE*, format, ...)
+ idx_format = 1;
+ info.argidx = 2;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_FPRINTF_CHK:
+ // Signature:
+ // __builtin_fprintf_chk (FILE*, ost, format, ...)
+ idx_format = 2;
+ info.argidx = 3;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_FPRINTF_UNLOCKED:
+ // Signature:
+ // __builtin_fprintf_unnlocked (FILE*, format, ...)
+ idx_format = 1;
+ info.argidx = 2;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_PRINTF:
+ // Signature:
+ // __builtin_printf (format, ...)
+ idx_format = 0;
+ info.argidx = 1;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_PRINTF_CHK:
+ // Signature:
+ // __builtin_printf_chk (ost, format, ...)
+ idx_format = 1;
+ info.argidx = 2;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_PRINTF_UNLOCKED:
+ // Signature:
+ // __builtin_printf (format, ...)
+ idx_format = 0;
+ info.argidx = 1;
+ idx_dstptr = -1;
+ break;
+
case BUILT_IN_SPRINTF:
// Signature:
// __builtin_sprintf (dst, format, ...)
info.bounded = true;
break;
+ case BUILT_IN_VFPRINTF:
+ // Signature:
+ // __builtin_vprintf (FILE*, format, va_list)
+ idx_format = 1;
+ info.argidx = -1;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_VFPRINTF_CHK:
+ // Signature:
+ // __builtin___vfprintf_chk (FILE*, ost, format, va_list)
+ idx_format = 2;
+ info.argidx = -1;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_VPRINTF:
+ // Signature:
+ // __builtin_vprintf (format, va_list)
+ idx_format = 0;
+ info.argidx = -1;
+ idx_dstptr = -1;
+ break;
+
+ case BUILT_IN_VPRINTF_CHK:
+ // Signature:
+ // __builtin___vprintf_chk (ost, format, va_list)
+ idx_format = 1;
+ info.argidx = -1;
+ idx_dstptr = -1;
+ break;
+
case BUILT_IN_VSNPRINTF:
// Signature:
// __builtin_vsprintf (dst, size, format, va)
/* Set the global warning level for this function. */
warn_level = info.bounded ? warn_format_trunc : warn_format_overflow;
- /* The first argument is a pointer to the destination. */
- tree dstptr = gimple_call_arg (info.callstmt, 0);
+ /* For all string functions the first argument is a pointer to
+ the destination. */
+ tree dstptr = (idx_dstptr < gimple_call_num_args (info.callstmt)
+ ? gimple_call_arg (info.callstmt, 0) : NULL_TREE);
info.format = gimple_call_arg (info.callstmt, idx_format);
/* True when the destination size is constant as opposed to the lower
or upper bound of a range. */
bool dstsize_cst_p = true;
+ bool posunder4k = true;
- if (idx_dstsize == HOST_WIDE_INT_M1U)
+ if (idx_dstsize == UINT_MAX)
{
/* For non-bounded functions like sprintf, determine the size
of the destination from the object or pointer passed to it
/* Avoid warning if -Wstringop-overflow is specified since
it also warns for the same thing though only for the
checking built-ins. */
- if ((idx_objsize == HOST_WIDE_INT_M1U
+ if ((idx_objsize == UINT_MAX
|| !warn_stringop_overflow))
warning_at (gimple_location (info.callstmt), info.warnopt (),
"specified bound %wu exceeds maximum object size "
"%wu",
dstsize, target_size_max () / 2);
+ /* POSIX requires snprintf to fail if DSTSIZE is greater
+ than INT_MAX. Even though not all POSIX implementations
+ conform to the requirement, avoid folding in this case. */
+ posunder4k = false;
}
else if (dstsize > target_int_max ())
- warning_at (gimple_location (info.callstmt), info.warnopt (),
- "specified bound %wu exceeds %<INT_MAX%>",
- dstsize);
+ {
+ warning_at (gimple_location (info.callstmt), info.warnopt (),
+ "specified bound %wu exceeds %<INT_MAX%>",
+ dstsize);
+ /* POSIX requires snprintf to fail if DSTSIZE is greater
+ than INT_MAX. Avoid folding in that case. */
+ posunder4k = false;
+ }
}
else if (TREE_CODE (size) == SSA_NAME)
{
/* Try to determine the range of values of the argument
and use the greater of the two at level 1 and the smaller
of them at level 2. */
- wide_int min, max;
- enum value_range_type range_type
- = get_range_info (size, &min, &max);
- if (range_type == VR_RANGE)
+ const value_range_equiv *vr
+ = CONST_CAST (class vr_values *, vr_values)->get_value_range (size);
+
+ if (range_int_cst_p (vr))
+ {
+ unsigned HOST_WIDE_INT minsize = TREE_INT_CST_LOW (vr->min ());
+ unsigned HOST_WIDE_INT maxsize = TREE_INT_CST_LOW (vr->max ());
+ dstsize = warn_level < 2 ? maxsize : minsize;
+
+ if (minsize > target_int_max ())
+ warning_at (gimple_location (info.callstmt), info.warnopt (),
+ "specified bound range [%wu, %wu] exceeds "
+ "%<INT_MAX%>",
+ minsize, maxsize);
+
+ /* POSIX requires snprintf to fail if DSTSIZE is greater
+ than INT_MAX. Avoid folding if that's possible. */
+ if (maxsize > target_int_max ())
+ posunder4k = false;
+ }
+ else if (vr->varying_p ())
{
- dstsize
- = (warn_level < 2
- ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
- : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+ /* POSIX requires snprintf to fail if DSTSIZE is greater
+ than INT_MAX. Since SIZE's range is unknown, avoid
+ folding. */
+ posunder4k = false;
}
/* The destination size is not constant. If the function is
}
}
- if (idx_objsize != HOST_WIDE_INT_M1U)
+ if (idx_objsize != UINT_MAX)
if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
if (tree_fits_uhwi_p (size))
objsize = tree_to_uhwi (size);
/* For calls to non-bounded functions or to those of bounded
functions with a non-zero size, warn if the destination
pointer is null. */
- if (integer_zerop (dstptr))
+ if (dstptr && integer_zerop (dstptr))
{
/* This is diagnosed with -Wformat only when the null is a constant
pointer. The warning here diagnoses instances where the pointer
is not constant. */
location_t loc = gimple_location (info.callstmt);
warning_at (EXPR_LOC_OR_LOC (dstptr, loc),
- info.warnopt (), "null destination pointer");
+ info.warnopt (), "%Gnull destination pointer",
+ info.callstmt);
return false;
}
/* Avoid warning if -Wstringop-overflow is specified since
it also warns for the same thing though only for the
checking built-ins. */
- && (idx_objsize == HOST_WIDE_INT_M1U
+ && (idx_objsize == UINT_MAX
|| !warn_stringop_overflow))
{
warning_at (gimple_location (info.callstmt), info.warnopt (),
}
}
- if (integer_zerop (info.format))
+ /* Determine if the format argument may be null and warn if not
+ and if the argument is null. */
+ if (integer_zerop (info.format)
+ && gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
{
- /* This is diagnosed with -Wformat only when the null is a constant
- pointer. The warning here diagnoses instances where the pointer
- is not constant. */
location_t loc = gimple_location (info.callstmt);
warning_at (EXPR_LOC_OR_LOC (info.format, loc),
- info.warnopt (), "null format string");
+ info.warnopt (), "%Gnull format string",
+ info.callstmt);
return false;
}
if (!info.fmtstr)
return false;
+ if (warn_restrict)
+ {
+ /* Compute the origin of the destination pointer and its offset
+ from the base object/pointer if possible. */
+ info.dst_offset = 0;
+ info.dst_origin = get_origin_and_offset (dstptr, &info.dst_field,
+ &info.dst_offset);
+ }
+
/* The result is the number of bytes output by the formatted function,
including the terminating NUL. */
- format_result res = format_result ();
+ format_result res;
+
+ /* I/O functions with no destination argument (i.e., all forms of fprintf
+ and printf) may fail under any conditions. Others (i.e., all forms of
+ sprintf) may only fail under specific conditions determined for each
+ directive. Clear POSUNDER4K for the former set of functions and set
+ it to true for the latter (it can only be cleared later, but it is
+ never set to true again). */
+ res.posunder4k = posunder4k && dstptr;
- bool success = compute_format_length (info, &res);
+ bool success = compute_format_length (info, &res, vr_values);
+ if (res.warned)
+ gimple_set_no_warning (info.callstmt, true);
/* When optimizing and the printf return value optimization is enabled,
attempt to substitute the computed result for the return value of
return call_removed;
}
-
-edge
-sprintf_dom_walker::before_dom_children (basic_block bb)
-{
- for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
- {
- /* Iterate over statements, looking for function calls. */
- gimple *stmt = gsi_stmt (si);
-
- if (is_gimple_call (stmt) && handle_gimple_call (&si))
- /* If handle_gimple_call returns true, the iterator is
- already pointing to the next statement. */
- continue;
-
- gsi_next (&si);
- }
- return NULL;
-}
-
-/* Execute the pass for function FUN. */
-
-unsigned int
-pass_sprintf_length::execute (function *fun)
-{
- init_target_to_host_charmap ();
-
- calculate_dominance_info (CDI_DOMINATORS);
-
- sprintf_dom_walker sprintf_dom_walker;
- sprintf_dom_walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
-
- /* Clean up object size info. */
- fini_object_sizes ();
- return 0;
-}
-
-} /* Unnamed namespace. */
-
-/* Return a pointer to a pass object newly constructed from the context
- CTXT. */
-
-gimple_opt_pass *
-make_pass_sprintf_length (gcc::context *ctxt)
-{
- return new pass_sprintf_length (ctxt);
-}