Skip to content

Commit 02765c9

Browse files
fix(ci): green up all four CI platforms
- DateTime: compute the Unix-ns product in u64, not i64. DateTimeToUnixNs overflowed i64 for years > ~2262, and test_year_boundaries_sweep walks to 2550 -> SIGABRT under UBSan on the Linux sanitized job. - TestRunner: author the AArch64 SetJmp/LongJmp as file-scope asm under GCC. GCC silently ignores __attribute__((naked)) on AArch64 (warns "'naked' attribute directive ignored") and emits a prologue that decrements SP, so the SP saved into the JmpBuf is wrong; LongJmp then restores it and every deadend test crashed in g_test_abort_jmp on the arm64 job. Clang honours naked (incl. macOS arm64), so that path is unchanged. GCC/AArch64 is Linux/ELF-only here, so no Mach-O underscore is needed. Verified red->green under qemu-aarch64. - Socket: gate the IPv6 raw sockaddr byte check to PLATFORM_LINUX. macOS prefixes sin6_len and uses AF_INET6==30, Windows uses 23; the raw layout is Linux-specific (and mull, which the check feeds, runs on Linux). Fixes the macOS job. - ArgParse: give the parser an output sink (ArgParse.out, default NULL = FileStderr()) so --help and error text is captured through the library's own File IO instead of OS-level stream redirection. The capture tests now point `out` at a FileOpenTemp file and read it back -- fully portable, so they compile and run identically on Linux, macOS and Windows. The old tests #include'd <unistd.h> and used pipe/dup2, which broke the Windows clang-cl/msvc build. No platform #if, no test gap across OSes.
1 parent d07d049 commit 02765c9

8 files changed

Lines changed: 111 additions & 110 deletions

File tree

Include/Misra/Std/ArgParse.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <Misra/Std/Allocator.h>
4242
#include <Misra/Std/Container/Str.h>
4343
#include <Misra/Std/Container/Vec.h>
44+
#include <Misra/Std/File.h>
4445
#include <Misra/Std/Zstr.h>
4546
#include <Misra/Types.h>
4647

