Skip to content

Conversation

@xingxue-ibm
Copy link
Contributor

@xingxue-ibm xingxue-ibm commented Nov 24, 2025

The POSIX standard POSIX.1-2024 specifies how the utility reacts to signals as follows.

ASYNCHRONOUS EVENTS

    The ASYNCHRONOUS EVENTS section lists how the utility reacts to such events as signals and what signals are caught.

    Default Behavior: When this section is listed as "Default.", or it refers to "the standard action" for any signal, it means that the action taken as a result of the signal shall be as follows:

        If the action inherited from the invoking process, according to the rules of inheritance of signal actions defined in the System Interfaces volume of POSIX.1-2024, is for the signal to be ignored, the utility shall ignore the signal.
        If the action inherited from the invoking process, according to the rules of inheritance of signal actions defined in System Interfaces volume of POSIX.1-2024, is the default signal action, the result of the utility's execution shall be as if the default signal action had been taken.

    When the required action is for the signal to terminate the utility, the utility may catch the signal, perform some additional processing (such as deleting temporary files), restore the default signal action, and resignal itself.

This PR updates the LLVM/Clang’s behavior accordingly by not installing a signal handler when the inherited disposition is SIG_IGN, and by ensuring that the exit code reflects the terminating signal number, resignaling after the signal is handled. Additionally, the clangd unit test ThreadCrashReporterTest is excluded on Unix platforms because it expects execution to continue after returning from the SIGUSR1 signal handler.

<signal.h> specifies the default action of signals.

@xingxue-ibm xingxue-ibm self-assigned this Nov 24, 2025
@xingxue-ibm xingxue-ibm added the clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' label Nov 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 24, 2025

@llvm/pr-subscribers-clangd
@llvm/pr-subscribers-llvm-support
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang-driver

Author: Xing Xue (xingxue-ibm)

Changes

The POSIX standard POSIX.1-2024 specifies how the utility reacts to signals as follows.

ASYNCHRONOUS EVENTS

    The ASYNCHRONOUS EVENTS section lists how the utility reacts to such events as signals and what signals are caught.

    Default Behavior: When this section is listed as "Default.", or it refers to "the standard action" for any signal, it means that the action taken as a result of the signal shall be as follows:

        If the action inherited from the invoking process, according to the rules of inheritance of signal actions defined in the System Interfaces volume of POSIX.1-2024, is for the signal to be ignored, the utility shall ignore the signal.
        If the action inherited from the invoking process, according to the rules of inheritance of signal actions defined in System Interfaces volume of POSIX.1-2024, is the default signal action, the result of the utility's execution shall be as if the default signal action had been taken.

    When the required action is for the signal to terminate the utility, the utility may catch the signal, perform some additional processing (such as deleting temporary files), restore the default signal action, and resignal itself.

This PR updates the LLVM/Clang’s behavior accordingly by not installing a signal handler when the inherited disposition is SIG_IGN, and by ensuring that the exit code reflects the terminating signal number, resignaling after the signal is handled. Test cases are updated as well, replacing not &lt;command&gt; with not --crash &lt;command&gt; to verify that &lt;command&gt; fails due to a signal rather than a normal error. Additionally, the clangd unit test ThreadCrashReporterTest is excluded on Unix platforms because it expects execution to continue after returning from the SIGUSR1 signal handler.

<signal.h> specifies the default action of signals.


Full diff: https://github.com/llvm/llvm-project/pull/169340.diff

14 Files Affected:

  • (modified) clang-tools-extra/clangd/unittests/ThreadCrashReporterTests.cpp (+12)
  • (modified) clang/lib/Driver/Driver.cpp (+7)
  • (modified) clang/test/Driver/crash-diagnostics-dir-3.c (+1-1)
  • (modified) clang/test/Driver/crash-diagnostics-dir.c (+1-1)
  • (modified) clang/test/Driver/crash-report-clang-cl.cpp (+1-1)
  • (modified) clang/test/Driver/crash-report-header.h (+1-1)
  • (modified) clang/test/Driver/crash-report-spaces.c (+1-1)
  • (modified) clang/test/Driver/crash-report-with-asserts.c (+2-2)
  • (modified) clang/test/Driver/crash-report.cpp (+2-2)
  • (modified) clang/test/Driver/emit-reproducer.c (+7-7)
  • (modified) clang/test/Driver/output-file-cleanup.c (+1-1)
  • (modified) clang/tools/driver/driver.cpp (+17-1)
  • (modified) llvm/lib/Support/CrashRecoveryContext.cpp (+5-1)
  • (modified) llvm/lib/Support/Unix/Signals.inc (+17-10)
