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.
168 lines
4.2 KiB
C
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;
|
|
}
|