mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Run buffer-crc32's and yazl's capability probes as Node does (#1549)
This commit is contained in:
@@ -28,6 +28,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
...(features.dynTier ? { dynTier: true } : {}),
|
||||
...(features.streamDyn ? { streamDyn: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.dynBufferNum ? { dynBufferNum: true } : {}),
|
||||
events: features.processEvents,
|
||||
emitter: features.emitter,
|
||||
symbol: features.symbol,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { elemAccess, vAdapters } from "./shapes.js";
|
||||
import { LlvmUnsupportedError } from "./unsupported.js";
|
||||
import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js";
|
||||
import { emitUnionWiden } from "./expr-records.js";
|
||||
import { DYN_BUFFER_NUM_METHODS } from "../../ir/runtime-features.js";
|
||||
|
||||
export function emitDynamicExpr(
|
||||
host: LlvmEmitterContext,
|
||||
@@ -226,9 +227,12 @@ export function emitDynamicExpr(
|
||||
});
|
||||
argsPtr = arr;
|
||||
}
|
||||
host.declare(
|
||||
`declare ptr @scr_dyn_invoke_prepared(ptr, ptr, ptr, ptr, ${host.sizeType}, ptr)`,
|
||||
);
|
||||
// Fixed-width Buffer method names take scr_bytes_dyn.c's entry, which
|
||||
// runs them on Buffer receivers and dispatches any other receiver.
|
||||
const invoke = DYN_BUFFER_NUM_METHODS.has(e.method)
|
||||
? "scr_dyn_invoke_buffer_num"
|
||||
: "scr_dyn_invoke_prepared";
|
||||
host.declare(`declare ptr @${invoke}(ptr, ptr, ptr, ptr, ${host.sizeType}, ptr)`);
|
||||
let name = host.cstr(e.calleeName);
|
||||
if (e.calleeNameValue !== undefined) {
|
||||
const value = emitBorrowedInput(host, e.calleeNameValue);
|
||||
@@ -237,7 +241,7 @@ export function emitDynamicExpr(
|
||||
}
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_dyn_invoke_prepared(ptr ${recv.name}, ptr ${prepared}, ptr ${host.cstr(e.method)}, ptr ${argsPtr}, ${host.sizeType} ${args.length}, ptr ${name})`,
|
||||
`${t} = call ptr @${invoke}(ptr ${recv.name}, ptr ${prepared}, ptr ${host.cstr(e.method)}, ptr ${argsPtr}, ${host.sizeType} ${args.length}, ptr ${name})`,
|
||||
);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
|
||||
@@ -444,6 +444,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"buffer.poolFromBytes": "scr_buffer_from_bytes",
|
||||
"buffer.poolFromArray": "scr_buffer_from_arr",
|
||||
"buffer.poolFromDyn": "scr_buffer_from_dyn_pooled",
|
||||
"buffer.poolFromDynRef": "scr_buffer_from_dyn_ref_pooled",
|
||||
"buffer.poolSize": "scr_buffer_pool_size",
|
||||
"buffer.setPoolSize": "scr_buffer_set_pool_size",
|
||||
"buffer.byteLenDyn": "scr_buffer_byte_length_dyn",
|
||||
|
||||
@@ -49,6 +49,9 @@ export interface NativeLinkFeatures {
|
||||
/** events.once with options: scr_events_once_signal.c. Absent (not
|
||||
* false) for programs without it. */
|
||||
eventsOnceSignal?: boolean;
|
||||
/** Fixed-width Buffer methods called on checked-dynamic values link
|
||||
* scr_bytes_dyn.c; absent (false) for programs without them. */
|
||||
dynBufferNum?: boolean;
|
||||
events: boolean;
|
||||
emitter: boolean;
|
||||
symbol: boolean;
|
||||
|
||||
@@ -119,6 +119,9 @@ export interface CcOptions {
|
||||
/** events.once with options (events.onceSignal): compiles
|
||||
* scr_events_once_signal.c. */
|
||||
eventsOnceSignal?: boolean;
|
||||
/** Fixed-width Buffer methods called on checked-dynamic values:
|
||||
* compiles scr_bytes_dyn.c. */
|
||||
dynBufferNum?: boolean;
|
||||
/** The program constructs EventTarget/Event/CustomEvent values or
|
||||
* calls events.addAbortListener (the event.* libCalls), or uses the
|
||||
* native fetch AbortSignal built on the same engine: compiles
|
||||
|
||||
@@ -179,6 +179,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.assert || u.regex || u.opts.symbol, "scr_assert.c"),
|
||||
unit((u) => u.opts.inspect, "scr_inspect.c", "scr_console_native.c"),
|
||||
unit((u) => u.opts.dynInvoke || u.opts.workers || u.nativeFetch, "scr_dyn_invoke.c"),
|
||||
// Fixed-width Buffer methods called on checked-dynamic values.
|
||||
unit((u) => u.opts.dynBufferNum === true && u.opts.dynInvoke, "scr_bytes_dyn.c"),
|
||||
unit((u) => u.opts.dc, "scr_dc.c"),
|
||||
unit((u) => u.opts.dynTier, "scr_js_value.c"),
|
||||
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
dynTier: boolean;
|
||||
streamDyn: boolean;
|
||||
eventsOnceSignal: boolean;
|
||||
dynBufferNum: boolean;
|
||||
warningReplace: boolean;
|
||||
stdioExtended: boolean;
|
||||
nativeFetch: boolean;
|
||||
@@ -279,6 +280,7 @@ export function effectiveRuntimeFeatures(
|
||||
dynTier: features.dynTier === true,
|
||||
streamDyn: features.streamDyn === true,
|
||||
eventsOnceSignal: features.eventsOnceSignal === true,
|
||||
dynBufferNum: features.dynBufferNum === true,
|
||||
warningReplace: features.warningReplace === true,
|
||||
stdioExtended: features.stdioExtended === true,
|
||||
nativeFetch,
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
dynTier?: boolean;
|
||||
streamDyn?: boolean;
|
||||
eventsOnceSignal?: boolean;
|
||||
dynBufferNum?: boolean;
|
||||
events: boolean;
|
||||
emitter: boolean;
|
||||
symbol: boolean;
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
typeEquals,
|
||||
} from "../../../ir/ir.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
import { newFnCtx, own } from "../lowerer.js";
|
||||
import { buildBytesSortFn, closureKeyOrder } from "../lower-array-sort.js";
|
||||
@@ -1306,6 +1307,185 @@ function knownBufferProducer(
|
||||
return type?.kind === "cryptoHash" || type?.kind === "cryptoHmac";
|
||||
}
|
||||
|
||||
/** Buffer.from over a checked-dynamic source and encoding, both already in
|
||||
* repeatable locals: Node's fromString takes utf8 for an absent, non-string
|
||||
* or empty encoding and throws ERR_UNKNOWN_ENCODING for an unknown name;
|
||||
* other sources ignore the encoding. */
|
||||
function dynBufferFromParts(source: IrExpr, encoding: IrExpr, loc: SrcLoc): IrExpr {
|
||||
const utf8: IrExpr = { kind: "strLit", value: "utf8", type: STRING, loc };
|
||||
const isString = (x: IrExpr): IrExpr => ({
|
||||
kind: "dynTest",
|
||||
test: "string",
|
||||
value: x,
|
||||
type: BOOL,
|
||||
loc,
|
||||
});
|
||||
const name: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: {
|
||||
kind: "logical",
|
||||
op: "&&",
|
||||
left: isString(encoding),
|
||||
right: {
|
||||
kind: "strEq",
|
||||
negated: true,
|
||||
left: { kind: "dynCheck", value: encoding, type: STRING, loc },
|
||||
right: { kind: "strLit", value: "", type: STRING, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: { kind: "dynCheck", value: encoding, type: STRING, loc },
|
||||
else_: utf8,
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "ternary",
|
||||
cond: isString(source),
|
||||
then: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromStrChecked",
|
||||
args: [{ kind: "dynCheck", value: source, type: STRING, loc }, name],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
else_: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromDynRef",
|
||||
args: [source, utf8],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Lowers `source` and `encNode` (or undefined) into hidden dyn locals, in
|
||||
* source order, and builds the result from their references. */
|
||||
function withDynBufferArguments(
|
||||
lowerer: Lowerer,
|
||||
source: IrExpr,
|
||||
encNode: ts.Expression | undefined,
|
||||
loc: SrcLoc,
|
||||
build: (source: IrExpr, encoding: IrExpr) => IrExpr,
|
||||
): IrExpr {
|
||||
const v = lowerer.declareHiddenLocal("%bufferValue", DYN);
|
||||
const e = lowerer.declareHiddenLocal("%bufferEncoding", DYN);
|
||||
const result = build(varRef(v.id, DYN, loc), varRef(e.id, DYN, loc));
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: v.id, init: lowerer.coerceToExpected(source, DYN), loc },
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: e.id,
|
||||
init:
|
||||
encNode === undefined
|
||||
? dynUndefinedExpr(loc)
|
||||
: lowerer.coerceToExpected(lowerer.lowerExpr(encNode), DYN),
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result,
|
||||
type: result.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Buffer.from(value, encoding) over a checked-dynamic value whose encoding
|
||||
* is not a literal: yazl forwards its `encoding` parameter, often undefined.
|
||||
* Null when the encoding is absent or a literal. */
|
||||
function dynBufferFromRuntimeEncoding(
|
||||
lowerer: Lowerer,
|
||||
value: IrExpr,
|
||||
encNode: ts.Expression | undefined,
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
if (encNode === undefined || lowerer.typeOf(encNode).isStringLiteralType()) return null;
|
||||
return withDynBufferArguments(lowerer, value, encNode, loc, (source, encoding) =>
|
||||
dynBufferFromParts(source, encoding, loc),
|
||||
);
|
||||
}
|
||||
|
||||
/** The deprecated `new Buffer(arg[, encoding])` in a dependency's
|
||||
* JavaScript, through the global or an alias of it (buffer-crc32 and yazl
|
||||
* keep it as the fallback for runtimes without Buffer.alloc/Buffer.from).
|
||||
* Node never emits DEP0005 for calls from node_modules, so the construction
|
||||
* is exactly Node's: a number allocates zero-filled bytes (and throws
|
||||
* ERR_INVALID_ARG_TYPE beside a string second argument), anything else
|
||||
* converts as Buffer.from does. Null elsewhere: other callers owe the
|
||||
* deprecation warning. */
|
||||
export function lowerNodeModulesBufferConstruct(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.NewExpression,
|
||||
): IrExpr | null {
|
||||
const sf = expr.getSourceFile();
|
||||
if (!isJsSourceFile(sf) || !/[\\/]node_modules[\\/]/.test(sf.fileName)) return null;
|
||||
const args = expr.arguments?.nodes ?? [];
|
||||
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) return null;
|
||||
if (stdlibGlobalNameOf(lowerer, expr.expression) !== "Buffer") return null;
|
||||
const loc = locOf(expr);
|
||||
const value = withDynBufferArguments(
|
||||
lowerer,
|
||||
lowerer.lowerExpr(args[0]!),
|
||||
args[1],
|
||||
loc,
|
||||
(source, encoding) => ({
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "number", value: source, type: BOOL, loc },
|
||||
then: {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "string", value: encoding, type: BOOL, loc },
|
||||
then: { kind: "libCall", fn: "buffer.newStringFail", args: [source], type: BYTES_U8, loc },
|
||||
else_: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.alloc",
|
||||
args: [{ kind: "dynCheck", value: source, type: F64, loc }],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
else_: dynBufferFromParts(source, encoding, loc),
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
}),
|
||||
);
|
||||
return { kind: "libCall", fn: "buffer.brand", args: [value], type: BYTES_U8, loc };
|
||||
}
|
||||
|
||||
/** Buffer.from(array or bytes, extra): Node copies an array-like source and
|
||||
* ignores the encoding/offset argument (yazl passes its `encoding` through
|
||||
* unchanged), but JavaScript still evaluates it after the source. */
|
||||
function withIgnoredBufferArgument(
|
||||
lowerer: Lowerer,
|
||||
extraNode: ts.Expression | undefined,
|
||||
source: IrExpr,
|
||||
build: (source: IrExpr) => IrExpr,
|
||||
): IrExpr {
|
||||
if (extraNode === undefined) return build(source);
|
||||
const extra = lowerer.lowerExpr(extraNode);
|
||||
if (isSafeToDiscard(extra)) return build(source);
|
||||
const loc = source.loc;
|
||||
const tmp = lowerer.declareHiddenLocal("%bufferSource", source.type);
|
||||
const result = build(varRef(tmp.id, source.type, loc));
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: tmp.id, init: source, loc },
|
||||
{ kind: "exprStmt", expr: extra, loc },
|
||||
],
|
||||
result,
|
||||
type: result.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
export function lowerBufferStaticCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
@@ -1378,17 +1558,23 @@ function lowerBufferStaticValue(
|
||||
) {
|
||||
// A number[] literal (contextually typed by the lib's readonly
|
||||
// number[] slot — build it element-wise, the Set-seed pattern).
|
||||
if (args.length === 1) {
|
||||
const elems = argNode.elements.nodes.map((el) => lowerer.lowerExprExpecting(el, F64));
|
||||
const seed: IrExpr = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
||||
return { kind: "libCall", fn: "buffer.poolFromArray", args: [seed], type: BYTES_U8, loc };
|
||||
}
|
||||
const elems = argNode.elements.nodes.map((el) => lowerer.lowerExprExpecting(el, F64));
|
||||
const seed: IrExpr = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
||||
return withIgnoredBufferArgument(lowerer, args[1], seed, (array) => ({
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromArray",
|
||||
args: [array],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
}));
|
||||
} else {
|
||||
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
||||
const value = lowerer.lowerExpr(argNode);
|
||||
// JS callback storage can be native DYN even when the checker
|
||||
// reports any (or a narrowed Buffer). Dispatch on its actual ABI.
|
||||
if (value.type.kind === "dyn") {
|
||||
const runtimeEncoding = dynBufferFromRuntimeEncoding(lowerer, value, args[1], loc);
|
||||
if (runtimeEncoding) return runtimeEncoding;
|
||||
const encName = args[1] ? bufEncoding(lowerer, "Buffer.from", args[1]) : "utf8";
|
||||
const enc: IrExpr = { kind: "strLit", value: encName, type: STRING, loc };
|
||||
return {
|
||||
@@ -1432,26 +1618,25 @@ function lowerBufferStaticValue(
|
||||
// { toText: true } ? string : Uint8Array` result) whose lowered
|
||||
// value is a u8 view copies like the typed form.
|
||||
if (
|
||||
args.length === 1 &&
|
||||
((srcIr?.kind === "bytes" && srcIr.elem === "u8") ||
|
||||
(srcIr === null && value.type.kind === "bytes" && value.type.elem === "u8"))
|
||||
(srcIr?.kind === "bytes" && srcIr.elem === "u8") ||
|
||||
(srcIr === null && value.type.kind === "bytes" && value.type.elem === "u8")
|
||||
) {
|
||||
return {
|
||||
return withIgnoredBufferArgument(lowerer, args[1], value, (bytes) => ({
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromBytes",
|
||||
args: [value],
|
||||
args: [bytes],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}));
|
||||
}
|
||||
if (args.length === 1 && srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
|
||||
return {
|
||||
if (srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
|
||||
return withIgnoredBufferArgument(lowerer, args[1], value, (array) => ({
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromArray",
|
||||
args: [value],
|
||||
args: [array],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}));
|
||||
}
|
||||
// A union of strings, u8 views and undefined/null (a JS-inferred
|
||||
// npm result such as pako's deflate): the checked form dispatches
|
||||
|
||||
@@ -100,7 +100,7 @@ import { isAssertClassNew } from "./assert-instances.js";
|
||||
import { lowerArrayConstructor } from "./containers/array-construction.js";
|
||||
import { lowerMapSeedNew, lowerSetSeedNew } from "./containers/collection-methods.js";
|
||||
import { lowerObjectConstructor } from "./containers/indexed-objects.js";
|
||||
import { knownBufEncoding } from "./containers/bytes.js";
|
||||
import { knownBufEncoding, lowerNodeModulesBufferConstruct } from "./containers/bytes.js";
|
||||
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { lowerSearchParamsNew } from "./builtins/url.js";
|
||||
import { lowerTextCodecNew } from "./builtins/text-codecs.js";
|
||||
@@ -9204,6 +9204,11 @@ function isCollectionOrErrorClass(ref: BuiltinClassRef | null): boolean {
|
||||
|
||||
export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
// A dependency's deprecated `new Buffer(...)` fallback (containers/bytes.ts).
|
||||
{
|
||||
const buffer = lowerNodeModulesBufferConstruct(lowerer, expr);
|
||||
if (buffer) return buffer;
|
||||
}
|
||||
// new Function(...): Node's EvalError under
|
||||
// --disallow-code-generation-from-strings (code-generation.ts).
|
||||
if (codeGenerationCalleeOf(lowerer, expr.expression) === "Function")
|
||||
|
||||
@@ -2,6 +2,7 @@ import { declareTypedArguments } from "./typed-arguments.js";
|
||||
import { varRef } from "../../ir/build.js";
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { isRebindableFunctionSymbol } from "./rebindable-functions.js";
|
||||
import { bodyReadsArguments } from "../arguments-usage.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import {
|
||||
@@ -59,6 +60,8 @@ function declareCallReceiver(
|
||||
* same collection lists top-level declarations ride. */
|
||||
export function isTopLevelFnSymbol(lowerer: Lowerer, ident: ts.Identifier): boolean {
|
||||
const symbol = lowerer.resolveValueSymbol(ident);
|
||||
// A reassigned JavaScript declaration is read through its module global.
|
||||
if (isRebindableFunctionSymbol(lowerer, symbol ?? undefined)) return false;
|
||||
if (symbol && lowerer.staticCallables.get(symbol)?.kind === "declared-function") return true;
|
||||
const decl = symbol ? symbol.declarations[0] : undefined;
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
* seeds), npm/JSON import collection, per-file %init functions, %main, and
|
||||
* the module artifacts (globals, embedded npm tables) the IR module carries. */
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { collectRebindableFunctions, isRebindableFunction } from "./rebindable-functions.js";
|
||||
import { bindingSource } from "../binding-source.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { dirname as dirnamePath, resolve as resolvePath } from "node:path";
|
||||
@@ -246,8 +247,12 @@ export function splitFiles(lowerer: Lowerer): FileParts[] {
|
||||
const files = lowerer.moduleOrder.length > 0 ? lowerer.moduleOrder : [lowerer.entry];
|
||||
return files.map((sf) => {
|
||||
const fp: FileParts = { sf, fnDecls: [], classDecls: [], topStmts: [] };
|
||||
const rebindable = collectRebindableFunctions(lowerer, sf);
|
||||
for (const stmt of sf.statements.nodes) {
|
||||
if (ts.isFunctionDeclaration(stmt)) fp.fnDecls.push(stmt);
|
||||
// A reassigned JavaScript declaration lives in a module global that
|
||||
// the init body assigns first (rebindable-functions.ts).
|
||||
if (ts.isFunctionDeclaration(stmt) && rebindable.has(stmt)) fp.topStmts.push(stmt);
|
||||
else if (ts.isFunctionDeclaration(stmt)) fp.fnDecls.push(stmt);
|
||||
else if (ts.isClassDeclaration(stmt)) fp.classDecls.push(stmt);
|
||||
// Namespaces: ambient/type-only ones are zero-runtime and skip;
|
||||
// instantiated bodies FLATTEN into this file's parts (functions/
|
||||
@@ -1274,6 +1279,21 @@ export function collectGlobals(
|
||||
// collected earlier in this pass must resolve them (lower-expando.ts).
|
||||
collectExpandoMembers(lowerer, sf);
|
||||
for (const stmt of topStmts) {
|
||||
if (ts.isFunctionDeclaration(stmt) && stmt.name && isRebindableFunction(lowerer, stmt)) {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(stmt.name);
|
||||
if (symbol && !lowerer.globalsBySymbol.has(symbol)) {
|
||||
const g: IrGlobal = {
|
||||
id: `%g.${tag}${stmt.name.text}`,
|
||||
name: stmt.name.text,
|
||||
type: DYN,
|
||||
mutable: true,
|
||||
};
|
||||
lowerer.globalsBySymbol.set(symbol, g);
|
||||
lowerer.globalsByDeclNode.set(stmt, g);
|
||||
lowerer.globalsList.push(g);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// `export default <expr>`: the module's `default` binding is a const
|
||||
// module global (registered under the checker's default-export
|
||||
// symbol — the symbol a default IMPORT's alias chain terminates at,
|
||||
@@ -2836,6 +2856,23 @@ export function lowerFileInit(
|
||||
loc: loc0,
|
||||
});
|
||||
}
|
||||
// Reassigned function declarations are initialized when the module
|
||||
// starts, as declarations are hoisted: before its dependencies run, so
|
||||
// a module of an import cycle can call one while it initializes. Their
|
||||
// statements then lower to nothing (rebindable-functions.ts).
|
||||
for (const stmt of stmts) {
|
||||
if (!ts.isFunctionDeclaration(stmt) || !stmt.name || !isRebindableFunction(lowerer, stmt))
|
||||
continue;
|
||||
const g = lowerer.globalsByDeclNode.get(stmt);
|
||||
if (!g) continue;
|
||||
header.push({
|
||||
kind: "assign",
|
||||
localId: g.id,
|
||||
value: lowerer.coerceToExpected(lowerer.lowerLambda(stmt), DYN),
|
||||
loc: locOf(stmt),
|
||||
});
|
||||
ctx.hoistedFnDecls.add(stmt);
|
||||
}
|
||||
for (const { stmt, dep } of orderedImportsOf(lowerer.program, sf)) {
|
||||
const npm = lowerer.npmInitActions.get(sf)?.get(stmt);
|
||||
if (npm !== undefined && npm.length > 0) {
|
||||
|
||||
@@ -5917,11 +5917,44 @@ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclar
|
||||
writes = indexed;
|
||||
lowerer.jsBindingWritesByOwner.set(owner, writes);
|
||||
}
|
||||
return (writes.get(decl.name.text) ?? []).some(
|
||||
(node) =>
|
||||
lowerer.resolveValueSymbol(node.left as ts.Identifier) === symbol &&
|
||||
(lowerer.typeOf(node.right).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0,
|
||||
const candidates = (writes.get(decl.name.text) ?? []).filter(
|
||||
(node) => lowerer.resolveValueSymbol(node.left as ts.Identifier) === symbol,
|
||||
);
|
||||
if (
|
||||
candidates.some(
|
||||
(node) =>
|
||||
(lowerer.typeOf(node.right).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0,
|
||||
)
|
||||
)
|
||||
return true;
|
||||
// A plain JavaScript write of a value the declared type cannot hold (the
|
||||
// checker does not report it): buffer-crc32 replaces its number[] table
|
||||
// with `new Int32Array(table)`. Only a different representation counts;
|
||||
// writes that coerce into the declared type keep it.
|
||||
// A write whose checker type is the declaration's own maps to the same
|
||||
// representation; the declared type is mapped only for the others.
|
||||
const declaredType = lowerer.typeOf(decl.name);
|
||||
const differing = candidates.filter(
|
||||
(node) =>
|
||||
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
||||
lowerer.typeOf(node.right) !== declaredType,
|
||||
);
|
||||
if (differing.length === 0) return false;
|
||||
const declared = lowerer.mapTypeOf(declaredType);
|
||||
if (declared === null || declared.kind === "dyn" || declared.kind === "union") return false;
|
||||
return differing.some((node) => {
|
||||
const written = lowerer.mapTypeOf(lowerer.typeOf(node.right));
|
||||
return (
|
||||
written !== null &&
|
||||
written.kind !== "dyn" &&
|
||||
written.kind !== "union" &&
|
||||
!isUnitType(written) &&
|
||||
lowerer.dynConvertible(written) &&
|
||||
lowerer.dynConvertible(declared) &&
|
||||
!typeEquals(written, declared) &&
|
||||
(written.kind !== declared.kind || written.kind === "bytes")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function immediatelyGuardedAbsenceProbe(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean {
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
isOptionalProcessVersionsRead,
|
||||
} from "./builtins/process.js";
|
||||
import { isSettledIteratorValueRead } from "./lower-generators.js";
|
||||
import { isRebindableFunctionSymbol } from "./rebindable-functions.js";
|
||||
import { concreteCollectionNarrow } from "./collection-narrowing.js";
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import { fenceStaticBlobMember, lowerBlobNew, lowerStaticBlobCall } from "./builtins/blob.js";
|
||||
@@ -1851,6 +1852,9 @@ export class Lowerer {
|
||||
/** Syntactic writes indexed once per JS scope. Symbol and RHS type
|
||||
* checks remain contextual to each binding probe. */
|
||||
readonly jsBindingWritesByOwner = new Map<ts.Node, Map<string, ts.BinaryExpression[]>>();
|
||||
/** JavaScript function declarations their own module reassigns
|
||||
* (rebindable-functions.ts). */
|
||||
readonly rebindableFunctions = new Set<ts.FunctionDeclaration>();
|
||||
/** TRAP bindings: declarations whose initializer provably throws before
|
||||
* producing a value (its chain roots at an ambient-undefined name —
|
||||
* ambientUndefVarRootOf). Module init unwinds at the declaration, so no
|
||||
@@ -11687,6 +11691,9 @@ export class Lowerer {
|
||||
isSelfReference(ident: ts.Identifier): boolean {
|
||||
const ctx = this.ctx;
|
||||
if (!ctx.selfSymbol) return false;
|
||||
// A reassigned JavaScript declaration names its module global, which
|
||||
// may hold another function by the time the body runs.
|
||||
if (isRebindableFunctionSymbol(this, ctx.selfSymbol)) return false;
|
||||
// The self binding can be shadowed by a scoped local of the same symbol?
|
||||
// No — a shadow is a different symbol; symbol identity is exact.
|
||||
return this.checker.getSymbolAtLocation(ident) === ctx.selfSymbol;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* JavaScript function declarations that their own module reassigns.
|
||||
*
|
||||
* Capability probes in published packages replace a function declaration
|
||||
* with the implementation that works on the first call (yazl's
|
||||
* `function bufferFrom(...) { bufferFrom = modern; ... }`). A top-level
|
||||
* declaration normally lowers to a static function, which cannot change.
|
||||
* A reassigned one instead becomes a mutable module global holding the
|
||||
* function as a checked-dynamic value: the module initializer assigns it
|
||||
* before its first statement (the declaration's hoisting), and reads,
|
||||
* calls and writes of the name all go through the global. TypeScript
|
||||
* rejects such writes, so only JavaScript sources take this path. */
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { isJsSourceFile } from "../program.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
|
||||
/** Top-level function declarations of `sf` that an assignment anywhere in
|
||||
* the file writes, recorded for the queries below. */
|
||||
export function collectRebindableFunctions(
|
||||
lowerer: Lowerer,
|
||||
sf: ts.SourceFile,
|
||||
): Set<ts.FunctionDeclaration> {
|
||||
const found = new Set<ts.FunctionDeclaration>();
|
||||
if (!isJsSourceFile(sf)) return found;
|
||||
const byName = new Map<string, ts.FunctionDeclaration>();
|
||||
for (const stmt of sf.statements.nodes) {
|
||||
if (
|
||||
ts.isFunctionDeclaration(stmt) &&
|
||||
stmt.name &&
|
||||
stmt.body &&
|
||||
stmt.typeParameters === undefined
|
||||
)
|
||||
byName.set(stmt.name.text, stmt);
|
||||
}
|
||||
if (byName.size === 0) return found;
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (
|
||||
!ts.isBinaryExpression(node) ||
|
||||
node.operatorToken.kind !== ts.SyntaxKind.EqualsToken ||
|
||||
!ts.isIdentifier(node.left)
|
||||
)
|
||||
return undefined;
|
||||
const decl = byName.get(node.left.text);
|
||||
if (!decl || found.has(decl)) return undefined;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(node.left);
|
||||
if (symbol && symbol.declarations.includes(decl)) found.add(decl);
|
||||
return undefined;
|
||||
});
|
||||
for (const decl of found) lowerer.rebindableFunctions.add(decl);
|
||||
return found;
|
||||
}
|
||||
|
||||
export function isRebindableFunction(lowerer: Lowerer, decl: ts.Node | undefined): boolean {
|
||||
return (
|
||||
decl !== undefined && ts.isFunctionDeclaration(decl) && lowerer.rebindableFunctions.has(decl)
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the symbol names a reassigned function declaration. */
|
||||
export function isRebindableFunctionSymbol(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol | undefined,
|
||||
): boolean {
|
||||
return symbol !== undefined && symbol.declarations.some((d) => isRebindableFunction(lowerer, d));
|
||||
}
|
||||
@@ -547,6 +547,7 @@ async function compileExecutableNative(
|
||||
...(features.dynTier ? { dynTier: true } : {}),
|
||||
...(features.streamDyn ? { streamDyn: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.dynBufferNum ? { dynBufferNum: true } : {}),
|
||||
events: features.events,
|
||||
emitter: features.emitter,
|
||||
symbol: features.symbol,
|
||||
|
||||
@@ -769,6 +769,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"buffer.poolFromBytes",
|
||||
"buffer.poolFromArray",
|
||||
"buffer.poolFromDyn",
|
||||
"buffer.poolFromDynRef",
|
||||
// The checked-dynamic compare/equals validators: Node's argument
|
||||
// ladders throw ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE catchably.
|
||||
"buffer.compareChk",
|
||||
|
||||
@@ -2879,6 +2879,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 },
|
||||
"buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 },
|
||||
"buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 },
|
||||
/** poolFromDyn that also copies an array crossing by reference. */
|
||||
"buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 },
|
||||
"buffer.poolSize": { argTypes: [], result: F64 },
|
||||
"buffer.setPoolSize": { argTypes: [F64], result: VOID },
|
||||
/** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like
|
||||
|
||||
@@ -9,6 +9,27 @@ import {
|
||||
} from "./ir.js";
|
||||
import { everyModuleNode } from "./traverse.js";
|
||||
|
||||
/** The fixed-width Buffer read/write method names (both "UInt" and "Uint"
|
||||
* spellings) a checked-dynamic method call routes to scr_bytes_dyn.c. */
|
||||
export const DYN_BUFFER_NUM_METHODS: ReadonlySet<string> = new Set(
|
||||
["read", "write"].flatMap((rw) => [
|
||||
...["UInt", "Uint"].flatMap((u) =>
|
||||
["8", "16LE", "16BE", "32LE", "32BE"].map((w) => `${rw}${u}${w}`),
|
||||
),
|
||||
...[
|
||||
"Int8",
|
||||
"Int16LE",
|
||||
"Int16BE",
|
||||
"Int32LE",
|
||||
"Int32BE",
|
||||
"FloatLE",
|
||||
"FloatBE",
|
||||
"DoubleLE",
|
||||
"DoubleBE",
|
||||
].map((n) => `${rw}${n}`),
|
||||
]),
|
||||
);
|
||||
|
||||
const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
// DEP0040 rides the dyn-async unit's warning machinery.
|
||||
"punycode.warn",
|
||||
@@ -573,6 +594,11 @@ const RUNTIME_FEATURE_RULES = {
|
||||
streamDyn: rule({
|
||||
fns: ["stream.dynNative", "stream.dynNativePipe", "stream.pipeDyn", "stream.unpipeDyn"],
|
||||
}),
|
||||
/** Fixed-width Buffer read/write methods called on checked-dynamic
|
||||
* values (scr_bytes_dyn.c). */
|
||||
dynBufferNum: rule({
|
||||
expr: (node, _tables) => node.kind === "dynInvoke" && DYN_BUFFER_NUM_METHODS.has(node.method),
|
||||
}),
|
||||
/** The stream unit emits through the emitter unit, also when the
|
||||
* program constructs no stream (a pipeline of functions). */
|
||||
emitter: rule({
|
||||
@@ -756,6 +782,7 @@ function noRuntimeFeatures(): RuntimeFeatures {
|
||||
stdioExtended: false,
|
||||
stream: false,
|
||||
streamDyn: false,
|
||||
dynBufferNum: false,
|
||||
symbol: false,
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
|
||||
@@ -79,6 +79,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
|
||||
dynTier: false,
|
||||
streamDyn: false,
|
||||
eventsOnceSignal: false,
|
||||
dynBufferNum: false,
|
||||
warningReplace: false,
|
||||
stdioExtended: false,
|
||||
regex: true,
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6560-npm-js-capability-probes/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6565-rebindable-function-import-cycle/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -61,6 +61,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
["scr_inspect.c", "inspect"],
|
||||
["scr_console_native.c", "inspect"],
|
||||
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")],
|
||||
// Fixed-width Buffer methods called on checked-dynamic values.
|
||||
["scr_bytes_dyn.c", all("dynBufferNum", "dynInvoke")],
|
||||
["scr_dc.c", "dc"],
|
||||
// The dynamic tier's jsval values (two-tier design I3): linked only into
|
||||
// programs whose IR has dynamic-tier operations.
|
||||
|
||||
@@ -389,6 +389,7 @@ ScrBytes *scr_buffer_from_str_checked(const ScrStr *s, const ScrStr *enc);
|
||||
ScrBytes *scr_buffer_from_bytes(const ScrBytes *src); /* a copy of any typed array's values */
|
||||
ScrBytes *scr_buffer_from_arr(const ScrArr *arr); /* number[] */
|
||||
ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding);
|
||||
ScrBytes *scr_buffer_from_dyn_ref_pooled(const ScrDyn *value, const ScrStr *encoding);
|
||||
ScrBytes *scr_buffer_concat(const ScrArr *list);
|
||||
ScrBytes *scr_buffer_concat_len(const ScrArr *list, double total);
|
||||
/* The untyped-list forms: Node's ERR_INVALID_ARG_TYPE checks on the list
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* The fixed-width read/write methods of a Buffer held as a checked-dynamic
|
||||
* value: a JavaScript dependency's `buf.writeUInt32LE(v, 0)` on a buffer it
|
||||
* got back untyped (yazl). The compiler routes only the dyn method calls
|
||||
* that name one of these methods here, so programs without them link
|
||||
* nothing new; any other receiver takes the ordinary dispatch. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
ScrBytesNumKind kind;
|
||||
bool le;
|
||||
} scr_bytes_dyn_names[] = {
|
||||
{ "UInt8", SCR_BN_U8, false }, { "Uint8", SCR_BN_U8, false },
|
||||
{ "Int8", SCR_BN_I8, false }, { "UInt16LE", SCR_BN_U16, true },
|
||||
{ "Uint16LE", SCR_BN_U16, true }, { "UInt16BE", SCR_BN_U16, false },
|
||||
{ "Uint16BE", SCR_BN_U16, false }, { "Int16LE", SCR_BN_I16, true },
|
||||
{ "Int16BE", SCR_BN_I16, false }, { "UInt32LE", SCR_BN_U32, true },
|
||||
{ "Uint32LE", SCR_BN_U32, true }, { "UInt32BE", SCR_BN_U32, false },
|
||||
{ "Uint32BE", SCR_BN_U32, false }, { "Int32LE", SCR_BN_I32, true },
|
||||
{ "Int32BE", SCR_BN_I32, false }, { "FloatLE", SCR_BN_F32, true },
|
||||
{ "FloatBE", SCR_BN_F32, false }, { "DoubleLE", SCR_BN_F64, true },
|
||||
{ "DoubleBE", SCR_BN_F64, false },
|
||||
};
|
||||
|
||||
static size_t scr_bytes_dyn_width(ScrBytesNumKind kind) {
|
||||
switch (kind) {
|
||||
case SCR_BN_U8:
|
||||
case SCR_BN_I8:
|
||||
return 1;
|
||||
case SCR_BN_U16:
|
||||
case SCR_BN_I16:
|
||||
return 2;
|
||||
case SCR_BN_F64:
|
||||
return 8;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Node's checkInt range test and message, before the offset is looked at. */
|
||||
static bool scr_bytes_dyn_value_in_range(double value, size_t width, bool sign) {
|
||||
double max = sign ? exp2((double)(8 * width - 1)) - 1 : exp2((double)(8 * width)) - 1;
|
||||
double min = sign ? -exp2((double)(8 * width - 1)) : 0;
|
||||
if (!(value > max || value < min)) return true;
|
||||
char recv[48];
|
||||
scr_num_received(value, recv);
|
||||
char minb[32], maxb[32];
|
||||
scr_f64_to_str(min, minb);
|
||||
scr_f64_to_str(max, maxb);
|
||||
char msg[160];
|
||||
int mlen = snprintf(msg, sizeof msg,
|
||||
"The value of \"value\" is out of range. It must be >= %s and <= %s. Received %s",
|
||||
minb, maxb, recv);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Node's order: a write converts the value with ToNumber and checks its
|
||||
* range, then the offset must be a number (absent means 0), then the
|
||||
* bounds. +1, or NULL with the exception pending. */
|
||||
static ScrDyn *scr_bytes_dyn_num_method(ScrBytes *b, bool read, size_t at, ScrDyn *const *args,
|
||||
size_t argc) {
|
||||
ScrBytesNumKind kind = scr_bytes_dyn_names[at].kind;
|
||||
double value = 0;
|
||||
if (!read) {
|
||||
if (!scr_dyn_number_coerce_js(argc > 0 ? args[0] : scr_dyn_undefined(), &value)) return NULL;
|
||||
if (kind != SCR_BN_F32 && kind != SCR_BN_F64) {
|
||||
bool sign = kind == SCR_BN_I8 || kind == SCR_BN_I16 || kind == SCR_BN_I32;
|
||||
if (!scr_bytes_dyn_value_in_range(value, scr_bytes_dyn_width(kind), sign)) return NULL;
|
||||
}
|
||||
}
|
||||
size_t offset_at = read ? 0 : 1;
|
||||
double offset = 0;
|
||||
if (argc > offset_at && args[offset_at]->kind != SCR_DYN_UNDEF) {
|
||||
if (args[offset_at]->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail("offset", "of type number", args[offset_at]);
|
||||
return NULL;
|
||||
}
|
||||
offset = args[offset_at]->v.num;
|
||||
}
|
||||
bool le = scr_bytes_dyn_names[at].le;
|
||||
double out = read ? scr_bytes_read_num(b, offset, kind, le) : scr_bytes_write_num(b, value, offset, kind, le);
|
||||
return scr_exc_pending() ? NULL : scr_dyn_new_num(out);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args,
|
||||
size_t argc, const char *what) {
|
||||
if (recv->kind == SCR_DYN_BYTES && recv->buffer && !recv->v.bytes->is_data_view) {
|
||||
bool read = !strncmp(method, "read", 4);
|
||||
const char *rest = method + (read ? 4 : 5);
|
||||
for (size_t at = 0; at < sizeof scr_bytes_dyn_names / sizeof scr_bytes_dyn_names[0]; at++) {
|
||||
if (!strcmp(rest, scr_bytes_dyn_names[at].name))
|
||||
return scr_bytes_dyn_num_method(recv->v.bytes, read, at, args, argc);
|
||||
}
|
||||
}
|
||||
return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what);
|
||||
}
|
||||
@@ -1350,6 +1350,24 @@ ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding
|
||||
return scr_buffer_pool_adopt(out);
|
||||
}
|
||||
|
||||
/* scr_buffer_from_dyn_pooled for values that can be a typed array crossing
|
||||
* an untyped slot by reference (a number[] passed to an untyped parameter,
|
||||
* as yazl's Buffer.from(something, encoding) receives it): the array copies
|
||||
* like the array it is. Used by the runtime-encoding Buffer.from forms. */
|
||||
ScrBytes *scr_buffer_from_dyn_ref_pooled(const ScrDyn *value, const ScrStr *encoding) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(value) != NULL) {
|
||||
ScrDyn *materialized = scr_dyn_typed_ref_materialize(value);
|
||||
if (scr_exc_pending()) {
|
||||
scr_dyn_release(materialized);
|
||||
return NULL;
|
||||
}
|
||||
ScrBytes *out = scr_buffer_from_dyn_pooled(materialized, encoding);
|
||||
scr_dyn_release(materialized);
|
||||
return out;
|
||||
}
|
||||
return scr_buffer_from_dyn_pooled(value, encoding);
|
||||
}
|
||||
|
||||
/* ── the checked-dynamic Buffer compare/equals validators ──────────────
|
||||
* Node's argument ladders for buf.equals / buf.compare / Buffer.compare
|
||||
* over dyn-boxed arguments (the invalid-input probes: string needles,
|
||||
|
||||
@@ -1134,6 +1134,10 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b);
|
||||
* result owned (+1). MAY THROW (NULL with the exception pending). */
|
||||
ScrDyn *scr_dyn_prepare_method(ScrDyn *recv, const char *method);
|
||||
ScrDyn *scr_dyn_invoke_prepared(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||||
/* scr_dyn_invoke_prepared for the fixed-width Buffer read/write method names
|
||||
* (scr_bytes_dyn.c): a Buffer receiver runs the method, any other takes the
|
||||
* ordinary dispatch. */
|
||||
ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||||
ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||||
/* Keyed read on a FUNC node: the own-property table first (defineProperties
|
||||
* writes land there), then "name" (the box's best-effort static name; ""
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// JavaScript dependency forms from bundled zip packages, which run as Node
|
||||
// runs them:
|
||||
// - a var table replaced by an Int32Array view of itself (buffer-crc32);
|
||||
// - function declarations that redefine themselves on the first call, and
|
||||
// Buffer.from over an array with an undefined encoding (yazl);
|
||||
// - the deprecated `new Buffer(...)` fallback both keep, and the Buffer
|
||||
// methods called on the untyped buffers they return.
|
||||
import { crc32, localHeader, signature, legacy } from 'zip-helpers';
|
||||
|
||||
console.log(crc32('hello'), crc32(Buffer.from([1, 2, 3])), crc32('world', 17), crc32(4));
|
||||
console.log(Array.from(localHeader('a.txt', 9)).join(','));
|
||||
console.log(Array.from(localHeader('b', 1)).join(','));
|
||||
console.log(Array.from(signature()).join(','));
|
||||
console.log(legacy());
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export declare function crc32(input: Uint8Array | string | number, previous?: number): number;
|
||||
export declare function localHeader(name: string, size: number): Uint8Array;
|
||||
export declare function signature(): Uint8Array;
|
||||
export declare function legacy(): string;
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
// Small parts of buffer-crc32 (MIT) and yazl (MIT), in their published
|
||||
// shapes: a var table replaced by an Int32Array view of itself, and
|
||||
// function declarations that replace themselves with the implementation
|
||||
// that works on the first call.
|
||||
'use strict';
|
||||
var Buffer2 = require('buffer').Buffer;
|
||||
var CRC_TABLE = [
|
||||
0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035,
|
||||
249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049,
|
||||
498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639,
|
||||
325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317,
|
||||
997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443,
|
||||
901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665,
|
||||
651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303,
|
||||
671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565,
|
||||
1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059,
|
||||
2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297,
|
||||
1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223,
|
||||
1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405,
|
||||
1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995,
|
||||
1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649,
|
||||
1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015,
|
||||
1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989,
|
||||
3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523,
|
||||
3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377,
|
||||
4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879,
|
||||
4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637,
|
||||
3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859,
|
||||
3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161,
|
||||
3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815,
|
||||
3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221,
|
||||
2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371,
|
||||
2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881,
|
||||
2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567,
|
||||
2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701,
|
||||
2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035,
|
||||
2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897,
|
||||
3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431,
|
||||
3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117
|
||||
];
|
||||
typeof Int32Array < 'u' && (CRC_TABLE = new Int32Array(CRC_TABLE));
|
||||
|
||||
function ensureBuffer(input) {
|
||||
if (Buffer2.isBuffer(input)) return input;
|
||||
var hasNewBufferAPI = typeof Buffer2.alloc == 'function' && typeof Buffer2.from == 'function';
|
||||
if (typeof input == 'number') return hasNewBufferAPI ? Buffer2.alloc(input) : new Buffer2(input);
|
||||
if (typeof input == 'string') return hasNewBufferAPI ? Buffer2.from(input) : new Buffer2(input);
|
||||
throw new Error('input must be buffer, number, or string, received ' + typeof input);
|
||||
}
|
||||
|
||||
function crc32(buf, previous) {
|
||||
buf = ensureBuffer(buf);
|
||||
for (var crc = ~~previous ^ -1, n = 0; n < buf.length; n++) crc = CRC_TABLE[(crc ^ buf[n]) & 255] ^ (crc >>> 8);
|
||||
return (crc ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
function bufferFrom(something, encoding) {
|
||||
bufferFrom = modern;
|
||||
try {
|
||||
return bufferFrom(something, encoding);
|
||||
} catch {
|
||||
return (bufferFrom = legacy), bufferFrom(something, encoding);
|
||||
}
|
||||
function modern(something2, encoding2) {
|
||||
return Buffer.from(something2, encoding2);
|
||||
}
|
||||
function legacy(something2, encoding2) {
|
||||
return new Buffer(something2, encoding2);
|
||||
}
|
||||
}
|
||||
|
||||
function bufferAlloc(size) {
|
||||
bufferAlloc = modern;
|
||||
try {
|
||||
return bufferAlloc(size);
|
||||
} catch {
|
||||
return (bufferAlloc = legacy), bufferAlloc(size);
|
||||
}
|
||||
function modern(size2) {
|
||||
return Buffer.alloc(size2);
|
||||
}
|
||||
function legacy(size2) {
|
||||
return new Buffer(size2);
|
||||
}
|
||||
}
|
||||
|
||||
function localHeader(name, size) {
|
||||
var nameBuffer = bufferFrom(name, 'utf-8');
|
||||
var fixed = bufferAlloc(8);
|
||||
fixed.writeUInt32LE(0x04034b50, 0);
|
||||
fixed.writeUInt16LE(nameBuffer.length, 4);
|
||||
fixed.writeUInt16LE(size, 6);
|
||||
return Buffer.concat([fixed, nameBuffer]);
|
||||
}
|
||||
|
||||
var SIGNATURE = bufferFrom([0x50, 0x4b, 0x05, 0x06], undefined);
|
||||
|
||||
function legacy() {
|
||||
var sized = new Buffer2(3);
|
||||
var text = new Buffer2('hi', 'utf8');
|
||||
var array = new Buffer2([1, 2, 255]);
|
||||
var out = [sized.length, sized[0], text.toString(), array.readUInt16BE(1), array.readInt8(2)];
|
||||
try {
|
||||
new Buffer2(2, 'utf8');
|
||||
} catch (error) {
|
||||
out.push(error.code);
|
||||
}
|
||||
try {
|
||||
array.writeUInt8(300, 0);
|
||||
} catch (error) {
|
||||
out.push(error.code);
|
||||
}
|
||||
try {
|
||||
array.readUInt8('0');
|
||||
} catch (error) {
|
||||
out.push(error.code);
|
||||
}
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
function signature() {
|
||||
return SIGNATURE;
|
||||
}
|
||||
|
||||
module.exports = { crc32: crc32, localHeader: localHeader, signature: signature, legacy: legacy };
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ "name": "zip-helpers", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
|
||||
@@ -0,0 +1,5 @@
|
||||
// A package whose import cycle calls a reassigned function declaration
|
||||
// while the cycle initializes.
|
||||
import { report } from "cycle-format";
|
||||
|
||||
for (const line of report()) console.log(line);
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { format } from "./index.js";
|
||||
|
||||
// Runs before index.js's body: format is the hoisted declaration.
|
||||
export const early = format("a");
|
||||
|
||||
export function describe(value) {
|
||||
return `${typeof format} ${format(value)}`;
|
||||
}
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare function format(value: string): string;
|
||||
export declare const report: () => string[];
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
// A reassigned function declaration of a module in an import cycle: the
|
||||
// dependency calls it while the cycle initializes, before this module's
|
||||
// body runs, and gets the hoisted declaration; later calls see the
|
||||
// replacement.
|
||||
import { early, describe } from "./dep.js";
|
||||
|
||||
export function format(value) {
|
||||
format = function formatUpper(text) {
|
||||
return `[${String(text).toUpperCase()}]`;
|
||||
};
|
||||
return `<${value}>`;
|
||||
}
|
||||
|
||||
export const report = () => [
|
||||
"early: " + early,
|
||||
"now: " + format("b") + " " + format("c"),
|
||||
"dep: " + describe("d"),
|
||||
];
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{ "name": "cycle-format", "version": "1.0.0", "type": "module", "types": "./index.d.ts", "exports": { ".": { "types": "./index.d.ts", "default": "./index.js" } } }
|
||||
Reference in New Issue
Block a user