mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
buffer: fromArrayBuffer ranges, the deprecated constructor, Buffer as a value (#1970)
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
`Buffer.from` accepts untyped strings, byte arrays, arrays, and plain array-like or Buffer JSON objects.
|
||||
Encoding arguments must be supported literals.
|
||||
`Buffer.from(arrayBuffer, byteOffset, length)` is a view of the ArrayBuffer or SharedArrayBuffer, with Node's offset and length checks.
|
||||
Custom input `valueOf` hooks and opaque class or native-handle inputs throw an unsupported-operation error.
|
||||
The deprecated `Buffer()` and `new Buffer()` take up to three arguments, without spreads, and follow Node: the first call made outside `node_modules` reports DEP0005 on the next tick, a number allocates zero-filled bytes, and other arguments convert as `Buffer.from` does.
|
||||
In JavaScript, `Buffer` read as a value (the global or the buffer module's `Buffer`) is one function with Node's statics and `poolSize`.
|
||||
Calling or constructing it through a stored value, reading its `prototype`, and its `copyBytesFrom` and `of` statics throw an unsupported-operation error.
|
||||
`Buffer.prototype` members read as values are functions that run on Buffer receivers; inherited and symbol-keyed members throw an unsupported-operation error.
|
||||
|
||||
@@ -917,6 +917,14 @@ interface Buffer<
|
||||
readBigUint64LE(offset?: number): bigint;
|
||||
}
|
||||
interface BufferConstructor {
|
||||
/** @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead. */
|
||||
new (str: string, encoding?: BufferEncoding): Buffer;
|
||||
/** @deprecated since v10.0.0 - Use `Buffer.alloc()` instead. */
|
||||
new (size: number): Buffer;
|
||||
/** @deprecated since v10.0.0 - Use `Buffer.from(array)` instead. */
|
||||
new (array: ArrayLike<number>): Buffer;
|
||||
/** @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead. */
|
||||
new (arrayBuffer: ArrayBuffer | SharedArrayBuffer): Buffer;
|
||||
from(data: string, encoding?: BufferEncoding): Buffer;
|
||||
/* The ArrayBuffer form is the VIEW construction: x.buffer with an
|
||||
* optional byte offset/length shares x's storage (the subarray rule). */
|
||||
|
||||
@@ -455,6 +455,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"buffer.poolFromDyn": "scr_buffer_from_dyn_pooled",
|
||||
"buffer.poolFromDynRef": "scr_buffer_from_dyn_ref_pooled",
|
||||
"buffer.poolFromDynOffset": "scr_buffer_from_dyn_offset_pooled",
|
||||
"buffer.poolFromDynRange": "scr_buffer_from_dyn_range_pooled",
|
||||
"buffer.poolSize": "scr_buffer_pool_size",
|
||||
"buffer.setPoolSize": "scr_buffer_set_pool_size",
|
||||
"buffer.byteLenDyn": "scr_buffer_byte_length_dyn",
|
||||
@@ -480,6 +481,12 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"bytes.equalsChk": "scr_bytes_equals_chk",
|
||||
"bytes.compareChk": "scr_bytes_compare_chk",
|
||||
"buffer.newStringFail": "scr_buffer_new_string_fail",
|
||||
"buffer.deprecation": "scr_buffer_deprecation",
|
||||
"buffer.deprecationCount": "scr_buffer_deprecation_count",
|
||||
"buffer.constructor": "scr_buffer_constructor_value",
|
||||
"buffer.installValue": "scr_buffer_install_value",
|
||||
"buffer.staticValue": "scr_buffer_static_value",
|
||||
"buffer.prototypeMember": "scr_buffer_prototype_member",
|
||||
"fs.toUnixTimestamp": "scr_fs_to_unix_timestamp",
|
||||
"fs.existsChk": "scr_fs_exists_async",
|
||||
"fs.mkdtempSyncChk": "scr_fs_mkdtemp_sync_chk",
|
||||
@@ -1682,6 +1689,8 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"url.legacyParse",
|
||||
"url.legacyFormat",
|
||||
"url.legacyResolve",
|
||||
// The deprecated Buffer constructor reports DEP0005 on the next tick.
|
||||
"buffer.deprecation",
|
||||
// The wrappers schedule warnings and callbacks on the tick queue.
|
||||
"util.deprecate",
|
||||
"util.callbackify",
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type IrLibFn,
|
||||
type IrLocal,
|
||||
type IrParam,
|
||||
type IrFunction,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
STRING,
|
||||
@@ -30,7 +31,8 @@ import {
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
import { newFnCtx, own } from "../lowerer.js";
|
||||
import { ENTRY_NAME, newFnCtx, own } from "../lowerer.js";
|
||||
import { everyStmt } from "../../../ir/traverse.js";
|
||||
import { buildBytesSortFn, closureKeyOrder } from "../lower-array-sort.js";
|
||||
import { isSafeToDiscard } from "../expressions/evaluation-safety.js";
|
||||
import { lowerDynObjectLiteral } from "../expressions/object-literals.js";
|
||||
@@ -1377,6 +1379,8 @@ export function lowerBytesMethodCall(
|
||||
"the numeric families read/write Buffer bytes",
|
||||
);
|
||||
}
|
||||
if (!numericArguments(lowerer, call))
|
||||
return dynNumericCall(lowerer, call, access, name, receiverIr, loc);
|
||||
const write = name.startsWith("write");
|
||||
const required = write ? 1 : 0; // value; offset defaults to 0
|
||||
if (nArgs < required || nArgs > required + 1) {
|
||||
@@ -1554,6 +1558,39 @@ const BUF_BIGINT_METHODS: Record<string, { sign: boolean; le: boolean } | undefi
|
||||
writeBigUint64LE: { sign: false, le: true },
|
||||
};
|
||||
|
||||
/** Whether every argument of a numeric read/write is statically a number. */
|
||||
function numericArguments(lowerer: Lowerer, call: ts.CallExpression): boolean {
|
||||
return call.arguments.nodes.every((a) => lowerer.mapTypeOf(lowerer.typeOf(a))?.kind === "f64");
|
||||
}
|
||||
|
||||
/** A numeric read/write with an argument that is not a number (a string
|
||||
* offset from untyped JavaScript): the checked-dynamic Buffer method runs
|
||||
* Node's argument validation at runtime. */
|
||||
function dynNumericCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
name: string,
|
||||
receiverIr: IrType,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, receiverIr);
|
||||
return {
|
||||
kind: "dynCheck",
|
||||
value: {
|
||||
kind: "dynInvoke",
|
||||
recv: { kind: "dynFrom", value: receiver, type: DYN, loc, liveRef: true },
|
||||
method: name,
|
||||
calleeName: ts.getTextOfNode(access),
|
||||
args: call.arguments.nodes.map((a) => lowerer.lowerExprExpecting(a, DYN)),
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The variable-width quartet (offset + byteLength) → sign/endian token. */
|
||||
const BUF_NUM_VAR_METHODS: Record<string, string | undefined> = {
|
||||
readUIntBE: "ube",
|
||||
@@ -1913,7 +1950,12 @@ function knownBufferProducer(
|
||||
* or empty encoding and throws ERR_UNKNOWN_ENCODING for an unknown name; an
|
||||
* ArrayBuffer takes the second argument as its byte offset; other sources
|
||||
* ignore it. */
|
||||
function dynBufferFromParts(source: IrExpr, encoding: IrExpr, loc: SrcLoc): IrExpr {
|
||||
function dynBufferFromParts(
|
||||
source: IrExpr,
|
||||
encoding: IrExpr,
|
||||
loc: SrcLoc,
|
||||
length?: IrExpr,
|
||||
): IrExpr {
|
||||
const utf8: IrExpr = { kind: "strLit", value: "utf8", type: STRING, loc };
|
||||
const isString = (x: IrExpr): IrExpr => ({
|
||||
kind: "dynTest",
|
||||
@@ -1954,20 +1996,48 @@ function dynBufferFromParts(source: IrExpr, encoding: IrExpr, loc: SrcLoc): IrEx
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
// An ArrayBuffer takes the second argument as its byte offset
|
||||
// (scr_buffer_from_dyn_offset_pooled); other values convert with utf8.
|
||||
else_: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromDynOffset",
|
||||
args: [source, encoding],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
// An ArrayBuffer takes the second argument as its byte offset and the
|
||||
// third as its length (scr_buffer_from_dyn_range_pooled); other values
|
||||
// convert with utf8.
|
||||
else_: length
|
||||
? {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromDynRange",
|
||||
args: [source, encoding, length],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
}
|
||||
: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromDynOffset",
|
||||
args: [source, encoding],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
},
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Buffer.from(arrayBuffer, byteOffset[, length]): each argument in order
|
||||
* as a checked-dynamic value, read by the runtime as Node's
|
||||
* fromArrayBuffer reads them. */
|
||||
function bufferFromArrayBufferRange(
|
||||
lowerer: Lowerer,
|
||||
args: readonly ts.Expression[],
|
||||
loc: SrcLoc,
|
||||
first?: IrExpr,
|
||||
): IrExpr {
|
||||
const operands = [0, 1, 2].map((i) =>
|
||||
i === 0 && first
|
||||
? first
|
||||
: args[i]
|
||||
? lowerer.lowerExprExpecting(args[i]!, DYN)
|
||||
: dynUndefinedExpr(loc),
|
||||
);
|
||||
return { kind: "libCall", fn: "buffer.poolFromDynRange", args: operands, 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(
|
||||
@@ -2015,52 +2085,81 @@ function dynBufferFromRuntimeEncoding(
|
||||
);
|
||||
}
|
||||
|
||||
/** 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(
|
||||
/** The deprecated `Buffer(arg[, encodingOrOffset[, length]])`, called or
|
||||
* constructed through the global or an alias of it, as Node's Buffer
|
||||
* function runs it: the arguments evaluate in order, DEP0005 is reported
|
||||
* on the next tick (once, and never for a call made from node_modules,
|
||||
* which only counts toward Node's 10000-call check), then a number
|
||||
* allocates zero-filled bytes (and throws ERR_INVALID_ARG_TYPE beside a
|
||||
* string second argument) and anything else converts as Buffer.from does.
|
||||
* Null for spread arguments and more than three. */
|
||||
export function lowerBufferConstruct(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.NewExpression,
|
||||
expr: ts.NewExpression | ts.CallExpression,
|
||||
): IrExpr | null {
|
||||
const sf = expr.getSourceFile();
|
||||
if (!isJsSourceFile(sf) || !/[\\/]node_modules[\\/]/.test(sf.fileName)) return null;
|
||||
if (ts.isCallExpression(expr) && expr.questionDotToken) return null;
|
||||
const args = expr.arguments?.nodes ?? [];
|
||||
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) return null;
|
||||
if (args.length > 3 || 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) => ({
|
||||
const locals = [0, 1, 2].slice(0, Math.max(args.length, 2)).map((i) => {
|
||||
const local = lowerer.declareHiddenLocal("%bufferArgument", DYN);
|
||||
const node = args[i];
|
||||
const init = node === undefined ? dynUndefinedExpr(loc) : lowerer.lowerExprExpecting(node, DYN);
|
||||
return { local, init };
|
||||
});
|
||||
const [source, second, third] = locals.map(({ local }) => varRef(local.id, DYN, loc)) as [
|
||||
IrExpr,
|
||||
IrExpr,
|
||||
IrExpr | undefined,
|
||||
];
|
||||
const inNodeModules = /[\\/]node_modules[\\/]/.test(expr.getSourceFile().fileName);
|
||||
const value: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "number", value: source, type: BOOL, loc },
|
||||
then: {
|
||||
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,
|
||||
},
|
||||
cond: { kind: "dynTest", test: "string", value: second, 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,
|
||||
},
|
||||
else_: dynBufferFromParts(source, encoding, loc),
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
}),
|
||||
);
|
||||
return { kind: "libCall", fn: "buffer.brand", args: [value], type: BYTES_U8, loc };
|
||||
},
|
||||
else_: dynBufferFromParts(source, second, loc, third),
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
...locals.map(({ local, init }): IrStmt => ({
|
||||
kind: "varDecl",
|
||||
localId: local.id,
|
||||
init,
|
||||
loc,
|
||||
})),
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: inNodeModules ? "buffer.deprecationCount" : "buffer.deprecation",
|
||||
args: [],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result: { kind: "libCall", fn: "buffer.brand", args: [value], type: BYTES_U8, loc },
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Buffer.from(array or bytes, extra): Node copies an array-like source and
|
||||
@@ -2124,7 +2223,16 @@ function lowerBufferStaticValue(
|
||||
lowerer.typeOf(args[0]!).getSymbol(),
|
||||
)
|
||||
) {
|
||||
return lowerArrayBufferView(lowerer, args, "u8", loc);
|
||||
// A byte offset or length follows Node's fromArrayBuffer (its
|
||||
// conversions and ERR_BUFFER_OUT_OF_BOUNDS), not the typed-array view.
|
||||
if (args.length === 1) return lowerArrayBufferView(lowerer, args, "u8", loc);
|
||||
return bufferFromArrayBufferRange(lowerer, args, loc);
|
||||
}
|
||||
// Buffer.from(value, byteOffset, length) over an untyped value: an
|
||||
// ArrayBuffer's range, or the two-argument conversion of other values.
|
||||
if (args.length === 3 && !args.some(ts.isSpreadElement)) {
|
||||
const first = lowerer.lowerExpr(args[0]!);
|
||||
if (first.type.kind === "dyn") return bufferFromArrayBufferRange(lowerer, args, loc, first);
|
||||
}
|
||||
// Buffer.from(x.buffer[, byteOffset[, length]]): a u8 VIEW sharing
|
||||
// x's storage (Node shares the ArrayBuffer; length is in BYTES).
|
||||
@@ -2551,11 +2659,101 @@ function lowerBufferStaticValue(
|
||||
const s = lowerer.lowerExprExpecting(args[0]!, STRING);
|
||||
return { kind: "libCall", fn: "buffer.isEncoding", args: [s], type: BOOL, loc };
|
||||
}
|
||||
// Any other single argument: Node answers false for non-strings.
|
||||
if (args.length === 1 && !ts.isSpreadElement(args[0]!)) {
|
||||
const value = lowerer.declareHiddenLocal("%bufferEncoding", DYN);
|
||||
const ref = varRef(value.id, DYN, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: value.id,
|
||||
init: lowerer.lowerExprExpecting(args[0]!, DYN),
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result: {
|
||||
kind: "logical",
|
||||
op: "&&",
|
||||
left: { kind: "dynTest", test: "string", value: ref, type: BOOL, loc },
|
||||
right: {
|
||||
kind: "libCall",
|
||||
fn: "buffer.isEncoding",
|
||||
args: [{ kind: "dynCheck", value: ref, type: STRING, loc }],
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
lowerer.noLowering(
|
||||
"Buffer.isEncoding with this argument shape",
|
||||
call,
|
||||
"one string-typed argument is the lowered form",
|
||||
"one argument is the lowered form",
|
||||
);
|
||||
}
|
||||
return null; // of, copyBytesFrom, ... → the SC2020 member fence
|
||||
}
|
||||
|
||||
/** `Buffer.prototype.<name>` or `Buffer.prototype[key]` read in JavaScript
|
||||
* (smart-buffer's `typeof Buffer.prototype[method]` probe): the member as
|
||||
* scr_buffer_prototype_member answers it. Null for other expressions. */
|
||||
export function lowerBufferPrototypeMember(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.PropertyAccessExpression | ts.ElementAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (expr.questionDotToken || !isJsSourceFile(expr.getSourceFile())) return null;
|
||||
const receiver = expr.expression;
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(receiver) ||
|
||||
receiver.questionDotToken ||
|
||||
receiver.name.text !== "prototype" ||
|
||||
stdlibGlobalNameOf(lowerer, receiver.expression) !== "Buffer"
|
||||
)
|
||||
return null;
|
||||
const loc = locOf(expr);
|
||||
const key: IrExpr = ts.isPropertyAccessExpression(expr)
|
||||
? {
|
||||
kind: "dynFrom",
|
||||
value: { kind: "strLit", value: expr.name.text, type: STRING, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
}
|
||||
: lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
|
||||
return { kind: "libCall", fn: "buffer.prototypeMember", args: [key], type: DYN, loc };
|
||||
}
|
||||
|
||||
const BUFFER_VALUE_FNS: ReadonlySet<string> = new Set([
|
||||
"buffer.constructor",
|
||||
"buffer.staticValue",
|
||||
"buffer.prototypeMember",
|
||||
]);
|
||||
|
||||
/** A program that reads the Buffer function's value installs it in %main,
|
||||
* so a Buffer's `.constructor` answers the same function. */
|
||||
export function installBufferValue(functions: IrFunction[]): void {
|
||||
let found = false;
|
||||
const visitor = {
|
||||
expr: (e: IrExpr): boolean => {
|
||||
if (e.kind === "libCall" && BUFFER_VALUE_FNS.has(e.fn)) found = true;
|
||||
return !found;
|
||||
},
|
||||
stmt: (): boolean => !found,
|
||||
};
|
||||
for (const fn of functions) {
|
||||
for (const s of fn.body) everyStmt(s, visitor);
|
||||
if (found) break;
|
||||
}
|
||||
const main = functions.find((fn) => fn.name === ENTRY_NAME);
|
||||
if (!found || !main) return;
|
||||
main.body.unshift({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn: "buffer.installValue", args: [], type: VOID, loc: main.loc },
|
||||
loc: main.loc,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ import { isClassCallback } from "../class-callbacks.js";
|
||||
import { isKeyedFieldAccess } from "./keyed-field-targets.js";
|
||||
import { lowerFieldCompoundValue } from "./updates.js";
|
||||
import { readsHeldEvolvingArray } from "../lower-stmts.js";
|
||||
import { lowerBufferPrototypeMember } from "../containers/bytes.js";
|
||||
|
||||
/** `a[i]` reads. Only f64 indices into array receivers are modeled; JS
|
||||
* string-key element access (`a["length"]`) and string indexing (`s[0]`
|
||||
@@ -104,6 +105,8 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre
|
||||
const en = lowerEnumAccess(lowerer, expr);
|
||||
if (en) return en;
|
||||
}
|
||||
const bufferMember = lowerBufferPrototypeMember(lowerer, expr);
|
||||
if (bufferMember) return bufferMember;
|
||||
const cachedModule = lowerRequireCacheElement(lowerer, expr);
|
||||
if (cachedModule) return cachedModule;
|
||||
const namespaceElement = lowerModuleNamespaceElement(lowerer, expr);
|
||||
|
||||
@@ -762,6 +762,9 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
|
||||
"test it in a condition, read its typeof, or call its members",
|
||||
);
|
||||
}
|
||||
// JavaScript reads the Buffer function's value (builtinGlobalValue).
|
||||
if (isJsSourceFile(ts.getSourceFileOfNode(expr)))
|
||||
return { kind: "libCall", fn: "buffer.constructor", args: [], type: DYN, loc };
|
||||
return { kind: "strLit", value: "[builtin Buffer]", type: STRING, loc };
|
||||
}
|
||||
if (lowerer.isStdlibGlobal(expr, "fetch")) {
|
||||
|
||||
@@ -154,6 +154,7 @@ import {
|
||||
lowerStringCodesValue,
|
||||
} from "../lower-builtin-values.js";
|
||||
import { lowerArrayFromValue } from "../containers/array-construction.js";
|
||||
import { lowerBufferPrototypeMember } from "../containers/bytes.js";
|
||||
import { lowerNetAddressStaticRead } from "../builtins/net-address.js";
|
||||
import { STATS_TYPE_TESTS } from "../builtins/filesystem.js";
|
||||
import { arrayValueRead, arrayValueType } from "../array-values.js";
|
||||
@@ -311,6 +312,10 @@ export function lowerPropertyAccessExpr(
|
||||
// Cloudflare probe) folds like `navigator.userAgent`.
|
||||
const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION);
|
||||
if (navigator) return navigator;
|
||||
{
|
||||
const member = lowerBufferPrototypeMember(lowerer, expr);
|
||||
if (member) return member;
|
||||
}
|
||||
// A dgram socket's method read as a value in JavaScript (`emit:
|
||||
// socket.emit.bind(socket)`): the socket crosses as its checked-dynamic
|
||||
// handle, whose methods are function values that dispatch on their
|
||||
@@ -1791,6 +1796,28 @@ export function lowerIntrinsicProperty(
|
||||
return lowerNumberParserValue(lowerer, numberMember, locOf(expr));
|
||||
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "poolSize")
|
||||
return { kind: "libCall", fn: "buffer.poolSize", args: [], type: F64, loc: locOf(expr) };
|
||||
{
|
||||
const member = lowerer.stdlibGlobalMember(expr, "Buffer");
|
||||
if (member === "name")
|
||||
return { kind: "strLit", value: "Buffer", type: STRING, loc: locOf(expr) };
|
||||
if (member === "length") return numLit(3, locOf(expr));
|
||||
// JavaScript reads the statics from the Buffer function's value, so
|
||||
// each has one identity and Node's name and length.
|
||||
if (
|
||||
member !== null &&
|
||||
BUFFER_VALUE_STATICS.has(member) &&
|
||||
isJsSourceFile(ts.getSourceFileOfNode(expr))
|
||||
) {
|
||||
const loc = locOf(expr);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "buffer.staticValue",
|
||||
args: [{ kind: "strLit", value: member, type: STRING, loc }],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
const netAddressStatic = lowerNetAddressStaticRead(lowerer, expr);
|
||||
if (netAddressStatic) return netAddressStatic;
|
||||
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "isBuffer")
|
||||
@@ -4195,6 +4222,8 @@ export function builtinFunctionValue(name: string, loc: SrcLoc): IrExpr {
|
||||
* null when it stays an identity token (JavaScript) or fenced. */
|
||||
export function builtinGlobalValue(canonical: string, loc: SrcLoc, js = false): IrExpr | null {
|
||||
if (js && GLOBAL_FUNCTION_VALUES.has(canonical)) return builtinFunctionValue(canonical, loc);
|
||||
if (js && canonical === "Buffer")
|
||||
return { kind: "libCall", fn: "buffer.constructor", args: [], type: DYN, loc };
|
||||
if (GLOBAL_CONSTRUCTOR_VALUES.has(canonical)) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -4322,6 +4351,22 @@ function fenceBuiltinConstructorStaticRead(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The Buffer statics the Buffer function's value holds
|
||||
* (scr_buffer_constructor_value). */
|
||||
const BUFFER_VALUE_STATICS: ReadonlySet<string> = new Set([
|
||||
"from",
|
||||
"copyBytesFrom",
|
||||
"of",
|
||||
"alloc",
|
||||
"allocUnsafe",
|
||||
"allocUnsafeSlow",
|
||||
"isBuffer",
|
||||
"compare",
|
||||
"isEncoding",
|
||||
"concat",
|
||||
"byteLength",
|
||||
]);
|
||||
|
||||
/** Object.prototype members a plain record inherits, read as values. */
|
||||
const OBJECT_PROTOTYPE_VALUE_READS: ReadonlySet<string> = new Set([
|
||||
"constructor",
|
||||
|
||||
@@ -119,7 +119,11 @@ import {
|
||||
} from "./containers/indexed-objects.js";
|
||||
import { lowerTupleReadMethodCall } from "./containers/array-methods.js";
|
||||
import { tryFuseIndexRead } from "./array-values.js";
|
||||
import { lowerBytesPrototypeSetCall, lowerBytesStaticCall } from "./containers/bytes.js";
|
||||
import {
|
||||
lowerBufferConstruct,
|
||||
lowerBytesPrototypeSetCall,
|
||||
lowerBytesStaticCall,
|
||||
} from "./containers/bytes.js";
|
||||
import {
|
||||
lowerRegexMethodCall,
|
||||
lowerStringIndexCall,
|
||||
@@ -363,6 +367,13 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression);
|
||||
if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration);
|
||||
}
|
||||
// The deprecated Buffer(...) call (containers/bytes.ts). The checker
|
||||
// types it `any`, so the bytes cross as a checked-dynamic value, which
|
||||
// keeps the Buffer brand for inspection.
|
||||
{
|
||||
const buffer = lowerBufferConstruct(lowerer, expr);
|
||||
if (buffer) return { kind: "dynFrom", value: buffer, type: DYN, loc };
|
||||
}
|
||||
// A package shim's runtime intrinsic (a bodiless `declare function`
|
||||
// named scriptcUnixDgram* in a shipped shim): a direct runtime call.
|
||||
if (ts.isIdentifier(expr.expression) && isShimIntrinsicName(expr.expression.text)) {
|
||||
|
||||
@@ -47,7 +47,6 @@ import {
|
||||
DATE_T,
|
||||
DYN,
|
||||
F64,
|
||||
bytesOf,
|
||||
type IrClassDef,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
@@ -116,8 +115,7 @@ import { isCjsRequireGlobal } from "./lower-node-module.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, lowerNodeModulesBufferConstruct } from "./containers/bytes.js";
|
||||
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { knownBufEncoding, lowerBufferConstruct } from "./containers/bytes.js";
|
||||
import { lowerSearchParamsNew } from "./builtins/url.js";
|
||||
import { lowerTextCodecNew } from "./builtins/text-codecs.js";
|
||||
import {
|
||||
@@ -9781,9 +9779,9 @@ 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).
|
||||
// The deprecated `new Buffer(...)` (containers/bytes.ts).
|
||||
{
|
||||
const buffer = lowerNodeModulesBufferConstruct(lowerer, expr);
|
||||
const buffer = lowerBufferConstruct(lowerer, expr);
|
||||
if (buffer) return buffer;
|
||||
}
|
||||
// new Function(...): Node's EvalError under
|
||||
@@ -10864,43 +10862,6 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
// families carry pointed hints: each states WHY no honest static
|
||||
// lowering exists (or what to use instead).
|
||||
if (lowerer.isStdlibSymbol(symbol ?? undefined)) {
|
||||
// The deprecated `new Buffer(string, encoding?)` ctor's string arm
|
||||
// with a NON-STRING first argument and a string second: Node
|
||||
// throws ERR_INVALID_ARG_TYPE synchronously (and DEP0005 never
|
||||
// fires on this throwing path, so the compiled silence matches).
|
||||
// Every other new Buffer form keeps the fence below — the
|
||||
// constructing forms would owe the deprecation warning.
|
||||
if (
|
||||
expr.expression.text === "Buffer" &&
|
||||
expr.arguments?.nodes.length === 2 &&
|
||||
!expr.arguments.nodes.some(ts.isSpreadElement) &&
|
||||
lowerer.mapTypeOf(lowerer.typeOf(expr.arguments.nodes[0]!))?.kind !== "string" &&
|
||||
lowerer.mapTypeOf(lowerer.typeOf(expr.arguments.nodes[1]!))?.kind === "string"
|
||||
) {
|
||||
const first = lowerer.lowerExpr(expr.arguments.nodes[0]!);
|
||||
if (
|
||||
first.kind === "unitLit" ||
|
||||
first.type.kind === "dyn" ||
|
||||
lowerer.dynConvertible(first.type)
|
||||
) {
|
||||
const got: IrExpr =
|
||||
first.type.kind === "dyn" ? first : { kind: "dynFrom", value: first, type: DYN, loc };
|
||||
// The encoding argument still evaluates in Node before the
|
||||
// throw only via the ctor body's later reads — it does NOT
|
||||
// observe it before throwing, so dropping it is exact for
|
||||
// effect-free operands; effectful ones keep the fence.
|
||||
const enc = lowerer.lowerExpr(expr.arguments.nodes[1]!);
|
||||
if (enc.kind === "strLit" || isSafeToRepeat(enc)) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "buffer.newStringFail",
|
||||
args: [got],
|
||||
type: bytesOf("u8"),
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
const ctorHints: Record<string, string | undefined> = {
|
||||
RegExp: "use a regex literal (/pattern/flags) — constructed regexes have no lowering",
|
||||
String:
|
||||
|
||||
@@ -14,7 +14,7 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { unsignedHex } from "../../format-integer.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { jsFuncNameOf, type Lowerer } from "./lowerer.js";
|
||||
import { isJsSourceFile } from "../program.js";
|
||||
import {
|
||||
BOOL,
|
||||
@@ -2416,6 +2416,12 @@ function directCallableInspect(lowerer: Lowerer, node: ts.Expression, loc: SrcLo
|
||||
}
|
||||
const decl = sym.declarations[0];
|
||||
if (!decl) return null;
|
||||
// A JavaScript function can hold own properties (`f.x = 1`, computed
|
||||
// keys), which the runtime walk prints after its name.
|
||||
if (isJsSourceFile(node.getSourceFile())) {
|
||||
const type = lowerer.mapTypeOf(lowerer.typeOf(node));
|
||||
if (type?.kind === "func" && lowerer.dynConvertible(type)) return null;
|
||||
}
|
||||
if (ts.isFunctionDeclaration(decl) && decl.name) {
|
||||
return strLit(`[Function: ${decl.name.text}]`, loc);
|
||||
}
|
||||
@@ -2476,7 +2482,17 @@ export function lowerInspectValue(
|
||||
loc,
|
||||
);
|
||||
}
|
||||
const value = lowerer.lowerExpr(valueNode);
|
||||
let value = lowerer.lowerExpr(valueNode);
|
||||
// A JavaScript function renders through the runtime walk, which prints
|
||||
// its own properties after its name.
|
||||
if (
|
||||
value.type.kind === "func" &&
|
||||
isJsSourceFile(valueNode.getSourceFile()) &&
|
||||
lowerer.dynConvertible(value.type)
|
||||
) {
|
||||
const name = jsFuncNameOf(valueNode);
|
||||
value = { kind: "dynFrom", value, type: DYN, ...(name !== null ? { fnName: name } : {}), loc };
|
||||
}
|
||||
if (value.type.kind === "bytes") {
|
||||
// One runtime representation serves Buffer AND Uint8Array; their
|
||||
// renderings differ (<Buffer aa> vs Uint8Array(1) [ 170 ]), so the
|
||||
|
||||
@@ -153,6 +153,7 @@ import { ClassDynamicDispatch, trackClassFieldCreation } from "./class-dynamic-d
|
||||
import { guardClassAttributeWrites } from "./class-attributes.js";
|
||||
import { guardFrozenWrites, markSloppyRecordWrites } from "./frozen-records.js";
|
||||
import { applyHandleProperties } from "./handle-properties.js";
|
||||
import { installBufferValue } from "./containers/bytes.js";
|
||||
import { ClassProtocols } from "./class-protocols.js";
|
||||
import { earlyReadVarSymbols } from "./var-hoisting.js";
|
||||
import { recordTextCodecClass } from "../../ir/ir.js";
|
||||
@@ -7734,6 +7735,7 @@ export class Lowerer {
|
||||
guardClassAttributeWrites(this, functions);
|
||||
guardFrozenWrites(this, functions);
|
||||
applyHandleProperties(this, functions);
|
||||
installBufferValue(functions);
|
||||
|
||||
// Globals typed by a class that never REGISTERED (a JS class whose
|
||||
// collection fenced — Symbol-keyed fields, an unsupported base): the
|
||||
@@ -13911,6 +13913,9 @@ export class Lowerer {
|
||||
loc,
|
||||
};
|
||||
}
|
||||
// The buffer module's Buffer is the global constructor's value.
|
||||
if (bi.module === "buffer" && bi.member === "Buffer")
|
||||
return { kind: "libCall", fn: "buffer.constructor", args: [], type: DYN, loc };
|
||||
if (bi.module === "fs" && (bi.member === "realpathSync" || bi.member === "realpathSync.native"))
|
||||
return fsRealpathValue(this, bi.member === "realpathSync.native", loc);
|
||||
const fn = builtinModuleFnOf(this, bi.module, bi.member);
|
||||
|
||||
@@ -3452,6 +3452,9 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin
|
||||
return ts.symbolName(resolved) === "global" ? "globalThis" : ts.symbolName(resolved);
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
|
||||
// The buffer module's Buffer (`require("buffer").Buffer`, `buffer_1.Buffer`)
|
||||
// is the global constructor.
|
||||
if (bufferNamespaceMember(lowerer, expr)) return "Buffer";
|
||||
// A cast of the global object (`(globalThis as { Buffer?: B }).Buffer`,
|
||||
// `(globalThis as any).Buffer`) changes only its static type: the
|
||||
// member is the global object's own.
|
||||
|
||||
@@ -1378,6 +1378,11 @@ function suppressedJsStrictness7(d: ts.Diagnostic): boolean {
|
||||
if (JS_ANY_OPERATOR_CODES.has(d.code)) {
|
||||
return ts.flattenDiagnosticMessageText(d, "\n").includes("'any'");
|
||||
}
|
||||
// The deprecated Buffer(...) call without `new`, which Node's Buffer
|
||||
// function accepts (containers/bytes.ts lowers it).
|
||||
if (d.code === 2348) {
|
||||
return ts.flattenDiagnosticMessageText(d, "\n").includes("'BufferConstructor'");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -835,12 +835,15 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"buffer.poolFromDyn",
|
||||
"buffer.poolFromDynRef",
|
||||
"buffer.poolFromDynOffset",
|
||||
"buffer.poolFromDynRange",
|
||||
// The checked-dynamic compare/equals validators: Node's argument
|
||||
// ladders throw ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE catchably.
|
||||
"buffer.compareChk",
|
||||
"bytes.equalsChk",
|
||||
"bytes.compareChk",
|
||||
"buffer.newStringFail",
|
||||
// Inherited and symbol-keyed Buffer.prototype members throw SC2020.
|
||||
"buffer.prototypeMember",
|
||||
"fs.toUnixTimestamp",
|
||||
"fs.existsChk",
|
||||
"fs.mkdtempChk",
|
||||
|
||||
@@ -3016,6 +3016,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
/** poolFromDyn that also copies an array crossing by reference. */
|
||||
"buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 },
|
||||
"buffer.poolFromDynOffset": { argTypes: [DYN, DYN], result: BYTES_U8 },
|
||||
/** Buffer.from(value, byteOffset, length): an ArrayBuffer's range with
|
||||
* Node's fromArrayBuffer checks; other values as poolFromDynOffset. */
|
||||
"buffer.poolFromDynRange": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 },
|
||||
"buffer.poolSize": { argTypes: [], result: F64 },
|
||||
"buffer.setPoolSize": { argTypes: [F64], result: VOID },
|
||||
/** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like
|
||||
@@ -3050,6 +3053,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* always throws Node's ERR_INVALID_ARG_TYPE ("The \"string\" argument
|
||||
* must be of type string. Received ..."). Borrowed dyn; may-throw. */
|
||||
"buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 },
|
||||
"buffer.deprecation": { argTypes: [], result: VOID },
|
||||
"buffer.deprecationCount": { argTypes: [], result: VOID },
|
||||
"buffer.constructor": { argTypes: [], result: DYN },
|
||||
"buffer.installValue": { argTypes: [], result: VOID },
|
||||
"buffer.staticValue": { argTypes: [STRING], result: DYN },
|
||||
"buffer.prototypeMember": { argTypes: [DYN], result: DYN },
|
||||
/** fs._toUnixTimestamp(time) over a dyn value: numeric strings and
|
||||
* finite numbers coerce (negatives answer now/1000, Node's shape);
|
||||
* everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */
|
||||
|
||||
@@ -449,6 +449,8 @@ const DYN_READ_ONLY = new Set<string>([
|
||||
"error.new",
|
||||
"error.newOptions",
|
||||
"fs.utimesSync",
|
||||
// Buffer.prototype members read as values: a lookup by key.
|
||||
"buffer.prototypeMember",
|
||||
"insp.dyn",
|
||||
"insp.dynS",
|
||||
// The dynamic-tier operators run their dyn twins (scr_js_value.c); the
|
||||
|
||||
@@ -100,6 +100,8 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"process.setDeprecationFlag",
|
||||
"process.setEmitWarning",
|
||||
"punycode.warn",
|
||||
// DEP0005 for the deprecated Buffer constructor.
|
||||
"buffer.deprecation",
|
||||
"timers.immediatePromise",
|
||||
// The performance timeline (scr_perf.c, linked with this unit for its
|
||||
// MaxPerformanceEntryBufferExceededWarning).
|
||||
@@ -366,6 +368,10 @@ const SYMBOL_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.usingDispose",
|
||||
"dyn.usingMethod",
|
||||
"util.isDeepStrictEqual",
|
||||
// The Buffer function value's Symbol(kIsEncodingSymbol) member
|
||||
"buffer.constructor",
|
||||
"buffer.staticValue",
|
||||
"buffer.prototypeMember",
|
||||
]);
|
||||
|
||||
/** Entry points that link the querystring unit. */
|
||||
@@ -907,6 +913,7 @@ const RUNTIME_FEATURE_RULES = {
|
||||
...UTIL_CALLABLE_LIB_FNS,
|
||||
...BUILTIN_CLASS_LIB_FNS,
|
||||
"util.promisifyDyn",
|
||||
"buffer.prototypeMember",
|
||||
],
|
||||
// Constant-key reads of Object.prototype member names answer the
|
||||
// inherited member through scr_dyn_invoke.c.
|
||||
@@ -935,10 +942,22 @@ const RUNTIME_FEATURE_RULES = {
|
||||
/** Buffer.prototype methods called on checked-dynamic values
|
||||
* (scr_bytes_dyn.c). */
|
||||
dynBuffer: rule({
|
||||
// Buffer.prototype members read as values dispatch to the same entries.
|
||||
fns: ["buffer.prototypeMember"],
|
||||
expr: (node, _tables) => node.kind === "dynInvoke" && DYN_BUFFER_METHODS.has(node.method),
|
||||
}),
|
||||
/** Builtin functions read as values (scr_builtin_values.c). */
|
||||
builtinValues: rule({ fns: ["buffer.byteLenValue", "buffer.allocValue", "fs.realpathValue"] }),
|
||||
builtinValues: rule({
|
||||
fns: [
|
||||
"buffer.byteLenValue",
|
||||
"buffer.allocValue",
|
||||
"fs.realpathValue",
|
||||
"buffer.constructor",
|
||||
"buffer.installValue",
|
||||
"buffer.staticValue",
|
||||
"buffer.prototypeMember",
|
||||
],
|
||||
}),
|
||||
/** The stream unit emits through the emitter unit, also when the
|
||||
* program constructs no stream (a pipeline of functions). */
|
||||
/** scr_ecdh.c over the mbedTLS archive; DEP0031 reports through the
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7527-buffer-from-array-buffer.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7528-buffer-from-array-buffer.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7529-buffer-constructor.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7530-buffer-constructor.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7531-buffer-constructor-dependency/main.cjs"
|
||||
],
|
||||
"diags": [
|
||||
{
|
||||
"code": "SC1010",
|
||||
"message": "the 'legacy-buf' module is not supported yet",
|
||||
"loc": {
|
||||
"file": "<repo>/tests/corpus/7531-buffer-constructor-dependency/main.cjs",
|
||||
"start": 362,
|
||||
"end": 392
|
||||
},
|
||||
"milestone": "M4",
|
||||
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported"
|
||||
}
|
||||
]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7532-buffer-constructor-check-limit/main.cjs"
|
||||
],
|
||||
"diags": [
|
||||
{
|
||||
"code": "SC1010",
|
||||
"message": "the 'legacy-buf' module is not supported yet",
|
||||
"loc": {
|
||||
"file": "<repo>/tests/corpus/7532-buffer-constructor-check-limit/main.cjs",
|
||||
"start": 279,
|
||||
"end": 309
|
||||
},
|
||||
"milestone": "M4",
|
||||
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported"
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7533-buffer-value.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7534-buffer-prototype-members.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7535-inspect-function-properties.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7713-buffer-numeric-untyped-arguments.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -269,6 +269,10 @@ void scr_process_set_emit_warning(ScrDyn *fn);
|
||||
* made from node_modules) — at once for a static ES import, which Node
|
||||
* links before any module evaluates, otherwise on the next tick. */
|
||||
void scr_punycode_warn(bool immediate, bool report);
|
||||
/* The deprecated Buffer() and new Buffer() called outside node_modules
|
||||
* (scr_async_dyn.c): DEP0005 on the next tick, once, within Node's
|
||||
* 10000-call check (scr_buffer_deprecation_count). */
|
||||
void scr_buffer_deprecation(void);
|
||||
/* scr_emit_warning once a 'warning' listener exists (scr_lib.c; set by the
|
||||
* listener registration): units that cannot link the dyn-async unit route
|
||||
* their warnings through it, and print the default report otherwise. */
|
||||
|
||||
@@ -1128,6 +1128,22 @@ void scr_punycode_warn(bool immediate, bool report) {
|
||||
scr_str_release(message);
|
||||
}
|
||||
|
||||
void scr_buffer_deprecation(void) {
|
||||
/* Node's showFlaggedDeprecation: after 10000 calls the check stops. */
|
||||
if (scr_buffer_ctor_warned || ++scr_buffer_ctor_checks > 10000) return;
|
||||
static const char text[] =
|
||||
"Buffer() is deprecated due to security and usability issues. Please use the "
|
||||
"Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.";
|
||||
ScrStr *message = scr_str_new(text, sizeof text - 1);
|
||||
ScrStr *code = scr_str_new("DEP0005", 7);
|
||||
ScrDyn *w = scr_warning_new(message, "DeprecationWarning", code);
|
||||
scr_warning_emit_deferred(w);
|
||||
scr_dyn_release(w);
|
||||
scr_str_release(code);
|
||||
scr_str_release(message);
|
||||
scr_buffer_ctor_warned = true;
|
||||
}
|
||||
|
||||
/* The console members' warnings. Node's emitWarning emits on
|
||||
* process.nextTick, so these queue in order and each rides one raw tick
|
||||
* on the shared tick queue; any left at exit are dropped with it. */
|
||||
|
||||
@@ -259,3 +259,217 @@ ScrDyn *scr_fs_realpath_value(const ScrDyn *path, const ScrDyn *options, bool na
|
||||
scr_str_release(resolved);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ── Buffer as a value ───────────────────────────────────────────────────
|
||||
* The Buffer function read as a value in JavaScript (bn.js assigns
|
||||
* `require('buffer').Buffer` to a variable and passes it to
|
||||
* `allocate(ArrayType, size)`): one function per thread, named Buffer
|
||||
* with length 3, whose own enumerable properties are Node's, in Node's
|
||||
* order: poolSize, the statics and Symbol(kIsEncodingSymbol). The statics
|
||||
* apply Node's argument handling. Calling or constructing Buffer through
|
||||
* the value, reading its prototype, and the copyBytesFrom and of statics
|
||||
* throw SC2020. poolSize is a data property the pool reads before each
|
||||
* decision (scr_buffer_pool_refresh_hook). */
|
||||
static SCR_TL ScrDyn *scr_bv_buffer;
|
||||
|
||||
#define SCR_BV_ARG(i) ((i) < argc ? args[i] : scr_dyn_undefined())
|
||||
|
||||
static ScrDyn *scr_bv_unsupported(const char *what) {
|
||||
char message[160];
|
||||
int n = snprintf(message, sizeof message, "%s through a stored Buffer value is not supported yet", what);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* A Buffer result as a value: +1, or NULL with the exception pending. */
|
||||
static ScrDyn *scr_bv_bytes(ScrBytes *bytes) {
|
||||
if (!bytes) return NULL;
|
||||
ScrBytes *buffer = scr_bytes_as_buffer(bytes);
|
||||
scr_bytes_release(bytes);
|
||||
ScrDyn *value = scr_dyn_new_bytes(buffer);
|
||||
scr_bytes_release(buffer);
|
||||
return value;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure; (void)args; (void)argc;
|
||||
return scr_bv_unsupported("Calling or constructing Buffer");
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_from(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *value = SCR_BV_ARG(0), *second = SCR_BV_ARG(1), *third = SCR_BV_ARG(2);
|
||||
if (value->kind != SCR_DYN_STR) return scr_bv_bytes(scr_buffer_from_dyn_range_pooled(value, second, third));
|
||||
/* fromString: an encoding that is not a non-empty string means utf8. */
|
||||
if (second->kind == SCR_DYN_STR && second->v.str->len > 0)
|
||||
return scr_bv_bytes(scr_buffer_from_str_checked(value->v.str, second->v.str));
|
||||
ScrStr *utf8 = scr_str_new("utf8", 4);
|
||||
ScrBytes *bytes = scr_buffer_from_str_checked(value->v.str, utf8);
|
||||
scr_str_release(utf8);
|
||||
return scr_bv_bytes(bytes);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_copy_bytes_from(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure; (void)args; (void)argc;
|
||||
return scr_bv_unsupported("Buffer.copyBytesFrom");
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_of(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure; (void)args; (void)argc;
|
||||
return scr_bv_unsupported("Buffer.of");
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_alloc(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
return scr_bv_bytes(scr_buffer_alloc_value(SCR_BV_ARG(0), SCR_BV_ARG(1), SCR_BV_ARG(2)));
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_alloc_unsafe(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *size = SCR_BV_ARG(0);
|
||||
if (size->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail("size", "of type number", size);
|
||||
return NULL;
|
||||
}
|
||||
return scr_bv_bytes(scr_buffer_alloc_unsafe(size->v.num));
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_alloc_unsafe_slow(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *size = SCR_BV_ARG(0);
|
||||
if (size->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail("size", "of type number", size);
|
||||
return NULL;
|
||||
}
|
||||
return scr_bv_bytes(scr_buffer_alloc_unsafe_slow(size->v.num));
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_is_buffer(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *value = SCR_BV_ARG(0);
|
||||
return scr_dyn_new_bool(value->kind == SCR_DYN_BYTES && value->buffer);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_compare(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
double order = scr_buffer_compare_chk(SCR_BV_ARG(0), SCR_BV_ARG(1));
|
||||
return scr_exc_pending() ? NULL : scr_dyn_new_num(order);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_is_encoding(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *encoding = SCR_BV_ARG(0);
|
||||
return scr_dyn_new_bool(encoding->kind == SCR_DYN_STR && encoding->v.str->len > 0 &&
|
||||
scr_bytes_is_encoding(encoding->v.str));
|
||||
}
|
||||
|
||||
/* Buffer.concat(list, length): Node validates the list, answers an empty
|
||||
* Buffer for an empty list before it reads the length, and otherwise
|
||||
* validates the length as an offset. */
|
||||
static ScrDyn *scr_bv_concat(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
const ScrDyn *list = SCR_BV_ARG(0), *length = SCR_BV_ARG(1);
|
||||
bool empty = list->kind == SCR_DYN_ARR && list->v.arr.len == 0;
|
||||
if (length->kind == SCR_DYN_UNDEF || empty || list->kind != SCR_DYN_ARR)
|
||||
return scr_bv_bytes(scr_buffer_concat_dyn(list));
|
||||
if (length->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail("length", "of type number", length);
|
||||
return NULL;
|
||||
}
|
||||
return scr_bv_bytes(scr_buffer_concat_len_dyn(list, length->v.num));
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_byte_length(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
double length = scr_buffer_byte_length_value((ScrDyn *)SCR_BV_ARG(0), (ScrDyn *)SCR_BV_ARG(1));
|
||||
return scr_exc_pending() ? NULL : scr_dyn_new_num(length);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bv_prototype(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure; (void)args; (void)argc;
|
||||
return scr_bv_unsupported("Buffer.prototype");
|
||||
}
|
||||
|
||||
/* The pool's setting is the value's poolSize: a number as it is, another
|
||||
* primitive through ToNumber, anything else NaN (no pooling). */
|
||||
static void scr_bv_pool_refresh(void) {
|
||||
ScrDyn *table = scr_dyn_function_properties(scr_bv_buffer);
|
||||
const ScrDyn *size = scr_dyn_obj_get(table, "poolSize", 8);
|
||||
double setting = NAN;
|
||||
if (size && size->kind == SCR_DYN_NUM) {
|
||||
setting = size->v.num;
|
||||
} else if (size && (size->kind == SCR_DYN_STR || size->kind == SCR_DYN_BOOL || size->kind == SCR_DYN_NULL ||
|
||||
size->kind == SCR_DYN_UNDEF)) {
|
||||
if (!scr_dyn_number_coerce_js(size, &setting)) setting = NAN;
|
||||
}
|
||||
scr_dyn_release(table);
|
||||
scr_buffer_pool_apply(setting);
|
||||
}
|
||||
|
||||
/* A static `Buffer.poolSize = n` write updates the value's property. */
|
||||
static void scr_bv_pool_publish(double size) {
|
||||
ScrDyn *table = scr_dyn_function_properties(scr_bv_buffer);
|
||||
scr_dyn_obj_set(table, "poolSize", 8, scr_dyn_new_num(size));
|
||||
scr_dyn_release(table);
|
||||
}
|
||||
|
||||
static void scr_bv_cleanup(void) {
|
||||
scr_buffer_pool_refresh_hook = NULL;
|
||||
scr_buffer_pool_publish_hook = NULL;
|
||||
scr_dyn_release(scr_bv_buffer);
|
||||
scr_bv_buffer = NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_buffer_constructor_value(void) {
|
||||
if (scr_bv_buffer) return scr_dyn_retain(scr_bv_buffer);
|
||||
static const struct { const char *name; uint32_t length; ScrDynThunk thunk; } statics[] = {
|
||||
{"from", 3, scr_bv_from},
|
||||
{"copyBytesFrom", 3, scr_bv_copy_bytes_from},
|
||||
{"of", 0, scr_bv_of},
|
||||
{"alloc", 3, scr_bv_alloc},
|
||||
{"allocUnsafe", 1, scr_bv_alloc_unsafe},
|
||||
{"allocUnsafeSlow", 1, scr_bv_alloc_unsafe_slow},
|
||||
{"isBuffer", 1, scr_bv_is_buffer},
|
||||
{"compare", 2, scr_bv_compare},
|
||||
{"isEncoding", 1, scr_bv_is_encoding},
|
||||
{"concat", 2, scr_bv_concat},
|
||||
{"byteLength", 2, scr_bv_byte_length},
|
||||
};
|
||||
double pool_size = scr_buffer_pool_size();
|
||||
scr_bv_buffer = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_bv_call, 3, "native:Buffer", "Buffer");
|
||||
scr_atexit(scr_bv_cleanup);
|
||||
ScrDyn *table = scr_dyn_function_properties(scr_bv_buffer);
|
||||
ScrDyn *getter = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_bv_prototype, 0, "native:function", "get prototype");
|
||||
scr_dyn_obj_set_accessor(table, "prototype", 9, getter, NULL);
|
||||
ScrDynEntry *entry = &table->v.obj.entries[table->v.obj.len - 1];
|
||||
entry->enumerable = entry->configurable = false;
|
||||
scr_dyn_obj_set(table, "poolSize", 8, scr_dyn_new_num(pool_size));
|
||||
ScrDyn *is_encoding = NULL;
|
||||
for (size_t i = 0; i < sizeof statics / sizeof statics[0]; i++) {
|
||||
ScrDyn *fn = scr_dyn_new_func(scr_closure_new(NULL, 0), statics[i].thunk, statics[i].length, "native:function",
|
||||
statics[i].name);
|
||||
if (statics[i].thunk == scr_bv_is_encoding) is_encoding = scr_dyn_retain(fn);
|
||||
scr_dyn_obj_set(table, statics[i].name, strlen(statics[i].name), fn);
|
||||
}
|
||||
scr_dyn_release(table);
|
||||
ScrStr *description = scr_str_new("kIsEncodingSymbol", 17);
|
||||
ScrSym *symbol = scr_sym_new(description);
|
||||
scr_str_release(description);
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_sym_release(symbol);
|
||||
scr_dyn_symbol_key_set(scr_bv_buffer, key, is_encoding);
|
||||
scr_dyn_release(key);
|
||||
scr_dyn_release(is_encoding);
|
||||
scr_buffer_pool_refresh_hook = scr_bv_pool_refresh;
|
||||
scr_buffer_pool_publish_hook = scr_bv_pool_publish;
|
||||
return scr_dyn_retain(scr_bv_buffer);
|
||||
}
|
||||
|
||||
void scr_buffer_install_value(void) { scr_bytes_buffer_constructor_fn = scr_buffer_constructor_value; }
|
||||
|
||||
ScrDyn *scr_buffer_static_value(const ScrStr *name) {
|
||||
ScrDyn *buffer = scr_buffer_constructor_value();
|
||||
ScrDyn *member = scr_dyn_fn_get(buffer, name->data, name->len);
|
||||
scr_dyn_release(buffer);
|
||||
return member;
|
||||
}
|
||||
|
||||
@@ -2787,16 +2787,30 @@ static SCR_TL bool scr_pool_registered; /* scr_buffer_pool_reset runs at context
|
||||
/* Buffer.poolSize >>> 1, kept with the setting. */
|
||||
static SCR_TL size_t scr_pool_half = 32768;
|
||||
|
||||
double scr_buffer_pool_size(void) { return scr_pool_setting; }
|
||||
SCR_TL void (*scr_buffer_pool_refresh_hook)(void) = NULL;
|
||||
SCR_TL void (*scr_buffer_pool_publish_hook)(double size) = NULL;
|
||||
|
||||
void scr_buffer_set_pool_size(double size) {
|
||||
double scr_buffer_pool_size(void) {
|
||||
if (scr_buffer_pool_refresh_hook) scr_buffer_pool_refresh_hook();
|
||||
return scr_pool_setting;
|
||||
}
|
||||
|
||||
void scr_buffer_pool_apply(double size) {
|
||||
scr_pool_setting = size;
|
||||
double t = isfinite(size) ? fmod(trunc(size), 4294967296.0) : 0;
|
||||
if (t < 0) t += 4294967296.0;
|
||||
scr_pool_half = (size_t)((uint32_t)t >> 1);
|
||||
}
|
||||
|
||||
static size_t scr_pool_threshold(void) { return scr_pool_half; }
|
||||
void scr_buffer_set_pool_size(double size) {
|
||||
scr_buffer_pool_apply(size);
|
||||
if (scr_buffer_pool_publish_hook) scr_buffer_pool_publish_hook(size);
|
||||
}
|
||||
|
||||
static size_t scr_pool_threshold(void) {
|
||||
if (scr_buffer_pool_refresh_hook) scr_buffer_pool_refresh_hook();
|
||||
return scr_pool_half;
|
||||
}
|
||||
|
||||
/* Bytes left in the slab. Aligning can move the offset past the end of a
|
||||
* slab whose size is not a multiple of 8; nothing is left then. */
|
||||
|
||||
@@ -400,6 +400,13 @@ ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc);
|
||||
* slab. A slab lives while any of its views does. All return +1 Buffers. */
|
||||
double scr_buffer_pool_size(void);
|
||||
void scr_buffer_set_pool_size(double size);
|
||||
/* The setting without publishing it (scr_buffer_pool_refresh_hook). */
|
||||
void scr_buffer_pool_apply(double size);
|
||||
/* Set while a Buffer function value exists (scr_builtin_values.c): its
|
||||
* poolSize property is the setting, which the refresh hook reads before
|
||||
* each pool decision and the publish hook receives from static writes. */
|
||||
extern SCR_TL void (*scr_buffer_pool_refresh_hook)(void);
|
||||
extern SCR_TL void (*scr_buffer_pool_publish_hook)(double size);
|
||||
ScrBytes *scr_buffer_alloc(double size); /* zeroed, never pooled */
|
||||
ScrBytes *scr_buffer_alloc_unsafe(double size); /* pooled */
|
||||
ScrBytes *scr_buffer_alloc_unsafe_slow(double size); /* never pooled */
|
||||
@@ -410,6 +417,15 @@ 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_from_dyn_offset_pooled(const ScrDyn *value, const ScrDyn *second);
|
||||
/* Buffer.from(value, byteOffset, length): an ArrayBuffer's view with
|
||||
* Node's fromArrayBuffer checks; other values as the two-argument form. */
|
||||
ScrBytes *scr_buffer_from_dyn_range_pooled(const ScrDyn *value, const ScrDyn *second,
|
||||
const ScrDyn *third);
|
||||
/* The deprecated Buffer constructor's DEP0005 state (scr_bytes_io.c): a
|
||||
* call from node_modules counts toward Node's 10000-call check. */
|
||||
extern SCR_TL bool scr_buffer_ctor_warned;
|
||||
extern SCR_TL uint64_t scr_buffer_ctor_checks;
|
||||
void scr_buffer_deprecation_count(void);
|
||||
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
|
||||
@@ -418,6 +434,17 @@ ScrBytes *scr_buffer_concat_dyn(const struct ScrDyn *list);
|
||||
ScrBytes *scr_buffer_alloc_value(const struct ScrDyn *size, const struct ScrDyn *fill,
|
||||
const struct ScrDyn *encoding);
|
||||
struct ScrDyn *scr_fs_realpath_value(const struct ScrDyn *path, const struct ScrDyn *options, bool native);
|
||||
/* The Buffer function as a value (scr_builtin_values.c), one per thread. +1. */
|
||||
struct ScrDyn *scr_buffer_constructor_value(void);
|
||||
/* Installs that function as a Buffer's `.constructor` (the program's
|
||||
* %main calls it when it reads the value): scr_bytes_buffer_constructor_fn. */
|
||||
void scr_buffer_install_value(void);
|
||||
extern struct ScrDyn *(*scr_bytes_buffer_constructor_fn)(void);
|
||||
/* A static of that value by name (`Buffer.alloc` read as a value). +1. */
|
||||
struct ScrDyn *scr_buffer_static_value(const ScrStr *name);
|
||||
/* `Buffer.prototype[key]` (scr_bytes_dyn.c). +1, or NULL with the
|
||||
* exception pending. */
|
||||
struct ScrDyn *scr_buffer_prototype_member(const struct ScrDyn *key);
|
||||
ScrBytes *scr_buffer_concat_len_dyn(const struct ScrDyn *list, double total);
|
||||
/* Drops the pool's slab reference and restores Buffer.poolSize (process
|
||||
* exit audits and library session resets). */
|
||||
|
||||
@@ -592,3 +592,215 @@ ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method,
|
||||
}
|
||||
return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what);
|
||||
}
|
||||
|
||||
/* A fixed-width read or write name (readUInt8, writeDoubleLE, ...). */
|
||||
static bool scr_bytes_dyn_num_method_name(const char *method) {
|
||||
bool read = !strncmp(method, "read", 4);
|
||||
if (!read && strncmp(method, "write", 5)) return false;
|
||||
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 true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ── Buffer.prototype members read as values ────────────────────────────
|
||||
* `Buffer.prototype[name]` (smart-buffer's typeof probe for the BigInt
|
||||
* methods): an own member answers one function per name, with the name and
|
||||
* length Node's function has; the parent and offset getters, read on the
|
||||
* prototype itself, answer undefined; constructor is the Buffer value.
|
||||
* Members inherited from Uint8Array.prototype and Object.prototype, symbol
|
||||
* keys and object keys throw SC2020, and other keys answer undefined. The
|
||||
* functions run the method on a Buffer receiver (`.call(buf, ...)`); other
|
||||
* receivers throw SC2020. */
|
||||
static const struct { const char *key; const char *name; uint32_t length; } scr_buffer_proto_own[] = {
|
||||
{"readBigUInt64LE", "readBigUInt64LE", 0},
|
||||
{"readBigUInt64BE", "readBigUInt64BE", 0},
|
||||
{"readBigUint64LE", "readBigUInt64LE", 0},
|
||||
{"readBigUint64BE", "readBigUInt64BE", 0},
|
||||
{"readBigInt64LE", "readBigInt64LE", 0},
|
||||
{"readBigInt64BE", "readBigInt64BE", 0},
|
||||
{"writeBigUInt64LE", "writeBigUInt64LE", 1},
|
||||
{"writeBigUInt64BE", "writeBigUInt64BE", 1},
|
||||
{"writeBigUint64LE", "writeBigUInt64LE", 1},
|
||||
{"writeBigUint64BE", "writeBigUInt64BE", 1},
|
||||
{"writeBigInt64LE", "writeBigInt64LE", 1},
|
||||
{"writeBigInt64BE", "writeBigInt64BE", 1},
|
||||
{"readUIntLE", "readUIntLE", 2},
|
||||
{"readUInt32LE", "", 1},
|
||||
{"readUInt16LE", "", 1},
|
||||
{"readUInt8", "", 1},
|
||||
{"readUIntBE", "readUIntBE", 2},
|
||||
{"readUInt32BE", "", 1},
|
||||
{"readUInt16BE", "", 1},
|
||||
{"readUintLE", "readUIntLE", 2},
|
||||
{"readUint32LE", "", 1},
|
||||
{"readUint16LE", "", 1},
|
||||
{"readUint8", "", 1},
|
||||
{"readUintBE", "readUIntBE", 2},
|
||||
{"readUint32BE", "", 1},
|
||||
{"readUint16BE", "", 1},
|
||||
{"readIntLE", "readIntLE", 2},
|
||||
{"readInt32LE", "", 1},
|
||||
{"readInt16LE", "", 1},
|
||||
{"readInt8", "", 1},
|
||||
{"readIntBE", "readIntBE", 2},
|
||||
{"readInt32BE", "", 1},
|
||||
{"readInt16BE", "", 1},
|
||||
{"writeUIntLE", "writeUIntLE", 3},
|
||||
{"writeUInt32LE", "writeUInt32LE", 1},
|
||||
{"writeUInt16LE", "writeUInt16LE", 1},
|
||||
{"writeUInt8", "writeUInt8", 1},
|
||||
{"writeUIntBE", "writeUIntBE", 3},
|
||||
{"writeUInt32BE", "writeUInt32BE", 1},
|
||||
{"writeUInt16BE", "writeUInt16BE", 1},
|
||||
{"writeUintLE", "writeUIntLE", 3},
|
||||
{"writeUint32LE", "writeUInt32LE", 1},
|
||||
{"writeUint16LE", "writeUInt16LE", 1},
|
||||
{"writeUint8", "writeUInt8", 1},
|
||||
{"writeUintBE", "writeUIntBE", 3},
|
||||
{"writeUint32BE", "writeUInt32BE", 1},
|
||||
{"writeUint16BE", "writeUInt16BE", 1},
|
||||
{"writeIntLE", "writeIntLE", 3},
|
||||
{"writeInt32LE", "writeInt32LE", 1},
|
||||
{"writeInt16LE", "writeInt16LE", 1},
|
||||
{"writeInt8", "writeInt8", 1},
|
||||
{"writeIntBE", "writeIntBE", 3},
|
||||
{"writeInt32BE", "writeInt32BE", 1},
|
||||
{"writeInt16BE", "writeInt16BE", 1},
|
||||
{"readFloatLE", "readFloatForwards", 0},
|
||||
{"readFloatBE", "readFloatBackwards", 0},
|
||||
{"readDoubleLE", "readDoubleForwards", 0},
|
||||
{"readDoubleBE", "readDoubleBackwards", 0},
|
||||
{"writeFloatLE", "writeFloatForwards", 1},
|
||||
{"writeFloatBE", "writeFloatBackwards", 1},
|
||||
{"writeDoubleLE", "writeDoubleForwards", 1},
|
||||
{"writeDoubleBE", "writeDoubleBackwards", 1},
|
||||
{"asciiSlice", "", 2},
|
||||
{"base64Slice", "", 2},
|
||||
{"base64urlSlice", "", 2},
|
||||
{"latin1Slice", "", 2},
|
||||
{"hexSlice", "", 2},
|
||||
{"ucs2Slice", "", 2},
|
||||
{"utf8Slice", "", 2},
|
||||
{"asciiWrite", "", 3},
|
||||
{"base64Write", "", 3},
|
||||
{"base64urlWrite", "", 3},
|
||||
{"latin1Write", "", 3},
|
||||
{"hexWrite", "", 3},
|
||||
{"ucs2Write", "", 3},
|
||||
{"utf8Write", "", 3},
|
||||
{"parent", NULL, 0},
|
||||
{"offset", NULL, 0},
|
||||
{"copy", "copy", 4},
|
||||
{"toString", "toString", 3},
|
||||
{"equals", "equals", 1},
|
||||
{"inspect", "inspect", 2},
|
||||
{"compare", "compare", 5},
|
||||
{"indexOf", "indexOf", 4},
|
||||
{"lastIndexOf", "lastIndexOf", 4},
|
||||
{"includes", "includes", 4},
|
||||
{"fill", "fill", 4},
|
||||
{"write", "write", 4},
|
||||
{"toJSON", "toJSON", 0},
|
||||
{"subarray", "subarray", 2},
|
||||
{"slice", "slice", 2},
|
||||
{"swap16", "swap16", 0},
|
||||
{"swap32", "swap32", 0},
|
||||
{"swap64", "swap64", 0},
|
||||
{"toLocaleString", "toString", 3},
|
||||
};
|
||||
|
||||
static const char *const scr_buffer_proto_inherited[] = {
|
||||
"BYTES_PER_ELEMENT", "buffer", "byteLength", "byteOffset", "length", "entries", "keys", "values",
|
||||
"at", "copyWithin", "every", "filter", "find", "findIndex", "findLast", "findLastIndex",
|
||||
"forEach", "join", "map", "reverse", "reduce", "reduceRight", "set", "some", "sort", "toReversed",
|
||||
"toSorted", "with", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__",
|
||||
"__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "valueOf", "__proto__",
|
||||
};
|
||||
|
||||
static SCR_TL ScrDyn *scr_buffer_proto_fns;
|
||||
|
||||
static void scr_buffer_proto_cleanup(void) {
|
||||
scr_dyn_release(scr_buffer_proto_fns);
|
||||
scr_buffer_proto_fns = NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_buffer_proto_unsupported(const char *format, const char *name) {
|
||||
char message[200];
|
||||
int n = snprintf(message, sizeof message, format, name);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_buffer_proto_run(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc) {
|
||||
if (!scr_bytes_dyn_buffer(recv))
|
||||
return scr_buffer_proto_unsupported("Buffer.prototype.%s on a receiver that is not a Buffer is not supported yet",
|
||||
method);
|
||||
const char *what = "Buffer.prototype method";
|
||||
/* The method-id cache slot of a call site, here a fresh one per call. */
|
||||
int32_t slot = -1;
|
||||
int32_t *mid = &slot;
|
||||
if (scr_bytes_dyn_num_method_name(method)) return scr_dyn_invoke_buffer_num(recv, NULL, method, mid, args, argc, what);
|
||||
if (!strcmp(method, "write")) return scr_dyn_invoke_buffer_write(recv, NULL, method, mid, args, argc, what);
|
||||
if (!strcmp(method, "fill")) return scr_dyn_invoke_buffer_fill(recv, NULL, method, mid, args, argc, what);
|
||||
if (!strcmp(method, "toString") || !strcmp(method, "toLocaleString"))
|
||||
return scr_dyn_invoke_buffer_to_string(recv, NULL, "toString", mid, args, argc, what);
|
||||
if (!strcmp(method, "indexOf") || !strcmp(method, "lastIndexOf") || !strcmp(method, "includes"))
|
||||
return scr_dyn_invoke_buffer_search(recv, NULL, method, mid, args, argc, what);
|
||||
if (!strcmp(method, "subarray") || !strcmp(method, "slice"))
|
||||
return scr_dyn_invoke(recv, method, args, argc, what);
|
||||
if (!strcmp(method, "inspect"))
|
||||
return scr_buffer_proto_unsupported("Buffer.prototype.%s through a stored value is not supported yet", method);
|
||||
return scr_dyn_invoke_buffer(recv, NULL, method, mid, args, argc, what);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_buffer_proto_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *recv = scr_dyn_this_get();
|
||||
ScrDyn *result = scr_buffer_proto_run(recv, closure->fn, args, argc);
|
||||
scr_dyn_release(recv);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScrDyn *scr_buffer_prototype_member(const ScrDyn *key) {
|
||||
if (key->kind == SCR_DYN_SYMBOL)
|
||||
return scr_buffer_proto_unsupported("Buffer.prototype members keyed by %s are not supported yet", "symbols");
|
||||
if (key->kind != SCR_DYN_STR) {
|
||||
if (key->kind == SCR_DYN_NUM || key->kind == SCR_DYN_BOOL || key->kind == SCR_DYN_UNDEF ||
|
||||
key->kind == SCR_DYN_NULL || key->kind == SCR_DYN_BIGINT)
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
return scr_buffer_proto_unsupported("Buffer.prototype members keyed by %s are not supported yet", "objects");
|
||||
}
|
||||
const ScrStr *name = key->v.str;
|
||||
if (name->len == 11 && memcmp(name->data, "constructor", 11) == 0) {
|
||||
/* A program that reads the Buffer function's value answers it (the
|
||||
* builtin-values unit installs the hook); otherwise Buffer keeps its
|
||||
* builtin identity token, as typed array reads do (scr_json.c). */
|
||||
if (scr_bytes_buffer_constructor_fn) return scr_bytes_buffer_constructor_fn();
|
||||
ScrStr *token = scr_str_new("[builtin Buffer]", 16);
|
||||
ScrDyn *result = scr_dyn_new_str(token);
|
||||
scr_str_release(token);
|
||||
return result;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof scr_buffer_proto_inherited / sizeof scr_buffer_proto_inherited[0]; i++) {
|
||||
const char *inherited = scr_buffer_proto_inherited[i];
|
||||
if (strlen(inherited) == name->len && memcmp(inherited, name->data, name->len) == 0)
|
||||
return scr_buffer_proto_unsupported("Buffer.prototype.%s is not supported yet", inherited);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof scr_buffer_proto_own / sizeof scr_buffer_proto_own[0]; i++) {
|
||||
const char *own = scr_buffer_proto_own[i].key;
|
||||
if (strlen(own) != name->len || memcmp(own, name->data, name->len) != 0) continue;
|
||||
if (!scr_buffer_proto_own[i].name) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (!scr_buffer_proto_fns) {
|
||||
scr_buffer_proto_fns = scr_dyn_new_obj_null_proto();
|
||||
scr_atexit(scr_buffer_proto_cleanup);
|
||||
}
|
||||
ScrDyn *fn = scr_dyn_obj_get(scr_buffer_proto_fns, own, name->len);
|
||||
if (!fn) {
|
||||
fn = scr_dyn_new_func(scr_closure_new((void *)own, 0), scr_buffer_proto_call, scr_buffer_proto_own[i].length,
|
||||
"native:function", scr_buffer_proto_own[i].name);
|
||||
scr_dyn_obj_set(scr_buffer_proto_fns, own, name->len, fn);
|
||||
}
|
||||
return scr_dyn_retain(fn);
|
||||
}
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
@@ -1299,13 +1299,16 @@ ScrBytes *scr_buffer_concat_len_dyn(const ScrDyn *list, double total) {
|
||||
return scr_buffer_concat_dyn_impl(list, total, true);
|
||||
}
|
||||
|
||||
/* Buffer.from(arrayBuffer, byteOffset) over a checked ArrayBuffer or
|
||||
* SharedArrayBuffer, as Node's fromArrayBuffer: a Buffer from the offset to
|
||||
* the end of the buffer that shares its memory. The offset converts with
|
||||
* ToNumber, where a missing offset (NULL) or undefined and NaN are 0; an
|
||||
* offset past the end throws ERR_BUFFER_OUT_OF_BOUNDS, and one at or below
|
||||
* -1 V8's start-offset RangeError. NULL with the exception pending. */
|
||||
static ScrBytes *scr_buffer_from_storage(const ScrDyn *storage, const ScrDyn *offset_value) {
|
||||
/* Buffer.from(arrayBuffer, byteOffset, length) over a checked ArrayBuffer
|
||||
* or SharedArrayBuffer, as Node's fromArrayBuffer: a Buffer that shares its
|
||||
* memory. The offset converts with ToNumber, where a missing offset (NULL)
|
||||
* or undefined and NaN are 0; an offset past the end throws
|
||||
* ERR_BUFFER_OUT_OF_BOUNDS, and one at or below -1 V8's start-offset
|
||||
* RangeError. A missing length runs to the end; a given one converts with
|
||||
* ToNumber, past the end throws ERR_BUFFER_OUT_OF_BOUNDS for "length", and
|
||||
* zero, negative and NaN lengths are 0. NULL with the exception pending. */
|
||||
static ScrBytes *scr_buffer_from_storage(const ScrDyn *storage, const ScrDyn *offset_value,
|
||||
const ScrDyn *length_value) {
|
||||
double offset = 0;
|
||||
if (offset_value && offset_value->kind != SCR_DYN_UNDEF) {
|
||||
if (!scr_dyn_number_coerce_js(offset_value, &offset)) return NULL;
|
||||
@@ -1313,11 +1316,26 @@ static ScrBytes *scr_buffer_from_storage(const ScrDyn *storage, const ScrDyn *of
|
||||
}
|
||||
ScrBytes *bytes = storage->v.handle.ptr;
|
||||
ScrBytes *owner = bytes->backing ? bytes->backing : bytes;
|
||||
if ((double)scr_bytes_byte_len(owner) - offset < 0) {
|
||||
double max_length = (double)scr_bytes_byte_len(owner) - offset;
|
||||
if (max_length < 0) {
|
||||
static const char msg[] = "\"offset\" is outside of buffer bounds";
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
|
||||
return NULL;
|
||||
}
|
||||
bool has_length = length_value && length_value->kind != SCR_DYN_UNDEF;
|
||||
double length = max_length;
|
||||
if (has_length) {
|
||||
if (!scr_dyn_number_coerce_js(length_value, &length)) return NULL;
|
||||
if (length > 0) {
|
||||
if (length > max_length) {
|
||||
static const char msg[] = "\"length\" is outside of buffer bounds";
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
length = 0;
|
||||
}
|
||||
}
|
||||
double start = trunc(offset);
|
||||
if (start < 0) {
|
||||
/* The offset as JavaScript prints it (-1.5, -1e+21, -Infinity). */
|
||||
@@ -1331,7 +1349,7 @@ static ScrBytes *scr_buffer_from_storage(const ScrDyn *storage, const ScrDyn *of
|
||||
scr_throw_error(SCR_ERR_RANGE, message);
|
||||
return NULL;
|
||||
}
|
||||
ScrBytes *out = scr_bytes_buffer_view(bytes, SCR_BYTES_U8, start, false, 0);
|
||||
ScrBytes *out = scr_bytes_buffer_view(bytes, SCR_BYTES_U8, start, has_length, trunc(length));
|
||||
if (out) out->is_buffer = true;
|
||||
return out;
|
||||
}
|
||||
@@ -1340,7 +1358,7 @@ ScrBytes *scr_buffer_from_dyn(const ScrDyn *value, const ScrStr *encoding) {
|
||||
if (value->kind == SCR_DYN_STR) return scr_bytes_from_str(value->v.str, encoding);
|
||||
/* The second argument here is an encoding name, which as a byte offset
|
||||
* converts to NaN: offset 0. */
|
||||
if (scr_buffer_storage_is(value)) return scr_buffer_from_storage(value, NULL);
|
||||
if (scr_buffer_storage_is(value)) return scr_buffer_from_storage(value, NULL, NULL);
|
||||
if (value->kind == SCR_DYN_BYTES) return scr_bytes_convert(SCR_BYTES_U8, value->v.bytes);
|
||||
if (value->kind == SCR_DYN_ARR) {
|
||||
return scr_buffer_from_array_like(value, (double)value->v.arr.len);
|
||||
@@ -1386,7 +1404,7 @@ ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding
|
||||
return scr_buffer_from_bytes(value->v.bytes);
|
||||
/* A view of an ArrayBuffer shares its memory, so it never comes from the
|
||||
* pool. The second argument here is an encoding name: offset 0. */
|
||||
if (scr_buffer_storage_is(value)) return scr_buffer_from_storage(value, NULL);
|
||||
if (scr_buffer_storage_is(value)) return scr_buffer_from_storage(value, NULL, NULL);
|
||||
ScrBytes *out = scr_buffer_from_dyn(value, encoding);
|
||||
if (!out) return NULL;
|
||||
out->is_buffer = true;
|
||||
@@ -1413,21 +1431,51 @@ ScrBytes *scr_buffer_from_dyn_ref_pooled(const ScrDyn *value, const ScrStr *enco
|
||||
|
||||
/* Buffer.from(value, second) over checked values whose second argument is
|
||||
* not a literal encoding name: an ArrayBuffer or SharedArrayBuffer takes it
|
||||
* as the byte offset (scr_buffer_from_storage); other values convert as
|
||||
* scr_buffer_from_dyn_ref_pooled with utf8. */
|
||||
* as the byte offset (scr_buffer_from_storage); a string takes it as the
|
||||
* encoding (Node's fromString: utf8 unless it is a non-empty string, which
|
||||
* must name a Buffer encoding or throw ERR_UNKNOWN_ENCODING); other values
|
||||
* convert as scr_buffer_from_dyn_ref_pooled with utf8. */
|
||||
ScrBytes *scr_buffer_from_dyn_offset_pooled(const ScrDyn *value, const ScrDyn *second) {
|
||||
if (value->kind == SCR_DYN_STR) {
|
||||
if (second->kind == SCR_DYN_STR && second->v.str->len > 0)
|
||||
return scr_buffer_from_str_checked(value->v.str, second->v.str);
|
||||
ScrStr *utf8 = scr_str_new("utf8", 4);
|
||||
ScrBytes *out = scr_buffer_from_str(value->v.str, utf8);
|
||||
scr_str_release(utf8);
|
||||
return out;
|
||||
}
|
||||
if (!scr_buffer_storage_is(value)) {
|
||||
ScrStr *utf8 = scr_str_new("utf8", 4);
|
||||
ScrBytes *out = scr_buffer_from_dyn_ref_pooled(value, utf8);
|
||||
scr_str_release(utf8);
|
||||
return out;
|
||||
}
|
||||
return scr_buffer_from_storage(value, second);
|
||||
return scr_buffer_from_storage(value, second, NULL);
|
||||
}
|
||||
|
||||
/* Buffer.from(value, second, third): an ArrayBuffer or SharedArrayBuffer
|
||||
* takes them as its byte offset and length; other values convert as
|
||||
* scr_buffer_from_dyn_offset_pooled does, without the third argument. */
|
||||
ScrBytes *scr_buffer_from_dyn_range_pooled(const ScrDyn *value, const ScrDyn *second,
|
||||
const ScrDyn *third) {
|
||||
if (!scr_buffer_storage_is(value)) return scr_buffer_from_dyn_offset_pooled(value, second);
|
||||
return scr_buffer_from_storage(value, second, third);
|
||||
}
|
||||
|
||||
/* The deprecated Buffer constructor's DEP0005 state, Node's
|
||||
* bufferWarningAlreadyEmitted and nodeModulesCheckCounter: a call from
|
||||
* node_modules only counts toward the 10000-call check, and
|
||||
* scr_buffer_deprecation (scr_async_dyn.c) reports for the others. */
|
||||
SCR_TL bool scr_buffer_ctor_warned = false;
|
||||
SCR_TL uint64_t scr_buffer_ctor_checks = 0;
|
||||
|
||||
void scr_buffer_deprecation_count(void) {
|
||||
if (!scr_buffer_ctor_warned) scr_buffer_ctor_checks++;
|
||||
}
|
||||
|
||||
/* new Buffer(number, encoding) — the deprecated ctor's string arm with a
|
||||
* non-string first argument: Node's exact ERR_INVALID_ARG_TYPE (the
|
||||
* throwing path never fires DEP0005, so compiled silence matches). */
|
||||
* non-string first argument: Node's exact ERR_INVALID_ARG_TYPE, thrown
|
||||
* after scr_buffer_deprecation has reported DEP0005. */
|
||||
ScrBytes *scr_buffer_new_string_fail(const ScrDyn *got) {
|
||||
scr_dyn_arg_type_fail("string", "of type string", got);
|
||||
return NULL;
|
||||
|
||||
@@ -1389,6 +1389,10 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
|
||||
* for anonymous) and "length" (the boxed arity). Returns +1, or NULL when
|
||||
* the key answers nothing (the caller's undefined). Never throws. */
|
||||
ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len);
|
||||
/* A function value's own property table, created on first use. +1. */
|
||||
ScrDyn *scr_dyn_function_properties(const ScrDyn *function);
|
||||
/* The table a function value already has. +1, or NULL. */
|
||||
ScrDyn *scr_dyn_function_properties_existing(const ScrDyn *function);
|
||||
/* Object.defineProperties over dyn values (targets: OBJ and FUNC): each
|
||||
* descriptor's `value` becomes an own data property. OBJ targets preserve
|
||||
* writable/enumerable/configurable; FUNC targets still use plain properties.
|
||||
|
||||
@@ -1600,7 +1600,45 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
} else {
|
||||
ib_cstr(&out, "[Function (anonymous)]");
|
||||
}
|
||||
return ib_take(&out);
|
||||
/* A function's own enumerable properties follow its base, as for an
|
||||
* object (`[Function: f] { x: 1 }`), and past the depth limit it is
|
||||
* `[Function]` (formatRaw). Classes keep the base. */
|
||||
ScrDyn *table = d->v.fn.class_obj ? NULL : scr_dyn_function_properties_existing(d);
|
||||
bool shown = false;
|
||||
for (size_t i = 0; table && i < table->v.obj.len && !shown; i++) shown = table->v.obj.entries[i].enumerable;
|
||||
for (size_t i = 0; table && !shown && table->symbol_keys && i < table->symbol_keys->v.arr.len; i++)
|
||||
shown = scr_insp_symbol_entry(table, i) != NULL;
|
||||
if (!shown) {
|
||||
scr_dyn_release(table);
|
||||
return ib_take(&out);
|
||||
}
|
||||
/* Every value of one function shares its table: the table is the
|
||||
* function's identity for the circular checks. */
|
||||
double circular = scr_insp_circ_check(table);
|
||||
if (circular != 0) {
|
||||
scr_dyn_release(table);
|
||||
free(out.data);
|
||||
return scr_insp_circular(circular);
|
||||
}
|
||||
if (recurse > depth) {
|
||||
scr_dyn_release(table);
|
||||
free(out.data);
|
||||
return scr_str_new("[Function]", 10);
|
||||
}
|
||||
ScrStr *base = ib_take(&out);
|
||||
char *cls = malloc(base->len + 1);
|
||||
if (!cls) scr_trap("scriptc: out of memory\n");
|
||||
memcpy(cls, base->data, base->len);
|
||||
cls[base->len] = '\0';
|
||||
scr_str_release(base);
|
||||
size_t nseen = g_nseen;
|
||||
g_pending_seen = table;
|
||||
ScrStr *rendered = scr_insp_obj(table, recurse, depth, cls);
|
||||
g_pending_seen = NULL;
|
||||
if (g_nseen > nseen) g_nseen = nseen;
|
||||
free(cls);
|
||||
scr_dyn_release(table);
|
||||
return rendered;
|
||||
}
|
||||
case SCR_DYN_HANDLE: {
|
||||
if (d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER ||
|
||||
|
||||
@@ -2444,6 +2444,10 @@ static ScrDyn *scr_dyn_fn_existing_table(const ScrDyn *function) {
|
||||
return owner->props ? (ScrDyn *)scr_box_get_ref(owner->props) : NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_function_properties_existing(const ScrDyn *function) {
|
||||
return scr_dyn_fn_existing_table(function);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object) || scr_dyn_error_prototype_fence(object)) return NULL;
|
||||
if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.preventExtensions"); return NULL; }
|
||||
@@ -4404,6 +4408,9 @@ bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const Sc
|
||||
}
|
||||
if (scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_is(value);
|
||||
if (scr_array_ctor && callee->v.fn.clo == scr_array_ctor->v.fn.clo) return value->kind == SCR_DYN_ARR || scr_dyn_ref_is_array(value);
|
||||
/* The Buffer function value: Buffer instances, not other Uint8Arrays. */
|
||||
if (callee->v.fn.sig && strcmp(callee->v.fn.sig, "native:Buffer") == 0)
|
||||
return value->kind == SCR_DYN_BYTES && value->buffer;
|
||||
for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) {
|
||||
if (scr_bytes_ctors[i] && callee->v.fn.clo == scr_bytes_ctors[i]->v.fn.clo)
|
||||
return scr_dyn_typed_array_is(value, (int)i);
|
||||
@@ -4466,6 +4473,7 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee) {
|
||||
return scr_bytes_instanceof_at(value, callee, NULL);
|
||||
}
|
||||
SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) = NULL;
|
||||
ScrDyn *(*scr_bytes_buffer_constructor_fn)(void) = NULL;
|
||||
|
||||
ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what) {
|
||||
if (callee->kind == SCR_DYN_FUNC && scr_array_ctor && callee->v.fn.clo == scr_array_ctor->v.fn.clo)
|
||||
@@ -4486,6 +4494,14 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS
|
||||
ScrDyn *constructed = NULL;
|
||||
if (scr_dyn_global_construct_fn && scr_dyn_global_construct_fn(callee, args, &constructed))
|
||||
return constructed;
|
||||
/* The Buffer function value (scr_builtin_values.c): Node's construction
|
||||
* reports DEP0005 by the caller's location, which a stored value does
|
||||
* not know. */
|
||||
if (callee->kind == SCR_DYN_FUNC && callee->v.fn.sig && strcmp(callee->v.fn.sig, "native:Buffer") == 0) {
|
||||
static const char message[] = "Calling or constructing Buffer through a stored Buffer value is not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_write(&message, what->data, what->len);
|
||||
@@ -4515,7 +4531,9 @@ ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) {
|
||||
return scr_dyn_new_num((double)scr_bytes_elem_size(bytes->elem));
|
||||
if (key->len == 11 && memcmp(key->data, "constructor", 11) == 0) {
|
||||
if (!value->buffer) return scr_bytes_constructor_for(bytes->elem);
|
||||
/* Buffer retains its separate builtin identity. */
|
||||
/* A program that reads the Buffer function's value answers it;
|
||||
* otherwise Buffer keeps its separate builtin identity token. */
|
||||
if (scr_bytes_buffer_constructor_fn) return scr_bytes_buffer_constructor_fn();
|
||||
char token[64];
|
||||
int length = snprintf(token, sizeof token, "[builtin %s]", value->buffer ? "Buffer" : scr_bytes_elem_name(bytes->elem));
|
||||
ScrStr *name = scr_str_new(token, (size_t)length);
|
||||
@@ -5509,6 +5527,8 @@ static ScrDyn *scr_dyn_fn_properties(const ScrDyn *function) {
|
||||
return (ScrDyn *)scr_box_get_ref(owner->props);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_function_properties(const ScrDyn *function) { return scr_dyn_fn_properties(function); }
|
||||
|
||||
ScrDyn *scr_dyn_class_inherit(ScrDyn *constructor, ScrDyn *base) {
|
||||
if (constructor->kind != SCR_DYN_FUNC || !constructor->v.fn.class_obj || (base->kind != SCR_DYN_FUNC && base->kind != SCR_DYN_UNDEF)) {
|
||||
static const char message[] = "Class inheritance requires native constructors";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @no-deprecation
|
||||
// Buffer's invalid-argument ladders, Node-exact: Buffer.compare /
|
||||
// buf.compare / buf.equals over non-buffer values and bad offsets throw
|
||||
// ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE with Node's argument names and
|
||||
@@ -7,6 +6,8 @@
|
||||
// real answers. Plus the deprecated `new Buffer(number, enc)` string-arm
|
||||
// rejection and the ERR_* codes on the write/swap/fill range ladders.
|
||||
'use strict';
|
||||
// The deprecated constructor's DEP0005 report carries a process id.
|
||||
process.noDeprecation = true;
|
||||
const show = (fn) => {
|
||||
try {
|
||||
console.log('ret', fn());
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Buffer.from(arrayBuffer, byteOffset, length): Node's fromArrayBuffer over
|
||||
// typed and untyped ArrayBuffers and SharedArrayBuffers: the offset and
|
||||
// length conversions, the shared memory, zero and negative lengths, and
|
||||
// ERR_BUFFER_OUT_OF_BOUNDS for an offset or length past the end.
|
||||
const ab = new ArrayBuffer(6);
|
||||
new Uint8Array(ab).set([1, 2, 3, 4, 5, 6]);
|
||||
const offsets: unknown[] = [undefined, NaN, "1", 1.5, -1, 0, 6, 7, null, true];
|
||||
const lengths: unknown[] = [undefined, 0, -1, 2.5, "2", 6, 9, NaN, null];
|
||||
for (const o of offsets) {
|
||||
for (const l of lengths) {
|
||||
try {
|
||||
const b = Buffer.from(ab, o as number, l as number);
|
||||
console.log(String(o), String(l), b, b.byteOffset, b.length);
|
||||
} catch (e) {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
console.log(String(o), String(l), err.name, err.code, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
const typed = Buffer.from(ab, 2, 2);
|
||||
typed[0] = 99;
|
||||
console.log(new Uint8Array(ab)[2], Buffer.from(ab, 4).toString("hex"), Buffer.isBuffer(Buffer.from(ab, 1, 1)));
|
||||
const shared = new SharedArrayBuffer(4);
|
||||
console.log(Buffer.from(shared, 1, 2).length);
|
||||
const loose: any = new ArrayBuffer(4);
|
||||
const pairs: [unknown, unknown][] = [[1, 2], [4, undefined], [5, 1], [0, 5], ["2", "1"]];
|
||||
for (const [o, l] of pairs) {
|
||||
try {
|
||||
console.log(Buffer.from(loose, o as number, l as number).toString("hex"));
|
||||
} catch (e) {
|
||||
console.log((e as NodeJS.ErrnoException).code, (e as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Buffer.from(arrayBuffer, byteOffset, length) in JavaScript, with offsets
|
||||
// and lengths of every kind and an ArrayBuffer held in an untyped slot.
|
||||
const ab = new ArrayBuffer(6);
|
||||
new Uint8Array(ab).set([1, 2, 3, 4, 5, 6]);
|
||||
const offsets = [undefined, NaN, "1", 1.5, -1, 0, 6, 7, null, true];
|
||||
const lengths = [undefined, 0, -1, 2.5, "2", 6, 9, NaN, null];
|
||||
for (const o of offsets) {
|
||||
for (const l of lengths) {
|
||||
try {
|
||||
const b = Buffer.from(ab, o, l);
|
||||
console.log(String(o), String(l), b, b.byteOffset, b.length);
|
||||
} catch (e) {
|
||||
const err = e;
|
||||
console.log(String(o), String(l), err.name, err.code, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
const typed = Buffer.from(ab, 2, 2);
|
||||
typed[0] = 99;
|
||||
console.log(new Uint8Array(ab)[2], Buffer.from(ab, 4).toString("hex"), Buffer.isBuffer(Buffer.from(ab, 1, 1)));
|
||||
const shared = new SharedArrayBuffer(4);
|
||||
console.log(Buffer.from(shared, 1, 2).length);
|
||||
const slots = { buffer: new ArrayBuffer(3) };
|
||||
new Uint8Array(slots.buffer).set([9, 8, 7]);
|
||||
for (const args of [[1], [1, 1], [0, 0], [3, 0], [2, 5], [-0.5, 2]]) {
|
||||
try {
|
||||
console.log(args.join(","), Buffer.from(slots.buffer, args[0], args[1]));
|
||||
} catch (e) {
|
||||
console.log(args.join(","), e.code, e.message);
|
||||
}
|
||||
}
|
||||
// A string's second argument is its encoding, with or without a third.
|
||||
const encoded = JSON.parse('"aGk="');
|
||||
console.log(
|
||||
Buffer.from(encoded, "base64", 1).toString(),
|
||||
Buffer.from(encoded, "latin1", 2).length,
|
||||
Buffer.from(encoded, undefined, 3).toString(),
|
||||
Buffer.from(encoded, "", 3).toString(),
|
||||
);
|
||||
for (const text of [encoded, JSON.parse('""')]) {
|
||||
try {
|
||||
console.log(Buffer.from(text, "bogus", 1));
|
||||
} catch (e) {
|
||||
console.log(e.code, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// The deprecated Buffer constructor, called and constructed, as Node's
|
||||
// Buffer function runs it: the arguments evaluate in order, the first call
|
||||
// reports DEP0005 on the next tick (once), a number allocates zero-filled
|
||||
// bytes, and other values convert as Buffer.from does.
|
||||
// The program re-runs itself and prints the child's output with process
|
||||
// ids normalized.
|
||||
"use strict";
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const { Buffer: Imported } = require("buffer");
|
||||
|
||||
function child() {
|
||||
process.on("warning", (w) => console.log("listener", w.name, w.code, w.message.length));
|
||||
const B = Buffer;
|
||||
|
||||
const show = (label, fn) => {
|
||||
try {
|
||||
const b = fn();
|
||||
console.log(label, b, b.length, Buffer.isBuffer(b));
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// The throwing form still reports the warning, after the catch.
|
||||
show("number+encoding", () => new Buffer(3, "utf8"));
|
||||
show("size", () => new Buffer(4));
|
||||
show("fraction", () => new Buffer(2.7));
|
||||
show("zero", () => Buffer(0));
|
||||
show("negative", () => new Buffer(-1));
|
||||
show("nan", () => new Buffer(NaN));
|
||||
show("string", () => new Buffer("héllo"));
|
||||
show("hex", () => new Buffer("6869", "hex"));
|
||||
show("base64", () => Buffer("aGk=", "base64"));
|
||||
show("empty encoding", () => new Buffer("x", ""));
|
||||
show("bad encoding", () => new Buffer("x", "nope"));
|
||||
show("array", () => new Buffer([1, 2, 300, -1]));
|
||||
show("array-like", () => new Buffer({ length: 2, 0: 7, 1: 8 }));
|
||||
show("buffer", () => new B(Buffer.from("copy")));
|
||||
show("typed", () => Buffer(new Uint16Array([1, 258])));
|
||||
const storage = new Uint8Array([10, 20, 30, 40, 50]).buffer;
|
||||
show("arraybuffer", () => new Buffer(storage));
|
||||
show("offset", () => new Buffer(storage, 1));
|
||||
show("range", () => Buffer(storage, 1, 3));
|
||||
show("range past end", () => new Buffer(storage, 6));
|
||||
show("string third", () => new Buffer("abc", "latin1", 1));
|
||||
show("undefined", () => new Buffer(undefined));
|
||||
show("null", () => Buffer(null));
|
||||
show("none", () => new Buffer());
|
||||
show("imported", () => new Imported("imp"));
|
||||
|
||||
console.log(Buffer(2), Buffer("ab").length, Buffer("hi").toString("hex"), new Buffer([5]));
|
||||
console.log(Buffer.isBuffer(Buffer(1)), Buffer(1) instanceof Uint8Array, new Buffer(1) instanceof Uint8Array);
|
||||
|
||||
// The view shares the ArrayBuffer's memory.
|
||||
const view = new Buffer(storage, 2, 2);
|
||||
new Uint8Array(storage)[2] = 99;
|
||||
console.log(view);
|
||||
|
||||
const order = [];
|
||||
const arg = (v) => (order.push(v), v);
|
||||
new Buffer(arg("a"), arg("utf8"));
|
||||
Buffer(arg(2));
|
||||
console.log(order);
|
||||
|
||||
console.log("sync end");
|
||||
process.nextTick(() => console.log("tick"));
|
||||
setTimeout(() => {
|
||||
new Buffer(1);
|
||||
console.log("timer");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (process.argv[2] === "child") {
|
||||
child();
|
||||
} else {
|
||||
const run = spawnSync(process.execPath, [process.argv[1], "child"], { encoding: "utf8" });
|
||||
console.log(`status: ${run.status}`);
|
||||
console.log(run.stdout);
|
||||
console.log(run.stderr.replace(/\(node:\d+\)/g, "(node:<pid>)"));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// The deprecated `new Buffer(...)` in TypeScript, over the forms
|
||||
// @types/node declares: strings with an encoding, sizes, arrays and
|
||||
// ArrayBuffers. The first call reports DEP0005 on the next tick.
|
||||
// The program re-runs itself and prints the child's output with process
|
||||
// ids normalized.
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
function child(): void {
|
||||
process.on("warning", (w) => console.log("listener", w.name, (w as { code?: string }).code));
|
||||
|
||||
function sized(n: number): Buffer {
|
||||
return new Buffer(n);
|
||||
}
|
||||
|
||||
const encodings: BufferEncoding[] = ["utf8", "hex", "base64", "latin1"];
|
||||
for (const encoding of encodings) {
|
||||
console.log(encoding, new Buffer("6869", encoding));
|
||||
}
|
||||
console.log(sized(3), sized(0).length);
|
||||
console.log(new Buffer([104, 105]).toString(), new Buffer(new Uint8Array([1, 2])));
|
||||
const storage = new ArrayBuffer(4);
|
||||
const view = new Buffer(storage);
|
||||
view[1] = 5;
|
||||
console.log(new Uint8Array(storage), view.buffer === storage);
|
||||
const text: string = "typed";
|
||||
console.log(new Buffer(text).toString("hex"));
|
||||
try {
|
||||
sized(-2);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as { code?: string }).code);
|
||||
}
|
||||
console.log("sync end");
|
||||
}
|
||||
|
||||
if (process.argv[2] === "child") {
|
||||
child();
|
||||
} else {
|
||||
const run = spawnSync(process.execPath, [process.argv[1]!, "child"], { encoding: "utf8" });
|
||||
console.log(`status: ${run.status}`);
|
||||
console.log(run.stdout);
|
||||
console.log(run.stderr.replace(/\(node:\d+\)/g, "(node:<pid>)"));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// The deprecated Buffer constructor called from a dependency under
|
||||
// node_modules: Node reports no DEP0005 for those calls, and the program's
|
||||
// own first call reports it once, on the next tick.
|
||||
// The program re-runs itself and prints the child's output with process
|
||||
// ids normalized.
|
||||
"use strict";
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const legacy = require("legacy-buf");
|
||||
|
||||
function child() {
|
||||
process.on("warning", (w) => console.log("listener", w.name, w.code));
|
||||
|
||||
console.log(legacy.construct("hi", "utf8"), legacy.construct(3), legacy.call([1, 2]));
|
||||
const storage = new Uint8Array([1, 2, 3, 4]).buffer;
|
||||
console.log(legacy.construct(storage, 1, 2), legacy.many(5));
|
||||
try {
|
||||
legacy.construct(4, "hex");
|
||||
} catch (e) {
|
||||
console.log(e.code);
|
||||
}
|
||||
process.nextTick(() => {
|
||||
console.log("tick");
|
||||
console.log(new Buffer("own"));
|
||||
console.log(Buffer(2), new Buffer([9]));
|
||||
setTimeout(() => console.log("timer"), 0);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv[2] === "child") {
|
||||
child();
|
||||
} else {
|
||||
const run = spawnSync(process.execPath, [process.argv[1], "child"], { encoding: "utf8" });
|
||||
console.log(`status: ${run.status}`);
|
||||
console.log(run.stdout);
|
||||
console.log(run.stderr.replace(/\(node:\d+\)/g, "(node:<pid>)"));
|
||||
}
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export declare function construct(value: unknown, encodingOrOffset?: unknown, length?: unknown): Buffer;
|
||||
export declare function call(value: unknown): Buffer;
|
||||
export declare function many(count: number): number;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
// A dependency that keeps the deprecated Buffer constructor.
|
||||
exports.construct = function (value, encodingOrOffset, length) {
|
||||
if (length !== undefined) return new Buffer(value, encodingOrOffset, length);
|
||||
if (encodingOrOffset !== undefined) return new Buffer(value, encodingOrOffset);
|
||||
return new Buffer(value);
|
||||
};
|
||||
exports.call = function (value) {
|
||||
return Buffer(value);
|
||||
};
|
||||
exports.many = function (count) {
|
||||
let total = 0;
|
||||
for (let i = 0; i < count; i++) total += new Buffer(1).length;
|
||||
return total;
|
||||
};
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "legacy-buf",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Node checks the caller of the deprecated Buffer constructor for its first
|
||||
// 10000 calls only: once a dependency under node_modules has made them, the
|
||||
// program's own calls report no DEP0005.
|
||||
"use strict";
|
||||
process.on("warning", (w) => console.log("listener", w.code));
|
||||
const legacy = require("legacy-buf");
|
||||
|
||||
console.log(legacy.many(10000));
|
||||
console.log(new Buffer("after"), Buffer(1));
|
||||
setTimeout(() => console.log("timer"), 0);
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export declare function construct(value: unknown, encodingOrOffset?: unknown, length?: unknown): Buffer;
|
||||
export declare function call(value: unknown): Buffer;
|
||||
export declare function many(count: number): number;
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
// A dependency that keeps the deprecated Buffer constructor.
|
||||
exports.construct = function (value, encodingOrOffset, length) {
|
||||
if (length !== undefined) return new Buffer(value, encodingOrOffset, length);
|
||||
if (encodingOrOffset !== undefined) return new Buffer(value, encodingOrOffset);
|
||||
return new Buffer(value);
|
||||
};
|
||||
exports.call = function (value) {
|
||||
return Buffer(value);
|
||||
};
|
||||
exports.many = function (count) {
|
||||
let total = 0;
|
||||
for (let i = 0; i < count; i++) total += new Buffer(1).length;
|
||||
return total;
|
||||
};
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "legacy-buf",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js"
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// The Buffer function read as a value: one identity across the global, the
|
||||
// buffer module and its aliases, Node's own enumerable members in Node's
|
||||
// order, statics that apply Node's argument handling, instanceof, and a
|
||||
// poolSize property the pool reads.
|
||||
"use strict";
|
||||
const buffer = require("buffer");
|
||||
const nodeBuffer = require("node:buffer");
|
||||
const util = require("util");
|
||||
|
||||
// bn.js keeps the module's Buffer in a variable assigned under try.
|
||||
var BnBuffer;
|
||||
try {
|
||||
BnBuffer = require("buffer").Buffer;
|
||||
} catch (e) {}
|
||||
const pass = (value) => value;
|
||||
const Stored = pass(require("buffer").Buffer);
|
||||
const Alias = Buffer;
|
||||
const table = { Buffer: buffer.Buffer };
|
||||
|
||||
console.log(typeof Stored, Stored.name, Stored.length, Alias.name, Alias.length);
|
||||
console.log(Stored === Buffer, Stored === globalThis.Buffer, Stored === buffer.Buffer, Stored === nodeBuffer.Buffer);
|
||||
console.log(Stored === Alias, table.Buffer === Stored, Stored.constructor === Function);
|
||||
console.log(Stored);
|
||||
console.log(Object.keys(Stored));
|
||||
console.log(util.inspect({ nested: { Buffer: Stored } }, { depth: 1 }));
|
||||
|
||||
// Statics through the value.
|
||||
const show = (label, run) => {
|
||||
try {
|
||||
console.log(label, run());
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
};
|
||||
show("from string", () => Stored.from("héllo"));
|
||||
show("from hex", () => Stored.from("6869", "hex"));
|
||||
show("from empty encoding", () => Stored.from("x", ""));
|
||||
show("from bad encoding", () => Stored.from("x", "nope"));
|
||||
show("from array", () => Stored.from([1, 2, 300]));
|
||||
const storage = new Uint8Array([1, 2, 3, 4, 5]).buffer;
|
||||
show("from range", () => Stored.from(storage, 1, 3));
|
||||
show("from number", () => Stored.from(5));
|
||||
show("alloc", () => Stored.alloc(3, "ab"));
|
||||
show("alloc fill", () => Stored.alloc(2, 7));
|
||||
show("alloc string size", () => Stored.alloc("3"));
|
||||
show("allocUnsafe", () => Stored.allocUnsafe(4).fill(9));
|
||||
show("allocUnsafe negative", () => Stored.allocUnsafe(-1));
|
||||
show("allocUnsafe string", () => Stored.allocUnsafe("4"));
|
||||
show("allocUnsafeSlow", () => Stored.allocUnsafeSlow(2).fill(1));
|
||||
show("isBuffer", () => [Stored.isBuffer(Buffer.alloc(1)), Stored.isBuffer(new Uint8Array(1)), Stored.isBuffer("x")]);
|
||||
show("isEncoding", () => [Stored.isEncoding("UTF-8"), Stored.isEncoding(""), Stored.isEncoding("nope"), Stored.isEncoding(1)]);
|
||||
show("compare", () => Stored.compare(Buffer.from("a"), Buffer.from("b")));
|
||||
show("compare bad", () => Stored.compare("a", Buffer.from("b")));
|
||||
show("concat", () => Stored.concat([Buffer.from("ab"), Buffer.from("cd")]));
|
||||
show("concat length", () => Stored.concat([Buffer.from("ab"), Buffer.from("cd")], 3));
|
||||
show("concat empty", () => Stored.concat([], "x"));
|
||||
show("concat bad length", () => Stored.concat([Buffer.from("a")], "x"));
|
||||
show("concat bad list", () => Stored.concat("x"));
|
||||
show("byteLength", () => [Stored.byteLength("héllo"), Stored.byteLength("aGk=", "base64"), Stored.byteLength(new Uint16Array(3))]);
|
||||
|
||||
// Member values keep one identity, with Node's names and lengths.
|
||||
const members = [
|
||||
[Buffer.from, Stored.from],
|
||||
[Buffer.alloc, Stored.alloc],
|
||||
[Buffer.allocUnsafe, Stored.allocUnsafe],
|
||||
[Buffer.isBuffer, Stored.isBuffer],
|
||||
[Buffer.compare, Stored.compare],
|
||||
[Buffer.concat, Stored.concat],
|
||||
[Buffer.byteLength, Stored.byteLength],
|
||||
[Buffer.isEncoding, Stored.isEncoding],
|
||||
];
|
||||
console.log(members.map(([a, b]) => `${a.name}/${a.length}/${a === b}`).join(" "));
|
||||
const isBuffer = require("buffer").Buffer.isBuffer;
|
||||
console.log(isBuffer(Buffer.from("x")), isBuffer(null), typeof Buffer.allocUnsafe);
|
||||
|
||||
// A bn.js-style allocator over Buffer or Array.
|
||||
function allocate(ArrayType, size) {
|
||||
if (ArrayType.allocUnsafe) return ArrayType.allocUnsafe(size);
|
||||
return new ArrayType(size);
|
||||
}
|
||||
function toArrayLike(ArrayType, size) {
|
||||
const made = allocate(ArrayType, size);
|
||||
made.fill(4);
|
||||
return made;
|
||||
}
|
||||
function toBuffer(size) {
|
||||
return BnBuffer ? toArrayLike(BnBuffer, size) : null;
|
||||
}
|
||||
const made = toBuffer(3);
|
||||
console.log(made, toArrayLike(Array, 2), made instanceof Stored, made instanceof Buffer);
|
||||
console.log(new Uint8Array(2) instanceof Stored, Buffer.from("x") instanceof Alias, pass("x") instanceof Stored);
|
||||
|
||||
// poolSize: one setting through the value and the static spelling.
|
||||
console.log(Stored.poolSize, Buffer.poolSize);
|
||||
Buffer.poolSize = 1024;
|
||||
console.log(Stored.poolSize);
|
||||
const small = Buffer.allocUnsafe(600);
|
||||
console.log(small.buffer.byteLength, Buffer.allocUnsafe(400).buffer.byteLength);
|
||||
Stored.poolSize = 2048;
|
||||
console.log(Buffer.poolSize, Buffer.allocUnsafe(600).buffer.byteLength);
|
||||
Stored.poolSize = 65536;
|
||||
@@ -0,0 +1,29 @@
|
||||
// Buffer.prototype members read as values, as smart-buffer probes and
|
||||
// calls them: own methods are functions with Node's names and lengths that
|
||||
// run on a Buffer receiver, the parent and offset getters read on the
|
||||
// prototype answer undefined, constructor is Buffer, and absent names are
|
||||
// undefined.
|
||||
"use strict";
|
||||
const buffer_1 = require("buffer");
|
||||
|
||||
function check(method) {
|
||||
if (typeof buffer_1.Buffer.prototype[method] === "undefined") return `${method}: missing`;
|
||||
return `${method}: ${typeof buffer_1.Buffer.prototype[method]}`;
|
||||
}
|
||||
console.log(["writeBigInt64BE", "readBigUInt64LE", "readUInt8", "nope", "parent", "offset"].map(check));
|
||||
console.log(Buffer.prototype.constructor === Buffer, Buffer.prototype["constructor"] === buffer_1.Buffer);
|
||||
console.log(Buffer.prototype[0], Buffer.prototype[undefined], Buffer.prototype.toJSON === Buffer.prototype.toJSON);
|
||||
|
||||
const names = ["readUInt8", "readUint8", "readFloatLE", "writeUInt16LE", "readUIntLE", "toString", "toLocaleString", "slice", "hexSlice"];
|
||||
console.log(names.map((n) => `${n}=${JSON.stringify(Buffer.prototype[n].name)}/${Buffer.prototype[n].length}`).join(" "));
|
||||
|
||||
const buf = Buffer.alloc(8);
|
||||
function write(func, value, offset) {
|
||||
return func.call(buf, value, offset);
|
||||
}
|
||||
console.log(write(Buffer.prototype.writeUInt16LE, 513, 0), write(Buffer.prototype.writeInt8, -2, 2), buf);
|
||||
console.log(Buffer.prototype.readUInt16LE.call(buf, 0), Buffer.prototype.readInt8.call(buf, 2));
|
||||
console.log(Buffer.prototype.toString.call(buf, "hex", 0, 3), Buffer.prototype.toLocaleString.call(Buffer.from("hi")));
|
||||
console.log(Buffer.prototype.slice.call(buf, 0, 2), Buffer.prototype.equals.call(buf, Buffer.alloc(8)));
|
||||
console.log(Buffer.prototype.fill.call(Buffer.alloc(3), 1), Buffer.prototype.indexOf.call(Buffer.from("abc"), "c"));
|
||||
console.log(Buffer.prototype.write.call(buf, "zz", 4), buf, Buffer.prototype.toJSON.call(Buffer.from([1])));
|
||||
@@ -0,0 +1,29 @@
|
||||
// util.inspect of functions with own enumerable properties, as Node's
|
||||
// formatRaw prints them: the function's base then its keys (symbols
|
||||
// included), `[Function]` past the depth limit, and the plain base when
|
||||
// every own property is hidden.
|
||||
"use strict";
|
||||
const util = require("util");
|
||||
|
||||
function named() {}
|
||||
named.x = 1;
|
||||
const arrow = () => {};
|
||||
arrow.a = "s";
|
||||
arrow[Symbol("k")] = 2;
|
||||
Object.defineProperty(arrow, "hidden", { value: 3, enumerable: false });
|
||||
function hiddenOnly() {}
|
||||
Object.defineProperty(hiddenOnly, "secret", { value: 1, enumerable: false });
|
||||
function big() {}
|
||||
for (let i = 0; i < 8; i++) big["key" + i] = i;
|
||||
function self() {}
|
||||
self.me = self;
|
||||
|
||||
const pass = (value) => value;
|
||||
|
||||
console.log(named, arrow, hiddenOnly);
|
||||
console.log(util.inspect(named, { depth: 0 }));
|
||||
console.log(util.inspect(pass({ wrap: named }), { depth: 0 }));
|
||||
console.log(util.inspect(pass([named, arrow])));
|
||||
console.log(util.inspect(big));
|
||||
console.log(util.inspect(self));
|
||||
console.log(util.inspect(pass({ a: { b: { f: big } } })));
|
||||
@@ -0,0 +1,22 @@
|
||||
// Buffer's numeric read/write methods with arguments that are not numbers
|
||||
// (untyped JavaScript): Node validates them at runtime.
|
||||
const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
const attempts = [
|
||||
() => buf.readUInt8("0"),
|
||||
() => buf.readUInt16LE("1"),
|
||||
() => buf.readInt32BE(true),
|
||||
() => buf.readDoubleLE(null),
|
||||
() => buf.writeUInt8(1, "0"),
|
||||
() => buf.writeUInt16BE("258", 0),
|
||||
() => buf.writeInt8("x", 0),
|
||||
() => buf.readUInt8(undefined),
|
||||
() => buf.readUInt8(1),
|
||||
];
|
||||
for (const attempt of attempts) {
|
||||
try {
|
||||
console.log("ok", attempt());
|
||||
} catch (error) {
|
||||
console.log(error.name, error.code, error.message);
|
||||
}
|
||||
}
|
||||
console.log(Array.from(buf).join(","));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4043, "san": 6242 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3823, "san": 5864 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5309, "san": 56270 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4282, "san": 5688 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5222, "san": 6614 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4707, "san": 5678 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4874, "san": 10466 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3944, "san": 5992 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3458, "san": 4785 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 6363, "san": 6796 }
|
||||
@@ -148,9 +148,9 @@ function nodeOracleFile(file: string): string {
|
||||
}
|
||||
|
||||
/** `// @no-deprecation` in the directive head: the Node oracle runs with
|
||||
* --no-deprecation — for programs exercising deprecated-API ERROR paths
|
||||
* (new Buffer's ctor ladder) whose DEP warnings carry a pid and can never
|
||||
* byte-compare; the compiled runtime emits no process warnings at all. */
|
||||
* --no-deprecation, for programs whose DEP warnings carry a pid and cannot
|
||||
* compare. The compiled program does not read the directive; a program
|
||||
* whose compiled form reports the warning sets process.noDeprecation. */
|
||||
function wantsNoDeprecation(file: string): boolean {
|
||||
return directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l));
|
||||
}
|
||||
|
||||
@@ -2112,3 +2112,52 @@ console.log(words.length);
|
||||
expect(r.stdout).toBe(`${fence}${fence}1\n`);
|
||||
});
|
||||
});
|
||||
|
||||
// The Buffer function read as a value models Node's statics; the forms it
|
||||
// cannot answer as Node does throw SC2020: calling or constructing it
|
||||
// through the value (Node reports DEP0005 by the caller's location), its
|
||||
// prototype as a whole, copyBytesFrom and of, and Buffer.prototype members
|
||||
// that are inherited, symbol-keyed or called on other receivers.
|
||||
describe("Buffer value refusals", () => {
|
||||
test("unmodeled Buffer value forms throw SC2020", async () => {
|
||||
const r = await compileAndRun(
|
||||
"buffer-value-refusals",
|
||||
`const pass = (value) => value;
|
||||
const Stored = pass(require("buffer").Buffer);
|
||||
const runs = [
|
||||
() => Stored("x"),
|
||||
() => new Stored(2),
|
||||
() => Stored.prototype,
|
||||
() => Stored.copyBytesFrom(new Uint8Array(2)),
|
||||
() => Stored.of(1),
|
||||
() => Buffer.prototype.map,
|
||||
() => Buffer.prototype[Symbol.iterator],
|
||||
() => Buffer.prototype.readUInt8.call(new Uint8Array(2), 0),
|
||||
];
|
||||
for (const run of runs) {
|
||||
try {
|
||||
run();
|
||||
console.log("ran");
|
||||
} catch (e) {
|
||||
console.log(e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
`,
|
||||
"cjs",
|
||||
);
|
||||
expect(r.exitCode).toBe(0);
|
||||
expect(r.stdout).toBe(
|
||||
[
|
||||
"Error SC2020 Calling or constructing Buffer through a stored Buffer value is not supported yet",
|
||||
"Error SC2020 Calling or constructing Buffer through a stored Buffer value is not supported yet",
|
||||
"Error SC2020 Buffer.prototype through a stored Buffer value is not supported yet",
|
||||
"Error SC2020 Buffer.copyBytesFrom through a stored Buffer value is not supported yet",
|
||||
"Error SC2020 Buffer.of through a stored Buffer value is not supported yet",
|
||||
"Error SC2020 Buffer.prototype.map is not supported yet",
|
||||
"Error SC2020 Buffer.prototype members keyed by symbols are not supported yet",
|
||||
"Error SC2020 Buffer.prototype.readUInt8 on a receiver that is not a Buffer is not supported yet",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user