]> git.kaiwu.me - njs.git/commitdiff
Buffer: convert QJS array-like lengths
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 22 Sep 2026 03:28:29 +0000 (20:28 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 25 Sep 2026 23:00:29 +0000 (16:00 -0700)
Convert array-like lengths through checked floating-point conversion before
applying ToLength semantics.  This avoids reading float-tagged QuickJS
numbers through the integer accessor.

The length is kept as a 64-bit value and passed to qjs_buffer_alloc()
unchanged, so an oversized length is rejected before any allocation
instead of being truncated.

src/qjs.h
src/qjs_buffer.c
test/buffer.t.js

index 865db0d2e9a51716794ef787cfb87ed2da20bfe2..41df904675a7ae9df354f396e2cc3c1333c2965a 100644 (file)
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -114,6 +114,29 @@ typedef struct {
 } qjs_bytes_t;
 
 
+njs_inline int
+qjs_to_length(JSContext *ctx, JSValueConst value, uint64_t *length)
+{
+    double  number;
+
+    if (JS_ToFloat64(ctx, &number, value)) {
+        return -1;
+    }
+
+    if (number != number || number <= 0) {
+        *length = 0;
+
+    } else if (number > 0x1fffffffffffffLL) {
+        *length = 0x1fffffffffffffLL;
+
+    } else {
+        *length = number;
+    }
+
+    return 0;
+}
+
+
 njs_inline int
 qjs_is_typed_array(JSContext *cx, JSValue val)
 {
index ade8559e37136e6ac7b11399f1bdd87617ebda53..9359a80e2fddc75a4e4f721feecb28a7dacda70e 100644 (file)
@@ -2018,7 +2018,7 @@ qjs_buffer_from_object(JSContext *ctx, JSValueConst obj)
 {
     int         v;
     u_char      *p;
-    int64_t     i, len;
+    uint64_t    i, len;
     JSValue     buffer, ret;
     njs_str_t   dst;
     const char  *str;
@@ -2068,7 +2068,12 @@ reject:
         return JS_EXCEPTION;
     }
 
-    len = JS_VALUE_GET_INT(ret);
+    if (qjs_to_length(ctx, ret, &len)) {
+        JS_FreeValue(ctx, ret);
+        return JS_EXCEPTION;
+    }
+
+    JS_FreeValue(ctx, ret);
 
     buffer = qjs_buffer_alloc(ctx, len);
     if (JS_IsException(buffer)) {
index 8111ffa4064ac3b05c2d1aa93dee228b656187e9..1d749493b26b1f602b2fc6efc82dafb44faf955a 100644 (file)
@@ -1,5 +1,5 @@
 /*---
-includes: [compatBuffer.js, runTsuite.js, compareArray.js]
+includes: [compatBuffer.js, compatNjs.js, runTsuite.js, compareArray.js]
 flags: [async]
 ---*/
 
@@ -35,6 +35,42 @@ let alloc_tsuite = {
 };
 
 
+let fromObject_tsuite = {
+    name: "Buffer.from() array-like length tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let buffer = Buffer.from(params.value);
+
+        if (buffer.toString() !== params.expected) {
+            throw Error(`unexpected output "${buffer.toString()}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    tests: [
+        { value: { length: 1.5, 0: 0x41 }, expected: 'A' },
+        { value: { length: -1, 0: 0x41 }, expected: '' },
+        { value: { length: NaN, 0: 0x41 }, expected: '' },
+    ],
+};
+
+
+let fromObjectLarge_tsuite = {
+    name: "Buffer.from() oversized array-like length tests",
+    skip: () => (!has_buffer() || !has_njs()),
+    T: async () => {
+        assert.throws(RangeError, () => Buffer.from({
+            length: 0x100000010,
+            get 0() { throw Error('unexpected index access'); },
+        }));
+
+        return 'SUCCESS';
+    },
+    tests: [{}],
+};
+
+
 let byteLength_tsuite = {
     name: "Buffer.byteLength() tests",
     skip: () => (!has_buffer()),
@@ -1122,6 +1158,8 @@ let writeGeneric_tsuite = {
 
 run([
     alloc_tsuite,
+    fromObject_tsuite,
+    fromObjectLarge_tsuite,
     byteLength_tsuite,
     concat_tsuite,
     concatRevalidate_tsuite,