Files
ww/test/wcc/961_opaque_guards.c
Hojun-Cho a95a7a316b test/wcc: carrier ownership repair and driver-contract adaptation
Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/
fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT-
tolerant checked unlinks, exact-path deletion (rm -rf only for an
owned pid-keyed dir or a .sepwork beneath one), and cleanup failure
fails a passing carrier without overwriting its diagnostic. In the
same pass the carriers adapt to the driver contract this branch lands:
--sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch
contract are asserted, and rows whose runtime or reject coverage moved
to test/wcc/data fixtures or test/lang @test owners are trimmed to the
byte/artifact/diagnostic observations only they can make.

Repair and adaptation ride together because most files interleave both
in the same hunks; splitting would manufacture intermediate carrier
states that never existed and cannot run against either driver.
2026-08-07 23:21:04 +09:00

168 lines
4.2 KiB
C

/*
* 961_opaque_guards — C-stage-only guards for the unsized opaque type.
*
* opaque is legal behind indirection (*opaque is 8 bytes and []opaque is a
* 24-byte header), but it cannot appear by value or be indexed through a
* slice. The WW frontend currently accepts the eight programs below because
* it does not validate those construction and binding sites. That polarity
* cannot be represented by the both-stage fixture protocol, so this carrier
* requires each program to fail with the C-stage driver.
*
* Both-stage size/align rejections, including nested aggregates, are owned by
* the six r961_neg_* fixtures. The sized pointer and slice controls are
* owned by r961_pos_ptr_opaque and r961_pos_slice_opaque.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; };
static const struct row rows[] = {
{ "neg_bare_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet x: opaque;\n"
"\treturn 0;\n"
"};\n" },
{ "neg_param",
"package main;\n"
"fn f(x: opaque) i32 = { return 0; };\n"
"export fn main() i32 = { return 0; };\n" },
/*
* A declaration isolates the return-type guard. A function body returning
* 0 would add an unrelated untyped_int-to-opaque rejection.
*/
{ "neg_return",
"package main;\n"
"fn f() opaque;\n"
"export fn main() i32 = { return 0; };\n" },
{ "neg_struct_field",
"package main;\n"
"type S = struct { x: opaque };\n"
"export fn main() i32 = { return 0; };\n" },
{ "neg_array_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]opaque;\n"
"\treturn 0;\n"
"};\n" },
/*
* The cast keeps this row on the slice-index guard rather than creating
* a bare opaque local.
*/
{ "neg_slice_index",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet s: []opaque;\n"
"\tlet v: i32 = s[0]: i32;\n"
"\treturn v;\n"
"};\n" },
{ "neg_tuple_member",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (opaque, i32);\n"
"\treturn 0;\n"
"};\n" },
{ "neg_tagged_variant",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet x: (opaque | i32);\n"
"\treturn 0;\n"
"};\n" },
};
static int
run_row(const char *driver, const struct row *r, int i)
{
char tmpdir[64] = "/tmp/wcopg_XXXXXX";
if (mkdtemp(tmpdir) == NULL) {
perror("opaque-guard: mkdtemp");
return -1;
}
char src[128], outbin[128], sepwork[160], rmcmd[192], cmd[2048];
snprintf(src, sizeof src, "%s/wcopg_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcopg_%d_%d", tmpdir, getpid(), i);
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork);
int result = -1;
FILE *f = fopen(src, "wb");
if (!f) goto cleanup;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1",
driver, outbin, src);
int rc = runwait(cmd);
int bad = (rc == 0);
if (bad) {
fprintf(stderr, "opaque-guard[%s]: build unexpectedly succeeded\n",
r->label);
}
result = bad ? -1 : 0;
cleanup: {
int cleanbad = 0;
if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1;
if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1;
if (runwait(rmcmd) != 0) cleanbad = 1;
if (rmdir(tmpdir) != 0) cleanbad = 1;
if (cleanbad) {
fprintf(stderr, "opaque-guard[%s]: temporary cleanup failed\n",
r->label);
result = -1;
}
}
return result;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int fail = 0;
for (int i = 0; i < n; i++) {
if (run_row(cdrv, &rows[i], i) != 0) fail++;
}
if (fail) {
fprintf(stderr, "opaque-guards: %d/%d row(s) failed\n", fail, n);
return 1;
}
printf("opaque-guards: %d/%d ok\n", n, n);
return 0;
}