diff options
Diffstat (limited to 'gcc/c-format.c')
-rw-r--r-- | gcc/c-format.c | 450 |
1 files changed, 152 insertions, 298 deletions
diff --git a/gcc/c-format.c b/gcc/c-format.c index e25c29e2a06..ea6ef57fa6d 100644 --- a/gcc/c-format.c +++ b/gcc/c-format.c @@ -77,10 +77,16 @@ enum format_type { printf_format_type, scanf_format_type, strftime_format_type, strfmon_format_type, format_type_error }; +typedef struct function_format_info +{ + enum format_type 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; + +static bool decode_format_attr PARAMS ((tree, + function_format_info *, int)); static enum format_type decode_format_type PARAMS ((const char *)); -static void record_function_format PARAMS ((tree, tree, enum format_type, - int, int)); -static void record_international_format PARAMS ((tree, tree, int)); /* Handle a "format" attribute; arguments as in struct attribute_spec.handler. */ @@ -89,75 +95,16 @@ handle_format_attribute (node, name, args, flags, no_add_attrs) tree *node; tree name ATTRIBUTE_UNUSED; tree args; - int flags ATTRIBUTE_UNUSED; + int flags; bool *no_add_attrs; { - tree decl = *node; - tree type = TREE_TYPE (decl); - tree format_type_id = TREE_VALUE (args); - tree format_num_expr = TREE_VALUE (TREE_CHAIN (args)); - tree first_arg_num_expr - = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args))); - unsigned HOST_WIDE_INT format_num, first_arg_num; - enum format_type format_type; + tree type = *node; + function_format_info info; tree argument; - unsigned int arg_num; + unsigned HOST_WIDE_INT arg_num; - if (TREE_CODE (decl) != FUNCTION_DECL) + if (!decode_format_attr (args, &info, 0)) { - error_with_decl (decl, - "argument format specified for non-function `%s'"); - *no_add_attrs = true; - return NULL_TREE; - } - - if (TREE_CODE (format_type_id) != IDENTIFIER_NODE) - { - error ("unrecognized format specifier"); - *no_add_attrs = true; - return NULL_TREE; - } - else - { - const char *p = IDENTIFIER_POINTER (format_type_id); - - format_type = decode_format_type (p); - - if (format_type == format_type_error) - { - warning ("`%s' is an unrecognized format function type", p); - *no_add_attrs = true; - return NULL_TREE; - } - } - - /* Strip any conversions from the string index and first arg number - and verify they are constants. */ - while (TREE_CODE (format_num_expr) == NOP_EXPR - || TREE_CODE (format_num_expr) == CONVERT_EXPR - || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR) - format_num_expr = TREE_OPERAND (format_num_expr, 0); - - while (TREE_CODE (first_arg_num_expr) == NOP_EXPR - || TREE_CODE (first_arg_num_expr) == CONVERT_EXPR - || TREE_CODE (first_arg_num_expr) == NON_LVALUE_EXPR) - first_arg_num_expr = TREE_OPERAND (first_arg_num_expr, 0); - - if (TREE_CODE (format_num_expr) != INTEGER_CST - || TREE_INT_CST_HIGH (format_num_expr) != 0 - || TREE_CODE (first_arg_num_expr) != INTEGER_CST - || TREE_INT_CST_HIGH (first_arg_num_expr) != 0) - { - error ("format string has invalid operand number"); - *no_add_attrs = true; - return NULL_TREE; - } - - format_num = TREE_INT_CST_LOW (format_num_expr); - first_arg_num = TREE_INT_CST_LOW (first_arg_num_expr); - if (first_arg_num != 0 && first_arg_num <= format_num) - { - error ("format string arg follows the args to be formatted"); *no_add_attrs = true; return NULL_TREE; } @@ -168,7 +115,7 @@ handle_format_attribute (node, name, args, flags, no_add_attrs) argument = TYPE_ARG_TYPES (type); if (argument) { - for (arg_num = 1; argument != 0 && arg_num != format_num; + for (arg_num = 1; argument != 0 && arg_num != info.format_num; ++arg_num, argument = TREE_CHAIN (argument)) ; @@ -177,65 +124,56 @@ handle_format_attribute (node, name, args, flags, no_add_attrs) || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument))) != char_type_node)) { - error ("format string arg not a string type"); + if (!(flags & (int) ATTR_FLAG_BUILT_IN)) + error ("format string arg not a string type"); *no_add_attrs = true; return NULL_TREE; } - else if (first_arg_num != 0) + else if (info.first_arg_num != 0) { /* Verify that first_arg_num points to the last arg, the ... */ while (argument) arg_num++, argument = TREE_CHAIN (argument); - if (arg_num != first_arg_num) + if (arg_num != info.first_arg_num) { - error ("args to be formatted is not '...'"); + if (!(flags & (int) ATTR_FLAG_BUILT_IN)) + error ("args to be formatted is not '...'"); *no_add_attrs = true; return NULL_TREE; } } } - if (format_type == strftime_format_type && first_arg_num != 0) + if (info.format_type == strftime_format_type && info.first_arg_num != 0) { error ("strftime formats cannot format arguments"); *no_add_attrs = true; return NULL_TREE; } - record_function_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl), - format_type, format_num, first_arg_num); return NULL_TREE; } -/* Handle a "format" attribute; arguments as in +/* Handle a "format_arg" attribute; arguments as in struct attribute_spec.handler. */ tree handle_format_arg_attribute (node, name, args, flags, no_add_attrs) tree *node; tree name ATTRIBUTE_UNUSED; tree args; - int flags ATTRIBUTE_UNUSED; + int flags; bool *no_add_attrs; { - tree decl = *node; - tree type = TREE_TYPE (decl); + tree type = *node; tree format_num_expr = TREE_VALUE (args); unsigned HOST_WIDE_INT format_num; - unsigned int arg_num; + unsigned HOST_WIDE_INT arg_num; tree argument; - if (TREE_CODE (decl) != FUNCTION_DECL) - { - error_with_decl (decl, - "argument format specified for non-function `%s'"); - *no_add_attrs = true; - return NULL_TREE; - } - /* Strip any conversions from the first arg number and verify it is a constant. */ while (TREE_CODE (format_num_expr) == NOP_EXPR @@ -268,203 +206,102 @@ handle_format_arg_attribute (node, name, args, flags, no_add_attrs) || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument))) != char_type_node)) { - error ("format string arg not a string type"); + if (!(flags & (int) ATTR_FLAG_BUILT_IN)) + error ("format string arg not a string type"); *no_add_attrs = true; return NULL_TREE; } } - if (TREE_CODE (TREE_TYPE (TREE_TYPE (decl))) != POINTER_TYPE - || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (TREE_TYPE (decl)))) + if (TREE_CODE (TREE_TYPE (type)) != POINTER_TYPE + || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (type))) != char_type_node)) { - error ("function does not return string type"); + if (!(flags & (int) ATTR_FLAG_BUILT_IN)) + error ("function does not return string type"); *no_add_attrs = true; return NULL_TREE; } - record_international_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl), - format_num); return NULL_TREE; } -typedef struct function_format_info -{ - struct function_format_info *next; /* next structure on the list */ - tree name; /* identifier such as "printf" */ - tree assembler_name; /* optional mangled identifier (for C++) */ - enum format_type format_type; /* type of format (printf, scanf, etc.) */ - int format_num; /* number of format argument */ - int first_arg_num; /* number of first arg (zero for varargs) */ -} function_format_info; -static function_format_info *function_format_list = NULL; +/* Decode the arguments to a "format" attribute into a function_format_info + structure. It is already known that the list is of the right length. + If VALIDATED_P is true, then these attributes have already been validated + and this function will abort if they are erroneous; if false, it + will give an error message. Returns true if the attributes are + successfully decoded, false otherwise. */ -typedef struct international_format_info -{ - struct international_format_info *next; /* next structure on the list */ - tree name; /* identifier such as "gettext" */ - tree assembler_name; /* optional mangled identifier (for C++) */ - int format_num; /* number of format argument */ -} international_format_info; - -static international_format_info *international_format_list = NULL; - -/* Initialize the table of functions to perform format checking on. - The ISO C functions are always checked (whether <stdio.h> is - included or not), since it is common to call printf without - including <stdio.h>. There shouldn't be a problem with this, - since ISO C reserves these function names whether you include the - header file or not. In any case, the checking is harmless. With - -ffreestanding, these default attributes are disabled, and must be - specified manually if desired. - - Also initialize the name of function that modify the format string for - internationalization purposes. */ - -void -init_function_format_info () +static bool +decode_format_attr (args, info, validated_p) + tree args; + function_format_info *info; + int validated_p; { - /* __builtin functions should be checked unconditionally, even with - -ffreestanding. */ - record_function_format (get_identifier ("__builtin_printf"), NULL_TREE, - printf_format_type, 1, 2); - record_function_format (get_identifier ("__builtin_fprintf"), NULL_TREE, - printf_format_type, 2, 3); - - if (flag_hosted) - { - /* Functions from ISO/IEC 9899:1990. */ - record_function_format (get_identifier ("printf"), NULL_TREE, - printf_format_type, 1, 2); - record_function_format (get_identifier ("fprintf"), NULL_TREE, - printf_format_type, 2, 3); - record_function_format (get_identifier ("sprintf"), NULL_TREE, - printf_format_type, 2, 3); - record_function_format (get_identifier ("scanf"), NULL_TREE, - scanf_format_type, 1, 2); - record_function_format (get_identifier ("fscanf"), NULL_TREE, - scanf_format_type, 2, 3); - record_function_format (get_identifier ("sscanf"), NULL_TREE, - scanf_format_type, 2, 3); - record_function_format (get_identifier ("vprintf"), NULL_TREE, - printf_format_type, 1, 0); - record_function_format (get_identifier ("vfprintf"), NULL_TREE, - printf_format_type, 2, 0); - record_function_format (get_identifier ("vsprintf"), NULL_TREE, - printf_format_type, 2, 0); - record_function_format (get_identifier ("strftime"), NULL_TREE, - strftime_format_type, 3, 0); - } + tree format_type_id = TREE_VALUE (args); + tree format_num_expr = TREE_VALUE (TREE_CHAIN (args)); + tree first_arg_num_expr + = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args))); - if (flag_hosted && (flag_isoc99 || flag_noniso_default_format_attributes)) + if (TREE_CODE (format_type_id) != IDENTIFIER_NODE) { - /* ISO C99 adds the snprintf and vscanf family functions. */ - record_function_format (get_identifier ("snprintf"), NULL_TREE, - printf_format_type, 3, 4); - record_function_format (get_identifier ("vsnprintf"), NULL_TREE, - printf_format_type, 3, 0); - record_function_format (get_identifier ("vscanf"), NULL_TREE, - scanf_format_type, 1, 0); - record_function_format (get_identifier ("vfscanf"), NULL_TREE, - scanf_format_type, 2, 0); - record_function_format (get_identifier ("vsscanf"), NULL_TREE, - scanf_format_type, 2, 0); + if (validated_p) + abort (); + error ("unrecognized format specifier"); + return false; } - - if (flag_hosted && flag_noniso_default_format_attributes) + else { - /* Uniforum/GNU gettext functions, not in ISO C. */ - record_international_format (get_identifier ("gettext"), NULL_TREE, 1); - record_international_format (get_identifier ("dgettext"), NULL_TREE, 2); - record_international_format (get_identifier ("dcgettext"), NULL_TREE, 2); - /* X/Open strfmon function. */ - record_function_format (get_identifier ("strfmon"), NULL_TREE, - strfmon_format_type, 3, 4); - } -} - -/* Record information for argument format checking. FUNCTION_IDENT is - the identifier node for the name of the function to check (its decl - need not exist yet). - FORMAT_TYPE specifies the type of format checking. FORMAT_NUM is the number - of the argument which is the format control string (starting from 1). - FIRST_ARG_NUM is the number of the first actual argument to check - against the format string, or zero if no checking is not be done - (e.g. for varargs such as vfprintf). */ - -static void -record_function_format (name, assembler_name, format_type, - format_num, first_arg_num) - tree name; - tree assembler_name; - enum format_type format_type; - int format_num; - int first_arg_num; -{ - function_format_info *info; - - /* Re-use existing structure if it's there. */ + const char *p = IDENTIFIER_POINTER (format_type_id); - for (info = function_format_list; info; info = info->next) - { - if (info->name == name && info->assembler_name == assembler_name) - break; - } - if (! info) - { - info = (function_format_info *) xmalloc (sizeof (function_format_info)); - info->next = function_format_list; - function_format_list = info; + info->format_type = decode_format_type (p); - info->name = name; - info->assembler_name = assembler_name; + if (info->format_type == format_type_error) + { + if (validated_p) + abort (); + warning ("`%s' is an unrecognized format function type", p); + return false; + } } - info->format_type = format_type; - info->format_num = format_num; - info->first_arg_num = first_arg_num; -} - -/* Record information for the names of function that modify the format - argument to format functions. FUNCTION_IDENT is the identifier node for - the name of the function (its decl need not exist yet) and FORMAT_NUM is - the number of the argument which is the format control string (starting - from 1). */ - -static void -record_international_format (name, assembler_name, format_num) - tree name; - tree assembler_name; - int format_num; -{ - international_format_info *info; + /* Strip any conversions from the string index and first arg number + and verify they are constants. */ + while (TREE_CODE (format_num_expr) == NOP_EXPR + || TREE_CODE (format_num_expr) == CONVERT_EXPR + || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR) + format_num_expr = TREE_OPERAND (format_num_expr, 0); - /* Re-use existing structure if it's there. */ + while (TREE_CODE (first_arg_num_expr) == NOP_EXPR + || TREE_CODE (first_arg_num_expr) == CONVERT_EXPR + || TREE_CODE (first_arg_num_expr) == NON_LVALUE_EXPR) + first_arg_num_expr = TREE_OPERAND (first_arg_num_expr, 0); - for (info = international_format_list; info; info = info->next) + if (TREE_CODE (format_num_expr) != INTEGER_CST + || TREE_INT_CST_HIGH (format_num_expr) != 0 + || TREE_CODE (first_arg_num_expr) != INTEGER_CST + || TREE_INT_CST_HIGH (first_arg_num_expr) != 0) { - if (info->name == name && info->assembler_name == assembler_name) - break; + if (validated_p) + abort (); + error ("format string has invalid operand number"); + return false; } - if (! info) + info->format_num = TREE_INT_CST_LOW (format_num_expr); + info->first_arg_num = TREE_INT_CST_LOW (first_arg_num_expr); + if (info->first_arg_num != 0 && info->first_arg_num <= info->format_num) { - info - = (international_format_info *) - xmalloc (sizeof (international_format_info)); - info->next = international_format_list; - international_format_list = info; - - info->name = name; - info->assembler_name = assembler_name; + if (validated_p) + abort (); + error ("format string arg follows the args to be formatted"); + return false; } - info->format_num = format_num; + return true; } - - - /* Check a call to a format function against a parameter list. */ @@ -497,7 +334,7 @@ enum format_std_version /* 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_C89 +#define CPLUSPLUS_STD_VER STD_C94 /* The C standard version we are checking formats against when pedantic. */ #define C_STD_VER ((int)(c_language == clk_cplusplus \ ? CPLUSPLUS_STD_VER \ @@ -1058,10 +895,11 @@ typedef struct static void check_format_info PARAMS ((int *, function_format_info *, tree)); static void check_format_info_recurse PARAMS ((int *, format_check_results *, function_format_info *, tree, - tree, int)); + tree, unsigned HOST_WIDE_INT)); static void check_format_info_main PARAMS ((int *, format_check_results *, function_format_info *, - const char *, int, tree, int)); + const char *, int, tree, + unsigned HOST_WIDE_INT)); static void status_warning PARAMS ((int *, const char *, ...)) ATTRIBUTE_PRINTF_2; @@ -1102,43 +940,42 @@ decode_format_type (s) /* Check the argument list of a call to printf, scanf, etc. - NAME is the function identifier. - ASSEMBLER_NAME is the function's assembler identifier. - (Either NAME or ASSEMBLER_NAME, but not both, may be NULL_TREE.) + ATTRS are the attributes on the function type. PARAMS is the list of argument values. Also, if -Wmissing-format-attribute, warn for calls to vprintf or vscanf in functions with no such format attribute themselves. */ void -check_function_format (status, name, assembler_name, params) +check_function_format (status, attrs, params) int *status; - tree name; - tree assembler_name; + tree attrs; tree params; { - function_format_info *info; + tree a; - /* See if this function is a format function. */ - for (info = function_format_list; info; info = info->next) + /* See if this function has any format attributes. */ + for (a = attrs; a; a = TREE_CHAIN (a)) { - if (info->assembler_name - ? (info->assembler_name == assembler_name) - : (info->name == name)) + if (is_attribute_p ("format", TREE_PURPOSE (a))) { /* Yup; check it. */ - check_format_info (status, info, params); - if (warn_missing_format_attribute && info->first_arg_num == 0 - && (format_types[info->format_type].flags + function_format_info info; + decode_format_attr (TREE_VALUE (a), &info, 1); + check_format_info (status, &info, params); + if (warn_missing_format_attribute && info.first_arg_num == 0 + && (format_types[info.format_type].flags & (int) FMT_FLAG_ARG_CONVERT)) { - function_format_info *info2; - for (info2 = function_format_list; info2; info2 = info2->next) - if ((info2->assembler_name - ? (info2->assembler_name == DECL_ASSEMBLER_NAME (current_function_decl)) - : (info2->name == DECL_NAME (current_function_decl))) - && info2->format_type == info->format_type) + tree c; + for (c = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); + c; + c = TREE_CHAIN (c)) + if (is_attribute_p ("format", TREE_PURPOSE (c)) + && (decode_format_type (IDENTIFIER_POINTER + (TREE_VALUE (TREE_VALUE (c)))) + == info.format_type)) break; - if (info2 == NULL) + if (c == NULL_TREE) { /* Check if the current function has a parameter to which the format attribute could be attached; if not, it @@ -1156,10 +993,9 @@ check_function_format (status, name, assembler_name, params) } if (args != 0) warning ("function might be possible candidate for `%s' format attribute", - format_types[info->format_type].name); + format_types[info.format_type].name); } } - break; } } } @@ -1417,7 +1253,7 @@ check_format_info (status, info, params) function_format_info *info; tree params; { - int arg_num; + unsigned HOST_WIDE_INT arg_num; tree format_tree; format_check_results res; /* Skip to format argument. If the argument isn't available, there's @@ -1512,7 +1348,7 @@ check_format_info_recurse (status, res, info, format_tree, params, arg_num) function_format_info *info; tree format_tree; tree params; - int arg_num; + unsigned HOST_WIDE_INT arg_num; { int format_length; HOST_WIDE_INT offset; @@ -1529,40 +1365,58 @@ check_format_info_recurse (status, res, info, format_tree, params, arg_num) return; } - if (TREE_CODE (format_tree) == CALL_EXPR - && TREE_CODE (TREE_OPERAND (format_tree, 0)) == ADDR_EXPR - && (TREE_CODE (TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0)) - == FUNCTION_DECL)) + if (TREE_CODE (format_tree) == CALL_EXPR) { - tree function = TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0); + tree type = TREE_TYPE (TREE_TYPE (TREE_OPERAND (format_tree, 0))); + tree attrs; + bool found_format_arg = false; /* See if this is a call to a known internationalization function - that modifies the format arg. */ - international_format_info *iinfo; + that modifies the format arg. Such a function may have multiple + format_arg attributes (for example, ngettext). */ - for (iinfo = international_format_list; iinfo; iinfo = iinfo->next) - if (iinfo->assembler_name - ? (iinfo->assembler_name == DECL_ASSEMBLER_NAME (function)) - : (iinfo->name == DECL_NAME (function))) + for (attrs = TYPE_ATTRIBUTES (type); + attrs; + attrs = TREE_CHAIN (attrs)) + if (is_attribute_p ("format_arg", TREE_PURPOSE (attrs))) { tree inner_args; + tree format_num_expr; + int format_num; int i; + /* Extract the argument number, which was previously checked + to be valid. */ + format_num_expr = TREE_VALUE (TREE_VALUE (attrs)); + while (TREE_CODE (format_num_expr) == NOP_EXPR + || TREE_CODE (format_num_expr) == CONVERT_EXPR + || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR) + format_num_expr = TREE_OPERAND (format_num_expr, 0); + + if (TREE_CODE (format_num_expr) != INTEGER_CST + || TREE_INT_CST_HIGH (format_num_expr) != 0) + abort (); + + format_num = TREE_INT_CST_LOW (format_num_expr); + for (inner_args = TREE_OPERAND (format_tree, 1), i = 1; inner_args != 0; inner_args = TREE_CHAIN (inner_args), i++) - if (i == iinfo->format_num) + if (i == format_num) { - /* FIXME: with Marc Espie's __attribute__((nonnull)) - patch in GCC, we will have chained attributes, - and be able to handle functions like ngettext - with multiple format_arg attributes properly. */ check_format_info_recurse (status, res, info, TREE_VALUE (inner_args), params, arg_num); - return; + found_format_arg = true; + break; } } + + /* If we found a format_arg attribute and did a recursive check, + we are done with checking this format string. Otherwise, we + continue and this will count as a non-literal format string. */ + if (found_format_arg) + return; } if (TREE_CODE (format_tree) == COND_EXPR) @@ -1736,7 +1590,7 @@ check_format_info_main (status, res, info, format_chars, format_length, const char *format_chars; int format_length; tree params; - int arg_num; + unsigned HOST_WIDE_INT arg_num; { const char *orig_format_chars = format_chars; tree first_fillin_param = params; |