mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
fix: stronger input checking for setTimeout; add function overload (#8957)
This commit is contained in:
parent
d364a0effe
commit
3761d054d0
2 changed files with 67 additions and 20 deletions
|
@ -7,10 +7,51 @@ import {
|
||||||
unitTest,
|
unitTest,
|
||||||
} from "./test_util.ts";
|
} from "./test_util.ts";
|
||||||
|
|
||||||
function waitForMs(ms: number): Promise<number> {
|
function waitForMs(ms: number): Promise<void> {
|
||||||
return new Promise((resolve): number => setTimeout(resolve, ms));
|
return new Promise((resolve): number => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unitTest(async function functionParameterBindingSuccess(): Promise<void> {
|
||||||
|
const promise = deferred();
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
const nullProto = (newCount: number): void => {
|
||||||
|
count = newCount;
|
||||||
|
promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
Reflect.setPrototypeOf(nullProto, null);
|
||||||
|
|
||||||
|
setTimeout(nullProto, 500, 1);
|
||||||
|
await promise;
|
||||||
|
// count should be reassigned
|
||||||
|
assertEquals(count, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
unitTest(async function stringifyAndEvalNonFunctions(): Promise<void> {
|
||||||
|
// eval can only access global scope
|
||||||
|
const global = globalThis as unknown as {
|
||||||
|
globalPromise: ReturnType<typeof deferred>;
|
||||||
|
globalCount: number;
|
||||||
|
};
|
||||||
|
global.globalPromise = deferred();
|
||||||
|
global.globalCount = 0;
|
||||||
|
|
||||||
|
const notAFunction =
|
||||||
|
"globalThis.globalCount++; globalThis.globalPromise.resolve();" as unknown as () =>
|
||||||
|
void;
|
||||||
|
|
||||||
|
setTimeout(notAFunction, 500);
|
||||||
|
|
||||||
|
await global.globalPromise;
|
||||||
|
|
||||||
|
// count should be incremented
|
||||||
|
assertEquals(global.globalCount, 1);
|
||||||
|
|
||||||
|
Reflect.deleteProperty(global, "globalPromise");
|
||||||
|
Reflect.deleteProperty(global, "globalCount");
|
||||||
|
});
|
||||||
|
|
||||||
unitTest(async function timeoutSuccess(): Promise<void> {
|
unitTest(async function timeoutSuccess(): Promise<void> {
|
||||||
const promise = deferred();
|
const promise = deferred();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
|
@ -274,7 +274,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const { console } = globalThis;
|
const { console } = globalThis;
|
||||||
const OriginalDate = Date;
|
const OriginalDateNow = Date.now;
|
||||||
|
|
||||||
// Timeout values > TIMEOUT_MAX are set to 1.
|
// Timeout values > TIMEOUT_MAX are set to 1.
|
||||||
const TIMEOUT_MAX = 2 ** 31 - 1;
|
const TIMEOUT_MAX = 2 ** 31 - 1;
|
||||||
|
@ -333,7 +333,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareReadyTimers() {
|
function prepareReadyTimers() {
|
||||||
const now = OriginalDate.now();
|
const now = OriginalDateNow();
|
||||||
// Bail out if we're not expecting the global timer to fire.
|
// Bail out if we're not expecting the global timer to fire.
|
||||||
if (globalTimeoutDue === null || pendingEvents > 0) {
|
if (globalTimeoutDue === null || pendingEvents > 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -409,7 +409,7 @@
|
||||||
const nextDueNode = dueTree.min();
|
const nextDueNode = dueTree.min();
|
||||||
setOrClearGlobalTimeout(
|
setOrClearGlobalTimeout(
|
||||||
nextDueNode && nextDueNode.due,
|
nextDueNode && nextDueNode.due,
|
||||||
OriginalDate.now(),
|
OriginalDateNow(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -434,14 +434,18 @@
|
||||||
} else {
|
} else {
|
||||||
// Interval timer: compute when timer was supposed to fire next.
|
// Interval timer: compute when timer was supposed to fire next.
|
||||||
// However make sure to never schedule the next interval in the past.
|
// However make sure to never schedule the next interval in the past.
|
||||||
const now = OriginalDate.now();
|
const now = OriginalDateNow();
|
||||||
timer.due = Math.max(now, timer.due + timer.delay);
|
timer.due = Math.max(now, timer.due + timer.delay);
|
||||||
schedule(timer, now);
|
schedule(timer, now);
|
||||||
}
|
}
|
||||||
// Call the user callback. Intermediate assignment is to avoid leaking `this`
|
// Call the user callback. Intermediate assignment is to avoid leaking `this`
|
||||||
// to it, while also keeping the stack trace neat when it shows up in there.
|
// to it, while also keeping the stack trace neat when it shows up in there.
|
||||||
const callback = timer.callback;
|
const callback = timer.callback;
|
||||||
callback();
|
if ("function" === typeof callback) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
eval(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkThis(thisArg) {
|
function checkThis(thisArg) {
|
||||||
|
@ -450,24 +454,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkBigInt(n) {
|
|
||||||
if (typeof n === "bigint") {
|
|
||||||
throw new TypeError("Cannot convert a BigInt value to a number");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTimer(
|
function setTimer(
|
||||||
cb,
|
cb,
|
||||||
delay,
|
delay,
|
||||||
args,
|
args,
|
||||||
repeat,
|
repeat,
|
||||||
) {
|
) {
|
||||||
// Bind `args` to the callback and bind `this` to globalThis(global).
|
// If the callack is a function, bind `args` to the callback and bind `this` to globalThis(global).
|
||||||
const callback = cb.bind(globalThis, ...args);
|
// otherwise call `String` on it, and `eval` it on calls; do not pass variardic args to the string
|
||||||
|
let callback;
|
||||||
|
|
||||||
|
if ("function" === typeof cb) {
|
||||||
|
callback = Function.prototype.bind.call(cb, globalThis, ...args);
|
||||||
|
} else {
|
||||||
|
callback = String(cb);
|
||||||
|
args = []; // args are ignored
|
||||||
|
}
|
||||||
// In the browser, the delay value must be coercible to an integer between 0
|
// In the browser, the delay value must be coercible to an integer between 0
|
||||||
// and INT32_MAX. Any other value will cause the timer to fire immediately.
|
// and INT32_MAX. Any other value will cause the timer to fire immediately.
|
||||||
// We emulate this behavior.
|
// We emulate this behavior.
|
||||||
const now = OriginalDate.now();
|
const now = OriginalDateNow();
|
||||||
if (delay > TIMEOUT_MAX) {
|
if (delay > TIMEOUT_MAX) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`${delay} does not fit into` +
|
`${delay} does not fit into` +
|
||||||
|
@ -500,7 +506,7 @@
|
||||||
delay = 0,
|
delay = 0,
|
||||||
...args
|
...args
|
||||||
) {
|
) {
|
||||||
checkBigInt(delay);
|
delay >>>= 0;
|
||||||
checkThis(this);
|
checkThis(this);
|
||||||
return setTimer(cb, delay, args, false);
|
return setTimer(cb, delay, args, false);
|
||||||
}
|
}
|
||||||
|
@ -510,13 +516,13 @@
|
||||||
delay = 0,
|
delay = 0,
|
||||||
...args
|
...args
|
||||||
) {
|
) {
|
||||||
checkBigInt(delay);
|
delay >>>= 0;
|
||||||
checkThis(this);
|
checkThis(this);
|
||||||
return setTimer(cb, delay, args, true);
|
return setTimer(cb, delay, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearTimer(id) {
|
function clearTimer(id) {
|
||||||
id = Number(id);
|
id >>>= 0;
|
||||||
const timer = idMap.get(id);
|
const timer = idMap.get(id);
|
||||||
if (timer === undefined) {
|
if (timer === undefined) {
|
||||||
// Timer doesn't exist any more or never existed. This is not an error.
|
// Timer doesn't exist any more or never existed. This is not an error.
|
||||||
|
@ -528,7 +534,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearTimeout(id = 0) {
|
function clearTimeout(id = 0) {
|
||||||
checkBigInt(id);
|
id >>>= 0;
|
||||||
if (id === 0) {
|
if (id === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -536,7 +542,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearInterval(id = 0) {
|
function clearInterval(id = 0) {
|
||||||
checkBigInt(id);
|
id >>>= 0;
|
||||||
if (id === 0) {
|
if (id === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue