From 101ee3a440e01a90787d294e57287c4f8de6286a Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Fri, 14 Oct 2022 12:10:55 -0400 Subject: [PATCH] Allow capitalized function identifiers to be allowlisted The current allowlist for capitalized function identifiers only allows stdlib JS modules. This PR introduces a new compiler option to allow passing in a set of allowed capitalized user function identifiers. It's a hack (in the absence of a type system) to let us check that capitalized function calls are only possible for identifiers that aren't bound to a React component. I considered adding a mechanism in fixtures to configure compiler options per-fixture, but opted to keep it simple for now and special case a `ReactForgetSecretInternals` identifier as one allowed user function in transform tests. --- compiler/forget/src/CompilerOptions.ts | 21 +++++++++++++++++-- .../forget/src/MiddleEnd/SketchyCodeCheck.ts | 7 +++++-- .../src/__tests__/CompilerOptions-test.ts | 1 + .../forget/src/__tests__/transform-test.ts | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/compiler/forget/src/CompilerOptions.ts b/compiler/forget/src/CompilerOptions.ts index ce634aec04..0ad6423d67 100644 --- a/compiler/forget/src/CompilerOptions.ts +++ b/compiler/forget/src/CompilerOptions.ts @@ -6,15 +6,15 @@ */ import { PluginOptions } from "@babel/core"; +import { hasOwnProperty } from "./Common/utils"; import { - createCompilerFlags, CompilerFlags, + createCompilerFlags, parseCompilerFlags, } from "./CompilerFlags"; import { isOutputKind, OutputKind } from "./CompilerOutputs"; import { Logger, noopLogger } from "./Logger"; import { PassName } from "./Pass"; -import { hasOwnProperty } from "./Common/utils"; export type CompilerOptions = { outputKinds: OutputKind[]; @@ -38,6 +38,11 @@ export type CompilerOptions = { * By default, logs are disabled. */ logger: Logger; + + /** + * Capitalized identifier names that can be used in call expressions. + */ + allowedCapitalizedUserFunctions: Set; }; /** @@ -98,6 +103,17 @@ export function parseCompilerOptions( } resOpts.logger = logger; } + if (hasOwnProperty(inputOpts, "allowedCapitalizedUserFunctions")) { + const allowedCapitalizedUserFunctions = + inputOpts.allowedCapitalizedUserFunctions; + if ( + typeof allowedCapitalizedUserFunctions !== "object" || + !(allowedCapitalizedUserFunctions instanceof Set) + ) { + throw `Invalid value for 'allowedCapitalizedUserFunctions': ${allowedCapitalizedUserFunctions}`; + } + resOpts.allowedCapitalizedUserFunctions = allowedCapitalizedUserFunctions; + } return resOpts; } @@ -111,5 +127,6 @@ export function createCompilerOptions(): CompilerOptions { stopPass: PassName.JSGen, optIn: false, logger: noopLogger, + allowedCapitalizedUserFunctions: new Set(), }; } diff --git a/compiler/forget/src/MiddleEnd/SketchyCodeCheck.ts b/compiler/forget/src/MiddleEnd/SketchyCodeCheck.ts index 8801d60145..904e3e676d 100644 --- a/compiler/forget/src/MiddleEnd/SketchyCodeCheck.ts +++ b/compiler/forget/src/MiddleEnd/SketchyCodeCheck.ts @@ -23,7 +23,7 @@ export default { run, }; -const ALLOWED_CAPITALIZED_FUNCTIONS = new Set([ +const ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS = new Set([ "AggregateError", "Array", "BigInt", @@ -58,7 +58,10 @@ export function run( const callee = path.get("callee"); if (t.isIdentifier(callee.node)) { const name = callee.node.name; - if (ALLOWED_CAPITALIZED_FUNCTIONS.has(name)) { + if ( + ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS.has(name) || + context.opts.allowedCapitalizedUserFunctions.has(name) + ) { return; } // Allow `Module().method()`; diff --git a/compiler/forget/src/__tests__/CompilerOptions-test.ts b/compiler/forget/src/__tests__/CompilerOptions-test.ts index 9a2dc859ec..fdcde972ce 100644 --- a/compiler/forget/src/__tests__/CompilerOptions-test.ts +++ b/compiler/forget/src/__tests__/CompilerOptions-test.ts @@ -64,6 +64,7 @@ describe("CompilerOptions", () => { optIn: true, stopPass: PassName.JSGen, logger: noopLogger, + allowedCapitalizedUserFunctions: new Set(), }; expect(parseCompilerOptions(fullInput)).toEqual(fullInput); }); diff --git a/compiler/forget/src/__tests__/transform-test.ts b/compiler/forget/src/__tests__/transform-test.ts index 06d2132a3f..33608b5caa 100644 --- a/compiler/forget/src/__tests__/transform-test.ts +++ b/compiler/forget/src/__tests__/transform-test.ts @@ -116,6 +116,9 @@ describe("React Forget", () => { stopPass, flags, logger: createArrayLogger(logs), + allowedCapitalizedUserFunctions: new Set([ + "ReactForgetSecretInternals", + ]), }, compileOptions );