| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e89e0b4 commit 100f6de
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,7 @@ const { | |||
| 12 | 12 | NumberIsFinite, | |
| 13 | 13 | NumberIsInteger, | |
| 14 | 14 | MathMin, | |
| 15 | + MathRound, | ||
| 15 | 16 | ObjectIs, | |
| 16 | 17 | ObjectPrototypeHasOwnProperty, | |
| 17 | 18 | ObjectSetPrototypeOf, | |
@@ -39,9 +40,9 @@ const { | |||
| 39 | 40 | } = require('internal/errors'); | |
| 40 | 41 | const { | |
| 41 | 42 | isArrayBufferView, | |
| 42 | - isUint8Array, | ||
| 43 | + isBigInt64Array, | ||
| 43 | 44 | isDate, | |
| 44 | - isBigUint64Array | ||
| 45 | + isUint8Array, | ||
| 45 | 46 | } = require('internal/util/types'); | |
| 46 | 47 | const { | |
| 47 | 48 | kEmptyObject, | |
@@ -454,14 +455,16 @@ function nsFromTimeSpecBigInt(sec, nsec) { | |||
| 454 | 455 | return sec * kNsPerSecBigInt + nsec; | |
| 455 | 456 | } | |
| 456 | 457 | ||
| 457 | - // The Date constructor performs Math.floor() to the timestamp. | ||
| 458 | - // https://www.ecma-international.org/ecma-262/#sec-timeclip | ||
| 458 | + // The Date constructor performs Math.floor() on the absolute value | ||
| 459 | + // of the timestamp: https://tc39.es/ecma262/#sec-timeclip | ||
| 459 | 460 | // Since there may be a precision loss when the timestamp is | |
| 460 | 461 | // converted to a floating point number, we manually round | |
| 461 | 462 | // the timestamp here before passing it to Date(). | |
| 462 | 463 | // Refs: https://github.com/nodejs/node/pull/12607 | |
| 464 | + // Refs: https://github.com/nodejs/node/pull/43714 | ||
| 463 | 465 | function dateFromMs(ms) { | |
| 464 | - return new Date(Number(ms) + 0.5); | ||
| 466 | + // Coercing to number, ms can be bigint | ||
| 467 | + return new Date(MathRound(Number(ms))); | ||
| 465 | 468 | } | |
| 466 | 469 | ||
| 467 | 470 | function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize, | |
@@ -526,12 +529,12 @@ Stats.prototype._checkModeProperty = function(property) { | |||
| 526 | 529 | }; | |
| 527 | 530 | ||
| 528 | 531 | /** | |
| 529 | - * @param {Float64Array | BigUint64Array} stats | ||
| 532 | + * @param {Float64Array | BigInt64Array} stats | ||
| 530 | 533 | * @param {number} offset | |
| 531 | 534 | * @returns {BigIntStats | Stats} | |
| 532 | 535 | */ | |
| 533 | 536 | function getStatsFromBinding(stats, offset = 0) { | |
| 534 | - if (isBigUint64Array(stats)) { | ||
| 537 | + if (isBigInt64Array(stats)) { | ||
| 535 | 538 | return new BigIntStats( | |
| 536 | 539 | stats[0 + offset], stats[1 + offset], stats[2 + offset], | |
| 537 | 540 | stats[3 + offset], stats[4 + offset], stats[5 + offset], | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -307,7 +307,7 @@ typedef AliasedBufferBase<int32_t, v8::Int32Array> AliasedInt32Array; | |||
| 307 | 307 | typedef AliasedBufferBase<uint8_t, v8::Uint8Array> AliasedUint8Array; | |
| 308 | 308 | typedef AliasedBufferBase<uint32_t, v8::Uint32Array> AliasedUint32Array; | |
| 309 | 309 | typedef AliasedBufferBase<double, v8::Float64Array> AliasedFloat64Array; | |
| 310 | - typedef AliasedBufferBase<uint64_t, v8::BigUint64Array> AliasedBigUint64Array; | ||
| 310 | + typedef AliasedBufferBase<int64_t, v8::BigInt64Array> AliasedBigInt64Array; | ||
| 311 | 311 | } // namespace node | |
| 312 | 312 | ||
| 313 | 313 | #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -86,13 +86,22 @@ template <typename NativeT, typename V8T> | |||
| 86 | 86 | void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields, | |
| 87 | 87 | const uv_stat_t* s, | |
| 88 | 88 | const size_t offset) { | |
| 89 | - #define SET_FIELD_WITH_STAT(stat_offset, stat) \ | ||
| 90 | - fields->SetValue(offset + static_cast<size_t>(FsStatsOffset::stat_offset), \ | ||
| 89 | + #define SET_FIELD_WITH_STAT(stat_offset, stat) \ | ||
| 90 | + fields->SetValue(offset + static_cast<size_t>(FsStatsOffset::stat_offset), \ | ||
| 91 | 91 | static_cast<NativeT>(stat)) | |
| 92 | 92 | ||
| 93 | - #define SET_FIELD_WITH_TIME_STAT(stat_offset, stat) \ | ||
| 94 | - /* NOLINTNEXTLINE(runtime/int) */ \ | ||
| 93 | + // On win32, time is stored in uint64_t and starts from 1601-01-01. | ||
| 94 | + // libuv calculates tv_sec and tv_nsec from it and converts to signed long, | ||
| 95 | + // which causes Y2038 overflow. On the other platforms it is safe to treat | ||
| 96 | + // negative values as pre-epoch time. | ||
| 97 | + #ifdef _WIN32 | ||
| 98 | + #define SET_FIELD_WITH_TIME_STAT(stat_offset, stat) \ | ||
| 99 | + /* NOLINTNEXTLINE(runtime/int) */ \ | ||
| 95 | 100 | SET_FIELD_WITH_STAT(stat_offset, static_cast<unsigned long>(stat)) | |
| 101 | + #else | ||
| 102 | + #define SET_FIELD_WITH_TIME_STAT(stat_offset, stat) \ | ||
| 103 | + SET_FIELD_WITH_STAT(stat_offset, static_cast<double>(stat)) | ||
| 104 | + #endif // _WIN32 | ||
| 96 | 105 | ||
| 97 | 106 | SET_FIELD_WITH_STAT(kDev, s->st_dev); | |
| 98 | 107 | SET_FIELD_WITH_STAT(kMode, s->st_mode); | |
@@ -233,7 +242,7 @@ FSReqBase* GetReqWrap(const v8::FunctionCallbackInfo<v8::Value>& args, | |||
| 233 | 242 | Environment* env = binding_data->env(); | |
| 234 | 243 | if (value->StrictEquals(env->fs_use_promises_symbol())) { | |
| 235 | 244 | if (use_bigint) { | |
| 236 | - return FSReqPromise<AliasedBigUint64Array>::New(binding_data, use_bigint); | ||
| 245 | + return FSReqPromise<AliasedBigInt64Array>::New(binding_data, use_bigint); | ||
| 237 | 246 | } else { | |
| 238 | 247 | return FSReqPromise<AliasedFloat64Array>::New(binding_data, use_bigint); | |
| 239 | 248 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,7 +18,7 @@ class BindingData : public SnapshotableObject { | |||
| 18 | 18 | explicit BindingData(Environment* env, v8::Local<v8::Object> wrap); | |
| 19 | 19 | ||
| 20 | 20 | AliasedFloat64Array stats_field_array; | |
| 21 | - AliasedBigUint64Array stats_field_bigint_array; | ||
| 21 | + AliasedBigInt64Array stats_field_bigint_array; | ||
| 22 | 22 | ||
| 23 | 23 | std::vector<BaseObjectPtr<FileHandleReadWrap>> | |
| 24 | 24 | file_handle_read_wrap_freelist; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,79 @@ | |||
| 1 | + import * as common from '../common/index.mjs'; | ||
| 2 | + | ||
| 3 | + // Test timestamps returned by fsPromises.stat and fs.statSync | ||
| 4 | + | ||
| 5 | + import fs from 'node:fs'; | ||
| 6 | + import fsPromises from 'node:fs/promises'; | ||
| 7 | + import path from 'node:path'; | ||
| 8 | + import assert from 'node:assert'; | ||
| 9 | + import tmpdir from '../common/tmpdir.js'; | ||
| 10 | + | ||
| 11 | + // On some platforms (for example, ppc64) boundaries are tighter | ||
| 12 | + // than usual. If we catch these errors, skip corresponding test. | ||
| 13 | + const ignoredErrors = new Set(['EINVAL', 'EOVERFLOW']); | ||
| 14 | + | ||
| 15 | + tmpdir.refresh(); | ||
| 16 | + const filepath = path.resolve(tmpdir.path, 'timestamp'); | ||
| 17 | + | ||
| 18 | + await (await fsPromises.open(filepath, 'w')).close(); | ||
| 19 | + | ||
| 20 | + // Date might round down timestamp | ||
| 21 | + function closeEnough(actual, expected, margin) { | ||
| 22 | + // On ppc64, value is rounded to seconds | ||
| 23 | + if (process.arch === 'ppc64') { | ||
| 24 | + margin += 1000; | ||
| 25 | + } | ||
| 26 | + assert.ok(Math.abs(Number(actual - expected)) < margin, | ||
| 27 | + `expected ${expected} ± ${margin}, got ${actual}`); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + async function runTest(atime, mtime, margin = 0) { | ||
| 31 | + margin += Number.EPSILON; | ||
| 32 | + try { | ||
| 33 | + await fsPromises.utimes(filepath, new Date(atime), new Date(mtime)); | ||
| 34 | + } catch (e) { | ||
| 35 | + if (ignoredErrors.has(e.code)) return; | ||
| 36 | + throw e; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + const stats = await fsPromises.stat(filepath); | ||
| 40 | + closeEnough(stats.atimeMs, atime, margin); | ||
| 41 | + closeEnough(stats.mtimeMs, mtime, margin); | ||
| 42 | + closeEnough(stats.atime.getTime(), new Date(atime).getTime(), margin); | ||
| 43 | + closeEnough(stats.mtime.getTime(), new Date(mtime).getTime(), margin); | ||
| 44 | + | ||
| 45 | + const statsBigint = await fsPromises.stat(filepath, { bigint: true }); | ||
| 46 | + closeEnough(statsBigint.atimeMs, BigInt(atime), margin); | ||
| 47 | + closeEnough(statsBigint.mtimeMs, BigInt(mtime), margin); | ||
| 48 | + closeEnough(statsBigint.atime.getTime(), new Date(atime).getTime(), margin); | ||
| 49 | + closeEnough(statsBigint.mtime.getTime(), new Date(mtime).getTime(), margin); | ||
| 50 | + | ||
| 51 | + const statsSync = fs.statSync(filepath); | ||
| 52 | + closeEnough(statsSync.atimeMs, atime, margin); | ||
| 53 | + closeEnough(statsSync.mtimeMs, mtime, margin); | ||
| 54 | + closeEnough(statsSync.atime.getTime(), new Date(atime).getTime(), margin); | ||
| 55 | + closeEnough(statsSync.mtime.getTime(), new Date(mtime).getTime(), margin); | ||
| 56 | + | ||
| 57 | + const statsSyncBigint = fs.statSync(filepath, { bigint: true }); | ||
| 58 | + closeEnough(statsSyncBigint.atimeMs, BigInt(atime), margin); | ||
| 59 | + closeEnough(statsSyncBigint.mtimeMs, BigInt(mtime), margin); | ||
| 60 | + closeEnough(statsSyncBigint.atime.getTime(), new Date(atime).getTime(), margin); | ||
| 61 | + closeEnough(statsSyncBigint.mtime.getTime(), new Date(mtime).getTime(), margin); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + // Too high/low numbers produce too different results on different platforms | ||
| 65 | + { | ||
| 66 | + // TODO(LiviaMedeiros): investigate outdated stat time on FreeBSD. | ||
| 67 | + // On Windows, filetime is stored and handled differently. Supporting dates | ||
| 68 | + // after Y2038 is preferred over supporting dates before 1970-01-01. | ||
| 69 | + if (!common.isFreeBSD && !common.isWindows) { | ||
| 70 | + await runTest(-40691, -355, 1); // Potential precision loss on 32bit | ||
| 71 | + await runTest(-355, -40691, 1); // Potential precision loss on 32bit | ||
| 72 | + await runTest(-1, -1); | ||
| 73 | + } | ||
| 74 | + await runTest(0, 0); | ||
| 75 | + await runTest(1, 1); | ||
| 76 | + await runTest(355, 40691, 1); // Precision loss on 32bit | ||
| 77 | + await runTest(40691, 355, 1); // Precision loss on 32bit | ||
| 78 | + await runTest(1713037251360, 1713037251360, 1); // Precision loss | ||
| 79 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments