Hi! These testcases show another problem with -fsanitize=address vs. musttail tail calls. In particular, there can be .ASAN_MARK (POISON, &a, 4); etc. calls after a tail call and those just prevent the tailc pass to mark the musttail calls as [tail call]. Normally, the sanopt pass (which comes after tailc) will optimize those away, the optimization is if there are no .ASAN_CHECK calls or normal function calls dominated by those .ASAN_MARK (POSION, ...) calls, the poison is not needed, because in the epilog sequence (the one dealt with in the patch posted earlier today) all the stack slots are unpoisoned anyway (or poisoned for use-after-return). Unlike __builtin_tsan_exit_function, .ASAN_MARK is not a real function and is always expanded inline, so can be never tail called successfully, so the patch just ignores those for the cfun->has_musttail && diag_musttail cases. If there is a non-musttail call, it will fail worst case during expansion because there is the epilog asan sequence.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk and 15.2 later? 2025-06-12 Jakub Jelinek <ja...@redhat.com> PR middle-end/120608 * tree-tailcall.cc (empty_eh_cleanup): Ignore .ASAN_MARK (POISON) internal calls for the cfun->has_musttail case and diag_musttail. (find_tail_calls): Likewise. * c-c++-common/asan/pr120608-1.c: New test. * c-c++-common/asan/pr120608-2.c: New test. --- gcc/tree-tailcall.cc.jj 2025-05-01 08:25:46.968898135 +0200 +++ gcc/tree-tailcall.cc 2025-06-12 14:52:52.171918526 +0200 @@ -528,6 +528,10 @@ empty_eh_cleanup (basic_block bb, int *e *eh_has_tsan_func_exit = 1; continue; } + if (eh_has_tsan_func_exit + && sanitize_flags_p (SANITIZE_ADDRESS) + && asan_mark_p (g, ASAN_MARK_POISON)) + continue; if (is_gimple_resx (g) && stmt_can_throw_external (cfun, g)) return true; return false; @@ -619,6 +623,12 @@ find_tail_calls (basic_block bb, struct continue; } + if (cfun->has_musttail + && sanitize_flags_p (SANITIZE_ADDRESS) + && asan_mark_p (stmt, ASAN_MARK_POISON) + && diag_musttail) + continue; + if (!last_stmt) last_stmt = stmt; /* Check for a call. */ @@ -995,6 +1005,12 @@ find_tail_calls (basic_block bb, struct continue; } + if (cfun->has_musttail + && sanitize_flags_p (SANITIZE_ADDRESS) + && asan_mark_p (stmt, ASAN_MARK_POISON) + && diag_musttail) + continue; + if (gimple_code (stmt) != GIMPLE_ASSIGN) { maybe_error_musttail (call, _("unhandled code after call"), --- gcc/testsuite/c-c++-common/asan/pr120608-1.c.jj 2025-06-12 15:09:45.622485409 +0200 +++ gcc/testsuite/c-c++-common/asan/pr120608-1.c 2025-06-12 15:14:12.066955322 +0200 @@ -0,0 +1,43 @@ +/* PR middle-end/120608 */ +/* { dg-do run { target musttail } } */ +/* { dg-options "-O2 -fsanitize=address" } */ + +__attribute__((noipa)) void +foo (int *x, int *y, int *z) +{ + ++x[0]; + ++y[0]; + ++z[0]; +} + +__attribute__((noipa)) void +bar (int *x, int *y, int *z) +{ + if (x || y || z) + __builtin_abort (); +} + +__attribute__((noipa)) void +baz (int *x, int *y, int *z) +{ + (void) x; (void) y; (void) z; + int a = 42, b = -42, c = 0; + foo (&a, &b, &c); + [[gnu::musttail]] return bar (0, 0, 0); +} + +__attribute__((noipa)) void +qux (int *x, int *y, int *z) +{ + (void) x; (void) y; (void) z; + int a = 42, b = -42, c = 0; + foo (&a, &b, &c); + [[gnu::musttail]] return bar (0, 0, 0); +} + +int +main () +{ + baz (0, 0, 0); + qux (0, 0, 0); +} --- gcc/testsuite/c-c++-common/asan/pr120608-2.c.jj 2025-06-12 15:13:19.494651848 +0200 +++ gcc/testsuite/c-c++-common/asan/pr120608-2.c 2025-06-12 15:27:12.930602784 +0200 @@ -0,0 +1,39 @@ +/* PR middle-end/120608 */ +/* { dg-do run { target musttail } } */ +/* { dg-options "-O2 -fsanitize=address" } */ +/* { dg-set-target-env-var ASAN_OPTIONS "detect_stack_use_after_return=1" } */ +/* { dg-shouldfail "asan" } */ + +__attribute__((noipa)) void +foo (int *x, int *y, int *z) +{ + ++x[0]; + ++y[0]; + ++z[0]; +} + +__attribute__((noipa)) void +bar (int *x, int *y, int *z) +{ + volatile int a = x[0] + y[0] + z[0]; +} + +__attribute__((noipa)) void +baz (int *x, int *y, int *z) +{ + (void) x; (void) y; (void) z; + int a = 42, b = -42, c = 0; + foo (&a, &b, &c); + [[gnu::musttail]] return bar (&a, &b, &c); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ +} /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" "" { target *-*-* } .-1 } */ + /* { dg-warning "address of automatic variable 'c' passed to 'musttail' call argument" "" { target *-*-* } .-2 } */ + +int +main () +{ + baz (0, 0, 0); +} + +// { dg-output "ERROR: AddressSanitizer: stack-use-after-return on address.*(\n|\r\n|\r)" } +// { dg-output "READ of size .*" } +// { dg-output ".*'a' \\(line 25\\) <== Memory access at offset \[0-9\]* is inside this variable.*" } Jakub