Repository: denoland/deno
Pull request: fix(ext/node): handle large buffer correctly

Previously, Deno throws error when creating `Buffer.allocUnsafe` or `Buffer.allocUnsafeSlow` with size of `2**31` where that's not the case with Node.js. 

The changes allow these tests to pass:
- [test-buffer-large-size-buffer-alloc-unsafe-slow.js](https://github.com/nodejs/node/blob/v24.2.0/test/pummel/test-buffer-large-size-buffer-alloc-unsafe-slow.js)
- [test-buffer-large-size-buffer-alloc-unsafe.js](https://github.com/nodejs/node/blob/v24.2.0/test/pummel/test-buffer-large-size-buffer-alloc-unsafe.js)
- [test-string-decoder-large-buffer.js](https://github.com/nodejs/node/blob/v24.2.0/test/pummel/test-string-decoder-large-buffer.js)

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/ops/buffer.rs
patch: @@ -156,7 +156,10 @@ pub fn op_node_decode_utf8<'a>(
 
 #[derive(Debug, thiserror::Error, deno_error::JsError)]
 enum BufferError {
-  #[error("String too long")]
+  #[error(
+    "Cannot create a string longer than 0x{:x} characters",
+    v8::String::MAX_LENGTH
+  )]
   #[class(generic)]
   #[property("code" = "ERR_STRING_TOO_LONG")]
   StringTooLong,

filename: ext/node/polyfills/internal/buffer.mjs
patch: @@ -175,8 +175,8 @@ function showFlaggedDeprecation() {
 }
 
 class FastBuffer extends Uint8Array {
-  constructor(arg0, arg1, arg2) {
-    super(arg0, arg1, arg2);
+  constructor(bufferOrLength, byteOffset, length) {
+    super(bufferOrLength, byteOffset, length);
   }
 }
 
@@ -380,6 +380,10 @@ const of = (...items) => {
 };
 Buffer.of = of;
 
+/**
+ * @param {unknown} size
+ * @returns {asserts size is number}
+ */
 function assertSize(size) {
   validateNumber(size, "size", 0, kMaxLength);
 }
@@ -411,12 +415,34 @@ function _allocUnsafe(size) {
   return createBuffer(size < 0 ? 0 : checked(size) | 0);
 }
 
+/**
+ * @param {number} size
+ * @returns {FastBuffer}
+ */
+function allocate(size) {
+  if (size <= 0) {
+    return new FastBuffer();
+  }
+  if (size < (Buffer.poolSize >>> 1)) {
+    if (size > (poolSize - poolOffset)) {
+      createPool();
+    }
+    const b = new FastBuffer(allocPool, poolOffset, size);
+    poolOffset += size;
+    alignPool();
+    return b;
+  }
+  return new FastBuffer(size);
+}
+
 Buffer.allocUnsafe = function allocUnsafe(size) {
-  return _allocUnsafe(size);
+  assertSize(size);
+  return allocate(size);
 };
 
 Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) {
-  return _allocUnsafe(size);
+  assertSize(size);
+  return new FastBuffer(size);
 };
 
 function fromString(string, encoding) {
@@ -2669,6 +2695,13 @@ export function boundsError(value, length, type) {
   );
 }
 
+/**
+ * @param {number} value
+ * @param {string} name
+ * @param {number} min
+ * @param {number} max
+ * @returns {asserts value is number}
+ */
 export function validateNumber(value, name, min = undefined, max) {
   if (typeof value !== "number") {
     throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value);

filename: ext/node/polyfills/string_decoder.ts
patch: @@ -41,6 +41,7 @@ const {
   DataViewPrototypeGetBuffer,
   DataViewPrototypeGetByteLength,
   DataViewPrototypeGetByteOffset,
+  NumberPrototypeToString,
   ObjectPrototypeIsPrototypeOf,
   String,
   TypedArrayPrototypeGetBuffer,
@@ -106,6 +107,7 @@ function normalizeBuffer(buf: Buffer) {
   }
 }
 
+const maxStringLengthHex = NumberPrototypeToString(MAX_STRING_LENGTH, 16);
 function bufferToString(
   buf: Buffer,
   encoding?: string,
@@ -114,7 +116,10 @@ function bufferToString(
 ): string {
   const len = (end ?? buf.length) - (start ?? 0);
   if (len > MAX_STRING_LENGTH) {
-    throw new NodeError("ERR_STRING_TOO_LONG", "string exceeds maximum length");
+    throw new NodeError(
+      "ERR_STRING_TOO_LONG",
+      `Cannot create a string longer than 0x${maxStringLengthHex} characters`,
+    );
   }
   // deno-lint-ignore prefer-primordials
   return buf.toString(encoding as Any, start, end);

filename: tests/node_compat/config.toml
patch: @@ -1345,9 +1345,12 @@
 "pseudo-tty/test-tty-stdin-call-end.js" = {}
 "pseudo-tty/test-tty-stdin-end.js" = {}
 "pseudo-tty/test-tty-stdout-end.js" = {}
+"pummel/test-buffer-large-size-buffer-alloc-unsafe-slow.js" = {}
+"pummel/test-buffer-large-size-buffer-alloc-unsafe.js" = {}
 "pummel/test-dh-regr.js" = {}
 "pummel/test-fs-watch-system-limit.js" = {}
 "pummel/test-heapdump-inspector.js" = {}
+"pummel/test-string-decoder-large-buffer.js" = {}
 "sequential/test-buffer-creation-regression.js" = {}
 "sequential/test-child-process-exit.js" = {}
 "sequential/test-cli-syntax-bad.js" = {}

filename: tests/unit_node/buffer_test.ts
patch: @@ -1,8 +1,10 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
-import { Buffer } from "node:buffer";
+import { Buffer, constants } from "node:buffer";
 import { assertEquals, assertThrows } from "@std/assert";
 import { strictEqual } from "node:assert";
 
+const { MAX_STRING_LENGTH } = constants;
+
 Deno.test({
   name: "[node/buffer] alloc fails if size is not a number",
   fn() {
@@ -671,3 +673,18 @@ Deno.test({
     );
   },
 });
+
+Deno.test({
+  name: "[node/buffer] throws ERR_STRING_TOO_LONG with the correct message",
+  fn() {
+    assertThrows(
+      () => {
+        Buffer.allocUnsafe(2 ** 31).toString();
+      },
+      Error,
+      `Cannot create a string longer than 0x${
+        MAX_STRING_LENGTH.toString(16)
+      } characters`,
+    );
+  },
+});