buffer: fromArrayBuffer ranges, the deprecated constructor, Buffer as a value (#1970)

This commit is contained in:
Chris Tate
2026-10-23 17:46:42 -05:00
committed by GitHub
66 changed files with 1639 additions and 119 deletions
@@ -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.
+8
View File
@@ -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. */
+2
View File
@@ -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
+20 -1
View File
@@ -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
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7527-buffer-from-array-buffer.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7528-buffer-from-array-buffer.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7529-buffer-constructor.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7530-buffer-constructor.ts"
],
"diags": []
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7533-buffer-value.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7534-buffer-prototype-members.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7535-inspect-function-properties.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7713-buffer-numeric-untyped-arguments.cjs"
],
"diags": []
}
+4
View File
@@ -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. */
+16
View File
@@ -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. */
+214
View File
@@ -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;
}
+17 -3
View File
@@ -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. */
+27
View File
@@ -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). */
+212
View File
@@ -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());
}
+64 -16
View File
@@ -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;
+4
View File
@@ -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.
+39 -1
View File
@@ -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 ||
+21 -1
View File
@@ -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";
+2 -1
View File
@@ -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);
}
}
+80
View File
@@ -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>)"));
}
+42
View File
@@ -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>)"));
}
@@ -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;
@@ -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;
};
@@ -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);
@@ -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;
@@ -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;
};
@@ -0,0 +1,5 @@
{
"name": "legacy-buf",
"version": "1.0.0",
"main": "index.js"
}
+101
View File
@@ -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 }
+3 -3
View File
@@ -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));
}
+49
View File
@@ -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"),
);
});
});