Mercurial > hg > CbC > CbC_gcc
diff gcc/c-family/c-format.c @ 111:04ced10e8804
gcc 7
author | kono |
---|---|
date | Fri, 27 Oct 2017 22:46:09 +0900 |
parents | 561a7518be6b |
children | 84e7813d76e9 |
line wrap: on
line diff
--- a/gcc/c-family/c-format.c Sun Aug 21 07:07:55 2011 +0900 +++ b/gcc/c-family/c-format.c Fri Oct 27 22:46:09 2017 +0900 @@ -1,7 +1,5 @@ /* Check calls to formatted I/O functions (-Wformat). - Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, - 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010 - Free Software Foundation, Inc. + Copyright (C) 1992-2017 Free Software Foundation, Inc. This file is part of GCC. @@ -23,38 +21,20 @@ #include "system.h" #include "coretypes.h" #include "tm.h" -#include "tree.h" -#include "flags.h" +#include "c-target.h" #include "c-common.h" +#include "alloc-pool.h" +#include "stringpool.h" #include "c-objc.h" #include "intl.h" -#include "diagnostic-core.h" #include "langhooks.h" #include "c-format.h" -#include "alloc-pool.h" -#include "target.h" - -/* Set format warning options according to a -Wformat=n option. */ - -void -set_Wformat (int setting) -{ - warn_format = setting; - warn_format_extra_args = setting; - warn_format_zero_length = setting; - warn_format_contains_nul = setting; - if (setting != 1) - { - warn_format_nonliteral = setting; - warn_format_security = setting; - warn_format_y2k = setting; - } - /* Make sure not to disable -Wnonnull if -Wformat=0 is specified. */ - if (setting) - warn_nonnull = setting; -} - - +#include "diagnostic.h" +#include "substring-locations.h" +#include "selftest.h" +#include "builtins.h" +#include "attribs.h" + /* Handle attributes associated with format checking. */ /* This must be in the same order as format_types, except for @@ -67,12 +47,17 @@ gcc_objc_string_format_type, format_type_error = -1}; -typedef struct function_format_info +struct function_format_info { int format_type; /* type of format (printf, scanf, etc.) */ unsigned HOST_WIDE_INT format_num; /* number of format argument */ unsigned HOST_WIDE_INT first_arg_num; /* number of first arg (zero for varargs) */ -} function_format_info; +}; + +/* Initialized in init_dynamic_diag_info. */ +static GTY(()) tree local_tree_type_node; +static GTY(()) tree local_gcall_ptr_node; +static GTY(()) tree locus; static bool decode_format_attr (tree, function_format_info *, int); static int decode_format_type (const char *); @@ -84,12 +69,41 @@ static bool get_constant (tree expr, unsigned HOST_WIDE_INT *value, int validated_p); static const char *convert_format_name_to_system_name (const char *attr_name); -static bool cmp_attribs (const char *tattr_name, const char *attr_name); static int first_target_format_type; static const char *format_name (int format_num); static int format_flags (int format_num); +/* 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. */ + +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, + char_idx); + bool warned = format_warning_va (fmt_loc, UNKNOWN_LOCATION, NULL, opt, + gmsgid, &ap); + va_end (ap); + + return warned; +} + /* Check that we have a pointer to a string suitable for use as a format. The default is to check for a char type. For objective-c dialects, this is extended to include references to string @@ -120,7 +134,6 @@ tree type = *node; tree format_num_expr = TREE_VALUE (args); unsigned HOST_WIDE_INT format_num = 0; - tree argument; if (!get_constant (format_num_expr, &format_num, 0)) { @@ -129,12 +142,11 @@ return NULL_TREE; } - argument = TYPE_ARG_TYPES (type); - if (argument) + if (prototype_p (type)) { /* The format arg can be any string reference valid for the language and - target. We cannot be more specific in this case. */ - if (!check_format_string (argument, format_num, flags, no_add_attrs, -1)) + target. We cannot be more specific in this case. */ + if (!check_format_string (type, format_num, flags, no_add_attrs, -1)) return NULL_TREE; } @@ -154,23 +166,24 @@ error). When we know the specific reference type expected, this is also checked. */ static bool -check_format_string (tree argument, unsigned HOST_WIDE_INT format_num, +check_format_string (tree fntype, unsigned HOST_WIDE_INT format_num, int flags, bool *no_add_attrs, int expected_format_type) { unsigned HOST_WIDE_INT i; bool is_objc_sref, is_target_sref, is_char_ref; tree ref; int fmt_flags; - - for (i = 1; i != format_num; i++) + function_args_iterator iter; + + i = 1; + FOREACH_FUNCTION_ARGS (fntype, ref, iter) { - if (argument == 0) + if (i == format_num) break; - argument = TREE_CHAIN (argument); + i++; } - if (!argument - || !(ref = TREE_VALUE (argument)) + if (!ref || !valid_stringptr_type_p (ref)) { if (!(flags & (int) ATTR_FLAG_BUILT_IN)) @@ -200,7 +213,7 @@ { /* We expected a char but found an extended string type. */ if (is_objc_sref) - error ("found a %<%s%> reference but the format argument should" + error ("found a %qs reference but the format argument should" " be a string", format_name (gcc_objc_string_format_type)); else error ("found a %qT but the format argument should be a string", @@ -213,7 +226,7 @@ /* We expect a string object type as the format arg. */ if (is_char_ref) { - error ("format argument should be a %<%s%> reference but" + error ("format argument should be a %qs reference but" " a string was found", format_name (expected_format_type)); *no_add_attrs = true; return false; @@ -235,7 +248,7 @@ return true; else { - error ("format argument should be a %<%s%> reference", + error ("format argument should be a %qs reference", format_name (expected_format_type)); *no_add_attrs = true; return false; @@ -250,7 +263,7 @@ static bool get_constant (tree expr, unsigned HOST_WIDE_INT *value, int validated_p) { - if (TREE_CODE (expr) != INTEGER_CST || TREE_INT_CST_HIGH (expr) != 0) + if (!tree_fits_uhwi_p (expr)) { gcc_assert (!validated_p); return false; @@ -294,7 +307,7 @@ && info->format_type == gcc_objc_string_format_type) { gcc_assert (!validated_p); - warning (OPT_Wformat, "%qE is only allowed in Objective-C dialects", + warning (OPT_Wformat_, "%qE is only allowed in Objective-C dialects", format_type_id); info->format_type = format_type_error; return false; @@ -303,7 +316,7 @@ if (info->format_type == format_type_error) { gcc_assert (!validated_p); - warning (OPT_Wformat, "%qE is an unrecognized format function type", + warning (OPT_Wformat_, "%qE is an unrecognized format function type", format_type_id); return false; } @@ -335,7 +348,7 @@ /* The C standard version C++ is treated as equivalent to or inheriting from, for the purpose of format features supported. */ -#define CPLUSPLUS_STD_VER STD_C94 +#define CPLUSPLUS_STD_VER (cxx_dialect < cxx11 ? STD_C94 : STD_C99) /* The C standard version we are checking formats against when pedantic. */ #define C_STD_VER ((int) (c_dialect_cxx () \ ? CPLUSPLUS_STD_VER \ @@ -346,7 +359,8 @@ pedantic. FEATURE_VER is the version in which the feature warned out appeared, which is higher than C_STD_VER. */ #define C_STD_NAME(FEATURE_VER) (c_dialect_cxx () \ - ? "ISO C++" \ + ? (cxx_dialect < cxx11 ? "ISO C++98" \ + : "ISO C++11") \ : ((FEATURE_VER) == STD_EXT \ ? "ISO C" \ : "ISO C90")) @@ -372,7 +386,7 @@ /* Structure describing details of a type expected in format checking, and the type to check against it. */ -typedef struct format_wanted_type +struct format_wanted_type { /* The type wanted. */ tree wanted_type; @@ -402,9 +416,12 @@ tree param; /* The argument number of that parameter. */ int arg_num; + /* The offset location of this argument with respect to the format + string location. */ + unsigned int offset_loc; /* The next type to check for this format conversion, or NULL if none. */ struct format_wanted_type *next; -} format_wanted_type; +}; /* Convenience macro for format_length_info meaning unused. */ #define NO_FMT NULL, FMT_LEN_none, STD_C89 @@ -481,17 +498,17 @@ static const format_flag_spec printf_flag_specs[] = { - { ' ', 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 }, - { '+', 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, - { '#', 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, - { '0', 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 }, - { '-', 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 }, - { '\'', 0, 0, N_("''' flag"), N_("the ''' printf flag"), STD_EXT }, - { 'I', 0, 0, N_("'I' flag"), N_("the 'I' printf flag"), STD_EXT }, - { 'w', 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 }, - { 'p', 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, - { 'L', 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { ' ', 0, 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 }, + { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, + { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, + { '0', 0, 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 }, + { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 }, + { '\'', 0, 0, 0, N_("''' flag"), N_("the ''' printf flag"), STD_EXT }, + { 'I', 0, 0, 0, N_("'I' flag"), N_("the 'I' printf flag"), STD_EXT }, + { 'w', 0, 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 }, + { 'p', 0, 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, + { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; @@ -505,15 +522,15 @@ static const format_flag_spec asm_fprintf_flag_specs[] = { - { ' ', 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 }, - { '+', 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, - { '#', 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, - { '0', 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 }, - { '-', 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 }, - { 'w', 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 }, - { 'p', 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, - { 'L', 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { ' ', 0, 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 }, + { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, + { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, + { '0', 0, 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 }, + { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 }, + { 'w', 0, 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 }, + { 'p', 0, 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, + { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; static const format_flag_pair asm_fprintf_flag_pairs[] = @@ -532,36 +549,33 @@ #define gcc_tdiag_flag_pairs gcc_diag_flag_pairs #define gcc_cdiag_flag_pairs gcc_diag_flag_pairs #define gcc_cxxdiag_flag_pairs gcc_diag_flag_pairs - -static const format_flag_pair gcc_gfc_flag_pairs[] = -{ - { 0, 0, 0, 0 } -}; +#define gcc_gfc_flag_pairs gcc_diag_flag_pairs static const format_flag_spec gcc_diag_flag_specs[] = { - { '+', 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, - { '#', 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, - { 'q', 0, 0, N_("'q' flag"), N_("the 'q' diagnostic flag"), STD_C89 }, - { 'p', 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, - { 'L', 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 }, + { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 }, + { 'q', 0, 0, 1, N_("'q' flag"), N_("the 'q' diagnostic flag"), STD_C89 }, + { 'p', 0, 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 }, + { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; #define gcc_tdiag_flag_specs gcc_diag_flag_specs #define gcc_cdiag_flag_specs gcc_diag_flag_specs #define gcc_cxxdiag_flag_specs gcc_diag_flag_specs +#define gcc_gfc_flag_specs gcc_diag_flag_specs static const format_flag_spec scanf_flag_specs[] = { - { '*', 0, 0, N_("assignment suppression"), N_("the assignment suppression scanf feature"), STD_C89 }, - { 'a', 0, 0, N_("'a' flag"), N_("the 'a' scanf flag"), STD_EXT }, - { 'm', 0, 0, N_("'m' flag"), N_("the 'm' scanf flag"), STD_EXT }, - { 'w', 0, 0, N_("field width"), N_("field width in scanf format"), STD_C89 }, - { 'L', 0, 0, N_("length modifier"), N_("length modifier in scanf format"), STD_C89 }, - { '\'', 0, 0, N_("''' flag"), N_("the ''' scanf flag"), STD_EXT }, - { 'I', 0, 0, N_("'I' flag"), N_("the 'I' scanf flag"), STD_EXT }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { '*', 0, 0, 0, N_("assignment suppression"), N_("the assignment suppression scanf feature"), STD_C89 }, + { 'a', 0, 0, 0, N_("'a' flag"), N_("the 'a' scanf flag"), STD_EXT }, + { 'm', 0, 0, 0, N_("'m' flag"), N_("the 'm' scanf flag"), STD_EXT }, + { 'w', 0, 0, 0, N_("field width"), N_("field width in scanf format"), STD_C89 }, + { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in scanf format"), STD_C89 }, + { '\'', 0, 0, 0, N_("''' flag"), N_("the ''' scanf flag"), STD_EXT }, + { 'I', 0, 0, 0, N_("'I' flag"), N_("the 'I' scanf flag"), STD_EXT }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; @@ -575,16 +589,16 @@ static const format_flag_spec strftime_flag_specs[] = { - { '_', 0, 0, N_("'_' flag"), N_("the '_' strftime flag"), STD_EXT }, - { '-', 0, 0, N_("'-' flag"), N_("the '-' strftime flag"), STD_EXT }, - { '0', 0, 0, N_("'0' flag"), N_("the '0' strftime flag"), STD_EXT }, - { '^', 0, 0, N_("'^' flag"), N_("the '^' strftime flag"), STD_EXT }, - { '#', 0, 0, N_("'#' flag"), N_("the '#' strftime flag"), STD_EXT }, - { 'w', 0, 0, N_("field width"), N_("field width in strftime format"), STD_EXT }, - { 'E', 0, 0, N_("'E' modifier"), N_("the 'E' strftime modifier"), STD_C99 }, - { 'O', 0, 0, N_("'O' modifier"), N_("the 'O' strftime modifier"), STD_C99 }, - { 'O', 'o', 0, NULL, N_("the 'O' modifier"), STD_EXT }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { '_', 0, 0, 0, N_("'_' flag"), N_("the '_' strftime flag"), STD_EXT }, + { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' strftime flag"), STD_EXT }, + { '0', 0, 0, 0, N_("'0' flag"), N_("the '0' strftime flag"), STD_EXT }, + { '^', 0, 0, 0, N_("'^' flag"), N_("the '^' strftime flag"), STD_EXT }, + { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' strftime flag"), STD_EXT }, + { 'w', 0, 0, 0, N_("field width"), N_("field width in strftime format"), STD_EXT }, + { 'E', 0, 0, 0, N_("'E' modifier"), N_("the 'E' strftime modifier"), STD_C99 }, + { 'O', 0, 0, 0, N_("'O' modifier"), N_("the 'O' strftime modifier"), STD_C99 }, + { 'O', 'o', 0, 0, NULL, N_("the 'O' modifier"), STD_EXT }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; @@ -601,17 +615,17 @@ static const format_flag_spec strfmon_flag_specs[] = { - { '=', 0, 1, N_("fill character"), N_("fill character in strfmon format"), STD_C89 }, - { '^', 0, 0, N_("'^' flag"), N_("the '^' strfmon flag"), STD_C89 }, - { '+', 0, 0, N_("'+' flag"), N_("the '+' strfmon flag"), STD_C89 }, - { '(', 0, 0, N_("'(' flag"), N_("the '(' strfmon flag"), STD_C89 }, - { '!', 0, 0, N_("'!' flag"), N_("the '!' strfmon flag"), STD_C89 }, - { '-', 0, 0, N_("'-' flag"), N_("the '-' strfmon flag"), STD_C89 }, - { 'w', 0, 0, N_("field width"), N_("field width in strfmon format"), STD_C89 }, - { '#', 0, 0, N_("left precision"), N_("left precision in strfmon format"), STD_C89 }, - { 'p', 0, 0, N_("right precision"), N_("right precision in strfmon format"), STD_C89 }, - { 'L', 0, 0, N_("length modifier"), N_("length modifier in strfmon format"), STD_C89 }, - { 0, 0, 0, NULL, NULL, STD_C89 } + { '=', 0, 1, 0, N_("fill character"), N_("fill character in strfmon format"), STD_C89 }, + { '^', 0, 0, 0, N_("'^' flag"), N_("the '^' strfmon flag"), STD_C89 }, + { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' strfmon flag"), STD_C89 }, + { '(', 0, 0, 0, N_("'(' flag"), N_("the '(' strfmon flag"), STD_C89 }, + { '!', 0, 0, 0, N_("'!' flag"), N_("the '!' strfmon flag"), STD_C89 }, + { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' strfmon flag"), STD_C89 }, + { 'w', 0, 0, 0, N_("field width"), N_("field width in strfmon format"), STD_C89 }, + { '#', 0, 0, 0, N_("left precision"), N_("left precision in strfmon format"), STD_C89 }, + { 'p', 0, 0, 0, N_("right precision"), N_("right precision in strfmon format"), STD_C89 }, + { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in strfmon format"), STD_C89 }, + { 0, 0, 0, 0, NULL, NULL, STD_C89 } }; static const format_flag_pair strfmon_flag_pairs[] = @@ -660,6 +674,7 @@ { "L", 0, STD_C89, NOARGUMENTS, "", "", NULL }, { "U", 0, STD_C89, NOARGUMENTS, "", "", NULL }, { "r", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", NULL }, + { "z", 0, STD_C89, NOARGUMENTS, "", "", NULL }, { "@", 0, STD_C89, NOARGUMENTS, "", "", NULL }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; @@ -676,10 +691,16 @@ /* Custom conversion specifiers. */ - /* These will require a "tree" at runtime. */ - { "K", 0, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL }, - - { "<>'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + /* G requires a "gcall*" argument at runtime. */ + { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + /* K requires a "tree" argument at runtime. */ + { "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + + { "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "//cR", NULL }, + { "<", 0, STD_C89, NOARGUMENTS, "", "<", NULL }, + { ">", 0, STD_C89, NOARGUMENTS, "", ">", NULL }, + { "'" , 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "R", 0, STD_C89, NOARGUMENTS, "", "\\", NULL }, { "m", 0, STD_C89, NOARGUMENTS, "q", "", NULL }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; @@ -697,12 +718,22 @@ /* Custom conversion specifiers. */ /* These will require a "tree" at runtime. */ - { "DFKTEV", 0, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL }, - - { "v", 0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL }, - - { "<>'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "DFTV", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "'", NULL }, + { "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL }, + { "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + + /* G requires a "gcall*" argument at runtime. */ + { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + + { "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL }, + + { "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL }, + { "<", 0, STD_C89, NOARGUMENTS, "", "<", NULL }, + { ">", 0, STD_C89, NOARGUMENTS, "", ">", NULL }, + { "'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "R", 0, STD_C89, NOARGUMENTS, "", "\\", NULL }, { "m", 0, STD_C89, NOARGUMENTS, "q", "", NULL }, + { "Z", 1, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", &gcc_tdiag_char_table[0] }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; @@ -719,12 +750,22 @@ /* Custom conversion specifiers. */ /* These will require a "tree" at runtime. */ - { "DEFKTV", 0, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL }, - - { "v", 0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL }, - - { "<>'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "DFTV", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "'", NULL }, + { "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL }, + { "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + + /* G requires a "gcall*" argument at runtime. */ + { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + + { "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL }, + + { "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL }, + { "<", 0, STD_C89, NOARGUMENTS, "", "<", NULL }, + { ">", 0, STD_C89, NOARGUMENTS, "", ">", NULL }, + { "'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "R", 0, STD_C89, NOARGUMENTS, "", "\\", NULL }, { "m", 0, STD_C89, NOARGUMENTS, "q", "", NULL }, + { "Z", 1, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", &gcc_tdiag_char_table[0] }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; @@ -741,25 +782,34 @@ /* Custom conversion specifiers. */ /* These will require a "tree" at runtime. */ - { "ADEFKTV",0,STD_C89,{ T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+#", "", NULL }, - + { "ADFHISTVX",1,STD_C89,{ T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+#", "'", NULL }, + { "E", 1,STD_C89,{ T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+#", "", NULL }, + { "K", 1, STD_C89,{ T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, { "v", 0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL }, + /* G requires a "gcall*" argument at runtime. */ + { "G", 1, STD_C89,{ T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, + /* These accept either an 'int' or an 'enum tree_code' (which is handled as an 'int'.) */ { "CLOPQ",0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL }, - { "<>'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL }, + { "<", 0, STD_C89, NOARGUMENTS, "", "<", NULL }, + { ">", 0, STD_C89, NOARGUMENTS, "", ">", NULL }, + { "'", 0, STD_C89, NOARGUMENTS, "", "", NULL }, + { "R", 0, STD_C89, NOARGUMENTS, "", "\\", NULL }, { "m", 0, STD_C89, NOARGUMENTS, "q", "", NULL }, + { "Z", 1, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", &gcc_tdiag_char_table[0] }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; static const format_char_info gcc_gfc_char_table[] = { /* C89 conversion specifiers. */ - { "di", 0, STD_C89, { T89_I, BADLEN, BADLEN, T89_L, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", NULL }, - { "u", 0, STD_C89, { T89_UI, BADLEN, BADLEN, T89_UL, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", NULL }, - { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "", NULL }, - { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "cR", NULL }, + { "di", 0, STD_C89, { T89_I, BADLEN, BADLEN, T89_L, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL }, + { "u", 0, STD_C89, { T89_UI, BADLEN, BADLEN, T89_UL, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL }, + { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL }, + { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "cR", NULL }, /* gfc conversion specifiers. */ @@ -768,6 +818,8 @@ /* This will require a "locus" at runtime. */ { "L", 0, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "R", NULL }, + /* These will require nothing. */ + { "<>",0, STD_C89, NOARGUMENTS, "", "", NULL }, { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL } }; @@ -864,8 +916,8 @@ 0, 0, 'p', 0, 'L', 0, NULL, &integer_type_node }, - { "gcc_gfc", gcc_gfc_length_specs, gcc_gfc_char_table, "", NULL, - NULL, gcc_gfc_flag_pairs, + { "gcc_gfc", gcc_gfc_length_specs, gcc_gfc_char_table, "q+#", NULL, + gcc_gfc_flag_specs, gcc_gfc_flag_pairs, FMT_FLAG_ARG_CONVERT, 0, 0, 0, 0, 0, 0, NULL, NULL @@ -906,7 +958,7 @@ /* Structure detailing the results of checking a format function call where the format expression may be a conditional expression with many leaves resulting from nested conditional expressions. */ -typedef struct +struct format_check_results { /* Number of leaves of the format argument that could not be checked as they were not string literals. */ @@ -914,6 +966,7 @@ /* Number of leaves of the format argument that were null pointers or string literals, but had extra format arguments. */ int number_extra_args; + location_t extra_arg_loc; /* Number of leaves of the format argument that were null pointers or string literals, but had extra format arguments and used $ operand numbers. */ @@ -928,14 +981,17 @@ int number_unterminated; /* Number of leaves of the format argument that were not counted above. */ int number_other; -} format_check_results; - -typedef struct + /* Location of the format string. */ + location_t format_string_loc; +}; + +struct format_check_context { format_check_results *res; function_format_info *info; tree params; -} format_check_context; + vec<location_t> *arglocs; +}; /* Return the format name (as specified in the original table) for the format type indicated by format_num. */ @@ -957,12 +1013,16 @@ gcc_unreachable (); } -static void check_format_info (function_format_info *, tree); +static void check_format_info (function_format_info *, tree, + vec<location_t> *); 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, - unsigned HOST_WIDE_INT, alloc_pool); + function_format_info *, const char *, + location_t, tree, + int, tree, + unsigned HOST_WIDE_INT, + object_allocator<format_wanted_type> &, + vec<location_t> *); static void init_dollar_format_checking (int, tree); static int maybe_read_dollar_number (const char **, int, @@ -973,8 +1033,19 @@ static const format_flag_spec *get_flag_spec (const format_flag_spec *, int, const char *); -static void check_format_types (format_wanted_type *); -static void format_type_warning (format_wanted_type *, tree, tree); +static void check_format_types (const substring_loc &fmt_loc, + format_wanted_type *, + const format_kind_info *fki, + int offset_to_type_start, + char conversion_char, + vec<location_t> *arglocs); +static void format_type_warning (const substring_loc &fmt_loc, + location_t param_loc, + format_wanted_type *, tree, + tree, + 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 @@ -1005,12 +1076,13 @@ /* Check the argument list of a call to printf, scanf, etc. ATTRS are the attributes on the function type. There are NARGS argument values in the array ARGARRAY. - Also, if -Wmissing-format-attribute, + Also, if -Wsuggest-attribute=format, warn for calls to vprintf or vscanf in functions with no such format attribute themselves. */ void -check_function_format (tree attrs, int nargs, tree *argarray) +check_function_format (tree attrs, int nargs, tree *argarray, + vec<location_t> *arglocs) { tree a; @@ -1021,7 +1093,7 @@ { /* Yup; check it. */ function_format_info info; - decode_format_attr (TREE_VALUE (a), &info, 1); + decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true); if (warn_format) { /* FIXME: Rewrite all the internal functions in this file @@ -1031,11 +1103,19 @@ int i; for (i = nargs - 1; i >= 0; i--) params = tree_cons (NULL_TREE, argarray[i], params); - check_format_info (&info, params); + check_format_info (&info, params, arglocs); } - if (warn_missing_format_attribute && info.first_arg_num == 0 + + /* Attempt to detect whether the current function might benefit + from the format attribute if the called function is decorated + with it. Avoid using calls with string literal formats for + guidance since those are unlikely to be viable candidates. */ + if (warn_suggest_attribute_format && info.first_arg_num == 0 && (format_types[info.format_type].flags - & (int) FMT_FLAG_ARG_CONVERT)) + & (int) FMT_FLAG_ARG_CONVERT) + /* c_strlen will fail for a function parameter but succeed + for a literal or constant array. */ + && !c_strlen (argarray[info.format_num - 1], 1)) { tree c; for (c = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); @@ -1063,8 +1143,9 @@ break; } if (args != 0) - warning (OPT_Wmissing_format_attribute, "function might " - "be possible candidate for %qs format attribute", + warning (OPT_Wsuggest_attribute_format, "function %qD " + "might be a candidate for %qs format attribute", + current_function_decl, format_types[info.format_type].name); } } @@ -1107,10 +1188,8 @@ } if (dollar_arguments_alloc < dollar_arguments_count) { - if (dollar_arguments_used) - free (dollar_arguments_used); - if (dollar_arguments_pointer_p) - free (dollar_arguments_pointer_p); + free (dollar_arguments_used); + free (dollar_arguments_pointer_p); dollar_arguments_alloc = dollar_arguments_count; dollar_arguments_used = XNEWVEC (char, dollar_arguments_alloc); dollar_arguments_pointer_p = XNEWVEC (char, dollar_arguments_alloc); @@ -1155,7 +1234,7 @@ { if (dollar_needed) { - warning (OPT_Wformat, "missing $ operand number in format"); + warning (OPT_Wformat_, "missing $ operand number in format"); return -1; } else @@ -1176,7 +1255,7 @@ { if (dollar_needed) { - warning (OPT_Wformat, "missing $ operand number in format"); + warning (OPT_Wformat_, "missing $ operand number in format"); return -1; } else @@ -1185,14 +1264,14 @@ *format = fcp + 1; if (pedantic && !dollar_format_warned) { - warning (OPT_Wformat, "%s does not support %%n$ operand number formats", + warning (OPT_Wformat_, "%s does not support %%n$ operand number formats", C_STD_NAME (STD_EXT)); dollar_format_warned = 1; } if (overflow_flag || argnum == 0 || (dollar_first_arg_num && argnum > dollar_arguments_count)) { - warning (OPT_Wformat, "operand number out of range in format"); + warning (OPT_Wformat_, "operand number out of range in format"); return -1; } if (argnum > dollar_max_arg_used) @@ -1215,7 +1294,7 @@ && dollar_arguments_used[argnum - 1] == 1) { dollar_arguments_used[argnum - 1] = 2; - warning (OPT_Wformat, "format argument %d used more than once in %s format", + warning (OPT_Wformat_, "format argument %d used more than once in %s format", argnum, fki->name); } else @@ -1247,7 +1326,7 @@ format++; if (*format == '$') { - warning (OPT_Wformat, "$ operand number used after format without operand number"); + warning (OPT_Wformat_, "$ operand number used after format without operand number"); return true; } return false; @@ -1277,9 +1356,9 @@ || dollar_arguments_pointer_p[i])) found_pointer_gap = true; else - warning (OPT_Wformat, - "format argument %d unused before used argument %d in $-style format", - i + 1, dollar_max_arg_used); + warning_at (res->format_string_loc, OPT_Wformat_, + "format argument %d unused before used argument %d in $-style format", + i + 1, dollar_max_arg_used); } } if (found_pointer_gap @@ -1327,7 +1406,8 @@ PARAMS is the list of argument values. */ static void -check_format_info (function_format_info *info, tree params) +check_format_info (function_format_info *info, tree params, + vec<location_t> *arglocs) { format_check_context format_ctx; unsigned HOST_WIDE_INT arg_num; @@ -1350,19 +1430,24 @@ res.number_non_literal = 0; res.number_extra_args = 0; + res.extra_arg_loc = UNKNOWN_LOCATION; res.number_dollar_extra_args = 0; res.number_wide = 0; res.number_empty = 0; res.number_unterminated = 0; res.number_other = 0; + res.format_string_loc = input_location; format_ctx.res = &res; format_ctx.info = info; format_ctx.params = params; + format_ctx.arglocs = arglocs; check_function_arguments_recurse (check_format_arg, &format_ctx, format_tree, arg_num); + location_t loc = format_ctx.res->format_string_loc; + if (res.number_non_literal > 0) { /* Functions taking a va_list normally pass a non-literal format @@ -1372,8 +1457,8 @@ { /* For strftime-like formats, warn for not checking the format string; but there are no arguments to check. */ - warning (OPT_Wformat_nonliteral, - "format not a string literal, format string not checked"); + warning_at (loc, OPT_Wformat_nonliteral, + "format not a string literal, format string not checked"); } else if (info->first_arg_num != 0) { @@ -1387,14 +1472,14 @@ ++arg_num; } if (params == 0 && warn_format_security) - warning (OPT_Wformat_security, - "format not a string literal and no format arguments"); + warning_at (loc, OPT_Wformat_security, + "format not a string literal and no format arguments"); else if (params == 0 && warn_format_nonliteral) - warning (OPT_Wformat_nonliteral, - "format not a string literal and no format arguments"); + warning_at (loc, OPT_Wformat_nonliteral, + "format not a string literal and no format arguments"); else - warning (OPT_Wformat_nonliteral, - "format not a string literal, argument types not checked"); + warning_at (loc, OPT_Wformat_nonliteral, + "format not a string literal, argument types not checked"); } } @@ -1407,20 +1492,25 @@ case of extra format arguments. */ if (res.number_extra_args > 0 && res.number_non_literal == 0 && res.number_other == 0) - warning (OPT_Wformat_extra_args, "too many arguments for format"); + { + if (res.extra_arg_loc == UNKNOWN_LOCATION) + res.extra_arg_loc = loc; + warning_at (res.extra_arg_loc, OPT_Wformat_extra_args, + "too many arguments for format"); + } if (res.number_dollar_extra_args > 0 && res.number_non_literal == 0 && res.number_other == 0) - warning (OPT_Wformat_extra_args, "unused arguments in $-style format"); + warning_at (loc, OPT_Wformat_extra_args, "unused arguments in $-style format"); if (res.number_empty > 0 && res.number_non_literal == 0 && res.number_other == 0) - warning (OPT_Wformat_zero_length, "zero-length %s format string", + warning_at (loc, OPT_Wformat_zero_length, "zero-length %s format string", format_types[info->format_type].name); if (res.number_wide > 0) - warning (OPT_Wformat, "format is a wide character string"); + warning_at (loc, OPT_Wformat_, "format is a wide character string"); if (res.number_unterminated > 0) - warning (OPT_Wformat, "unterminated format string"); + warning_at (loc, OPT_Wformat_, "unterminated format string"); } /* Callback from check_function_arguments_recurse to check a @@ -1436,13 +1526,22 @@ format_check_results *res = format_ctx->res; function_format_info *info = format_ctx->info; tree params = format_ctx->params; + vec<location_t> *arglocs = format_ctx->arglocs; int format_length; HOST_WIDE_INT offset; const char *format_chars; tree array_size = 0; tree array_init; - alloc_pool fwt_pool; + + 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. */ + format_tree = decl_constant_value (format_tree); + STRIP_NOPS (format_tree); + } if (integer_zerop (format_tree)) { @@ -1458,9 +1557,13 @@ if (params == 0) res->number_other++; - else - res->number_extra_args++; - + else + { + if (res->number_extra_args == 0) + res->extra_arg_loc = EXPR_LOC_OR_LOC (TREE_VALUE (params), + input_location); + res->number_extra_args++; + } return; } @@ -1480,25 +1583,27 @@ res->number_non_literal++; return; } - if (!host_integerp (arg1, 0) - || (offset = tree_low_cst (arg1, 0)) < 0) + /* POINTER_PLUS_EXPR offsets are to be interpreted signed. */ + if (!cst_and_fits_in_hwi (arg1)) { res->number_non_literal++; return; } + offset = int_cst_value (arg1); } if (TREE_CODE (format_tree) != ADDR_EXPR) { res->number_non_literal++; return; } + res->format_string_loc = EXPR_LOC_OR_LOC (format_tree, input_location); format_tree = TREE_OPERAND (format_tree, 0); if (format_types[info->format_type].flags & (int) FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL) { bool objc_str = (info->format_type == gcc_objc_string_format_type); /* We cannot examine this string here - but we can check that it is - a valid type. */ + a valid type. */ if (TREE_CODE (format_tree) != CONST_DECL || !((objc_str && objc_string_ref_type_p (TREE_TYPE (format_tree))) || (*targetcm.string_object_ref_type_p) @@ -1516,9 +1621,9 @@ ++arg_num; } /* So, we have a valid literal string object and one or more params. - We need to use an external helper to parse the string into format - info. For Objective-C variants we provide the resource within the - objc tree, for target variants, via a hook. */ + We need to use an external helper to parse the string into format + info. For Objective-C variants we provide the resource within the + objc tree, for target variants, via a hook. */ if (objc_str) objc_check_format_arg (format_tree, params); else if (targetcm.check_string_object_format_arg) @@ -1527,10 +1632,15 @@ return; } if (TREE_CODE (format_tree) == ARRAY_REF - && host_integerp (TREE_OPERAND (format_tree, 1), 0) - && (offset += tree_low_cst (TREE_OPERAND (format_tree, 1), 0)) >= 0) + && tree_fits_shwi_p (TREE_OPERAND (format_tree, 1)) + && (offset += tree_to_shwi (TREE_OPERAND (format_tree, 1))) >= 0) format_tree = TREE_OPERAND (format_tree, 0); - if (TREE_CODE (format_tree) == VAR_DECL + if (offset < 0) + { + res->number_non_literal++; + return; + } + if (VAR_P (format_tree) && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE && (array_init = decl_constant_value (format_tree)) != format_tree && TREE_CODE (array_init) == STRING_CST) @@ -1558,9 +1668,9 @@ /* Variable length arrays can't be initialized. */ gcc_assert (TREE_CODE (array_size) == INTEGER_CST); - if (host_integerp (array_size, 0)) + if (tree_fits_shwi_p (array_size)) { - HOST_WIDE_INT array_size_value = TREE_INT_CST_LOW (array_size); + HOST_WIDE_INT array_size_value = tree_to_shwi (array_size); if (array_size_value > 0 && array_size_value == (int) array_size_value && format_length > array_size_value) @@ -1600,13 +1710,1050 @@ will decrement it if it finds there are extra arguments, but this way need not adjust it for every return. */ res->number_other++; - fwt_pool = create_alloc_pool ("format_wanted_type pool", - sizeof (format_wanted_type), 10); - check_format_info_main (res, info, format_chars, format_length, - params, arg_num, fwt_pool); - free_alloc_pool (fwt_pool); + object_allocator <format_wanted_type> fwt_pool ("format_wanted_type pool"); + check_format_info_main (res, info, format_chars, fmt_param_loc, format_tree, + format_length, params, arg_num, fwt_pool, arglocs); +} + +/* Support class for argument_parser and check_format_info_main. + Tracks any flag characters that have been applied to the + current argument. */ + +class flag_chars_t +{ + public: + flag_chars_t (); + bool has_char_p (char ch) const; + void add_char (char ch); + void validate (const format_kind_info *fki, + 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, + bool quoted); + int get_alloc_flag (const format_kind_info *fki); + int assignment_suppression_p (const format_kind_info *fki); + + private: + char m_flag_chars[256]; +}; + +/* Support struct for argument_parser and check_format_info_main. + Encapsulates any length modifier applied to the current argument. */ + +struct length_modifier +{ + length_modifier () + : chars (NULL), val (FMT_LEN_none), std (STD_C89), + scalar_identity_flag (0) + { + } + + length_modifier (const char *chars_, + enum format_lengths val_, + enum format_std_version std_, + int scalar_identity_flag_) + : chars (chars_), val (val_), std (std_), + scalar_identity_flag (scalar_identity_flag_) + { + } + + const char *chars; + enum format_lengths val; + enum format_std_version std; + int scalar_identity_flag; +}; + +/* Parsing one argument within a format string. */ + +class argument_parser +{ + 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, + object_allocator <format_wanted_type> &fwt_pool_, + vec<location_t> *arglocs); + + bool read_any_dollar (); + + bool read_format_flags (); + + bool + read_any_format_width (tree ¶ms, + unsigned HOST_WIDE_INT &arg_num); + + void + read_any_format_left_precision (); + + bool + read_any_format_precision (tree ¶ms, + unsigned HOST_WIDE_INT &arg_num); + + void handle_alloc_chars (); + + length_modifier read_any_length_modifier (); + + void read_any_other_modifier (); + + const format_char_info *find_format_char_info (char format_char); + + void + validate_flag_pairs (const format_char_info *fci, + char format_char); + + void + give_y2k_warnings (const format_char_info *fci, + char format_char); + + void parse_any_scan_set (const format_char_info *fci); + + bool handle_conversions (const format_char_info *fci, + const length_modifier &len_modifier, + tree &wanted_type, + const char *&wanted_type_name, + unsigned HOST_WIDE_INT &arg_num, + tree ¶ms, + char format_char); + + bool + check_argument_type (const format_char_info *fci, + const length_modifier &len_modifier, + tree &wanted_type, + const char *&wanted_type_name, + const bool suppressed, + unsigned HOST_WIDE_INT &arg_num, + tree ¶ms, + const int alloc_flag, + const char * const format_start, + const char * const type_start, + location_t fmt_param_loc, + char conversion_char); + + 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; + flag_chars_t &flag_chars; + int main_arg_num; + tree main_arg_params; + int &has_operand_number; + const tree first_fillin_param; + format_wanted_type width_wanted_type; + format_wanted_type precision_wanted_type; + public: + format_wanted_type main_wanted_type; + private: + format_wanted_type *first_wanted_type; + format_wanted_type *last_wanted_type; + vec<location_t> *arglocs; +}; + +/* flag_chars_t's constructor. */ + +flag_chars_t::flag_chars_t () +{ + m_flag_chars[0] = 0; +} + +/* Has CH been seen as a flag within the current argument? */ + +bool +flag_chars_t::has_char_p (char ch) const +{ + return strchr (m_flag_chars, ch) != 0; +} + +/* Add CH to the flags seen within the current argument. */ + +void +flag_chars_t::add_char (char ch) +{ + int i = strlen (m_flag_chars); + m_flag_chars[i++] = ch; + m_flag_chars[i] = 0; +} + +/* Validate the individual flags used, removing any that are invalid. */ + +void +flag_chars_t::validate (const format_kind_info *fki, + 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, + bool quoted) +{ + int i; + int d = 0; + bool quotflag = false; + + for (i = 0; m_flag_chars[i] != 0; i++) + { + const format_flag_spec *s = get_flag_spec (flag_specs, + m_flag_chars[i], NULL); + m_flag_chars[i - d] = m_flag_chars[i]; + if (m_flag_chars[i] == fki->length_code_char) + continue; + + /* Remember if a quoting flag is seen. */ + quotflag |= s->quoting; + + if (strchr (fci->flag_chars, m_flag_chars[i]) == 0) + { + 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; + } + if (pedantic) + { + const format_flag_spec *t; + if (ADJ_STD (s->std) > C_STD_VER) + warning_at (format_string_loc, OPT_Wformat_, + "%s does not support %s", + C_STD_NAME (s->std), _(s->long_name)); + t = get_flag_spec (flag_specs, m_flag_chars[i], fci->flags2); + if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std)) + { + const char *long_name = (t->long_name != NULL + ? t->long_name + : s->long_name); + if (ADJ_STD (t->std) > C_STD_VER) + warning_at (format_string_loc, OPT_Wformat_, + "%s does not support %s with" + " the %<%%%c%> %s format", + C_STD_NAME (t->std), _(long_name), + format_char, fki->name); + } + } + + /* Detect quoting directives used within a quoted sequence, such + as GCC's "%<...%qE". */ + if (quoted && s->quoting) + { + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars - 1, + OPT_Wformat_, + "%s used within a quoted sequence", + _(s->name)); + } + } + m_flag_chars[i - d] = 0; + + if (!quoted + && !quotflag + && strchr (fci->flags2, '\'')) + { + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "%qc conversion used unquoted", + format_char); + } +} + +/* Determine if an assignment-allocation has been set, requiring + an extra char ** for writing back a dynamically-allocated char *. + This is for handling the optional 'm' character in scanf. */ + +int +flag_chars_t::get_alloc_flag (const format_kind_info *fki) +{ + if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE) + && has_char_p ('a')) + return 1; + if (fki->alloc_char && has_char_p (fki->alloc_char)) + return 1; + return 0; +} + +/* Determine if an assignment-suppression character was seen. + ('*' in scanf, for discarding the converted input). */ + +int +flag_chars_t::assignment_suppression_p (const format_kind_info *fki) +{ + if (fki->suppression_char + && has_char_p (fki->suppression_char)) + return 1; + return 0; +} + +/* Constructor for argument_parser. Initialize for parsing one + argument within a format string. */ + +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_, + int &has_operand_number_, + tree first_fillin_param_, + object_allocator <format_wanted_type> &fwt_pool_, + vec<location_t> *arglocs_) +: 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_), + flag_chars (flag_chars_), + main_arg_num (0), + main_arg_params (NULL), + has_operand_number (has_operand_number_), + first_fillin_param (first_fillin_param_), + first_wanted_type (NULL), + last_wanted_type (NULL), + arglocs (arglocs_) +{ +} + +/* Handle dollars at the start of format arguments, setting up main_arg_params + and main_arg_num. + + Return true if format parsing is to continue, false otherwise. */ + +bool +argument_parser::read_any_dollar () +{ + if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0) + { + /* Possibly read a $ operand number at the start of the format. + If one was previously used, one is required here. If one + is not used here, we can't immediately conclude this is a + format without them, since it could be printf %m or scanf %*. */ + int opnum; + opnum = maybe_read_dollar_number (&format_chars, 0, + first_fillin_param, + &main_arg_params, fki); + if (opnum == -1) + return false; + else if (opnum > 0) + { + has_operand_number = 1; + main_arg_num = opnum + info->first_arg_num - 1; + } + } + else if (fki->flags & FMT_FLAG_USE_DOLLAR) + { + if (avoid_dollar_number (format_chars)) + return false; + } + return true; +} + +/* Read any format flags, but do not yet validate them beyond removing + duplicates, since in general validation depends on the rest of + the format. + + Return true if format parsing is to continue, false otherwise. */ + +bool +argument_parser::read_format_flags () +{ + while (*format_chars != 0 + && strchr (fki->flag_chars, *format_chars) != 0) + { + const format_flag_spec *s = get_flag_spec (flag_specs, + *format_chars, NULL); + if (flag_chars.has_char_p (*format_chars)) + { + 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); + + if (s->skip_next_char) + { + ++format_chars; + if (*format_chars == 0) + { + warning_at (format_string_loc, OPT_Wformat_, + "missing fill character at end of strfmon format"); + return false; + } + } + ++format_chars; + } + + return true; +} + +/* Read any format width, possibly * or *m$. + + Return true if format parsing is to continue, false otherwise. */ + +bool +argument_parser:: +read_any_format_width (tree ¶ms, + unsigned HOST_WIDE_INT &arg_num) +{ + if (!fki->width_char) + return true; + + if (fki->width_type != NULL && *format_chars == '*') + { + flag_chars.add_char (fki->width_char); + /* "...a field width...may be indicated by an asterisk. + In this case, an int argument supplies the field width..." */ + ++format_chars; + if (has_operand_number != 0) + { + int opnum; + opnum = maybe_read_dollar_number (&format_chars, + has_operand_number == 1, + first_fillin_param, + ¶ms, fki); + if (opnum == -1) + return false; + else if (opnum > 0) + { + has_operand_number = 1; + arg_num = opnum + info->first_arg_num - 1; + } + else + has_operand_number = 0; + } + else + { + if (avoid_dollar_number (format_chars)) + return false; + } + if (info->first_arg_num != 0) + { + tree cur_param; + if (params == 0) + cur_param = NULL; + else + { + cur_param = TREE_VALUE (params); + if (has_operand_number <= 0) + { + params = TREE_CHAIN (params); + ++arg_num; + } + } + width_wanted_type.wanted_type = *fki->width_type; + width_wanted_type.wanted_type_name = NULL; + width_wanted_type.pointer_count = 0; + width_wanted_type.char_lenient_flag = 0; + width_wanted_type.scalar_identity_flag = 0; + width_wanted_type.writing_in_flag = 0; + width_wanted_type.reading_from_flag = 0; + width_wanted_type.kind = CF_KIND_FIELD_WIDTH; + width_wanted_type.format_start = format_chars - 1; + width_wanted_type.format_length = 1; + width_wanted_type.param = cur_param; + width_wanted_type.arg_num = arg_num; + width_wanted_type.offset_loc = + format_chars - orig_format_chars; + width_wanted_type.next = NULL; + if (last_wanted_type != 0) + last_wanted_type->next = &width_wanted_type; + if (first_wanted_type == 0) + first_wanted_type = &width_wanted_type; + last_wanted_type = &width_wanted_type; + } + } + else + { + /* Possibly read a numeric width. If the width is zero, + we complain if appropriate. */ + int non_zero_width_char = FALSE; + int found_width = FALSE; + while (ISDIGIT (*format_chars)) + { + found_width = TRUE; + if (*format_chars != '0') + non_zero_width_char = TRUE; + ++format_chars; + } + if (found_width && !non_zero_width_char && + (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD)) + warning_at (format_string_loc, OPT_Wformat_, + "zero width in %s format", fki->name); + if (found_width) + flag_chars.add_char (fki->width_char); + } + + return true; +} + +/* Read any format left precision (must be a number, not *). */ +void +argument_parser::read_any_format_left_precision () +{ + if (fki->left_precision_char == 0) + return; + if (*format_chars != '#') + return; + + ++format_chars; + flag_chars.add_char (fki->left_precision_char); + if (!ISDIGIT (*format_chars)) + 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; } +/* Read any format precision, possibly * or *m$. + + Return true if format parsing is to continue, false otherwise. */ + +bool +argument_parser:: +read_any_format_precision (tree ¶ms, + unsigned HOST_WIDE_INT &arg_num) +{ + if (fki->precision_char == 0) + return true; + if (*format_chars != '.') + return true; + + ++format_chars; + flag_chars.add_char (fki->precision_char); + if (fki->precision_type != NULL && *format_chars == '*') + { + /* "...a...precision...may be indicated by an asterisk. + In this case, an int argument supplies the...precision." */ + ++format_chars; + if (has_operand_number != 0) + { + int opnum; + opnum = maybe_read_dollar_number (&format_chars, + has_operand_number == 1, + first_fillin_param, + ¶ms, fki); + if (opnum == -1) + return false; + else if (opnum > 0) + { + has_operand_number = 1; + arg_num = opnum + info->first_arg_num - 1; + } + else + has_operand_number = 0; + } + else + { + if (avoid_dollar_number (format_chars)) + return false; + } + if (info->first_arg_num != 0) + { + tree cur_param; + if (params == 0) + cur_param = NULL; + else + { + cur_param = TREE_VALUE (params); + if (has_operand_number <= 0) + { + params = TREE_CHAIN (params); + ++arg_num; + } + } + precision_wanted_type.wanted_type = *fki->precision_type; + precision_wanted_type.wanted_type_name = NULL; + precision_wanted_type.pointer_count = 0; + precision_wanted_type.char_lenient_flag = 0; + precision_wanted_type.scalar_identity_flag = 0; + precision_wanted_type.writing_in_flag = 0; + precision_wanted_type.reading_from_flag = 0; + precision_wanted_type.kind = CF_KIND_FIELD_PRECISION; + precision_wanted_type.param = cur_param; + precision_wanted_type.format_start = format_chars - 2; + precision_wanted_type.format_length = 2; + precision_wanted_type.arg_num = arg_num; + precision_wanted_type.offset_loc = + format_chars - orig_format_chars; + precision_wanted_type.next = NULL; + if (last_wanted_type != 0) + last_wanted_type->next = &precision_wanted_type; + if (first_wanted_type == 0) + first_wanted_type = &precision_wanted_type; + last_wanted_type = &precision_wanted_type; + } + } + else + { + if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK) + && !ISDIGIT (*format_chars)) + 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; + } + + return true; +} + +/* Parse any assignment-allocation flags, which request an extra + char ** for writing back a dynamically-allocated char *. + This is for handling the optional 'm' character in scanf, + and, before C99, 'a' (for compatibility with a non-standard + GNU libc extension). */ + +void +argument_parser::handle_alloc_chars () +{ + if (fki->alloc_char && fki->alloc_char == *format_chars) + { + flag_chars.add_char (fki->alloc_char); + format_chars++; + } + + /* Handle the scanf allocation kludge. */ + if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE) + { + if (*format_chars == 'a' && !flag_isoc99) + { + if (format_chars[1] == 's' || format_chars[1] == 'S' + || format_chars[1] == '[') + { + /* 'a' is used as a flag. */ + flag_chars.add_char ('a'); + format_chars++; + } + } + } +} + +/* Look for length modifiers within the current format argument, + returning a length_modifier instance describing it (or the + default if one is not found). + + Issue warnings about non-standard modifiers. */ + +length_modifier +argument_parser::read_any_length_modifier () +{ + length_modifier result; + + const format_length_info *fli = fki->length_char_specs; + if (!fli) + return result; + + while (fli->name != 0 + && strncmp (fli->name, format_chars, strlen (fli->name))) + fli++; + if (fli->name != 0) + { + format_chars += strlen (fli->name); + if (fli->double_name != 0 && fli->name[0] == *format_chars) + { + format_chars++; + result = length_modifier (fli->double_name, fli->double_index, + fli->double_std, 0); + } + else + { + result = length_modifier (fli->name, fli->index, fli->std, + fli->scalar_identity_flag); + } + flag_chars.add_char (fki->length_code_char); + } + if (pedantic) + { + /* Warn if the length modifier is non-standard. */ + if (ADJ_STD (result.std) > C_STD_VER) + warning_at (format_string_loc, OPT_Wformat_, + "%s does not support the %qs %s length modifier", + C_STD_NAME (result.std), result.chars, + fki->name); + } + + return result; +} + +/* Read any other modifier (strftime E/O). */ + +void +argument_parser::read_any_other_modifier () +{ + if (fki->modifier_chars == NULL) + return; + + while (*format_chars != 0 + && strchr (fki->modifier_chars, *format_chars) != 0) + { + if (flag_chars.has_char_p (*format_chars)) + { + const format_flag_spec *s = get_flag_spec (flag_specs, + *format_chars, NULL); + 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); + ++format_chars; + } +} + +/* Return the format_char_info corresponding to FORMAT_CHAR, + potentially issuing a warning if the format char is + not supported in the C standard version we are checking + against. + + Issue a warning and return NULL if it is not found. + + Issue warnings about non-standard modifiers. */ + +const format_char_info * +argument_parser::find_format_char_info (char format_char) +{ + const format_char_info *fci = fki->conversion_specs; + + while (fci->format_chars != 0 + && strchr (fci->format_chars, format_char) == 0) + ++fci; + if (fci->format_chars == 0) + { + 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); + return NULL; + } + + if (pedantic) + { + if (ADJ_STD (fci->std) > C_STD_VER) + 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; +} + +/* Validate the pairs of flags used. + Issue warnings about incompatible combinations of flags. */ + +void +argument_parser::validate_flag_pairs (const format_char_info *fci, + char format_char) +{ + const format_flag_pair * const bad_flag_pairs = fki->bad_flag_pairs; + + for (int i = 0; bad_flag_pairs[i].flag_char1 != 0; i++) + { + const format_flag_spec *s, *t; + if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char1)) + continue; + if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char2)) + continue; + if (bad_flag_pairs[i].predicate != 0 + && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0) + continue; + s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL); + t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL); + if (bad_flag_pairs[i].ignored) + { + if (bad_flag_pairs[i].predicate != 0) + warning_at (format_string_loc, OPT_Wformat_, + "%s ignored with %s and %<%%%c%> %s format", + _(s->name), _(t->name), format_char, + fki->name); + else + warning_at (format_string_loc, OPT_Wformat_, + "%s ignored with %s in %s format", + _(s->name), _(t->name), fki->name); + } + else + { + if (bad_flag_pairs[i].predicate != 0) + warning_at (format_string_loc, OPT_Wformat_, + "use of %s and %s together with %<%%%c%> %s format", + _(s->name), _(t->name), format_char, + fki->name); + else + warning_at (format_string_loc, OPT_Wformat_, + "use of %s and %s together in %s format", + _(s->name), _(t->name), fki->name); + } + } +} + +/* Give Y2K warnings. */ + +void +argument_parser::give_y2k_warnings (const format_char_info *fci, + char format_char) +{ + if (!warn_format_y2k) + return; + + int y2k_level = 0; + if (strchr (fci->flags2, '4') != 0) + if (flag_chars.has_char_p ('E')) + y2k_level = 3; + else + y2k_level = 2; + else if (strchr (fci->flags2, '3') != 0) + y2k_level = 3; + else if (strchr (fci->flags2, '2') != 0) + y2k_level = 2; + if (y2k_level == 3) + warning_at (format_string_loc, OPT_Wformat_y2k, + "%<%%%c%> yields only last 2 digits of " + "year in some locales", format_char); + else if (y2k_level == 2) + warning_at (format_string_loc, OPT_Wformat_y2k, + "%<%%%c%> yields only last 2 digits of year", + format_char); +} + +/* Parse any "scan sets" enclosed in square brackets, e.g. + for scanf-style calls. */ + +void +argument_parser::parse_any_scan_set (const format_char_info *fci) +{ + if (strchr (fci->flags2, '[') == NULL) + return; + + /* Skip over scan set, in case it happens to have '%' in it. */ + if (*format_chars == '^') + ++format_chars; + /* Find closing bracket; if one is hit immediately, then + it's part of the scan set rather than a terminator. */ + if (*format_chars == ']') + ++format_chars; + while (*format_chars && *format_chars != ']') + ++format_chars; + if (*format_chars != ']') + /* The end of the format string was reached. */ + 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, + false to skip to next argument. */ + +bool +argument_parser::handle_conversions (const format_char_info *fci, + const length_modifier &len_modifier, + tree &wanted_type, + const char *&wanted_type_name, + unsigned HOST_WIDE_INT &arg_num, + tree ¶ms, + char format_char) +{ + enum format_std_version wanted_type_std; + + if (!(fki->flags & (int) FMT_FLAG_ARG_CONVERT)) + return true; + + wanted_type = (fci->types[len_modifier.val].type + ? *fci->types[len_modifier.val].type : 0); + wanted_type_name = fci->types[len_modifier.val].name; + wanted_type_std = fci->types[len_modifier.val].std; + if (wanted_type == 0) + { + 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++; + if (params != 0) + params = TREE_CHAIN (params); + return false; + } + else if (pedantic + /* Warn if non-standard, provided it is more non-standard + than the length and type characters that may already + have been warned for. */ + && ADJ_STD (wanted_type_std) > ADJ_STD (len_modifier.std) + && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std)) + { + if (ADJ_STD (wanted_type_std) > C_STD_VER) + 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; +} + +/* Check type of argument against desired type. + + Return true if format parsing is to continue, false otherwise. */ + +bool +argument_parser:: +check_argument_type (const format_char_info *fci, + const length_modifier &len_modifier, + tree &wanted_type, + const char *&wanted_type_name, + const bool suppressed, + unsigned HOST_WIDE_INT &arg_num, + tree ¶ms, + const int alloc_flag, + const char * const format_start, + const char * const type_start, + location_t fmt_param_loc, + char conversion_char) +{ + if (info->first_arg_num == 0) + return true; + + if ((fci->pointer_count == 0 && wanted_type == void_type_node) + || suppressed) + { + if (main_arg_num != 0) + { + if (suppressed) + warning_at (format_string_loc, OPT_Wformat_, + "operand number specified with " + "suppressed assignment"); + else + warning_at (format_string_loc, OPT_Wformat_, + "operand number specified for format " + "taking no argument"); + } + } + else + { + format_wanted_type *wanted_type_ptr; + + if (main_arg_num != 0) + { + arg_num = main_arg_num; + params = main_arg_params; + } + else + { + ++arg_num; + if (has_operand_number > 0) + { + warning_at (format_string_loc, OPT_Wformat_, + "missing $ operand number in format"); + return false; + } + else + has_operand_number = 0; + } + + wanted_type_ptr = &main_wanted_type; + while (fci) + { + tree cur_param; + if (params == 0) + cur_param = NULL; + else + { + cur_param = TREE_VALUE (params); + params = TREE_CHAIN (params); + } + + wanted_type_ptr->wanted_type = wanted_type; + wanted_type_ptr->wanted_type_name = wanted_type_name; + wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag; + wanted_type_ptr->char_lenient_flag = 0; + if (strchr (fci->flags2, 'c') != 0) + wanted_type_ptr->char_lenient_flag = 1; + wanted_type_ptr->scalar_identity_flag = 0; + if (len_modifier.scalar_identity_flag) + wanted_type_ptr->scalar_identity_flag = 1; + wanted_type_ptr->writing_in_flag = 0; + wanted_type_ptr->reading_from_flag = 0; + if (alloc_flag) + wanted_type_ptr->writing_in_flag = 1; + else + { + if (strchr (fci->flags2, 'W') != 0) + wanted_type_ptr->writing_in_flag = 1; + if (strchr (fci->flags2, 'R') != 0) + wanted_type_ptr->reading_from_flag = 1; + } + wanted_type_ptr->kind = CF_KIND_FORMAT; + wanted_type_ptr->param = cur_param; + wanted_type_ptr->arg_num = arg_num; + wanted_type_ptr->format_start = format_start; + wanted_type_ptr->format_length = format_chars - format_start; + wanted_type_ptr->offset_loc = format_chars - orig_format_chars; + wanted_type_ptr->next = NULL; + if (last_wanted_type != 0) + last_wanted_type->next = wanted_type_ptr; + if (first_wanted_type == 0) + first_wanted_type = wanted_type_ptr; + last_wanted_type = wanted_type_ptr; + + fci = fci->chain; + if (fci) + { + wanted_type_ptr = fwt_pool.allocate (); + arg_num++; + wanted_type = *fci->types[len_modifier.val].type; + wanted_type_name = fci->types[len_modifier.val].name; + } + } + } + + if (first_wanted_type != 0) + { + 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; + /* By default, use the end of the range for the caret location. */ + substring_loc fmt_loc (fmt_param_loc, TREE_TYPE (format_string_cst), + offset_to_format_end, + offset_to_format_start, offset_to_format_end); + 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, arglocs); + } + + return true; +} /* Do the main part of checking a call to a format function. FORMAT_CHARS is the NUL-terminated format string (which at this point may contain @@ -1618,53 +2765,45 @@ 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, alloc_pool fwt_pool) + unsigned HOST_WIDE_INT arg_num, + object_allocator <format_wanted_type> &fwt_pool, + vec<location_t> *arglocs) { - const char *orig_format_chars = format_chars; - tree first_fillin_param = params; - - const format_kind_info *fki = &format_types[info->format_type]; - const format_flag_spec *flag_specs = fki->flag_specs; - const format_flag_pair *bad_flag_pairs = fki->bad_flag_pairs; + const char * const orig_format_chars = format_chars; + const tree first_fillin_param = params; + + const format_kind_info * const fki = &format_types[info->format_type]; + const format_flag_spec * const flag_specs = fki->flag_specs; + const location_t format_string_loc = res->format_string_loc; /* -1 if no conversions taking an operand have been found; 0 if one has and it didn't use $; 1 if $ formats are in use. */ int has_operand_number = -1; + /* Vector of pointers to opening quoting directives (like GCC "%<"). */ + auto_vec<const char*> quotdirs; + + /* Pointers to the most recent color directives (like GCC's "%r or %R"). + A starting color directive much be terminated before the end of + the format string. A terminating directive makes no sense without + a prior starting directive. */ + const char *color_begin = NULL; + const char *color_end = NULL; + init_dollar_format_checking (info->first_arg_num, first_fillin_param); while (*format_chars != 0) { - int i; - int suppressed = FALSE; - const char *length_chars = NULL; - enum format_lengths length_chars_val = FMT_LEN_none; - enum format_std_version length_chars_std = STD_C89; - int format_char; - tree cur_param; - tree wanted_type; - int main_arg_num = 0; - tree main_arg_params = 0; - enum format_std_version wanted_type_std; - const char *wanted_type_name; - format_wanted_type width_wanted_type; - format_wanted_type precision_wanted_type; - format_wanted_type main_wanted_type; - format_wanted_type *first_wanted_type = NULL; - format_wanted_type *last_wanted_type = NULL; - const format_length_info *fli = NULL; - const format_char_info *fci = NULL; - char flag_chars[256]; - int alloc_flag = 0; - int scalar_identity_flag = 0; - const char *format_start; - if (*format_chars++ != '%') continue; if (*format_chars == 0) { - warning (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 == '%') @@ -1672,623 +2811,170 @@ ++format_chars; continue; } - flag_chars[0] = 0; - - if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0) - { - /* Possibly read a $ operand number at the start of the format. - If one was previously used, one is required here. If one - is not used here, we can't immediately conclude this is a - format without them, since it could be printf %m or scanf %*. */ - int opnum; - opnum = maybe_read_dollar_number (&format_chars, 0, - first_fillin_param, - &main_arg_params, fki); - if (opnum == -1) - return; - else if (opnum > 0) - { - has_operand_number = 1; - main_arg_num = opnum + info->first_arg_num - 1; - } - } - else if (fki->flags & FMT_FLAG_USE_DOLLAR) - { - if (avoid_dollar_number (format_chars)) - return; - } - - /* Read any format flags, but do not yet validate them beyond removing - duplicates, since in general validation depends on the rest of - the format. */ - while (*format_chars != 0 - && strchr (fki->flag_chars, *format_chars) != 0) - { - const format_flag_spec *s = get_flag_spec (flag_specs, - *format_chars, NULL); - if (strchr (flag_chars, *format_chars) != 0) - { - warning (OPT_Wformat, "repeated %s in format", _(s->name)); - } - else - { - i = strlen (flag_chars); - flag_chars[i++] = *format_chars; - flag_chars[i] = 0; - } - if (s->skip_next_char) - { - ++format_chars; - if (*format_chars == 0) - { - warning (OPT_Wformat, "missing fill character at end of strfmon format"); - return; - } - } - ++format_chars; - } + + flag_chars_t flag_chars; + 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, arglocs); + + if (!arg_parser.read_any_dollar ()) + return; + + if (!arg_parser.read_format_flags ()) + return; /* Read any format width, possibly * or *m$. */ - if (fki->width_char != 0) - { - if (fki->width_type != NULL && *format_chars == '*') - { - i = strlen (flag_chars); - flag_chars[i++] = fki->width_char; - flag_chars[i] = 0; - /* "...a field width...may be indicated by an asterisk. - In this case, an int argument supplies the field width..." */ - ++format_chars; - if (has_operand_number != 0) - { - int opnum; - opnum = maybe_read_dollar_number (&format_chars, - has_operand_number == 1, - first_fillin_param, - ¶ms, fki); - if (opnum == -1) - return; - else if (opnum > 0) - { - has_operand_number = 1; - arg_num = opnum + info->first_arg_num - 1; - } - else - has_operand_number = 0; - } - else - { - if (avoid_dollar_number (format_chars)) - return; - } - if (info->first_arg_num != 0) - { - if (params == 0) - cur_param = NULL; - else - { - cur_param = TREE_VALUE (params); - if (has_operand_number <= 0) - { - params = TREE_CHAIN (params); - ++arg_num; - } - } - width_wanted_type.wanted_type = *fki->width_type; - width_wanted_type.wanted_type_name = NULL; - width_wanted_type.pointer_count = 0; - width_wanted_type.char_lenient_flag = 0; - width_wanted_type.scalar_identity_flag = 0; - width_wanted_type.writing_in_flag = 0; - width_wanted_type.reading_from_flag = 0; - width_wanted_type.kind = CF_KIND_FIELD_WIDTH; - width_wanted_type.format_start = format_chars - 1; - width_wanted_type.format_length = 1; - width_wanted_type.param = cur_param; - width_wanted_type.arg_num = arg_num; - width_wanted_type.next = NULL; - if (last_wanted_type != 0) - last_wanted_type->next = &width_wanted_type; - if (first_wanted_type == 0) - first_wanted_type = &width_wanted_type; - last_wanted_type = &width_wanted_type; - } - } - else - { - /* Possibly read a numeric width. If the width is zero, - we complain if appropriate. */ - int non_zero_width_char = FALSE; - int found_width = FALSE; - while (ISDIGIT (*format_chars)) - { - found_width = TRUE; - if (*format_chars != '0') - non_zero_width_char = TRUE; - ++format_chars; - } - if (found_width && !non_zero_width_char && - (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD)) - warning (OPT_Wformat, "zero width in %s format", fki->name); - if (found_width) - { - i = strlen (flag_chars); - flag_chars[i++] = fki->width_char; - flag_chars[i] = 0; - } - } - } + if (!arg_parser.read_any_format_width (params, arg_num)) + return; /* Read any format left precision (must be a number, not *). */ - if (fki->left_precision_char != 0 && *format_chars == '#') - { - ++format_chars; - i = strlen (flag_chars); - flag_chars[i++] = fki->left_precision_char; - flag_chars[i] = 0; - if (!ISDIGIT (*format_chars)) - warning (OPT_Wformat, "empty left precision in %s format", fki->name); - while (ISDIGIT (*format_chars)) - ++format_chars; - } + arg_parser.read_any_format_left_precision (); /* Read any format precision, possibly * or *m$. */ - if (fki->precision_char != 0 && *format_chars == '.') - { - ++format_chars; - i = strlen (flag_chars); - flag_chars[i++] = fki->precision_char; - flag_chars[i] = 0; - if (fki->precision_type != NULL && *format_chars == '*') - { - /* "...a...precision...may be indicated by an asterisk. - In this case, an int argument supplies the...precision." */ - ++format_chars; - if (has_operand_number != 0) - { - int opnum; - opnum = maybe_read_dollar_number (&format_chars, - has_operand_number == 1, - first_fillin_param, - ¶ms, fki); - if (opnum == -1) - return; - else if (opnum > 0) - { - has_operand_number = 1; - arg_num = opnum + info->first_arg_num - 1; - } - else - has_operand_number = 0; - } - else - { - if (avoid_dollar_number (format_chars)) - return; - } - if (info->first_arg_num != 0) - { - if (params == 0) - cur_param = NULL; - else - { - cur_param = TREE_VALUE (params); - if (has_operand_number <= 0) - { - params = TREE_CHAIN (params); - ++arg_num; - } - } - precision_wanted_type.wanted_type = *fki->precision_type; - precision_wanted_type.wanted_type_name = NULL; - precision_wanted_type.pointer_count = 0; - precision_wanted_type.char_lenient_flag = 0; - precision_wanted_type.scalar_identity_flag = 0; - precision_wanted_type.writing_in_flag = 0; - precision_wanted_type.reading_from_flag = 0; - precision_wanted_type.kind = CF_KIND_FIELD_PRECISION; - precision_wanted_type.param = cur_param; - precision_wanted_type.format_start = format_chars - 2; - precision_wanted_type.format_length = 2; - precision_wanted_type.arg_num = arg_num; - precision_wanted_type.next = NULL; - if (last_wanted_type != 0) - last_wanted_type->next = &precision_wanted_type; - if (first_wanted_type == 0) - first_wanted_type = &precision_wanted_type; - last_wanted_type = &precision_wanted_type; - } - } - else - { - if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK) - && !ISDIGIT (*format_chars)) - warning (OPT_Wformat, "empty precision in %s format", fki->name); - while (ISDIGIT (*format_chars)) - ++format_chars; - } - } - - format_start = format_chars; - if (fki->alloc_char && fki->alloc_char == *format_chars) - { - i = strlen (flag_chars); - flag_chars[i++] = fki->alloc_char; - flag_chars[i] = 0; - format_chars++; - } - - /* Handle the scanf allocation kludge. */ - if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE) - { - if (*format_chars == 'a' && !flag_isoc99) - { - if (format_chars[1] == 's' || format_chars[1] == 'S' - || format_chars[1] == '[') - { - /* 'a' is used as a flag. */ - i = strlen (flag_chars); - flag_chars[i++] = 'a'; - flag_chars[i] = 0; - format_chars++; - } - } - } + if (!arg_parser.read_any_format_precision (params, arg_num)) + return; + + const char *format_start = format_chars; + + 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. */ - fli = fki->length_char_specs; - length_chars = NULL; - length_chars_val = FMT_LEN_none; - length_chars_std = STD_C89; - scalar_identity_flag = 0; - if (fli) - { - while (fli->name != 0 - && strncmp (fli->name, format_chars, strlen (fli->name))) - fli++; - if (fli->name != 0) - { - format_chars += strlen (fli->name); - if (fli->double_name != 0 && fli->name[0] == *format_chars) - { - format_chars++; - length_chars = fli->double_name; - length_chars_val = fli->double_index; - length_chars_std = fli->double_std; - } - else - { - length_chars = fli->name; - length_chars_val = fli->index; - length_chars_std = fli->std; - scalar_identity_flag = fli->scalar_identity_flag; - } - i = strlen (flag_chars); - flag_chars[i++] = fki->length_code_char; - flag_chars[i] = 0; - } - if (pedantic) - { - /* Warn if the length modifier is non-standard. */ - if (ADJ_STD (length_chars_std) > C_STD_VER) - warning (OPT_Wformat, - "%s does not support the %qs %s length modifier", - C_STD_NAME (length_chars_std), length_chars, - fki->name); - } - } + const length_modifier len_modifier + = arg_parser.read_any_length_modifier (); /* Read any modifier (strftime E/O). */ - if (fki->modifier_chars != NULL) - { - while (*format_chars != 0 - && strchr (fki->modifier_chars, *format_chars) != 0) - { - if (strchr (flag_chars, *format_chars) != 0) - { - const format_flag_spec *s = get_flag_spec (flag_specs, - *format_chars, NULL); - warning (OPT_Wformat, "repeated %s in format", _(s->name)); - } - else - { - i = strlen (flag_chars); - flag_chars[i++] = *format_chars; - flag_chars[i] = 0; - } - ++format_chars; - } - } - - format_char = *format_chars; + arg_parser.read_any_other_modifier (); + + char format_char = *format_chars; if (format_char == 0 || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK) && format_char == '%')) { - warning (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++; - fci = fki->conversion_specs; - while (fci->format_chars != 0 - && strchr (fci->format_chars, format_char) == 0) - ++fci; - if (fci->format_chars == 0) + + const format_char_info * const fci + = arg_parser.find_format_char_info (format_char); + if (!fci) + continue; + + flag_chars.validate (fki, fci, flag_specs, format_chars, + format_string_cst, + format_string_loc, orig_format_chars, format_char, + quotdirs.length () > 0); + + const int alloc_flag = flag_chars.get_alloc_flag (fki); + const bool suppressed = flag_chars.assignment_suppression_p (fki); + + /* Diagnose nested or unmatched quoting directives such as GCC's + "%<...%<" and "%>...%>". */ + bool quot_begin_p = strchr (fci->flags2, '<'); + bool quot_end_p = strchr (fci->flags2, '>'); + + if (quot_begin_p && !quot_end_p) { - if (ISGRAPH (format_char)) - warning (OPT_Wformat, "unknown conversion type character %qc in format", - format_char); - else - warning (OPT_Wformat, "unknown conversion type character 0x%x in format", - format_char); - continue; - } - if (pedantic) - { - if (ADJ_STD (fci->std) > C_STD_VER) - warning (OPT_Wformat, "%s does not support the %<%%%c%> %s format", - C_STD_NAME (fci->std), format_char, fki->name); + if (quotdirs.length ()) + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "nested quoting directive"); + quotdirs.safe_push (format_chars); } - - /* Validate the individual flags used, removing any that are invalid. */ - { - int d = 0; - for (i = 0; flag_chars[i] != 0; i++) - { - const format_flag_spec *s = get_flag_spec (flag_specs, - flag_chars[i], NULL); - flag_chars[i - d] = flag_chars[i]; - if (flag_chars[i] == fki->length_code_char) - continue; - if (strchr (fci->flag_chars, flag_chars[i]) == 0) - { - warning (OPT_Wformat, "%s used with %<%%%c%> %s format", - _(s->name), format_char, fki->name); - d++; - continue; - } - if (pedantic) - { - const format_flag_spec *t; - if (ADJ_STD (s->std) > C_STD_VER) - warning (OPT_Wformat, "%s does not support %s", - C_STD_NAME (s->std), _(s->long_name)); - t = get_flag_spec (flag_specs, flag_chars[i], fci->flags2); - if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std)) - { - const char *long_name = (t->long_name != NULL - ? t->long_name - : s->long_name); - if (ADJ_STD (t->std) > C_STD_VER) - warning (OPT_Wformat, - "%s does not support %s with the %<%%%c%> %s format", - C_STD_NAME (t->std), _(long_name), - format_char, fki->name); - } - } - } - flag_chars[i - d] = 0; - } - - if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE) - && strchr (flag_chars, 'a') != 0) - alloc_flag = 1; - if (fki->alloc_char && strchr (flag_chars, fki->alloc_char) != 0) - alloc_flag = 1; - - if (fki->suppression_char - && strchr (flag_chars, fki->suppression_char) != 0) - suppressed = 1; + else if (!quot_begin_p && quot_end_p) + { + if (quotdirs.length ()) + quotdirs.pop (); + else + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "unmatched quoting directive"); + } + + bool color_begin_p = strchr (fci->flags2, '/'); + if (color_begin_p) + { + color_begin = format_chars; + color_end = NULL; + } + else if (strchr (fci->flags2, '\\')) + { + if (color_end) + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "%qc directive redundant after prior " + "occurence of the same", format_char); + else if (!color_begin) + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "unmatched color reset directive"); + color_end = format_chars; + } + + /* Diagnose directives that shouldn't appear in a quoted sequence. + (They are denoted by a double quote in FLAGS2.) */ + if (quotdirs.length ()) + { + if (strchr (fci->flags2, '"')) + format_warning_at_char (format_string_loc, format_string_cst, + format_chars - orig_format_chars, + OPT_Wformat_, + "%qc conversion used within a quoted " + "sequence", + format_char); + } /* Validate the pairs of flags used. */ - for (i = 0; bad_flag_pairs[i].flag_char1 != 0; i++) - { - const format_flag_spec *s, *t; - if (strchr (flag_chars, bad_flag_pairs[i].flag_char1) == 0) - continue; - if (strchr (flag_chars, bad_flag_pairs[i].flag_char2) == 0) - continue; - if (bad_flag_pairs[i].predicate != 0 - && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0) - continue; - s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL); - t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL); - if (bad_flag_pairs[i].ignored) - { - if (bad_flag_pairs[i].predicate != 0) - warning (OPT_Wformat, - "%s ignored with %s and %<%%%c%> %s format", - _(s->name), _(t->name), format_char, - fki->name); - else - warning (OPT_Wformat, "%s ignored with %s in %s format", - _(s->name), _(t->name), fki->name); - } - else - { - if (bad_flag_pairs[i].predicate != 0) - warning (OPT_Wformat, - "use of %s and %s together with %<%%%c%> %s format", - _(s->name), _(t->name), format_char, - fki->name); - else - warning (OPT_Wformat, "use of %s and %s together in %s format", - _(s->name), _(t->name), fki->name); - } - } - - /* Give Y2K warnings. */ - if (warn_format_y2k) - { - int y2k_level = 0; - if (strchr (fci->flags2, '4') != 0) - if (strchr (flag_chars, 'E') != 0) - y2k_level = 3; - else - y2k_level = 2; - else if (strchr (fci->flags2, '3') != 0) - y2k_level = 3; - else if (strchr (fci->flags2, '2') != 0) - y2k_level = 2; - if (y2k_level == 3) - warning (OPT_Wformat_y2k, "%<%%%c%> yields only last 2 digits of " - "year in some locales", format_char); - else if (y2k_level == 2) - warning (OPT_Wformat_y2k, "%<%%%c%> yields only last 2 digits of " - "year", format_char); - } - - if (strchr (fci->flags2, '[') != 0) - { - /* Skip over scan set, in case it happens to have '%' in it. */ - if (*format_chars == '^') - ++format_chars; - /* Find closing bracket; if one is hit immediately, then - it's part of the scan set rather than a terminator. */ - if (*format_chars == ']') - ++format_chars; - while (*format_chars && *format_chars != ']') - ++format_chars; - if (*format_chars != ']') - /* The end of the format string was reached. */ - warning (OPT_Wformat, "no closing %<]%> for %<%%[%> format"); - } - - wanted_type = 0; - wanted_type_name = 0; - if (fki->flags & (int) FMT_FLAG_ARG_CONVERT) - { - wanted_type = (fci->types[length_chars_val].type - ? *fci->types[length_chars_val].type : 0); - wanted_type_name = fci->types[length_chars_val].name; - wanted_type_std = fci->types[length_chars_val].std; - if (wanted_type == 0) - { - warning (OPT_Wformat, - "use of %qs length modifier with %qc type character", - length_chars, format_char); - /* Heuristic: skip one argument when an invalid length/type - combination is encountered. */ - arg_num++; - if (params != 0) - params = TREE_CHAIN (params); - continue; - } - else if (pedantic - /* Warn if non-standard, provided it is more non-standard - than the length and type characters that may already - have been warned for. */ - && ADJ_STD (wanted_type_std) > ADJ_STD (length_chars_std) - && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std)) - { - if (ADJ_STD (wanted_type_std) > C_STD_VER) - warning (OPT_Wformat, - "%s does not support the %<%%%s%c%> %s format", - C_STD_NAME (wanted_type_std), length_chars, - format_char, fki->name); - } - } - - main_wanted_type.next = NULL; + arg_parser.validate_flag_pairs (fci, format_char); + + arg_parser.give_y2k_warnings (fci, format_char); + + arg_parser.parse_any_scan_set (fci); + + tree wanted_type = NULL; + const char *wanted_type_name = NULL; + + if (!arg_parser.handle_conversions (fci, len_modifier, + wanted_type, wanted_type_name, + arg_num, + params, + format_char)) + continue; + + arg_parser.main_wanted_type.next = NULL; /* Finally. . .check type of argument against desired type! */ - if (info->first_arg_num == 0) - continue; - if ((fci->pointer_count == 0 && wanted_type == void_type_node) - || suppressed) - { - if (main_arg_num != 0) - { - if (suppressed) - warning (OPT_Wformat, "operand number specified with " - "suppressed assignment"); - else - warning (OPT_Wformat, "operand number specified for format " - "taking no argument"); - } - } - else - { - format_wanted_type *wanted_type_ptr; - - if (main_arg_num != 0) - { - arg_num = main_arg_num; - params = main_arg_params; - } - else - { - ++arg_num; - if (has_operand_number > 0) - { - warning (OPT_Wformat, "missing $ operand number in format"); - return; - } - else - has_operand_number = 0; - } - - wanted_type_ptr = &main_wanted_type; - while (fci) - { - if (params == 0) - cur_param = NULL; - else - { - cur_param = TREE_VALUE (params); - params = TREE_CHAIN (params); - } - - wanted_type_ptr->wanted_type = wanted_type; - wanted_type_ptr->wanted_type_name = wanted_type_name; - wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag; - wanted_type_ptr->char_lenient_flag = 0; - if (strchr (fci->flags2, 'c') != 0) - wanted_type_ptr->char_lenient_flag = 1; - wanted_type_ptr->scalar_identity_flag = 0; - if (scalar_identity_flag) - wanted_type_ptr->scalar_identity_flag = 1; - wanted_type_ptr->writing_in_flag = 0; - wanted_type_ptr->reading_from_flag = 0; - if (alloc_flag) - wanted_type_ptr->writing_in_flag = 1; - else - { - if (strchr (fci->flags2, 'W') != 0) - wanted_type_ptr->writing_in_flag = 1; - if (strchr (fci->flags2, 'R') != 0) - wanted_type_ptr->reading_from_flag = 1; - } - wanted_type_ptr->kind = CF_KIND_FORMAT; - wanted_type_ptr->param = cur_param; - wanted_type_ptr->arg_num = arg_num; - wanted_type_ptr->format_start = format_start; - wanted_type_ptr->format_length = format_chars - format_start; - wanted_type_ptr->next = NULL; - if (last_wanted_type != 0) - last_wanted_type->next = wanted_type_ptr; - if (first_wanted_type == 0) - first_wanted_type = wanted_type_ptr; - last_wanted_type = wanted_type_ptr; - - fci = fci->chain; - if (fci) - { - wanted_type_ptr = (format_wanted_type *) - pool_alloc (fwt_pool); - arg_num++; - wanted_type = *fci->types[length_chars_val].type; - wanted_type_name = fci->types[length_chars_val].name; - } - } - } - - if (first_wanted_type != 0) - check_format_types (first_wanted_type); + if (!arg_parser.check_argument_type (fci, len_modifier, + wanted_type, wanted_type_name, + suppressed, + arg_num, params, + alloc_flag, + format_start, type_start, + fmt_param_loc, + format_char)) + return; } if (format_chars - orig_format_chars != format_length) - warning (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) { @@ -2297,13 +2983,71 @@ } if (has_operand_number > 0) finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK); + + if (quotdirs.length ()) + format_warning_at_char (format_string_loc, format_string_cst, + quotdirs.pop () - orig_format_chars, + OPT_Wformat_, "unterminated quoting directive"); + if (color_begin && !color_end) + format_warning_at_char (format_string_loc, format_string_cst, + color_begin - orig_format_chars, + OPT_Wformat_, "unterminated color directive"); } - /* Check the argument types from a single format conversion (possibly - including width and precision arguments). */ + 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 (format_wanted_type *types) +check_format_types (const substring_loc &fmt_loc, + format_wanted_type *types, const format_kind_info *fki, + int offset_to_type_start, + char conversion_char, + vec<location_t> *arglocs) { for (; types != 0; types = types->next) { @@ -2330,7 +3074,9 @@ cur_param = types->param; if (!cur_param) { - format_type_warning (types, wanted_type, NULL); + format_type_warning (fmt_loc, UNKNOWN_LOCATION, types, wanted_type, + NULL, fki, offset_to_type_start, + conversion_char); continue; } @@ -2340,6 +3086,16 @@ orig_cur_type = cur_type; char_type_flag = 0; + location_t param_loc = UNKNOWN_LOCATION; + if (EXPR_HAS_LOCATION (cur_param)) + param_loc = EXPR_LOCATION (cur_param); + else if (arglocs) + { + /* arg_num is 1-based. */ + gcc_assert (types->arg_num > 0); + param_loc = (*arglocs)[types->arg_num - 1]; + } + STRIP_NOPS (cur_param); /* Check the types of any additional pointer arguments @@ -2357,7 +3113,7 @@ && i == 0 && cur_param != 0 && integer_zerop (cur_param)) - warning (OPT_Wformat, "writing through null pointer " + warning (OPT_Wformat_, "writing through null pointer " "(argument %d)", arg_num); /* Check for reading through a NULL pointer. */ @@ -2365,7 +3121,7 @@ && i == 0 && cur_param != 0 && integer_zerop (cur_param)) - warning (OPT_Wformat, "reading through null pointer " + warning (OPT_Wformat_, "reading through null pointer " "(argument %d)", arg_num); if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR) @@ -2385,7 +3141,7 @@ && (CONSTANT_CLASS_P (cur_param) || (DECL_P (cur_param) && TREE_READONLY (cur_param)))))) - warning (OPT_Wformat, "writing into constant object " + warning (OPT_Wformat_, "writing into constant object " "(argument %d)", arg_num); /* If there are extra type qualifiers beyond the first @@ -2395,15 +3151,18 @@ && pedantic && (TYPE_READONLY (cur_type) || TYPE_VOLATILE (cur_type) + || TYPE_ATOMIC (cur_type) || TYPE_RESTRICT (cur_type))) - warning (OPT_Wformat, "extra type qualifiers in format " + warning (OPT_Wformat_, "extra type qualifiers in format " "argument (argument %d)", arg_num); } else { - format_type_warning (types, wanted_type, orig_cur_type); + format_type_warning (fmt_loc, param_loc, + types, wanted_type, orig_cur_type, fki, + offset_to_type_start, conversion_char); break; } } @@ -2414,8 +3173,7 @@ cur_type = TYPE_MAIN_VARIANT (cur_type); /* Check whether the argument type is a character type. This leniency - only applies to certain formats, flagged with 'c'. - */ + only applies to certain formats, flagged with 'c'. */ if (types->char_lenient_flag) char_type_flag = (cur_type == char_type_node || cur_type == signed_char_type_node @@ -2426,22 +3184,39 @@ continue; /* If we want 'void *', allow any pointer type. (Anything else would already have got a warning.) - With -pedantic, only allow pointers to void and to character + With -Wpedantic, only allow pointers to void and to character types. */ if (wanted_type == void_type_node && (!pedantic || (i == 1 && char_type_flag))) continue; /* Don't warn about differences merely in signedness, unless - -pedantic. With -pedantic, warn if the type is a pointer + -Wpedantic. With -Wpedantic, warn if the type is a pointer target and not a character type, and for character types at a second level of indirection. */ if (TREE_CODE (wanted_type) == INTEGER_TYPE && TREE_CODE (cur_type) == INTEGER_TYPE - && (!pedantic || i == 0 || (i == 1 && char_type_flag)) + && ((!pedantic && !warn_format_signedness) + || (i == 0 && !warn_format_signedness) + || (i == 1 && char_type_flag)) && (TYPE_UNSIGNED (wanted_type) ? wanted_type == c_common_unsigned_type (cur_type) : wanted_type == c_common_signed_type (cur_type))) continue; + /* Don't warn about differences merely in signedness if we know + that the current type is integer-promoted and its original type + was unsigned such as that it is in the range of WANTED_TYPE. */ + if (TREE_CODE (wanted_type) == INTEGER_TYPE + && TREE_CODE (cur_type) == INTEGER_TYPE + && warn_format_signedness + && TYPE_UNSIGNED (wanted_type) + && cur_param != NULL_TREE + && TREE_CODE (cur_param) == NOP_EXPR) + { + tree t = TREE_TYPE (TREE_OPERAND (cur_param, 0)); + if (TYPE_UNSIGNED (t) + && cur_type == lang_hooks.types.type_promotes_to (t)) + continue; + } /* Likewise, "signed char", "unsigned char" and "char" are equivalent but the above test won't consider them equivalent. */ if (wanted_type == char_type_node @@ -2455,22 +3230,353 @@ && TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type)) continue; /* Now we have a type mismatch. */ - format_type_warning (types, wanted_type, orig_cur_type); + format_type_warning (fmt_loc, param_loc, types, + wanted_type, orig_cur_type, fki, + offset_to_type_start, conversion_char); } } - -/* Give a warning 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 +/* Given type TYPE, attempt to dereference the type N times + (e.g. from ("int ***", 2) to "int *") + + Return the derefenced type, with any qualifiers + such as "const" stripped from the result, or + NULL if unsuccessful (e.g. TYPE is not a pointer type). */ + +static tree +deref_n_times (tree type, int n) +{ + gcc_assert (type); + + for (int i = n; i > 0; i--) + { + if (TREE_CODE (type) != POINTER_TYPE) + return NULL_TREE; + type = TREE_TYPE (type); + } + /* Strip off any "const" etc. */ + return build_qualified_type (type, 0); +} + +/* Lookup the format code for FORMAT_LEN within FLI, + returning the string code for expressing it, or NULL + if it is not found. */ + +static const char * +get_modifier_for_format_len (const format_length_info *fli, + enum format_lengths format_len) +{ + for (; fli->name; fli++) + { + if (fli->index == format_len) + return fli->name; + if (fli->double_index == format_len) + return fli->double_name; + } + return NULL; +} + +#if CHECKING_P + +namespace selftest { + +static void +test_get_modifier_for_format_len () +{ + ASSERT_STREQ ("h", + get_modifier_for_format_len (printf_length_specs, FMT_LEN_h)); + ASSERT_STREQ ("hh", + get_modifier_for_format_len (printf_length_specs, FMT_LEN_hh)); + ASSERT_STREQ ("L", + get_modifier_for_format_len (printf_length_specs, FMT_LEN_L)); + ASSERT_EQ (NULL, + get_modifier_for_format_len (printf_length_specs, FMT_LEN_none)); +} + +} // namespace selftest + +#endif /* CHECKING_P */ + +/* 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); + + /* If any of the types requires structural equality, we can't compare + their canonical types. */ + if (TYPE_STRUCTURAL_EQUALITY_P (spec_type) + || TYPE_STRUCTURAL_EQUALITY_P (arg_type)) + return false; + + 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 caller. + Otherwise, returns NULL. */ + +static char * +get_format_for_type_1 (const format_kind_info *fki, tree arg_type, + char conversion_char) +{ + gcc_assert (arg_type); + + const format_char_info *spec; + for (spec = &fki->conversion_specs[0]; + 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) + continue; + for (int i = 0; i < FMT_LEN_MAX; i++) + { + const format_type_detail *ftd = &spec->types[i]; + if (!ftd->type) + continue; + if (matching_type_p (*ftd->type, effective_arg_type)) + { + const char *len_modifier + = get_modifier_for_format_len (fki->length_char_specs, + (enum format_lengths)i); + if (!len_modifier) + len_modifier = ""; + + 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. + PARAM_LOC is the location of the relevant argument, or UNKNOWN_LOCATION + if this is unavailable. + 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. */ + 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_LOC + type starts here + + OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the + STRING_CST. */ + static void -format_type_warning (format_wanted_type *type, tree wanted_type, tree arg_type) +format_type_warning (const substring_loc &whole_fmt_loc, + location_t param_loc, + format_wanted_type *type, + tree wanted_type, tree arg_type, + const format_kind_info *fki, + int offset_to_type_start, + char conversion_char) { - int kind = type->kind; + enum format_specifier_kind kind = type->kind; const char *wanted_type_name = type->wanted_type_name; const char *format_start = type->format_start; int format_length = type->format_length; @@ -2509,36 +3615,62 @@ p[pointer_count + 1] = 0; } + /* WHOLE_FMT_LOC has the caret at the end of the range. + Set the caret to be at the offset from TYPE. Subtract one + from the offset for the same reason as in format_warning_at_char. */ + substring_loc fmt_loc (whole_fmt_loc); + fmt_loc.set_caret_index (type->offset_loc - 1); + + /* 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) - warning (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_loc, + corrected_substring, 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 (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_loc, + corrected_substring, 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 (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_loc, + corrected_substring, 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 (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_loc, + 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 (corrected_substring); } @@ -2639,8 +3771,6 @@ static void init_dynamic_gfc_info (void) { - static tree locus; - if (!locus) { static format_char_info *gfc_fci; @@ -2689,58 +3819,81 @@ static void init_dynamic_diag_info (void) { - static tree t, loc, hwi; - - if (!loc || !t || !hwi) + /* For the GCC-diagnostics custom format specifiers to work, one + must have declared 'tree' and 'location_t' prior to using those + attributes. If we haven't seen these declarations then + the specifiers requiring these types shouldn't be used. + However we don't force a hard ICE because we may see only one + or the other type. */ + if (tree loc = maybe_get_identifier ("location_t")) + { + loc = identifier_global_value (loc); + if (loc && TREE_CODE (loc) != TYPE_DECL) + error ("%<location_t%> is not defined as a type"); + } + + /* Initialize the global tree node type local to this file. */ + if (!local_tree_type_node + || local_tree_type_node == void_type_node) { - static format_char_info *diag_fci, *tdiag_fci, *cdiag_fci, *cxxdiag_fci; - static format_length_info *diag_ls; - unsigned int i; - - /* For the GCC-diagnostics custom format specifiers to work, one - must have declared 'tree' and/or 'location_t' prior to using - those attributes. If we haven't seen these declarations then - you shouldn't use the specifiers requiring these types. - However we don't force a hard ICE because we may see only one - or the other type. */ - if ((loc = maybe_get_identifier ("location_t"))) + /* We need to grab the underlying 'union tree_node' so peek into + an extra type level. */ + if ((local_tree_type_node = maybe_get_identifier ("tree"))) { - loc = identifier_global_value (loc); - if (loc) + local_tree_type_node = identifier_global_value (local_tree_type_node); + if (local_tree_type_node) { - if (TREE_CODE (loc) != TYPE_DECL) + if (TREE_CODE (local_tree_type_node) != TYPE_DECL) { - error ("%<location_t%> is not defined as a type"); - loc = 0; + error ("%<tree%> is not defined as a type"); + local_tree_type_node = 0; + } + else if (TREE_CODE (TREE_TYPE (local_tree_type_node)) + != POINTER_TYPE) + { + error ("%<tree%> is not defined as a pointer type"); + local_tree_type_node = 0; } else - loc = TREE_TYPE (loc); + local_tree_type_node = + TREE_TYPE (TREE_TYPE (local_tree_type_node)); } } - - /* We need to grab the underlying 'union tree_node' so peek into - an extra type level. */ - if ((t = maybe_get_identifier ("tree"))) + else + local_tree_type_node = void_type_node; + } + + /* Similar to the above but for gcall*. */ + if (!local_gcall_ptr_node + || local_gcall_ptr_node == void_type_node) + { + if ((local_gcall_ptr_node = maybe_get_identifier ("gcall"))) { - t = identifier_global_value (t); - if (t) + local_gcall_ptr_node + = identifier_global_value (local_gcall_ptr_node); + if (local_gcall_ptr_node) { - if (TREE_CODE (t) != TYPE_DECL) + if (TREE_CODE (local_gcall_ptr_node) != TYPE_DECL) { - error ("%<tree%> is not defined as a type"); - t = 0; - } - else if (TREE_CODE (TREE_TYPE (t)) != POINTER_TYPE) - { - error ("%<tree%> is not defined as a pointer type"); - t = 0; + error ("%<gcall%> is not defined as a type"); + local_gcall_ptr_node = 0; } else - t = TREE_TYPE (TREE_TYPE (t)); + local_gcall_ptr_node = TREE_TYPE (local_gcall_ptr_node); } } - - /* Find the underlying type for HOST_WIDE_INT. For the %w + else + local_gcall_ptr_node = void_type_node; + } + + static tree hwi; + + if (!hwi) + { + static format_length_info *diag_ls; + unsigned int i; + + /* Find the underlying type for HOST_WIDE_INT. For the 'w' length modifier to work, one must have issued: "typedef HOST_WIDE_INT __gcc_host_wide_int__;" in one's source code prior to using that modifier. */ @@ -2792,75 +3945,17 @@ else gcc_unreachable (); } - - /* Handle the __gcc_diag__ format specifics. */ - if (!diag_fci) - dynamic_format_types[gcc_diag_format_type].conversion_specs = - diag_fci = (format_char_info *) - xmemdup (gcc_diag_char_table, - sizeof (gcc_diag_char_table), - sizeof (gcc_diag_char_table)); - if (t) - { - i = find_char_info_specifier_index (diag_fci, 'K'); - diag_fci[i].types[0].type = &t; - diag_fci[i].pointer_count = 1; - } - - /* Handle the __gcc_tdiag__ format specifics. */ - if (!tdiag_fci) - dynamic_format_types[gcc_tdiag_format_type].conversion_specs = - tdiag_fci = (format_char_info *) - xmemdup (gcc_tdiag_char_table, - sizeof (gcc_tdiag_char_table), - sizeof (gcc_tdiag_char_table)); - if (t) - { - /* All specifiers taking a tree share the same struct. */ - i = find_char_info_specifier_index (tdiag_fci, 'D'); - tdiag_fci[i].types[0].type = &t; - tdiag_fci[i].pointer_count = 1; - i = find_char_info_specifier_index (tdiag_fci, 'K'); - tdiag_fci[i].types[0].type = &t; - tdiag_fci[i].pointer_count = 1; - } - - /* Handle the __gcc_cdiag__ format specifics. */ - if (!cdiag_fci) - dynamic_format_types[gcc_cdiag_format_type].conversion_specs = - cdiag_fci = (format_char_info *) - xmemdup (gcc_cdiag_char_table, - sizeof (gcc_cdiag_char_table), - sizeof (gcc_cdiag_char_table)); - if (t) - { - /* All specifiers taking a tree share the same struct. */ - i = find_char_info_specifier_index (cdiag_fci, 'D'); - cdiag_fci[i].types[0].type = &t; - cdiag_fci[i].pointer_count = 1; - i = find_char_info_specifier_index (cdiag_fci, 'K'); - cdiag_fci[i].types[0].type = &t; - cdiag_fci[i].pointer_count = 1; - } - - /* Handle the __gcc_cxxdiag__ format specifics. */ - if (!cxxdiag_fci) - dynamic_format_types[gcc_cxxdiag_format_type].conversion_specs = - cxxdiag_fci = (format_char_info *) - xmemdup (gcc_cxxdiag_char_table, - sizeof (gcc_cxxdiag_char_table), - sizeof (gcc_cxxdiag_char_table)); - if (t) - { - /* All specifiers taking a tree share the same struct. */ - i = find_char_info_specifier_index (cxxdiag_fci, 'D'); - cxxdiag_fci[i].types[0].type = &t; - cxxdiag_fci[i].pointer_count = 1; - i = find_char_info_specifier_index (cxxdiag_fci, 'K'); - cxxdiag_fci[i].types[0].type = &t; - cxxdiag_fci[i].pointer_count = 1; - } } + + /* It's safe to "re-initialize these to the same values. */ + dynamic_format_types[gcc_diag_format_type].conversion_specs = + gcc_diag_char_table; + dynamic_format_types[gcc_tdiag_format_type].conversion_specs = + gcc_tdiag_char_table; + dynamic_format_types[gcc_cdiag_format_type].conversion_specs = + gcc_cdiag_char_table; + dynamic_format_types[gcc_cxxdiag_format_type].conversion_specs = + gcc_cxxdiag_char_table; } #ifdef TARGET_FORMAT_TYPES @@ -2933,24 +4028,6 @@ return attr_name; } -/* Return true if TATTR_NAME and ATTR_NAME are the same format attribute, - counting "name" and "__name__" as the same, false otherwise. */ -static bool -cmp_attribs (const char *tattr_name, const char *attr_name) -{ - int alen = strlen (attr_name); - int slen = (tattr_name ? strlen (tattr_name) : 0); - if (alen > 4 && attr_name[0] == '_' && attr_name[1] == '_' - && attr_name[alen - 1] == '_' && attr_name[alen - 2] == '_') - { - attr_name += 2; - alen -= 4; - } - if (alen != slen || strncmp (tattr_name, attr_name, alen) != 0) - return false; - return true; -} - /* Handle a "format" attribute; arguments as in struct attribute_spec.handler. */ tree @@ -2959,7 +4036,6 @@ { tree type = *node; function_format_info info; - tree argument; #ifdef TARGET_FORMAT_TYPES /* If the target provides additional format types, we need to @@ -2980,27 +4056,32 @@ } #endif + /* Canonicalize name of format function. */ + if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE) + TREE_VALUE (args) = canonicalize_attr_name (TREE_VALUE (args)); + if (!decode_format_attr (args, &info, 0)) { *no_add_attrs = true; return NULL_TREE; } - argument = TYPE_ARG_TYPES (type); - if (argument) + if (prototype_p (type)) { - if (!check_format_string (argument, info.format_num, flags, + if (!check_format_string (type, info.format_num, flags, no_add_attrs, info.format_type)) return NULL_TREE; if (info.first_arg_num != 0) { unsigned HOST_WIDE_INT arg_num = 1; + function_args_iterator iter; + tree arg_type; /* Verify that first_arg_num points to the last arg, the ... */ - while (argument) - arg_num++, argument = TREE_CHAIN (argument); + FOREACH_FUNCTION_ARGS (type, arg_type, iter) + arg_num++; if (arg_num != info.first_arg_num) { @@ -3059,3 +4140,120 @@ return NULL_TREE; } + +#if CHECKING_P + +namespace selftest { + +/* Selftests of location handling. */ + +/* Get the format_kind_info with the given name. */ + +static const format_kind_info * +get_info (const char *name) +{ + int idx = decode_format_type (name); + const format_kind_info *fki = &format_types[idx]; + ASSERT_STREQ (fki->name, name); + return fki; +} + +/* 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, + char conversion_char) +{ + gcc_assert (fki); + gcc_assert (expected_format); + gcc_assert (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, 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. */ + +static void +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, '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. */ + +static void +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), '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), 'd'); + ASSERT_FORMAT_FOR_TYPE_STREQ + ("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 + +/* Run all of the selftests within this file. */ + +void +c_format_c_tests () +{ + test_get_modifier_for_format_len (); + test_get_format_for_type_printf (); + test_get_format_for_type_scanf (); +} + +} // namespace selftest + +#endif /* CHECKING_P */ + +#include "gt-c-family-c-format.h"