diff mbox series

[v2] c, c++: attribute format on a ctor with a vbase [PR101833, PR47634]

Message ID YldZ+ba1M5GEyggl@redhat.com
State New
Headers show
Series [v2] c, c++: attribute format on a ctor with a vbase [PR101833, PR47634] | expand

Commit Message

Marek Polacek April 13, 2022, 11:17 p.m. UTC
On Tue, Apr 12, 2022 at 04:01:14PM -0400, Jason Merrill wrote:
> On 4/12/22 14:38, Marek Polacek wrote:
> > On Mon, Apr 11, 2022 at 04:39:22PM -0400, Jason Merrill wrote:
> > > On 4/8/22 15:21, Marek Polacek wrote:
> > > > On Wed, Apr 06, 2022 at 04:55:54PM -0400, Jason Merrill wrote:
> > > > > On 4/1/22 15:14, Marek Polacek wrote:
> > > > > > Attribute format takes three arguments: archetype, string-index, and
> > > > > > first-to-check.  The last two specify the position in the function
> > > > > > parameter list.  r63030 clarified that "Since non-static C++ methods have
> > > > > > an implicit this argument, the arguments of such methods should be counted
> > > > > > from two, not one, when giving values for string-index and first-to-check."
> > > > > > Therefore one has to write
> > > > > > 
> > > > > >      struct D {
> > > > > >        D(const char *, ...) __attribute__((format(printf, 2, 3)));
> > > > > >      };
> > > > > > 
> > > > > > However -- and this is the problem in this PR -- ctors with virtual
> > > > > > bases also get two additional parameters: the in-charge parameter and
> > > > > > the VTT parameter (added in maybe_retrofit_in_chrg).  In fact we'll end up
> > > > > > with two clones of the ctor: an in-charge and a not-in-charge version (see
> > > > > > build_cdtor_clones).  That means that the argument position the user
> > > > > > specified in the attribute argument will refer to different arguments,
> > > > > > depending on which constructor we're currently dealing with.  This can
> > > > > > cause a range of problems: wrong errors, confusing warnings, or crashes.
> > > > > > 
> > > > > > This patch corrects that; for C we don't have to do anything, and in C++
> > > > > > we can use num_artificial_parms_for.  It would be wrong to rewrite the
> > > > > > attributes the user supplied, so I've added an extra parameter called
> > > > > > adjust_pos.
> > > > > > 
> > > > > > Attribute format_arg is not affected, because it requires that the
> > > > > > function returns "const char *" which will never be the case for cdtors.
> > > > > > 
> > > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > > > 
> > > > > > 	PR c++/101833
> > > > > > 	PR c++/47634
> > > > > > 
> > > > > > gcc/c-family/ChangeLog:
> > > > > > 
> > > > > > 	* c-attribs.cc (positional_argument): Add new argument adjust_pos,
> > > > > > 	use it.
> > > > > > 	* c-common.cc (check_function_arguments): Pass fndecl to
> > > > > > 	check_function_format.
> > > > > > 	* c-common.h (check_function_format): Adjust declaration.
> > > > > > 	(maybe_adjust_arg_pos_for_attribute): Add.
> > > > > > 	(positional_argument): Adjust declaration.
> > > > > > 	* c-format.cc (decode_format_attr): Add fndecl argument.  Pass it to
> > > > > > 	maybe_adjust_arg_pos_for_attribute.  Adjust calls to get_constant.
> > > > > 
> > > > > I wonder about, instead of adding another parameter, allowing the current
> > > > > fntype parameter to be the fndecl when we have one.
> > > > > 
> > > > > And then that gets passed down into positional_argument, so we can call
> > > > > maybe_adjust_arg_pos_for_attribute there, and adjust the return value
> > > > > appropriately so we don't need the extra adjustment in get_constant?
> > > > 
> > > > Unfortunately I can't do that.  positional_argument can't return the
> > > > adjusted position, because get_constant returns it and in decode_format_attr
> > > > it's used to rewrite the arguments in the attribute list:
> > > > 
> > > >     tree *format_num_expr = &TREE_VALUE (TREE_CHAIN (args));
> > > >     tree *first_arg_num_expr = &TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
> > > >     ...
> > > >       if (tree val = get_constant (fntype, atname, *format_num_expr,
> > > >                                  2, &info->format_num, 0, validated_p,
> > > >                                  adjust_pos))
> > > >       *format_num_expr = val;
> > > 
> > > Could we not do that?  Currently isn't it just overwriting the value with
> > > the same value after default_conversion?
> > 
> > I think it is.
> > 
> > > Maybe do that conversion directly in decode_format_attr instead?
> > 
> > I'm afraid I can't move the default_conversion call out of positional_argument
> > because positional_argument is called from a lot of handle_*_attribute
> > functions, and each of those would have to call default_conversion, which
> > we don't want to do.  (Failure to call default_conversion would break e.g.
> > g++.dg/cpp0x/constexpr-attribute2.C.)
> 
> Maybe pass in the pointer where we want to store the converted value?

