Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bend2/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,12 @@ async function cli_file(args: string[]): Promise<void> {
t.$ === "Def" && t.i !== undefined ? t.i.map(path_real) : [])]);
for (const out of outs) {
const at = path_real(out);
if (ins.has(at) || (fs.existsSync(at) && fs.statSync(at).isDirectory())) {
if (ins.has(at) || (fs.existsSync(at) && (fs.statSync(at).isDirectory()
|| [...ins].some((input) => same_file(at, input))))) {
cli_fail("-o " + out + " is a file the program reads, or a directory");
}
}
for (const out of outs) {
cli_emit(book, out);
}
} catch (e) {
Expand Down
12 changes: 12 additions & 0 deletions tests/io/output_input_links.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Output files must not overwrite inputs through a path, symlink or hard link.
import Base

def Output.input_links() -> IO(U32):
import "./output_input_links.js"

def main() -> IO(Unit):
do IO<Unit>:
result : U32 <- Output.input_links()
IO.print(U32.show(result))

#|0
63 changes: 63 additions & 0 deletions tests/io/output_input_links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
function output_input_links() {
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const child = require("node:child_process");
// Outside the repo, so a run the alarm kills leaves no files in the tree.
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "bend-output-input-"));
const inputs = new Map([
["main.bend", 'import Base\nimport ./lib.bend as Lib\n'
+ 'def Probe.value() -> IO(U32):\n'
+ ' import "./effect.js"\n import "./effect.c"\n'
+ 'def main() -> U32:\n Lib.value()\n'],
["lib.bend", "import Base\ndef value() -> U32:\n 42\n"],
["effect.js", "io_eff(CID(Probe.value), () => 42);\n"],
["effect.c", "// Foreign input.\n"],
].map(([name, text]) => [path.join(dir, name), text]));
const main = path.join(dir, "main.bend");
const options = { encoding: "utf8", timeout: 4000,
env: { ...process.env, HOME: dir, BEND_ORIGIN: "http://127.0.0.1:9",
BEND_NO_TELEMETRY: "1" },
};
const build = (...outs) => child.spawnSync(process.execPath,
[path.resolve("bend2/main.ts"), main, ...outs.flatMap((out) => ["-o", out])],
options);
const intact = () => [...inputs].every(([file, text]) =>
fs.readFileSync(file, "utf8") === text);
const rejects = (got, msg) => got.status === 1 && got.stderr.includes(msg)
&& intact();
try {
for (const [file, text] of inputs) fs.writeFileSync(file, text);
const link = path.join(dir, "out.js");
const ok = path.join(dir, "ok.js");
let code = 1;
if (!rejects(build(main), "is a file the program reads")) return code;
code++;
fs.symlinkSync(main, link);
if (!rejects(build(link), "is a file the program reads")) return code;
fs.unlinkSync(link);
code++;
for (const file of inputs.keys()) {
fs.linkSync(file, link);
if (!rejects(build(link), "is a file the program reads")) return code;
fs.unlinkSync(link);
code++;
}
if (!rejects(build(dir), "or a directory")) return code;
code++;
// A rejected output fails the build before any output is written.
fs.linkSync(main, link);
if (!rejects(build(ok, link), "is a file the program reads")
|| fs.existsSync(ok)) return code;
fs.unlinkSync(link);
code++;
if (build(ok).status !== 0 || !intact()) return code;
const run = child.spawnSync(process.execPath, [ok], options);
if (run.status !== 0 || run.stdout.trim() !== "42") return code;
return 0;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

io_eff(CID(Output.input_links), output_input_links);