Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions benchmark/dgram/multi-buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ const common = require('../common.js');
const dgram = require('dgram');
const PORT = common.PORT;

// `num` is the number of send requests to queue up each time.
// `n` is the number of send requests to queue up each time.
// Keep it reasonably high (>10) otherwise you're benchmarking the speed of
// event loop cycles more than anything else.
const bench = common.createBenchmark(main, {
len: [64, 256, 1024],
num: [100],
chunks: [1, 2, 4, 8],
len: [64, 512, 1024],
n: [100],
chunks: [1, 8],
type: ['send', 'recv'],
dur: [5],
});

function main({ dur, len, num, type, chunks }) {
function main({ dur, len, n, type, chunks }) {
const chunk = [];
for (let i = 0; i < chunks; i++) {
chunk.push(Buffer.allocUnsafe(Math.round(len / chunks)));
Expand All @@ -26,11 +26,11 @@ function main({ dur, len, num, type, chunks }) {
const socket = dgram.createSocket('udp4');

function onsend() {
if (sent++ % num === 0) {
if (sent++ % n === 0) {
// The setImmediate() is necessary to have event loop progress on OSes
// that only perform synchronous I/O on nonblocking UDP sockets.
setImmediate(() => {
for (let i = 0; i < num; i++) {
for (let i = 0; i < n; i++) {
socket.send(chunk, PORT, '127.0.0.1', onsend);
}
});
Expand Down
47 changes: 47 additions & 0 deletions benchmark/zlib/crc32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const common = require('../common.js');
const { crc32 } = require('zlib');

// Benchmark crc32 on Buffer and String inputs across sizes.
// Iteration count is scaled inversely with input length to keep runtime sane.
// Example:
// node benchmark/zlib/crc32.js type=buffer len=4096 n=4000000
// ./out/Release/node benchmark/zlib/crc32.js --test

const bench = common.createBenchmark(main, {
type: ['buffer', 'string'],
len: [32, 256, 4096, 65536],
n: [4e6],
});

function makeBuffer(size) {
const buf = Buffer.allocUnsafe(size);
for (let i = 0; i < size; i++) buf[i] = (i * 1103515245 + 12345) & 0xff;
return buf;
}

function makeAsciiString(size) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
let out = '';
for (let i = 0, j = 0; i < size; i++, j = (j + 7) % chars.length) out += chars[j];
return out;
}

function main({ type, len, n }) {
// Scale iterations so that total processed bytes roughly constant around n*4096 bytes.
const scale = 4096 / len;
const iters = Math.max(1, Math.floor(n * scale));

const data = type === 'buffer' ? makeBuffer(len) : makeAsciiString(len);

let acc = 0;
for (let i = 0; i < Math.min(iters, 10000); i++) acc ^= crc32(data, 0);

bench.start();
let sum = 0;
for (let i = 0; i < iters; i++) sum ^= crc32(data, 0);
bench.end(iters);

if (sum === acc - 1) process.stderr.write('');
}
39 changes: 38 additions & 1 deletion doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,20 @@

### `new assert.Assert([options])`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59762

Check warning on line 235 in doc/api/assert.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added `skipPrototype` option.
-->

* `options` {Object}
* `diff` {string} If set to `'full'`, shows the full diff in assertion errors. Defaults to `'simple'`.
Accepted values: `'simple'`, `'full'`.
* `strict` {boolean} If set to `true`, non-strict methods behave like their
corresponding strict methods. Defaults to `true`.
* `skipPrototype` {boolean} If set to `true`, skips prototype and constructor
comparison in deep equality checks. Defaults to `false`.

Creates a new assertion instance. The `diff` option controls the verbosity of diffs in assertion error messages.

Expand All @@ -245,7 +254,8 @@
```

**Important**: When destructuring assertion methods from an `Assert` instance,
the methods lose their connection to the instance's configuration options (such as `diff` and `strict` settings).
the methods lose their connection to the instance's configuration options (such
as `diff`, `strict`, and `skipPrototype` settings).
The destructured methods will fall back to default behavior instead.

```js
Expand All @@ -259,6 +269,33 @@
strictEqual({ a: 1 }, { b: { c: 1 } });
```

The `skipPrototype` option affects all deep equality methods:

```js
class Foo {
constructor(a) {
this.a = a;
}
}

class Bar {
constructor(a) {
this.a = a;
}
}

const foo = new Foo(1);
const bar = new Bar(1);

// Default behavior - fails due to different constructors
const assert1 = new Assert();
assert1.deepStrictEqual(foo, bar); // AssertionError

// Skip prototype comparison - passes if properties are equal
const assert2 = new Assert({ skipPrototype: true });
assert2.deepStrictEqual(foo, bar); // OK
```

When destructured, methods lose access to the instance's `this` context and revert to default assertion behavior
(diff: 'simple', non-strict mode).
To maintain custom options when using destructured methods, avoid
Expand Down
8 changes: 4 additions & 4 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -2142,7 +2142,7 @@ added: v0.4.0
-->

* `name` {string}
* Returns: {any}
* Returns: {number | string | string\[] | undefined}

Reads out a header that's already been queued but not sent to the client.
The name is case-insensitive. The type of the return value depends
Expand Down Expand Up @@ -2277,7 +2277,7 @@ added: v0.4.0
-->

* `name` {string}
* `value` {any}
* `value` {number | string | string\[]}
* Returns: {http.ServerResponse}

Returns the response object.
Expand Down Expand Up @@ -3228,7 +3228,7 @@ added: v0.4.0
-->

* `name` {string} Name of header
* Returns: {string | undefined}
* Returns: {number | string | string\[] | undefined}

Gets the value of the HTTP header with the given name. If that header is not
set, the returned value will be `undefined`.
Expand Down Expand Up @@ -3330,7 +3330,7 @@ added: v0.4.0
-->

* `name` {string} Header name
* `value` {any} Header value
* `value` {number | string | string\[]} Header value
* Returns: {this}

Sets a single header value. If the header already exists in the to-be-sent
Expand Down
39 changes: 38 additions & 1 deletion doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -1574,19 +1574,56 @@
console.log(arr); // logs the full array
```

## `util.isDeepStrictEqual(val1, val2)`
## `util.isDeepStrictEqual(val1, val2[, options])`

<!-- YAML
added: v9.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59762

Check warning on line 1583 in doc/api/util.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added `options` parameter to allow skipping prototype comparison.
-->

* `val1` {any}
* `val2` {any}
* `skipPrototype` {boolean} If `true`, prototype and constructor
comparison is skipped during deep strict equality check. **Default:** `false`.
* Returns: {boolean}

Returns `true` if there is deep strict equality between `val1` and `val2`.
Otherwise, returns `false`.

By default, deep strict equality includes comparison of object prototypes and
constructors. When `skipPrototype` is `true`, objects with
different prototypes or constructors can still be considered equal if their
enumerable properties are deeply strictly equal.

```js
const util = require('node:util');

class Foo {
constructor(a) {
this.a = a;
}
}

class Bar {
constructor(a) {
this.a = a;
}
}

const foo = new Foo(1);
const bar = new Bar(1);

// Different constructors, same properties
console.log(util.isDeepStrictEqual(foo, bar));
// false

console.log(util.isDeepStrictEqual(foo, bar, true));
// true
```

See [`assert.deepStrictEqual()`][] for more information about deep strict
equality.

Expand Down
38 changes: 37 additions & 1 deletion lib/_http_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
const {
MathMin,
Symbol,
Uint8Array,
} = primordials;
const { setImmediate } = require('timers');

Expand Down Expand Up @@ -205,7 +206,30 @@ function freeParser(parser, req, socket) {
}
}

// Character code ranges for valid HTTP tokens
// Valid chars: ^_`a-zA-Z-0-9!#$%&'*+.|~
// Based on RFC 7230 Section 3.2.6 token definition
// See https://tools.ietf.org/html/rfc7230#section-3.2.6
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
const validTokenChars = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 240-255
]);

/**
* Verifies that the given val is a valid HTTP token
* per the rules defined in RFC 7230
Expand All @@ -214,7 +238,19 @@ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
* @returns {boolean}
*/
function checkIsHttpToken(val) {
return tokenRegExp.test(val);
if (val.length >= 10) {
return tokenRegExp.test(val);
}

if (val.length === 0) return false;

// Use lookup table for short strings, regex for longer ones
for (let i = 0; i < val.length; i++) {
if (!validTokenChars[val.charCodeAt(i)]) {
return false;
}
}
return true;
}

const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
Expand Down
8 changes: 5 additions & 3 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ const NO_EXCEPTION_SENTINEL = {};
* @property {'full'|'simple'} [diff='simple'] - If set to 'full', shows the full diff in assertion errors.
* @property {boolean} [strict=true] - If set to true, non-strict methods behave like their corresponding
* strict methods.
* @property {boolean} [skipPrototype=false] - If set to true, skips comparing prototypes
* in deep equality checks.
*/

/**
Expand All @@ -105,7 +107,7 @@ function Assert(options) {
throw new ERR_CONSTRUCT_CALL_REQUIRED('Assert');
}

options = ObjectAssign({ __proto__: null, strict: true }, options);
options = ObjectAssign({ __proto__: null, strict: true, skipPrototype: false }, options);

const allowedDiffs = ['simple', 'full'];
if (options.diff !== undefined) {
Expand Down Expand Up @@ -311,7 +313,7 @@ Assert.prototype.deepStrictEqual = function deepStrictEqual(actual, expected, me
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isDeepStrictEqual(actual, expected)) {
if (!isDeepStrictEqual(actual, expected, this?.[kOptions]?.skipPrototype)) {
innerFail({
actual,
expected,
Expand All @@ -337,7 +339,7 @@ function notDeepStrictEqual(actual, expected, message) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (isDeepStrictEqual(actual, expected)) {
if (isDeepStrictEqual(actual, expected, this?.[kOptions]?.skipPrototype)) {
innerFail({
actual,
expected,
Expand Down
12 changes: 8 additions & 4 deletions lib/internal/util/comparisons.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ const {
getOwnNonIndexProperties,
} = internalBinding('util');

const kStrict = 1;
const kStrict = 2;
const kStrictWithoutPrototypes = 3;
const kLoose = 0;
const kPartial = 2;
const kPartial = 1;

const kNoIterator = 0;
const kIsArray = 1;
Expand Down Expand Up @@ -458,7 +459,7 @@ function keyCheck(val1, val2, mode, memos, iterationType, keys2) {
}
} else if (keys2.length !== (keys1 = ObjectKeys(val1)).length) {
return false;
} else if (mode === kStrict) {
} else if (mode === kStrict || mode === kStrictWithoutPrototypes) {
const symbolKeysA = getOwnSymbols(val1);
if (symbolKeysA.length !== 0) {
let count = 0;
Expand Down Expand Up @@ -1027,7 +1028,10 @@ module.exports = {
isDeepEqual(val1, val2) {
return detectCycles(val1, val2, kLoose);
},
isDeepStrictEqual(val1, val2) {
isDeepStrictEqual(val1, val2, skipPrototype) {
if (skipPrototype) {
return detectCycles(val1, val2, kStrictWithoutPrototypes);
}
return detectCycles(val1, val2, kStrict);
},
isPartialStrictEqual(val1, val2) {
Expand Down
Loading
Loading