... or use pass-by-reference so that I don't have to adjust the call sites
of positional_argument, since we never pass a NULL_TREE.  That makes the
patch a bit smaller.

Now maybe_adjust_arg_pos_for_attribute is only called in positional_argument
and the adjustment is done only there.  It's still somewhat messier than I
hoped so I'm happy to defer to GCC 13.  Thanks,

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/11.3?

-- >8 --
Attribute format takes three arguments: archetype, string-index, and
first-to-check.  The last two specify the position in the function
parameter list.  r63030 clarified that "Since non-static C++ methods have
an implicit this argument, the arguments of such methods should be counted
from two, not one, when giving values for string-index and first-to-check."
Therefore one has to write

  struct D {
    D(const char *, ...) __attribute__((format(printf, 2, 3)));
  };

However -- and this is the problem in this PR -- ctors with virtual
bases also get two additional parameters: the in-charge parameter and
the VTT parameter (added in maybe_retrofit_in_chrg).  In fact we'll end up
with two clones of the ctor: an in-charge and a not-in-charge version (see
build_cdtor_clones).  That means that the argument position the user
specified in the attribute argument will refer to different arguments,
depending on which constructor we're currently dealing with.  This can
cause a range of problems: wrong errors, confusing warnings, or crashes.

This patch corrects that; for C we don't have to do anything, and in C++
we can use num_artificial_parms_for.  It would be wrong to rewrite the
attributes the user supplied, so I've changed POS to be passed by
reference so that we don't have to change all the call sites of
positional_argument and we still get the default_conversion adjustment.

Attribute format_arg is not affected, because it requires that the
function returns "const char *" which will never be the case for cdtors.

	PR c++/101833
	PR c++/47634

gcc/c-family/ChangeLog:

	* c-attribs.cc (positional_argument): Pass POS by reference.  Deal
	with FN being either a function declaration or function type.  Use
	maybe_adjust_arg_pos_for_attribute.
	* c-common.cc (check_function_arguments): Maybe pass FNDECL down to
	check_function_format.
	* c-common.h (maybe_adjust_arg_pos_for_attribute): Declare.
	(positional_argument): Adjust.
	* c-format.cc (get_constant): Take EXPR by reference.  Return bool
	instead of tree.
	(handle_format_arg_attribute): Don't overwrite FORMAT_NUM_EXPR by the
	return value of get_constant.
	(decode_format_attr): Don't overwrite FORMAT_NUM_EXPR and
	FIRST_ARG_NUM_EXPR by the return value of get_constant.
	(check_function_format): Adjust a parameter name.
	(handle_format_attribute): Maybe pass FNDECL down to decode_format_attr.

gcc/c/ChangeLog:

	* c-objc-common.cc (maybe_adjust_arg_pos_for_attribute): New.

gcc/cp/ChangeLog:

	* tree.cc (maybe_adjust_arg_pos_for_attribute): New.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/attr-format-arg1.C: New test.
	* g++.dg/ext/attr-format1.C: New test.
	* g++.dg/ext/attr-format2.C: New test.
	* g++.dg/ext/attr-format3.C: New test.
