fix: stronger input checking for setTimeout; add function overload (#8957)

This commit is contained in:
Anonymous 2021-01-06 05:53:30 -08:00 committed by GitHub
parent d364a0effe
commit 3761d054d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 20 deletions

View file

@ -7,10 +7,51 @@ import {
unitTest,
} from "./test_util.ts";
function waitForMs(ms: number): Promise<number> {
function waitForMs(ms: number): Promise<void> {
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> {
const promise = deferred();
let count = 0;

View file

@ -274,7 +274,7 @@
}
const { console } = globalThis;
const OriginalDate = Date;
const OriginalDateNow = Date.now;
// Timeout values > TIMEOUT_MAX are set to 1.
const TIMEOUT_MAX = 2 ** 31 - 1;
@ -333,7 +333,7 @@
}
function prepareReadyTimers() {
const now = OriginalDate.now();
const now = OriginalDateNow();
// Bail out if we're not expecting the global timer to fire.
if (globalTimeoutDue === null || pendingEvents > 0) {
return;
@ -409,7 +409,7 @@
const nextDueNode = dueTree.min();
setOrClearGlobalTimeout(
nextDueNode && nextDueNode.due,
OriginalDate.now(),
OriginalDateNow(),
);
}
} else {
@ -434,14 +434,18 @@
} else {
// Interval timer: compute when timer was supposed to fire next.
// 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);
schedule(timer, now);
}
// 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.
const callback = timer.callback;
callback();
if ("function" === typeof callback) {
callback();
} else {
eval(callback);
}
}
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(
cb,
delay,
args,
repeat,
) {
// Bind `args` to the callback and bind `this` to globalThis(global).
const callback = cb.bind(globalThis, ...args);
// If the callack is a function, bind `args` to the callback and bind `this` to globalThis(global).
// 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
// and INT32_MAX. Any other value will cause the timer to fire immediately.
// We emulate this behavior.
const now = OriginalDate.now();
const now = OriginalDateNow();
if (delay > TIMEOUT_MAX) {
console.warn(
`${delay} does not fit into` +
@ -500,7 +506,7 @@
delay = 0,
...args
) {
checkBigInt(delay);
delay >>>= 0;
checkThis(this);
return setTimer(cb, delay, args, false);
}
@ -510,13 +516,13 @@
delay = 0,
...args
) {
checkBigInt(delay);
delay >>>= 0;
checkThis(this);
return setTimer(cb, delay, args, true);
}
function clearTimer(id) {
id = Number(id);
id >>>= 0;
const timer = idMap.get(id);
if (timer === undefined) {
// Timer doesn't exist any more or never existed. This is not an error.
@ -528,7 +534,7 @@
}
function clearTimeout(id = 0) {
checkBigInt(id);
id >>>= 0;
if (id === 0) {
return;
}
@ -536,7 +542,7 @@
}
function clearInterval(id = 0) {
checkBigInt(id);
id >>>= 0;
if (id === 0) {
return;
}