public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jason Merrill <jason@redhat.com>
To: Marek Polacek <polacek@redhat.com>,
	Joseph Myers <joseph@codesourcery.com>,
	GCC Patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] c, c++: attribute format on a ctor with a vbase [PR101833, PR47634]
Date: Wed, 6 Apr 2022 16:55:54 -0400	[thread overview]
Message-ID: <cdba0cb1-6a9e-c995-75e5-0b063cb94646@redhat.com> (raw)
In-Reply-To: <20220401191454.464924-1-polacek@redhat.com>

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?

> 	(handle_format_arg_attribute): Pass 0 to get_constant.
> 	(get_constant): Add new argument adjust_pos, use it.
> 	(check_function_format): Add fndecl argument.  Pass it to
> 	decode_format_attr.
> 	(handle_format_attribute): Get the fndecl from node[2].  Pass it 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                   | 14 ++++---
>   gcc/c-family/c-common.cc                    |  4 +-
>   gcc/c-family/c-common.h                     |  5 ++-
>   gcc/c-family/c-format.cc                    | 46 +++++++++++++--------
>   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, 182 insertions(+), 26 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..6e17847ec9e 100644
> --- a/gcc/c-family/c-attribs.cc
> +++ b/gcc/c-family/c-attribs.cc
> @@ -599,12 +599,15 @@ attribute_takes_identifier_p (const_tree attr_id)
>      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.  */
> +   in by callers only for attributes with more than one argument.
> +   ADJUST_POS is used and non-zero in C++ when the function type has
> +   invisible parameters generated by the compiler, such as the in-charge
> +   or VTT parameters.  */
>   
>   tree
>   positional_argument (const_tree fntype, const_tree atname, tree pos,
>   		     tree_code code, int argno /* = 0 */,
> -		     int flags /* = posargflags () */)
> +		     int flags /* = posargflags () */, int adjust_pos /* = 0 */)
>   {
>     if (pos && TREE_CODE (pos) != IDENTIFIER_NODE
>         && TREE_CODE (pos) != FUNCTION_DECL)
> @@ -690,7 +693,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 +710,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);
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index d034837bb5b..6f08b55d4a7 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -6069,8 +6069,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 (fntype, fndecl, 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..db6ff07db37 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -857,7 +857,7 @@ extern void check_function_arguments_recurse (void (*)
>   					      opt_code);
>   extern bool check_builtin_function_arguments (location_t, vec<location_t>,
>   					      tree, tree, int, tree *);
> -extern void check_function_format (const_tree, tree, int, tree *,
> +extern void check_function_format (const_tree, const_tree, tree, int, tree *,
>   				   vec<location_t> *);
>   extern bool attribute_fallthrough_p (tree);
>   extern tree handle_format_attribute (tree *, tree, tree, int, bool *);
> @@ -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);
> @@ -1494,7 +1495,7 @@ enum posargflags {
>   };
>   
>   extern tree positional_argument (const_tree, const_tree, tree, tree_code,
> -				 int = 0, int = posargflags ());
> +				 int = 0, int = posargflags (), int = 0);
>   
>   extern enum flt_eval_method
>   excess_precision_mode_join (enum flt_eval_method, enum flt_eval_method);
> diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
> index 98f28c0dcc6..87ae7bb73b0 100644
> --- a/gcc/c-family/c-format.cc
> +++ b/gcc/c-family/c-format.cc
> @@ -70,8 +70,8 @@ static GTY(()) tree local_gimple_ptr_node;
>   static GTY(()) tree local_cgraph_node_ptr_node;
>   static GTY(()) tree locus;
>   
> -static bool decode_format_attr (const_tree, tree, tree, function_format_info *,
> -				bool);
> +static bool decode_format_attr (const_tree, const_tree, tree, tree,
> +				function_format_info *, bool);
>   static format_type decode_format_type (const char *, bool * = NULL);
>   
>   static bool check_format_string (const_tree argument,
> @@ -80,7 +80,7 @@ static bool check_format_string (const_tree argument,
>   				 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);
> +			  int flags, bool validated_p, int adjust_pos);
>   static const char *convert_format_name_to_system_name (const char *attr_name);
>   
>   static int first_target_format_type;
> @@ -177,7 +177,7 @@ handle_format_arg_attribute (tree *node, tree atname,
>     unsigned HOST_WIDE_INT format_num = 0;
>   
>     if (tree val = get_constant (type, atname, *format_num_expr, 0, &format_num,
> -			       0, false))
> +			       0, false, 0))
>       *format_num_expr = val;
>     else
>       {
> @@ -305,18 +305,24 @@ check_format_string (const_tree fntype, unsigned HOST_WIDE_INT format_num,
>      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.  */
> +   Returns the converted constant value on success, null otherwise.
> +   ADJUST_POS is used and non-zero in C++ when the function type has
> +   invisible parameters generated by the compiler, such as the in-charge
> +   or VTT parameters.  */
>   
>   static tree
>   get_constant (const_tree fntype, const_tree atname, tree expr, int argno,
> -	      unsigned HOST_WIDE_INT *value, int flags, bool validated_p)
> +	      unsigned HOST_WIDE_INT *value, int flags, bool validated_p,
> +	      int adjust_pos)
>   {
>     /* 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,
> -				      argno, flags))
> +				      argno, flags, adjust_pos))
>       {
> -      *value = TREE_INT_CST_LOW (val);
> +      /* Invisible parameters may have been added by the compiler, so adjust
> +	 the position accordingly.  */
> +      *value = TREE_INT_CST_LOW (val) + adjust_pos;
>         return val;
>       }
>   
> @@ -332,8 +338,8 @@ get_constant (const_tree fntype, const_tree atname, tree expr, int argno,
>      attributes are successfully decoded, false otherwise.  */
>   
>   static bool
> -decode_format_attr (const_tree fntype, tree atname, tree args,
> -		    function_format_info *info, bool validated_p)
> +decode_format_attr (const_tree fntype, const_tree fndecl, tree atname,
> +		    tree args, function_format_info *info, bool validated_p)
>   {
>     tree format_type_id = TREE_VALUE (args);
>     /* Note that TREE_VALUE (args) is changed in place below.  Ditto
> @@ -372,15 +378,19 @@ decode_format_attr (const_tree fntype, tree atname, tree args,
>   	}
>       }
>   
> +  int adjust_pos = maybe_adjust_arg_pos_for_attribute (fndecl);
> +
>     if (tree val = get_constant (fntype, atname, *format_num_expr,
> -			       2, &info->format_num, 0, validated_p))
> +			       2, &info->format_num, 0, validated_p,
> +			       adjust_pos))
>       *format_num_expr = val;
>     else
>       return false;
>   
>     if (tree val = get_constant (fntype, atname, *first_arg_num_expr,
>   			       3, &info->first_arg_num,
> -			       (POSARG_ZERO | POSARG_ELLIPSIS), validated_p))
> +			       (POSARG_ZERO | POSARG_ELLIPSIS), validated_p,
> +			       adjust_pos))
>       *first_arg_num_expr = val;
>     else
>       return false;
> @@ -1160,8 +1170,8 @@ decode_format_type (const char *s, bool *is_raw /* = NULL */)
>      attribute themselves.  */
>   
>   void
> -check_function_format (const_tree fntype, tree attrs, int nargs,
> -		       tree *argarray, vec<location_t> *arglocs)
> +check_function_format (const_tree fntype, const_tree fndecl, tree attrs,
> +		       int nargs, tree *argarray, vec<location_t> *arglocs)
>   {
>     tree a;
>   
> @@ -1174,7 +1184,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 (fntype, fndecl, atname, TREE_VALUE (a), &info,
>   			      /*validated=*/true);
>   	  if (warn_format)
>   	    {
> @@ -5150,10 +5160,11 @@ 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;
> +  const_tree fndecl = node[2];
>     function_format_info info;
>   
>   #ifdef TARGET_FORMAT_TYPES
> @@ -5179,7 +5190,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 (type, fndecl, 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 780a8d89165..ddfeb8ce428 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -6101,6 +6101,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;
> +}
> +
>   \f
>   /* 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..d6c9e40756f
> --- /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: 95533fe4f014c10dd18de649927668aba6117daf


  reply	other threads:[~2022-04-06 20:55 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-04-01 19:14 Marek Polacek
2022-04-06 20:55 ` Jason Merrill [this message]
2022-04-08 19:21   ` Marek Polacek
2022-04-11 20:39     ` Jason Merrill
2022-04-12 18:38       ` Marek Polacek
2022-04-12 20:01         ` Jason Merrill
2022-04-13 23:17           ` [PATCH v2] " Marek Polacek
2022-04-15  3:55             ` Jason Merrill
2022-04-21 19:53               ` [PATCH v3] " Marek Polacek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cdba0cb1-6a9e-c995-75e5-0b063cb94646@redhat.com \
    --to=jason@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=joseph@codesourcery.com \
    --cc=polacek@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).