ww source: reject raw NUL characters
This commit is contained in:
@@ -130,6 +130,58 @@ fn rewritemidbomfile(path: str, before: str, after: str) void = {
|
||||
putbomfile(path, before, after, true);
|
||||
};
|
||||
|
||||
fn putnulfile(path: str, before: str, after: str, rewrite: bool) void = {
|
||||
let flags: os.flag = os.flag.WRONLY;
|
||||
if (rewrite) { flags |= os.flag.TRUNC; }
|
||||
else { flags |= os.flag.CREATE | os.flag.EXCL; };
|
||||
let fd: i32 = os.open(path, flags, 384i32);
|
||||
assert(fd >= 0);
|
||||
match (os.writeall(fd, before.ptr, before.len: u64)) {
|
||||
case let n: i64 => assert(n == before.len: i64);
|
||||
case let e: os.oserror => abort("write before NUL failed");
|
||||
};
|
||||
let nul: [1]u8;
|
||||
nul[0] = 0u8;
|
||||
match (os.writeall(fd, nul.ptr, 1u64)) {
|
||||
case let n: i64 => assert(n == 1i64);
|
||||
case let e: os.oserror => abort("write NUL failed");
|
||||
};
|
||||
match (os.writeall(fd, after.ptr, after.len: u64)) {
|
||||
case let n: i64 => assert(n == after.len: i64);
|
||||
case let e: os.oserror => abort("write after NUL failed");
|
||||
};
|
||||
assert(os.close(fd) == 0);
|
||||
};
|
||||
|
||||
fn writenulfile(path: str, before: str, after: str) void = {
|
||||
putnulfile(path, before, after, false);
|
||||
};
|
||||
|
||||
fn rewritenulfile(path: str, before: str, after: str) void = {
|
||||
putnulfile(path, before, after, true);
|
||||
};
|
||||
|
||||
fn writedoublenulfile(path: str, before: str, after: str) void = {
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32);
|
||||
assert(fd >= 0);
|
||||
match (os.writeall(fd, before.ptr, before.len: u64)) {
|
||||
case let n: i64 => assert(n == before.len: i64);
|
||||
case let e: os.oserror => abort("write before NUL pair failed");
|
||||
};
|
||||
let pair: [2]u8;
|
||||
pair[0] = 0u8; pair[1] = 0u8;
|
||||
match (os.writeall(fd, pair.ptr, 2u64)) {
|
||||
case let n: i64 => assert(n == 2i64);
|
||||
case let e: os.oserror => abort("write NUL pair failed");
|
||||
};
|
||||
match (os.writeall(fd, after.ptr, after.len: u64)) {
|
||||
case let n: i64 => assert(n == after.len: i64);
|
||||
case let e: os.oserror => abort("write after NUL pair failed");
|
||||
};
|
||||
assert(os.close(fd) == 0);
|
||||
};
|
||||
|
||||
fn writeexecutable(path: str, content: str) void = {
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 448i32);
|
||||
@@ -16968,3 +17020,479 @@ fn runtimepath(relative: str) str = {
|
||||
&& !directoryhasnew(root));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// Go 1.26.5's physical-source reader rejects U+0000 before token context can
|
||||
// turn it into comment or literal payload. Selected roots, dependencies, and
|
||||
// every test-source role share that rule; target-excluded files do not.
|
||||
@test fn raw_nul_is_rejected_in_every_selected_source() void = {
|
||||
let root: str = fresh();
|
||||
let source: str = strings.concat(root, "/source");
|
||||
mkdirall(source);
|
||||
|
||||
// Direct frontend ownership and exact C/WW recovery parity.
|
||||
let leading: str = strings.concat(root, "/leading.ww");
|
||||
let comment: str = strings.concat(root, "/comment.ww");
|
||||
let stringlit: str = strings.concat(root, "/string.ww");
|
||||
let runelit: str = strings.concat(root, "/rune.ww");
|
||||
let between: str = strings.concat(root, "/between.ww");
|
||||
let escapechar: str = strings.concat(root, "/escape-char.ww");
|
||||
let escapedigit: str = strings.concat(root, "/escape-digit.ww");
|
||||
let identifier: str = strings.concat(root, "/identifier.ww");
|
||||
let lineend: str = strings.concat(root, "/line-end.ww");
|
||||
let blockend: str = strings.concat(root, "/block-end.ww");
|
||||
let operator: str = strings.concat(root, "/operator.ww");
|
||||
let number: str = strings.concat(root, "/number.ww");
|
||||
let suffix: str = strings.concat(root, "/suffix.ww");
|
||||
let trailing: str = strings.concat(root, "/trailing.ww");
|
||||
writenulfile(leading, "",
|
||||
"package main;\nfn main() void = {};\n");
|
||||
writenulfile(comment, "package main;\n// NUL: ",
|
||||
" here\nfn main() void = {};\n");
|
||||
writenulfile(stringlit,
|
||||
"package main;\nfn main() void = { let value: str = \"NUL: ",
|
||||
"\"; };\n");
|
||||
writenulfile(runelit,
|
||||
"package main;\nfn main() void = { let value: rune = '",
|
||||
"A'; };\n");
|
||||
writenulfile(between, "package main;\n",
|
||||
"\nfn main() void = {};\n");
|
||||
writenulfile(escapechar,
|
||||
"package main;\nfn main() void = { let value: str = \"\\",
|
||||
"n\"; };\n");
|
||||
writenulfile(escapedigit,
|
||||
"package main;\nfn main() void = { let value: str = \"\\x0",
|
||||
"0\"; };\n");
|
||||
writenulfile(identifier, "pack",
|
||||
"age main;\nfn main() void = {};\n");
|
||||
writenulfile(lineend, "package main;\n// comment",
|
||||
"\nfn main() void = {};\n");
|
||||
writenulfile(blockend, "package main;\n/* end *",
|
||||
"/\nfn main() void = {};\n");
|
||||
writenulfile(operator,
|
||||
"package main;\nfn main() void = { let value: bool = true =",
|
||||
"= true; };\n");
|
||||
writenulfile(number,
|
||||
"package main;\nfn main() void = { let value: i32 = 1",
|
||||
"_0; };\n");
|
||||
writenulfile(suffix,
|
||||
"package main;\nfn main() void = { let value: i32 = 1i",
|
||||
"32; };\n");
|
||||
writenulfile(trailing,
|
||||
"package main;\nfn main() void = {};\n", "");
|
||||
let direct: []str = [leading, comment, stringlit, runelit, between,
|
||||
escapechar, escapedigit, identifier, lineend, blockend, operator,
|
||||
number, suffix, trailing];
|
||||
let positions: []str = [":1:1: error: invalid NUL character\n",
|
||||
":2:9: error: invalid NUL character\n",
|
||||
":2:43: error: invalid NUL character\n",
|
||||
":2:39: error: invalid NUL character\n",
|
||||
":2:1: error: invalid NUL character\n",
|
||||
":2:39: error: invalid NUL character\n",
|
||||
":2:41: error: invalid NUL character\n",
|
||||
":1:5: error: invalid NUL character\n",
|
||||
":2:11: error: invalid NUL character\n",
|
||||
":2:9: error: invalid NUL character\n",
|
||||
":2:44: error: invalid NUL character\n",
|
||||
":2:38: error: invalid NUL character\n",
|
||||
":2:39: error: invalid NUL character\n",
|
||||
":3:1: error: invalid NUL character\n"];
|
||||
let compilers: []str = ["w6c", "w6c_ww"];
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let di: i32 = 0;
|
||||
for (di < direct.len) {
|
||||
let reference: str = "";
|
||||
let si: i32 = 0;
|
||||
for (si < compilers.len) {
|
||||
let asmout: str = strings.concat(root, "/direct-", tags[si], "-",
|
||||
boundarypkgname(di), ".s");
|
||||
let av: []str = [driver(compilers[si]), "-c", "--command-package",
|
||||
"-o", asmout, direct[di]];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("nul-direct-", tags[si], "-",
|
||||
boundarypkgname(di)), av,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && has(out.stderr, positions[di]));
|
||||
assert(occurrences(out.stderr, "invalid NUL character") == 1);
|
||||
assert(!os.exists(asmout));
|
||||
if (si == 0) { reference = strings.dup(out.stderr); }
|
||||
else { assert(same(reference, out.stderr)); };
|
||||
si += 1;
|
||||
};
|
||||
di += 1;
|
||||
};
|
||||
|
||||
// Decoder recovery is also exact for repeated bytes and the internal
|
||||
// module-boundary directive used by package-unit composition.
|
||||
let repeated: str = strings.concat(root, "/repeated.ww");
|
||||
let directive: str = strings.concat(root, "/directive.ww");
|
||||
writedoublenulfile(repeated, "pack",
|
||||
"age main;\nfn main() void = {};\n");
|
||||
writenulfile(directive, "//ww:module-reset ma",
|
||||
"in\npackage main;\nfn main() void = {};\n");
|
||||
let recoverypaths: []str = [repeated, directive];
|
||||
let recoverycounts: []i32 = [2, 1];
|
||||
let recoverypositions: []str = [
|
||||
":1:5: error: invalid NUL character\n",
|
||||
":1:21: error: invalid NUL character\n"];
|
||||
let ri: i32 = 0;
|
||||
for (ri < recoverypaths.len) {
|
||||
let reference: str = "";
|
||||
let rsi: i32 = 0;
|
||||
for (rsi < compilers.len) {
|
||||
let asmout: str = strings.concat(root, "/recovery-", tags[rsi],
|
||||
"-", boundarypkgname(ri), ".s");
|
||||
let av: []str = [driver(compilers[rsi]), "-c",
|
||||
"--command-package", "-o", asmout, recoverypaths[ri]];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("nul-recovery-", tags[rsi], "-",
|
||||
boundarypkgname(ri)), av,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& has(out.stderr, recoverypositions[ri])
|
||||
&& occurrences(out.stderr,
|
||||
"invalid NUL character") == recoverycounts[ri]
|
||||
&& !os.exists(asmout));
|
||||
if (ri == 0) {
|
||||
assert(has(out.stderr,
|
||||
":1:6: error: invalid NUL character\n"));
|
||||
};
|
||||
if (rsi == 0) { reference = strings.dup(out.stderr); }
|
||||
else { assert(same(reference, out.stderr)); };
|
||||
rsi += 1;
|
||||
};
|
||||
ri += 1;
|
||||
};
|
||||
|
||||
// An escape denotes a literal value; it is not a raw source NUL.
|
||||
let escaped: str = strings.concat(root, "/escaped.ww");
|
||||
writefile(escaped, strings.concat(
|
||||
"package main;\n",
|
||||
"fn main() i32 = { let value: str = \"\\x00\"; return value.len - 1; };\n"));
|
||||
let escapedasm: []str = [strings.concat(root, "/escaped-c.s"),
|
||||
strings.concat(root, "/escaped-ww.s")];
|
||||
let si: i32 = 0;
|
||||
for (si < compilers.len) {
|
||||
let av: []str = [driver(compilers[si]), "-c", "--command-package",
|
||||
"-o", escapedasm[si], escaped];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("nul-escaped-", tags[si]), av,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
si += 1;
|
||||
};
|
||||
assert(same(readfile(escapedasm[0]), readfile(escapedasm[1])));
|
||||
|
||||
let dep: str = strings.concat(source, "/dep");
|
||||
let app: str = strings.concat(source, "/app");
|
||||
let bad: str = strings.concat(source, "/bad");
|
||||
let good: str = strings.concat(source, "/good");
|
||||
mkdirall(dep); mkdirall(app); mkdirall(bad); mkdirall(good);
|
||||
writenulfile(strings.concat(dep, "/dep.ww"),
|
||||
"package dep;\n// NUL: ",
|
||||
" dependency\nexport fn value() i32 = { return 42; };\n");
|
||||
writefile(strings.concat(app, "/main.ww"), strings.concat(
|
||||
"package main;\nimport dep;\n",
|
||||
"fn main() i32 = { return dep.value() - 42; };\n"));
|
||||
writefile(strings.concat(bad, "/a.ww"),
|
||||
"package main;\nfn helper() i32 = { return 1; };\n");
|
||||
writenulfile(strings.concat(bad, "/main.ww"),
|
||||
"package main;\n// NUL: ", strings.concat(
|
||||
" root\nimport missing;\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(good, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 0; };\n");
|
||||
writenulfile(strings.concat(good, "/ignored_windows.ww"),
|
||||
"not package NUL: ", " and not WW\n");
|
||||
|
||||
// Invalid root and dependency sources stop before graph actions and public
|
||||
// output. The wrong-target source remains outside the semantic input set.
|
||||
let goodbins: []str = [strings.concat(root, "/good-c"),
|
||||
strings.concat(root, "/good-ww")];
|
||||
let invaliddiags: []str = ["", ""];
|
||||
let rootdiags: []str = ["", ""];
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let depwork: str = strings.concat(root, "/dep-work-", tags[si]);
|
||||
let depout: str = strings.concat(root, "/dep-output-", tags[si]);
|
||||
mkdirall(depwork);
|
||||
let depav: []str = [driver(stages[si]), "build", "-w", depwork,
|
||||
"-I", source, "-o", depout, "app"];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("nul-dependency-", tags[si]), depav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& has(out.stderr, "/dep/dep.ww:2:9: error: invalid NUL character\n"));
|
||||
assert(occurrences(out.stderr, "invalid NUL character") == 1
|
||||
&& !has(out.stderr, "w6c failed"));
|
||||
assert(!os.exists(depout) && directoryisempty(depwork));
|
||||
invaliddiags[si] = strings.dup(out.stderr);
|
||||
|
||||
let badwork: str = strings.concat(root, "/bad-work-", tags[si]);
|
||||
let badout: str = strings.concat(root, "/bad-output-", tags[si]);
|
||||
mkdirall(badwork);
|
||||
let badav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-I", source, "-o", badout, "bad"];
|
||||
runcommand(root, strings.concat("nul-root-", tags[si]), badav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& has(out.stderr, "/bad/main.ww:2:9: error: invalid NUL character\n"));
|
||||
assert(!has(out.stderr, "cannot find import")
|
||||
&& occurrences(out.stderr, "invalid NUL character") == 1);
|
||||
assert(!os.exists(badout) && directoryisempty(badwork));
|
||||
rootdiags[si] = strings.dup(out.stderr);
|
||||
|
||||
let goodwork: str = strings.concat(root, "/good-work-", tags[si]);
|
||||
mkdirall(goodwork);
|
||||
let goodav: []str = [driver(stages[si]), "build", "-w", goodwork,
|
||||
"-I", source, "-o", goodbins[si], "good"];
|
||||
runcommand(root, strings.concat("nul-wrong-target-", tags[si]), goodav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !directoryhasnew(goodwork)
|
||||
&& !directoryhasfragment(goodwork, ".wwtxn."));
|
||||
let runav: []str = [goodbins[si]];
|
||||
runcommand(root, strings.concat("nul-good-run-", tags[si]), runav,
|
||||
time.second, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
si += 1;
|
||||
};
|
||||
assert(same(invaliddiags[0], invaliddiags[1])
|
||||
&& same(rootdiags[0], rootdiags[1])
|
||||
&& same(readfile(goodbins[0]), readfile(goodbins[1])));
|
||||
|
||||
// Production, same-package, external-package, and test-only sources all
|
||||
// reject before product construction, execution, accounting, and retention.
|
||||
let prodcase: str = strings.concat(source, "/prodcase");
|
||||
let samecase: str = strings.concat(source, "/samecase");
|
||||
let externalcase: str = strings.concat(source, "/externalcase");
|
||||
let onlycase: str = strings.concat(source, "/onlycase");
|
||||
let repeatcase: str = strings.concat(source, "/repeatcase");
|
||||
mkdirall(prodcase); mkdirall(samecase); mkdirall(externalcase);
|
||||
mkdirall(onlycase); mkdirall(repeatcase);
|
||||
writefile(strings.concat(prodcase, "/more.ww"),
|
||||
"package prodcase;\nfn more() i32 = { return 2; };\n");
|
||||
writenulfile(strings.concat(prodcase, "/prod.ww"),
|
||||
"package prodcase;\n// NUL: ",
|
||||
" production\nexport fn value() i32 = { return 1; };\n");
|
||||
writefile(strings.concat(prodcase, "/same_test.ww"),
|
||||
"package prodcase;\n@test fn must_not_run() void = { abort(); };\n");
|
||||
writefile(strings.concat(samecase, "/prod.ww"),
|
||||
"package samecase;\nfn value() i32 = { return 1; };\n");
|
||||
writenulfile(strings.concat(samecase, "/same_test.ww"),
|
||||
"package samecase;\n// NUL: ",
|
||||
" same\n@test fn must_not_run() void = { abort(); };\n");
|
||||
writefile(strings.concat(externalcase, "/prod.ww"),
|
||||
"package externalcase;\nexport fn value() i32 = { return 1; };\n");
|
||||
writenulfile(strings.concat(externalcase, "/external_test.ww"),
|
||||
"package externalcase_test;\n// NUL: ", strings.concat(
|
||||
" external\nimport externalcase;\n",
|
||||
"@test fn must_not_run() void = { abort(); };\n"));
|
||||
writenulfile(strings.concat(onlycase, "/only_test.ww"), "",
|
||||
"package onlycase;\n@test fn must_not_run() void = { abort(); };\n");
|
||||
writedoublenulfile(strings.concat(repeatcase, "/prod.ww"),
|
||||
"package repeatcase;\n// pair ",
|
||||
" here\nfn value() i32 = { return 1; };\n");
|
||||
let testids: []str = ["prodcase", "samecase", "externalcase", "onlycase",
|
||||
"repeatcase"];
|
||||
let testpositions: []str = ["/prodcase/prod.ww:2:9: error: invalid NUL character\n",
|
||||
"/samecase/same_test.ww:2:9: error: invalid NUL character\n",
|
||||
"/externalcase/external_test.ww:2:9: error: invalid NUL character\n",
|
||||
"/onlycase/only_test.ww:1:1: error: invalid NUL character\n",
|
||||
"/repeatcase/prod.ww:2:9: error: invalid NUL character\n"];
|
||||
let testcounts: []i32 = [1, 1, 1, 1, 2];
|
||||
let testdiags: []str = ["", "", "", "", ""];
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let ti: i32 = 0;
|
||||
for (ti < testids.len) {
|
||||
let testwork: str = strings.concat(root, "/test-work-", tags[si], "-",
|
||||
boundarypkgname(ti));
|
||||
let testout: str = strings.concat(root, "/test-output-", tags[si], "-",
|
||||
boundarypkgname(ti));
|
||||
mkdirall(testwork);
|
||||
let av: []str = [driver(stages[si]), "test", "-w", testwork,
|
||||
"-I", source, "-o", testout, testids[ti]];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("nul-test-", tags[si], "-",
|
||||
boundarypkgname(ti)), av,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n")
|
||||
&& has(out.stderr, testpositions[ti]));
|
||||
assert(occurrences(out.stderr,
|
||||
"invalid NUL character") == testcounts[ti]
|
||||
&& !has(out.stdout, "must_not_run")
|
||||
&& !has(out.stdout, "discovered") && !has(out.stdout, "ok "));
|
||||
if (ti == 4) {
|
||||
assert(has(out.stderr,
|
||||
"/repeatcase/prod.ww:2:10: error: invalid NUL character\n"));
|
||||
};
|
||||
assert(!os.exists(testout) && directoryisempty(testwork));
|
||||
if (si == 0) { testdiags[ti] = strings.dup(out.stderr); }
|
||||
else { assert(same(testdiags[ti], out.stderr)); };
|
||||
ti += 1;
|
||||
};
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// A warm invalid edit commits nothing and preserves prior public and action
|
||||
// bytes. Exact restoration reuses the committed generation without a tool.
|
||||
let warm: str = strings.concat(source, "/warm");
|
||||
mkdirall(warm);
|
||||
let warmpath: str = strings.concat(warm, "/main.ww");
|
||||
let warmvalid: str =
|
||||
"package main;\nfn main() i32 = { return 0; };\n";
|
||||
writefile(warmpath, warmvalid);
|
||||
let wrapper: str = strings.concat(root, "/nul-compiler-wrapper.sh");
|
||||
writeexecutable(wrapper, strings.concat(
|
||||
"#!/bin/sh\n",
|
||||
"printf 'compile\\n' >> \"$WW_NUL_TRACE\"\n",
|
||||
"exec \"$WW_NUL_REAL\" \"$@\"\n"));
|
||||
let commandsuffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
|
||||
".init.unit.ww", ".init.s", ".init.o"];
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let warmwork: str = strings.concat(root, "/warm-work-", tags[si]);
|
||||
let warmout: str = strings.concat(root, "/warm-output-", tags[si]);
|
||||
let trace: str = strings.concat(root, "/warm-trace-", tags[si]);
|
||||
mkdirall(warmwork); writefile(trace, "");
|
||||
let env: []str = os.getenvs();
|
||||
append(env, strings.concat("WW_W6C=", wrapper));
|
||||
append(env, strings.concat("WW_NUL_TRACE=", trace));
|
||||
append(env, strings.concat("WW_NUL_REAL=", driver(compilers[si])));
|
||||
let av: []str = [driver(stages[si]), "build", "-w", warmwork,
|
||||
"-I", source, "-o", warmout, "warm"];
|
||||
let out: commandout;
|
||||
runcommandenv(root, strings.concat("nul-warm-cold-", tags[si]), av,
|
||||
env, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& readfile(trace).len != 0);
|
||||
let refs: []str = alloc([], commandsuffixes.len: u64)!;
|
||||
let fi: i32 = 0;
|
||||
for (fi < commandsuffixes.len) {
|
||||
append(refs, strings.dup(readfile(strings.concat(warmwork,
|
||||
"/warm", commandsuffixes[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
let toolpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c",
|
||||
"/.wwtool.w6a", "/.wwtool.stamp"];
|
||||
let toolrefs: []str = alloc([], toolpaths.len: u64)!;
|
||||
fi = 0;
|
||||
for (fi < toolpaths.len) {
|
||||
append(toolrefs, strings.dup(readfile(strings.concat(warmwork,
|
||||
toolpaths[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
let binref: str = strings.dup(readfile(warmout));
|
||||
rewritefile(trace, "");
|
||||
rewritenulfile(warmpath, "package main;\n// NUL: ",
|
||||
" invalid\nfn main() i32 = { return 0; };\n");
|
||||
runcommandenv(root, strings.concat("nul-warm-invalid-", tags[si]), av,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& has(out.stderr, ":2:9: error: invalid NUL character\n"));
|
||||
assert(readfile(trace).len == 0 && same(binref, readfile(warmout)));
|
||||
fi = 0;
|
||||
for (fi < commandsuffixes.len) {
|
||||
assert(same(refs[fi], readfile(strings.concat(warmwork,
|
||||
"/warm", commandsuffixes[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
fi = 0;
|
||||
for (fi < toolpaths.len) {
|
||||
assert(same(toolrefs[fi], readfile(strings.concat(warmwork,
|
||||
toolpaths[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
assert(!directoryhasnew(warmwork)
|
||||
&& !directoryhasfragment(warmwork, ".wwtxn."));
|
||||
rewritefile(warmpath, warmvalid);
|
||||
runcommandenv(root, strings.concat("nul-warm-restored-", tags[si]), av,
|
||||
env, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& readfile(trace).len == 0 && same(binref, readfile(warmout)));
|
||||
fi = 0;
|
||||
for (fi < commandsuffixes.len) {
|
||||
assert(same(refs[fi], readfile(strings.concat(warmwork,
|
||||
"/warm", commandsuffixes[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
fi = 0;
|
||||
for (fi < toolpaths.len) {
|
||||
assert(same(toolrefs[fi], readfile(strings.concat(warmwork,
|
||||
toolpaths[fi]))));
|
||||
fi += 1;
|
||||
};
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// Source validation is request-local under overlapping valid and invalid
|
||||
// builds, including independent work, outputs, diagnostics, and cleanup.
|
||||
let pcwork: str = strings.concat(root, "/parallel-c-work");
|
||||
let pwwork: str = strings.concat(root, "/parallel-ww-work");
|
||||
let pcout: str = strings.concat(root, "/parallel-c-output");
|
||||
let pwout: str = strings.concat(root, "/parallel-ww-output");
|
||||
mkdirall(pcwork); mkdirall(pwwork);
|
||||
let pcav: []str = [driver("ww"), "build", "-w", pcwork,
|
||||
"-I", source, "-o", pcout, "good"];
|
||||
let pwav: []str = [driver("ww_ww"), "build", "-w", pwwork,
|
||||
"-I", source, "-o", pwout, "bad"];
|
||||
let pc: exec.command;
|
||||
pc.path = pcav[0]; pc.argv = pcav; pc.env = os.getenvs(); pc.dir = repo();
|
||||
pc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
|
||||
pc.stderrpath = strings.concat(root, "/parallel-c.stderr");
|
||||
pc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(120i64 * (time.second: i64)): time.duration);
|
||||
pc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let pw: exec.command;
|
||||
pw.path = pwav[0]; pw.argv = pwav; pw.env = os.getenvs(); pw.dir = repo();
|
||||
pw.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
|
||||
pw.stderrpath = strings.concat(root, "/parallel-ww.stderr");
|
||||
pw.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(120i64 * (time.second: i64)): time.duration);
|
||||
pw.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let pcp: exec.process;
|
||||
let pwp: exec.process;
|
||||
exec.start(&pcp, &pc); exec.start(&pwp, &pw);
|
||||
let pcdone: bool = false;
|
||||
let pwdone: bool = false;
|
||||
for (!pcdone || !pwdone) {
|
||||
if (!pcdone) { pcdone = exec.poll(&pcp); };
|
||||
if (!pwdone) { pwdone = exec.poll(&pwp); };
|
||||
if (!pcdone || !pwdone) {
|
||||
time.sleep(time.millisecond, time.clock.monotonic);
|
||||
};
|
||||
};
|
||||
assert(pcp.result.errno == 0 && pcp.result.cleanuperrno == 0
|
||||
&& pcp.result.termination == exec.termination.EXIT
|
||||
&& pcp.result.code == 0);
|
||||
assert(pwp.result.errno == 0 && pwp.result.cleanuperrno == 0
|
||||
&& pwp.result.termination == exec.termination.EXIT
|
||||
&& pwp.result.code == 1);
|
||||
assert(readfile(pc.stdoutpath).len == 0 && readfile(pc.stderrpath).len == 0);
|
||||
assert(readfile(pw.stdoutpath).len == 0
|
||||
&& has(readfile(pw.stderrpath), "invalid NUL character"));
|
||||
assert(same(readfile(pcout), readfile(goodbins[0])) && !os.exists(pwout));
|
||||
assert(directoryisempty(pwwork) && !directoryhasnew(pcwork)
|
||||
&& !directoryhasfragment(pcwork, ".wwtxn."));
|
||||
let runav: []str = [pcout];
|
||||
let out: commandout;
|
||||
runcommand(root, "nul-parallel-run", runav, time.second, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
assert(!directoryhasfragment(root, ".wwtxn.")
|
||||
&& !directoryhasfragment(root, ".install")
|
||||
&& !directoryhasnew(root));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@@ -18,11 +18,11 @@ toklit(Arena *a, Tok t)
|
||||
}
|
||||
|
||||
static int
|
||||
runrow(const char *src, const char *expect)
|
||||
runrown(const char *src, size_t len, const char *expect, int expecterrs)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
lexinit(&l, a, "<test>", src, strlen(src));
|
||||
lexinit(&l, a, "<test>", src, len);
|
||||
|
||||
char *got = amalloc(a, 1);
|
||||
got[0] = '\0';
|
||||
@@ -48,15 +48,23 @@ runrow(const char *src, const char *expect)
|
||||
got[n] = '\0';
|
||||
}
|
||||
|
||||
int ok = strcmp(got, expect) == 0;
|
||||
int ok = strcmp(got, expect) == 0
|
||||
&& (expecterrs < 0 || l.errs == expecterrs);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "lex mismatch:\n src: %s\n"
|
||||
" want: %s\n got: %s\n", src, expect, got);
|
||||
fprintf(stderr, "lex mismatch:\n want: %s (%d errors)\n"
|
||||
" got: %s (%d errors)\n", expect, expecterrs, got,
|
||||
l.errs);
|
||||
}
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
runrow(const char *src, const char *expect)
|
||||
{
|
||||
return runrown(src, strlen(src), expect, -1);
|
||||
}
|
||||
|
||||
struct row { const char *src, *expect; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
@@ -133,11 +141,11 @@ static const struct row rows[] = {
|
||||
* not the token stream. The verbatim Hare messages live at lexunicode
|
||||
* in cmd/wcc/lex.c. */
|
||||
static int
|
||||
runerr(const char *src)
|
||||
runerrn(const char *src, size_t len)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
lexinit(&l, a, "<test>", src, strlen(src));
|
||||
lexinit(&l, a, "<test>", src, len);
|
||||
for (;;) {
|
||||
Tok t = lexnext(&l);
|
||||
if (t.kind == TK_EOF)
|
||||
@@ -151,6 +159,12 @@ runerr(const char *src)
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
runerr(const char *src)
|
||||
{
|
||||
return runerrn(src, strlen(src));
|
||||
}
|
||||
|
||||
static int
|
||||
runrewind(void)
|
||||
{
|
||||
@@ -205,6 +219,93 @@ static const char *const errrows[] = {
|
||||
"'\\uDFFF'", /* top of the UTF-16 surrogate range */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *src;
|
||||
size_t len;
|
||||
const char *expect;
|
||||
int errs;
|
||||
} nulrows[] = {
|
||||
{ "\0package main;", sizeof "\0package main;" - 1,
|
||||
"package IDENT(main) ;", 1 },
|
||||
{ "pack\0age main;", sizeof "pack\0age main;" - 1,
|
||||
"package IDENT(main) ;", 1 },
|
||||
{ "1\0_0", sizeof "1\0_0" - 1, "INT(10)", 1 },
|
||||
{ "1f\0oo", sizeof "1f\0oo" - 1, "INT(1) IDENT(foo)", 1 },
|
||||
{ "=\0=", sizeof "=\0=" - 1, "==", 1 },
|
||||
{ "/\0/ comment\nfn", sizeof "/\0/ comment\nfn" - 1, "fn", 1 },
|
||||
{ "/* end *\0/ fn", sizeof "/* end *\0/ fn" - 1, "fn", 1 },
|
||||
{ "\"a\0b\"", sizeof "\"a\0b\"" - 1, "STR(ab)", 1 },
|
||||
{ "'\0A'", sizeof "'\0A'" - 1, "RUNE(65)", 1 },
|
||||
{ "fn\0", sizeof "fn\0" - 1, "fn", 1 },
|
||||
{ "f\0\0n", sizeof "f\0\0n" - 1, "fn", 2 },
|
||||
};
|
||||
|
||||
static int
|
||||
runescapenul(void)
|
||||
{
|
||||
static const char line[] = "\"\\\0n\"";
|
||||
static const char hex[] = "\"\\x0" "\0" "0\"";
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
lexinit(&l, a, "<test>", line, sizeof line - 1);
|
||||
Tok t = lexnext(&l);
|
||||
int ok = t.kind == TK_STR && t.tlen == 1 && t.text[0] == '\n'
|
||||
&& l.errs == 1 && lexnext(&l).kind == TK_EOF;
|
||||
lexinit(&l, a, "<test>", hex, sizeof hex - 1);
|
||||
t = lexnext(&l);
|
||||
ok = ok && t.kind == TK_STR && t.tlen == 1 && t.text[0] == '\0'
|
||||
&& l.errs == 1 && lexnext(&l).kind == TK_EOF;
|
||||
if (!ok)
|
||||
fprintf(stderr, "NUL escape recovery failed\n");
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
runsuffixnul(void)
|
||||
{
|
||||
static const char src[] = "1i" "\0" "8";
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
lexinit(&l, a, "<test>", src, sizeof src - 1);
|
||||
Tok t = lexnext(&l);
|
||||
int ok = t.kind == TK_INT && t.v.uval == 1 && t.tsuffix != NULL
|
||||
&& strcmp(t.tsuffix, "i8") == 0 && l.errs == 1
|
||||
&& lexnext(&l).kind == TK_EOF;
|
||||
if (!ok)
|
||||
fprintf(stderr, "NUL typed-suffix recovery failed\n");
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
runlongdirective(void)
|
||||
{
|
||||
static const char pre[] = "//ww:module ";
|
||||
static const char tail[] = "\nfn";
|
||||
const size_t pathlen = 16384;
|
||||
const size_t len = sizeof pre - 1 + pathlen + sizeof tail - 1;
|
||||
char *src = malloc(len);
|
||||
if (!src)
|
||||
return 0;
|
||||
memcpy(src, pre, sizeof pre - 1);
|
||||
memset(src + sizeof pre - 1, 'a', pathlen);
|
||||
memcpy(src + sizeof pre - 1 + pathlen, tail, sizeof tail - 1);
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
lexinit(&l, a, "<test>", src, len);
|
||||
Tok m = lexnext(&l);
|
||||
Tok f = lexnext(&l);
|
||||
int ok = m.kind == TK_MODPATH && m.tlen == pathlen
|
||||
&& m.text[0] == 'a' && m.text[pathlen - 1] == 'a'
|
||||
&& f.kind == TK_FN && l.errs == 0;
|
||||
if (!ok)
|
||||
fprintf(stderr, "long module directive failed\n");
|
||||
freearena(a);
|
||||
free(src);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
@@ -225,6 +326,19 @@ main(void)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < sizeof nulrows / sizeof nulrows[0]; i++) {
|
||||
if (!runrown(nulrows[i].src, nulrows[i].len,
|
||||
nulrows[i].expect, nulrows[i].errs)) {
|
||||
fprintf(stderr, "NUL row %zu failed\n", i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (!runescapenul())
|
||||
fail++;
|
||||
if (!runsuffixnul())
|
||||
fail++;
|
||||
if (!runlongdirective())
|
||||
fail++;
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%zu lex tests failed\n", fail,
|
||||
sizeof rows / sizeof rows[0]);
|
||||
|
||||
Reference in New Issue
Block a user