diff options
author | 2023-10-17 19:42:37 -0700 | |
---|---|---|
committer | 2023-10-17 19:42:37 -0700 | |
commit | cb5c4c71c866362dce24eff79251fed6add53e9f (patch) | |
tree | 5635cf21140ff2eac14539316f7c6d6704925bd3 /src/codegen/replacements.ts | |
parent | bf12268274faac1a38d33007be7a48af9e570761 (diff) | |
download | bun-jarred/prepare-for-libuv.tar.gz bun-jarred/prepare-for-libuv.tar.zst bun-jarred/prepare-for-libuv.zip |
Diffstat (limited to 'src/codegen/replacements.ts')
-rw-r--r-- | src/codegen/replacements.ts | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/codegen/replacements.ts b/src/codegen/replacements.ts new file mode 100644 index 000000000..fbe7478de --- /dev/null +++ b/src/codegen/replacements.ts @@ -0,0 +1,179 @@ +import { LoaderKeys } from "../api/schema"; +import { sliceSourceCode } from "./builtin-parser"; + +// This is a list of extra syntax replacements to do. Kind of like macros +// These are only run on code itself, not string contents or comments. +export const replacements: ReplacementRule[] = [ + { from: /\bthrow new TypeError\b/g, to: "$throwTypeError" }, + { from: /\bthrow new RangeError\b/g, to: "$throwRangeError" }, + { from: /\bthrow new OutOfMemoryError\b/g, to: "$throwOutOfMemoryError" }, + { from: /\bnew TypeError\b/g, to: "$makeTypeError" }, + { from: /\bexport\s*default/g, to: "$exports =" }, +]; + +// These rules are run on the entire file, including within strings. +export const globalReplacements: ReplacementRule[] = [ + { + from: /\bnotImplementedIssue\(\s*([0-9]+)\s*,\s*((?:"[^"]*"|'[^']+'))\s*\)/g, + to: "new TypeError(`${$2} is not implemented yet. See https://github.com/oven-sh/bun/issues/$1`)", + }, + { + from: /\bnotImplementedIssueFn\(\s*([0-9]+)\s*,\s*((?:"[^"]*"|'[^']+'))\s*\)/g, + to: "() => $throwTypeError(`${$2} is not implemented yet. See https://github.com/oven-sh/bun/issues/$1`)", + }, +]; + +// This is a list of globals we should access using @ notation +// This prevents a global override attacks. +// Note that the public `Bun` global is immutable. +// undefined -> __intrinsic__undefined -> @undefined +export const globalsToPrefix = [ + "AbortSignal", + "Array", + "ArrayBuffer", + "Buffer", + "Infinity", + "Loader", + "Promise", + "ReadableByteStreamController", + "ReadableStream", + "ReadableStreamBYOBReader", + "ReadableStreamBYOBRequest", + "ReadableStreamDefaultController", + "ReadableStreamDefaultReader", + "TransformStream", + "TransformStreamDefaultController", + "Uint8Array", + "String", + "Buffer", + "RegExp", + "WritableStream", + "WritableStreamDefaultController", + "WritableStreamDefaultWriter", + "isFinite", + "undefined", +]; + +// These enums map to $<enum>IdToLabel and $<enum>LabelToId +// Make sure to define in ./builtins.d.ts +export const enums = { + Loader: LoaderKeys, + ImportKind: [ + "entry-point", + "import-statement", + "require-call", + "dynamic-import", + "require-resolve", + "import-rule", + "url-token", + "internal", + ], +}; + +// These identifiers have typedef but not present at runtime (converted with replacements) +// If they are present in the bundle after runtime, we warn at the user. +// TODO: implement this check. +export const warnOnIdentifiersNotPresentAtRuntime = [ + // + "OutOfMemoryError", + "notImplementedIssue", + "notImplementedIssueFn", +]; + +// These are passed to --define to the bundler +export const define: Record<string, string> = { + "process.env.NODE_ENV": "production", + "IS_BUN_DEVELOPMENT": "false", + + $streamClosed: "1", + $streamClosing: "2", + $streamErrored: "3", + $streamReadable: "4", + $streamWaiting: "5", + $streamWritable: "6", +}; + +// ------------------------------ // + +for (const name in enums) { + const value = enums[name]; + if (typeof value !== "object") throw new Error("Invalid enum object " + name + " defined in " + import.meta.file); + if (typeof value === null) throw new Error("Invalid enum object " + name + " defined in " + import.meta.file); + const keys = Array.isArray(value) ? value : Object.keys(value).filter(k => !k.match(/^[0-9]+$/)); + define[`$${name}IdToLabel`] = "[" + keys.map(k => `"${k}"`).join(", ") + "]"; + define[`$${name}LabelToId`] = "{" + keys.map(k => `"${k}": ${keys.indexOf(k)}`).join(", ") + "}"; +} + +for (const name of globalsToPrefix) { + define[name] = "__intrinsic__" + name; +} + +for (const key in define) { + if (key.startsWith("$")) { + define["__intrinsic__" + key.slice(1)] = define[key]; + delete define[key]; + } +} + +export interface ReplacementRule { + from: RegExp; + to: string; + global?: boolean; +} + +/** Applies source code replacements as defined in `replacements` */ +export function applyReplacements(src: string, length: number) { + let slice = src.slice(0, length); + let rest = src.slice(length); + slice = slice.replace(/([^a-zA-Z0-9_\$])\$([a-zA-Z0-9_]+\b)/gm, `$1__intrinsic__$2`); + for (const replacement of replacements) { + slice = slice.replace(replacement.from, replacement.to.replaceAll("$", "__intrinsic__")); + } + let match; + if ((match = slice.match(/__intrinsic__(debug|assert)$/)) && rest.startsWith("(")) { + const name = match[1]; + if (name === "debug") { + const innerSlice = sliceSourceCode(rest, true); + return [ + slice.slice(0, match.index) + "(IS_BUN_DEVELOPMENT?$debug_log" + innerSlice.result + ":void 0)", + innerSlice.rest, + true, + ]; + } else if (name === "assert") { + const checkSlice = sliceSourceCode(rest, true, undefined, true); + let rest2 = checkSlice.rest; + let extraArgs = ""; + if (checkSlice.result.at(-1) === ",") { + const sliced = sliceSourceCode("(" + rest2.slice(1), true, undefined, false); + extraArgs = ", " + sliced.result.slice(1, -1); + rest2 = sliced.rest; + } + return [ + slice.slice(0, match.index) + + "(IS_BUN_DEVELOPMENT?$assert(" + + checkSlice.result.slice(1, -1) + + "," + + JSON.stringify( + checkSlice.result + .slice(1, -1) + .replace(/__intrinsic__/g, "$") + .trim(), + ) + + extraArgs + + "):void 0)", + rest2, + true, + ]; + } + } + return [slice, rest, false]; +} + +/** Applies source code replacements as defined in `globalReplacements` */ +export function applyGlobalReplacements(src: string) { + let result = src; + for (const replacement of globalReplacements) { + result = result.replace(replacement.from, replacement.to.replaceAll("$", "__intrinsic__")); + } + return result; +} |