summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenta Moriuchi <moriken@kimamass.com>2024-06-03 10:29:01 +0900
committerGitHub <noreply@github.com>2024-06-02 21:29:01 -0400
commiteda43c46de12ed589fdbe62ba0574887cfbb3574 (patch)
tree9703b6c92b2e3ca21768d9ae6d82730c41e41157
parent754f21f0cd6b788ded48238c5acc3f635329d473 (diff)
fix: validate integer values in `Deno.exitCode` setter (#24068)
-rw-r--r--cli/tests/unit/os_test.ts94
-rw-r--r--ext/node/polyfills/process.ts41
-rw-r--r--runtime/js/30_os.js15
-rw-r--r--tests/unit/os_test.ts67
-rw-r--r--tests/unit_node/process_test.ts8
5 files changed, 90 insertions, 135 deletions
diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts
deleted file mode 100644
index af6ef219a..000000000
--- a/cli/tests/unit/os_test.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-import { assertEquals, assertThrows } from "../../testing/asserts.ts";
-
-Deno.test("Deno.exitCode getter and setter", () => {
- // Initial value is 0
- assertEquals(Deno.exitCode, 0);
-
- // Set a new value
- Deno.exitCode = 5;
- assertEquals(Deno.exitCode, 5);
-
- // Reset to initial value
- Deno.exitCode = 0;
- assertEquals(Deno.exitCode, 0);
-});
-
-Deno.test("Setting Deno.exitCode to NaN throws TypeError", () => {
- // @ts-expect-error;
- Deno.exitCode = "123";
- assertEquals(Deno.exitCode, 123);
-
- // Reset
- Deno.exitCode = 0;
- assertEquals(Deno.exitCode, 0);
-
- // Throws on non-number values
- assertThrows(
- () => {
- // @ts-expect-error Testing for runtime error
- Deno.exitCode = "not a number";
- },
- TypeError,
- "Exit code must be a number.",
- );
-});
-
-Deno.test("Setting Deno.exitCode does not cause an immediate exit", () => {
- let exited = false;
- const originalExit = Deno.exit;
-
- // @ts-expect-error; read-only
- Deno.exit = () => {
- exited = true;
- };
-
- Deno.exitCode = 1;
- assertEquals(exited, false);
-
- // @ts-expect-error; read-only
- Deno.exit = originalExit;
-});
-
-Deno.test("Running Deno.exit(value) overrides Deno.exitCode", () => {
- let args: unknown[] | undefined;
-
- const originalExit = Deno.exit;
- // @ts-expect-error; read-only
- Deno.exit = (...x) => {
- args = x;
- };
-
- Deno.exitCode = 42;
- Deno.exit(0);
-
- assertEquals(args, [0]);
- // @ts-expect-error; read-only
- Deno.exit = originalExit;
-});
-
-Deno.test("Running Deno.exit() uses Deno.exitCode as fallback", () => {
- let args: unknown[] | undefined;
-
- const originalExit = Deno.exit;
- // @ts-expect-error; read-only
- Deno.exit = (...x) => {
- args = x;
- };
-
- Deno.exitCode = 42;
- Deno.exit();
-
- assertEquals(args, [42]);
- // @ts-expect-error; read-only
- Deno.exit = originalExit;
-});
-
-Deno.test("Retrieving the set exit code before process termination", () => {
- Deno.exitCode = 42;
- assertEquals(Deno.exitCode, 42);
-
- // Reset to initial value
- Deno.exitCode = 0;
-});
diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts
index 9a28137af..a001d2e0f 100644
--- a/ext/node/polyfills/process.ts
+++ b/ext/node/polyfills/process.ts
@@ -19,6 +19,7 @@ import { report } from "ext:deno_node/internal/process/report.ts";
import { validateString } from "ext:deno_node/internal/validators.mjs";
import {
ERR_INVALID_ARG_TYPE,
+ ERR_OUT_OF_RANGE,
ERR_UNKNOWN_SIGNAL,
errnoException,
} from "ext:deno_node/internal/errors.ts";
@@ -439,38 +440,22 @@ Object.defineProperty(Process.prototype, "exitCode", {
return ProcessExitCode;
},
set(code: number | string | null | undefined) {
- let parsedCode;
-
- if (typeof code === "number") {
- if (Number.isNaN(code)) {
- parsedCode = 1;
- denoOs.setExitCode(parsedCode);
- ProcessExitCode = parsedCode;
- return;
- }
-
- // This is looser than `denoOs.setExitCode` which requires exit code
- // to be decimal or string of a decimal, but Node accept eg. 0x10.
- parsedCode = parseInt(code);
- denoOs.setExitCode(parsedCode);
- ProcessExitCode = parsedCode;
- return;
+ let parsedCode: number;
+ if (code == null) {
+ parsedCode = 0;
+ } else if (typeof code === "number") {
+ parsedCode = code;
+ } else if (typeof code === "string") {
+ parsedCode = Number(code);
+ } else {
+ throw new ERR_INVALID_ARG_TYPE("code", "number", code);
}
- if (typeof code === "string") {
- parsedCode = parseInt(code);
- if (Number.isNaN(parsedCode)) {
- throw new TypeError(
- `The "code" argument must be of type number. Received type ${typeof code} (${code})`,
- );
- }
- denoOs.setExitCode(parsedCode);
- ProcessExitCode = parsedCode;
- return;
+ if (!Number.isInteger(parsedCode)) {
+ throw new ERR_OUT_OF_RANGE("code", "an integer", parsedCode);
}
- // TODO(bartlomieju): hope for the best here. This should be further tightened.
- denoOs.setExitCode(code);
+ denoOs.setExitCode(parsedCode);
ProcessExitCode = code;
},
});
diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js
index 866fad287..f3dfda886 100644
--- a/runtime/js/30_os.js
+++ b/runtime/js/30_os.js
@@ -22,7 +22,8 @@ import {
const {
Error,
FunctionPrototypeBind,
- NumberParseInt,
+ NumberIsInteger,
+ RangeError,
SymbolFor,
TypeError,
} = primordials;
@@ -102,13 +103,17 @@ function getExitCode() {
}
function setExitCode(value) {
- const code = NumberParseInt(value, 10);
- if (typeof code !== "number") {
+ if (typeof value !== "number") {
throw new TypeError(
- `Exit code must be a number, got: ${code} (${typeof code}).`,
+ `Exit code must be a number, got: ${value} (${typeof value})`,
);
}
- op_set_exit_code(code);
+ if (!NumberIsInteger(value)) {
+ throw new RangeError(
+ `Exit code must be an integer, got: ${value}`,
+ );
+ }
+ op_set_exit_code(value);
}
function setEnv(key, value) {
diff --git a/tests/unit/os_test.ts b/tests/unit/os_test.ts
index e24494854..30d8f26ee 100644
--- a/tests/unit/os_test.ts
+++ b/tests/unit/os_test.ts
@@ -302,3 +302,70 @@ Deno.test(function memoryUsage() {
assert(typeof mem.external === "number");
assert(mem.rss >= mem.heapTotal);
});
+
+Deno.test("Deno.exitCode getter and setter", () => {
+ // Initial value is 0
+ assertEquals(Deno.exitCode, 0);
+
+ try {
+ // Set a new value
+ Deno.exitCode = 5;
+ assertEquals(Deno.exitCode, 5);
+ } finally {
+ // Reset to initial value
+ Deno.exitCode = 0;
+ }
+
+ assertEquals(Deno.exitCode, 0);
+});
+
+Deno.test("Setting Deno.exitCode to non-number throws TypeError", () => {
+ // Throws on non-number values
+ assertThrows(
+ () => {
+ // @ts-expect-error Testing for runtime error
+ Deno.exitCode = "123";
+ },
+ TypeError,
+ "Exit code must be a number, got: 123 (string)",
+ );
+
+ // Throws on bigint values
+ assertThrows(
+ () => {
+ // @ts-expect-error Testing for runtime error
+ Deno.exitCode = 1n;
+ },
+ TypeError,
+ "Exit code must be a number, got: 1 (bigint)",
+ );
+});
+
+Deno.test("Setting Deno.exitCode to non-integer throws RangeError", () => {
+ // Throws on non-integer values
+ assertThrows(
+ () => {
+ Deno.exitCode = 3.14;
+ },
+ RangeError,
+ "Exit code must be an integer, got: 3.14",
+ );
+});
+
+Deno.test("Setting Deno.exitCode does not cause an immediate exit", () => {
+ let exited = false;
+
+ const originalExit = Deno.exit;
+ // @ts-expect-error; read-only
+ Deno.exit = () => {
+ exited = true;
+ };
+
+ try {
+ Deno.exitCode = 1;
+ assertEquals(exited, false);
+ } finally {
+ Deno.exit = originalExit;
+ Deno.exitCode = 0;
+ }
+});
diff --git a/tests/unit_node/process_test.ts b/tests/unit_node/process_test.ts
index 5e2fb69c2..24fd3909d 100644
--- a/tests/unit_node/process_test.ts
+++ b/tests/unit_node/process_test.ts
@@ -814,10 +814,6 @@ Deno.test("process.exitCode in should change exit code", async () => {
127,
);
await exitCodeTest(
- "import process from 'node:process'; process.exitCode = 2.5;",
- 2,
- );
- await exitCodeTest(
"import process from 'node:process'; process.exitCode = '10';",
10,
);
@@ -825,10 +821,6 @@ Deno.test("process.exitCode in should change exit code", async () => {
"import process from 'node:process'; process.exitCode = '0x10';",
16,
);
- await exitCodeTest(
- "import process from 'node:process'; process.exitCode = NaN;",
- 1,
- );
});
Deno.test("Deno.exit should override process exit", async () => {