diff --git a/src/shell/builtin/seq.zig b/src/shell/builtin/seq.zig index f5b0061fa7..ab29d821ee 100644 --- a/src/shell/builtin/seq.zig +++ b/src/shell/builtin/seq.zig @@ -46,12 +46,14 @@ pub fn start(this: *@This()) Yield { const maybe1 = iter.next().?; const int1 = std.fmt.parseFloat(f32, bun.sliceTo(maybe1, 0)) catch return this.fail("seq: invalid argument\n"); + if (!std.math.isFinite(int1)) return this.fail("seq: invalid argument\n"); this._end = int1; if (this._start > this._end) this.increment = -1; const maybe2 = iter.next(); if (maybe2 == null) return this.do(); const int2 = std.fmt.parseFloat(f32, bun.sliceTo(maybe2.?, 0)) catch return this.fail("seq: invalid argument\n"); + if (!std.math.isFinite(int2)) return this.fail("seq: invalid argument\n"); this._start = int1; this._end = int2; if (this._start < this._end) this.increment = 1; @@ -60,6 +62,7 @@ pub fn start(this: *@This()) Yield { const maybe3 = iter.next(); if (maybe3 == null) return this.do(); const int3 = std.fmt.parseFloat(f32, bun.sliceTo(maybe3.?, 0)) catch return this.fail("seq: invalid argument\n"); + if (!std.math.isFinite(int3)) return this.fail("seq: invalid argument\n"); this._start = int1; this.increment = int2; this._end = int3; diff --git a/src/shell/states/CondExpr.zig b/src/shell/states/CondExpr.zig index 75c1dd2f37..9bec738fdd 100644 --- a/src/shell/states/CondExpr.zig +++ b/src/shell/states/CondExpr.zig @@ -168,6 +168,8 @@ fn commandImplStart(this: *CondExpr) Yield { .@"-d", .@"-f", => { + // Empty string expansion produces no args; the path doesn't exist. + if (this.args.items.len == 0) return this.parent.childDone(this, 1); this.state = .waiting_stat; return this.doStat(); }, diff --git a/test/js/bun/shell/shell-seq-condexpr.test.ts b/test/js/bun/shell/shell-seq-condexpr.test.ts new file mode 100644 index 0000000000..adb1756d92 --- /dev/null +++ b/test/js/bun/shell/shell-seq-condexpr.test.ts @@ -0,0 +1,85 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("seq inf does not hang", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `import { $ } from "bun"; $.throws(false); const r = await $\`seq inf\`; process.exit(r.exitCode)`, + ], + env: bunEnv, + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("invalid argument"); + expect(exitCode).toBe(1); +}, 10_000); + +test("seq nan does not hang", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `import { $ } from "bun"; $.throws(false); const r = await $\`seq nan\`; process.exit(r.exitCode)`, + ], + env: bunEnv, + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("invalid argument"); + expect(exitCode).toBe(1); +}, 10_000); + +test("seq -inf does not hang", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `import { $ } from "bun"; $.throws(false); const r = await $\`seq -- -inf\`; process.exit(r.exitCode)`, + ], + env: bunEnv, + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("invalid argument"); + expect(exitCode).toBe(1); +}, 10_000); + +test('[[ -d "" ]] does not crash', async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `import { $ } from "bun"; $.throws(false); const r = await $\`[[ -d "" ]]\`; process.exit(r.exitCode)`, + ], + env: bunEnv, + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(exitCode).toBe(1); +}, 10_000); + +test('[[ -f "" ]] does not crash', async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `import { $ } from "bun"; $.throws(false); const r = await $\`[[ -f "" ]]\`; process.exit(r.exitCode)`, + ], + env: bunEnv, + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(exitCode).toBe(1); +}, 10_000);