diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index b1953a45f9b..0f047a105c2 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -594,18 +594,23 @@ attribute_takes_identifier_p (const_tree attr_id) } /* Verify that argument value POS at position ARGNO to attribute NAME - applied to function TYPE refers to a function parameter at position - POS and the expected type CODE. Treat CODE == INTEGER_TYPE as - matching all C integral types except bool. If successful, return - POS after default conversions, if any. Otherwise, issue appropriate - warnings and return null. A non-zero 1-based ARGNO should be passed - in by callers only for attributes with more than one argument. */ + applied to function FN (which is either a function declaration or function + type) refers to a function parameter at position POS and the expected type + CODE. Treat CODE == INTEGER_TYPE as matching all C integral types except + bool. If successful, return POS after default conversions (and possibly + adjusted by ADJUST_POS). Otherwise, issue appropriate warnings and return + null. A non-zero 1-based ARGNO should be passed in by callers only for + attributes with more than one argument. + + N.B. This function modifies POS. */ tree -positional_argument (const_tree fntype, const_tree atname, tree pos, +positional_argument (const_tree fn, const_tree atname, tree &pos, tree_code code, int argno /* = 0 */, int flags /* = posargflags () */) { + const_tree fndecl = TYPE_P (fn) ? NULL_TREE : fn; + const_tree fntype = TYPE_P (fn) ? fn : TREE_TYPE (fn); if (pos && TREE_CODE (pos) != IDENTIFIER_NODE && TREE_CODE (pos) != FUNCTION_DECL) pos = default_conversion (pos); @@ -682,6 +687,11 @@ positional_argument (const_tree fntype, const_tree atname, tree pos, if (!prototype_p (fntype)) return pos; + /* ADJUST_POS is non-zero in C++ when the function type has invisible + parameters generated by the compiler, such as the in-charge or VTT + parameters. */ + const int adjust_pos = maybe_adjust_arg_pos_for_attribute (fndecl); + /* Verify that the argument position does not exceed the number of formal arguments to the function. When POSARG_ELLIPSIS is set, ARGNO may be beyond the last argument of a vararg @@ -690,7 +700,7 @@ positional_argument (const_tree fntype, const_tree atname, tree pos, if (!nargs || !tree_fits_uhwi_p (pos) || ((flags & POSARG_ELLIPSIS) == 0 - && !IN_RANGE (tree_to_uhwi (pos), 1, nargs))) + && !IN_RANGE (tree_to_uhwi (pos) + adjust_pos, 1, nargs))) { if (argno < 1) @@ -707,8 +717,9 @@ positional_argument (const_tree fntype, const_tree atname, tree pos, } /* Verify that the type of the referenced formal argument matches - the expected type. */ - unsigned HOST_WIDE_INT ipos = tree_to_uhwi (pos); + the expected type. Invisible parameters may have been added by + the compiler, so adjust the position accordingly. */ + unsigned HOST_WIDE_INT ipos = tree_to_uhwi (pos) + adjust_pos; /* Zero was handled above. */ gcc_assert (ipos != 0); @@ -791,7 +802,7 @@ positional_argument (const_tree fntype, const_tree atname, tree pos, return NULL_TREE; } - return pos; + return build_int_cst (TREE_TYPE (pos), ipos); } /* Return the first of DECL or TYPE attributes installed in NODE if it's diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index 730faa9e87f..6156e5fcb0c 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -6071,8 +6071,8 @@ check_function_arguments (location_t loc, const_tree fndecl, const_tree fntype, /* Check for errors in format strings. */ if (warn_format || warn_suggest_attribute_format) - check_function_format (fntype, TYPE_ATTRIBUTES (fntype), nargs, argarray, - arglocs); + check_function_format (fndecl ? fndecl : fntype, TYPE_ATTRIBUTES (fntype), nargs, + argarray, arglocs); if (warn_format) check_function_sentinel (fntype, nargs, argarray); diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 52a85bfb783..f10be9bd67e 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -1049,6 +1049,7 @@ extern tree finish_label_address_expr (tree, location_t); extern tree lookup_label (tree); extern tree lookup_name (tree); extern bool lvalue_p (const_tree); +extern int maybe_adjust_arg_pos_for_attribute (const_tree); extern bool vector_targets_convertible_p (const_tree t1, const_tree t2); extern bool vector_types_convertible_p (const_tree t1, const_tree t2, bool emit_lax_note); @@ -1493,7 +1494,7 @@ enum posargflags { POSARG_ELLIPSIS = 2 }; -extern tree positional_argument (const_tree, const_tree, tree, tree_code, +extern tree positional_argument (const_tree, const_tree, tree &, tree_code, int = 0, int = posargflags ()); extern enum flt_eval_method diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc index 98f28c0dcc6..ea57fde801c 100644 --- a/gcc/c-family/c-format.cc +++ b/gcc/c-family/c-format.cc @@ -78,9 +78,9 @@ static bool check_format_string (const_tree argument, unsigned HOST_WIDE_INT format_num, int flags, bool *no_add_attrs, int expected_format_type); -static tree get_constant (const_tree fntype, const_tree atname, tree expr, - int argno, unsigned HOST_WIDE_INT *value, - int flags, bool validated_p); +static bool validate_constant (const_tree fn, const_tree atname, tree &expr, + int argno, unsigned HOST_WIDE_INT *value, + int flags, bool validated_p); static const char *convert_format_name_to_system_name (const char *attr_name); static int first_target_format_type; @@ -172,14 +172,12 @@ handle_format_arg_attribute (tree *node, tree atname, tree args, int flags, bool *no_add_attrs) { tree type = *node; - /* Note that TREE_VALUE (args) is changed in place below. */ + /* Note that TREE_VALUE (args) is changed in the validate_constant call. */ tree *format_num_expr = &TREE_VALUE (args); unsigned HOST_WIDE_INT format_num = 0; - if (tree val = get_constant (type, atname, *format_num_expr, 0, &format_num, - 0, false)) - *format_num_expr = val; - else + if (!validate_constant (type, atname, *format_num_expr, 0, &format_num, 0, + false)) { *no_add_attrs = true; return NULL_TREE; @@ -301,38 +299,39 @@ check_format_string (const_tree fntype, unsigned HOST_WIDE_INT format_num, /* Under the control of FLAGS, verify EXPR is a valid constant that refers to a positional argument ARGNO having a string type (char* or, for targets like Darwin, a pointer to struct CFString) to - a function type FNTYPE declared with attribute ATNAME. - If valid, store the constant's integer value in *VALUE and return - the value. - If VALIDATED_P is true assert the validation is successful. - Returns the converted constant value on success, null otherwise. */ + a function FN declared with attribute ATNAME. If valid, store the + constant's integer value in *VALUE and return true. If VALIDATED_P + is true assert the validation is successful. -static tree -get_constant (const_tree fntype, const_tree atname, tree expr, int argno, - unsigned HOST_WIDE_INT *value, int flags, bool validated_p) + N.B. This function modifies EXPR. */ + +static bool +validate_constant (const_tree fn, const_tree atname, tree &expr, int argno, + unsigned HOST_WIDE_INT *value, int flags, bool validated_p) { /* Require the referenced argument to have a string type. For targets like Darwin, also accept pointers to struct CFString. */ - if (tree val = positional_argument (fntype, atname, expr, STRING_CST, + if (tree val = positional_argument (fn, atname, expr, STRING_CST, argno, flags)) { *value = TREE_INT_CST_LOW (val); - return val; + return true; } gcc_assert (!validated_p); - return NULL_TREE; + return false; } /* 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 must not be erroneous; - if false, it will give an error message. Returns true if the - attributes are successfully decoded, false otherwise. */ + if false, it will give an error message. FN is either a function + declaration or function type. Returns true if the attributes are + successfully decoded, false otherwise. */ static bool -decode_format_attr (const_tree fntype, tree atname, tree args, +decode_format_attr (const_tree fn, tree atname, tree args, function_format_info *info, bool validated_p) { tree format_type_id = TREE_VALUE (args); @@ -372,17 +371,13 @@ decode_format_attr (const_tree fntype, tree atname, tree args, } } - if (tree val = get_constant (fntype, atname, *format_num_expr, - 2, &info->format_num, 0, validated_p)) - *format_num_expr = val; - else + if (!validate_constant (fn, atname, *format_num_expr, 2, &info->format_num, + 0, validated_p)) return false; - if (tree val = get_constant (fntype, atname, *first_arg_num_expr, - 3, &info->first_arg_num, - (POSARG_ZERO | POSARG_ELLIPSIS), validated_p)) - *first_arg_num_expr = val; - else + if (!validate_constant (fn, atname, *first_arg_num_expr, 3, + &info->first_arg_num, + (POSARG_ZERO | POSARG_ELLIPSIS), validated_p)) return false; if (info->first_arg_num != 0 && info->first_arg_num <= info->format_num) @@ -1154,13 +1149,12 @@ decode_format_type (const char *s, bool *is_raw /* = NULL */) /* 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 -Wsuggest-attribute=format, - warn for calls to vprintf or vscanf in functions with no such format - attribute themselves. */ + values in the array ARGARRAY. FN is either a function declaration or + function type. Also, if -Wsuggest-attribute=format, warn for calls to + vprintf or vscanf in functions with no such format attribute themselves. */ void -check_function_format (const_tree fntype, tree attrs, int nargs, +check_function_format (const_tree fn, tree attrs, int nargs, tree *argarray, vec *arglocs) { tree a; @@ -1174,7 +1168,7 @@ check_function_format (const_tree fntype, tree attrs, int nargs, { /* Yup; check it. */ function_format_info info; - decode_format_attr (fntype, atname, TREE_VALUE (a), &info, + decode_format_attr (fn, atname, TREE_VALUE (a), &info, /*validated=*/true); if (warn_format) { @@ -5150,10 +5144,14 @@ convert_format_name_to_system_name (const char *attr_name) /* Handle a "format" attribute; arguments as in struct attribute_spec.handler. */ tree -handle_format_attribute (tree *node, tree atname, tree args, +handle_format_attribute (tree node[3], tree atname, tree args, int flags, bool *no_add_attrs) { const_tree type = *node; + /* NODE[2] may be NULL, and it also may be a PARM_DECL for function + pointers. */ + const_tree fndecl = ((node[2] && TREE_CODE (node[2]) == FUNCTION_DECL) + ? node[2] : NULL_TREE); function_format_info info; #ifdef TARGET_FORMAT_TYPES @@ -5179,7 +5177,8 @@ handle_format_attribute (tree *node, tree atname, tree args, if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE) TREE_VALUE (args) = canonicalize_attr_name (TREE_VALUE (args)); - if (!decode_format_attr (type, atname, args, &info, /* validated_p = */false)) + if (!decode_format_attr (fndecl ? fndecl : type, atname, args, &info, + /* validated_p = */false)) { *no_add_attrs = true; return NULL_TREE; diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc index 97850ada2c8..70e10a98e33 100644 --- a/gcc/c/c-objc-common.cc +++ b/gcc/c/c-objc-common.cc @@ -394,3 +394,12 @@ c_get_alias_set (tree t) return c_common_get_alias_set (t); } + +/* In C there are no invisible parameters like in C++ (this, in-charge, VTT, + etc.). */ + +int +maybe_adjust_arg_pos_for_attribute (const_tree) +{ + return 0; +} diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ed0d0d22950..979728027ed 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -6119,6 +6119,25 @@ maybe_warn_zero_as_null_pointer_constant (tree expr, location_t loc) } return false; } + +/* FNDECL is a function declaration whose type may have been altered by + adding extra parameters such as this, in-charge, or VTT. When this + takes place, the positional arguments supplied by the user (as in the + 'format' attribute arguments) may refer to the wrong argument. This + function returns an integer indicating how many arguments should be + skipped. */ + +int +maybe_adjust_arg_pos_for_attribute (const_tree fndecl) +{ + if (!fndecl) + return 0; + int n = num_artificial_parms_for (fndecl); + /* The manual states that it's the user's responsibility to account + for the implicit this parameter. */ + return n > 0 ? n - 1 : 0; +} + /* Release memory we no longer need after parsing. */ void diff --git a/gcc/testsuite/g++.dg/ext/attr-format-arg1.C b/gcc/testsuite/g++.dg/ext/attr-format-arg1.C new file mode 100644 index 00000000000..a7ad0f9ca33 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-format-arg1.C @@ -0,0 +1,26 @@ +// PR c++/101833 +// { dg-do compile } +// { dg-options "-Wall" } + +struct B { }; + +struct V : virtual B { + const char *fmt (int, const char *) __attribute__((format_arg(3))); +}; + +struct D : B { + const char *fmt (int, const char *) __attribute__((format_arg(3))); +}; + +extern void fmt (const char *, ...) __attribute__((format(printf, 1, 2))); + +void +g () +{ + V v; + fmt (v.fmt (1, "%d"), 1); + fmt (v.fmt (1, "%d"), 1lu); // { dg-warning "expects argument of type" } + D d; + fmt (d.fmt (1, "%d"), 1); + fmt (d.fmt (1, "%d"), 1lu); // { dg-warning "expects argument of type" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-format1.C b/gcc/testsuite/g++.dg/ext/attr-format1.C new file mode 100644 index 00000000000..1b8464ed6ac --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-format1.C @@ -0,0 +1,32 @@ +// PR c++/47634 +// { dg-do compile } + +class Base { +public: + Base() { } +}; + +class VDerived : public virtual Base { +public: + VDerived(int x, const char * f, ...) __attribute__((format(printf, 3, 4))); +}; + +class Derived : public Base { +public: + Derived(int x, const char * f, ...) __attribute__((format(printf, 3, 4))); +}; + +VDerived::VDerived(int, const char *, ...) +{ +} + +Derived::Derived(int, const char *, ...) +{ +} + +int +main(int, char **) +{ + throw VDerived(1, "%s %d", "foo", 1); + throw Derived(1, "%s %d", "bar", 1); +} diff --git a/gcc/testsuite/g++.dg/ext/attr-format2.C b/gcc/testsuite/g++.dg/ext/attr-format2.C new file mode 100644 index 00000000000..7e6eec58047 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-format2.C @@ -0,0 +1,38 @@ +// PR c++/101833 +// { dg-do compile } +// { dg-options "-Wall" } + +struct B { }; + +struct V : virtual B { + V(int, const char *, ...) __attribute__((format(printf, 3, 4))); +}; + +struct D : B { + D(int, const char *, ...) __attribute__((format(printf, 3, 4))); +}; + +struct D2 : B { + template + D2(T, const char *, ...) __attribute__((format(printf, 3, 4))); +}; + +struct V2 : virtual B { + template + V2(T, const char *, ...) __attribute__((format(printf, 3, 4))); +}; + +struct X { + template + X(T, ...) __attribute__((format(printf, 2, 3))); +}; + +V v(1, "%s %d", "foo", 1); +D d(1, "%s %d", "foo", 1); +D2 d2(1, "%s %d", "foo", 1); +V2 v2(1, "%s %d", "foo", 1); + +// Test that it actually works. +V e1(1, "%d", 1L); // { dg-warning "expects argument of type" } +D e2(1, "%d", 1L); // { dg-warning "expects argument of type" } +X e3("%d", 1L); // { dg-warning "expects argument of type" } diff --git a/gcc/testsuite/g++.dg/ext/attr-format3.C b/gcc/testsuite/g++.dg/ext/attr-format3.C new file mode 100644 index 00000000000..60a672cd389 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-format3.C @@ -0,0 +1,15 @@ +// PR c++/101833 +// { dg-do compile } +// { dg-options "-Wall" } + +class Base {}; + +struct VDerived : virtual Base { + VDerived(int, int, const char *, ...) __attribute__((format(printf, 2, 3))); // { dg-error ".format. attribute argument 2 value .2. refers to parameter type .int." } + VDerived(int, const char *, ...) __attribute__((format(printf, 5, 6))); // { dg-warning ".format. attribute argument 2 value .5. exceeds" } +} a(1, "%s %d", "foo", 1); + +struct Derived : Base { + Derived(int, int, const char *, ...) __attribute__((format(printf, 2, 3))); // { dg-error ".format. attribute argument 2 value .2. refers to parameter type .int." } + Derived(int, const char *, ...) __attribute__((format(printf, 5, 6))); // { dg-warning ".format. attribute argument 2 value .5. exceeds" } +} b(1, "%s %d", "foo", 1); diff --git a/gcc/tree-core.h b/gcc/tree-core.h index c79b8b28da3..93258e381c6 100644 --- a/gcc/tree-core.h +++ b/gcc/tree-core.h @@ -2113,8 +2113,10 @@ struct attribute_spec { bool function_type_required; /* Specifies if attribute affects type's identity. */ bool affects_type_identity; - /* Function to handle this attribute. NODE points to the node to which - the attribute is to be applied. If a DECL, it should be modified in + /* Function to handle this attribute. NODE points to a tree[3] array, + where node[0] is the node to which the attribute is to be applied; + node[1] is the last pushed/merged declaration if one exists, and node[2] + may be the declaration for node[0]. If a DECL, it should be modified in place; if a TYPE, a copy should be created. NAME is the canonicalized name of the attribute i.e. without any leading or trailing underscores. ARGS is the TREE_LIST of the arguments (which may be NULL). FLAGS gives