From 0c9850060325f4e03cd72b5caa0dff97a89d538c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:47:46 +0900 Subject: [PATCH] test: port the archive-driven linker observers to ww archive_test.ww absorbs 610_arch and 632_w6l_manyflags: archive member selectivity through a load-bearing undefined @symbol extern (link exit 0, run exit 7), and the -L capacity table {1,64,65,100, 128} against both w6l and w6l_ww with an ET_EXEC check per leg. ar is /usr/bin/ar via runcommand; the junk -L flags are real argv entries instead of a 16KB shell string. --- Makefile | 3 +- test/object/archive_test.ww | 169 ++++++++++++++++++++++++++++ test/wcc/610_arch.c | 127 --------------------- test/wcc/632_w6l_manyflags.c | 207 ----------------------------------- 4 files changed, 171 insertions(+), 335 deletions(-) create mode 100644 test/object/archive_test.ww delete mode 100644 test/wcc/610_arch.c delete mode 100644 test/wcc/632_w6l_manyflags.c diff --git a/Makefile b/Makefile index 14c6994b..ed2c3cf8 100644 --- a/Makefile +++ b/Makefile @@ -395,7 +395,8 @@ ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) # layout, program-header split and BSS trim, archive selectivity, # linker flag capacity, assembler input gates). Compiler/driver gates # like test/sep: they run under test-compiler. -OBJECT_WW_TESTS = test/object/elfobj_test.ww test/object/link_test.ww +OBJECT_WW_TESTS = test/object/elfobj_test.ww test/object/link_test.ww \ + test/object/archive_test.ww OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/object/archive_test.ww b/test/object/archive_test.ww new file mode 100644 index 00000000..8e4e5e8c --- /dev/null +++ b/test/object/archive_test.ww @@ -0,0 +1,169 @@ +package archive_test; + +// Archive-driven w6l observers (host ar). Ports of the retired native +// carriers test/wcc/610_arch.c and 632_w6l_manyflags.c; every +// assertion preserved. +// +// selectivity (610) — libfoo.a holds a needed member and a member +// referencing an undefined extern; the link succeeds (exit 0) because +// w6l pulls ONLY the needed member — pulling lib_bad would fail with +// an undefined reference to this_symbol_does_not_exist — and the +// binary runs with exit 7. The bad member's @symbol extern is the +// discriminator; keep it verbatim. +// +// manyflags (632, drain F-C) — -L/-l arrays must be sized by argc, +// not a fixed 64-slot cap. -lfoo resolves only through the FINAL real +// -L placed after N-1 junk dirs, so a successful link (exit 0 + +// ELF64 ET_EXEC output) proves the Nth -L was honoured. N table +// {1, 64, 65, 100, 128}; N=65 is the exact pre-fix overflow slot. +// Both w6l and w6l_ww are driven (the w6l_ww leg was ungated in the +// carrier too). +// +// Dropped C machinery, not assertions: wwtestpkg.h package-clause +// injection, the 16KB shell-command buffer (argv is a real vector +// here), and per-file unlink accounting (testenv.clean). + +import os; +import os.exec; +import strconv; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("archive FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +// itos returns a view into a static buffer; dup materializes it. +fn dec(n: i32) str = { + return strings.dup(strconv.itos(n: int, strconv.base.DEC)); +}; + +// w6c + w6a on one package-main source; leaves .o next to it. +fn compile(td: str, stem: str, body: str) void = { + let src: str = strings.concat(td, "/", stem, ".ww"); + let asmf: str = strings.concat(td, "/", stem, ".s"); + let obj: str = strings.concat(td, "/", stem, ".o"); + testenv.writefile(src, body); + let cav: []str = [testenv.driver("w6c"), "-o", asmf, src]; + if (runcode(td, strings.concat("c_", stem), cav) != 0) { + fail(stem, "w6c failed"); + }; + let aav: []str = [testenv.driver("w6a"), "-o", obj, asmf]; + if (runcode(td, strings.concat("a_", stem), aav) != 0) { + fail(stem, "w6a failed"); + }; +}; + +// ---- selectivity (610) ------------------------------------------------- + +@test fn selectivity() void = { + let td: str = testenv.fresh(); + compile(td, "lib_good", strings.concat("package main;\n", + "export fn f1() i32 = { return 7; };\n")); + // defines f2 but also references an undefined extern — the + // discriminator: pulling this member makes the link fail + compile(td, "lib_bad", strings.concat("package main;\n", + "@symbol(\"this_symbol_does_not_exist\") fn bogus() i32;\n", + "export fn f2() i32 = { return bogus(); };\n")); + // the plain prototype declares f1 in the same (main) module, so + // the call resolves to main.f1 — matching lib_good's definition + compile(td, "m", strings.concat("package main;\n", + "fn f1() i32;\n", + "fn main() i32 = { return f1(); };\n")); + + let lib: str = strings.concat(td, "/libfoo.a"); + let arav: []str = ["/usr/bin/ar", "rcs", lib, + strings.concat(td, "/lib_good.o"), + strings.concat(td, "/lib_bad.o")]; + if (runcode(td, "ar", arav) != 0) { fail("selectivity", "ar failed"); }; + + let exe: str = strings.concat(td, "/m"); + let start: str = strings.concat(testenv.repo(), + "/out/obj/rt/start.o"); + let lav: []str = [testenv.driver("w6l"), "-o", exe, + strings.concat(td, "/m.o"), start, lib]; + let co: testenv.commandout; + testenv.runcommand(td, td, "link", lav, tmo(), &co); + if (co.termination != exec.termination.EXIT || co.code != 0) { + fail("selectivity", strings.concat( + "link failed (bad member pulled?): ", co.stderr)); + }; + + let rav: []str = [exe]; + if (runcode(td, "run_m", rav) != 7) { + fail("selectivity", "exit != 7"); + }; + testenv.clean(td); +}; + +// ---- manyflags (632) --------------------------------------------------- + +fn linkwith(td: str, lnk: str, n: i32) void = { + let tag: str = strings.concat(lnk, "_", dec(n)); + let exe: str = strings.concat(td, "/x_", tag); + let av: []str = alloc([], 8u64)!; + append(av, testenv.driver(lnk)); + append(av, "-o"); + append(av, exe); + append(av, strings.concat(td, "/m.o")); + let i: i32 = 1; + for (i < n) { + append(av, strings.concat("-L/nonexist/wwfc_d", dec(i))); + i += 1; + }; + append(av, strings.concat("-L", td)); + append(av, "-lfoo"); + if (runcode(td, strings.concat("lnk_", tag), av) != 0) { + fail("manyflags", strings.concat(lnk, " nflags=", dec(n), + " link failed (Nth -L not honoured)")); + }; + let o: str = testenv.readfile(exe); + if (o.len < 64 || o[0] != 0x7fu8 || o[1] != 0x45u8 + || o[2] != 0x4cu8 || o[3] != 0x46u8) { + fail("manyflags", strings.concat(lnk, " nflags=", dec(n), + " output not an ELF")); + }; + // ET_EXEC == 2 (Ehdr e_type at 16, LE u16) + if (testenv.leu16(o, 16) != 2u64) { + fail("manyflags", strings.concat(lnk, " nflags=", dec(n), + " output not ET_EXEC")); + }; +}; + +@test fn manyflags() void = { + let td: str = testenv.fresh(); + // a standalone main.o (no undefs) + an unrelated archived foo.o + compile(td, "m", strings.concat("package main;\n", + "fn main() i32 = { return 42; };\n")); + compile(td, "f", strings.concat("package main;\n", + "export fn foo() i32 = { return 7; };\n")); + let arav: []str = ["/usr/bin/ar", "rcs", + strings.concat(td, "/libfoo.a"), + strings.concat(td, "/f.o")]; + if (runcode(td, "ar", arav) != 0) { fail("manyflags", "ar failed"); }; + + let counts: []i32 = [1, 64, 65, 100, 128]; + let i: i32 = 0; + for (i < 5) { + linkwith(td, "w6l_ww", counts[i]); + linkwith(td, "w6l", counts[i]); + i += 1; + }; + testenv.clean(td); +}; diff --git a/test/wcc/610_arch.c b/test/wcc/610_arch.c deleted file mode 100644 index baa379f3..00000000 --- a/test/wcc/610_arch.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 610_arch — archive selectivity. Build two .o files into an archive - * where one defines a symbol main needs, and the other references an - * undefined external. Linking should pull only the needed member; - * the other one's bad reference must NOT cause a link error. - * - * If w6l were still pulling all members, this test would fail with - * "undefined reference to 'this_symbol_does_not_exist'". - */ -#include -#include -#include -#include -#include -#include -#include "wwtestpkg.h" - -static int run(const char *cmd) { return system(cmd); } - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char dir[64]; - snprintf(dir, sizeof dir, "/tmp/wwarch_%d", getpid()); - if (mkdir(dir, 0755) != 0) { - perror(dir); - return 1; - } - int result = 1; - - /* lib_good.ww — defines f1() (→ main.f1 under strict-package #24a) */ - char path[256]; - snprintf(path, sizeof path, "%s/lib_good.ww", dir); - FILE *f = fopen(path, "wb"); - if (!f) { perror(path); goto cleanup; } - wwtest_fputs("export fn f1() i32 = { return 7; };", f); - if (fclose(f) != 0) { perror(path); goto cleanup; } - - /* lib_bad.ww — defines f2() but also references an undefined extern */ - snprintf(path, sizeof path, "%s/lib_bad.ww", dir); - f = fopen(path, "wb"); - if (!f) { perror(path); goto cleanup; } - wwtest_fputs("@symbol(\"this_symbol_does_not_exist\") fn bogus() i32;\n" - "export fn f2() i32 = { return bogus(); };", f); - if (fclose(f) != 0) { perror(path); goto cleanup; } - - /* main.ww — calls f1, NOT f2. The plain prototype declares f1 in the - * same (main) module, so the call resolves to main.f1 — matching - * lib_good's moduled definition (no @symbol bare-pin needed). */ - snprintf(path, sizeof path, "%s/m.ww", dir); - f = fopen(path, "wb"); - if (!f) { perror(path); goto cleanup; } - wwtest_fputs("fn f1() i32;\n" - "fn main() i32 = { return f1(); };", f); - if (fclose(f) != 0) { perror(path); goto cleanup; } - - char cmd[2048]; - snprintf(cmd, sizeof cmd, - "set -e; cd %s && %s/w6c -o lib_good.s lib_good.ww && " - "%s/w6c -o lib_bad.s lib_bad.ww && " - "%s/w6a -o lib_good.o lib_good.s && %s/w6a -o lib_bad.o lib_bad.s && " - "ar rcs libfoo.a lib_good.o lib_bad.o && " - "%s/w6c -o m.s m.ww && %s/w6a -o m.o m.s", - dir, bin, bin, bin, bin, bin, bin); - if (run(cmd) != 0) { - fprintf(stderr, "build failed\n"); - goto cleanup; - } - - /* Find start.o for the runtime */ - char startobj[256]; - snprintf(startobj, sizeof startobj, "%s/../obj/rt/start.o", bin); - - snprintf(cmd, sizeof cmd, - "%s/w6l -o %s/m %s/m.o %s %s/libfoo.a 2>%s/link.err", - bin, dir, dir, startobj, dir, dir); - if (run(cmd) != 0) { - char errpath[300]; - snprintf(errpath, sizeof errpath, "%s/link.err", dir); - FILE *e = fopen(errpath, "r"); - if (e) { - char buf[512]; size_t r = fread(buf, 1, sizeof buf - 1, e); buf[r]='\0'; fclose(e); - fprintf(stderr, "link failed:\n%s\n", buf); - } - goto cleanup; - } - - char exepath[256]; - snprintf(exepath, sizeof exepath, "%s/m", dir); - int rc = run(exepath); - if (WIFEXITED(rc) && WEXITSTATUS(rc) == 7) { - result = 0; - } else { - fprintf(stderr, "arch: unexpected exit\n"); - } - -cleanup: - snprintf(path, sizeof path, "%s/lib_good.ww", dir); unlink(path); - snprintf(path, sizeof path, "%s/lib_bad.ww", dir); unlink(path); - snprintf(path, sizeof path, "%s/m.ww", dir); unlink(path); - snprintf(path, sizeof path, "%s/lib_good.s", dir); unlink(path); - snprintf(path, sizeof path, "%s/lib_bad.s", dir); unlink(path); - snprintf(path, sizeof path, "%s/lib_good.o", dir); unlink(path); - snprintf(path, sizeof path, "%s/lib_bad.o", dir); unlink(path); - snprintf(path, sizeof path, "%s/libfoo.a", dir); unlink(path); - snprintf(path, sizeof path, "%s/m.s", dir); unlink(path); - snprintf(path, sizeof path, "%s/m.o", dir); unlink(path); - snprintf(path, sizeof path, "%s/m", dir); unlink(path); - snprintf(path, sizeof path, "%s/link.err", dir); unlink(path); - if (rmdir(dir) != 0) { - perror(dir); - result = 1; - } - if (result == 0) - printf("arch: ok (only needed archive member pulled)\n"); - return result; -} diff --git a/test/wcc/632_w6l_manyflags.c b/test/wcc/632_w6l_manyflags.c deleted file mode 100644 index 17fd60a3..00000000 --- a/test/wcc/632_w6l_manyflags.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 632_w6l_manyflags — w6l -L/-l arrays must be sized by argc, not a - * fixed 64-slot cap (drain F-C). The 65th -L used to write past the - * allocation: `libdirs[nlibdirs] = ...` with no bound check, corrupting - * the heap and dropping the flag. - * - * Behavioural discriminator: make a -L past slot 64 LOAD-BEARING. We - * build libfoo.a in a real directory and reach it ONLY via the Nth -L, - * preceded by N-1 junk dirs. w6l errors `cannot find -lfoo` unless it - * can locate the archive, so a successful link (exit 0 + ET_EXEC) proves - * the Nth -L was honoured. N is a table {1, 64, 65, 100, 128}; pre-fix, - * N=65 (the slot exactly one past the 64-element array) reliably fails - * to round-trip and the link errors — verified by rebuilding the pre-fix - * w6l_ww. Both stages are driven for parity. - */ -#include -#include -#include -#include -#include -#include -#include -#include "wwtestpkg.h" - -static const char * -absbin(void) -{ - const char *b = getenv("BIN"); - if (!b) b = "out/bin"; - if (b[0] == '/') return b; - static char buf[2048]; - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return NULL; - snprintf(buf, sizeof buf, "%s/%s", cwd, b); - return buf; -} - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return 1; -} - -/* link `obj` with linker `lnk`, resolving -lfoo through `libdir` placed - * as the Nth (last) -L after nflags-1 junk dirs. Returns the linker's - * exit code; on success also fails (-1) if the output isn't an ET_EXEC - * ELF — proving the archive was actually located and linked. */ -static int -linkwith(const char *lnk, const char *obj, const char *libdir, int nflags) -{ - char exe[128]; - snprintf(exe, sizeof exe, "%s/wwfc_x", libdir); - - char junk[8192]; - size_t off = 0; - for (int i = 1; i < nflags; i++) - off += snprintf(junk + off, sizeof junk - off, - " -L/nonexist/wwfc_d%d", i); - - char cmd[16384]; - snprintf(cmd, sizeof cmd, - "%s -o %s %s%s -L%s -lfoo 2>/dev/null", - lnk, exe, obj, junk, libdir); - int rc = runwait(cmd); - if (rc != 0) goto cleanup; - - FILE *f = fopen(exe, "rb"); - if (!f) { - rc = -1; - goto cleanup; - } - unsigned char hdr[20]; - int ok = fread(hdr, 1, sizeof hdr, f) == sizeof hdr; - fclose(f); - if (!ok || memcmp(hdr, "\x7f""ELF", 4) != 0) { - rc = -1; - goto cleanup; - } - unsigned short etype = (unsigned short)hdr[16] - | ((unsigned short)hdr[17] << 8); - if (etype != 2) rc = -1; /* ET_EXEC */ - -cleanup: - if (unlink(exe) != 0 && errno != ENOENT) { - perror(exe); - if (rc == 0) rc = -1; - } - return rc; -} - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) return 1; - - char src[128], asmf[128], obj[128]; - char fsrc[128], fasm[128], fobj[128]; - char libdir[64], lib[128], cmd[1024]; - int pid = getpid(); - snprintf(libdir, sizeof libdir, "/tmp/wwfc_%d_lib", pid); - if (mkdir(libdir, 0755) != 0) { - perror(libdir); - return 1; - } - int result = 1; - snprintf(src, sizeof src, "%s/m.ww", libdir); - snprintf(asmf, sizeof asmf, "%s/m.s", libdir); - snprintf(obj, sizeof obj, "%s/m.o", libdir); - snprintf(fsrc, sizeof fsrc, "%s/f.ww", libdir); - snprintf(fasm, sizeof fasm, "%s/f.s", libdir); - snprintf(fobj, sizeof fobj, "%s/f.o", libdir); - snprintf(lib, sizeof lib, "%s/libfoo.a", libdir); - - /* a standalone main.o (no undefs) + an unrelated archived foo.o. */ - FILE *f = fopen(src, "wb"); - if (!f) { perror(src); goto cleanup; } - wwtest_fputs("fn main() i32 = { return 42; };", f); - if (fclose(f) != 0) { perror(src); goto cleanup; } - f = fopen(fsrc, "wb"); - if (!f) { perror(fsrc); goto cleanup; } - wwtest_fputs("export fn foo() i32 = { return 7; };", f); - if (fclose(f) != 0) { perror(fsrc); goto cleanup; } - - snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src); - if (runwait(cmd) != 0) { fprintf(stderr, "w6c main failed\n"); goto cleanup; } - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf); - if (runwait(cmd) != 0) { fprintf(stderr, "w6a main failed\n"); goto cleanup; } - snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, fasm, fsrc); - if (runwait(cmd) != 0) { fprintf(stderr, "w6c foo failed\n"); goto cleanup; } - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, fobj, fasm); - if (runwait(cmd) != 0) { fprintf(stderr, "w6a foo failed\n"); goto cleanup; } - snprintf(cmd, sizeof cmd, "ar rcs %s %s", lib, fobj); - if (runwait(cmd) != 0) { fprintf(stderr, "ar failed\n"); goto cleanup; } - - int counts[] = { 1, 64, 65, 100, 128 }; - int fail = 0; - for (int i = 0; i < (int)(sizeof counts / sizeof counts[0]); i++) { - int n = counts[i]; - char wlnk[2048], clnk[2048]; - snprintf(wlnk, sizeof wlnk, "%s/w6l_ww", bin); - snprintf(clnk, sizeof clnk, "%s/w6l", bin); - int wrc = linkwith(wlnk, obj, libdir, n); - int crc = linkwith(clnk, obj, libdir, n); - if (wrc != 0) { - fprintf(stderr, "FAIL: w6l_ww nflags=%d link rc=%d " - "(65th-style -L not honoured → heap overflow)\n", - n, wrc); - fail++; - } - if (crc != 0) { - fprintf(stderr, "FAIL: w6l (cstage) nflags=%d link rc=%d\n", - n, crc); - fail++; - } - } - - if (fail) { - fprintf(stderr, "w6l_manyflags: %d failure(s)\n", fail); - goto cleanup; - } - result = 0; - -cleanup: - { - int cleanfail = 0; - if (unlink(src) != 0 && errno != ENOENT) { - perror(src); cleanfail = 1; - } - if (unlink(asmf) != 0 && errno != ENOENT) { - perror(asmf); cleanfail = 1; - } - if (unlink(obj) != 0 && errno != ENOENT) { - perror(obj); cleanfail = 1; - } - if (unlink(fsrc) != 0 && errno != ENOENT) { - perror(fsrc); cleanfail = 1; - } - if (unlink(fasm) != 0 && errno != ENOENT) { - perror(fasm); cleanfail = 1; - } - if (unlink(fobj) != 0 && errno != ENOENT) { - perror(fobj); cleanfail = 1; - } - if (unlink(lib) != 0 && errno != ENOENT) { - perror(lib); cleanfail = 1; - } - char exe[128]; - snprintf(exe, sizeof exe, "%s/wwfc_x", libdir); - if (unlink(exe) != 0 && errno != ENOENT) { - perror(exe); - cleanfail = 1; - } - if (rmdir(libdir) != 0) { - perror(libdir); - cleanfail = 1; - } - if (cleanfail && result == 0) result = 1; - } - if (result != 0) return result; - printf("w6l_manyflags: -L past slot 64 honoured by both stages " - "(1/64/65/100/128 flags)\n"); - return result; -}