mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Read SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array, Proxy and Intl as values
SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array and Proxy read
as values are interned constructor values with Node's name and length.
Calling one throws Node's TypeError "Constructor X requires 'new'".
instanceof through a stored value answers as Node does: SharedArrayBuffer
and DataView test the value's kind, BigInt64Array and BigUint64Array answer
false (neither is constructed), and Proxy answers false for a primitive and
throws Node's TypeError for an object.
Intl read as a value is a namespace object with Node's 12 members in
Node's order. Its members are function values that throw the SC1090 fence
when called; direct calls such as new Intl.NumberFormat("en-US") keep
their lowerings.
TypeScript sources read the same values into any and unknown slots, so
the diagnostics program uses Atomics for a builtin without a value.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/** The standard namespace objects (`Math`, `JSON`, `Reflect`) read as
|
||||
/** The standard namespace objects (`Math`, `JSON`, `Reflect`, `Intl`) read as
|
||||
* JavaScript values. Each is one object per program, built on first use,
|
||||
* with Node's own properties in Node's order: the functions are writable,
|
||||
* configurable and non-enumerable, the Math constants are read-only and
|
||||
@@ -106,6 +106,21 @@ function namespaceMembers(name: string): readonly NamespaceMember[] | null {
|
||||
{ key: "set", length: 3 },
|
||||
{ key: "setPrototypeOf", length: 2 },
|
||||
];
|
||||
case "Intl":
|
||||
return [
|
||||
{ key: "getCanonicalLocales", length: 1 },
|
||||
{ key: "supportedValuesOf", length: 1 },
|
||||
{ key: "DateTimeFormat", length: 0 },
|
||||
{ key: "NumberFormat", length: 0 },
|
||||
{ key: "Collator", length: 0 },
|
||||
{ key: "PluralRules", length: 0 },
|
||||
{ key: "RelativeTimeFormat", length: 0 },
|
||||
{ key: "ListFormat", length: 0 },
|
||||
{ key: "Locale", length: 1 },
|
||||
{ key: "DisplayNames", length: 2 },
|
||||
{ key: "Segmenter", length: 0 },
|
||||
{ key: "DurationFormat", length: 0 },
|
||||
];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -270,7 +285,9 @@ function functionValue(lowerer: Lowerer, canonical: string, key: string, loc: Sr
|
||||
? mathMemberValue(lowerer, key, loc)
|
||||
: canonical === "JSON"
|
||||
? jsonMemberValue(lowerer, key, loc)
|
||||
: reflectMemberValue(lowerer, key, loc);
|
||||
: canonical === "Reflect"
|
||||
? reflectMemberValue(lowerer, key, loc)
|
||||
: null;
|
||||
if (value !== null)
|
||||
return value.type.kind === "dyn"
|
||||
? value
|
||||
|
||||
@@ -4993,7 +4993,10 @@ const ERROR_CONSTRUCTOR_VALUES: ReadonlySet<string> = new Set([
|
||||
* constructor, and dynamic values answer `.constructor` with the same one,
|
||||
* so `o.constructor === Object` and `err.constructor === TypeError` are
|
||||
* exact identity tests, and `err.constructor.name` is the constructor's
|
||||
* name. */
|
||||
* name. SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array and
|
||||
* Proxy have values with Node's name, length and instanceof answers;
|
||||
* calling one throws Node's TypeError, and constructing through the value
|
||||
* is fenced. */
|
||||
const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet<string> = new Set([
|
||||
"Object",
|
||||
"Function",
|
||||
@@ -5005,6 +5008,11 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet<string> = new Set([
|
||||
"WeakMap",
|
||||
"WeakSet",
|
||||
"FinalizationRegistry",
|
||||
"SharedArrayBuffer",
|
||||
"DataView",
|
||||
"BigInt64Array",
|
||||
"BigUint64Array",
|
||||
"Proxy",
|
||||
...ERROR_CONSTRUCTOR_VALUES,
|
||||
]);
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/builtin-values-proxy-intl-views.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -1959,7 +1959,9 @@ ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) {
|
||||
* answer `.constructor` on the matching dynamic values, so
|
||||
* `o.constructor === Object` and `a.constructor === b.constructor` are
|
||||
* exact identity tests. `Object(v)` works through the stored value; calling
|
||||
* the others through it is fenced. */
|
||||
* the others through it is fenced. SharedArrayBuffer, DataView,
|
||||
* BigInt64Array, BigUint64Array and Proxy are interned the same way;
|
||||
* calling one throws Node's "Constructor X requires 'new'" TypeError. */
|
||||
/* String constants live in named arrays in their own section rather than
|
||||
* in the unit's merged string pool (the explicit section also keeps the
|
||||
* compiler from folding the unit's existing literals into them), so
|
||||
@@ -2012,11 +2014,23 @@ static const char dyn_s38[] SCR_DYN_COMPAT_STR = "structuredClone";
|
||||
static const char dyn_s39[] SCR_DYN_COMPAT_STR = "The \"input\" argument must be specified";
|
||||
static const char dyn_s40[] SCR_DYN_COMPAT_STR = "The \"The value argument must be specified\" argument must be specified";
|
||||
static const char dyn_s41[] SCR_DYN_COMPAT_STR = "ERR_MISSING_ARGS";
|
||||
static const char dyn_s42[] SCR_DYN_COMPAT_STR = "SharedArrayBuffer";
|
||||
static const char dyn_s43[] SCR_DYN_COMPAT_STR = "DataView";
|
||||
static const char dyn_s44[] SCR_DYN_COMPAT_STR = "BigInt64Array";
|
||||
static const char dyn_s45[] SCR_DYN_COMPAT_STR = "BigUint64Array";
|
||||
static const char dyn_s46[] SCR_DYN_COMPAT_STR = "Proxy";
|
||||
static const char dyn_s47[] SCR_DYN_COMPAT_STR = "Constructor %s requires 'new'";
|
||||
|
||||
static const char *const dyn_global_ctor_names[] = {
|
||||
dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, dyn_s35,
|
||||
dyn_s42, dyn_s43, dyn_s44, dyn_s45, dyn_s46,
|
||||
};
|
||||
#define DYN_GLOBAL_CTORS (sizeof dyn_global_ctor_names / sizeof dyn_global_ctor_names[0])
|
||||
/* Each constructor's own length, indexed like dyn_global_ctor_names. */
|
||||
static const uint8_t dyn_global_ctor_lengths[DYN_GLOBAL_CTORS] = {1, 7, 2, 0, 0, 1, 0, 0, 1, 1, 1, 3, 3, 2};
|
||||
/* The first of the constructors that only `new` builds
|
||||
* (SharedArrayBuffer onward): calling one throws Node's TypeError. */
|
||||
#define DYN_GLOBAL_NEW_ONLY 9
|
||||
static SCR_TL ScrDyn *dyn_global_ctors[DYN_GLOBAL_CTORS];
|
||||
static SCR_TL bool dyn_global_ctors_registered;
|
||||
|
||||
@@ -2029,6 +2043,13 @@ static void dyn_global_ctors_cleanup(void) {
|
||||
|
||||
static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
const char *name = closure->fn;
|
||||
for (size_t i = DYN_GLOBAL_NEW_ONLY; i < DYN_GLOBAL_CTORS; i++) {
|
||||
if (name != dyn_global_ctor_names[i]) continue;
|
||||
char message[64];
|
||||
int length = snprintf(message, sizeof message, dyn_s47, name);
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, (size_t)length);
|
||||
return NULL;
|
||||
}
|
||||
bool object = strcmp(name, dyn_s26) == 0;
|
||||
if (object) {
|
||||
const ScrDyn *value = argc ? args[0] : scr_dyn_undefined();
|
||||
@@ -2046,8 +2067,8 @@ static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, si
|
||||
}
|
||||
|
||||
/* `new C(...args)` through a stored Object, Date, Map, Set, WeakMap or
|
||||
* WeakSet builds what the `new` expression builds. RegExp, Promise, and
|
||||
* Object over a primitive (a wrapper object) are fenced. */
|
||||
* WeakSet builds what the `new` expression builds. The other constructors,
|
||||
* and Object over a primitive (a wrapper object), are fenced. */
|
||||
static bool dyn_global_construct(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) {
|
||||
if (callee->kind != SCR_DYN_FUNC || callee->v.fn.thunk != dyn_global_ctor_call) return false;
|
||||
const char *name = callee->v.fn.clo->fn;
|
||||
@@ -2083,11 +2104,8 @@ static ScrDyn *dyn_global_ctor(const char *name) {
|
||||
scr_dyn_global_construct_fn = dyn_global_construct;
|
||||
}
|
||||
if (!dyn_global_ctors[i]) {
|
||||
/* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1,
|
||||
* the collections 0, FinalizationRegistry 1. */
|
||||
uint32_t length = i == 0 || i == 5 || i == 8 ? 1 : i == 1 ? 7 : i == 2 ? 2 : 0;
|
||||
dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0),
|
||||
dyn_global_ctor_call, length, dyn_s6,
|
||||
dyn_global_ctor_call, dyn_global_ctor_lengths[i], dyn_s6,
|
||||
dyn_global_ctor_names[i]);
|
||||
/* `o.constructor.prototype` of a plain object is Object.prototype
|
||||
* (zod's isPlainObject). The other prototypes are not modeled. */
|
||||
|
||||
@@ -5538,10 +5538,26 @@ static int scr_dyn_global_constructor_instanceof(const ScrDyn *value, const ScrD
|
||||
return -1;
|
||||
/* A native Date, or an instance of a class declared `extends Date`. */
|
||||
if (strcmp(callee->v.fn.name, "Date") == 0) return scr_dyn_date_instance_is(value);
|
||||
if (strcmp(callee->v.fn.name, "DataView") == 0) return scr_dyn_data_view_is(value);
|
||||
/* No value is a BigInt64Array or BigUint64Array: neither is constructed. */
|
||||
if (strcmp(callee->v.fn.name, "BigInt64Array") == 0 || strcmp(callee->v.fn.name, "BigUint64Array") == 0)
|
||||
return 0;
|
||||
/* Proxy has no prototype: false for a primitive, Node's TypeError for an
|
||||
* object. */
|
||||
if (strcmp(callee->v.fn.name, "Proxy") == 0) {
|
||||
if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_BOOL ||
|
||||
value->kind == SCR_DYN_NUM || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BIGINT ||
|
||||
value->kind == SCR_DYN_SYMBOL)
|
||||
return 0;
|
||||
static const char message[] = "Function has non-object prototype 'undefined' in instanceof check";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return 0;
|
||||
}
|
||||
static const struct { const char *name; int tag; } kinds[] = {
|
||||
{"RegExp", SCR_DYNH_REGEXP}, {"Map", SCR_DYNH_MAP}, {"Set", SCR_DYNH_SET},
|
||||
{"WeakMap", SCR_DYNH_WEAK_MAP}, {"WeakSet", SCR_DYNH_WEAK_SET},
|
||||
{"FinalizationRegistry", SCR_DYNH_FINALIZATION_REGISTRY},
|
||||
{"SharedArrayBuffer", SCR_DYNH_SHARED_ARRAY_BUFFER},
|
||||
};
|
||||
bool promise = strcmp(callee->v.fn.name, "Promise") == 0;
|
||||
int tag = -1;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array, Proxy and Intl
|
||||
// read as values hold Node's functions and namespace object.
|
||||
function attempt(label, run) {
|
||||
try {
|
||||
console.log(label, run());
|
||||
} catch (error) {
|
||||
console.log(label, error.name, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const constructors = { SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array, Proxy };
|
||||
for (const key of Object.keys(constructors)) {
|
||||
const value = constructors[key];
|
||||
console.log(key, typeof value, value.name, value.length, value === globalThis[key]);
|
||||
attempt(`call ${key}`, () => value());
|
||||
}
|
||||
console.log(constructors);
|
||||
console.log(String(DataView), Object.prototype.toString.call(constructors.Proxy));
|
||||
|
||||
const shared = new SharedArrayBuffer(8);
|
||||
const view = new DataView(new ArrayBuffer(4));
|
||||
const stored = { shared: SharedArrayBuffer, view: DataView, proxy: Proxy, big: BigInt64Array };
|
||||
console.log(shared instanceof stored.shared, new ArrayBuffer(1) instanceof stored.shared);
|
||||
console.log(view instanceof stored.view, new Uint8Array(4) instanceof stored.view);
|
||||
console.log(view instanceof stored.big, JSON.parse("1") instanceof stored.proxy);
|
||||
attempt("instanceof Proxy", () => ({}) instanceof stored.proxy);
|
||||
|
||||
const intl = Intl;
|
||||
console.log(typeof intl, intl === globalThis.Intl, Object.keys(intl).length, "NumberFormat" in intl);
|
||||
console.log(Object.getOwnPropertyNames(intl).join(","));
|
||||
console.log(Object.prototype.toString.call(intl), intl[Symbol.toStringTag]);
|
||||
console.log(typeof intl.DateTimeFormat, intl.Locale.length, intl.DisplayNames.length);
|
||||
console.log(new Intl.NumberFormat("en-US").format(1234.5));
|
||||
@@ -51,3 +51,14 @@ console.log(
|
||||
Object.keys(math).length,
|
||||
Object.getOwnPropertyNames(json).join(","),
|
||||
);
|
||||
|
||||
const views: any = { intl: Intl, proxy: Proxy, view: DataView, shared: SharedArrayBuffer };
|
||||
console.log(typeof views.intl, Object.getOwnPropertyNames(views.intl).length);
|
||||
console.log(Object.prototype.toString.call(views.intl), views.intl.Locale.length);
|
||||
console.log(views.proxy.name, views.proxy.length, views.view.name, views.shared.length);
|
||||
try {
|
||||
views.proxy();
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
}
|
||||
console.log(views);
|
||||
|
||||
@@ -5,7 +5,7 @@ function take(x: unknown): unknown {
|
||||
}
|
||||
|
||||
const inferred = { json: JSON };
|
||||
const loose: any = { intl: Intl, proxy: Proxy };
|
||||
const loose: any = { atomics: Atomics };
|
||||
const timers: Record<string, unknown> = { setTimeout };
|
||||
const list: any[] = [Array, parseInt];
|
||||
console.log(inferred, loose, timers, list, take(process));
|
||||
|
||||
@@ -3,7 +3,7 @@ builtin-values-without-lowering.ts:7:7 - error SC2009: values of type '{ json: J
|
||||
6 |
|
||||
7 | const inferred = { json: JSON };
|
||||
| ^~~~~~~~
|
||||
8 | const loose: any = { intl: Intl, proxy: Proxy };
|
||||
8 | const loose: any = { atomics: Atomics };
|
||||
|
||||
hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
@@ -12,22 +12,22 @@ builtin-values-without-lowering.ts:7:18 - error SC2009: values of type '{ json:
|
||||
6 |
|
||||
7 | const inferred = { json: JSON };
|
||||
| ^~~~~~~~~~~~~~
|
||||
8 | const loose: any = { intl: Intl, proxy: Proxy };
|
||||
8 | const loose: any = { atomics: Atomics };
|
||||
|
||||
hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
builtin-values-without-lowering.ts:8:28 - error SC2020: 'Intl' is part of the standard library types but has no scriptc lowering yet
|
||||
builtin-values-without-lowering.ts:8:31 - error SC2020: 'Atomics' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
7 | const inferred = { json: JSON };
|
||||
8 | const loose: any = { intl: Intl, proxy: Proxy };
|
||||
| ^~~~
|
||||
8 | const loose: any = { atomics: Atomics };
|
||||
| ^~~~~~~
|
||||
9 | const timers: Record<string, unknown> = { setTimeout };
|
||||
|
||||
hint: native Intl supports Intl.Segmenter with grapheme or word granularity and the composed new Intl.NumberFormat("en-US").format(x), x.toLocaleString("en-US") and x.toLocaleString(); formatter objects, locale negotiation and the other Intl constructors remain unsupported
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
builtin-values-without-lowering.ts:9:43 - error SC2020: 'setTimeout' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
8 | const loose: any = { intl: Intl, proxy: Proxy };
|
||||
8 | const loose: any = { atomics: Atomics };
|
||||
9 | const timers: Record<string, unknown> = { setTimeout };
|
||||
| ^~~~~~~~~~
|
||||
10 | const list: any[] = [Array, parseInt];
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4800, "san": 7200 }
|
||||
Reference in New Issue
Block a user