From 978d2ad905f3c85b99b030c71aa8dee9a4235d5e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 14:08:14 +0200 Subject: [PATCH] test: add more cases for sigTest --- src/sigTest.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/src/sigTest.c b/src/sigTest.c index 8f0bdf7..46b1070 100644 --- a/src/sigTest.c +++ b/src/sigTest.c @@ -473,3 +473,156 @@ KTEST(sigConsumePendingDefaultDispositionReturnsTerminate) KT_EQU(t, regs.rax, before.rax); KT_EQU(t, thread->sigHandlerDepth, 0u); + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigConsumePendingSigTstpDefaultDispositionReturnsStopOutcome) +{ + uint32 id = threadCreate("sigConsumeTstp", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + Registers regs = makeUserRegs(); + Registers before = regs; + int signum = -1; + + /* SIGTSTP's real-world default disposition is Stop, not Terminate -- + the catchable half of job control (see sigDefaultIsStop, signal.c). + sigStop itself never reaches here at all (sigRaise bypasses this + whole scan for it, see sigRaiseSigStopDelegatesToThreadStopAndBypassesPending + above), so this is the ONLY way sigOutcomeStop is ever produced. */ + sigRaise(id, sigTstp); + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeStop); + KT_EQ(t, signum, sigTstp); + KT_FALSE(t, thread->sigPending & (1ull << sigTstp)); + + /* Reported, not carried out, here -- isr.c's deliverPendingSignals is + the one that actually calls threadStop, the same contract + sigOutcomeTerminate already has with threadExit. */ + KT_EQU(t, regs.rip, before.rip); + KT_EQU(t, regs.rax, before.rax); + KT_EQU(t, thread->sigHandlerDepth, 0u); + KT_EQ(t, thread->state, threadRunnable); + + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigConsumePendingSigTstpWithHandlerRunsHandlerInsteadOfStopping) +{ + uint32 id = threadCreate("sigConsumeTstpCaught", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + Registers regs = makeUserRegs(); + int signum = -1; + + /* Unlike SIGSTOP, SIGTSTP is fully catchable: an installed handler + overrides the default Stop disposition, exactly like any other + catchable signal overriding a default Terminate. */ + uint64 handlerAddr = 0x4000A000ull; + sigSetHandler(thread, sigTstp, handlerAddr, 0, 0); + sigRaise(id, sigTstp); + + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + KT_EQ(t, signum, sigTstp); + KT_EQU(t, regs.rax, handlerAddr); + KT_EQU(t, regs.rip, sigTrampolineVirtualAddr); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigConsumePendingSaMaskDefersDifferentSignalUntilHandlerReturns) +{ + uint32 id = threadCreate("sigSaMaskTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + Registers regs = makeUserRegs(); + int signum = -1; + + /* sigUsr1's sa_mask explicitly includes sigHup -- unlike the nesting + test below (mask 0), THIS is what should keep sigHup from + interrupting sigUsr1's handler at all. */ + uint64 usr1Handler = 0x40009000ull; + uint64 hupHandler = 0x4000C000ull; + sigSetHandler(thread, sigUsr1, usr1Handler, (1ull << sigHup), 0); + sigSetHandler(thread, sigHup, hupHandler, 0, 0); + + sigRaise(id, sigUsr1); + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + KT_EQ(t, signum, sigUsr1); + KT_TRUE(t, thread->sigBlocked & (1ull << sigHup)); + + /* sigHup arrives while sigUsr1's handler is active -- masked by its + sa_mask, so it must stay pending, NOT nest, unlike an unmasked + signal would (see the nesting test below). */ + sigRaise(id, sigHup); + int deferredSignum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &deferredSignum), sigOutcomeNone); + KT_EQ(t, deferredSignum, -1); + KT_TRUE(t, thread->sigPending & (1ull << sigHup)); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + /* Once sigUsr1's handler returns, its sa_mask's grip on sigHup ends + with it (sigBlocked restored to what it was before) -- the deferred + signal is finally deliverable, as its own TOP-LEVEL handler, not a + nested one. */ + KT_TRUE(t, sigOnSigReturn(thread, ®s)); + KT_FALSE(t, thread->sigBlocked & (1ull << sigHup)); + + int finalSignum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &finalSignum), sigOutcomeHandler); + KT_EQ(t, finalSignum, sigHup); + KT_EQU(t, regs.rax, hupHandler); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + KT_TRUE(t, threadDiscard(id)); +} + +/* Default (sigFlagNoDefer NOT set) half of the contrast: a signal raised + again against ITSELF while its own handler is still active must stay + merely pending, not nest -- sigConsumePendingCustomHandlerRewritesTrapframeAndPushesContext + above already asserts sigUsr1's own bit lands in sigBlocked once its + handler starts; this test is the direct behavioral consequence of that + bit actually being there, driven the same way + sigConsumePendingSaMaskDefersDifferentSignalUntilHandlerReturns drives + sa_mask's deferral. */ +KTEST(sigConsumePendingDefersSameSignalRaisedAgainWhileItsOwnHandlerIsActive) +{ + uint32 id = threadCreate("sigNoDeferOffTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + Registers regs = makeUserRegs(); + int signum = -1; + + uint64 handlerAddr = 0x40009000ull; + sigSetHandler(thread, sigUsr1, handlerAddr, 0, 0); + sigRaise(id, sigUsr1); + + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + KT_EQ(t, signum, sigUsr1); + KT_TRUE(t, thread->sigBlocked & (1ull << sigUsr1)); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + /* Same signum, raised again while its own handler is still active -- + self-blocked (no sigFlagNoDefer), so this must stay pending, not + enter a second, nested instance of the same handler. */ + sigRaise(id, sigUsr1); + int deferredSignum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &deferredSignum), sigOutcomeNone); + KT_EQ(t, deferredSignum, -1); + KT_TRUE(t, thread->sigPending & (1ull << sigUsr1)); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + /* Once the (only) active handler returns, its self-block lifts and the + deferred re-raise is finally deliverable -- as a fresh TOP-LEVEL + invocation (depth back to 1, not 2), never having nested at all. */ + KT_TRUE(t, sigOnSigReturn(thread, ®s)); + KT_FALSE(t, thread->sigBlocked & (1ull << sigUsr1)); + + int finalSignum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &finalSignum), sigOutcomeHandler); + KT_EQ(t, finalSignum, sigUsr1); + KT_EQU(t, thread->sigHandlerDepth, 1u); + + KT_TRUE(t, threadDiscard(id)); +} +