From aaa5e6613a739f8e2ff7579b69c2504bcdc37d4f Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Sat, 19 Sep 2020 22:30:59 +0100 Subject: [PATCH] fix(cli/rt): make some web API constructors illegal at runtime (#7468) --- cli/dts/lib.deno.unstable.d.ts | 6 +++--- cli/rt/01_web_util.js | 3 +++ cli/rt/40_performance.js | 24 +++++++++++++++++++++--- cli/rt/40_permissions.js | 26 +++++++++++++++++++++----- cli/rt/99_main.js | 2 +- cli/tests/unit/performance_test.ts | 17 +++++++++++++++++ cli/tests/unit/permissions_test.ts | 14 +++++++++++++- op_crates/web/02_abort_signal.js | 9 +++++++-- op_crates/web/abort_controller_test.js | 20 ++++++++++++++++++++ 9 files changed, 106 insertions(+), 15 deletions(-) diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 04c08b8938..2dcd94f75f 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -130,11 +130,11 @@ declare namespace Deno { */ export function osRelease(): string; - /** **Unstable** new API. yet to be vetted. + /** **Unstable** new API. yet to be vetted. * * Displays the total amount of free and used physical and swap memory in the * system, as well as the buffers and caches used by the kernel. - * + * * This is similar to the `free` command in Linux * * ```ts @@ -1113,7 +1113,7 @@ declare namespace Deno { /** see: https://w3c.github.io/permissions/#permissionstatus */ export class PermissionStatus { state: PermissionState; - constructor(state: PermissionState); + constructor(); } /** **UNSTABLE**: New API, yet to be vetted. Additional consideration is still diff --git a/cli/rt/01_web_util.js b/cli/rt/01_web_util.js index 1b7f7b83a3..ee8992eb25 100644 --- a/cli/rt/01_web_util.js +++ b/cli/rt/01_web_util.js @@ -1,6 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. ((window) => { + const illegalConstructorKey = Symbol("illegalConstructorKey"); + function isInvalidDate(x) { return isNaN(x.getTime()); } @@ -146,6 +148,7 @@ } window.__bootstrap.webUtil = { + illegalConstructorKey, isInvalidDate, requiredArguments, immutableDefine, diff --git a/cli/rt/40_performance.js b/cli/rt/40_performance.js index a785d23b12..b921ca43a4 100644 --- a/cli/rt/40_performance.js +++ b/cli/rt/40_performance.js @@ -2,7 +2,7 @@ ((window) => { const { opNow } = window.__bootstrap.timers; - const { cloneValue } = window.__bootstrap.webUtil; + const { cloneValue, illegalConstructorKey } = window.__bootstrap.webUtil; const customInspect = Symbol.for("Deno.customInspect"); let performanceEntries = []; @@ -74,7 +74,11 @@ entryType, startTime, duration, + key, ) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } this.#name = name; this.#entryType = entryType; this.#startTime = startTime; @@ -110,7 +114,7 @@ name, { detail = null, startTime = now() } = {}, ) { - super(name, "mark", startTime, 0); + super(name, "mark", startTime, 0, illegalConstructorKey); if (startTime < 0) { throw new TypeError("startTime cannot be negative"); } @@ -152,8 +156,12 @@ startTime, duration, detail = null, + key, ) { - super(name, "measure", startTime, duration); + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(name, "measure", startTime, duration, illegalConstructorKey); this.#detail = cloneValue(detail); } @@ -177,6 +185,12 @@ } class Performance { + constructor(key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + clearMarks(markName) { if (markName == null) { performanceEntries = performanceEntries.filter( @@ -302,6 +316,7 @@ typeof startOrMeasureOptions === "object" ? startOrMeasureOptions.detail ?? null : null, + illegalConstructorKey, ); performanceEntries.push(entry); return entry; @@ -312,10 +327,13 @@ } } + const performance = new Performance(illegalConstructorKey); + window.__bootstrap.performance = { PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance, + performance, }; })(this); diff --git a/cli/rt/40_permissions.js b/cli/rt/40_permissions.js index 983d0895ad..982e4c842d 100644 --- a/cli/rt/40_permissions.js +++ b/cli/rt/40_permissions.js @@ -2,6 +2,7 @@ ((window) => { const core = window.Deno.core; + const { illegalConstructorKey } = window.__bootstrap.webUtil; function opQuery(desc) { return core.jsonOpSync("op_query_permission", desc).state; @@ -16,30 +17,45 @@ } class PermissionStatus { - constructor(state) { + constructor(state, key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } this.state = state; } // TODO(kt3k): implement onchange handler } class Permissions { + constructor(key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + query(desc) { const state = opQuery(desc); - return Promise.resolve(new PermissionStatus(state)); + return Promise.resolve( + new PermissionStatus(state, illegalConstructorKey), + ); } revoke(desc) { const state = opRevoke(desc); - return Promise.resolve(new PermissionStatus(state)); + return Promise.resolve( + new PermissionStatus(state, illegalConstructorKey), + ); } request(desc) { const state = opRequest(desc); - return Promise.resolve(new PermissionStatus(state)); + return Promise.resolve( + new PermissionStatus(state, illegalConstructorKey), + ); } } - const permissions = new Permissions(); + const permissions = new Permissions(illegalConstructorKey); window.__bootstrap.permissions = { permissions, diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index a15de081c9..40ea8434b8 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -235,7 +235,7 @@ delete Object.prototype.__proto__; crypto: util.readOnly(crypto), dispatchEvent: util.readOnly(EventTarget.prototype.dispatchEvent), fetch: util.writable(fetch.fetch), - performance: util.writable(new performance.Performance()), + performance: util.writable(performance.performance), removeEventListener: util.readOnly( EventTarget.prototype.removeEventListener, ), diff --git a/cli/tests/unit/performance_test.ts b/cli/tests/unit/performance_test.ts index 3c98e4e406..7b2fa5cefd 100644 --- a/cli/tests/unit/performance_test.ts +++ b/cli/tests/unit/performance_test.ts @@ -3,6 +3,7 @@ import { unitTest, assert, assertEquals, + assertThrows, createResolvable, } from "./test_util.ts"; @@ -64,3 +65,19 @@ unitTest(function performanceMeasure() { }, 100); }); }); + +unitTest(function performanceIllegalConstructor() { + assertThrows(() => new Performance(), TypeError, "Illegal constructor."); +}); + +unitTest(function performanceEntryIllegalConstructor() { + assertThrows(() => new PerformanceEntry(), TypeError, "Illegal constructor."); +}); + +unitTest(function performanceMeasureIllegalConstructor() { + assertThrows( + () => new PerformanceMeasure(), + TypeError, + "Illegal constructor.", + ); +}); diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts index 0057277212..21fa1d27b8 100644 --- a/cli/tests/unit/permissions_test.ts +++ b/cli/tests/unit/permissions_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assertThrowsAsync } from "./test_util.ts"; +import { unitTest, assertThrows, assertThrowsAsync } from "./test_util.ts"; unitTest(async function permissionInvalidName(): Promise { await assertThrowsAsync(async () => { @@ -13,3 +13,15 @@ unitTest(async function permissionNetInvalidUrl(): Promise { await Deno.permissions.query({ name: "net", url: ":" }); }, URIError); }); + +unitTest(function permissionsIllegalConstructor() { + assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor."); +}); + +unitTest(function permissionStatusIllegalConstructor() { + assertThrows( + () => new Deno.PermissionStatus(), + TypeError, + "Illegal constructor.", + ); +}); diff --git a/op_crates/web/02_abort_signal.js b/op_crates/web/02_abort_signal.js index 908e85ac97..2b51ef1ea9 100644 --- a/op_crates/web/02_abort_signal.js +++ b/op_crates/web/02_abort_signal.js @@ -5,6 +5,8 @@ const signalAbort = Symbol("signalAbort"); const remove = Symbol("remove"); + const illegalConstructorKey = Symbol("illegalConstructorKey"); + class AbortSignal extends EventTarget { #aborted = false; #abortAlgorithms = new Set(); @@ -29,7 +31,10 @@ this.#abortAlgorithms.delete(algorithm); } - constructor() { + constructor(key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } super(); this.onabort = null; this.addEventListener("abort", (evt) => { @@ -50,7 +55,7 @@ } class AbortController { - #signal = new AbortSignal(); + #signal = new AbortSignal(illegalConstructorKey); get signal() { return this.#signal; diff --git a/op_crates/web/abort_controller_test.js b/op_crates/web/abort_controller_test.js index a2fb12c65e..0243c66280 100644 --- a/op_crates/web/abort_controller_test.js +++ b/op_crates/web/abort_controller_test.js @@ -9,6 +9,19 @@ function assertEquals(left, right) { assert(left === right); } +function assertThrows(fn) { + let error = null; + try { + fn(); + } catch (error_) { + error = error_; + } + if (error == null) { + throw new Error("Didn't throw."); + } + return error; +} + function basicAbortController() { controller = new AbortController(); assert(controller); @@ -64,12 +77,19 @@ function controllerHasProperToString() { assertEquals(actual, "[object AbortController]"); } +function abortSignalIllegalConstructor() { + const error = assertThrows(() => new AbortSignal()); + assert(error instanceof TypeError); + assertEquals(error.message, "Illegal constructor."); +} + function main() { basicAbortController(); signalCallsOnabort(); signalEventListener(); onlyAbortsOnce(); controllerHasProperToString(); + abortSignalIllegalConstructor(); } main();