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:
Malte Ubl
2026-10-18 13:34:57 -07:00
co-authored by Claude Opus 5.5
parent 1144e3cea4
commit 9fab4348d3
5 changed files with 54 additions and 4 deletions
@@ -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.
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6234-object-prototype-values.cjs"
],
"diags": []
}
+11 -1
View File
@@ -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);
+10 -2
View File
@@ -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]));