@@ -128,12 +129,17 @@ extern "C" {
128129
/// `name` -- shown as the program name in `--help` and errors.
129130
/// `about` -- one-line description at the top of `--help`. May be NULL.
130131
/// `alloc` -- where the `specs` Vec and any owned strings come from.
132+
/// `out` -- where `--help` and error text are written. NULL (the
133+
/// default) means `FileStderr()`; point it at any `File`
134+
/// (e.g. a `FileOpenTemp` handle) to capture the output
135+
/// portably, with no OS-level stream redirection.
131136
///
132137
typedef struct ArgParse {
133138
Allocator *alloc;
134139
Zstr name;
135140
Zstr about;
136141
ArgSpecs specs;
142+
File *out;
137143
} ArgParse;
138144

139145
///

Source/Misra/Std/ArgParse.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ static u64 spec_format_left(const ArgSpec *sp, Str *out) {
312312
}
313313

314314
static void print_help(ArgParse *self) {
315-
File err = FileStderr();
315+
File err = self->out ? *self->out : FileStderr();
316316

317317
if (self->about) {
318318
FWriteFmtLn(&err, "{} -- {}", self->name, self->about);
@@ -643,7 +643,7 @@ ArgRun ArgParseRun(ArgParse *self, int argc, char **argv) {
643643
VecPushBack(&self->specs, help);
644644
}
645645

646-
File err = FileStderr();
646+
File err = self->out ? *self->out : FileStderr();
647647

648648
bool rest_positional = false;
649649
u64 next_positional = 0;

Source/Misra/Std/DateTime.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ u64 DateTimeToUnixNs(DateTime dt) {
7474
i64 days = days_from_civil(dt.year, dt.month, dt.day);
7575
i64 local_sec = days * SECS_PER_DAY + (i64)dt.hour * 3600 + (i64)dt.minute * 60 + (i64)dt.second;
7676
i64 utc_sec = local_sec - (i64)dt.utc_offset_seconds;
77-
return (u64)(utc_sec * NS_PER_SEC + (i64)dt.nanosecond);
77+
return (u64)utc_sec * (u64)NS_PER_SEC + (u64)dt.nanosecond;
7878
}
7979

8080
i32 DateTimeCompare(DateTime a, DateTime b) {

Tests/Std/ArgParse.Blind.c

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,31 @@
44
#include <Misra/Std/Zstr.h>
55
#include <Misra/Std/ArgParse.h>
66
#include <Misra/Std/Container/Str.h>
7+
#include <Misra/Std/File.h>
78
#include <Misra/Std/Log.h>
89

9-
#include <unistd.h>
10-
1110
#include "../Util/TestRunner.h"
1211

1312
// ---------------------------------------------------------------------------
14-
// stderr capture: ArgParse writes errors / --help to fd 2 (FileStderr). To
15-
// assert error wording (which distinguishes several "both branches ERROR but
16-
// different message" mutants) we redirect fd 2 into a pipe, run, then drain.
17-
// pipe/dup/dup2/read are POSIX OS interfaces, allowed in tests.
13+
// ArgParse writes errors / --help to its output sink (`p->out`, default
14+
// FileStderr()). To assert error wording (which distinguishes several "both
15+
// branches ERROR but different message" mutants) we point the sink at a temp
16+
// file, run, then read it back. Fully portable -- no fd/handle redirection.
1817
// ---------------------------------------------------------------------------
1918
static ArgRun capture_run(ArgParse *p, int argc, char **argv, Str *out) {
20-
int pipefd[2];
21-
if (pipe(pipefd) != 0)
22-
LOG_FATAL("capture_run: pipe failed");
23-
int saved = dup(2);
24-
if (saved < 0)
25-
LOG_FATAL("capture_run: dup failed");
26-
if (dup2(pipefd[1], 2) < 0)
27-
LOG_FATAL("capture_run: dup2 failed");
28-
close(pipefd[1]);
19+
Str tmp_path = StrInit(p->alloc);
20+
File tmp = FileOpenTemp(&tmp_path, p->alloc);
21+
StrDeinit(&tmp_path);
22+
if (!FileIsOpen(&tmp))
23+
LOG_FATAL("capture_run: FileOpenTemp failed");
2924

25+
p->out = &tmp;
3026
ArgRun rc = ArgParseRun(p, argc, argv);
27+
p->out = NULL;
3128

32-
dup2(saved, 2);
33-
close(saved);
34-
35-
char buf[4096];
36-
for (;;) {
37-
long n = read(pipefd[0], buf, sizeof(buf));
38-
if (n <= 0)
39-
break;
40-
for (long i = 0; i < n; ++i)
41-
StrPushBackR(out, buf[i]);
42-
}
43-
close(pipefd[0]);
29+
FileSeek(&tmp, 0, FILE_SEEK_SET);
30+
FileRead(&tmp, out);
31+
FileClose(&tmp);
4432
return rc;
4533
}
4634

Tests/Std/ArgParse.Mut.c

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,31 @@
1212
#include <Misra/Std/Io.h>
1313
#include <Misra/Std/Log.h>
1414

15-
#include <unistd.h>
16-
1715
#include "../Util/TestRunner.h"
1816

1917
// ---------------------------------------------------------------------------
20-
// --help capture: print_help writes the usage table to fd 2 (FileStderr).
21-
// Redirect fd 2 into a pipe, run --help, drain the pipe back into a Str.
22-
// (Same technique the existing ArgParse.c suite uses.)
18+
// --help capture: point the parser's output sink at a temp file (`p->out`),
19+
// run --help, then read the file back. Fully portable -- the `File`
20+
// abstraction handles the platform difference, no fd/handle redirection.
2321
// ---------------------------------------------------------------------------
2422
static void capture_help(ArgParse *p, Str *out) {
25-
int pipefd[2];
26-
if (pipe(pipefd) != 0)
27-
LOG_FATAL("capture_help: pipe failed");
28-
29-
int saved = dup(2);
30-
if (saved < 0)
31-
LOG_FATAL("capture_help: dup failed");
32-
if (dup2(pipefd[1], 2) < 0)
33-
LOG_FATAL("capture_help: dup2 failed");
34-
close(pipefd[1]);
23+
Str tmp_path = StrInit(p->alloc);
24+
File tmp = FileOpenTemp(&tmp_path, p->alloc);
25+
StrDeinit(&tmp_path);
26+
if (!FileIsOpen(&tmp))
27+
LOG_FATAL("capture_help: FileOpenTemp failed");
3528

29+
p->out = &tmp;
3630
char *argv[] = {(char *)"prog", (char *)"--help"};
3731
ArgRun rc = ArgParseRun(p, 2, argv);
38-
39-
dup2(saved, 2);
40-
close(saved);
32+
p->out = NULL;
4133

4234
if (rc != ARG_RUN_HELP)
4335
LOG_FATAL("capture_help: expected ARG_RUN_HELP, got {}", (int)rc);
4436

45-
char buf[4096];
46-
for (;;) {
47-
long n = read(pipefd[0], buf, sizeof(buf));
48-
if (n <= 0)
49-
break;
50-
for (long i = 0; i < n; ++i)
51-
StrPushBackR(out, buf[i]);
52-
}
53-
close(pipefd[0]);
37+
FileSeek(&tmp, 0, FILE_SEEK_SET);
38+
FileRead(&tmp, out);
39+
FileClose(&tmp);
5440
}
5541

5642
static bool help_equals(ArgParse *p, Zstr expected) {

Tests/Std/ArgParse.c

Lines changed: 19 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#include <Misra/Std/Io.h>
77
#include <Misra/Std/Log.h>
88

9-
#include <unistd.h>
10-
119
#include "../Util/TestRunner.h"
1210

1311
// ----------------------------------------------------------------------------
@@ -603,47 +601,33 @@ static bool test_help_returns_help_code(void) {
603601
// ===========================================================================
604602

605603
// ---------------------------------------------------------------------------
606-
// print_help writes its usage table straight to fd 2 (FileStderr). To
607-
// assert the EXACT layout -- column alignment, padding width, section
608-
// order, separators -- we redirect fd 2 to a pipe, run
609-
// ArgParseRun(--help), then drain the pipe back into a Str. pipe / dup /
610-
// dup2 / read are OS interfaces (POSIX), not libc, so they are allowed
611-
// in MisraStdC tests.
604+
// To assert the EXACT --help layout -- column alignment, padding width,
605+
// section order, separators -- we point the parser's output sink (`p->out`)
606+
// at a temp file, run ArgParseRun(--help), then read the file back. Fully
607+
// portable: the `File` abstraction handles the platform difference, so the
608+
// test needs no fd/handle redirection.
612609
//
613610
// ArgParseRun appends a synthetic "-h, --help print this help" FLAG
614611
// spec before printing, so every captured help text ends with that row.
615612
// ---------------------------------------------------------------------------
616613
static void capture_help(ArgParse *p, Str *out) {
617-
int pipefd[2];
618-
if (pipe(pipefd) != 0)
619-
LOG_FATAL("capture_help: pipe failed");
620-
621-
int saved = dup(2);
622-
if (saved < 0)
623-
LOG_FATAL("capture_help: dup failed");
624-
if (dup2(pipefd[1], 2) < 0)
625-
LOG_FATAL("capture_help: dup2 failed");
626-
close(pipefd[1]);
614+
Str tmp_path = StrInit(p->alloc);
615+
File tmp = FileOpenTemp(&tmp_path, p->alloc);
616+
StrDeinit(&tmp_path);
617+
if (!FileIsOpen(&tmp))
618+
LOG_FATAL("capture_help: FileOpenTemp failed");
627619

620+
p->out = &tmp;
628621
char *argv[] = {(char *)"prog", (char *)"--help"};
629622
ArgRun rc = ArgParseRun(p, 2, argv);
630-
631-
// Restore the real stderr before draining so later logging is sane.
632-
dup2(saved, 2);
633-
close(saved);
623+
p->out = NULL;
634624

635625
if (rc != ARG_RUN_HELP)
636626
LOG_FATAL("capture_help: expected ARG_RUN_HELP, got {}", (int)rc);
637627

638-
char buf[4096];
639-
for (;;) {
640-
long n = read(pipefd[0], buf, sizeof(buf));
641-
if (n <= 0)
642-
break;
643-
for (long i = 0; i < n; ++i)
644-
StrPushBackR(out, buf[i]);
645-
}
646-
close(pipefd[0]);
628+
FileSeek(&tmp, 0, FILE_SEEK_SET);
629+
FileRead(&tmp, out);
630+
FileClose(&tmp);
647631
}
648632

649633
static bool help_equals(ArgParse *p, Zstr expected) {
@@ -895,34 +879,19 @@ static bool test_a1_render_cap_is_64(void) {
895879
// width) are then asserted.
896880
// ----------------------------------------------------------------------------
897881

898-
// Run --help with stderr redirected into `out`. Returns true on a clean
899-
// capture. The captured help text (whole stderr stream) lands in `out`.
900-
// (Tempfile-based variant; the pipe-based capture_help above is kept
901-
// under its own name.)
882+
// Run --help with the parser's output sink pointed at a temp file, then
883+
// read it back into `out`. Returns true on a clean ARG_RUN_HELP capture.
902884
static bool capture_help_file(ArgParse *p, Str *out) {
903885
Str tmp_path = StrInit(p->alloc);
904886
File tmp = FileOpenTemp(&tmp_path, p->alloc);
905887
StrDeinit(&tmp_path);
906888
if (!FileIsOpen(&tmp))
907889
return false;
908890

909-
int saved = dup(2);
910-
if (saved < 0) {
911-
FileClose(&tmp);
912-
return false;
913-
}
914-
if (dup2(tmp.fd, 2) < 0) {
915-
close(saved);
916-
FileClose(&tmp);
917-
return false;
918-
}
919-
891+
p->out = &tmp;
920892
char *argv[] = {(char *)"prog", (char *)"--help"};
921893
ArgRun rc = ArgParseRun(p, 2, argv);
922-
923-
// Restore stderr before doing anything that might write to it.
924-
dup2(saved, 2);
925-
close(saved);
894+
p->out = NULL;
926895

927896
bool ok = (rc == ARG_RUN_HELP);
928897
FileSeek(&tmp, 0, FILE_SEEK_SET);

Tests/Std/Socket.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,8 +807,13 @@ bool test_sk3_parse_ipv6_fields(void) {
807807
ok = ok && addr.length == 28u; // sizeof(struct sockaddr_in6)
808808

809809
// raw[0..1] is sin6_family (Linux: u16 host order at offset 0).
810-
// AF_INET6 == 10. A mutation to 42 changes this byte.
810+
// AF_INET6 == 10. A mutation to 42 changes this byte. The raw byte
811+
// layout (offset, width, AF_INET6 value) is Linux-specific -- macOS
812+
// prefixes sin6_len and uses AF_INET6==30, Windows uses 23 -- so this
813+
// check is gated to Linux, which is also the only platform mull runs on.
814+
#if PLATFORM_LINUX
811815
ok = ok && addr.raw[0] == 10u && addr.raw[1] == 0u;
816+
#endif
812817

813818
Str s = SocketAddrFormat(&addr, a);
814819
ok = ok && StrLen(&s) > 0 && ZstrCompare(StrBegin(&s), "[2001:db8::1]:443") == 0;

Tests/Util/TestRunner.c

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,10 @@ __attribute__((naked, used, noreturn)) static void LongJmp(JmpBuf env, int val)
160160
"1: jmpq *56(%rdi)\n"
161161
);
162162
}
163-
#elif (PLATFORM_LINUX || PLATFORM_DARWIN) && ARCHITECTURE_AARCH64
163+
#elif (PLATFORM_LINUX || PLATFORM_DARWIN) && ARCHITECTURE_AARCH64 && defined(__clang__)
164164
// AAPCS64: 1st arg in X0, 2nd arg in X1. Callee-saved scalars:
165-
// X19-X28, X29 (fp), X30 (lr). Plus SP.
165+
// X19-X28, X29 (fp), X30 (lr). Plus SP. Clang honours `naked` on
166+
// AArch64, so the asm body is emitted verbatim with no prologue.
166167
__attribute__((naked, used)) static int SetJmp(JmpBuf env) {
167168
__asm__(
168169
"stp x19, x20, [x0, #0]\n"
@@ -194,6 +195,52 @@ __attribute__((naked, used, noreturn)) static void LongJmp(JmpBuf env, int val)
194195
"1: ret\n"
195196
);
196197
}
198+
#elif (PLATFORM_LINUX || PLATFORM_DARWIN) && ARCHITECTURE_AARCH64
199+
// GCC silently IGNORES `__attribute__((naked))` on AArch64 (it warns
200+
// `'naked' attribute directive ignored` and emits a real
201+
// prologue/epilogue). That prologue does `stp x29,x30,[sp,#-16]!`, so
202+
// the SP this routine hands back in the JmpBuf is 16 bytes below the
203+
// caller's frame; `LongJmp` then restores that bad SP and the unwound
204+
// frame is corrupt -- under ASan it lands in a guard page (the
205+
// `SEGV in g_test_abort_jmp` seen on the arm64 CI). Authoring the pair
206+
// as file-scope asm sidesteps `naked` entirely: top-level asm is never
207+
// wrapped in a prologue by any compiler. GCC/AArch64 only ever targets
208+
// Linux/ELF here (Darwin uses Clang, handled above), so the symbols
209+
// need no Mach-O underscore prefix.
210+
int SetJmp(JmpBuf env);
211+
void LongJmp(JmpBuf env, int val) __attribute__((noreturn));
212+
213+
__asm__(
214+
".text\n"
215+
".p2align 2\n"
216+
".globl SetJmp\n"
217+
"SetJmp:\n"
218+
" stp x19, x20, [x0, #0]\n"
219+
" stp x21, x22, [x0, #16]\n"
220+
" stp x23, x24, [x0, #32]\n"
221+
" stp x25, x26, [x0, #48]\n"
222+
" stp x27, x28, [x0, #64]\n"
223+
" stp x29, x30, [x0, #80]\n"
224+
" mov x1, sp\n"
225+
" str x1, [x0, #96]\n"
226+
" mov w0, #0\n"
227+
" ret\n"
228+
".p2align 2\n"
229+
".globl LongJmp\n"
230+
"LongJmp:\n"
231+
" ldp x19, x20, [x0, #0]\n"
232+
" ldp x21, x22, [x0, #16]\n"
233+
" ldp x23, x24, [x0, #32]\n"
234+
" ldp x25, x26, [x0, #48]\n"
235+
" ldp x27, x28, [x0, #64]\n"
236+
" ldp x29, x30, [x0, #80]\n"
237+
" ldr x2, [x0, #96]\n"
238+
" mov sp, x2\n"
239+
" mov w0, w1\n"
240+
" cbnz w0, 1f\n"
241+
" mov w0, #1\n"
242+
"1: ret\n"
243+
);
197244
#endif
198245

199246
// Global jump buffer for capturing aborts

0 commit comments

Comments
 (0)