---
 gcc/c-family/c-attribs.cc                   | 33 ++++++----
 gcc/c-family/c-common.cc                    |  4 +-
 gcc/c-family/c-common.h                     |  3 +-
 gcc/c-family/c-format.cc                    | 69 ++++++++++-----------
 gcc/c/c-objc-common.cc                      |  9 +++
 gcc/cp/tree.cc                              | 19 ++++++
 gcc/testsuite/g++.dg/ext/attr-format-arg1.C | 26 ++++++++
 gcc/testsuite/g++.dg/ext/attr-format1.C     | 32 ++++++++++
 gcc/testsuite/g++.dg/ext/attr-format2.C     | 38 ++++++++++++
 gcc/testsuite/g++.dg/ext/attr-format3.C     | 15 +++++
 10 files changed, 198 insertions(+), 50 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/attr-format-arg1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/attr-format1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/attr-format2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/attr-format3.C


base-commit: 33ba46663cdf2c9e995265eb838585488f81e907

Comments

Jason Merrill April 15, 2022, 3:55 a.m. UTC | #1
On 4/13/22 19:17, Marek Polacek wrote:
> On Tue, Apr 12, 2022 at 04:01:14PM -0400, Jason Merrill wrote:
>> On 4/12/22 14:38, Marek Polacek wrote:
>>> On Mon, Apr 11, 2022 at 04:39:22PM -0400, Jason Merrill wrote:
>>>> On 4/8/22 15:21, Marek Polacek wrote:
>>>>> On Wed, Apr 06, 2022 at 04:55:54PM -0400, Jason Merrill wrote:
>>>>>> On 4/1/22 15:14, Marek Polacek wrote:
>>>>>>> Attribute format takes three arguments: archetype, string-index, and
>>>>>>> first-to-check.  The last two specify the position in the function
>>>>>>> parameter list.  r63030 clarified that "Since non-static C++ methods have
>>>>>>> an implicit this argument, the arguments of such methods should be counted
>>>>>>> from two, not one, when giving values for string-index and first-to-check."
>>>>>>> Therefore one has to write
>>>>>>>
>>>>>>>       struct D {
>>>>>>>         D(const char *, ...) __attribute__((format(printf, 2, 3)));
>>>>>>>       };
>>>>>>>
>>>>>>> However -- and this is the problem in this PR -- ctors with virtual
>>>>>>> bases also get two additional parameters: the in-charge parameter and
>>>>>>> the VTT parameter (added in maybe_retrofit_in_chrg).  In fact we'll end up
>>>>>>> with two clones of the ctor: an in-charge and a not-in-charge version (see
>>>>>>> build_cdtor_clones).  That means that the argument position the user
>>>>>>> specified in the attribute argument will refer to different arguments,
>>>>>>> depending on which constructor we're currently dealing with.  This can
>>>>>>> cause a range of problems: wrong errors, confusing warnings, or crashes.
>>>>>>>
>>>>>>> This patch corrects that; for C we don't have to do anything, and in C++
>>>>>>> we can use num_artificial_parms_for.  It would be wrong to rewrite the
>>>>>>> attributes the user supplied, so I've added an extra parameter called
>>>>>>> adjust_pos.
>>>>>>>
>>>>>>> Attribute format_arg is not affected, because it requires that the
>>>>>>> function returns "const char *" which will never be the case for cdtors.
>>>>>>>
>>>>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>>>>>
>>>>>>> 	PR c++/101833
>>>>>>> 	PR c++/47634
>>>>>>>
>>>>>>> gcc/c-family/ChangeLog:
>>>>>>>
>>>>>>> 	* c-attribs.cc (positional_argument): Add new argument adjust_pos,
>>>>>>> 	use it.
>>>>>>> 	* c-common.cc (check_function_arguments): Pass fndecl to
>>>>>>> 	check_function_format.
>>>>>>> 	* c-common.h (check_function_format): Adjust declaration.
>>>>>>> 	(maybe_adjust_arg_pos_for_attribute): Add.
>>>>>>> 	(positional_argument): Adjust declaration.
>>>>>>> 	* c-format.cc (decode_format_attr): Add fndecl argument.  Pass it to
>>>>>>> 	maybe_adjust_arg_pos_for_attribute.  Adjust calls to get_constant.
>>>>>>
>>>>>> I wonder about, instead of adding another parameter, allowing the current
>>>>>> fntype parameter to be the fndecl when we have one.
>>>>>>
>>>>>> And then that gets passed down into positional_argument, so we can call
>>>>>> maybe_adjust_arg_pos_for_attribute there, and adjust the return value
>>>>>> appropriately so we don't need the extra adjustment in get_constant?
>>>>>
>>>>> Unfortunately I can't do that.  positional_argument can't return the
>>>>> adjusted position, because get_constant returns it and in decode_format_attr
>>>>> it's used to rewrite the arguments in the attribute list:
>>>>>
>>>>>      tree *format_num_expr = &TREE_VALUE (TREE_CHAIN (args));
>>>>>      tree *first_arg_num_expr = &TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
>>>>>      ...
>>>>>        if (tree val = get_constant (fntype, atname, *format_num_expr,
>>>>>                                   2, &info->format_num, 0, validated_p,
>>>>>                                   adjust_pos))
>>>>>        *format_num_expr = val;
>>>>
>>>> Could we not do that?  Currently isn't it just overwriting the value with
>>>> the same value after default_conversion?
>>>
>>> I think it is.
>>>
>>>> Maybe do that conversion directly in decode_format_attr instead?
>>>
>>> I'm afraid I can't move the default_conversion call out of positional_argument
>>> because positional_argument is called from a lot of handle_*_attribute
>>> functions, and each of those would have to call default_conversion, which
>>> we don't want to do.  (Failure to call default_conversion would break e.g.
>>> g++.dg/cpp0x/constexpr-attribute2.C.)
>>
>> Maybe pass in the pointer where we want to store the converted value?
> 
> ... or use pass-by-reference so that I don't have to adjust the call sites
> of positional_argument, since we never pass a NULL_TREE.  That makes the
> patch a bit smaller.
> 
> Now maybe_adjust_arg_pos_for_attribute is only called in positional_argument
> and the adjustment is done only there.  It's still somewhat messier than I
> hoped so I'm happy to defer to GCC 13.  Thanks,
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/11.3?
> 
> -- >8 --
> Attribute format takes three arguments: archetype, string-index, and
> first-to-check.  The last two specify the position in the function
> parameter list.  r63030 clarified that "Since non-static C++ methods have
> an implicit this argument, the arguments of such methods should be counted
> from two, not one, when giving values for string-index and first-to-check."
> Therefore one has to write
> 
>    struct D {
>      D(const char *, ...) __attribute__((format(printf, 2, 3)));
>    };
> 
> However -- and this is the problem in this PR -- ctors with virtual
> bases also get two additional parameters: the in-charge parameter and
> the VTT parameter (added in maybe_retrofit_in_chrg).  In fact we'll end up
> with two clones of the ctor: an in-charge and a not-in-charge version (see
> build_cdtor_clones).  That means that the argument position the user
> specified in the attribute argument will refer to different arguments,
> depending on which constructor we're currently dealing with.  This can
> cause a range of problems: wrong errors, confusing warnings, or crashes.
> 
> This patch corrects that; for C we don't have to do anything, and in C++
> we can use num_artificial_parms_for.  It would be wrong to rewrite the
> attributes the user supplied, so I've changed POS to be passed by
> reference so that we don't have to change all the call sites of
> positional_argument and we still get the default_conversion adjustment.
> 
> Attribute format_arg is not affected, because it requires that the
> function returns "const char *" which will never be the case for cdtors.
> 
> 	PR c++/101833
> 	PR c++/47634
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-attribs.cc (positional_argument): Pass POS by reference.  Deal
> 	with FN being either a function declaration or function type.  Use
> 	maybe_adjust_arg_pos_for_attribute.
> 	* c-common.cc (check_function_arguments): Maybe pass FNDECL down to
> 	check_function_format.
> 	* c-common.h (maybe_adjust_arg_pos_for_attribute): Declare.
> 	(positional_argument): Adjust.
> 	* c-format.cc (get_constant): Take EXPR by reference.  Return bool
> 	instead of tree.
> 	(handle_format_arg_attribute): Don't overwrite FORMAT_NUM_EXPR by the
> 	return value of get_constant.
> 	(decode_format_attr): Don't overwrite FORMAT_NUM_EXPR and
> 	FIRST_ARG_NUM_EXPR by the return value of get_constant.
> 	(check_function_format): Adjust a parameter name.
> 	(handle_format_attribute): Maybe pass FNDECL down to decode_format_attr.
> 
> gcc/c/ChangeLog:
> 
> 	* c-objc-common.cc (maybe_adjust_arg_pos_for_attribute): New.
> 
> gcc/cp/ChangeLog:
> 
> 	* tree.cc (maybe_adjust_arg_pos_for_attribute): New.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/attr-format-arg1.C: New test.
> 	* g++.dg/ext/attr-format1.C: New test.
> 	* g++.dg/ext/attr-format2.C: New test.
> 	* g++.dg/ext/attr-format3.C: New test.
> ---
>   gcc/c-family/c-attribs.cc                   | 33 ++++++----
>   gcc/c-family/c-common.cc                    |  4 +-
>   gcc/c-family/c-common.h                     |  3 +-
>   gcc/c-family/c-format.cc                    | 69 ++++++++++-----------
>   gcc/c/c-objc-common.cc                      |  9 +++
>   gcc/cp/tree.cc                              | 19 ++++++
>   gcc/testsuite/g++.dg/ext/attr-format-arg1.C | 26 ++++++++
>   gcc/testsuite/g++.dg/ext/attr-format1.C     | 32 ++++++++++
>   gcc/testsuite/g++.dg/ext/attr-format2.C     | 38 ++++++++++++
>   gcc/testsuite/g++.dg/ext/attr-format3.C     | 15 +++++
>   10 files changed, 198 insertions(+), 50 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/ext/attr-format-arg1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/attr-format1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/attr-format2.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/attr-format3.C
> 
> diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
> index 111a33f405a..bcc316fa259 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 70f55f3a346..9fb664c9872 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..393dda462a4 100644
> --- a/gcc/c-family/c-format.cc
> +++ b/gcc/c-family/c-format.cc
> @@ -78,7 +78,7 @@ 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,
> +static bool get_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);
> @@ -172,14 +172,11 @@ 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 get_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 (!get_constant (type, atname, *format_num_expr, 0, &format_num, 0, false))
>       {
>         *no_add_attrs = true;
>         return NULL_TREE;
> @@ -301,38 +298,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,
> +   N.B. This function modifies EXPR.  */
> +
> +static bool
> +get_constant (const_tree fn, const_tree atname, tree &expr, int argno,

I think this function could use a new name to reflect its new behavior; 
maybe validate_constant?

>   	      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 +370,12 @@ 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 (!get_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 (!get_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 +1147,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<location_t> *arglocs)
>   {
>     tree a;
> @@ -1174,7 +1166,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 +5142,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);

I notice that the comment for the handler member of attribute_spec could 
use an update to note that the node argument now points to an array of 3.

With those changes, OK for GCC 13.

>     function_format_info info;
>   
>   #ifdef TARGET_FORMAT_TYPES
> @@ -5179,7 +5175,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 63164bee638..6c77b9c19b5 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -6118,6 +6118,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<typename T>
> +  D2(T, const char *, ...) __attribute__((format(printf, 3, 4)));
> +};
> +
> +struct V2 : virtual B {
> +  template<typename T>
> +  V2(T, const char *, ...) __attribute__((format(printf, 3, 4)));
> +};
> +
> +struct X {
> +  template<typename T>
> +  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);
> 
> base-commit: 33ba46663cdf2c9e995265eb838585488f81e907
diff mbox series

Patch

diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index 111a33f405a..bcc316fa259 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 70f55f3a346..9fb664c9872 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..393dda462a4 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -78,7 +78,7 @@  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,
+static bool get_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);
@@ -172,14 +172,11 @@  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 get_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 (!get_constant (type, atname, *format_num_expr, 0, &format_num, 0, false))
     {
       *no_add_attrs = true;
       return NULL_TREE;
@@ -301,38 +298,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,
+   N.B. This function modifies EXPR.  */
+
+static bool
+get_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 +370,12 @@  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 (!get_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 (!get_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 +1147,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<location_t> *arglocs)
 {
   tree a;
@@ -1174,7 +1166,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 +5142,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 +5175,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 63164bee638..6c77b9c19b5 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -6118,6 +6118,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<typename T>
+  D2(T, const char *, ...) __attribute__((format(printf, 3, 4)));
+};
+
+struct V2 : virtual B {
+  template<typename T>
+  V2(T, const char *, ...) __attribute__((format(printf, 3, 4)));
+};
+
+struct X {
+  template<typename T>
+  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);