Files
vlibc/tests/test_setjmp.c
T

348 lines
9.0 KiB
C

/*
* vlibc — setjmp/longjmp + sigsetjmp/siglongjmp test (todo 4).
*
* Exercises the non-local jump implementation end to end:
*
* 1. setjmp returns 0 on the initial call and 42 after longjmp(env, 42);
* 2. longjmp(env, 0) makes setjmp return 1, not 0 (POSIX coercion);
* 3. a volatile local survives a jump across a real call boundary;
* 4. FP control state (MXCSR and the x87 control word) round-trips:
* changed at the jump site, must come back as saved;
* 5. sigsetjmp(env, 1) captures a blocked SIGUSR1 mask that siglongjmp
* restores; sigsetjmp(env2, 0) must NOT restore it (flag logic);
* 6. _setjmp/_longjmp basic round-trip.
*
* The signal mask is manipulated with raw SYS_rt_sigprocmask (no signal.h:
* that header belongs to a later todo), and all diagnostics go through raw
* SYS_write (no stdio: this test must not depend on host libc headers, which
* the -Iinclude search path could otherwise shadow). With `-f`, only the
* POSIX coercion failure scenario runs: longjmp(env, 0) must return 1 — a 0
* return is the defect.
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <setjmp.h>
#include "../src/internal/syscall.h"
/* x86_64 signal number (kernel UAPI <asm/signal.h>): SIGUSR1 = 10. */
#define TEST_SIGUSR1 10
/* rt_sigprocmask how codes (kernel UAPI). */
#define TEST_SIG_BLOCK 0
#define TEST_SIG_UNBLOCK 1
#define TEST_SIG_SETMASK 2
/* x86_64 Linux sigset_t is a single 64-bit word. */
#define TEST_SIGSETSIZE 8
static int failures;
/*
* rt_sigprocmask with the kernel argument registers pinned by hand. The
* internal __syscall4() wrapper hands its 4th argument to the compiler as a
* generic "r" operand, which the compiler may place in r8 — but the x86_64
* syscall ABI delivers the 4th argument in r10, so the kernel then sees
* garbage in sigsetsize and rejects the call. Pin r10 here (the same
* technique src/internal/syscall.h uses for __syscall6); this test is
* self-contained and must be deterministic regardless of register
* allocation.
*/
static long
test_rt_sigprocmask(long how, const unsigned long *set,
unsigned long *oldset) // NOLINT(readability-non-const-parameter)
{
register long rdx __asm__("rdx") = (long)oldset;
register long rsi __asm__("rsi") = (long)set;
register long rdi __asm__("rdi") = how;
register long r10 __asm__("r10") = TEST_SIGSETSIZE;
register long rax __asm__("rax") = SYS_rt_sigprocmask;
__asm__ volatile("syscall"
: "+a"(rax)
: "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10)
: "rcx", "r11", "memory");
return rax;
}
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
/* Write v in decimal to fd. */
static void
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* Raw FP control-state access (the public <fenv.h> owns this later). */
static unsigned
read_mxcsr(void)
{
unsigned x;
__asm__ volatile("stmxcsr %0" : "=m"(x));
return x;
}
static void
write_mxcsr(unsigned x)
{
__asm__ volatile("ldmxcsr %0" : : "m"(x));
}
static unsigned short
read_x87_cw(void)
{
unsigned short c;
__asm__ volatile("fnstcw %0" : "=m"(c));
return c;
}
static void
write_x87_cw(unsigned short c)
{
__asm__ volatile("fldcw %0" : : "m"(c));
}
/* Query the current signal mask via raw rt_sigprocmask (SIG_BLOCK + NULL). */
static unsigned long
query_mask(void)
{
unsigned long old = 0;
test_rt_sigprocmask(TEST_SIG_BLOCK, 0, &old);
return old;
}
/*
* Must stay a real function: the volatile-local test needs the longjmp to
* cross an actual call boundary, not an inlined body.
*/
static __attribute__((noinline)) void
jump_out(jmp_buf env, int val)
{
longjmp(env, val);
}
/* 1. Initial return 0, then longjmp(env, 42) -> 42. */
static void
basic_roundtrip(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 42);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 42, "longjmp(env,42) -> setjmp returned 42");
}
/* 2. longjmp(env, 0) -> setjmp returns 1, never 0. */
static void
coercion_test(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 0);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 1, "longjmp(env,0) -> setjmp returned 1, not 0 (POSIX coercion)");
}
/* 3. Volatile locals survive a jump across a call boundary. */
static void
volatile_local_test(void)
{
jmp_buf env;
volatile int local = 1;
int r = setjmp(env);
if (r == 0)
{
local = 99;
jump_out(env, 5);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 5 && local == 99, "volatile local survives longjmp across a call boundary");
}
/* 4. FP control state round-trips through setjmp/longjmp. */
static void
fp_state_test(void)
{
jmp_buf env;
unsigned mx = read_mxcsr();
unsigned short cw = read_x87_cw();
int r = setjmp(env);
if (r == 0)
{
/* Toggle the MXCSR rounding-control bits and the x87 PC+RC bits. */
write_mxcsr(mx ^ 0x6000U);
write_x87_cw((unsigned short)(cw ^ 0x0f00U));
longjmp(env, 3);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 3, "FP-state test: longjmp(env,3) -> setjmp returned 3");
check(read_mxcsr() == mx, "mxcsr restored by longjmp");
check(read_x87_cw() == cw, "x87 control word restored by longjmp");
}
/* 5. sigsetjmp/siglongjmp round-trip a blocked mask; savemask=0 skips it. */
static void
sigmask_test(void)
{
sigjmp_buf env;
sigjmp_buf env2;
unsigned long block = 1UL << (TEST_SIGUSR1 - 1);
unsigned long orig = query_mask();
int r;
/* Block SIGUSR1 (raw rt_sigprocmask), then save the masked state. */
test_rt_sigprocmask(TEST_SIG_BLOCK, &block, 0);
r = sigsetjmp(env, 1);
if (r == 0)
{
/* Drop the block; siglongjmp must restore it. */
test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0);
siglongjmp(env, 7);
check(0, "siglongjmp returned to its caller (noreturn violated)");
return;
}
check(r == 7, "siglongjmp(env,7) -> sigsetjmp returned 7");
check((query_mask() & block) != 0, "siglongjmp restored the blocked SIGUSR1 mask");
/* Negative: savemask == 0 -> the mask must NOT be restored. */
r = sigsetjmp(env2, 0);
if (r == 0)
{
test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0);
siglongjmp(env2, 8);
check(0, "siglongjmp returned to its caller (noreturn violated)");
return;
}
check(r == 8, "siglongjmp(env2,8) -> sigsetjmp returned 8");
check((query_mask() & block) == 0, "sigsetjmp(env2,0): mask NOT restored (flag logic)");
/* Leave the process mask as it was found. */
test_rt_sigprocmask(TEST_SIG_SETMASK, &orig, 0);
}
/* 6. _setjmp/_longjmp basic round-trip. */
static void
underscore_forms_test(void)
{
jmp_buf env;
int r = _setjmp(env);
if (r == 0)
{
_longjmp(env, 9);
check(0, "_longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 9, "_longjmp(env,9) -> _setjmp returned 9");
}
/*
* Failure scenario (-f): longjmp(env, 0) must make setjmp return 1. Exits 0
* only when the coercion behaved exactly as POSIX specifies.
*/
static int
failure_scenario(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 0);
return 1; /* longjmp returned: noreturn violated */
}
if (r == 1)
{
say(1, "longjmp(env,0) -> setjmp returned 1 (a 0 return would be the defect)\n");
return 0;
}
say(1, "longjmp(env,0) -> setjmp returned ");
say_dec(1, (unsigned long)r);
say(1, ", want 1\n");
return 1;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
return failure_scenario();
}
basic_roundtrip();
coercion_test();
volatile_local_test();
fp_state_test();
sigmask_test();
underscore_forms_test();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all setjmp tests passed\n");
return 0;
}