Skip to content

Commit 0bbda5e

Browse files
zbjornsonTrott
authored andcommitted
fs: allow int64 offset in fs.read/readSync/fd.read
Since v10.10.0, 'buf' can be any DataView, meaning the largest byteLength can be Float64Array.BYTES_PER_ELEMENT * kMaxLength = 17,179,869,176. 'offset' can now be up to 2**53 - 1. This makes it possible to tile reads into a large buffer. Breaking: now throws if read offset is not a safe int, is null or is undefined. Fixes #26563 PR-URL: #26572 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 91a4cb7 commit 0bbda5e

File tree

6 files changed

+69
-8
lines changed

6 files changed

+69
-8
lines changed

lib/fs.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,12 @@ function read(fd, buffer, offset, length, position, callback) {
453453
validateBuffer(buffer);
454454
callback = maybeCallback(callback);
455455

456-
offset |= 0;
456+
if (offset == null) {
457+
offset = 0;
458+
} else {
459+
validateSafeInteger(offset, 'offset');
460+
}
461+
457462
length |= 0;
458463

459464
if (length === 0) {
@@ -490,7 +495,12 @@ function readSync(fd, buffer, offset, length, position) {
490495
validateInt32(fd, 'fd', 0);
491496
validateBuffer(buffer);
492497

493-
offset |= 0;
498+
if (offset == null) {
499+
offset = 0;
500+
} else {
501+
validateSafeInteger(offset, 'offset');
502+
}
503+
494504
length |= 0;
495505

496506
if (length === 0) {

lib/internal/fs/promises.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,12 @@ async function read(handle, buffer, offset, length, position) {
206206
validateFileHandle(handle);
207207
validateBuffer(buffer);
208208

209-
offset |= 0;
209+
if (offset == null) {
210+
offset = 0;
211+
} else {
212+
validateSafeInteger(offset, 'offset');
213+
}
214+
210215
length |= 0;
211216

212217
if (length === 0)

src/node_file.cc

+7-5
Original file line numberDiff line numberDiff line change
@@ -1851,7 +1851,7 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
18511851
*
18521852
* 0 fd int32. file descriptor
18531853
* 1 buffer instance of Buffer
1854-
* 2 offset int32. offset to start reading into inside buffer
1854+
* 2 offset int64. offset to start reading into inside buffer
18551855
* 3 length int32. length to read
18561856
* 4 position int64. file position - -1 for current position
18571857
*/
@@ -1869,15 +1869,17 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
18691869
char* buffer_data = Buffer::Data(buffer_obj);
18701870
size_t buffer_length = Buffer::Length(buffer_obj);
18711871

1872-
CHECK(args[2]->IsInt32());
1873-
const size_t off = static_cast<size_t>(args[2].As<Int32>()->Value());
1874-
CHECK_LT(off, buffer_length);
1872+
CHECK(IsSafeJsInt(args[2]));
1873+
const int64_t off_64 = args[2].As<Integer>()->Value();
1874+
CHECK_GE(off_64, 0);
1875+
CHECK_LT(static_cast<uint64_t>(off_64), buffer_length);
1876+
const size_t off = static_cast<size_t>(off_64);
18751877

18761878
CHECK(args[3]->IsInt32());
18771879
const size_t len = static_cast<size_t>(args[3].As<Int32>()->Value());
18781880
CHECK(Buffer::IsWithinBounds(off, len, buffer_length));
18791881

1880-
CHECK(args[4]->IsNumber());
1882+
CHECK(IsSafeJsInt(args[4]));
18811883
const int64_t pos = args[4].As<Integer>()->Value();
18821884

18831885
char* buf = buffer_data + off;

src/util-inl.h

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
2626

27+
#include <cmath>
2728
#include <cstring>
2829
#include "util.h"
2930

@@ -521,6 +522,17 @@ void ArrayBufferViewContents<T, S>::Read(v8::Local<v8::ArrayBufferView> abv) {
521522
}
522523
}
523524

525+
// ECMA262 20.1.2.5
526+
inline bool IsSafeJsInt(v8::Local<v8::Value> v) {
527+
if (!v->IsNumber()) return false;
528+
double v_d = v.As<v8::Number>()->Value();
529+
if (std::isnan(v_d)) return false;
530+
if (std::isinf(v_d)) return false;
531+
if (std::trunc(v_d) != v_d) return false; // not int
532+
if (std::abs(v_d) <= static_cast<double>(kMaxSafeJsInteger)) return true;
533+
return false;
534+
}
535+
524536
} // namespace node
525537

526538
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/util.h

+5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ void DumpBacktrace(FILE* fp);
184184
#define UNREACHABLE(...) \
185185
ERROR_AND_ABORT("Unreachable code reached" __VA_OPT__(": ") __VA_ARGS__)
186186

187+
// ECMA262 20.1.2.6 Number.MAX_SAFE_INTEGER (2^53-1)
188+
constexpr int64_t kMaxSafeJsInteger = 9007199254740991;
189+
190+
inline bool IsSafeJsInt(v8::Local<v8::Value> v);
191+
187192
// TAILQ-style intrusive list node.
188193
template <typename T>
189194
class ListNode;

test/parallel/test-fs-read-type.js

+27
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ assert.throws(() => {
5050
'Received -1'
5151
});
5252

53+
assert.throws(() => {
54+
fs.read(fd,
55+
Buffer.allocUnsafe(expected.length),
56+
NaN,
57+
expected.length,
58+
0,
59+
common.mustNotCall());
60+
}, {
61+
code: 'ERR_OUT_OF_RANGE',
62+
name: 'RangeError',
63+
message: 'The value of "offset" is out of range. It must be an integer. ' +
64+
'Received NaN'
65+
});
66+
5367
assert.throws(() => {
5468
fs.read(fd,
5569
Buffer.allocUnsafe(expected.length),
@@ -103,6 +117,19 @@ assert.throws(() => {
103117
'It must be >= 0 && <= 4. Received -1'
104118
});
105119

120+
assert.throws(() => {
121+
fs.readSync(fd,
122+
Buffer.allocUnsafe(expected.length),
123+
NaN,
124+
expected.length,
125+
0);
126+
}, {
127+
code: 'ERR_OUT_OF_RANGE',
128+
name: 'RangeError',
129+
message: 'The value of "offset" is out of range. It must be an integer. ' +
130+
'Received NaN'
131+
});
132+
106133
assert.throws(() => {
107134
fs.readSync(fd,
108135
Buffer.allocUnsafe(expected.length),

0 commit comments

Comments
 (0)