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?

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

Reply via email to