On Thu, 12 Jun 2025, Jakub Jelinek wrote:

> Hi!
> 
> The following testcase is rejected by GCC 15 but accepted (with
> s/gnu/clang/) by clang.
> The problem is that we want to execute a sequence of instructions to
> unpoison all automatic variables in the function and mark the var block
> allocated for use-after-return sanitization poisoned after the call,
> so we were just disabling tail calls if there are any instructions
> returned from asan_emit_stack_protection.
> It is fine and necessary for normal tail calls, but for musttail
> tail calls we actually document that accessing the automatic vars of
> the caller is UB as if they end their lifetime right before the tail
> call, so we also want address sanitizer user-after-return to diagnose
> that.
> 
> The following patch will only disable normal tail calls when that sequence
> is present, for musttail it will arrange to emit a copy of that sequence
> before the tail call sequence.  That sequence only tweaks the shadow memory
> and nothing in the code emitted by call expansion should touch the shadow
> memory, so it is ok to emit it already before argument setup.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk and
> later 15.2?

OK.

Thanks,
Richard.

> 2025-06-12  Jakub Jelinek  <ja...@redhat.com>
> 
>       PR middle-end/120608
>       * cfgexpand.cc: Include rtl-iter.h.
>       (expand_gimple_tailcall): Add ASAN_EPILOG_SEQ argument, if non-NULL
>       and expand_gimple_stmt emitted a tail call, emit a copy of that
>       insn sequence before the call sequence.
>       (expand_gimple_basic_block): Remove DISABLE_TAIL_CALLS argument, add
>       ASAN_EPILOG_SEQ argument.  Disable tail call flag only on non-musttail
>       calls if that flag is set, pass it to expand_gimple_tailcall.
>       (pass_expand::execute): Pass VAR_RET_SEQ directly as last
>       expand_gimple_basic_block argument rather than its comparison with
>       NULL.
> 
>       * g++.dg/asan/pr120608.C: New test.
> 
> --- gcc/cfgexpand.cc.jj       2025-06-12 10:05:14.961227842 +0200
> +++ gcc/cfgexpand.cc  2025-06-12 11:38:06.875469134 +0200
> @@ -75,6 +75,7 @@ along with GCC; see the file COPYING3.
>  #include "builtins.h"
>  #include "opts.h"
>  #include "gimple-range.h"
> +#include "rtl-iter.h"
>  
>  /* Some systems use __main in a way incompatible with its use in gcc, in 
> these
>     cases use the macros NAME__MAIN to give a quoted symbol and SYMBOL__MAIN 
> to
> @@ -4434,9 +4435,10 @@ expand_gimple_stmt (gimple *stmt)
>     tailcall) and the normal result happens via a sqrt instruction.  */
>  
>  static basic_block
> -expand_gimple_tailcall (basic_block bb, gcall *stmt, bool *can_fallthru)
> +expand_gimple_tailcall (basic_block bb, gcall *stmt, bool *can_fallthru,
> +                     rtx_insn *asan_epilog_seq)
>  {
> -  rtx_insn *last2, *last;
> +  rtx_insn *last2, *last, *first = get_last_insn ();
>    edge e;
>    edge_iterator ei;
>    profile_probability probability;
> @@ -4453,6 +4455,58 @@ expand_gimple_tailcall (basic_block bb,
>    return NULL;
>  
>   found:
> +
> +  if (asan_epilog_seq)
> +    {
> +      /* We need to emit a copy of the asan_epilog_seq before
> +      the insns emitted by expand_gimple_stmt above.  The sequence
> +      can contain labels, which need to be remapped.  */
> +      hash_map<rtx, rtx> label_map;
> +      start_sequence ();
> +      emit_note (NOTE_INSN_DELETED);
> +      for (rtx_insn *insn = asan_epilog_seq; insn; insn = NEXT_INSN (insn))
> +     switch (GET_CODE (insn))
> +       {
> +       case INSN:
> +       case CALL_INSN:
> +       case JUMP_INSN:
> +         emit_copy_of_insn_after (insn, get_last_insn ());
> +         break;
> +       case CODE_LABEL:
> +         label_map.put ((rtx) insn, (rtx) emit_label (gen_label_rtx ()));
> +         break;
> +       case BARRIER:
> +         emit_barrier ();
> +         break;
> +       default:
> +         gcc_unreachable ();
> +       }
> +      for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
> +     if (JUMP_P (insn))
> +       {
> +         subrtx_ptr_iterator::array_type array;
> +         FOR_EACH_SUBRTX_PTR (iter, array, &PATTERN (insn), ALL)
> +           {
> +             rtx *loc = *iter;
> +             if (LABEL_REF_P (*loc))
> +               {
> +                 rtx *lab = label_map.get ((rtx) label_ref_label (*loc));
> +                 gcc_assert (lab);
> +                 set_label_ref_label (*loc, as_a <rtx_insn *> (*lab));
> +               }
> +           }
> +         if (JUMP_LABEL (insn))
> +           {
> +             rtx *lab = label_map.get (JUMP_LABEL (insn));
> +             gcc_assert (lab);
> +             JUMP_LABEL (insn) = *lab;
> +           }
> +       }
> +      asan_epilog_seq = NEXT_INSN (get_insns ());
> +      end_sequence ();
> +      emit_insn_before (asan_epilog_seq, NEXT_INSN (first));
> +    }
> +
>    /* ??? Wouldn't it be better to just reset any pending stack adjust?
>       Any instructions emitted here are about to be deleted.  */
>    do_pending_stack_adjust ();
> @@ -6118,7 +6172,7 @@ reorder_operands (basic_block bb)
>  /* Expand basic block BB from GIMPLE trees to RTL.  */
>  
>  static basic_block
> -expand_gimple_basic_block (basic_block bb, bool disable_tail_calls)
> +expand_gimple_basic_block (basic_block bb, rtx_insn *asan_epilog_seq)
>  {
>    gimple_stmt_iterator gsi;
>    gimple *stmt = NULL;
> @@ -6423,14 +6477,16 @@ expand_gimple_basic_block (basic_block b
>       {
>         gcall *call_stmt = dyn_cast <gcall *> (stmt);
>         if (call_stmt
> +           && asan_epilog_seq
>             && gimple_call_tail_p (call_stmt)
> -           && disable_tail_calls)
> +           && !gimple_call_must_tail_p (call_stmt))
>           gimple_call_set_tail (call_stmt, false);
>  
>         if (call_stmt && gimple_call_tail_p (call_stmt))
>           {
>             bool can_fallthru;
> -           new_bb = expand_gimple_tailcall (bb, call_stmt, &can_fallthru);
> +           new_bb = expand_gimple_tailcall (bb, call_stmt, &can_fallthru,
> +                                            asan_epilog_seq);
>             if (new_bb)
>               {
>                 if (can_fallthru)
> @@ -7207,7 +7263,7 @@ pass_expand::execute (function *fun)
>    head_end_for_bb.create (last_basic_block_for_fn (fun));
>    FOR_BB_BETWEEN (bb, init_block->next_bb, EXIT_BLOCK_PTR_FOR_FN (fun),
>                 next_bb)
> -    bb = expand_gimple_basic_block (bb, var_ret_seq != NULL_RTX);
> +    bb = expand_gimple_basic_block (bb, var_ret_seq);
>    disable_ranger (fun);
>    FOR_BB_BETWEEN (bb, init_block->next_bb, EXIT_BLOCK_PTR_FOR_FN (fun),
>                 next_bb)
> --- gcc/testsuite/g++.dg/asan/pr120608.C.jj   2025-06-12 12:39:35.063565552 
> +0200
> +++ gcc/testsuite/g++.dg/asan/pr120608.C      2025-06-12 12:44:15.431004623 
> +0200
> @@ -0,0 +1,17 @@
> +// PR middle-end/120608
> +// { dg-do compile { target musttail } }
> +// { dg-options "-fsanitize=address" }
> +// { dg-skip-if "" { *-*-* } { "*" } { "-O0" } }
> +
> +struct A;
> +struct B { unsigned long b; };
> +typedef const char *(*F) (void *u, const char *v, void *w, const struct A *x,
> +                       unsigned long y, struct B z);
> +struct A { F a; };
> +
> +const char *
> +foo (void *u, const char *v, void *w, const struct A *x, unsigned long y,
> +     struct B z)
> +{
> +  [[gnu::musttail]] return x->a (u, v, w, x, y, z);
> +}
> 
>       Jakub
> 
> 

-- 
Richard Biener <rguent...@suse.de>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to