Bind builtin static functions and read Buffer.alloc, fs.realpathSync and String methods as values

- bind/call/apply on builtin function values held as checked-dynamic
  functions (String.fromCharCode, JSON.stringify and the forms below).
- Function values for Buffer.byteLength (Node's runtime encoding ladder),
  Buffer.compare, Buffer.alloc (size check, then fill), JSON.parse,
  fs.realpathSync and fs.realpathSync.native (Node's options handling).
- A JavaScript string method read as a value answers String.prototype's
  function (smol-toml's `!!(''.isWellFormed)` probe).

The runtime entries live in a new unit, scr_builtin_values.c, linked only
by programs that read these values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-20 13:50:09 -07:00
co-authored by Claude Opus 5.5
parent 139542bcf1
commit be9c18f0b3
37 changed files with 1058 additions and 12 deletions
@@ -28,6 +28,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
...(features.dynTier ? { dynTier: true } : {}),
...(features.streamDyn ? { streamDyn: true } : {}),
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
...(features.builtinValues ? { builtinValues: true } : {}),
events: features.processEvents,
emitter: features.emitter,
symbol: features.symbol,
@@ -374,6 +374,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"fsp.globStrings": "scr_fsp_glob_strings",
"fsp.globError": "scr_fsp_glob_error",
"fs.realpathSync": "scr_fs_realpath",
"fs.realpathValue": "scr_fs_realpath_value",
"fs.realpathNativeSync": "scr_fs_realpath_promise",
"fs.unlinkSync": "scr_fs_unlink",
"fs.chmodSync": "scr_fs_chmod",
@@ -437,6 +438,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"buffer.concatDyn": "scr_buffer_concat_dyn",
"buffer.concatLenDyn": "scr_buffer_concat_len_dyn",
"buffer.alloc": "scr_buffer_alloc",
"buffer.allocValue": "scr_buffer_alloc_value",
"buffer.allocUnsafe": "scr_buffer_alloc_unsafe",
"buffer.allocUnsafeSlow": "scr_buffer_alloc_unsafe_slow",
"buffer.poolFromStr": "scr_buffer_from_str",
@@ -447,6 +449,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"buffer.poolSize": "scr_buffer_pool_size",
"buffer.setPoolSize": "scr_buffer_set_pool_size",
"buffer.byteLenDyn": "scr_buffer_byte_length_dyn",
"buffer.byteLenValue": "scr_buffer_byte_length_value",
"buffer.byteLenStr": "scr_bytes_byte_length_str",
"buffer.isEncoding": "scr_bytes_is_encoding",
// The checked-dynamic compare/equals validators (scr_bytes_io.c):
@@ -49,6 +49,9 @@ export interface NativeLinkFeatures {
/** events.once with options: scr_events_once_signal.c. Absent (not
* false) for programs without it. */
eventsOnceSignal?: boolean;
/** Builtin functions read as values (Buffer.byteLength, Buffer.alloc,
* fs.realpathSync) link scr_builtin_values.c; absent (false) otherwise. */
builtinValues?: boolean;
events: boolean;
emitter: boolean;
symbol: boolean;
@@ -119,6 +119,8 @@ export interface CcOptions {
/** events.once with options (events.onceSignal): compiles
* scr_events_once_signal.c. */
eventsOnceSignal?: boolean;
/** Builtin functions read as values: compiles scr_builtin_values.c. */
builtinValues?: boolean;
/** The program constructs EventTarget/Event/CustomEvent values or
* calls events.addAbortListener (the event.* libCalls), or uses the
* native fetch AbortSignal built on the same engine: compiles
@@ -179,6 +179,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
unit((u) => u.opts.assert || u.regex || u.opts.symbol, "scr_assert.c"),
unit((u) => u.opts.inspect, "scr_inspect.c", "scr_console_native.c"),
unit((u) => u.opts.dynInvoke || u.opts.workers || u.nativeFetch, "scr_dyn_invoke.c"),
// Builtin functions read as values in JavaScript dependencies.
unit((u) => u.opts.builtinValues === true, "scr_builtin_values.c"),
unit((u) => u.opts.dc, "scr_dc.c"),
unit((u) => u.opts.dynTier, "scr_js_value.c"),
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
@@ -86,6 +86,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
dynTier: boolean;
streamDyn: boolean;
eventsOnceSignal: boolean;
builtinValues: boolean;
warningReplace: boolean;
stdioExtended: boolean;
nativeFetch: boolean;
@@ -279,6 +280,7 @@ export function effectiveRuntimeFeatures(
dynTier: features.dynTier === true,
streamDyn: features.streamDyn === true,
eventsOnceSignal: features.eventsOnceSignal === true,
builtinValues: features.builtinValues === true,
warningReplace: features.warningReplace === true,
stdioExtended: features.stdioExtended === true,
nativeFetch,
@@ -68,6 +68,7 @@ export interface EarlyExecutableNativeFeatures {
dynTier?: boolean;
streamDyn?: boolean;
eventsOnceSignal?: boolean;
builtinValues?: boolean;
events: boolean;
emitter: boolean;
symbol: boolean;
@@ -650,5 +650,57 @@ export function lowerJsonProperty(
loc,
};
}
if (member === "parse") {
// JSON.parse as a value: ToString of the text, then the reviver form
// (a reviver that is not a function is ignored, as in Node).
const loc = locOf(expr);
const name = "%builtin.JSON.parse";
if (!lowerer.builtinCallableValueFns.has(name)) {
lowerer.builtinCallableValueFns.set(name, name);
const params = ["text", "reviver"].map((name) => ({ localId: name, name, type: DYN }));
lowerer.liftedFns.push({
name,
params,
returnType: DYN,
locals: params.map((p) => ({ id: p.localId, name: p.name, type: DYN, mutable: false })),
loc,
body: [
{
kind: "return",
value: {
kind: "libCall",
fn: "json.parseReviver",
args: [
{
kind: "libCall",
fn: "dyn.toStringCoerce",
args: [varRef("text", DYN, loc)],
type: STRING,
loc,
},
varRef("reviver", DYN, loc),
],
type: DYN,
loc,
},
loc,
},
],
});
}
return {
kind: "dynFrom",
value: {
kind: "closure",
fnName: name,
captures: [],
type: { kind: "func", params: [DYN, DYN], ret: DYN },
loc,
},
fnName: "parse",
type: DYN,
loc,
};
}
lowerer.unsupported("SC1090", expr, `JSON methods as values (call '${member}' directly)`);
}
@@ -27,6 +27,7 @@ import {
import {
BOOL,
BYTES_ELEMENT_NAME,
BYTES_U8,
DYN,
F64,
type IrExpr,
@@ -156,7 +157,7 @@ import { narrowStoredClassValue, structuralReadType } from "../class-unions.js";
import { InternalCompilerError } from "../../../errors.js";
import { lowerUnionFieldRead } from "./union-fields.js";
import { errorPropertyRead } from "../error-methods.js";
import { isSafeToRepeat } from "./evaluation-safety.js";
import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js";
import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "../builtins/compression.js";
import { isFsStreamMember, lowerFsStreamProperty } from "../builtins/fs-streams.js";
import { keyedFieldRead } from "./keyed-field-targets.js";
@@ -1624,6 +1625,38 @@ export function lowerIntrinsicProperty(
if (netAddressStatic) return netAddressStatic;
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "isBuffer")
return lowerCheckedPredicateValue(lowerer, "Buffer.isBuffer", "buffer", locOf(expr));
// Buffer statics as function values (`Buffer.byteLength.bind(Buffer)` in
// truncate-utf8-bytes): the checked forms take any argument as Node does.
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "byteLength")
return lowerer.lowerNativeCallableValue(
{
fn: "buffer.byteLenValue",
params: [DYN, DYN],
result: F64,
valueParams: [
{ mode: "required", type: DYN },
{ mode: "optional", type: DYN, defaultValue: undefined },
],
},
"Buffer.byteLength",
locOf(expr),
);
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "alloc")
return bufferAllocValue(lowerer, locOf(expr));
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "compare")
return lowerer.lowerNativeCallableValue(
{
fn: "buffer.compareChk",
params: [DYN, DYN],
result: F64,
valueParams: [
{ mode: "required", type: DYN },
{ mode: "required", type: DYN },
],
},
"Buffer.compare",
locOf(expr),
);
if (lowerer.stdlibGlobalMember(expr, "Date") === "now")
return lowerer.lowerNativeCallableValue(
{
@@ -2205,11 +2238,165 @@ export function lowerIntrinsicProperty(
own(STDLIB_SURFACE.string, name) !== undefined
: ARRAY_METHODS.has(name)
) {
// A JavaScript string method read as a value (smol-toml's
// `!!(''.isWellFormed)` feature probe): String.prototype's method, the
// runtime's interned function that runs it on its receiver.
if (kind === "string" && isJsSourceFile(expr.getSourceFile()) && !expr.questionDotToken) {
const receiver = lowerer.lowerExpr(expr.expression);
const method: IrExpr = {
kind: "libCall",
fn: "dyn.builtinMethod",
args: [
{ kind: "strLit", value: "String", type: STRING, loc: locOf(expr) },
{ kind: "strLit", value: name, type: STRING, loc: locOf(expr.name) },
],
type: DYN,
loc: locOf(expr),
};
return isSafeToDiscard(receiver)
? method
: {
kind: "seqExpr",
stmts: [{ kind: "exprStmt", expr: receiver, loc: locOf(expr.expression) }],
result: method,
type: DYN,
loc: locOf(expr),
};
}
lowerer.unsupported("SC1090", expr, `${kind} methods as values (call '${name}' directly)`);
}
return null;
}
/** The fill step of Buffer.alloc's value form: a number, or a string with
* no encoding (utf8), fills the fresh buffer statically; any other fill
* takes the checked-dynamic `fill` dispatch. */
function bufferAllocFill(loc: SrcLoc): IrStmt {
const buf = varRef("buf", BYTES_U8, loc);
const fill = varRef("fill", DYN, loc);
const encoding = varRef("encoding", DYN, loc);
const test = (kind: "number" | "string" | "undefined", value: IrExpr): IrExpr => ({
kind: "dynTest",
test: kind,
value,
type: BOOL,
loc,
});
const fillStr: IrExpr = {
kind: "bytesIntrinsic",
method: "fillStr",
receiver: buf,
args: [
{ kind: "dynCheck", value: fill, type: STRING, loc },
{ kind: "strLit", value: "utf8", type: STRING, loc },
],
type: BYTES_U8,
loc,
};
const fillNum: IrExpr = {
kind: "bytesIntrinsic",
method: "fillNum",
receiver: buf,
args: [{ kind: "dynCheck", value: fill, type: F64, loc }],
type: BYTES_U8,
loc,
};
const other: IrExpr = {
kind: "dynInvoke",
recv: { kind: "dynFrom", value: buf, type: DYN, loc },
method: "fill",
calleeName: "buf.fill",
args: [fill, encoding],
type: DYN,
loc,
};
return {
kind: "if",
cond: test("number", fill),
then: [{ kind: "exprStmt", expr: fillNum, loc }],
else_: [
{
kind: "if",
cond: {
kind: "logical",
op: "&&",
left: test("string", fill),
right: test("undefined", encoding),
type: BOOL,
loc,
},
then: [{ kind: "exprStmt", expr: fillStr, loc }],
else_: [{ kind: "exprStmt", expr: other, loc }],
loc,
},
],
loc,
};
}
/** Buffer.alloc as a function value (`var alloc = Buffer.alloc` in
* tar-stream): the checked allocation, then `fill(fill, encoding)` on the
* fresh buffer when a fill is given, which is how Node's alloc fills. */
function bufferAllocValue(lowerer: Lowerer, loc: SrcLoc): IrExpr {
const name = "%builtin.Buffer.alloc";
if (!lowerer.builtinCallableValueFns.has(name)) {
lowerer.builtinCallableValueFns.set(name, name);
const params = ["size", "fill", "encoding"].map((id) => ({ localId: id, name: id, type: DYN }));
lowerer.liftedFns.push({
name,
params,
returnType: BYTES_U8,
locals: [
...params.map((p) => ({ id: p.localId, name: p.name, type: DYN, mutable: false })),
{ id: "buf", name: "buf", type: BYTES_U8, mutable: false },
],
loc,
body: [
{
kind: "varDecl",
localId: "buf",
init: {
kind: "libCall",
fn: "buffer.allocValue",
args: [varRef("size", DYN, loc)],
type: BYTES_U8,
loc,
},
loc,
},
{
kind: "if",
cond: {
kind: "dynTest",
test: "undefined",
negated: true,
value: varRef("fill", DYN, loc),
type: BOOL,
loc,
},
then: [bufferAllocFill(loc)],
else_: null,
loc,
},
{ kind: "return", value: varRef("buf", BYTES_U8, loc), loc },
],
});
}
return {
kind: "dynFrom",
value: {
kind: "closure",
fnName: name,
captures: [],
type: { kind: "func", params: [DYN, DYN, DYN], ret: BYTES_U8 },
loc,
},
fnName: "alloc",
type: DYN,
loc,
};
}
/** A `T | undefined` class field read that the checker narrowed to `T`
* where the field was assigned a value that may be missing (an unchecked
* indexed read). TypeScript's assignment narrowing is not a runtime fact
@@ -9338,15 +9338,18 @@ function lowerObjLitGenericMethodCall(
lowerer.checker.getCallSignatures(recvT).length > 0
) {
const value = tryLowerExpression(lowerer, access.expression);
// Builtin function values held as checked-dynamic functions
// (String.fromCharCode, JSON.stringify) bind the same way.
if (
value?.type.kind === "func" &&
lowerer.dynConvertible(value.type) &&
value &&
(value.type.kind === "dyn" ||
(value.type.kind === "func" && lowerer.dynConvertible(value.type))) &&
!call.arguments.nodes.some(ts.isSpreadElement)
) {
const loc = locOf(call);
return {
kind: "dynInvoke",
recv: { kind: "dynFrom", value, type: DYN, loc },
recv: value.type.kind === "dyn" ? value : { kind: "dynFrom", value, type: DYN, loc },
method: name,
calleeName: ts.getTextOfNode(access),
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
@@ -12945,6 +12945,8 @@ export class Lowerer {
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);
return fn ? this.lowerNativeCallableValue(fn, `${bi.module}.${bi.member}`, loc) : null;
}
@@ -13657,3 +13659,51 @@ function droppedMembers(lowerer: Lowerer, from: IrType, to: IrType, seen: Set<st
}
return out;
}
/** fs.realpathSync and its .native as function values (fs.realpath stores
* `var origRealpathSync = fs.realpathSync`): path and options pass through
* unchecked to the runtime, which applies Node's argument handling. */
function fsRealpathValue(lowerer: Lowerer, native: boolean, loc: SrcLoc): IrExpr {
const name = native ? "%builtin.fs.realpathSync.native" : "%builtin.fs.realpathSync";
if (!lowerer.builtinCallableValueFns.has(name)) {
lowerer.builtinCallableValueFns.set(name, name);
const params = ["path", "options"].map((id) => ({ localId: id, name: id, type: DYN }));
lowerer.liftedFns.push({
name,
params,
returnType: DYN,
locals: params.map((p) => ({ id: p.localId, name: p.name, type: DYN, mutable: false })),
loc,
body: [
{
kind: "return",
value: {
kind: "libCall",
fn: "fs.realpathValue",
args: [
{ kind: "varRef", localId: "path", type: DYN, loc },
{ kind: "varRef", localId: "options", type: DYN, loc },
{ kind: "boolLit", value: native, type: BOOL, loc },
],
type: DYN,
loc,
},
loc,
},
],
});
}
return {
kind: "dynFrom",
value: {
kind: "closure",
fnName: name,
captures: [],
type: { kind: "func", params: [DYN, DYN], ret: DYN },
loc,
},
fnName: native ? "native" : "realpathSync",
type: DYN,
loc,
};
}
+1
View File
@@ -547,6 +547,7 @@ async function compileExecutableNative(
...(features.dynTier ? { dynTier: true } : {}),
...(features.streamDyn ? { streamDyn: true } : {}),
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
...(features.builtinValues ? { builtinValues: true } : {}),
events: features.events,
emitter: features.emitter,
symbol: features.symbol,
@@ -85,6 +85,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"bytes.constructor",
"bytes.instanceOf",
"buffer.byteLenDyn",
"buffer.byteLenValue",
"buffer.allocValue",
"fs.realpathValue",
"bytes.construct",
"arrayBuffer.new",
"sharedArrayBuffer.new",
@@ -687,6 +687,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
/** realpath(3) with Node's error shape (syscall "lstat" in the message,
* Node's own spelling for realpathSync failures). +1 fresh string. */
"fs.realpathSync": { argTypes: [STRING], result: STRING },
/** fs.realpathSync (or its .native when the flag is true) as a function
* value: path and options arrive unchecked; the result is a string or a
* Buffer by the options' encoding. */
"fs.realpathValue": { argTypes: [DYN, DYN, BOOL], result: DYN },
/** Native realpath with syscall "realpath" on failure. +1 fresh string. */
"fs.realpathNativeSync": { argTypes: [STRING], result: STRING },
/** The os.userInfo() field trio (uv_os_get_passwd's slices): pw_name,
@@ -2868,6 +2872,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* allocUnsafeSlow never pool. The size forms validate like Node
* (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */
"buffer.alloc": { argTypes: [F64], result: BYTES_U8 },
/** Buffer.alloc as a function value: the size arrives unchecked. */
"buffer.allocValue": { argTypes: [DYN], result: BYTES_U8 },
"buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 },
"buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 },
"buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 },
@@ -2881,6 +2887,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* fromStr's — and Buffer.isEncoding(name) over a runtime string
* (case-insensitive against Node's alias set). Pure; never throw. */
"buffer.byteLenDyn": { argTypes: [DYN, STRING], result: F64 },
/** Buffer.byteLength as a function value: the encoding arrives at run time. */
"buffer.byteLenValue": { argTypes: [DYN, DYN], result: F64 },
"buffer.byteLenStr": { argTypes: [STRING, STRING], result: F64 },
"buffer.isEncoding": { argTypes: [STRING], result: BOOL },
/** Buffer.concat(list, totalLength): the concatenation truncated or
@@ -569,6 +569,8 @@ const RUNTIME_FEATURE_RULES = {
/** Checked-dynamic stream slots test values and pipe into JavaScript
* stream objects (scr_stream_dyn.c). */
streamDyn: rule({ fns: ["stream.dynNative", "stream.pipeDyn", "stream.unpipeDyn"] }),
/** Builtin functions read as values (scr_builtin_values.c). */
builtinValues: rule({ fns: ["buffer.byteLenValue", "buffer.allocValue", "fs.realpathValue"] }),
/** The stream unit emits through the emitter unit, also when the
* program constructs no stream (a pipeline of functions). */
emitter: rule({
@@ -752,6 +754,7 @@ function noRuntimeFeatures(): RuntimeFeatures {
stdioExtended: false,
stream: false,
streamDyn: false,
builtinValues: false,
symbol: false,
tls: false,
tlsCa: false,
@@ -79,6 +79,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
dynTier: false,
streamDyn: false,
eventsOnceSignal: false,
builtinValues: false,
warningReplace: false,
stdioExtended: false,
regex: true,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6570-npm-builtin-function-bind/main.ts"
],
"diags": []
}
@@ -61,6 +61,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
["scr_inspect.c", "inspect"],
["scr_console_native.c", "inspect"],
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")],
// Builtin functions read as values in JavaScript dependencies.
["scr_builtin_values.c", "builtinValues"],
["scr_dc.c", "dc"],
// The dynamic tier's jsval values (two-tier design I3): linked only into
// programs whose IR has dynamic-tier operations.
+141
View File
@@ -0,0 +1,141 @@
/* Builtin functions called as function values in JavaScript dependencies
* (`Buffer.byteLength.bind(Buffer)`, `var alloc = Buffer.alloc`, a stored
* `fs.realpathSync`): the arguments arrive unchecked, so these entries apply
* Node's argument handling before the ordinary operation. A separate unit,
* linked only by programs that read these values. */
#include "scr_runtime.h"
#include <stdio.h>
#include <string.h>
/* Buffer.byteLength called as a function value (`Buffer.byteLength.bind(
* Buffer)` in truncate-utf8-bytes), with the encoding known only at run
* time. Node's ladder: a non-string source answers its byte length or
* throws before the encoding is read; an empty string answers 0; a falsy
* encoding, or one getEncodingOps does not know after ToString (any ASCII
* case), counts UTF-8. */
double scr_buffer_byte_length_value(ScrDyn *value, ScrDyn *encoding) {
static const struct { const char *from; const char *to; } map[] = {
{"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"},
{"base64", "base64"}, {"base64url", "base64url"},
{"latin1", "latin1"}, {"binary", "latin1"}, {"ascii", "ascii"},
{"utf16le", "utf16le"}, {"utf-16le", "utf16le"},
{"ucs2", "utf16le"}, {"ucs-2", "utf16le"},
};
const char *name = "utf8";
if (value->kind == SCR_DYN_STR && value->v.str->len > 0 && scr_dyn_truthy(encoding)) {
ScrStr *raw = scr_dyn_string_coerce_js(encoding);
if (!raw) return 0;
for (size_t i = 0; i < sizeof map / sizeof map[0]; i++) {
size_t n = strlen(map[i].from);
bool same = raw->len == n;
for (size_t k = 0; same && k < n; k++) {
char c = raw->data[k];
if (c >= 'A' && c <= 'Z') c = (char)(c + ('a' - 'A'));
same = c == map[i].from[k];
}
if (same) {
name = map[i].to;
break;
}
}
scr_str_release(raw);
}
ScrStr *canonical = scr_str_new(name, strlen(name));
double length = scr_buffer_byte_length_dyn(value, canonical);
scr_str_release(canonical);
return length;
}
/* Buffer.alloc called as a function value (`var alloc = Buffer.alloc` in
* tar-stream): the size must be a number, ERR_INVALID_ARG_TYPE otherwise,
* then Node's allocation and range checks. The fill argument rides the
* compiled caller. +1, or NULL with the exception pending. */
ScrBytes *scr_buffer_alloc_value(const ScrDyn *size) {
if (size->kind != SCR_DYN_NUM) {
scr_dyn_arg_type_fail("size", "of type number", size);
return NULL;
}
return scr_buffer_alloc(size->v.num);
}
/* fs.realpathSync and fs.realpathSync.native called as function values
* (fs.realpath keeps `var origRealpathSync = fs.realpathSync`). Node's
* argument handling: options that are null, undefined or a function mean
* none, a string is the encoding, an object supplies `encoding`, anything
* else throws ERR_INVALID_ARG_TYPE; a path that is not a string converts
* with ToString (a Buffer decodes as UTF-8). The result is a string for
* utf8, a Buffer for "buffer", and the UTF-8 bytes decoded in any other
* encoding, whose name must be one Node knows. +1 or NULL. */
ScrDyn *scr_fs_realpath_value(const ScrDyn *path, const ScrDyn *options, bool native) {
const ScrDyn *encoding = NULL;
if (options->kind == SCR_DYN_STR) {
encoding = options;
} else if (options->kind == SCR_DYN_OBJ) {
encoding = scr_dyn_obj_get(options, "encoding", 8);
} else if (options->kind != SCR_DYN_UNDEF && options->kind != SCR_DYN_NULL &&
options->kind != SCR_DYN_FUNC) {
scr_dyn_arg_type_fail("options", "of type string or an instance of Object", options);
return NULL;
}
static const struct { const char *from; const char *to; } names[] = {
{"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"},
{"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"},
{"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"},
{"ucs2", "utf16le"}, {"ucs-2", "utf16le"}, {"buffer", "buffer"},
};
const char *canonical = "utf8";
if (encoding && encoding->kind != SCR_DYN_UNDEF && encoding->kind != SCR_DYN_NULL &&
!(encoding->kind == SCR_DYN_STR && encoding->v.str->len == 0)) {
const char *found = NULL;
if (encoding->kind == SCR_DYN_STR) {
for (size_t i = 0; i < sizeof names / sizeof names[0] && !found; i++) {
size_t n = strlen(names[i].from);
if (encoding->v.str->len != n) continue;
bool same = true;
for (size_t k = 0; k < n && same; k++) {
char c = encoding->v.str->data[k];
if (c >= 'A' && c <= 'Z') c = (char)(c + ('a' - 'A'));
same = c == names[i].from[k];
}
if (same) found = names[i].to;
}
}
if (!found) {
ScrStr *shown = scr_dyn_string_coerce_js(encoding);
if (!shown) return NULL;
char msg[256];
int n = snprintf(msg, sizeof msg, "The argument 'encoding' is invalid encoding. Received '%.*s'",
(int)(shown->len > 160 ? 160 : shown->len), shown->data);
scr_str_release(shown);
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_ARG_VALUE");
return NULL;
}
canonical = found;
}
ScrStr *p = path->kind == SCR_DYN_STR ? scr_str_retain(path->v.str) : scr_dyn_string_coerce_js(path);
if (!p) return NULL;
ScrStr *resolved = native ? scr_fs_realpath_promise(p) : scr_fs_realpath(p);
scr_str_release(p);
if (!resolved) return NULL;
ScrDyn *out;
if (!strcmp(canonical, "utf8")) {
out = scr_dyn_new_str(resolved);
} else {
ScrStr *utf8 = scr_str_new("utf8", 4);
ScrBytes *bytes = scr_buffer_from_str(resolved, utf8);
scr_str_release(utf8);
if (!strcmp(canonical, "buffer")) {
out = scr_dyn_new_buffer(bytes);
} else {
ScrStr *enc = scr_str_new(canonical, strlen(canonical));
ScrStr *text = scr_bytes_to_str(bytes, enc);
scr_str_release(enc);
out = scr_dyn_new_str(text);
scr_str_release(text);
}
scr_bytes_release(bytes);
}
scr_str_release(resolved);
return out;
}
+3
View File
@@ -394,6 +394,8 @@ ScrBytes *scr_buffer_concat_len(const ScrArr *list, double total);
/* The untyped-list forms: Node's ERR_INVALID_ARG_TYPE checks on the list
* and each element, then the typed concat. NULL with the exception pending. */
ScrBytes *scr_buffer_concat_dyn(const struct ScrDyn *list);
ScrBytes *scr_buffer_alloc_value(const struct ScrDyn *size);
struct ScrDyn *scr_fs_realpath_value(const struct ScrDyn *path, const struct ScrDyn *options, bool native);
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). */
@@ -408,6 +410,7 @@ ScrBytes *scr_bytes_from_str_checked(const ScrStr *s, const ScrStr *enc);
* Borrow; never throw. */
double scr_bytes_byte_length_str(ScrStr *s, const ScrStr *enc);
double scr_buffer_byte_length_dyn(ScrDyn *value, ScrStr *enc);
double scr_buffer_byte_length_value(ScrDyn *value, ScrDyn *encoding);
bool scr_bytes_is_encoding(const ScrStr *s);
/* equals / compare / indexOf-lastIndexOf / fill / copy / swap / write on
@@ -0,0 +1,25 @@
// Function.prototype.bind, call and apply on builtin static functions in
// JavaScript dependencies. The vendored truncate-utf8-bytes 1.0.2 (WTFPL)
// binds Buffer.byteLength at load time and calls the bound function per
// character; bound-builtins binds Buffer, Math, String and JSON statics.
// fs.realpath 1.0.0 (ISC) and value-forms read builtin functions as values:
// fs.realpathSync stored and compared, Buffer.alloc aliased, and a String
// method read for a feature probe.
import truncate from 'truncate-utf8-bytes';
import { report, badLength } from 'bound-builtins';
import realpath from 'fs.realpath';
import { report as valueForms } from 'value-forms';
console.log(truncate('hello world', 5), truncate('héllo', 2), truncate('héllo', 3));
console.log(JSON.stringify(truncate('a😀b', 3)), JSON.stringify(truncate('a😀b', 5)), truncate('short', 99));
try {
(truncate as unknown as (value: unknown, n: number) => string)(42, 1);
} catch (error) {
console.log((error as Error).message);
}
for (const value of report()) console.log(value);
console.log(badLength());
console.log(realpath.realpathSync('/'), realpath.sync('/'));
realpath('/', (error, resolved) => console.log('async', error, resolved));
for (const value of valueForms()) console.log(value);
@@ -0,0 +1,2 @@
export declare function report(): unknown[];
export declare function badLength(): string;
@@ -0,0 +1,42 @@
// Function.prototype.bind, call and apply on builtin static functions in
// JavaScript: Buffer, Math, String and JSON.
'use strict';
var byteLength = Buffer.byteLength.bind(Buffer);
var max = Math.max.bind(null, 10);
var fromCodes = String.fromCharCode.bind(String, 104);
var stringify = JSON.stringify.bind(JSON);
var parse = JSON.parse.bind(JSON);
var compare = Buffer.compare.bind(Buffer);
exports.report = function report() {
return [
byteLength('héllo'),
byteLength('héllo', 'latin1'),
byteLength('abcd', 'hex'),
byteLength('héllo', 'not-an-encoding'),
byteLength(''),
byteLength(Buffer.from([1, 2, 3])),
max(3, 42),
max(),
Math.min.call(null, 4, 2),
Math.abs.apply(null, [-7]),
fromCodes(105, 33),
String.fromCharCode.apply(null, [111, 107]),
stringify({ a: [1, 2] }),
stringify({ b: 1 }, null, 2),
compare(Buffer.from('a'), Buffer.from('b')),
parse('{"n":1}').n,
JSON.stringify(parse('[1,2]', function (key, value) {
return typeof value === 'number' ? value * 10 : value;
})),
];
};
exports.badLength = function badLength() {
try {
byteLength(42);
return 'no error';
} catch (error) {
return error.code + ' ' + error.name;
}
};
@@ -0,0 +1 @@
{ "name": "bound-builtins", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
@@ -0,0 +1,43 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
----
This library bundles a version of the `fs.realpath` and `fs.realpathSync`
methods from Node.js v0.10 under the terms of the Node.js MIT license.
Node's license follows, also included at the header of `old.js` which contains
the licensed code:
Copyright Joyent, Inc. and other Node contributors.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,6 @@
declare function realpath(p: string, cb: (error: Error | null, resolved?: string) => void): void;
declare namespace realpath {
function realpathSync(p: string, cache?: object): string;
function sync(p: string, cache?: object): string;
}
export = realpath;
@@ -0,0 +1,66 @@
module.exports = realpath
realpath.realpath = realpath
realpath.sync = realpathSync
realpath.realpathSync = realpathSync
realpath.monkeypatch = monkeypatch
realpath.unmonkeypatch = unmonkeypatch
var fs = require('fs')
var origRealpath = fs.realpath
var origRealpathSync = fs.realpathSync
var version = process.version
var ok = /^v[0-5]\./.test(version)
var old = require('./old.js')
function newError (er) {
return er && er.syscall === 'realpath' && (
er.code === 'ELOOP' ||
er.code === 'ENOMEM' ||
er.code === 'ENAMETOOLONG'
)
}
function realpath (p, cache, cb) {
if (ok) {
return origRealpath(p, cache, cb)
}
if (typeof cache === 'function') {
cb = cache
cache = null
}
origRealpath(p, cache, function (er, result) {
if (newError(er)) {
old.realpath(p, cache, cb)
} else {
cb(er, result)
}
})
}
function realpathSync (p, cache) {
if (ok) {
return origRealpathSync(p, cache)
}
try {
return origRealpathSync(p, cache)
} catch (er) {
if (newError(er)) {
return old.realpathSync(p, cache)
} else {
throw er
}
}
}
function monkeypatch () {
fs.realpath = realpath
fs.realpathSync = realpathSync
}
function unmonkeypatch () {
fs.realpath = origRealpath
fs.realpathSync = origRealpathSync
}
@@ -0,0 +1,303 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var pathModule = require('path');
var isWindows = process.platform === 'win32';
var fs = require('fs');
// JavaScript implementation of realpath, ported from node pre-v6
var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG);
function rethrow() {
// Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and
// is fairly slow to generate.
var callback;
if (DEBUG) {
var backtrace = new Error;
callback = debugCallback;
} else
callback = missingCallback;
return callback;
function debugCallback(err) {
if (err) {
backtrace.message = err.message;
err = backtrace;
missingCallback(err);
}
}
function missingCallback(err) {
if (err) {
if (process.throwDeprecation)
throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs
else if (!process.noDeprecation) {
var msg = 'fs: missing callback ' + (err.stack || err.message);
if (process.traceDeprecation)
console.trace(msg);
else
console.error(msg);
}
}
}
}
function maybeCallback(cb) {
return typeof cb === 'function' ? cb : rethrow();
}
var normalize = pathModule.normalize;
// Regexp that finds the next partion of a (partial) path
// result is [base_with_slash, base], e.g. ['somedir/', 'somedir']
if (isWindows) {
var nextPartRe = /(.*?)(?:[\/\\]+|$)/g;
} else {
var nextPartRe = /(.*?)(?:[\/]+|$)/g;
}
// Regex to find the device root, including trailing slash. E.g. 'c:\\'.
if (isWindows) {
var splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/;
} else {
var splitRootRe = /^[\/]*/;
}
exports.realpathSync = function realpathSync(p, cache) {
// make p is absolute
p = pathModule.resolve(p);
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
return cache[p];
}
var original = p,
seenLinks = {},
knownHard = {};
// current character position in p
var pos;
// the partial path so far, including a trailing slash if any
var current;
// the partial path without a trailing slash (except when pointing at a root)
var base;
// the partial path scanned in the previous round, with slash
var previous;
start();
function start() {
// Skip over roots
var m = splitRootRe.exec(p);
pos = m[0].length;
current = m[0];
base = m[0];
previous = '';
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
fs.lstatSync(base);
knownHard[base] = true;
}
}
// walk down the path, swapping out linked pathparts for their real
// values
// NB: p.length changes.
while (pos < p.length) {
// find the next part
nextPartRe.lastIndex = pos;
var result = nextPartRe.exec(p);
previous = current;
current += result[0];
base = previous + result[1];
pos = nextPartRe.lastIndex;
// continue if not a symlink
if (knownHard[base] || (cache && cache[base] === base)) {
continue;
}
var resolvedLink;
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
// some known symbolic link. no need to stat again.
resolvedLink = cache[base];
} else {
var stat = fs.lstatSync(base);
if (!stat.isSymbolicLink()) {
knownHard[base] = true;
if (cache) cache[base] = base;
continue;
}
// read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
var linkTarget = null;
if (!isWindows) {
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
if (seenLinks.hasOwnProperty(id)) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
fs.statSync(base);
linkTarget = fs.readlinkSync(base);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
// track this, if given a cache.
if (cache) cache[base] = resolvedLink;
if (!isWindows) seenLinks[id] = linkTarget;
}
// resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
start();
}
if (cache) cache[original] = p;
return p;
};
exports.realpath = function realpath(p, cache, cb) {
if (typeof cb !== 'function') {
cb = maybeCallback(cache);
cache = null;
}
// make p is absolute
p = pathModule.resolve(p);
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
return process.nextTick(cb.bind(null, null, cache[p]));
}
var original = p,
seenLinks = {},
knownHard = {};
// current character position in p
var pos;
// the partial path so far, including a trailing slash if any
var current;
// the partial path without a trailing slash (except when pointing at a root)
var base;
// the partial path scanned in the previous round, with slash
var previous;
start();
function start() {
// Skip over roots
var m = splitRootRe.exec(p);
pos = m[0].length;
current = m[0];
base = m[0];
previous = '';
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
fs.lstat(base, function(err) {
if (err) return cb(err);
knownHard[base] = true;
LOOP();
});
} else {
process.nextTick(LOOP);
}
}
// walk down the path, swapping out linked pathparts for their real
// values
function LOOP() {
// stop if scanned past end of path
if (pos >= p.length) {
if (cache) cache[original] = p;
return cb(null, p);
}
// find the next part
nextPartRe.lastIndex = pos;
var result = nextPartRe.exec(p);
previous = current;
current += result[0];
base = previous + result[1];
pos = nextPartRe.lastIndex;
// continue if not a symlink
if (knownHard[base] || (cache && cache[base] === base)) {
return process.nextTick(LOOP);
}
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
// known symbolic link. no need to stat again.
return gotResolvedLink(cache[base]);
}
return fs.lstat(base, gotStat);
}
function gotStat(err, stat) {
if (err) return cb(err);
// if not a symlink, skip to the next path part
if (!stat.isSymbolicLink()) {
knownHard[base] = true;
if (cache) cache[base] = base;
return process.nextTick(LOOP);
}
// stat & read the link if not read before
// call gotTarget as soon as the link target is known
// dev/ino always return 0 on windows, so skip the check.
if (!isWindows) {
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
if (seenLinks.hasOwnProperty(id)) {
return gotTarget(null, seenLinks[id], base);
}
}
fs.stat(base, function(err) {
if (err) return cb(err);
fs.readlink(base, function(err, target) {
if (!isWindows) seenLinks[id] = target;
gotTarget(err, target);
});
});
}
function gotTarget(err, target, base) {
if (err) return cb(err);
var resolvedLink = pathModule.resolve(previous, target);
if (cache) cache[base] = resolvedLink;
gotResolvedLink(resolvedLink);
}
function gotResolvedLink(resolvedLink) {
// resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
start();
}
};
@@ -0,0 +1 @@
{ "name": "fs.realpath", "version": "1.0.0", "main": "index.js", "types": "index.d.ts", "license": "ISC" }
@@ -0,0 +1,2 @@
declare function truncate(string: string, byteLength: number): string;
export = truncate;
@@ -0,0 +1,5 @@
'use strict';
var truncate = require("./lib/truncate");
var getLength = Buffer.byteLength.bind(Buffer);
module.exports = truncate.bind(null, getLength);
@@ -0,0 +1,42 @@
'use strict';
function isHighSurrogate(codePoint) {
return codePoint >= 0xd800 && codePoint <= 0xdbff;
}
function isLowSurrogate(codePoint) {
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
}
// Truncate string by size in bytes
module.exports = function truncate(getLength, string, byteLength) {
if (typeof string !== "string") {
throw new Error("Input must be string");
}
var charLength = string.length;
var curByteLength = 0;
var codePoint;
var segment;
for (var i = 0; i < charLength; i += 1) {
codePoint = string.charCodeAt(i);
segment = string[i];
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
i += 1;
segment += string[i];
}
curByteLength += getLength(segment);
if (curByteLength === byteLength) {
return string.slice(0, i + 1);
}
else if (curByteLength > byteLength) {
return string.slice(0, i - segment.length + 1);
}
}
return string;
};
@@ -0,0 +1,8 @@
{
"name": "truncate-utf8-bytes",
"version": "1.0.2",
"description": "Truncate string to given length in bytes",
"main": "index.js",
"types": "index.d.ts",
"license": "WTFPL"
}
@@ -0,0 +1 @@
export declare function report(): unknown[];
@@ -0,0 +1,31 @@
// Builtin functions read as values: tar-stream's `var alloc = Buffer.alloc`,
// fs.realpathSync stored and compared, and smol-toml's `''.isWellFormed`
// feature probe.
'use strict';
var fs = require('fs');
var alloc = Buffer.alloc;
var origRealpathSync = fs.realpathSync;
var HAS_WELLFORMED = !!''.isWellFormed;
exports.report = function report() {
var header = alloc(8);
var filled = alloc(5, 'ab');
var numbered = alloc(3, 7);
var out = [];
out.push(header.length, header[0], filled.toString(), numbered[2], typeof alloc, alloc.length);
try {
alloc('8');
} catch (error) {
out.push(error.code);
}
out.push(origRealpathSync('/'), origRealpathSync === fs.realpathSync, typeof fs.realpathSync.native);
out.push(Buffer.isBuffer(origRealpathSync('/', 'buffer')), origRealpathSync('/', { encoding: 'hex' }));
out.push(fs.realpathSync.native('/'));
try {
origRealpathSync('/definitely/missing/path');
} catch (error) {
out.push(error.code);
}
out.push(HAS_WELLFORMED, 'ok'.isWellFormed(), typeof ''.toUpperCase);
return out;
};
@@ -0,0 +1 @@
{ "name": "value-forms", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
+1 -8
View File
@@ -12,11 +12,4 @@ json-dyn.ts:46:7 - error SC2007: values of type '{ (text: string, reviver?: ((th
| ^~~~~~~~
47 | // The exact `string | undefined` root now lowers: strings serialize while
hint: annotate the slot with the ONE signature the program actually calls (e.g. '(x: number) => string'), or wrap the overloaded function in a single-signature arrow
json-dyn.ts:46:18 - error SC1090: JSON methods as values (call 'parse' directly) are not supported yet
45 | const dynArray: unknown[] = []; // unknown[] IS the dyn array now — no fence (corpus 2585)
46 | const parseRef = JSON.parse;
| ^~~~~~~~~~
47 | // The exact `string | undefined` root now lowers: strings serialize while
hint: annotate the slot with the ONE signature the program actually calls (e.g. '(x: number) => string'), or wrap the overloaded function in a single-signature arrow