mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-29 07:04:09 +08:00
Read Object.prototype's own members as values
`const { hasOwnProperty } = Object.prototype`, `Object.prototype.constructor`
and `hasOwnProperty.call(Object.prototype, "constructor")` hit the runtime
"Object.prototype reflection and mutation" fence (acorn, ini,
safe-stable-stringify and qs read the prototype this way). Reads of the
prototype's constructor and shared methods now answer the same interned
values the inherited reads answer, own-property checks see its twelve own
members (none enumerable), and Object.prototype.toString.call(error)
answers "[object Error]" instead of "[object Object]". Mutation and the
remaining reflection (isPrototypeOf, __proto__, accessors) stay fenced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1144e3cea4
commit
9fab4348d3
@@ -1 +1 @@
|
||||
- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed.
|
||||
- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed.
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6234-object-prototype-values.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -5713,6 +5713,8 @@ void scr_dyn_throw(ScrDyn *d) {
|
||||
scr_dyn_trace_v, scr_dyn_is_object(d));
|
||||
}
|
||||
|
||||
static size_t scr_errdyn_find(const void *key, bool by_err);
|
||||
|
||||
ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
|
||||
if (d->kind == SCR_DYN_PROXY) {
|
||||
scr_dyn_proxy_unsupported("Symbol.toStringTag lookup");
|
||||
@@ -5736,7 +5738,7 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
|
||||
case SCR_DYN_NUM: tag = "[object Number]"; break;
|
||||
case SCR_DYN_STR: tag = "[object String]"; break;
|
||||
case SCR_DYN_ARR: tag = "[object Array]"; break;
|
||||
case SCR_DYN_OBJ: tag = "[object Object]"; break;
|
||||
case SCR_DYN_OBJ: tag = scr_errdyn_find(d, false) != SIZE_MAX ? "[object Error]" : "[object Object]"; break;
|
||||
case SCR_DYN_HANDLE:
|
||||
if ((d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL &&
|
||||
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET ||
|
||||
@@ -10223,6 +10225,10 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
|
||||
static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver) {
|
||||
for (const ScrDyn *current = d; current; current = scr_dyn_proto_next(current)) {
|
||||
if (current == scr_builtin_object_prototype) {
|
||||
/* Object.prototype's own constructor and the methods its values
|
||||
* share read as those values; other reflection stays fenced. */
|
||||
ScrDyn *member = scr_dyn_object_proto_inherited(key, key_len, SCR_INHERIT_ALL);
|
||||
if (member) return member;
|
||||
scr_dyn_class_reflection_fence(current);
|
||||
return NULL;
|
||||
}
|
||||
@@ -11704,6 +11710,8 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
||||
|
||||
|
||||
if (v->kind == SCR_DYN_OBJ) {
|
||||
/* Object.prototype's own members (its value carries no entries). */
|
||||
if (v == scr_builtin_object_prototype) return scr_dyn_object_proto_name(key);
|
||||
return scr_dyn_obj_get(v, key->data, key->len) != NULL;
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR) {
|
||||
@@ -11736,6 +11744,8 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
||||
}
|
||||
|
||||
bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) {
|
||||
/* Object.prototype's own members are all non-enumerable. */
|
||||
if (value == scr_builtin_object_prototype) return false;
|
||||
if (scr_dyn_class_reflection_fence(value)) return false;
|
||||
if (value->kind == SCR_DYN_TYPED_REF) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(value);
|
||||
|
||||
@@ -62,7 +62,8 @@ for (const name of Object.keys(values)) {
|
||||
console.log(name, hits.join("|") || "-", v.valueOf === Object.prototype.valueOf,
|
||||
name === "regexp" ? "-" : v.toString === Object.prototype.toString,
|
||||
v.hasOwnProperty === Object.prototype.hasOwnProperty,
|
||||
v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable);
|
||||
v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable,
|
||||
Object.prototype.toString.call(v));
|
||||
}
|
||||
const o = { a: 1 };
|
||||
console.log(typeof Object, typeof Date, typeof Map, Object.name, Date.name, o.constructor.name, typeof o.constructor);
|
||||
@@ -78,9 +79,16 @@ function isPlainObj(value) {
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
|
||||
}
|
||||
function isPlainLodash(value) {
|
||||
if (value == null || Object.prototype.toString.call(value) != "[object Object]") return false;
|
||||
const proto = Object.getPrototypeOf(value);
|
||||
if (proto === null) return true;
|
||||
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
|
||||
return typeof Ctor == "function" && Ctor instanceof Ctor && Ctor === Object;
|
||||
}
|
||||
function isPlainCtor(value) {
|
||||
return !!value && typeof value === "object" && (value.constructor === Object || value.constructor === undefined);
|
||||
}
|
||||
for (const name of ["plain", "parsed", "array", "nullProto", "created", "instance"]) {
|
||||
console.log(name, isPlainObj(values[name]), isPlainCtor(values[name]));
|
||||
console.log(name, isPlainObj(values[name]), isPlainLodash(values[name]), isPlainCtor(values[name]));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Object.prototype as a value: destructured and aliased methods, the
|
||||
// prototype's own constructor and members, `{}.valueOf`, and the
|
||||
// `Object.prototype.toString.call(x)` tag idiom (acorn, ini, qs,
|
||||
// safe-stable-stringify, rfdc).
|
||||
const { hasOwnProperty, propertyIsEnumerable } = Object.prototype;
|
||||
const toStr = Object.prototype.toString;
|
||||
const has = Object.prototype.hasOwnProperty;
|
||||
const o = { a: 1 };
|
||||
console.log(hasOwnProperty.call(o, "a"), hasOwnProperty.call(o, "b"), has.call(o, "a"), propertyIsEnumerable.call(o, "a"));
|
||||
console.log(toStr.call([]), toStr.call(null), toStr.call(undefined), toStr.call(new Date(0)), toStr.call(/x/), toStr.call(1), toStr.call("s"), toStr.call(true), toStr.call(o), toStr.call(new Error("e")), toStr.call(new RangeError("r")), toStr.call(() => 1), toStr.call(new Map()));
|
||||
console.log({}.valueOf === Object.prototype.valueOf, ({}).toString === toStr, typeof Object.prototype.toString, Object.prototype.constructor === Object);
|
||||
console.log(hasOwnProperty.call(Object.prototype, "constructor"), has.call(Object.prototype, "toString"), has.call(Object.prototype, "missing"), propertyIsEnumerable.call(Object.prototype, "toString"));
|
||||
console.log(hasOwnProperty === Object.prototype.hasOwnProperty, o.hasOwnProperty === hasOwnProperty, [].hasOwnProperty === has);
|
||||
function kind(value) {
|
||||
switch (toStr.call(value)) {
|
||||
case "[object Date]": return "date:" + value.getTime();
|
||||
case "[object Array]": return "array:" + value.length;
|
||||
case "[object RegExp]": return "regexp:" + value.source;
|
||||
case "[object Error]": return "error:" + value.message;
|
||||
case "[object Object]": return "object:" + Object.keys(value).join(",");
|
||||
default: return "other";
|
||||
}
|
||||
}
|
||||
console.log([new Date(5), [1, 2], /ab/, new Error("boom"), { p: 1, q: 2 }, 3].map(kind).join(" "));
|
||||
const config = JSON.parse('{"name":"app","db":{"host":"h"}}');
|
||||
for (const key in config) if (has.call(config, key)) console.log(key, typeof config[key], toStr.call(config[key]));
|
||||
Reference in New Issue
Block a user