From: David Malcolm Date: Mon, 12 Sep 2016 16:01:02 +0000 (+0000) Subject: Fix suggestions for non-trivial Wformat type cases (PR c/72858) X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=d1463eb947cbc1033109e5142d1edcb21fe9b168;p=gcc.git Fix suggestions for non-trivial Wformat type cases (PR c/72858) gcc/c-family/ChangeLog: PR c/72858 * c-format.c (argument_parser::check_argument_type): Add params "type_start" and "conversion_char". Use the former to generate offset_to_type_start and pass it and conversion_char to check_format_types. (check_format_info_main): Capture the start of the type information as "type_start", and pass it an format_char to arg_parser.check_argument_type. (check_format_types): Provide an example in the leading comment. Add params "offset_to_type_start" and "conversion_char"; pass them to format_type_warning calls. (test_get_modifier_for_format_len): Likewise. (matching_type_p): New function. (get_format_for_type): Add param "conversion_char" and move implementation into... (get_format_for_type_1): ...new function, called twice. Use new function matching_type_p rather than checking for TYPE_CANONICAL equality. (get_corrected_substring): New function. (format_type_warning): Provide an example in the leading comment. Add params "offset_to_type_start" and "conversion_char". Replace call to get_format_for_type with call to get_corrected_substring and move rejection of hints for widths/precisions there. (assert_format_for_type_streq): Add param "conversion_char". (ASSERT_FORMAT_FOR_TYPE_STREQ): Add param CONVERSION_CHAR. (test_get_format_for_type_printf): Add conversion chars to the tests, adding coverage for various combinations of integer vs double conversions, and for preserving octal and hexadecimal conversions. (test_get_format_for_type_scanf): Add conversion chars to the tests. gcc/testsuite/ChangeLog: PR c/72858 * gcc.dg/format/diagnostic-ranges.c: Update expected suggestions to preserve conversion chars, and to preserve prefix information. * gcc.dg/format/pr72858.c: New test case. From-SVN: r240095 --- diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index 635e981f4a5..8ba99f29bf4 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,37 @@ +2016-09-12 David Malcolm + + PR c/72858 + * c-format.c (argument_parser::check_argument_type): Add params + "type_start" and "conversion_char". Use the former to generate + offset_to_type_start and pass it and conversion_char to + check_format_types. + (check_format_info_main): Capture the start of the type + information as "type_start", and pass it an format_char + to arg_parser.check_argument_type. + (check_format_types): Provide an example in the leading comment. + Add params "offset_to_type_start" and "conversion_char"; pass + them to format_type_warning calls. + (test_get_modifier_for_format_len): Likewise. + (matching_type_p): New function. + (get_format_for_type): Add param "conversion_char" and move + implementation into... + (get_format_for_type_1): ...new function, called twice. + Use new function matching_type_p rather than checking for + TYPE_CANONICAL equality. + (get_corrected_substring): New function. + (format_type_warning): Provide an example in the leading comment. + Add params "offset_to_type_start" and "conversion_char". Replace + call to get_format_for_type with call to get_corrected_substring + and move rejection of hints for widths/precisions there. + (assert_format_for_type_streq): Add param "conversion_char". + (ASSERT_FORMAT_FOR_TYPE_STREQ): Add param CONVERSION_CHAR. + (test_get_format_for_type_printf): Add conversion chars to the + tests, adding coverage for various combinations of integer + vs double conversions, and for preserving octal and hexadecimal + conversions. + (test_get_format_for_type_scanf): Add conversion chars to the + tests. + 2016-09-10 Tom de Vries PR C/71602 diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c index 0c173409520..bf39ee06e79 100644 --- a/gcc/c-family/c-format.c +++ b/gcc/c-family/c-format.c @@ -994,12 +994,16 @@ static const format_flag_spec *get_flag_spec (const format_flag_spec *, 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 @@ -1779,7 +1783,9 @@ class argument_parser 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; @@ -2552,7 +2558,9 @@ check_argument_type (const format_char_info *fci, 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; @@ -2658,7 +2666,10 @@ check_argument_type (const format_char_info *fci, 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; @@ -2737,6 +2748,13 @@ check_format_info_main (format_check_results *res, 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 (); @@ -2794,7 +2812,9 @@ check_format_info_main (format_check_results *res, suppressed, arg_num, params, alloc_flag, - format_start, fmt_param_loc)) + format_start, type_start, + fmt_param_loc, + format_char)) return; } @@ -2814,11 +2834,58 @@ check_format_info_main (format_check_results *res, } /* 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) { @@ -2845,7 +2912,8 @@ check_format_types (const substring_loc &fmt_loc, 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; } @@ -2930,7 +2998,8 @@ check_format_types (const substring_loc &fmt_loc, 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; } } @@ -2999,7 +3068,8 @@ check_format_types (const substring_loc &fmt_loc, 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); } } @@ -3064,16 +3134,47 @@ test_get_modifier_for_format_len () #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); @@ -3082,6 +3183,10 @@ get_format_for_type (const format_kind_info *fki, tree 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) @@ -3091,8 +3196,7 @@ get_format_for_type (const format_kind_info *fki, tree 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, @@ -3100,15 +3204,168 @@ get_format_for_type (const format_kind_info *fki, tree arg_type) 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. @@ -3118,13 +3375,36 @@ get_format_for_type (const format_kind_info *fki, tree arg_type) 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; @@ -3171,18 +3451,18 @@ format_type_warning (const substring_loc &whole_fmt_loc, 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]), @@ -3192,7 +3472,7 @@ format_type_warning (const substring_loc &whole_fmt_loc, 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 ? "%" : ""), @@ -3203,7 +3483,7 @@ format_type_warning (const substring_loc &whole_fmt_loc, 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]), @@ -3213,14 +3493,14 @@ format_type_warning (const substring_loc &whole_fmt_loc, 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); } @@ -3759,25 +4039,28 @@ get_info (const char *name) 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. */ @@ -3787,15 +4070,34 @@ test_get_format_for_type_printf () 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. */ @@ -3805,18 +4107,18 @@ test_get_format_for_type_scanf () { 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 diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 84f06a0881c..10936ada31f 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2016-09-12 David Malcolm + + PR c/72858 + * gcc.dg/format/diagnostic-ranges.c: Update expected suggestions + to preserve conversion chars, and to preserve prefix information. + * gcc.dg/format/pr72858.c: New test case. + 2016-09-11 Steven G. Kargl * gfortran.dg/pr77507.f90: Move to ... diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c index 63075c80609..e5e6ade6bbe 100644 --- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c +++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c @@ -30,7 +30,7 @@ void test_mismatching_types (const char *msg) /* { dg-begin-multiline-output "" } printf("hello %i", (long)0); ~^ - %ld + %li { dg-end-multiline-output "" } */ } @@ -88,7 +88,7 @@ void test_hex (const char *msg) /* { dg-begin-multiline-output "" } printf("hello \x25\x69", msg); ~~~~^~~~ - %s + \x25s { dg-end-multiline-output "" } */ } @@ -102,7 +102,7 @@ void test_oct (const char *msg) /* { dg-begin-multiline-output "" } printf("hello \045\151", msg); ~~~~^~~~ - %s + \045s { dg-end-multiline-output "" } */ } @@ -121,7 +121,7 @@ void test_multiple (const char *msg) /* { dg-begin-multiline-output "" } printf("prefix" "\x25" "\151" "suffix", ~~~~~~~~^~~~ - %s + \x25" "s { dg-end-multiline-output "" } */ } @@ -267,7 +267,7 @@ void test_non_contiguous_strings (void) /* { dg-begin-multiline-output "" } __builtin_printf(" %" "d ", 0.5); ~~~~^ - %f + %" "f { dg-end-multiline-output "" } */ } diff --git a/gcc/testsuite/gcc.dg/format/pr72858.c b/gcc/testsuite/gcc.dg/format/pr72858.c new file mode 100644 index 00000000000..d8d0499b778 --- /dev/null +++ b/gcc/testsuite/gcc.dg/format/pr72858.c @@ -0,0 +1,410 @@ +/* { 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 "" } */ +}