package wwfixture; import crypto.sha256; import encoding.hex; import fnmatch; import hash; import os; import strings; def dirbufsize: i32 = 8192; def typemask: u32 = 61440u32; let fixtureerror: str = ""; export fn error() str = { return fixtureerror; }; fn seterror(message: str, detail: str) bool = { if (detail.len == 0) { fixtureerror = strings.dup(message); } else { fixtureerror = strings.concat(message, ": ", detail); }; return false; }; fn startswith(s: str, prefix: str) bool = { if (s.len < prefix.len) { return false; }; let i: i32 = 0; for (i < prefix.len) { if (s[i] != prefix[i]) { return false; }; i += 1; }; return true; }; fn safeatom(s: str) bool = { if (s.len == 0) { return false; }; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; if (!((c >= 'a': u8 && c <= 'z': u8) || (c >= '0': u8 && c <= '9': u8) || c == '_': u8)) { return false; }; i += 1; }; return true; }; fn join(a: str, b: str) str = { if (a.len == 0) { return strings.dup(b); }; if (a[a.len - 1] == '/': u8) { return strings.concat(a, b); }; return strings.concat(a, "/", b); }; fn currentdir(out: *str) bool = { let b: []u8 = alloc([], os.PATH_MAX: u64)!; b.len = os.PATH_MAX; let n: i64 = os.getcwd(b.ptr, b.len: u64); if (n <= 1i64 || n > b.len: i64) { return seterror("cannot resolve current directory", ""); }; b.len = (n - 1i64): i32; *out = strings.dup(strings.frombytes(b)); return true; }; fn absoluteroot(root: str, out: *str) bool = { if (root.len == 0) { return currentdir(out); }; if (root[0] == '/': u8) { *out = strings.dup(root); return true; }; let cwd: str; if (!currentdir(&cwd)) { return false; }; *out = join(cwd, root); return true; }; fn readfile(path: str, out: *str) bool = { let st: os.filestat; match (os.stat(&st, path)) { case void => void; case let e: os.oserror => return seterror("cannot stat file", path); }; if (st.sz > 2147483647u64) { return seterror("file is too large", path); }; let fd: i32 = os.open(path, os.flag.RDONLY, 0i32); if (fd < 0) { return seterror("cannot open file", path); }; if (st.sz == 0u64) { if (os.close(fd) != 0) { return seterror("cannot close file", path); }; let empty: str; empty.ptr = nil: *u8; empty.len = 0; *out = empty; return true; }; let b: []u8 = alloc([], st.sz)!; b.len = st.sz: i32; let got: i64 = match (os.readall(fd, b.ptr, st.sz)) { case let n: i64 => yield n; case let e: os.oserror => { os.close(fd); return seterror("cannot read file", path); }; }; if (os.close(fd) != 0) { return seterror("cannot close file", path); }; if (got != st.sz: i64) { return seterror("short read", path); }; *out = strings.dup(strings.frombytes(b)); return true; }; fn writeall(fd: i32, s: str) bool = { let off: i32 = 0; for (off < s.len) { let n: i64 = os.write(fd, s.ptr + (off: u64), (s.len - off): u64); if (n == -4i64) { continue; }; if (n <= 0i64) { return false; }; off += n: i32; }; return true; }; fn writefile(path: str, data: str) bool = { let fd: i32 = os.open(path, os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32); if (fd < 0) { return false; }; let ok: bool = writeall(fd, data); if (os.close(fd) != 0) { ok = false; }; return ok; }; fn writeharness(dir: str, message: str) bool = { let path: str = join(dir, "harness.txt"); return writefile(path, strings.concat(message, "\n")); }; fn direntname(buf: []u8, off: i32, reclen: i32, out: *str) bool = { if (reclen < 20 || off < 0 || off + reclen > buf.len) { return false; }; let first: i32 = off + 19; let last: i32 = first; let limit: i32 = off + reclen; for (last < limit && buf[last] != 0u8) { last += 1; }; if (last == limit) { return false; }; out.ptr = buf.ptr + (first: u64); out.len = last - first; return true; }; fn filekind(path: str, out: *u32) i32 = { let st: os.filestat; match (os.lstat(&st, path)) { case void => { *out = (st.mode: u32) & typemask; return 1; }; case let e: os.oserror => { if ((e: i64) == -2i64) { return 0; }; seterror("cannot inspect path", path); return -1; }; }; }; fn sortnames(names: []str) void = { let i: i32 = 1; for (i < names.len) { let v: str = names[i]; let j: i32 = i; for (j > 0 && strings.compare(names[j - 1], v) > 0) { names[j] = names[j - 1]; j -= 1; }; names[j] = v; i += 1; }; }; fn parsenumber(s: str, out: *i32) bool = { if (s.len == 0 || (s.len > 1 && s[0] == '0': u8)) { return false; }; let value: i32 = 0; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; if (c < '0': u8 || c > '9': u8) { return false; }; value = value * 10 + ((c - '0': u8): i32); if (value > 255) { return false; }; i += 1; }; *out = value; return true; }; fn parsedirective(path: str, data: str, out: *fixture) bool = { if (data.len == 0) { return seterror("empty fixture", path); }; let end: i32 = 0; for (end < data.len && data[end] != 10u8) { end += 1; }; if (end == data.len) { return seterror("fixture directive line is not LF terminated", path); }; let line: str; line.ptr = data.ptr; line.len = end; out.diagnostic = ""; out.wwdiagnostic = ""; out.exitcode = 0; if (line == "//ww:run") { out.directive = directive.RUN; return true; }; let runexit: str = "//ww:run-exit "; if (startswith(line, runexit)) { let tail: str; tail.ptr = line.ptr + (runexit.len: u64); tail.len = line.len - runexit.len; if (!parsenumber(tail, &out.exitcode)) { return seterror("malformed run-exit directive", path); }; out.directive = directive.RUNEXIT; return true; }; let stagedprefix: str = "//ww:error c \""; if (startswith(line, stagedprefix)) { let cend: i32 = stagedprefix.len; for (cend < line.len && line[cend] != 34u8) { cend += 1; }; if (cend == stagedprefix.len || cend + 6 >= line.len || line[cend] != 34u8 || line[cend + 1] != ' ': u8 || line[cend + 2] != 'w': u8 || line[cend + 3] != 'w': u8 || line[cend + 4] != ' ': u8 || line[cend + 5] != 34u8 || line[line.len - 1] != 34u8 || cend + 6 >= line.len - 1) { return seterror("malformed stage-specific error directive", path); }; let cbody: str; cbody.ptr = line.ptr + (stagedprefix.len: u64); cbody.len = cend - stagedprefix.len; let wwbody: str; wwbody.ptr = line.ptr + ((cend + 6): u64); wwbody.len = line.len - cend - 7; let i: i32 = 0; for (i < cbody.len) { if (cbody[i] == 34u8 || cbody[i] == 9u8 || cbody[i] == 13u8) { return seterror("malformed stage-specific error directive", path); }; i += 1; }; i = 0; for (i < wwbody.len) { if (wwbody[i] == 34u8 || wwbody[i] == 9u8 || wwbody[i] == 13u8) { return seterror("malformed stage-specific error directive", path); }; i += 1; }; out.directive = directive.ERROR; out.diagnostic = strings.dup(cbody); out.wwdiagnostic = strings.dup(wwbody); return true; }; let errprefix: str = "//ww:error \""; if (startswith(line, errprefix) && line.len > errprefix.len + 1 && line[line.len - 1] == 34u8) { let body: str; body.ptr = line.ptr + (errprefix.len: u64); body.len = line.len - errprefix.len - 1; let i: i32 = 0; for (i < body.len) { if (body[i] == 34u8 || body[i] == 9u8 || body[i] == 13u8) { return seterror("malformed error directive", path); }; i += 1; }; out.directive = directive.ERROR; out.diagnostic = strings.dup(body); return true; }; if (line == "//ww:compile") { out.directive = directive.COMPILE; return true; }; return seterror("malformed fixture directive", path); }; fn discovernames(root: str, out: *[]str) bool = { let datadir: str = join(root, "test/wcc/data"); let fd: i32 = os.open(datadir, os.flag.RDONLY, 0i32); if (fd < 0) { return seterror("cannot open fixture root", datadir); }; let names: []str = alloc([], 128u64)!; let buf: []u8 = alloc([], dirbufsize: u64)!; buf.len = dirbufsize; for (true) { let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64); if (n < 0i64) { os.close(fd); return seterror("cannot enumerate fixture root", datadir); }; if (n == 0i64) { break; }; let off: i32 = 0; for (off < n: i32) { if (off + 18 >= n: i32) { os.close(fd); return seterror("malformed directory record", datadir); }; let reclen: i32 = (buf[off + 16]: i32) + ((buf[off + 17]: i32) * 256); if (reclen <= 0 || off + reclen > n: i32) { os.close(fd); return seterror("malformed directory record", datadir); }; let name: str; if (!direntname(buf, off, reclen, &name)) { os.close(fd); return seterror("malformed directory record", datadir); }; if (name != "." && name != "..") { let dir: str = join(datadir, name); let kind: u32 = 0u32; let status: i32 = filekind(dir, &kind); if (status < 0) { os.close(fd); return false; }; if (status == 1 && kind == (os.mode.LINK: u32)) { os.close(fd); return seterror("symlink fixture directory is forbidden", dir); }; if (status == 1 && kind == (os.mode.DIR: u32)) { let source: str = join(dir, "case.ww"); let sourcekind: u32 = 0u32; let sourcestatus: i32 = filekind(source, &sourcekind); if (sourcestatus < 0) { os.close(fd); return false; }; if (sourcestatus == 1 && sourcekind == (os.mode.LINK: u32)) { os.close(fd); return seterror("symlink fixture source is forbidden", source); }; if (sourcestatus == 1 && sourcekind == (os.mode.REG: u32)) { if (!safeatom(name)) { os.close(fd); return seterror("invalid fixture directory name", name); }; append(names, strings.dup(name)); }; }; }; off += reclen; }; }; if (os.close(fd) != 0) { return seterror("cannot close fixture root", datadir); }; sortnames(names); *out = names; return true; }; fn identityhash(names: []str) str = { let state: sha256.state = sha256.sha256(); let h: *hash.hash = (&state): *hash.hash; let nl: [1]u8 = [10u8]; let i: i32 = 0; for (i < names.len) { hash.write(h, strings.toutf8(names[i])); hash.write(h, nl[0:1]); i += 1; }; let digest: [32]u8; hash.sum(h, digest[0:32]); return strings.dup(hex.encodestr(digest[0:32])); }; fn loadcorpus(rootarg: str, out: *corpus) bool = { fixtureerror = ""; let root: str; if (!absoluteroot(rootarg, &root)) { return false; }; let names: []str; if (!discovernames(root, &names)) { return false; }; let digest: str = identityhash(names); if (names.len != corpuscount || digest != corpushash) { return seterror("compiler corpus identity drift", strings.concat("expected ", rundecimal(corpuscount), "/", corpushash, ", observed ", rundecimal(names.len), "/", digest)); }; let fixtures: []fixture = alloc([], names.len: u64)!; let errors: i32 = 0; let compiles: i32 = 0; let runs: i32 = 0; let runexits: i32 = 0; let i: i32 = 0; for (i < names.len) { let f: fixture; f.name = names[i]; f.id = strings.concat("compiler.", f.name); f.source = join(join(join(root, "test/wcc/data"), f.name), "case.ww"); let data: str; if (!readfile(f.source, &data)) { return false; }; if (!parsedirective(f.source, data, &f)) { return false; }; if (f.directive == directive.ERROR) { errors += 1; } else { if (f.directive == directive.COMPILE) { compiles += 1; } else { if (f.directive == directive.RUN) { runs += 1; } else { if (f.directive == directive.RUNEXIT) { runexits += 1; }; }; }; }; append(fixtures, f); i += 1; }; let derivednative: i32 = fixtures.len * 2; if (errors != errorcount || compiles != compilecount || runs != runcount || runexits != runexitcount || derivednative != nativecount) { return seterror("compiler stage matrix drift", strings.concat("expected ", rundecimal(errorcount), " errors, ", rundecimal(compilecount), " compiles, ", rundecimal(runcount), " runs, and ", rundecimal(runexitcount), " run-exits")); }; out.root = root; out.fixtures = fixtures; return true; }; fn cellname(f: *fixture, s: stage) str = { return strings.concat(f.id, "/", stageword(s)); }; fn selectedby(patterns: []str, f: *fixture, s: stage) bool = { if (patterns.len == 0) { return true; }; let full: str = cellname(f, s); let i: i32 = 0; for (i < patterns.len) { if (fnmatch.fnmatch(patterns[i], f.id, fnmatch.flag.NONE) || fnmatch.fnmatch(patterns[i], full, fnmatch.flag.NONE)) { return true; }; i += 1; }; return false; }; fn selectidentities(c: *corpus, patterns: []str, out: *[]identity) bool = { let ids: []identity = alloc([], nativecount: u64)!; let ordinal: i32 = 0; let i: i32 = 0; for (i < c.fixtures.len) { let si: i32 = 0; for (si < 2) { let s: stage = stage.C; if (si == 1) { s = stage.WW; }; if (selectedby(patterns, &c.fixtures[i], s)) { ordinal += 1; let id: identity; id.ordinal = ordinal; id.fixtureindex = i; id.stage = s; append(ids, id); }; si += 1; }; i += 1; }; *out = ids; return true; }; fn isdot(name: str) bool = { return name == "." || name == ".."; }; // Callers restrict this to directories minted beneath one temp.dir result; // lstat plus the opened-directory inode check keeps cleanup from following a // replaced path outside that ownership boundary. fn removeall(path: str) bool = { let st: os.filestat; match (os.lstat(&st, path)) { case void => void; case let e: os.oserror => return false; }; let kind: u32 = (st.mode: u32) & typemask; if (kind != (os.mode.DIR: u32)) { return os.remove(path) == 0; }; let fd: i32 = os.open(path, os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; let opened: os.filestat; match (os.fstat(&opened, fd)) { case void => void; case let e: os.oserror => { os.close(fd); return false; }; }; if (((opened.mode: u32) & typemask) != (os.mode.DIR: u32) || opened.inode != st.inode) { os.close(fd); return false; }; let ok: bool = true; let buf: []u8 = alloc([], dirbufsize: u64)!; buf.len = dirbufsize; let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64); for (n > 0i64) { let off: i32 = 0; for (off < n: i32) { let reclen: i32 = (buf[off + 16]: i32) + ((buf[off + 17]: i32) * 256); if (reclen < 20 || off + reclen > n: i32) { ok = false; break; }; let name: str; if (!direntname(buf, off, reclen, &name)) { ok = false; break; }; if (!isdot(name) && !removeall(join(path, name))) { ok = false; }; off += reclen; }; n = os.getdents64(fd, buf.ptr, buf.len: u64); }; if (n < 0i64 || os.close(fd) != 0) { ok = false; }; if (os.rmdir(path) != 0) { ok = false; }; return ok; };