From: Paul Eggert Date: Sun, 12 Apr 1998 19:52:16 +0000 (+0000) Subject: This change is from an idea suggested by Arthur David Olson. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=bb72a0843b71be335863c0a177c612b5ffcb2b2e;p=gcc.git This change is from an idea suggested by Arthur David Olson. * c-common.c (decl_attributes, record_function_format, check_format_info, init_function_format_info): Add support for strftime format checking. (enum format_type): New type. (record_function_format): Now static, and takes value of type enum format_type instead of int. (time_char_table): New constant. (struct function_format_info): format_type member renamed from is_scan. (check_format_info): Use `warning' rather than sprintf followed by `warning', to avoid mishandling `%' in warnings. Change `pedwarn' to `warning', since these warnings do not necessarily mean the program does not conform to the C Standard, as the code need not be executed. * c-tree.h (record_function_format): Remove decl; no longer extern. * extend.texi: Add documentation for strftime format checking. From-SVN: r19151 --- diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 44bd2dd4e69..b63fcfcdff7 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,25 @@ +1998-04-12 Paul Eggert + + This change is from an idea suggested by Arthur David Olson. + + * c-common.c (decl_attributes, record_function_format, + check_format_info, init_function_format_info): + Add support for strftime format checking. + (enum format_type): New type. + (record_function_format): Now static, and takes value of type + enum format_type instead of int. + (time_char_table): New constant. + (struct function_format_info): format_type member renamed from is_scan. + (check_format_info): Use `warning' rather than sprintf followed by + `warning', to avoid mishandling `%' in warnings. + Change `pedwarn' to `warning', since these warnings do not necessarily + mean the program does not conform to the C Standard, as the code + need not be executed. + + * c-tree.h (record_function_format): Remove decl; no longer extern. + + * extend.texi: Add documentation for strftime format checking. + Sun Apr 12 20:23:03 1998 Jeffrey A Law (law@cygnus.com) * mips/ecoffl.h: Do not include mips.h. diff --git a/gcc/c-common.c b/gcc/c-common.c index 2422741ccee..c84e1957c33 100644 --- a/gcc/c-common.c +++ b/gcc/c-common.c @@ -44,10 +44,15 @@ enum attrs {A_PACKED, A_NOCOMMON, A_COMMON, A_NORETURN, A_CONST, A_T_UNION, A_CONSTRUCTOR, A_DESTRUCTOR, A_MODE, A_SECTION, A_ALIGNED, A_UNUSED, A_FORMAT, A_FORMAT_ARG, A_WEAK, A_ALIAS}; +enum format_type { printf_format_type, scanf_format_type, + strftime_format_type }; + static void declare_hidden_char_array PROTO((char *, char *)); static void add_attribute PROTO((enum attrs, char *, int, int, int)); static void init_attributes PROTO((void)); +static void record_function_format PROTO((tree, tree, enum format_type, + int, int)); static void record_international_format PROTO((tree, tree, int)); /* Keep a stack of if statements. We record the number of compound @@ -649,13 +654,13 @@ decl_attributes (node, attributes, prefix_attributes) case A_FORMAT: { - tree format_type = TREE_VALUE (args); + 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))); int format_num; int first_arg_num; - int is_scan; + enum format_type format_type; tree argument; int arg_num; @@ -666,26 +671,27 @@ decl_attributes (node, attributes, prefix_attributes) continue; } - if (TREE_CODE (format_type) == IDENTIFIER_NODE - && (!strcmp (IDENTIFIER_POINTER (format_type), "printf") - || !strcmp (IDENTIFIER_POINTER (format_type), - "__printf__"))) - is_scan = 0; - else if (TREE_CODE (format_type) == IDENTIFIER_NODE - && (!strcmp (IDENTIFIER_POINTER (format_type), "scanf") - || !strcmp (IDENTIFIER_POINTER (format_type), - "__scanf__"))) - is_scan = 1; - else if (TREE_CODE (format_type) == IDENTIFIER_NODE) + if (TREE_CODE (format_type_id) != IDENTIFIER_NODE) { - warning ("`%s' is an unrecognized format function type", - IDENTIFIER_POINTER (format_type)); + error ("unrecognized format specifier"); continue; } else { - error ("unrecognized format specifier"); - continue; + char *p = IDENTIFIER_POINTER (format_type_id); + + if (!strcmp (p, "printf") || !strcmp (p, "__printf__")) + format_type = printf_format_type; + else if (!strcmp (p, "scanf") || !strcmp (p, "__scanf__")) + format_type = scanf_format_type; + else if (!strcmp (p, "strftime") + || !strcmp (p, "__strftime__")) + format_type = strftime_format_type; + else + { + error ("`%s' is an unrecognized format function type", p); + continue; + } } /* Strip any conversions from the string index and first arg number @@ -751,7 +757,7 @@ decl_attributes (node, attributes, prefix_attributes) record_function_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl), - is_scan, format_num, first_arg_num); + format_type, format_num, first_arg_num); break; } @@ -1011,12 +1017,37 @@ static format_char_info scan_char_table[] = { { NULL } }; +/* Handle format characters recognized by glibc's strftime.c. + '2' - MUST do years as only two digits + '3' - MAY do years as only two digits (depending on locale) + 'E' - E modifier is acceptable + 'O' - O modifier is acceptable to Standard C + 'o' - O modifier is acceptable as a GNU extension + 'G' - other GNU extensions */ + +static format_char_info time_char_table[] = { + { "y", 0, NULL, NULL, NULL, NULL, NULL, NULL, "2EO-_0w" }, + { "D", 0, NULL, NULL, NULL, NULL, NULL, NULL, "2" }, + { "g", 0, NULL, NULL, NULL, NULL, NULL, NULL, "2O-_0w" }, + { "cx", 0, NULL, NULL, NULL, NULL, NULL, NULL, "3E" }, + { "%RTXnrt", 0, NULL, NULL, NULL, NULL, NULL, NULL, "" }, + { "P", 0, NULL, NULL, NULL, NULL, NULL, NULL, "G" }, + { "HIMSUWdemw", 0, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Ow" }, + { "Vju", 0, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Oow" }, + { "Gklsz", 0, NULL, NULL, NULL, NULL, NULL, NULL, "-_0OGw" }, + { "ABZa", 0, NULL, NULL, NULL, NULL, NULL, NULL, "^#" }, + { "p", 0, NULL, NULL, NULL, NULL, NULL, NULL, "#" }, + { "bh", 0, NULL, NULL, NULL, NULL, NULL, NULL, "^" }, + { "CY", 0, NULL, NULL, NULL, NULL, NULL, NULL, "-_0EOw" }, + { NULL } +}; + 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++) */ - int is_scan; /* TRUE if *scanf */ + 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; @@ -1048,15 +1079,26 @@ static void check_format_info PROTO((function_format_info *, tree)); void init_function_format_info () { - record_function_format (get_identifier ("printf"), NULL_TREE, 0, 1, 2); - record_function_format (get_identifier ("fprintf"), NULL_TREE, 0, 2, 3); - record_function_format (get_identifier ("sprintf"), NULL_TREE, 0, 2, 3); - record_function_format (get_identifier ("scanf"), NULL_TREE, 1, 1, 2); - record_function_format (get_identifier ("fscanf"), NULL_TREE, 1, 2, 3); - record_function_format (get_identifier ("sscanf"), NULL_TREE, 1, 2, 3); - record_function_format (get_identifier ("vprintf"), NULL_TREE, 0, 1, 0); - record_function_format (get_identifier ("vfprintf"), NULL_TREE, 0, 2, 0); - record_function_format (get_identifier ("vsprintf"), NULL_TREE, 0, 2, 0); + 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); record_international_format (get_identifier ("gettext"), NULL_TREE, 1); record_international_format (get_identifier ("dgettext"), NULL_TREE, 2); @@ -1065,19 +1107,19 @@ init_function_format_info () /* 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). IS_SCAN is true for scanf-type format checking; - false indicates printf-style format checking. FORMAT_NUM is the number + 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). */ -void -record_function_format (name, assembler_name, is_scan, +static void +record_function_format (name, assembler_name, format_type, format_num, first_arg_num) tree name; tree assembler_name; - int is_scan; + enum format_type format_type; int format_num; int first_arg_num; { @@ -1100,7 +1142,7 @@ record_function_format (name, assembler_name, is_scan, info->assembler_name = assembler_name; } - info->is_scan = is_scan; + info->format_type = format_type; info->format_num = format_num; info->first_arg_num = first_arg_num; } @@ -1195,7 +1237,6 @@ check_format_info (info, params) tree first_fillin_param; char *format_chars; format_char_info *fci; - static char message[132]; char flag_chars[8]; int has_operand_number = 0; @@ -1304,7 +1345,7 @@ check_format_info (info, params) } flag_chars[0] = 0; suppressed = wide = precise = FALSE; - if (info->is_scan) + if (info->format_type == scanf_format_type) { suppressed = *format_chars == '*'; if (suppressed) @@ -1312,7 +1353,47 @@ check_format_info (info, params) while (isdigit (*format_chars)) ++format_chars; } - else + else if (info->format_type == strftime_format_type) + { + while (*format_chars != 0 && index ("_-0^#", *format_chars) != 0) + { + if (pedantic) + warning ("ANSI C does not support the strftime `%c' flag", + *format_chars); + if (index (flag_chars, *format_chars) != 0) + { + warning ("repeated `%c' flag in format", + *format_chars); + ++format_chars; + } + else + { + i = strlen (flag_chars); + flag_chars[i++] = *format_chars++; + flag_chars[i] = 0; + } + } + while (isdigit ((unsigned char) *format_chars)) + { + wide = TRUE; + ++format_chars; + } + if (wide && pedantic) + warning ("ANSI C does not support strftime format width"); + if (*format_chars == 'E' || *format_chars == 'O') + { + i = strlen (flag_chars); + flag_chars[i++] = *format_chars++; + flag_chars[i] = 0; + if (*format_chars == 'E' || *format_chars == 'O') + { + warning ("multiple E/O modifiers in format"); + while (*format_chars == 'E' || *format_chars == 'O') + ++format_chars; + } + } + } + else if (info->format_type == printf_format_type) { /* See if we have a number followed by a dollar sign. If we do, it is an operand number, so set PARAMS to that operand. */ @@ -1345,11 +1426,7 @@ check_format_info (info, params) while (*format_chars != 0 && index (" +#0-", *format_chars) != 0) { if (index (flag_chars, *format_chars) != 0) - { - sprintf (message, "repeated `%c' flag in format", - *format_chars++); - warning (message); - } + warning ("repeated `%c' flag in format", *format_chars++); else { i = strlen (flag_chars); @@ -1392,12 +1469,7 @@ check_format_info (info, params) && (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param)) != unsigned_type_node)) - { - sprintf (message, - "field width is not type int (arg %d)", - arg_num); - warning (message); - } + warning ("field width is not type int (arg %d)", arg_num); } } else @@ -1431,12 +1503,8 @@ check_format_info (info, params) ++arg_num; if (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param)) != integer_type_node) - { - sprintf (message, - "field width is not type int (arg %d)", - arg_num); - warning (message); - } + warning ("field width is not type int (arg %d)", + arg_num); } } else @@ -1446,92 +1514,106 @@ check_format_info (info, params) } } } - if (*format_chars == 'h' || *format_chars == 'l') - length_char = *format_chars++; - else if (*format_chars == 'q' || *format_chars == 'L') - { - length_char = *format_chars++; - if (pedantic && length_char == 'q') - pedwarn ("ANSI C does not support the `%c' length modifier", - length_char); - } - else if (*format_chars == 'Z') - { - length_char = *format_chars++; - if (pedantic) - pedwarn ("ANSI C does not support the `Z' length modifier"); - } - else - length_char = 0; - if (length_char == 'l' && *format_chars == 'l') - { - length_char = 'q', format_chars++; - if (pedantic) - pedwarn ("ANSI C does not support the `ll' length modifier"); - } + aflag = 0; - if (*format_chars == 'a' && info->is_scan) + + if (info->format_type != strftime_format_type) { - if (format_chars[1] == 's' || format_chars[1] == 'S' - || format_chars[1] == '[') + if (*format_chars == 'h' || *format_chars == 'l') + length_char = *format_chars++; + else if (*format_chars == 'q' || *format_chars == 'L') { - /* `a' is used as a flag. */ - aflag = 1; - format_chars++; + length_char = *format_chars++; + if (pedantic && length_char == 'q') + warning ("ANSI C does not support the `%c' length modifier", + length_char); } - } - if (suppressed && length_char != 0) - { - sprintf (message, - "use of `*' and `%c' together in format", - length_char); - warning (message); + else if (*format_chars == 'Z') + { + length_char = *format_chars++; + if (pedantic) + warning ("ANSI C does not support the `Z' length modifier"); + } + else + length_char = 0; + if (length_char == 'l' && *format_chars == 'l') + { + length_char = 'q', format_chars++; + if (pedantic) + warning ("ANSI C does not support the `ll' length modifier"); + } + if (*format_chars == 'a' && info->format_type == scanf_format_type) + { + if (format_chars[1] == 's' || format_chars[1] == 'S' + || format_chars[1] == '[') + { + /* `a' is used as a flag. */ + aflag = 1; + format_chars++; + } + } + if (suppressed && length_char != 0) + warning ("use of `*' and `%c' together in format", length_char); } format_char = *format_chars; - if (format_char == 0 || format_char == '%') + if (format_char == 0 + || (info->format_type != strftime_format_type && format_char == '%')) { warning ("conversion lacks type at end of format"); continue; } format_chars++; - fci = info->is_scan ? scan_char_table : print_char_table; + switch (info->format_type) + { + case printf_format_type: + fci = print_char_table; + break; + case scanf_format_type: + fci = scan_char_table; + break; + case strftime_format_type: + fci = time_char_table; + break; + default: + abort (); + } while (fci->format_chars != 0 && index (fci->format_chars, format_char) == 0) ++fci; if (fci->format_chars == 0) { if (format_char >= 040 && format_char < 0177) - sprintf (message, - "unknown conversion type character `%c' in format", + warning ("unknown conversion type character `%c' in format", format_char); else - sprintf (message, - "unknown conversion type character 0x%x in format", + warning ("unknown conversion type character 0x%x in format", format_char); - warning (message); continue; } - if (wide && index (fci->flag_chars, 'w') == 0) + if (pedantic) { - sprintf (message, "width used with `%c' format", - format_char); - warning (message); + if (index (fci->flag_chars, 'G') != 0) + warning ("ANSI C does not support `%%%c'", format_char); + if (index (fci->flag_chars, 'o') != 0 + && index (flag_chars, 'O') != 0) + warning ("ANSI C does not support `%%O%c'", format_char); } + if (wide && index (fci->flag_chars, 'w') == 0) + warning ("width used with `%c' format", format_char); + if (index (fci->flag_chars, '2') != 0) + warning ("`%%%c' yields only last 2 digits of year", format_char); + else if (index (fci->flag_chars, '3') != 0) + warning ("`%%%c' yields only last 2 digits of year in some locales", + format_char); if (precise && index (fci->flag_chars, 'p') == 0) - { - sprintf (message, "precision used with `%c' format", - format_char); - warning (message); - } + warning ("precision used with `%c' format", format_char); if (aflag && index (fci->flag_chars, 'a') == 0) { - sprintf (message, "`a' flag used with `%c' format", - format_char); - warning (message); + warning ("`a' flag used with `%c' format", format_char); /* To simplify the following code. */ aflag = 0; } - if (info->is_scan && format_char == '[') + if (info->format_type == scanf_format_type && format_char == '[') { /* Skip over scan set, in case it happens to have '%' in it. */ if (*format_chars == '^') @@ -1543,39 +1625,29 @@ check_format_info (info, params) while (*format_chars && *format_chars != ']') ++format_chars; if (*format_chars != ']') - /* The end of the format string was reached. */ - warning ("no closing `]' for `%%[' format"); + /* The end of the format string was reached. */ + warning ("no closing `]' for `%%[' format"); } if (suppressed) { if (index (fci->flag_chars, '*') == 0) - { - sprintf (message, - "suppression of `%c' conversion in format", - format_char); - warning (message); - } + warning ("suppression of `%c' conversion in format", format_char); continue; } for (i = 0; flag_chars[i] != 0; ++i) { if (index (fci->flag_chars, flag_chars[i]) == 0) - { - sprintf (message, "flag `%c' used with type `%c'", - flag_chars[i], format_char); - warning (message); - } + warning ("flag `%c' used with type `%c'", + flag_chars[i], format_char); } + if (info->format_type == strftime_format_type) + continue; integral_format = (format_char == 'd' || format_char == 'i' || format_char == 'o' || format_char == 'u' || format_char == 'x' || format_char == 'x'); if (precise && index (flag_chars, '0') != 0 && integral_format) - { - sprintf (message, - "`0' flag ignored with precision specifier and `%c' format", - format_char); - warning (message); - } + warning ("`0' flag ignored with precision specifier and `%c' format", + format_char); switch (length_char) { default: wanted_type = fci->nolen ? *(fci->nolen) : 0; break; @@ -1637,20 +1709,17 @@ check_format_info (info, params) continue; } if (TREE_CODE (cur_type) != ERROR_MARK) - { - sprintf (message, - "format argument is not a %s (arg %d)", - ((fci->pointer_count + aflag == 1) - ? "pointer" : "pointer to a pointer"), - arg_num); - warning (message); - } + warning ("format argument is not a %s (arg %d)", + ((fci->pointer_count + aflag == 1) + ? "pointer" : "pointer to a pointer"), + arg_num); break; } /* See if this is an attempt to write into a const type with scanf. */ - if (info->is_scan && i == fci->pointer_count + aflag + if (info->format_type == scanf_format_type + && i == fci->pointer_count + aflag && wanted_type != 0 && TREE_CODE (cur_type) != ERROR_MARK && (TYPE_READONLY (cur_type) @@ -1658,10 +1727,7 @@ check_format_info (info, params) && (TREE_CODE_CLASS (TREE_CODE (cur_param)) == 'c' || (TREE_CODE_CLASS (TREE_CODE (cur_param)) == 'd' && TREE_READONLY (cur_param)))))) - { - sprintf (message, "writing into constant object (arg %d)", arg_num); - warning (message); - } + warning ("writing into constant object (arg %d)", arg_num); /* Check the type of the "real" argument, if there's a type we want. */ if (i == fci->pointer_count + aflag && wanted_type != 0 @@ -1721,11 +1787,7 @@ check_format_info (info, params) that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (cur_type))); if (strcmp (this, that) != 0) - { - sprintf (message, "%s format, %s arg (arg %d)", - this, that, arg_num); - warning (message); - } + warning ("%s format, %s arg (arg %d)", this, that, arg_num); } } } @@ -1829,7 +1891,7 @@ convert_and_check (type, expr) || TREE_UNSIGNED (type) || ! int_fits_type_p (expr, unsigned_type (type))) && skip_evaluation == 0) - warning ("overflow in implicit constant conversion"); + warning ("overflow in implicit constant conversion"); } else unsigned_conversion_warning (t, expr); diff --git a/gcc/c-tree.h b/gcc/c-tree.h index 3e0e10a401f..d3884dc44fd 100644 --- a/gcc/c-tree.h +++ b/gcc/c-tree.h @@ -162,7 +162,6 @@ extern void gen_aux_info_record PROTO((tree, int, int, int)); extern void declare_function_name PROTO((void)); extern void decl_attributes PROTO((tree, tree, tree)); extern void init_function_format_info PROTO((void)); -extern void record_function_format PROTO((tree, tree, int, int, int)); extern void check_function_format PROTO((tree, tree, tree)); /* Print an error message for invalid operands to arith operation CODE. NOP_EXPR is used as a special case (see truthvalue_conversion). */ diff --git a/gcc/extend.texi b/gcc/extend.texi index f7598b84d3c..a691162bf6b 100644 --- a/gcc/extend.texi +++ b/gcc/extend.texi @@ -1274,7 +1274,7 @@ hack ((union foo) x); @cindex functions in arbitrary sections @cindex @code{volatile} applied to function @cindex @code{const} applied to function -@cindex functions with @code{printf} or @code{scanf} style arguments +@cindex functions with @code{printf}, @code{scanf} or @code{strftime} style arguments @cindex functions that are passed arguments in registers on the 386 @cindex functions that pop the argument stack on the 386 @cindex functions that do not pop the argument stack on the 386 @@ -1378,9 +1378,9 @@ return @code{void}. @item format (@var{archetype}, @var{string-index}, @var{first-to-check}) @cindex @code{format} function attribute -The @code{format} attribute specifies that a function takes @code{printf} -or @code{scanf} style arguments which should be type-checked against a -format string. For example, the declaration: +The @code{format} attribute specifies that a function takes @code{printf}, +@code{scanf}, or @code{strftime} style arguments which should be type-checked +against a format string. For example, the declaration: @smallexample extern int @@ -1394,7 +1394,8 @@ for consistency with the @code{printf} style format string argument @code{my_format}. The parameter @var{archetype} determines how the format string is -interpreted, and should be either @code{printf} or @code{scanf}. The +interpreted, and should be either @code{printf}, @code{scanf}, or +@code{strftime}. The parameter @var{string-index} specifies which argument is the format string argument (starting from 1), while @var{first-to-check} is the number of the first argument to check against the format string. For @@ -1411,7 +1412,7 @@ The @code{format} attribute allows you to identify your own functions which take format strings as arguments, so that GNU CC can check the calls to these functions for errors. The compiler always checks formats for the ANSI library functions @code{printf}, @code{fprintf}, -@code{sprintf}, @code{scanf}, @code{fscanf}, @code{sscanf}, +@code{sprintf}, @code{scanf}, @code{fscanf}, @code{sscanf}, @code{strftime}, @code{vprintf}, @code{vfprintf} and @code{vsprintf} whenever such warnings are requested (using @samp{-Wformat}), so there is no need to modify the header file @file{stdio.h}. @@ -1431,18 +1432,19 @@ my_dgettext (char *my_domain, const char *my_format) @noindent causes the compiler to check the arguments in calls to -@code{my_dgettext} whose result is passed to a @code{printf} or -@code{scanf} type function for consistency with the @code{printf} style -format string argument @code{my_format}. +@code{my_dgettext} whose result is passed to a @code{printf}, +@code{scanf}, or @code{strftime} type function for consistency with the +@code{printf} style format string argument @code{my_format}. The parameter @var{string-index} specifies which argument is the format string argument (starting from 1). The @code{format-arg} attribute allows you to identify your own functions which modify format strings, so that GNU CC can check the -calls to @code{printf} and @code{scanf} function whose operands are a -call to one of your own function. The compiler always treats -@code{gettext}, @code{dgettext}, and @code{dcgettext} in this manner. +calls to @code{printf}, @code{scanf}, or @code{strftime} function whose +operands are a call to one of your own function. The compiler always +treats @code{gettext}, @code{dgettext}, and @code{dcgettext} in this +manner. @item section ("section-name") @cindex @code{section} function attribute