Skip to content

fix(runtime): make most globals configurable/deletable, allow resuming the console iterator #5216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bun.js/bindings/CommonJSModuleRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ class JSCommonJSModulePrototype final : public JSC::JSNonFinalObject {
globalObject,
clientData(vm)->builtinNames().requirePrivateName(),
2,
jsFunctionRequireCommonJS, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
jsFunctionRequireCommonJS, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | 0);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/bindings/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ static JSValue constructProcessSend(VM& vm, JSObject* processObject)
if (Bun__GlobalObject__hasIPC(globalObject)) {
return JSC::JSFunction::create(vm, globalObject, 1, String("send"_s), Bun__Process__send, ImplementationVisibility::Public);
} else {
return jsNumber(4);
return jsUndefined();
}
}

Expand Down
532 changes: 234 additions & 298 deletions src/bun.js/bindings/ZigGlobalObject.cpp

Large diffs are not rendered by default.

64 changes: 2 additions & 62 deletions src/bun.js/node/node_fs_constant.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ fn get(comptime name: []const u8) comptime_int {
return if (@hasDecl(std.os.O, name))
return @field(std.os.O, name)
else
return 0;
@compileError("Unknown Constant: " ++ name);
}

pub const Constants = struct {
// File Access Constants
/// Constant for fs.access(). File is visible to the calling process.
Expand Down Expand Up @@ -142,64 +143,3 @@ pub const Constants = struct {
/// this flag is ignored.
pub const UV_FS_O_FILEMAP = 49152;
};

// Due to zig's format support max 32 arguments, we need to split
// here.
const constants_string_format1 =
\\var constants = {{
\\ F_OK: {d},
\\ R_OK: {d},
\\ W_OK: {d},
\\ X_OK: {d},
\\ COPYFILE_EXCL: {d},
\\ COPYFILE_FICLONE: {d},
\\ COPYFILE_FICLONE_FORCE: {d},
\\ O_RDONLY: {d},
\\ O_WRONLY: {d},
\\ O_RDWR: {d},
\\ O_CREAT: {d},
\\ O_EXCL: {d},
\\ O_NOCTTY: {d},
\\ O_TRUNC: {d},
\\ O_APPEND: {d},
\\ O_DIRECTORY: {d},
\\ O_NOATIME: {d},
\\ O_NOFOLLOW: {d},
\\ O_SYNC: {d},
\\ O_DSYNC: {d},
;
const constants_string_format2 =
\\ O_SYMLINK: {s},
\\ O_DIRECT: {d},
\\ O_NONBLOCK: {d},
\\ S_IFMT: {d},
\\ S_IFREG: {d},
\\ S_IFDIR: {d},
\\ S_IFCHR: {d},
\\ S_IFBLK: {d},
\\ S_IFIFO: {d},
\\ S_IFLNK: {d},
\\ S_IFSOCK: {d},
\\ S_IRWXU: {d},
\\ S_IRUSR: {d},
\\ S_IWUSR: {d},
\\ S_IXUSR: {d},
\\ S_IRWXG: {d},
\\ S_IRGRP: {d},
\\ S_IWGRP: {d},
\\ S_IXGRP: {d},
\\ S_IRWXO: {d},
\\ S_IROTH: {d},
\\ S_IWOTH: {d},
\\ S_IXOTH: {d},
\\ UV_FS_O_FILEMAP: {d}
\\}};
\\
;

const constants_string1 = std.fmt.comptimePrint(constants_string_format1, .{ Constants.F_OK, Constants.R_OK, Constants.W_OK, Constants.X_OK, Constants.COPYFILE_EXCL, Constants.COPYFILE_FICLONE, Constants.COPYFILE_FICLONE_FORCE, Constants.O_RDONLY, Constants.O_WRONLY, Constants.O_RDWR, Constants.O_CREAT, Constants.O_EXCL, Constants.O_NOCTTY, Constants.O_TRUNC, Constants.O_APPEND, Constants.O_DIRECTORY, Constants.O_NOATIME, Constants.O_NOFOLLOW, Constants.O_SYNC, Constants.O_DSYNC });

const constants_string2 =
std.fmt.comptimePrint(constants_string_format2, .{ if (@TypeOf(Constants.O_SYMLINK) == void) "undefined" else std.fmt.comptimePrint("{}", .{Constants.O_SYMLINK}), Constants.O_DIRECT, Constants.O_NONBLOCK, Constants.S_IFMT, Constants.S_IFREG, Constants.S_IFDIR, Constants.S_IFCHR, Constants.S_IFBLK, Constants.S_IFIFO, Constants.S_IFLNK, Constants.S_IFSOCK, Constants.S_IRWXU, Constants.S_IRUSR, Constants.S_IWUSR, Constants.S_IXUSR, Constants.S_IRWXG, Constants.S_IRGRP, Constants.S_IWGRP, Constants.S_IXGRP, Constants.S_IRWXO, Constants.S_IROTH, Constants.S_IWOTH, Constants.S_IXOTH, Constants.UV_FS_O_FILEMAP });

pub const constants_string = constants_string1 ++ constants_string2;
26 changes: 4 additions & 22 deletions src/js/_codegen/build-functions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
import path from "path";
import { sliceSourceCode } from "./builtin-parser";
import { applyGlobalReplacements, enums, globalsToPrefix } from "./replacements";
import { applyGlobalReplacements, define } from "./replacements";
import { cap, fmtCPPString, low } from "./helpers";
import { spawn } from "bun";

Expand Down Expand Up @@ -48,24 +48,6 @@ const TMP_DIR = path.join(SRC_DIR, "../out/tmp/builtins");
if (existsSync(TMP_DIR)) rmSync(TMP_DIR, { recursive: true });
mkdirSync(TMP_DIR, { recursive: true });

const define = {
"process.env.NODE_ENV": "production",
"IS_BUN_DEVELOPMENT": "false",
};

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[`__intrinsic__${name}IdToLabel`] = "[" + keys.map(k => `"${k}"`).join(", ") + "]";
define[`__intrinsic__${name}LabelToId`] = "{" + keys.map(k => `"${k}": ${keys.indexOf(k)}`).join(", ") + "}";
}

