static void check_format_types (const substring_loc &fmt_loc,
format_wanted_type *,
- const format_kind_info *fki);
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char);
static void format_type_warning (const substring_loc &fmt_loc,
source_range *param_range,
format_wanted_type *, tree,
tree,
- const format_kind_info *fki);
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char);
/* 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 ¶ms,
const int alloc_flag,
const char * const format_start,
- location_t fmt_param_loc);
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char);
private:
const function_format_info *const info;
tree ¶ms,
const int alloc_flag,
const char * const format_start,
- location_t fmt_param_loc)
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char)
{
if (info->first_arg_num == 0)
return true;
substring_loc fmt_loc (fmt_param_loc, TREE_TYPE (format_string_cst),
offset_to_format_end,
offset_to_format_start, offset_to_format_end);
- check_format_types (fmt_loc, first_wanted_type, fki);
+ ptrdiff_t offset_to_type_start = type_start - orig_format_chars;
+ check_format_types (fmt_loc, first_wanted_type, fki,
+ offset_to_type_start,
+ conversion_char);
}
return true;
arg_parser.handle_alloc_chars ();
+ /* The rest of the conversion specification is the length modifier
+ (if any), and the conversion specifier, so this is where the
+ type information starts. If we need to issue a suggestion
+ about a type mismatch, then we should preserve everything up
+ to here. */
+ const char *type_start = format_chars;
+
/* Read any length modifier, if this kind of format has them. */
const length_modifier len_modifier
= arg_parser.read_any_length_modifier ();
suppressed,
arg_num, params,
alloc_flag,
- format_start, fmt_param_loc))
+ format_start, type_start,
+ fmt_param_loc,
+ format_char))
return;
}
}
/* Check the argument types from a single format conversion (possibly
- including width and precision arguments). FMT_LOC is the
- location of the format conversion. */
+ including width and precision arguments).
+
+ FMT_LOC is the location of the format conversion.
+
+ TYPES is a singly-linked list expressing the parts of the format
+ conversion that expect argument types, and the arguments they
+ correspond to.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+
+ then FMT_LOC covers this range:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^^^^^^^
+
+ and TYPES in this case is a three-entry singly-linked list consisting of:
+ (1) the check for the field width here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^^^^
+ against arg3, and
+ (2) the check for the field precision here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^ ^^^^
+ against arg4, and
+ (3) the check for the length modifier and conversion char here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^ ^^^^
+ against arg5.
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST:
+
+ 0000000000111111111122
+ 0123456789012345678901
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here. */
+
static void
check_format_types (const substring_loc &fmt_loc,
- format_wanted_type *types, const format_kind_info *fki)
+ format_wanted_type *types, const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char)
{
for (; types != 0; types = types->next)
{
cur_param = types->param;
if (!cur_param)
{
- format_type_warning (fmt_loc, NULL, types, wanted_type, NULL, fki);
+ format_type_warning (fmt_loc, NULL, types, wanted_type, NULL, fki,
+ offset_to_type_start, conversion_char);
continue;
}
else
{
format_type_warning (fmt_loc, param_range_ptr,
- types, wanted_type, orig_cur_type, fki);
+ types, wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
break;
}
}
continue;
/* Now we have a type mismatch. */
format_type_warning (fmt_loc, param_range_ptr, types,
- wanted_type, orig_cur_type, fki);
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
}
}
#endif /* CHECKING_P */
-/* Generate a string containing the format string that should be
- used to format arguments of type ARG_TYPE within FKI (effectively
- the inverse of the checking code).
+/* Determine if SPEC_TYPE and ARG_TYPE are sufficiently similar for a
+ format_type_detail using SPEC_TYPE to be offered as a suggestion for
+ Wformat type errors where the argument has type ARG_TYPE. */
+
+static bool
+matching_type_p (tree spec_type, tree arg_type)
+{
+ gcc_assert (spec_type);
+ gcc_assert (arg_type);
+
+ spec_type = TYPE_CANONICAL (spec_type);
+ arg_type = TYPE_CANONICAL (arg_type);
+
+ if (TREE_CODE (spec_type) == INTEGER_TYPE
+ && TREE_CODE (arg_type) == INTEGER_TYPE
+ && (TYPE_UNSIGNED (spec_type)
+ ? spec_type == c_common_unsigned_type (arg_type)
+ : spec_type == c_common_signed_type (arg_type)))
+ return true;
+
+ return spec_type == arg_type;
+}
+
+/* Subroutine of get_format_for_type.
+
+ Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If CONVERSION_CHAR is not zero (the first pass), the resulting suggestion
+ is required to use it, for correcting bogus length modifiers.
+ If CONVERSION_CHAR is zero (the second pass), then allow any suggestion
+ that matches ARG_TYPE.
If successful, returns a non-NULL string which should be freed
- by the called.
+ by the caller.
Otherwise, returns NULL. */
static char *
-get_format_for_type (const format_kind_info *fki, tree arg_type)
+get_format_for_type_1 (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
{
gcc_assert (arg_type);
spec->format_chars;
spec++)
{
+ if (conversion_char)
+ if (!strchr (spec->format_chars, conversion_char))
+ continue;
+
tree effective_arg_type = deref_n_times (arg_type,
spec->pointer_count);
if (!effective_arg_type)
const format_type_detail *ftd = &spec->types[i];
if (!ftd->type)
continue;
- if (TYPE_CANONICAL (*ftd->type)
- == TYPE_CANONICAL (effective_arg_type))
+ if (matching_type_p (*ftd->type, effective_arg_type))
{
const char *len_modifier
= get_modifier_for_format_len (fki->length_char_specs,
if (!len_modifier)
len_modifier = "";
- return xasprintf ("%%%s%c",
- len_modifier,
- spec->format_chars[0]);
+ if (conversion_char)
+ /* We found a match, using the given conversion char - the
+ length modifier was incorrect (or absent).
+ Provide a suggestion using the conversion char with the
+ correct length modifier for the type. */
+ return xasprintf ("%s%c", len_modifier, conversion_char);
+ else
+ /* 2nd pass: no match was possible using the user-provided
+ conversion char, but we do have a match without using it.
+ Provide a suggestion using the first conversion char
+ listed for the given type. */
+ return xasprintf ("%s%c", len_modifier, spec->format_chars[0]);
}
}
}
+
return NULL;
}
+/* Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_format_for_type (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
+{
+ gcc_assert (arg_type);
+ gcc_assert (conversion_char);
+
+ /* First pass: look for a format_char_info containing CONVERSION_CHAR
+ If we find one, then presumably the length modifier was incorrect
+ (or absent). */
+ char *result = get_format_for_type_1 (fki, arg_type, conversion_char);
+ if (result)
+ return result;
+
+ /* Second pass: we didn't find a match for CONVERSION_CHAR, so try
+ matching just on the type. */
+ return get_format_for_type_1 (fki, arg_type, '\0');
+}
+
+/* Attempt to get a string for use as a replacement fix-it hint for the
+ source range in FMT_LOC.
+
+ Preserve all of the text within the range of FMT_LOC up to
+ OFFSET_TO_TYPE_START, replacing the rest with an appropriate
+ length modifier and conversion specifier for ARG_TYPE, attempting
+ to keep the user-provided CONVERSION_CHAR if possible.
+
+ For example, given a long vs long long mismatch for arg5 here:
+
+ 000000000111111111122222222223333333333|
+ 123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` string offsets
+ V~~~~~~~~ : range of FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here
+
+ where OFFSET_TO_TYPE_START is 13 (the offset to the "lld" within the
+ STRING_CST), where the user provided:
+ %-+*.*lld
+ the result (assuming "long" argument 5) should be:
+ %-+*.*ld
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_corrected_substring (const substring_loc &fmt_loc,
+ format_wanted_type *type, tree arg_type,
+ const format_kind_info *fki,
+ int offset_to_type_start, char conversion_char)
+{
+ /* Attempt to provide hints for argument types, but not for field widths
+ and precisions. */
+ if (!arg_type)
+ return NULL;
+ if (type->kind != CF_KIND_FORMAT)
+ return NULL;
+
+ /* Locate the current code within the source range, rejecting
+ any awkward cases where the format string occupies more than
+ one line.
+ Lookup the place where the type starts (including any length
+ modifiers), getting it as the caret location. */
+ substring_loc type_loc (fmt_loc);
+ type_loc.set_caret_index (offset_to_type_start);
+
+ location_t fmt_substring_loc;
+ const char *err = type_loc.get_location (&fmt_substring_loc);
+ if (err)
+ return NULL;
+
+ source_range fmt_substring_range
+ = get_range_from_loc (line_table, fmt_substring_loc);
+
+ expanded_location caret
+ = expand_location_to_spelling_point (fmt_substring_loc);
+ expanded_location start
+ = expand_location_to_spelling_point (fmt_substring_range.m_start);
+ expanded_location finish
+ = expand_location_to_spelling_point (fmt_substring_range.m_finish);
+ if (caret.file != start.file)
+ return NULL;
+ if (start.file != finish.file)
+ return NULL;
+ if (caret.line != start.line)
+ return NULL;
+ if (start.line != finish.line)
+ return NULL;
+ if (start.column > caret.column)
+ return NULL;
+ if (start.column > finish.column)
+ return NULL;
+ if (caret.column > finish.column)
+ return NULL;
+
+ int line_width;
+ const char *line = location_get_source_line (start.file, start.line,
+ &line_width);
+ if (line == NULL)
+ return NULL;
+
+ /* If we got this far, then we have the line containing the
+ existing conversion specification.
+
+ Generate a trimmed copy, containing the prefix part of the conversion
+ specification, up to the (but not including) the length modifier.
+ In the above example, this would be "%-+*.*". */
+ const char *current_content = line + start.column - 1;
+ int length_up_to_type = caret.column - start.column;
+ char *prefix = xstrndup (current_content, length_up_to_type);
+
+ /* Now attempt to generate a suggestion for the rest of the specification
+ (length modifier and conversion char), based on ARG_TYPE and
+ CONVERSION_CHAR.
+ In the above example, this would be "ld". */
+ char *format_for_type = get_format_for_type (fki, arg_type, conversion_char);
+ if (!format_for_type)
+ {
+ free (prefix);
+ return NULL;
+ }
+
+ /* Success. Generate the resulting suggestion for the whole range of
+ FMT_LOC by concatenating the two strings.
+ In the above example, this would be "%-+*.*ld". */
+ char *result = concat (prefix, format_for_type, NULL);
+ free (format_for_type);
+ free (prefix);
+ return result;
+}
+
/* Give a warning about a format argument of different type from that expected.
The range of the diagnostic is taken from WHOLE_FMT_LOC; the caret location
is based on the location of the char at TYPE->offset_loc.
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. */
+ or NULL if it is missing.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given a type mismatch for argument 5 here:
+
+ 00000000011111111112222222222333333333344444444445555555555|
+ 12345678901234567890123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` offsets within STRING_CST
+ V~~~~~~~~ : range of WHOLE_FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", int_expr, int_expr, long_expr);
+ ^ ^ ^~~~~~~~~
+ | ` CONVERSION_CHAR: 'd' *PARAM_RANGE
+ type starts here
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST. */
+
static void
format_type_warning (const substring_loc &whole_fmt_loc,
source_range *param_range,
format_wanted_type *type,
tree wanted_type, tree arg_type,
- const format_kind_info *fki)
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char)
{
enum format_specifier_kind kind = type->kind;
const char *wanted_type_name = type->wanted_type_name;
substring_loc fmt_loc (whole_fmt_loc);
fmt_loc.set_caret_index (type->offset_loc - 1);
- /* Attempt to provide hints for argument types, but not for field widths
- and precisions. */
- char *format_for_type = NULL;
- if (arg_type && kind == CF_KIND_FORMAT)
- format_for_type = get_format_for_type (fki, arg_type);
+ /* Get a string for use as a replacement fix-it hint for the range in
+ fmt_loc, or NULL. */
+ char *corrected_substring
+ = get_corrected_substring (fmt_loc, type, arg_type, fki,
+ offset_to_type_start, conversion_char);
if (wanted_type_name)
{
if (arg_type)
format_warning_at_substring
(fmt_loc, param_range,
- format_for_type, OPT_Wformat_,
+ corrected_substring, OPT_Wformat_,
"%s %<%s%.*s%> expects argument of type %<%s%s%>, "
"but argument %d has type %qT",
gettext (kind_descriptions[kind]),
else
format_warning_at_substring
(fmt_loc, param_range,
- format_for_type, OPT_Wformat_,
+ corrected_substring, OPT_Wformat_,
"%s %<%s%.*s%> expects a matching %<%s%s%> argument",
gettext (kind_descriptions[kind]),
(kind == CF_KIND_FORMAT ? "%" : ""),
if (arg_type)
format_warning_at_substring
(fmt_loc, param_range,
- format_for_type, OPT_Wformat_,
+ corrected_substring, OPT_Wformat_,
"%s %<%s%.*s%> expects argument of type %<%T%s%>, "
"but argument %d has type %qT",
gettext (kind_descriptions[kind]),
else
format_warning_at_substring
(fmt_loc, param_range,
- format_for_type, OPT_Wformat_,
+ corrected_substring, 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);
}
- free (format_for_type);
+ free (corrected_substring);
}
return fki;
}
-/* Verify that get_format_for_type (FKI, TYPE) is EXPECTED_FORMAT. */
+/* Verify that get_format_for_type (FKI, TYPE, CONVERSION_CHAR)
+ is EXPECTED_FORMAT. */
static void
assert_format_for_type_streq (const location &loc, const format_kind_info *fki,
- const char *expected_format, tree type)
+ const char *expected_format, tree type,
+ char conversion_char)
{
gcc_assert (fki);
gcc_assert (expected_format);
gcc_assert (type);
- char *actual_format = get_format_for_type (fki, type);
+ char *actual_format = get_format_for_type (fki, type, conversion_char);
ASSERT_STREQ_AT (loc, expected_format, actual_format);
free (actual_format);
}
/* Selftests for get_format_for_type. */
-#define ASSERT_FORMAT_FOR_TYPE_STREQ(EXPECTED_FORMAT, TYPE) \
- assert_format_for_type_streq (SELFTEST_LOCATION, (fki), (EXPECTED_FORMAT), (TYPE))
+#define ASSERT_FORMAT_FOR_TYPE_STREQ(EXPECTED_FORMAT, TYPE, CONVERSION_CHAR) \
+ assert_format_for_type_streq (SELFTEST_LOCATION, (fki), (EXPECTED_FORMAT), \
+ (TYPE), (CONVERSION_CHAR))
/* Selftest for get_format_for_type for "printf"-style functions. */
const format_kind_info *fki = get_info ("gnu_printf");
ASSERT_NE (fki, NULL);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%f", double_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%Lf", long_double_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%d", integer_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%o", unsigned_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%ld", long_integer_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%lo", long_unsigned_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%lld", long_long_integer_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%llo", long_long_unsigned_type_node);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%s", build_pointer_type (char_type_node));
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", integer_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", integer_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", unsigned_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", unsigned_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", unsigned_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld", long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("li", long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lo", long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lld", long_long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lli", long_long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llo", long_long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llx", long_long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("s", build_pointer_type (char_type_node), 'i');
}
/* Selftest for get_format_for_type for "scanf"-style functions. */
{
const format_kind_info *fki = get_info ("gnu_scanf");
ASSERT_NE (fki, NULL);
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%d", build_pointer_type (integer_type_node));
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%u", build_pointer_type (unsigned_type_node));
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%ld",
- build_pointer_type (long_integer_type_node));
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%lu",
- build_pointer_type (long_unsigned_type_node));
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", build_pointer_type (integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("u", build_pointer_type (unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld",
+ build_pointer_type (long_integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lu",
+ build_pointer_type (long_unsigned_type_node), 'u');
ASSERT_FORMAT_FOR_TYPE_STREQ
- ("%lld", build_pointer_type (long_long_integer_type_node));
+ ("lld", build_pointer_type (long_long_integer_type_node), 'd');
ASSERT_FORMAT_FOR_TYPE_STREQ
- ("%llu", build_pointer_type (long_long_unsigned_type_node));
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%e", build_pointer_type (float_type_node));
- ASSERT_FORMAT_FOR_TYPE_STREQ ("%le", build_pointer_type (double_type_node));
+ ("llu", build_pointer_type (long_long_unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("e", build_pointer_type (float_type_node), 'e');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("le", build_pointer_type (double_type_node), 'e');
}
#undef ASSERT_FORMAT_FOR_TYPE_STREQ
--- /dev/null
+/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+
+#include "format.h"
+
+/* Various format tests, some containing type mismatches. Verify that for
+ the type mismatch cases that we offer "good" suggestions. Specifically,
+ any suggestions should preserve flags characters, field width and precision,
+ and, if possible, the conversion specifier character, whilst giving a
+ corrected length modifier appropriate to the argument type. */
+
+/* Tests of "x" without a length modifier, with various param types.
+ Suggestions should preserve the "x" for integer arguments. */
+
+void
+test_x (char *d,
+ int iexpr, unsigned int uiexpr,
+ long lexpr, unsigned long ulexpr,
+ long long llexpr, unsigned long long ullexpr,
+ float fexpr, double dexpr, long double ldexpr,
+ void *ptr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8x ", iexpr);
+ sprintf (d, " %-8x ", uiexpr);
+
+ sprintf (d, " %-8x ", lexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", lexpr);
+ ~~~^
+ %-8lx
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8x ", ulexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long unsigned int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", ulexpr);
+ ~~~^
+ %-8lx
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8x ", llexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long long int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", llexpr);
+ ~~~^
+ %-8llx
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8x ", ullexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long long unsigned int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", ullexpr);
+ ~~~^
+ %-8llx
+ { dg-end-multiline-output "" } */
+
+ /* Floating-point arguments. */
+
+ sprintf (d, " %-8x ", fexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'double'" } */
+/* TODO: ideally would also underline "fexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", fexpr);
+ ~~~^
+ %-8f
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8x ", dexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'double'" } */
+/* TODO: ideally would also underline "dexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", dexpr);
+ ~~~^
+ %-8f
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8x ", ldexpr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long double'" } */
+/* TODO: ideally would also underline "ldexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", ldexpr);
+ ~~~^
+ %-8Lf
+ { dg-end-multiline-output "" } */
+
+ /* Pointer. */
+ sprintf (d, " %-8x ", ptr); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'void \\*'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", ptr);
+ ~~~^
+ %-8p
+ { dg-end-multiline-output "" } */
+
+ /* Something unrecognized. */
+ struct s { int i; };
+ struct s s;
+ sprintf (d, " %-8x ", s); /* { dg-warning "20: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'struct s'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8x ", s);
+ ~~~^
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "x" with "l", with various param types.
+ Suggestions should preserve the "x" for integer arguments. */
+
+void
+test_lx (char *d,
+ int iexpr, unsigned int uiexpr,
+ long lexpr, unsigned long ulexpr,
+ long long llexpr, unsigned long long ullexpr,
+ float fexpr, double dexpr, long double ldexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8lx ", iexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", iexpr);
+ ~~~~^
+ %-8x
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lx ", uiexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'unsigned int'" } */
+/* TODO: ideally would also underline "uiexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", uiexpr);
+ ~~~~^
+ %-8x
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8lx ", lexpr);
+ sprintf (d, " %-8lx ", ulexpr);
+
+ sprintf (d, " %-8lx ", llexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'long long int'" } */
+/* TODO: ideally would also underline "llexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", llexpr);
+ ~~~~^
+ %-8llx
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lx ", ullexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'long long unsigned int'" } */
+/* TODO: ideally would also underline "ullexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", ullexpr);
+ ~~~~^
+ %-8llx
+ { dg-end-multiline-output "" } */
+
+ /* Floating-point arguments. */
+
+ sprintf (d, " %-8lx ", fexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'double'" } */
+/* TODO: ideally would also underline "fexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", fexpr);
+ ~~~~^
+ %-8f
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lx ", dexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'double'" } */
+/* TODO: ideally would also underline "dexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", dexpr);
+ ~~~~^
+ %-8f
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lx ", ldexpr); /* { dg-warning "21: format '%lx' expects argument of type 'long unsigned int', but argument 3 has type 'long double'" } */
+/* TODO: ideally would also underline "ldexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lx ", ldexpr);
+ ~~~~^
+ %-8Lf
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "o" without a length modifier, with various param types.
+ Suggestions should preserve the "o" for integer arguments. */
+
+void
+test_o (char *d,
+ int iexpr, unsigned int uiexpr,
+ long lexpr, unsigned long ulexpr,
+ long long llexpr, unsigned long long ullexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8o ", iexpr);
+ sprintf (d, " %-8o ", uiexpr);
+
+ sprintf (d, " %-8o ", lexpr); /* { dg-warning "20: format '%o' expects argument of type 'unsigned int', but argument 3 has type 'long int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8o ", lexpr);
+ ~~~^
+ %-8lo
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8o ", ulexpr); /* { dg-warning "20: format '%o' expects argument of type 'unsigned int', but argument 3 has type 'long unsigned int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8o ", ulexpr);
+ ~~~^
+ %-8lo
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8o ", llexpr); /* { dg-warning "20: format '%o' expects argument of type 'unsigned int', but argument 3 has type 'long long int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8o ", llexpr);
+ ~~~^
+ %-8llo
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8o ", ullexpr); /* { dg-warning "20: format '%o' expects argument of type 'unsigned int', but argument 3 has type 'long long unsigned int'" } */
+/* TODO: ideally would also underline "lexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8o ", ullexpr);
+ ~~~^
+ %-8llo
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "o" with "l", with various param types.
+ Suggestions should preserve the "o" for integer arguments. */
+
+void
+test_lo (char *d,
+ int iexpr, unsigned int uiexpr,
+ long lexpr, unsigned long ulexpr,
+ long long llexpr, unsigned long long ullexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8lo ", iexpr); /* { dg-warning "21: format '%lo' expects argument of type 'long unsigned int', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lo ", iexpr);
+ ~~~~^
+ %-8o
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lo ", uiexpr); /* { dg-warning "21: format '%lo' expects argument of type 'long unsigned int', but argument 3 has type 'unsigned int'" } */
+/* TODO: ideally would also underline "uiexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lo ", uiexpr);
+ ~~~~^
+ %-8o
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8lo ", lexpr);
+ sprintf (d, " %-8lo ", ulexpr);
+
+ sprintf (d, " %-8lo ", llexpr); /* { dg-warning "21: format '%lo' expects argument of type 'long unsigned int', but argument 3 has type 'long long int'" } */
+/* TODO: ideally would also underline "llexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lo ", llexpr);
+ ~~~~^
+ %-8llo
+ { dg-end-multiline-output "" } */
+ sprintf (d, " %-8lo ", ullexpr); /* { dg-warning "21: format '%lo' expects argument of type 'long unsigned int', but argument 3 has type 'long long unsigned int'" } */
+/* TODO: ideally would also underline "ullexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8lo ", ullexpr);
+ ~~~~^
+ %-8llo
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "e" without a length modifier, with various param types.
+ Suggestions should preserve the "e" for float arguments. */
+
+void
+test_e (char *d, int iexpr, float fexpr, double dexpr, long double ldexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8e ", iexpr); /* { dg-warning "20: format '%e' expects argument of type 'double', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8e ", iexpr);
+ ~~~^
+ %-8d
+ { dg-end-multiline-output "" } */
+
+ /* Floating-point arguments. */
+
+ sprintf (d, " %-8e ", fexpr);
+ sprintf (d, " %-8e ", dexpr);
+ sprintf (d, " %-8e ", ldexpr); /* { dg-warning "20: format '%e' expects argument of type 'double', but argument 3 has type 'long double'" } */
+/* TODO: ideally would also underline "ldexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8e ", ldexpr);
+ ~~~^
+ %-8Le
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "e" with "L", with various param types.
+ Suggestions should preserve the "e" for float arguments. */
+
+void
+test_Le (char *d, int iexpr, float fexpr, double dexpr, long double ldexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8Le ", iexpr); /* { dg-warning "21: format '%Le' expects argument of type 'long double', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8Le ", iexpr);
+ ~~~~^
+ %-8d
+ { dg-end-multiline-output "" } */
+
+ /* Floating-point arguments. */
+
+ sprintf (d, " %-8Le ", fexpr); /* { dg-warning "21: format '%Le' expects argument of type 'long double', but argument 3 has type 'double'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8Le ", fexpr);
+ ~~~~^
+ %-8e
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8Le ", dexpr); /* { dg-warning "21: format '%Le' expects argument of type 'long double', but argument 3 has type 'double'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8Le ", dexpr);
+ ~~~~^
+ %-8e
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8Le ", ldexpr);
+}
+
+/* Tests of "E" without a length modifier, with various param types.
+ Suggestions should preserve the "E" for floating-point arguments. */
+
+void
+test_E (char *d, int iexpr, float fexpr, double dexpr, long double ldexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8E ", iexpr); /* { dg-warning "20: format '%E' expects argument of type 'double', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8E ", iexpr);
+ ~~~^
+ %-8d
+ { dg-end-multiline-output "" } */
+
+ /* Floating-point arguments. */
+
+ sprintf (d, " %-8E ", fexpr);
+ sprintf (d, " %-8E ", dexpr);
+ sprintf (d, " %-8E ", ldexpr); /* { dg-warning "20: format '%E' expects argument of type 'double', but argument 3 has type 'long double'" } */
+/* TODO: ideally would also underline "ldexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8E ", ldexpr);
+ ~~~^
+ %-8LE
+ { dg-end-multiline-output "" } */
+}
+
+/* Tests of "E" with "L", with various param types.
+ Suggestions should preserve the "E" for floating-point arguments. */
+
+void
+test_LE (char *d, int iexpr, float fexpr, double dexpr, long double ldexpr)
+{
+ /* Integer arguments. */
+
+ sprintf (d, " %-8LE ", iexpr); /* { dg-warning "21: format '%LE' expects argument of type 'long double', but argument 3 has type 'int'" } */
+/* TODO: ideally would also underline "iexpr". */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8LE ", iexpr);
+ ~~~~^
+ %-8d
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8LE ", fexpr); /* { dg-warning "21: format '%LE' expects argument of type 'long double', but argument 3 has type 'double'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8LE ", fexpr);
+ ~~~~^
+ %-8E
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8LE ", dexpr); /* { dg-warning "21: format '%LE' expects argument of type 'long double', but argument 3 has type 'double'" } */
+/* { dg-begin-multiline-output "" }
+ sprintf (d, " %-8LE ", dexpr);
+ ~~~~^
+ %-8E
+ { dg-end-multiline-output "" } */
+
+ sprintf (d, " %-8LE ", ldexpr);
+}
+
+/* Test of a suggestion for a conversion specification containing
+ all features (flags, width, precision, length modifier), where
+ all the other arguments have mismatching types. */
+
+void
+test_everything (char *d, long lexpr)
+{
+ sprintf (d, "before %-+*.*lld after", lexpr, lexpr, lexpr); /* { dg-warning "26: field width specifier '\\*' expects argument of type 'int', but argument 3 has type 'long int'" } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (d, "before %-+*.*lld after", lexpr, lexpr, lexpr);
+ ~~~^~~~~~
+ { dg-end-multiline-output "" } */
+
+ /* { dg-warning "28: field precision specifier '\\.\\*' expects argument of type 'int', but argument 4 has type 'long int'" "" { target *-*-* } 392 } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (d, "before %-+*.*lld after", lexpr, lexpr, lexpr);
+ ~~~~~^~~~
+ { dg-end-multiline-output "" } */
+
+ /* { dg-warning "31: format '%lld' expects argument of type 'long long int', but argument 5 has type 'long int'" "" { target *-*-* } 392 } */
+ /* { dg-begin-multiline-output "" }
+ sprintf (d, "before %-+*.*lld after", lexpr, lexpr, lexpr);
+ ~~~~~~~~^
+ %-+*.*ld
+ { dg-end-multiline-output "" } */
+}