From 7470b1a5c3ca4e9a880fb473262fe2738cc72251 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 10 Jun 2026 21:38:51 +0900 Subject: [PATCH] ww driver: align cstage flag-parse to ww twin; 949 pins parity Lone -I/-L/-l/-o now error "ww : -X needs an argument" (rc2), matching selfhost main.ww. do_test rewritten to an -I-only loop that loud-rejects -l/-L/-o/unknown instead of silently swallowing them. 949_driver_flagargs: table-driven, 10 rows, each run against both twins asserting identical rc+stderr. (#15-team) --- Makefile | 5 ++ cmd/ww/main.c | 81 ++++++++++++++++++----- test/wcc/949_driver_flagargs.c | 114 +++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 17 deletions(-) create mode 100644 test/wcc/949_driver_flagargs.c diff --git a/Makefile b/Makefile index 1e78a3a1..adc8435b 100644 --- a/Makefile +++ b/Makefile @@ -235,6 +235,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_arch \ $(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \ $(BIN)/test_at_test $(BIN)/test_selfimport $(BIN)/test_missingpkg \ + $(BIN)/test_driver_flagargs \ $(BIN)/test_let_global $(BIN)/test_def_neg_global \ $(BIN)/test_def_const_fold \ $(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \ @@ -604,6 +605,10 @@ $(BIN)/test_missingpkg: test/wcc/949_missingpkg.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_driver_flagargs: test/wcc/949_driver_flagargs.c $(BIN)/ww \ + $(BIN)/ww_ww | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 9a691a0d..5dcfa2bc 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -629,11 +629,14 @@ collect_tests(const char *dir, char ***files, int *n) return 0; } -/* Parse the standard -I/-L/-l flags into incs/libdirs/libs. The first - * non-flag positional becomes *src_out. Returns the index past the last - * arg consumed for positionals (so callers can pick up trailing args). */ +/* Parse the standard -I/-L/-l/-o flags into incs/libdirs/libs/outpath. The + * first non-flag positional becomes *src_out. Returns the index past the last + * arg consumed for positionals (so callers can pick up trailing args), or -1 + * if a flag is missing its argument (diagnostic already emitted). `cmd` names + * the subcommand for the diagnostic, byte-identical to the wwstage twin's + * per-subcommand wording (selfhost/cmd/ww/main.ww dobuild/dorun). */ static int -parse_build_flags(int argc, char **argv, +parse_build_flags(const char *cmd, int argc, char **argv, char *incs, size_t incsz, char *libdirs, size_t libdirsz, char *libs, size_t libsz, @@ -647,11 +650,21 @@ parse_build_flags(int argc, char **argv, size_t n = strlen(libs); snprintf(libs + n, libsz - n, "%s%s", n ? " " : "", argv[i]); - } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-l") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, + "ww %s: -l needs an argument\n", cmd); + return -1; + } size_t n = strlen(libs); snprintf(libs + n, libsz - n, "%s-l%s", n ? " " : "", argv[++i]); - } else if (strcmp(argv[i], "-L") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-L") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, + "ww %s: -L needs an argument\n", cmd); + return -1; + } size_t n = strlen(libdirs); snprintf(libdirs + n, libdirsz - n, "%s-L%s", n ? " " : "", argv[++i]); @@ -659,7 +672,12 @@ parse_build_flags(int argc, char **argv, size_t n = strlen(libdirs); snprintf(libdirs + n, libdirsz - n, "%s%s", n ? " " : "", argv[i]); - } else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-I") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, + "ww %s: -I needs an argument\n", cmd); + return -1; + } size_t n = strlen(incs); snprintf(incs + n, incsz - n, "%s%s", n ? ":" : "", argv[++i]); @@ -667,7 +685,12 @@ parse_build_flags(int argc, char **argv, size_t n = strlen(incs); snprintf(incs + n, incsz - n, "%s%s", n ? ":" : "", argv[i] + 2); - } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-o") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, + "ww %s: -o needs an argument\n", cmd); + return -1; + } snprintf(outpath, outsz, "%s", argv[++i]); } else if (strncmp(argv[i], "-o", 2) == 0 && argv[i][2]) { snprintf(outpath, outsz, "%s", argv[i] + 2); @@ -688,9 +711,10 @@ do_build(int argc, char **argv) char libdirs[2048] = {0}; char incs[2048] = {0}; char outflag[1024] = {0}; - parse_build_flags(argc, argv, incs, sizeof incs, + if (parse_build_flags("build", argc, argv, incs, sizeof incs, libdirs, sizeof libdirs, libs, sizeof libs, - outflag, sizeof outflag, &src); + outflag, sizeof outflag, &src) < 0) + return 2; if (src == NULL) src = "."; /* default: build cwd */ char resolved[1024]; int is_dir = 0; @@ -726,9 +750,10 @@ do_run(int argc, char **argv) char libdirs[2048] = {0}; char incs[2048] = {0}; char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */ - int next = parse_build_flags(argc, argv, incs, sizeof incs, + int next = parse_build_flags("run", argc, argv, incs, sizeof incs, libdirs, sizeof libdirs, libs, sizeof libs, outflag, sizeof outflag, &src); + if (next < 0) return 2; if (src == NULL) src = "."; char resolved[1024]; int is_dir = 0; @@ -766,13 +791,35 @@ static int do_test(int argc, char **argv) { const char *src = NULL; - char libs[2048] = {0}; - char libdirs[2048] = {0}; char incs[2048] = {0}; - char outflag[1024] = {0}; /* -o accepted+ignored: test uses the temp */ - parse_build_flags(argc, argv, incs, sizeof incs, - libdirs, sizeof libdirs, libs, sizeof libs, - outflag, sizeof outflag, &src); + /* test builds to a temp and runs it: -l/-L/-o carry no meaning here, so + * reject them (and any unknown flag) rather than silently swallow — + * byte-identical to the wwstage twin (selfhost/cmd/ww/main.ww dotest). */ + for (int i = 0; i < argc; i++) { + if (argv[i][0] == '-') { + if (argv[i][1] == 'I') { + const char *dir; + if (argv[i][2]) { + dir = argv[i] + 2; + } else { + if (i + 1 >= argc) { + fprintf(stderr, + "ww test: -I needs an argument\n"); + return 2; + } + dir = argv[++i]; + } + size_t n = strlen(incs); + snprintf(incs + n, sizeof incs - n, + "%s%s", n ? ":" : "", dir); + } else { + fprintf(stderr, "ww test: unknown flag\n"); + return 2; + } + } else if (src == NULL) { + src = argv[i]; + } + } const char *target = src ? src : "."; struct stat st; if (stat(target, &st) != 0) { diff --git a/test/wcc/949_driver_flagargs.c b/test/wcc/949_driver_flagargs.c new file mode 100644 index 00000000..23f60385 --- /dev/null +++ b/test/wcc/949_driver_flagargs.c @@ -0,0 +1,114 @@ +/* + * 949_driver_flagargs — task #15 B2: the ww driver's flag-argument error + * branches, pinned across BOTH driver twins (cstage `ww` and wwstage + * `ww_ww`). The branches under test: + * build/run — a lone -I/-L/-l/-o (flag with no following argument) is a + * hard error "ww : -X needs an argument" (rc 2), not a + * silently-swallowed positional. + * test — only -I carries meaning; -l/-L/-o and any unknown flag are + * rejected with "ww test: unknown flag" (rc 2); a lone -I is + * "ww test: -I needs an argument" (rc 2). + * Each row asserts the expected rc + stderr substring AND that the two + * drivers are byte-identical (rule 10): the cstage parse_build_flags / + * do_test must match the wwstage main.ww dobuild/dorun/dotest verbatim. + * + * All rows error during flag parsing, before any compile, so there are no + * build intermediates to redirect (rule 14: light driver test, any NNN). + * Sibling of 949_missingpkg (driver enforcement) and 993_ww_ww (twin parity). + */ +#include +#include +#include +#include +#include + +struct row { + const char *args; /* argv tail after the driver path */ + int rc; /* expected exit code */ + const char *sub; /* expected stderr substring */ +}; + +static const struct row rows[] = { + { "build -o", 2, "ww build: -o needs an argument" }, + { "build -I", 2, "ww build: -I needs an argument" }, + { "build -L", 2, "ww build: -L needs an argument" }, + { "build -l", 2, "ww build: -l needs an argument" }, + { "run -o", 2, "ww run: -o needs an argument" }, + { "run -l", 2, "ww run: -l needs an argument" }, + { "test -l", 2, "ww test: unknown flag" }, + { "test -o x", 2, "ww test: unknown flag" }, + { "test -zz", 2, "ww test: unknown flag" }, + { "test -I", 2, "ww test: -I needs an argument" }, +}; + +/* Run "/ " capturing rc + stderr text into err (NUL- + * terminated). Returns the child's exit code (or -1 on spawn failure). */ +static int +run_drv(const char *bin, const char *drv, const char *args, + char *err, size_t errsz) +{ + int pid = getpid(); + char errf[64], cmd[2048]; + snprintf(errf, sizeof errf, "/tmp/dfa949_%d.err", pid); + snprintf(cmd, sizeof cmd, "%s/%s %s >/dev/null 2>%s", bin, drv, args, errf); + int rc = system(cmd); + if (rc == -1) { unlink(errf); return -1; } + rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : 1; + err[0] = '\0'; + FILE *f = fopen(errf, "r"); + if (f) { + size_t got = fread(err, 1, errsz - 1, f); + err[got] = '\0'; + fclose(f); + } + unlink(errf); + return rc; +} + +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; + } + + int fail = 0; + size_t n = sizeof rows / sizeof rows[0]; + for (size_t i = 0; i < n; i++) { + char cerr[4096], werr[4096]; + int crc = run_drv(bin, "ww", rows[i].args, cerr, sizeof cerr); + int wrc = run_drv(bin, "ww_ww", rows[i].args, werr, sizeof werr); + + if (crc != rows[i].rc) { + fprintf(stderr, "949 FAIL: ww %s rc=%d want=%d\n", + rows[i].args, crc, rows[i].rc); + fail = 1; + } + if (!strstr(cerr, rows[i].sub)) { + fprintf(stderr, "949 FAIL: ww %s stderr missing '%s' (got '%s')\n", + rows[i].args, rows[i].sub, cerr); + fail = 1; + } + /* twin parity: ww_ww must match ww exactly (rc + stderr). */ + if (wrc != crc) { + fprintf(stderr, "949 FAIL: %s ww_ww rc=%d != ww rc=%d\n", + rows[i].args, wrc, crc); + fail = 1; + } + if (strcmp(werr, cerr) != 0) { + fprintf(stderr, "949 FAIL: %s ww_ww stderr '%s' != ww '%s'\n", + rows[i].args, werr, cerr); + fail = 1; + } + } + + if (fail) return 1; + printf("driver flag-argument error branches: %zu rows, ww==ww_ww pinned\n", n); + return 0; +}