diff --git a/clang-tools-extra/clangd/unittests/ThreadCrashReporterTests.cpp b/clang-tools-extra/clangd/unittests/ThreadCrashReporterTests.cpp
index 0eb8d472c6702..1c86fcbea4a4b 100644
--- a/clang-tools-extra/clangd/unittests/ThreadCrashReporterTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ThreadCrashReporterTests.cpp
@@ -13,6 +13,17 @@
 #include <csignal>
 #include <string>
 
+// According to the POSIX specification, if the inherited disposition of a
+// signal is the default action, the behavior of utilitys must be as if the
+// default action had been taken. When the required default action is to
+// terminate the process, such as for SIGUSR1, the utility may catch the
+// signal, perform additional processing, restore the default disposition,
+// and then re-signal itself. This causes the process to terminate as
+// required. Because of this behavior, the crash-reporter test here is not
+// suitable for Unix platforms.
+
+#ifndef LLVM_ON_UNIX
+
 namespace clang {
 namespace clangd {
 
@@ -76,3 +87,4 @@ TEST(ThreadCrashReporterTest, All) {
 } // namespace
 } // namespace clangd
 } // namespace clang
+#endif // !LLVM_ON_UNIX
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index c6dd66d9efef3..85fc200d88169 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -2298,7 +2298,14 @@ int Driver::ExecuteCompilation(
 
     if (!FailingCommand->getCreator().hasGoodDiagnostics() || CommandRes != 1) {
       // FIXME: See FIXME above regarding result code interpretation.
+#if LLVM_ON_UNIX
+      // On Unix, signals are represented by return codes of 128 plus the
+      // signal number. Return code 255 is excluded because some tools,
+      // such as llvm-ifs, exit with code 255 (-1) on failure.
+      if (CommandRes > 128 && CommandRes != 255)
+#else
       if (CommandRes < 0)
+#endif
         Diag(clang::diag::err_drv_command_signalled)
             << FailingTool.getShortName();
       else
diff --git a/clang/test/Driver/crash-diagnostics-dir-3.c b/clang/test/Driver/crash-diagnostics-dir-3.c
index a91bc48d7e462..8d22df85abd2f 100644
--- a/clang/test/Driver/crash-diagnostics-dir-3.c
+++ b/clang/test/Driver/crash-diagnostics-dir-3.c
@@ -1,6 +1,6 @@
 // RUN: export LSAN_OPTIONS=detect_leaks=0
 // RUN: rm -rf %t
-// RUN: not env CLANG_CRASH_DIAGNOSTICS_DIR=%t %clang -c %s -o - 2>&1 | FileCheck %s
+// RUN: not --crash env CLANG_CRASH_DIAGNOSTICS_DIR=%t %clang -c %s -o - 2>&1 | FileCheck %s
 #pragma clang __debug parser_crash
 // CHECK: Preprocessed source(s) and associated run script(s) are located at:
 // CHECK: diagnostic msg: {{.*}}{{/|\\}}crash-diagnostics-dir-3.c.tmp{{(/|\\).*}}.c
diff --git a/clang/test/Driver/crash-diagnostics-dir.c b/clang/test/Driver/crash-diagnostics-dir.c
index 16382eff1cde7..56c77beae7234 100644
--- a/clang/test/Driver/crash-diagnostics-dir.c
+++ b/clang/test/Driver/crash-diagnostics-dir.c
@@ -1,6 +1,6 @@
 // RUN: export LSAN_OPTIONS=detect_leaks=0
 // RUN: rm -rf %t
-// RUN: not %clang -fcrash-diagnostics-dir=%t -c %s -o - 2>&1 | FileCheck %s
+// RUN: not --crash %clang -fcrash-diagnostics-dir=%t -c %s -o - 2>&1 | FileCheck %s
 #pragma clang __debug parser_crash
 // CHECK: Preprocessed source(s) and associated run script(s) are located at:
 // CHECK: diagnostic msg: {{.*}}{{/|\\}}crash-diagnostics-dir.c.tmp{{(/|\\).*}}.c
diff --git a/clang/test/Driver/crash-report-clang-cl.cpp b/clang/test/Driver/crash-report-clang-cl.cpp
index 963c3b6d0ab03..3efeb8088db52 100644
--- a/clang/test/Driver/crash-report-clang-cl.cpp
+++ b/clang/test/Driver/crash-report-clang-cl.cpp
@@ -2,7 +2,7 @@
 // RUN: rm -rf %t
 // RUN: mkdir %t
 
-// RUN: not %clang_cl -fsyntax-only /Brepro /source-charset:utf-8 \
+// RUN: not --crash %clang_cl -fsyntax-only /Brepro /source-charset:utf-8 \
 // RUN:     -fcrash-diagnostics-dir=%t -- %s 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-clang-cl-*.cpp | FileCheck --check-prefix=CHECKSRC %s
 // RUN: cat %t/crash-report-clang-cl-*.sh | FileCheck --check-prefix=CHECKSH %s
diff --git a/clang/test/Driver/crash-report-header.h b/clang/test/Driver/crash-report-header.h
index 04865a0cc300f..31c71ff625c05 100644
--- a/clang/test/Driver/crash-report-header.h
+++ b/clang/test/Driver/crash-report-header.h
@@ -1,7 +1,7 @@
 // RUN: export LSAN_OPTIONS=detect_leaks=0
 // RUN: rm -rf %t
 // RUN: mkdir %t
-// RUN: env TMPDIR="%t" TEMP="%t" TMP="%t" RC_DEBUG_OPTIONS=1 not %clang -fsyntax-only %s 2>&1 | FileCheck %s
+// RUN: env TMPDIR="%t" TEMP="%t" TMP="%t" RC_DEBUG_OPTIONS=1 not --crash %clang -fsyntax-only %s 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-header-*.h | FileCheck --check-prefix=CHECKSRC "%s"
 // RUN: cat %t/crash-report-header-*.sh | FileCheck --check-prefix=CHECKSH "%s"
 // REQUIRES: crash-recovery
diff --git a/clang/test/Driver/crash-report-spaces.c b/clang/test/Driver/crash-report-spaces.c
index b4d8ac1f57e83..c01c8df0552de 100644
--- a/clang/test/Driver/crash-report-spaces.c
+++ b/clang/test/Driver/crash-report-spaces.c
@@ -2,7 +2,7 @@
 // RUN: rm -rf "%t"
 // RUN: mkdir "%t"
 // RUN: cp "%s" "%t/crash report spaces.c"
-// RUN: env TMPDIR="%t" TEMP="%t" TMP="%t" RC_DEBUG_OPTIONS=1 not %clang -fsyntax-only "%t/crash report spaces.c" 2>&1 | FileCheck "%s"
+// RUN: env TMPDIR="%t" TEMP="%t" TMP="%t" RC_DEBUG_OPTIONS=1 not --crash %clang -fsyntax-only "%t/crash report spaces.c" 2>&1 | FileCheck "%s"
 // RUN: cat "%t/crash report spaces"-*.c | FileCheck --check-prefix=CHECKSRC "%s"
 // RUN: cat "%t/crash report spaces"-*.sh | FileCheck --check-prefix=CHECKSH "%s"
 // REQUIRES: crash-recovery
diff --git a/clang/test/Driver/crash-report-with-asserts.c b/clang/test/Driver/crash-report-with-asserts.c
index 686c49f339fb7..0b619d9ec2f46 100644
--- a/clang/test/Driver/crash-report-with-asserts.c
+++ b/clang/test/Driver/crash-report-with-asserts.c
@@ -12,13 +12,13 @@
 
 // RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1                  \
 // RUN:  CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1                         \
-// RUN:  not %clang %s @%t.rsp -DASSERT 2>&1 | FileCheck %s
+// RUN:  not --crash %clang %s @%t.rsp -DASSERT 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-*.c | FileCheck --check-prefix=CHECKSRC %s
 // RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s
 
 // RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1                  \
 // RUN:  CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1                         \
-// RUN:  not %clang %s @%t.rsp -DUNREACHABLE 2>&1 | FileCheck %s
+// RUN:  not --crash %clang %s @%t.rsp -DUNREACHABLE 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-with-asserts-*.c | FileCheck --check-prefix=CHECKSRC %s
 // RUN: cat %t/crash-report-with-asserts-*.sh | FileCheck --check-prefix=CHECKSH %s
 
diff --git a/clang/test/Driver/crash-report.cpp b/clang/test/Driver/crash-report.cpp
index 59eee65af57ee..4b5adfc02bff4 100644
--- a/clang/test/Driver/crash-report.cpp
+++ b/clang/test/Driver/crash-report.cpp
@@ -12,13 +12,13 @@
 
 // RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1                  \
 // RUN:  CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1                         \
-// RUN:  not %clang %s @%t.rsp -DPARSER 2>&1 | FileCheck %s
+// RUN:  not --crash %clang %s @%t.rsp -DPARSER 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s
 // RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s
 
 // RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1                  \
 // RUN:  CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1                         \
-// RUN:  not %clang %s @%t.rsp -DCRASH 2>&1 | FileCheck %s
+// RUN:  not --crash %clang %s @%t.rsp -DCRASH 2>&1 | FileCheck %s
 // RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s
 // RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s
 
diff --git a/clang/test/Driver/emit-reproducer.c b/clang/test/Driver/emit-reproducer.c
index 18e1b4e41b91d..28c585193a7d0 100644
--- a/clang/test/Driver/emit-reproducer.c
+++ b/clang/test/Driver/emit-reproducer.c
@@ -3,13 +3,13 @@
 
 // RUN: echo "%s -fcrash-diagnostics-dir=%t -fsyntax-only" | sed -e 's/\\/\\\\/g' > %t.rsp
 
-// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=off    2>&1 | FileCheck %s --check-prefix=NOT
-// RUN: not %clang -DFATAL @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT
-// RUN: not %clang -DFATAL @%t.rsp                        2>&1 | FileCheck %s
-// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=crash  2>&1 | FileCheck %s
-// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=error  2>&1 | FileCheck %s
-// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=always 2>&1 | FileCheck %s
-// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer        2>&1 | FileCheck %s
+// RUN: not --crash %clang -DFATAL @%t.rsp -gen-reproducer=off    2>&1 | FileCheck %s --check-prefix=NOT
+// RUN: not --crash %clang -DFATAL @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT
+// RUN: not --crash %clang -DFATAL @%t.rsp                        2>&1 | FileCheck %s
+// RUN: not --crash %clang -DFATAL @%t.rsp -gen-reproducer=crash  2>&1 | FileCheck %s
+// RUN: not --crash %clang -DFATAL @%t.rsp -gen-reproducer=error  2>&1 | FileCheck %s
+// RUN: not --crash %clang -DFATAL @%t.rsp -gen-reproducer=always 2>&1 | FileCheck %s
+// RUN: not --crash %clang -DFATAL @%t.rsp -gen-reproducer        2>&1 | FileCheck %s
 
 // RUN: not %clang -DERROR @%t.rsp -gen-reproducer=off    2>&1 | FileCheck %s --check-prefix=NOT
 // RUN: not %clang -DERROR @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT
diff --git a/clang/test/Driver/output-file-cleanup.c b/clang/test/Driver/output-file-cleanup.c
index 3628df8192652..b5f548dc0b01a 100644
--- a/clang/test/Driver/output-file-cleanup.c
+++ b/clang/test/Driver/output-file-cleanup.c
@@ -2,7 +2,7 @@
 // RUN: rm -f "%t.d" "%t1.s" "%t2.s" "%t3.s" "%t4.s" "%t5.s"
 //
 // RUN: touch %t.s
-// RUN: not %clang -S -DCRASH -o %t.s -MMD -MF %t.d %s
+// RUN: not --crash %clang -S -DCRASH -o %t.s -MMD -MF %t.d %s
 // RUN: test ! -f %t.s
 // RUN: test ! -f %t.d
 
diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp
index 1e2c9884ba63d..262043bbb78ad 100644
--- a/clang/tools/driver/driver.cpp
+++ b/clang/tools/driver/driver.cpp
@@ -54,6 +54,9 @@
 #include <optional>
 #include <set>
 #include <system_error>
+#if LLVM_ON_UNIX
+#include <signal.h>
+#endif
 
 using namespace clang;
 using namespace clang::driver;
@@ -400,6 +403,7 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) {
   Driver::CommandStatus CommandStatus = Driver::CommandStatus::Ok;
   // Pretend the first command failed if ReproStatus is Always.
   const Command *FailingCommand = nullptr;
+  int CommandRes = 0;
   if (!C->getJobs().empty())
     FailingCommand = &*C->getJobs().begin();
   if (C && !C->containsError()) {
@@ -407,7 +411,7 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) {
     Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
 
     for (const auto &P : FailingCommands) {
-      int CommandRes = P.first;
+      CommandRes = P.first;
       FailingCommand = P.second;
       if (!Res)
         Res = CommandRes;
@@ -464,6 +468,18 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) {
     Res = 1;
 #endif
 
+#if LLVM_ON_UNIX
+  // On Unix, signals are represented by return codes of 128 plus the signal
+  // number. If the return code indicates it was from a signal handler, raise
+  // the signal so that the exit code includes the signal number, as required
+  // by POSIX. Return code 255 is excluded because some tools, such as
+  // llvm-ifs, exit with code 255 (-1) on failure.
+  if (CommandRes > 128 && CommandRes != 255) {
+    llvm::sys::unregisterHandlers();
+    raise(CommandRes - 128);
+  }
+#endif
+
   // If we have multiple failing commands, we return the result of the first
   // failing command.
   return Res;
diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp
index 1ba0c2383daec..c6bfa4d0245da 100644
--- a/llvm/lib/Support/CrashRecoveryContext.cpp
+++ b/llvm/lib/Support/CrashRecoveryContext.cpp
@@ -398,7 +398,11 @@ static void installExceptionOrSignalHandlers() {
   sigemptyset(&Handler.sa_mask);
 
   for (unsigned i = 0; i != NumSignals; ++i) {
-    sigaction(Signals[i], &Handler, &PrevActions[i]);
+    struct sigaction act;
+    sigaction(Signals[i], NULL, &act);
+    // Don't install signal handler if the disposition of a signal is SIG_IGN.
+    if (act.sa_handler != SIG_IGN)
+      sigaction(Signals[i], &Handler, &PrevActions[i]);
   }
 }
 
diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc
index 78d6540db98a7..7e674eba01c4e 100644
--- a/llvm/lib/Support/Unix/Signals.inc
+++ b/llvm/lib/Support/Unix/Signals.inc
@@ -327,8 +327,12 @@ static void RegisterHandlers() { // Not signal-safe.
     }
     sigemptyset(&NewHandler.sa_mask);
 
-    // Install the new handler, save the old one in RegisteredSignalInfo.
-    sigaction(Signal, &NewHandler, &RegisteredSignalInfo[Index].SA);
+    // Install the new handler if the signal disposition isn't SIG_IGN,
+    // save the old one in RegisteredSignalInfo.
+    struct sigaction act;
+    sigaction(Signal, NULL, &act);
+    if (act.sa_handler != SIG_IGN)
+      sigaction(Signal, &NewHandler, &RegisteredSignalInfo[Index].SA);
     RegisteredSignalInfo[Index].SigNo = Signal;
     ++NumRegisteredSignals;
   };
@@ -408,14 +412,12 @@ static void SignalHandler(int Sig, siginfo_t *Info, void *) {
   // Otherwise if it is a fault (like SEGV) run any handler.
   llvm::sys::RunSignalHandlers();
 
-#ifdef __s390__
-  // On S/390, certain signals are delivered with PSW Address pointing to
-  // *after* the faulting instruction.  Simply returning from the signal
-  // handler would continue execution after that point, instead of
-  // re-raising the signal.  Raise the signal manually in those cases.
-  if (Sig == SIGILL || Sig == SIGFPE || Sig == SIGTRAP)
-    raise(Sig);
-#endif
+  // Resignal if it is a kill signal so that the exit code contains the
+  // terminating signal number.
+  if (llvm::is_contained(KillSigs, Sig)) {
+    raise(Sig); // Execute the default handler.
+    return;
+  }
 
 #if defined(__linux__)
   // Re-raising a signal via `raise` loses the original siginfo. Recent
@@ -438,6 +440,11 @@ static void InfoSignalHandler(int Sig) {
   SaveAndRestore SaveErrnoDuringASignalHandler(errno);
   if (SignalHandlerFunctionType CurrentInfoFunction = InfoSignalFunction)
     CurrentInfoFunction();
+
+  if (Sig == SIGUSR1) {
+    sys::unregisterHandlers();
+    raise(Sig);
+  }
 }
 
 void llvm::sys::RunInterruptHandlers() { RemoveFilesToRemove(); }

…N, resignal after the signal handler handles a signal so the exit code of the compiler contains the signal number.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang-tools-extra clangd llvm:support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants