Skip to content

Commit f5ed338

Browse files
authoredJun 28, 2024··
timers: emit warning if delay is negative or NaN
Emit process warning once per process when delay is a negative number or not a number, this will prevent unexpected behaviour caused by invalid `delay` also keep the consistency of the behaviour and warning message for `TIMEOUT_MAX` number As the negative number is invalid delay will be set to 1. PR-URL: #46678 Reviewed-By: Debadree Chatterjee <debadree333@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 764b13d commit f5ed338

9 files changed

+272
-5
lines changed
 

Diff for: ‎doc/api/process.md

+4
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,10 @@ A few of the warning types that are most common include:
676676
* `'TimeoutOverflowWarning'` - Indicates that a numeric value that cannot fit
677677
within a 32-bit signed integer has been provided to either the `setTimeout()`
678678
or `setInterval()` functions.
679+
* `'TimeoutNegativeWarning'` - Indicates that a negative number has provided to
680+
either the `setTimeout()` or `setInterval()` functions.
681+
* `'TimeoutNaNWarning'` - Indicates that a value which is not a number has
682+
provided to either the `setTimeout()` or `setInterval()` functions.
679683
* `'UnsupportedWarning'` - Indicates use of an unsupported option or feature
680684
that will be ignored rather than treated as an error. One example is use of
681685
the HTTP response status message when using the HTTP/2 compatibility API.

Diff for: ‎doc/api/timers.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ changes:
239239

240240
Schedules repeated execution of `callback` every `delay` milliseconds.
241241

242-
When `delay` is larger than `2147483647` or less than `1`, the `delay` will be
243-
set to `1`. Non-integer delays are truncated to an integer.
242+
When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay`
243+
will be set to `1`. Non-integer delays are truncated to an integer.
244244

245245
If `callback` is not a function, a [`TypeError`][] will be thrown.
246246

@@ -272,7 +272,7 @@ Node.js makes no guarantees about the exact timing of when callbacks will fire,
272272
nor of their ordering. The callback will be called as close as possible to the
273273
time specified.
274274

275-
When `delay` is larger than `2147483647` or less than `1`, the `delay`
275+
When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay`
276276
will be set to `1`. Non-integer delays are truncated to an integer.
277277

278278
If `callback` is not a function, a [`TypeError`][] will be thrown.

