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 https://github.com/nodejs/node/issues/26563 PR-URL: https://github.com/nodejs/node/pull/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>
This commit is contained in:
parent
91a4cb7175
commit
0bbda5e5ae
14
lib/fs.js
14
lib/fs.js
@ -453,7 +453,12 @@ function read(fd, buffer, offset, length, position, callback) {
|
|||||||
validateBuffer(buffer);
|
validateBuffer(buffer);
|
||||||
callback = maybeCallback(callback);
|
callback = maybeCallback(callback);
|
||||||
|
|
||||||
offset |= 0;
|
if (offset == null) {
|
||||||
|
offset = 0;
|
||||||
|
} else {
|
||||||
|
validateSafeInteger(offset, 'offset');
|
||||||
|
}
|
||||||
|
|
||||||
length |= 0;
|
length |= 0;
|
||||||
|
|
||||||
if (length === 0) {
|
if (length === 0) {
|
||||||
@ -490,7 +495,12 @@ function readSync(fd, buffer, offset, length, position) {
|
|||||||
validateInt32(fd, 'fd', 0);
|
validateInt32(fd, 'fd', 0);
|
||||||
validateBuffer(buffer);
|
validateBuffer(buffer);
|
||||||
|
|
||||||
offset |= 0;
|
if (offset == null) {
|
||||||
|
offset = 0;
|
||||||
|
} else {
|
||||||
|
validateSafeInteger(offset, 'offset');
|
||||||
|
}
|
||||||
|
|
||||||
length |= 0;
|
length |= 0;
|
||||||
|
|
||||||
if (length === 0) {
|
if (length === 0) {
|
||||||
|
@ -206,7 +206,12 @@ async function read(handle, buffer, offset, length, position) {
|
|||||||
validateFileHandle(handle);
|
validateFileHandle(handle);
|
||||||
validateBuffer(buffer);
|
validateBuffer(buffer);
|
||||||
|
|
||||||
offset |= 0;
|
if (offset == null) {
|
||||||
|
offset = 0;
|
||||||
|
} else {
|
||||||
|
validateSafeInteger(offset, 'offset');
|
||||||
|
}
|
||||||
|
|
||||||
length |= 0;
|
length |= 0;
|
||||||
|
|
||||||
if (length === 0)
|
if (length === 0)
|
||||||
|
@ -1851,7 +1851,7 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
|
|||||||
*
|
*
|
||||||
* 0 fd int32. file descriptor
|
* 0 fd int32. file descriptor
|
||||||
* 1 buffer instance of Buffer
|
* 1 buffer instance of Buffer
|
||||||
* 2 offset int32. offset to start reading into inside buffer
|
* 2 offset int64. offset to start reading into inside buffer
|
||||||
* 3 length int32. length to read
|
* 3 length int32. length to read
|
||||||
* 4 position int64. file position - -1 for current position
|
* 4 position int64. file position - -1 for current position
|
||||||
*/
|
*/
|
||||||
@ -1869,15 +1869,17 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
|
|||||||
char* buffer_data = Buffer::Data(buffer_obj);
|
char* buffer_data = Buffer::Data(buffer_obj);
|
||||||
size_t buffer_length = Buffer::Length(buffer_obj);
|
size_t buffer_length = Buffer::Length(buffer_obj);
|
||||||
|
|
||||||
CHECK(args[2]->IsInt32());
|
CHECK(IsSafeJsInt(args[2]));
|
||||||
const size_t off = static_cast<size_t>(args[2].As<Int32>()->Value());
|
const int64_t off_64 = args[2].As<Integer>()->Value();
|
||||||
CHECK_LT(off, buffer_length);
|
CHECK_GE(off_64, 0);
|
||||||
|
CHECK_LT(static_cast<uint64_t>(off_64), buffer_length);
|
||||||
|
const size_t off = static_cast<size_t>(off_64);
|
||||||
|
|
||||||
CHECK(args[3]->IsInt32());
|
CHECK(args[3]->IsInt32());
|
||||||
const size_t len = static_cast<size_t>(args[3].As<Int32>()->Value());
|
const size_t len = static_cast<size_t>(args[3].As<Int32>()->Value());
|
||||||
CHECK(Buffer::IsWithinBounds(off, len, buffer_length));
|
CHECK(Buffer::IsWithinBounds(off, len, buffer_length));
|
||||||
|
|
||||||
CHECK(args[4]->IsNumber());
|
CHECK(IsSafeJsInt(args[4]));
|
||||||
const int64_t pos = args[4].As<Integer>()->Value();
|
const int64_t pos = args[4].As<Integer>()->Value();
|
||||||
|
|
||||||
char* buf = buffer_data + off;
|
char* buf = buffer_data + off;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
@ -521,6 +522,17 @@ void ArrayBufferViewContents<T, S>::Read(v8::Local<v8::ArrayBufferView> abv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ECMA262 20.1.2.5
|
||||||
|
inline bool IsSafeJsInt(v8::Local<v8::Value> v) {
|
||||||
|
if (!v->IsNumber()) return false;
|
||||||
|
double v_d = v.As<v8::Number>()->Value();
|
||||||
|
if (std::isnan(v_d)) return false;
|
||||||
|
if (std::isinf(v_d)) return false;
|
||||||
|
if (std::trunc(v_d) != v_d) return false; // not int
|
||||||
|
if (std::abs(v_d) <= static_cast<double>(kMaxSafeJsInteger)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
@ -184,6 +184,11 @@ void DumpBacktrace(FILE* fp);
|
|||||||
#define UNREACHABLE(...) \
|
#define UNREACHABLE(...) \
|
||||||
ERROR_AND_ABORT("Unreachable code reached" __VA_OPT__(": ") __VA_ARGS__)
|
ERROR_AND_ABORT("Unreachable code reached" __VA_OPT__(": ") __VA_ARGS__)
|
||||||
|
|
||||||
|
// ECMA262 20.1.2.6 Number.MAX_SAFE_INTEGER (2^53-1)
|
||||||
|
constexpr int64_t kMaxSafeJsInteger = 9007199254740991;
|
||||||
|
|
||||||
|
inline bool IsSafeJsInt(v8::Local<v8::Value> v);
|
||||||
|
|
||||||
// TAILQ-style intrusive list node.
|
// TAILQ-style intrusive list node.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class ListNode;
|
class ListNode;
|
||||||
|
@ -50,6 +50,20 @@ assert.throws(() => {
|
|||||||
'Received -1'
|
'Received -1'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
fs.read(fd,
|
||||||
|
Buffer.allocUnsafe(expected.length),
|
||||||
|
NaN,
|
||||||
|
expected.length,
|
||||||
|
0,
|
||||||
|
common.mustNotCall());
|
||||||
|
}, {
|
||||||
|
code: 'ERR_OUT_OF_RANGE',
|
||||||
|
name: 'RangeError',
|
||||||
|
message: 'The value of "offset" is out of range. It must be an integer. ' +
|
||||||
|
'Received NaN'
|
||||||
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
fs.read(fd,
|
fs.read(fd,
|
||||||
Buffer.allocUnsafe(expected.length),
|
Buffer.allocUnsafe(expected.length),
|
||||||
@ -103,6 +117,19 @@ assert.throws(() => {
|
|||||||
'It must be >= 0 && <= 4. Received -1'
|
'It must be >= 0 && <= 4. Received -1'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
fs.readSync(fd,
|
||||||
|
Buffer.allocUnsafe(expected.length),
|
||||||
|
NaN,
|
||||||
|
expected.length,
|
||||||
|
0);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_OUT_OF_RANGE',
|
||||||
|
name: 'RangeError',
|
||||||
|
message: 'The value of "offset" is out of range. It must be an integer. ' +
|
||||||
|
'Received NaN'
|
||||||
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
fs.readSync(fd,
|
fs.readSync(fd,
|
||||||
Buffer.allocUnsafe(expected.length),
|
Buffer.allocUnsafe(expected.length),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user