for (const name of globalsToPrefix) {
define[name] = "__intrinsic__" + name;
}

interface ParsedBuiltin {
name: string;
params: string[];
Expand Down Expand Up @@ -218,7 +200,7 @@ $$capture_start$$(${fn.async ? "async " : ""}${
const build = await Bun.build({
entrypoints: [tmpFile],
define,
minify: { syntax: true, whitespace: true },
minify: { syntax: true, whitespace: false },
});
if (!build.success) {
throw new AggregateError(build.logs, "Failed bundling builtin function " + fn.name + " from " + basename + ".ts");
Expand All @@ -231,7 +213,7 @@ $$capture_start$$(${fn.async ? "async " : ""}${
const finalReplacement =
(fn.directives.sloppy ? captured : captured.replace(/function\s*\(.*?\)\s*{/, '$&"use strict";'))
.replace(/^\((async )?function\(/, "($1function (")
.replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
// .replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
.replace(/__intrinsic__/g, "@") + "\n";

bundledFunctions.push({
Expand Down
4 changes: 3 additions & 1 deletion src/js/_codegen/build-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { cap, fmtCPPString, readdirRecursive, resolveSyncOrNull } from "./helper
import { createAssertClientJS, createLogClientJS } from "./client-js";
import { builtinModules } from "node:module";
import { BuildConfig } from "bun";
import { define } from "./replacements";

const t = new Bun.Transpiler({ loader: "tsx" });

Expand Down Expand Up @@ -173,6 +174,7 @@ const config = ({ platform, debug }: { platform: string; debug?: boolean }) =>
target: "bun",
external: builtinModules,
define: {
...define,
IS_BUN_DEVELOPMENT: String(!!debug),
__intrinsic__debug: debug ? "$debug_log_enabled" : "false",
"process.platform": JSON.stringify(platform),
Expand Down Expand Up @@ -222,7 +224,7 @@ for (const [name, bundle, outputs] of [
.replace(/\$\$EXPORT\$\$\((.*)\).\$\$EXPORT_END\$\$;/, "return $1")
.replace(/]\s*,\s*__(debug|assert)_end__\)/g, ")")
.replace(/]\s*,\s*__debug_end__\)/g, ")")
.replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
// .replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
.replace(/import.meta.require\((.*?)\)/g, (expr, specifier) => {
try {
const str = JSON.parse(specifier);
Expand Down
2 changes: 1 addition & 1 deletion src/js/_codegen/client-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let $debug_log_enabled = ((env) => (
.split(/[-_./]/g)
.join("_")
.toUpperCase()})
))(@Bun.env);
))(Bun.env);
let $debug_log = $debug_log_enabled ? (...args) => {
// warn goes to stderr without colorizing
console.warn(Bun.enableANSIColors ? '\\x1b[90m[${publicName}]\\x1b[0m' : '[${publicName}]', ...args);
Expand Down
38 changes: 37 additions & 1 deletion src/js/_codegen/replacements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ export const globalReplacements: ReplacementRule[] = [
];

// 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",
"Bun",
"Infinity",
"Loader",
"Promise",
Expand Down Expand Up @@ -79,6 +80,41 @@ export const warnOnIdentifiersNotPresentAtRuntime = [
"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;
Expand Down
2 changes: 0 additions & 2 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,6 @@ declare function $isPaused(): TODO;
declare function $isWindows(): TODO;
declare function $join(): TODO;
declare function $kind(): TODO;
declare function $lazy(): TODO;
declare function $lazyLoad(): TODO;
declare function $lazyStreamPrototypeMap(): TODO;
declare function $loadModule(): TODO;
declare function $localStreams(): TODO;
Expand Down
7 changes: 0 additions & 7 deletions src/js/builtins/BunBuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ using namespace JSC;
macro(join) \
macro(kind) \
macro(lazy) \
macro(lazyLoad) \
macro(lazyStreamPrototypeMap) \
macro(loadCJS2ESM) \
macro(localStreams) \
Expand Down Expand Up @@ -211,12 +210,6 @@ using namespace JSC;
macro(strategyHWM) \
macro(strategySizeAlgorithm) \
macro(stream) \
macro(streamClosed) \
macro(streamClosing) \
macro(streamErrored) \
macro(streamReadable) \
macro(streamWaiting) \
macro(streamWritable) \
macro(structuredCloneForStream) \
macro(syscall) \
macro(textDecoderStreamDecoder) \
Expand Down
78 changes: 59 additions & 19 deletions src/js/builtins/ConsoleObject.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
$overriddenName = "[Symbol.asyncIterator]";
export function asyncIterator(this: Console) {
const Iterator = async function* ConsoleAsyncIterator() {
const stream = Bun.stdin.stream();
var reader = stream.getReader();
var stream = Bun.stdin.stream();

// TODO: use builtin
var decoder = new (globalThis as any).TextDecoder("utf-8", { fatal: false }) as TextDecoder;
var deferredError;
var indexOf = Bun.indexOfLine;
var decoder = new TextDecoder("utf-8", { fatal: false });
var indexOf = Bun.indexOfLine;
var actualChunk: Uint8Array;
var i: number = -1;
var idx: number;
var last: number;
var done: boolean;
var value: Uint8Array[];
var value_len: number;
var pendingChunk: Uint8Array | undefined;

async function* ConsoleAsyncIterator() {
var reader = stream.getReader();
var deferredError;
try {
if (i !== -1) {
last = i + 1;
i = indexOf(actualChunk, last);

while (i !== -1) {
yield decoder.decode(actualChunk.subarray(last, i));
last = i + 1;
i = indexOf(actualChunk, last);
}

for (idx++; idx < value_len; idx++) {
actualChunk = value[idx];
if (pendingChunk) {
actualChunk = Buffer.concat([pendingChunk, actualChunk]);
pendingChunk = undefined;
}

last = 0;
// TODO: "\r", 0x4048, 0x4049, 0x404A, 0x404B, 0x404C, 0x404D, 0x404E, 0x404F
i = indexOf(actualChunk, last);
while (i !== -1) {
yield decoder.decode(actualChunk.subarray(last, i));
last = i + 1;
i = indexOf(actualChunk, last);
}
i = -1;

pendingChunk = actualChunk.subarray(last);
}
actualChunk = undefined!;
}

while (true) {
var done, value;
var pendingChunk;
const firstResult = reader.readMany();
if ($isPromise(firstResult)) {
({ done, value } = await firstResult);
Expand All @@ -27,26 +64,29 @@ export function asyncIterator(this: Console) {
return;
}

var actualChunk;
// we assume it was given line-by-line
for (const chunk of value) {
actualChunk = chunk;
for (idx = 0, value_len = value.length; idx < value_len; idx++) {
actualChunk = value[idx];
if (pendingChunk) {
actualChunk = Buffer.concat([pendingChunk, chunk]);
pendingChunk = null;
actualChunk = Buffer.concat([pendingChunk, actualChunk]);
pendingChunk = undefined;
}

var last = 0;
last = 0;
// TODO: "\r", 0x4048, 0x4049, 0x404A, 0x404B, 0x404C, 0x404D, 0x404E, 0x404F
var i = indexOf(actualChunk, last);
i = indexOf(actualChunk, last);
while (i !== -1) {
// This yield may end the function, in that case we need to be able to recover state
// if the iterator was fired up again.
yield decoder.decode(actualChunk.subarray(last, i));
last = i + 1;
i = indexOf(actualChunk, last);
}
i = -1;

pendingChunk = actualChunk.subarray(last);
}
actualChunk = undefined!;
}
} catch (e) {
deferredError = e;
Expand All @@ -57,11 +97,11 @@ export function asyncIterator(this: Console) {
throw deferredError;
}
}
};
}

const symbol = globalThis.Symbol.asyncIterator;
this[symbol] = Iterator;
return Iterator();
this[symbol] = ConsoleAsyncIterator;
return ConsoleAsyncIterator();
}

export function write(this: Console, input) {
Expand Down
Loading