Diff for: ‎lib/internal/timers.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const {
7676
MathMax,
7777
MathTrunc,
7878
NumberIsFinite,
79+
NumberIsNaN,
7980
NumberMIN_SAFE_INTEGER,
8081
ReflectApply,
8182
Symbol,
@@ -164,17 +165,36 @@ function initAsyncResource(resource, type) {
164165
if (initHooksExist())
165166
emitInit(asyncId, type, triggerAsyncId, resource);
166167
}
168+
169+
let warnedNegativeNumber = false;
170+
let warnedNotNumber = false;
171+
167172
class Timeout {
168173
// Timer constructor function.
169174
// The entire prototype is defined in lib/timers.js
170175
constructor(callback, after, args, isRepeat, isRefed) {
171-
after *= 1; // Coalesce to number or NaN
176+
if (after === undefined) {
177+
after = 1;
178+
} else {
179+
after *= 1; // Coalesce to number or NaN
180+
}
181+
172182
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
173183
if (after > TIMEOUT_MAX) {
174184
process.emitWarning(`${after} does not fit into` +
175185
' a 32-bit signed integer.' +
176186
'\nTimeout duration was set to 1.',
177187
'TimeoutOverflowWarning');
188+
} else if (after < 0 && !warnedNegativeNumber) {
189+
warnedNegativeNumber = true;
190+
process.emitWarning(`${after} is a negative number.` +
191+
'\nTimeout duration was set to 1.',
192+
'TimeoutNegativeWarning');
193+
} else if (NumberIsNaN(after) && !warnedNotNumber) {
194+
warnedNotNumber = true;
195+
process.emitWarning(`${after} is not a number.` +
196+
'\nTimeout duration was set to 1.',
197+
'TimeoutNaNWarning');
178198
}
179199
after = 1; // Schedule on next tick, follows browser behavior
180200
}

Diff for: ‎test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
Error: goodbye
77
at Hello (*uglify-throw-original.js:5:9)
88
at Immediate.<anonymous> (*uglify-throw-original.js:9:3)
9-
at process.processImmediate (node:internal*timers:483:21)
9+
at process.processImmediate (node:internal*timers:503:21)
1010

1111
Node.js *
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const NOT_A_NUMBER = NaN;
7+
8+
function timerNotCanceled() {
9+
assert.fail('Timer should be canceled');
10+
}
11+
12+
process.on(
13+
'warning',
14+
common.mustCall((warning) => {
15+
if (warning.name === 'DeprecationWarning') return;
16+
17+
const lines = warning.message.split('\n');
18+
19+
assert.strictEqual(warning.name, 'TimeoutNaNWarning');
20+
assert.strictEqual(lines[0], `${NOT_A_NUMBER} is not a number.`);
21+
assert.strictEqual(lines.length, 2);
22+
}, 1)
23+
);
24+
25+
{
26+
const timeout = setTimeout(timerNotCanceled, NOT_A_NUMBER);
27+
clearTimeout(timeout);
28+
}
29+
30+
{
31+
const interval = setInterval(timerNotCanceled, NOT_A_NUMBER);
32+
clearInterval(interval);
33+
}
34+
35+
{
36+
const timeout = setTimeout(timerNotCanceled, NOT_A_NUMBER);
37+
timeout.refresh();
38+
clearTimeout(timeout);
39+
}

Diff for: ‎test/parallel/test-timers-nan-duration-warning.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const child_process = require('child_process');
6+
const path = require('path');
7+
8+
const NOT_A_NUMBER = NaN;
9+
10+
function timerNotCanceled() {
11+
assert.fail('Timer should be canceled');
12+
}
13+
14+
const testCases = ['timeout', 'interval', 'refresh'];
15+
16+
function runTests() {
17+
const args = process.argv.slice(2);
18+
19+
const testChoice = args[0];
20+
21+
if (!testChoice) {
22+
const filePath = path.join(__filename);
23+
24+
testCases.forEach((testCase) => {
25+
const { stdout } = child_process.spawnSync(
26+
process.execPath,
27+
[filePath, testCase],
28+
{ encoding: 'utf8' }
29+
);
30+
31+
const lines = stdout.split('\n');
32+
33+
if (lines[0] === 'DeprecationWarning') return;
34+
35+
assert.strictEqual(lines[0], 'TimeoutNaNWarning');
36+
assert.strictEqual(lines[1], `${NOT_A_NUMBER} is not a number.`);
37+
assert.strictEqual(lines[2], 'Timeout duration was set to 1.');
38+
});
39+
}
40+
41+
if (args[0] === testCases[0]) {
42+
const timeout = setTimeout(timerNotCanceled, NOT_A_NUMBER);
43+
clearTimeout(timeout);
44+
}
45+
46+
if (args[0] === testCases[1]) {
47+
const interval = setInterval(timerNotCanceled, NOT_A_NUMBER);
48+
clearInterval(interval);
49+
}
50+
51+
if (args[0] === testCases[2]) {
52+
const timeout = setTimeout(timerNotCanceled, NOT_A_NUMBER);
53+
timeout.refresh();
54+
clearTimeout(timeout);
55+
}
56+
57+
process.on(
58+
'warning',
59+
60+
(warning) => {
61+
console.log(warning.name);
62+
console.log(warning.message);
63+
}
64+
);
65+
}
66+
67+
runTests();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const NEGATIVE_NUMBER = -1;
7+
8+
function timerNotCanceled() {
9+
assert.fail('Timer should be canceled');
10+
}
11+
12+
process.on(
13+
'warning',
14+
common.mustCall((warning) => {
15+
if (warning.name === 'DeprecationWarning') return;
16+
17+
const lines = warning.message.split('\n');
18+
19+
assert.strictEqual(warning.name, 'TimeoutNegativeWarning');
20+
assert.strictEqual(lines[0], `${NEGATIVE_NUMBER} is a negative number.`);
21+
assert.strictEqual(lines.length, 2);
22+
}, 1)
23+
);
24+
25+
{
26+
const timeout = setTimeout(timerNotCanceled, NEGATIVE_NUMBER);
27+
clearTimeout(timeout);
28+
}
29+
30+
{
31+
const interval = setInterval(timerNotCanceled, NEGATIVE_NUMBER);
32+
clearInterval(interval);
33+
}
34+
35+
{
36+
const timeout = setTimeout(timerNotCanceled, NEGATIVE_NUMBER);
37+
timeout.refresh();
38+
clearTimeout(timeout);
39+
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const child_process = require('child_process');
6+
const path = require('path');
7+
8+
const NEGATIVE_NUMBER = -1;
9+
10+
function timerNotCanceled() {
11+
assert.fail('Timer should be canceled');
12+
}
13+
14+
const testCases = ['timeout', 'interval', 'refresh'];
15+
16+
function runTests() {
17+
const args = process.argv.slice(2);
18+
19+
const testChoice = args[0];
20+
21+
if (!testChoice) {
22+
const filePath = path.join(__filename);
23+
24+
testCases.forEach((testCase) => {
25+
const { stdout } = child_process.spawnSync(
26+
process.execPath,
27+
[filePath, testCase],
28+
{ encoding: 'utf8' }
29+
);
30+
31+
const lines = stdout.split('\n');
32+
33+
if (lines[0] === 'DeprecationWarning') return;
34+
35+
assert.strictEqual(lines[0], 'TimeoutNegativeWarning');
36+
assert.strictEqual(lines[1], `${NEGATIVE_NUMBER} is a negative number.`);
37+
assert.strictEqual(lines[2], 'Timeout duration was set to 1.');
38+
});
39+
}
40+
41+
if (args[0] === testCases[0]) {
42+
const timeout = setTimeout(timerNotCanceled, NEGATIVE_NUMBER);
43+
clearTimeout(timeout);
44+
}
45+
46+
if (args[0] === testCases[1]) {
47+
const interval = setInterval(timerNotCanceled, NEGATIVE_NUMBER);
48+
clearInterval(interval);
49+
}
50+
51+
if (args[0] === testCases[2]) {
52+
const timeout = setTimeout(timerNotCanceled, NEGATIVE_NUMBER);
53+
timeout.refresh();
54+
clearTimeout(timeout);
55+
}
56+
57+
process.on(
58+
'warning',
59+
60+
(warning) => {
61+
console.log(warning.name);
62+
console.log(warning.message);
63+
}
64+
);
65+
}
66+
67+
runTests();

Diff for: ‎test/parallel/test-timers-not-emit-duration-zero.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
function timerNotCanceled() {
7+
assert.fail('Timer should be canceled');
8+
}
9+
10+
process.on(
11+
'warning',
12+
common.mustNotCall(() => {
13+
assert.fail('Timer should be canceled');
14+
})
15+
);
16+
17+
{
18+
const timeout = setTimeout(timerNotCanceled, 0);
19+
clearTimeout(timeout);
20+
}
21+
22+
{
23+
const interval = setInterval(timerNotCanceled, 0);
24+
clearInterval(interval);
25+
}
26+
27+
{
28+
const timeout = setTimeout(timerNotCanceled, 0);
29+
timeout.refresh();
30+
clearTimeout(timeout);
31+
}

0 commit comments

Comments
 (0)
Please sign in to comment.