#include "intl.h"
#include "langhooks.h"
#include "c-format.h"
+#include "diagnostic.h"
/* Handle attributes associated with format checking. */
static const char *format_name (int format_num);
static int format_flags (int format_num);
-/* Given a string S of length LINE_WIDTH, find the visual column
- corresponding to OFFSET bytes. */
+/* Emit a warning governed by option OPT, using GMSGID as the format
+ string and AP as its arguments.
-static unsigned int
-location_column_from_byte_offset (const char *s, int line_width,
- unsigned int offset)
-{
- const char * c = s;
- if (*c != '"')
- return 0;
+ Attempt to obtain precise location information within a string
+ literal from FMT_LOC.
+
+ Case 1: if substring location is available, and is within the range of
+ the format string itself, the primary location of the
+ diagnostic is the substring range obtained from FMT_LOC, with the
+ caret at the *end* of the substring range.
+
+ For example:
+
+ test.c:90:10: warning: problem with '%i' here [-Wformat=]
+ printf ("hello %i", msg);
+ ~^
+
+ Case 2: if the substring location is available, but is not within
+ the range of the format string, the primary location is that of the
+ format string, and an note is emitted showing the substring location.
+
+ For example:
+ test.c:90:10: warning: problem with '%i' here [-Wformat=]
+ printf("hello " INT_FMT " world", msg);
+ ^~~~~~~~~~~~~~~~~~~~~~~~~
+ test.c:19: note: format string is defined here
+ #define INT_FMT "%i"
+ ~^
+
+ Case 3: if precise substring information is unavailable, the primary
+ location is that of the whole string passed to FMT_LOC's constructor.
+ For example:
+
+ test.c:90:10: warning: problem with '%i' here [-Wformat=]
+ printf(fmt, msg);
+ ^~~
+
+ For each of cases 1-3, if param_range is non-NULL, then it is used
+ as a secondary range within the warning. For example, here it
+ is used with case 1:
+
+ test.c:90:16: warning: '%s' here but arg 2 has 'long' type [-Wformat=]
+ printf ("foo %s bar", long_i + long_j);
+ ~^ ~~~~~~~~~~~~~~~
- c++, offset--;
- while (offset > 0)
+ and here with case 2:
+
+ test.c:90:16: warning: '%s' here but arg 2 has 'long' type [-Wformat=]
+ printf ("foo " STR_FMT " bar", long_i + long_j);
+ ^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
+ test.c:89:16: note: format string is defined here
+ #define STR_FMT "%s"
+ ~^
+
+ and with case 3:
+
+ test.c:90:10: warning: '%i' here, but arg 2 is "const char *' [-Wformat=]
+ printf(fmt, msg);
+ ^~~ ~~~
+
+ Return true if a warning was emitted, false otherwise. */
+
+ATTRIBUTE_GCC_DIAG (4,0)
+static bool
+format_warning_va (const substring_loc &fmt_loc, source_range *param_range,
+ int opt, const char *gmsgid, va_list *ap)
+{
+ bool substring_within_range = false;
+ location_t primary_loc;
+ location_t substring_loc = UNKNOWN_LOCATION;
+ source_range fmt_loc_range
+ = get_range_from_loc (line_table, fmt_loc.get_fmt_string_loc ());
+ source_range fmt_substring_range;
+ const char *err = fmt_loc.get_range (&fmt_substring_range);
+ if (err)
+ /* Case 3: unable to get substring location. */
+ primary_loc = fmt_loc.get_fmt_string_loc ();
+ else
{
- if (c - s >= line_width)
- return 0;
+ substring_loc = make_location (fmt_substring_range.m_finish,
+ fmt_substring_range.m_start,
+ fmt_substring_range.m_finish);
- switch (*c)
+ if (fmt_substring_range.m_start >= fmt_loc_range.m_start
+ && fmt_substring_range.m_finish <= fmt_loc_range.m_finish)
+ /* Case 1. */
{
- case '\\':
- c++;
- if (c - s >= line_width)
- return 0;
- switch (*c)
- {
- case '\\': case '\'': case '"': case '?':
- case '(': case '{': case '[': case '%':
- case 'a': case 'b': case 'f': case 'n':
- case 'r': case 't': case 'v':
- case 'e': case 'E':
- c++, offset--;
- break;
+ substring_within_range = true;
+ primary_loc = substring_loc;
+ }
+ else
+ /* Case 2. */
+ {
+ substring_within_range = false;
+ primary_loc = fmt_loc.get_fmt_string_loc ();
+ }
+ }
- default:
- return 0;
- }
- break;
+ rich_location richloc (line_table, primary_loc);
- case '"':
- /* We found the end of the string too early. */
- return 0;
-
- default:
- c++, offset--;
- break;
- }
+ if (param_range)
+ {
+ location_t param_loc = make_location (param_range->m_start,
+ param_range->m_start,
+ param_range->m_finish);
+ richloc.add_range (param_loc, false);
}
- return c - s;
+
+ diagnostic_info diagnostic;
+ diagnostic_set_info (&diagnostic, gmsgid, ap, &richloc, DK_WARNING);
+ diagnostic.option_index = opt;
+ bool warned = report_diagnostic (&diagnostic);
+
+ if (!err && substring_loc && !substring_within_range)
+ /* Case 2. */
+ if (warned)
+ inform (substring_loc, "format string is defined here");
+
+ return warned;
}
-/* Return a location that encodes the same location as LOC but shifted
- by OFFSET bytes. */
+/* Variadic call to format_warning_va. */
-static location_t
-location_from_offset (location_t loc, int offset)
+ATTRIBUTE_GCC_DIAG (4,0)
+static bool
+format_warning_at_substring (const substring_loc &fmt_loc,
+ source_range *param_range,
+ int opt, const char *gmsgid, ...)
{
- gcc_checking_assert (offset >= 0);
- if (linemap_location_from_macro_expansion_p (line_table, loc)
- || offset < 0)
- return loc;
+ va_list ap;
+ va_start (ap, gmsgid);
+ bool warned = format_warning_va (fmt_loc, param_range, opt, gmsgid, &ap);
+ va_end (ap);
- expanded_location s = expand_location_to_spelling_point (loc);
- int line_width;
- const char *line = location_get_source_line (s.file, s.line, &line_width);
- if (line == NULL)
- return loc;
- line += s.column - 1 ;
- line_width -= s.column - 1;
- unsigned int column =
- location_column_from_byte_offset (line, line_width, (unsigned) offset);
+ return warned;
+}
+
+/* Emit a warning as per format_warning_va, but construct the substring_loc
+ for the character at offset (CHAR_IDX - 1) within a string constant
+ FORMAT_STRING_CST at FMT_STRING_LOC. */
- return linemap_position_for_loc_and_offset (line_table, loc, column);
+ATTRIBUTE_GCC_DIAG (5,6)
+static bool
+format_warning_at_char (location_t fmt_string_loc, tree format_string_cst,
+ int char_idx, int opt, const char *gmsgid, ...)
+{
+ va_list ap;
+ va_start (ap, gmsgid);
+ tree string_type = TREE_TYPE (format_string_cst);
+
+ /* The callers are of the form:
+ format_warning (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ where format_chars has already been incremented, so that
+ CHAR_IDX is one character beyond where the warning should
+ be emitted. Fix it. */
+ char_idx -= 1;
+
+ substring_loc fmt_loc (fmt_string_loc, string_type, char_idx, char_idx);
+ bool warned = format_warning_va (fmt_loc, NULL, opt, gmsgid, &ap);
+ va_end (ap);
+
+ return warned;
}
/* Check that we have a pointer to a string suitable for use as a format.
static void check_format_info (function_format_info *, tree);
static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
static void check_format_info_main (format_check_results *,
- function_format_info *,
- const char *, int, tree,
+ function_format_info *, const char *,
+ location_t, tree,
+ int, tree,
unsigned HOST_WIDE_INT,
object_allocator<format_wanted_type> &);
static const format_flag_spec *get_flag_spec (const format_flag_spec *,
int, const char *);
-static void check_format_types (location_t, format_wanted_type *);
-static void format_type_warning (location_t, format_wanted_type *, tree, tree);
+static void check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *);
+static void format_type_warning (const substring_loc &fmt_loc,
+ source_range *param_range,
+ format_wanted_type *, tree,
+ tree);
/* Decode a format type from a string, returning the type, or
format_type_error if not valid, in which case the caller should print an
tree array_size = 0;
tree array_init;
+ location_t fmt_param_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+
if (VAR_P (format_tree))
{
/* Pull out a constant value if the front end didn't. */
need not adjust it for every return. */
res->number_other++;
object_allocator <format_wanted_type> fwt_pool ("format_wanted_type pool");
- check_format_info_main (res, info, format_chars, format_length,
- params, arg_num, fwt_pool);
+ check_format_info_main (res, info, format_chars, fmt_param_loc, format_tree,
+ format_length, params, arg_num, fwt_pool);
}
/* Support class for argument_parser and check_format_info_main.
const format_char_info *fci,
const format_flag_spec *flag_specs,
const char * const format_chars,
+ tree format_string_cst,
location_t format_string_loc,
const char * const orig_format_chars,
char format_char);
{
public:
argument_parser (function_format_info *info, const char *&format_chars,
+ tree format_string_cst,
const char * const orig_format_chars,
location_t format_string_loc, flag_chars_t &flag_chars,
int &has_operand_number, tree first_fillin_param,
unsigned HOST_WIDE_INT &arg_num,
tree ¶ms,
const int alloc_flag,
- const char * const format_start);
+ const char * const format_start,
+ location_t fmt_param_loc);
private:
const function_format_info *const info;
const format_kind_info * const fki;
const format_flag_spec * const flag_specs;
+ const char *start_of_this_format;
const char *&format_chars;
+ const tree format_string_cst;
const char * const orig_format_chars;
const location_t format_string_loc;
object_allocator <format_wanted_type> &fwt_pool;
const format_char_info *fci,
const format_flag_spec *flag_specs,
const char * const format_chars,
+ tree format_string_cst,
location_t format_string_loc,
const char * const orig_format_chars,
char format_char)
continue;
if (strchr (fci->flag_chars, m_flag_chars[i]) == 0)
{
- warning_at (location_from_offset (format_string_loc,
- format_chars
- - orig_format_chars),
- OPT_Wformat_, "%s used with %<%%%c%> %s format",
- _(s->name), format_char, fki->name);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s used with %<%%%c%> %s format",
+ _(s->name), format_char, fki->name);
d++;
continue;
}
argument_parser::
argument_parser (function_format_info *info_, const char *&format_chars_,
+ tree format_string_cst_,
const char * const orig_format_chars_,
location_t format_string_loc_,
flag_chars_t &flag_chars_,
: info (info_),
fki (&format_types[info->format_type]),
flag_specs (fki->flag_specs),
+ start_of_this_format (format_chars_),
format_chars (format_chars_),
+ format_string_cst (format_string_cst_),
orig_format_chars (orig_format_chars_),
format_string_loc (format_string_loc_),
fwt_pool (fwt_pool_),
*format_chars, NULL);
if (flag_chars.has_char_p (*format_chars))
{
- warning_at (location_from_offset (format_string_loc,
- format_chars + 1
- - orig_format_chars),
- OPT_Wformat_,
- "repeated %s in format", _(s->name));
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
}
else
flag_chars.add_char (*format_chars);
++format_chars;
flag_chars.add_char (fki->left_precision_char);
if (!ISDIGIT (*format_chars))
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "empty left precision in %s format", fki->name);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty left precision in %s format", fki->name);
while (ISDIGIT (*format_chars))
++format_chars;
}
{
if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
&& !ISDIGIT (*format_chars))
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "empty precision in %s format", fki->name);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty precision in %s format", fki->name);
while (ISDIGIT (*format_chars))
++format_chars;
}
{
const format_flag_spec *s = get_flag_spec (flag_specs,
*format_chars, NULL);
- warning_at (location_from_offset (format_string_loc,
- format_chars
- - orig_format_chars),
- OPT_Wformat_,
- "repeated %s in format", _(s->name));
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
}
else
flag_chars.add_char (*format_chars);
if (fci->format_chars == 0)
{
if (ISGRAPH (format_char))
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "unknown conversion type character %qc in format",
- format_char);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unknown conversion type character"
+ " %qc in format",
+ format_char);
else
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "unknown conversion type character 0x%x in format",
- format_char);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unknown conversion type character"
+ " 0x%x in format",
+ format_char);
return NULL;
}
if (pedantic)
{
if (ADJ_STD (fci->std) > C_STD_VER)
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "%s does not support the %<%%%c%> %s format",
- C_STD_NAME (fci->std), format_char, fki->name);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%c%> %s format",
+ C_STD_NAME (fci->std), format_char, fki->name);
}
return fci;
++format_chars;
if (*format_chars != ']')
/* The end of the format string was reached. */
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "no closing %<]%> for %<%%[%> format");
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "no closing %<]%> for %<%%[%> format");
}
/* Return true if this argument is to be continued to be parsed,
wanted_type_std = fci->types[len_modifier.val].std;
if (wanted_type == 0)
{
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "use of %qs length modifier with %qc type character"
- " has either no effect or undefined behavior",
- len_modifier.chars, format_char);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "use of %qs length modifier with %qc type"
+ " character has either no effect"
+ " or undefined behavior",
+ len_modifier.chars, format_char);
/* Heuristic: skip one argument when an invalid length/type
combination is encountered. */
arg_num++;
&& ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
{
if (ADJ_STD (wanted_type_std) > C_STD_VER)
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "%s does not support the %<%%%s%c%> %s format",
- C_STD_NAME (wanted_type_std), len_modifier.chars,
- format_char, fki->name);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%s%c%> %s format",
+ C_STD_NAME (wanted_type_std),
+ len_modifier.chars,
+ format_char, fki->name);
}
return true;
unsigned HOST_WIDE_INT &arg_num,
tree ¶ms,
const int alloc_flag,
- const char * const format_start)
+ const char * const format_start,
+ location_t fmt_param_loc)
{
if (info->first_arg_num == 0)
return true;
}
if (first_wanted_type != 0)
- check_format_types (format_string_loc, first_wanted_type);
+ {
+ ptrdiff_t offset_to_format_start = (start_of_this_format - 1) - orig_format_chars;
+ ptrdiff_t offset_to_format_end = (format_chars - 1) - orig_format_chars;
+ substring_loc fmt_loc (fmt_param_loc, TREE_TYPE (format_string_cst),
+ offset_to_format_start, offset_to_format_end);
+ check_format_types (fmt_loc, first_wanted_type);
+ }
return true;
}
static void
check_format_info_main (format_check_results *res,
function_format_info *info, const char *format_chars,
+ location_t fmt_param_loc, tree format_string_cst,
int format_length, tree params,
unsigned HOST_WIDE_INT arg_num,
object_allocator <format_wanted_type> &fwt_pool)
continue;
if (*format_chars == 0)
{
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "spurious trailing %<%%%> in format");
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "spurious trailing %<%%%> in format");
continue;
}
if (*format_chars == '%')
}
flag_chars_t flag_chars;
- argument_parser arg_parser (info, format_chars, orig_format_chars,
- format_string_loc,
+ argument_parser arg_parser (info, format_chars, format_string_cst,
+ orig_format_chars, format_string_loc,
flag_chars, has_operand_number,
first_fillin_param, fwt_pool);
|| (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
&& format_char == '%'))
{
- warning_at (location_from_offset (format_string_loc,
- format_chars - orig_format_chars),
- OPT_Wformat_,
- "conversion lacks type at end of format");
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "conversion lacks type at end of format");
continue;
}
format_chars++;
continue;
flag_chars.validate (fki, fci, flag_specs, format_chars,
+ format_string_cst,
format_string_loc, orig_format_chars, format_char);
const int alloc_flag = flag_chars.get_alloc_flag (fki);
suppressed,
arg_num, params,
alloc_flag,
- format_start))
+ format_start, fmt_param_loc))
return;
}
if (format_chars - orig_format_chars != format_length)
- warning_at (location_from_offset (format_string_loc,
- format_chars + 1 - orig_format_chars),
- OPT_Wformat_contains_nul,
- "embedded %<\\0%> in format");
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_contains_nul,
+ "embedded %<\\0%> in format");
if (info->first_arg_num != 0 && params != 0
&& has_operand_number <= 0)
{
finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
}
-
/* Check the argument types from a single format conversion (possibly
- including width and precision arguments). LOC is the location of
- the format string. */
+ including width and precision arguments). FMT_LOC is the
+ location of the format conversion. */
static void
-check_format_types (location_t loc, format_wanted_type *types)
+check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *types)
{
for (; types != 0; types = types->next)
{
cur_param = types->param;
if (!cur_param)
{
- format_type_warning (loc, types, wanted_type, NULL);
+ format_type_warning (fmt_loc, NULL, types, wanted_type, NULL);
continue;
}
orig_cur_type = cur_type;
char_type_flag = 0;
+ source_range param_range;
+ source_range *param_range_ptr;
+ if (CAN_HAVE_LOCATION_P (cur_param))
+ {
+ param_range = EXPR_LOCATION_RANGE (cur_param);
+ param_range_ptr = ¶m_range;
+ }
+ else
+ param_range_ptr = NULL;
+
STRIP_NOPS (cur_param);
/* Check the types of any additional pointer arguments
}
else
{
- format_type_warning (loc, types, wanted_type, orig_cur_type);
+ format_type_warning (fmt_loc, param_range_ptr,
+ types, wanted_type, orig_cur_type);
break;
}
}
&& TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type))
continue;
/* Now we have a type mismatch. */
- format_type_warning (loc, types, wanted_type, orig_cur_type);
+ format_type_warning (fmt_loc, param_range_ptr, types,
+ wanted_type, orig_cur_type);
}
}
-/* Give a warning at LOC about a format argument of different type from that
- expected. WANTED_TYPE is the type the argument should have, possibly
- stripped of pointer dereferences. The description (such as "field
+/* Give a warning at FMT_LOC about a format argument of different type
+ from that expected. If non-NULL, PARAM_RANGE is the source range of the
+ relevant argument. WANTED_TYPE is the type the argument should have,
+ possibly stripped of pointer dereferences. The description (such as "field
precision"), the placement in the format string, a possibly more
friendly name of WANTED_TYPE, and the number of pointer dereferences
are taken from TYPE. ARG_TYPE is the type of the actual argument,
or NULL if it is missing. */
static void
-format_type_warning (location_t loc, format_wanted_type *type,
+format_type_warning (const substring_loc &fmt_loc,
+ source_range *param_range,
+ format_wanted_type *type,
tree wanted_type, tree arg_type)
{
int kind = type->kind;
int format_length = type->format_length;
int pointer_count = type->pointer_count;
int arg_num = type->arg_num;
- unsigned int offset_loc = type->offset_loc;
char *p;
/* If ARG_TYPE is a typedef with a misleading name (for example,
p[pointer_count + 1] = 0;
}
- loc = location_from_offset (loc, offset_loc);
-
if (wanted_type_name)
{
if (arg_type)
- warning_at (loc, OPT_Wformat_,
- "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
- "but argument %d has type %qT",
- gettext (kind_descriptions[kind]),
- (kind == CF_KIND_FORMAT ? "%" : ""),
- format_length, format_start,
- wanted_type_name, p, arg_num, arg_type);
+ format_warning_at_substring
+ (fmt_loc, param_range,
+ OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
+ "but argument %d has type %qT",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type_name, p, arg_num, arg_type);
else
- warning_at (loc, OPT_Wformat_,
- "%s %<%s%.*s%> expects a matching %<%s%s%> argument",
- gettext (kind_descriptions[kind]),
- (kind == CF_KIND_FORMAT ? "%" : ""),
- format_length, format_start, wanted_type_name, p);
+ format_warning_at_substring
+ (fmt_loc, param_range,
+ OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%s%s%> argument",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type_name, p);
}
else
{
if (arg_type)
- warning_at (loc, OPT_Wformat_,
- "%s %<%s%.*s%> expects argument of type %<%T%s%>, "
- "but argument %d has type %qT",
- gettext (kind_descriptions[kind]),
- (kind == CF_KIND_FORMAT ? "%" : ""),
- format_length, format_start,
- wanted_type, p, arg_num, arg_type);
+ format_warning_at_substring
+ (fmt_loc, param_range,
+ OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%T%s%>, "
+ "but argument %d has type %qT",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type, p, arg_num, arg_type);
else
- warning_at (loc, OPT_Wformat_,
- "%s %<%s%.*s%> expects a matching %<%T%s%> argument",
- gettext (kind_descriptions[kind]),
- (kind == CF_KIND_FORMAT ? "%" : ""),
- format_length, format_start, wanted_type, p);
+ format_warning_at_substring
+ (fmt_loc, param_range,
+ OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%T%s%> argument",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type, p);
}
}
--- /dev/null
+/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+
+/* See PR 52952. */
+
+#include "format.h"
+
+void test_mismatching_types (const char *msg)
+{
+ printf("hello %i", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+
+/* TODO: ideally would also underline "msg". */
+/* { dg-begin-multiline-output "" }
+ printf("hello %i", msg);
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_multiple_arguments (void)
+{
+ printf ("arg0: %i arg1: %s arg 2: %i", /* { dg-warning "29: format '%s'" } */
+ 100, 101, 102);
+/* TODO: ideally would also underline "101". */
+/* { dg-begin-multiline-output "" }
+ printf ("arg0: %i arg1: %s arg 2: %i",
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_multiple_arguments_2 (int i, int j)
+{
+ printf ("arg0: %i arg1: %s arg 2: %i", /* { dg-warning "29: format '%s'" } */
+ 100, i + j, 102);
+/* { dg-begin-multiline-output "" }
+ printf ("arg0: %i arg1: %s arg 2: %i",
+ ~^
+ 100, i + j, 102);
+ ~~~~~
+ { dg-end-multiline-output "" } */
+}
+
+void multiline_format_string (void) {
+ printf ("before the fmt specifier" /* { dg-warning "11: format '%d' expects a matching 'int' argument" } */
+/* { dg-begin-multiline-output "" }
+ printf ("before the fmt specifier"
+ ^~~~~~~~~~~~~~~~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
+
+ "%"
+ "d" /* { dg-message "12: format string is defined here" } */
+ "after the fmt specifier");
+
+/* { dg-begin-multiline-output "" }
+ "%"
+ ~~
+ "d"
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_hex (const char *msg)
+{
+ /* "%" is \x25
+ "i" is \x69 */
+ printf("hello \x25\x69", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+
+/* TODO: ideally would also underline "msg". */
+/* { dg-begin-multiline-output "" }
+ printf("hello \x25\x69", msg);
+ ~~~~~~~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_oct (const char *msg)
+{
+ /* "%" is octal 045
+ "i" is octal 151. */
+ printf("hello \045\151", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+
+/* TODO: ideally would also underline "msg". */
+/* { dg-begin-multiline-output "" }
+ printf("hello \045\151", msg);
+ ~~~~~~~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_multiple (const char *msg)
+{
+ /* "%" is \x25 in hex
+ "i" is \151 in octal. */
+ printf("prefix" "\x25" "\151" "suffix", /* { dg-warning "format '%i'" } */
+ msg);
+/* { dg-begin-multiline-output "" }
+ printf("prefix" "\x25" "\151" "suffix",
+ ^~~~~~~~
+ { dg-end-multiline-output "" } */
+
+/* TODO: ideally would also underline "msg". */
+/* { dg-begin-multiline-output "" }
+ printf("prefix" "\x25" "\151" "suffix",
+ ~~~~~~~~~~~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_u8 (const char *msg)
+{
+ printf(u8"hello %i", msg);/* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+/* TODO: ideally would also underline "msg". */
+/* { dg-begin-multiline-output "" }
+ printf(u8"hello %i", msg);
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_param (long long_i, long long_j)
+{
+ printf ("foo %s bar", long_i + long_j); /* { dg-warning "17: format '%s' expects argument of type 'char \\*', but argument 2 has type 'long int'" } */
+/* { dg-begin-multiline-output "" }
+ printf ("foo %s bar", long_i + long_j);
+ ~^ ~~~~~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
+}
+
+void test_field_width_specifier (long l, int i1, int i2)
+{
+ printf (" %*.*d ", l, i1, i2); /* { dg-warning "17: field width specifier '\\*' expects argument of type 'int', but argument 2 has type 'long int'" } */
+/* { dg-begin-multiline-output "" }
+ printf (" %*.*d ", l, i1, i2);
+ ~~~~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_spurious_percent (void)
+{
+ printf("hello world %"); /* { dg-warning "23: spurious trailing" } */
+
+/* { dg-begin-multiline-output "" }
+ printf("hello world %");
+ ^
+ { dg-end-multiline-output "" } */
+}
+
+void test_empty_precision (char *s, size_t m, double d)
+{
+ strfmon (s, m, "%#.5n", d); /* { dg-warning "20: empty left precision in gnu_strfmon format" } */
+/* { dg-begin-multiline-output "" }
+ strfmon (s, m, "%#.5n", d);
+ ^
+ { dg-end-multiline-output "" } */
+
+ strfmon (s, m, "%#5.n", d); /* { dg-warning "22: empty precision in gnu_strfmon format" } */
+/* { dg-begin-multiline-output "" }
+ strfmon (s, m, "%#5.n", d);
+ ^
+ { dg-end-multiline-output "" } */
+}
+
+void test_repeated (int i)
+{
+ printf ("%++d", i); /* { dg-warning "14: repeated '\\+' flag in format" } */
+/* { dg-begin-multiline-output "" }
+ printf ("%++d", i);
+ ^
+ { dg-end-multiline-output "" } */
+}
+
+void test_conversion_lacks_type (void)
+{
+ printf (" %h"); /* { dg-warning "14:conversion lacks type at end of format" } */
+/* { dg-begin-multiline-output "" }
+ printf (" %h");
+ ^
+ { dg-end-multiline-output "" } */
+}
+
+void test_embedded_nul (void)
+{
+ printf (" \0 "); /* { dg-warning "14:embedded" "warning for embedded NUL" } */
+/* { dg-begin-multiline-output "" }
+ printf (" \0 ");
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_macro (const char *msg)
+{
+#define INT_FMT "%i" /* { dg-message "19: format string is defined here" } */
+ printf("hello " INT_FMT " world", msg); /* { dg-warning "10: format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+/* { dg-begin-multiline-output "" }
+ printf("hello " INT_FMT " world", msg);
+ ^~~~~~~~
+ { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+ #define INT_FMT "%i"
+ ~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_non_contiguous_strings (void)
+{
+ __builtin_printf(" %" "d ", 0.5); /* { dg-warning "20: format .%d. expects argument of type .int., but argument 2 has type .double." } */
+ /* { dg-message "26: format string is defined here" "" { target *-*-* } 200 } */
+ /* { dg-begin-multiline-output "" }
+ __builtin_printf(" %" "d ", 0.5);
+ ^~~~
+ { dg-end-multiline-output "" } */
+ /* { dg-begin-multiline-output "" }
+ __builtin_printf(" %" "d ", 0.5);
+ ~~~~^
+ { dg-end-multiline-output "" } */
+}
+
+void test_const_arrays (void)
+{
+ /* TODO: ideally we'd highlight both the format string *and* the use of
+ it here. For now, just verify that we gracefully handle this case. */
+ const char a[] = " %d ";
+ __builtin_printf(a, 0.5); /* { dg-warning "20: format .%d. expects argument of type .int., but argument 2 has type .double." } */
+ /* { dg-begin-multiline-output "" }
+ __builtin_printf(a, 0.5);
+ ^
+ { dg-end-multiline-output "" } */
+}