diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index 07e3bac95..4a4be59c8 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -462,6 +462,50 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property) return base.GetOwnProperty(property); } + /// + /// Element probe without the materialization: enumerating a + /// dense array (for-in, Object.keys/values/entries, spread, assign) previously allocated one + /// CEW descriptor per touched index AND grew a permanent _sparse shadow dictionary + /// beside _dense. Presence and flags answer here allocation-free; a previously + /// materialized descriptor (if any) stays authoritative for its flags. + /// + internal override OwnPropertyProbe ProbeOwnProperty(JsValue property) + { + if (CommonProperties.Length.Equals(property)) + { + // the length property is never enumerable + return HasLength ? OwnPropertyProbe.NonEnumerable : base.ProbeOwnProperty(property); + } + + if (IsArrayIndex(property, out var index)) + { + var temp = _dense; + if (temp is not null) + { + if (index < (uint) temp.Length && temp[index] is not null) + { + if (_sparse is not null && _sparse.TryGetValue(index, out var materialized) && materialized is not null) + { + return materialized.Enumerable ? OwnPropertyProbe.Enumerable : OwnPropertyProbe.NonEnumerable; + } + + return OwnPropertyProbe.Enumerable; + } + + return OwnPropertyProbe.Missing; + } + + if (_sparse is not null && _sparse.TryGetValue(index, out var descriptor) && descriptor is not null) + { + return descriptor.Enumerable ? OwnPropertyProbe.Enumerable : OwnPropertyProbe.NonEnumerable; + } + + return OwnPropertyProbe.Missing; + } + + return base.ProbeOwnProperty(property); + } + internal JsValue Get(uint index) { if (!TryGetValue(index, out var value)) diff --git a/Jint/Native/Json/JsonSerializer.cs b/Jint/Native/Json/JsonSerializer.cs index 98aecbc06..f0ea42011 100644 --- a/Jint/Native/Json/JsonSerializer.cs +++ b/Jint/Native/Json/JsonSerializer.cs @@ -608,8 +608,7 @@ private static void RemoveUnserializableProperties(ObjectInstance instance, List for (var i = 0; i < keys.Count; i++) { var key = keys[i]; - var desc = instance.GetOwnProperty(key); - if (desc == PropertyDescriptor.Undefined || !desc.Enumerable) + if (instance.ProbeOwnProperty(key) != OwnPropertyProbe.Enumerable) { keys.RemoveAt(i); i--; diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index 47f554941..bf83cfcff 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -54,8 +54,7 @@ private ObjectInstance Assign(JsValue thisObject, JsValue target, [Rest] ReadOnl } processed++; - var desc = from.GetOwnProperty(nextKey); - if (desc != PropertyDescriptor.Undefined && desc.Enumerable) + if (from.ProbeOwnProperty(nextKey) == OwnPropertyProbe.Enumerable) { var propValue = from.Get(nextKey); to.Set(nextKey, propValue, throwOnError: true); @@ -335,8 +334,7 @@ private ObjectInstance ObjectDefineProperties(ObjectInstance o, JsValue properti for (var i = 0; i < keys.Count; i++) { var nextKey = keys[i]; - var propDesc = props.GetOwnProperty(nextKey); - if (propDesc == PropertyDescriptor.Undefined || !propDesc.Enumerable) + if (props.ProbeOwnProperty(nextKey) != OwnPropertyProbe.Enumerable) { continue; } diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index d0b65df37..68b67f619 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -515,7 +515,7 @@ protected virtual bool TryGetProperty(JsValue property, [NotNullWhen(true)] out [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasOwnProperty(JsValue property) { - return !ReferenceEquals(GetOwnProperty(property), PropertyDescriptor.Undefined); + return ProbeOwnProperty(property) != OwnPropertyProbe.Missing; } public virtual void RemoveOwnProperty(JsValue property) @@ -666,6 +666,33 @@ public virtual PropertyDescriptor GetOwnProperty(JsValue property) return descriptor ?? PropertyDescriptor.Undefined; } + /// + /// Answers whether the named own property exists and is enumerable without materializing a + /// . Shape-mode objects (sealed , + /// whose slots are always configurable/enumerable/writable — anything else deopts to + /// dictionary mode) answer straight from the shape; every other object — including exotics + /// like proxies (traps still fire), typed arrays and interop wrappers — routes through the + /// virtual . Read-only callers that don't need the descriptor's + /// value (existence checks, enumerability filters) should prefer this over GetOwnProperty. + /// + internal virtual OwnPropertyProbe ProbeOwnProperty(JsValue property) + { + if ((_type & InternalTypes.ShapeMode) != InternalTypes.Empty && property is JsString jsString) + { + return Unsafe.As(this).ShapeOf.TryGetSlot(jsString.ToString(), out _) + ? OwnPropertyProbe.Enumerable + : OwnPropertyProbe.Missing; + } + + var desc = GetOwnProperty(property); + if (ReferenceEquals(desc, PropertyDescriptor.Undefined)) + { + return OwnPropertyProbe.Missing; + } + + return desc.Enumerable ? OwnPropertyProbe.Enumerable : OwnPropertyProbe.NonEnumerable; + } + // Built-in-shape storage helpers (InternalTypes.BuiltinShapeMode). Shared by every host that implements // IBuiltinShaped — BuiltinShapeObject-derived namespaces today, generator-emitted prototypes/constructors // later — so the storage is composable across base classes that cannot share a single base. See BuiltinShape. @@ -1041,8 +1068,7 @@ internal bool CanPut(JsValue property) public virtual bool HasProperty(JsValue property) { var key = TypeConverter.ToPropertyKey(property); - var hasOwn = GetOwnProperty(key); - if (hasOwn != PropertyDescriptor.Undefined) + if (ProbeOwnProperty(key) != OwnPropertyProbe.Missing) { return true; } @@ -1899,8 +1925,7 @@ internal void CopyDataProperties( var key = keys[i]; if (excludedItems == null || !excludedItems.Contains(key)) { - var desc = GetOwnProperty(key); - if (desc.Enumerable) + if (ProbeOwnProperty(key) == OwnPropertyProbe.Enumerable) { var propValue = Get(key); target.CreateDataProperty(key, propValue); @@ -1942,8 +1967,7 @@ internal JsArray EnumerableOwnProperties(EnumerableOwnPropertyNamesKind kind) continue; } - var desc = GetOwnProperty(property); - if (desc != PropertyDescriptor.Undefined && desc.Enumerable) + if (ProbeOwnProperty(property) == OwnPropertyProbe.Enumerable) { if (kind == EnumerableOwnPropertyNamesKind.Key) { @@ -2023,11 +2047,11 @@ internal IEnumerable GetKeys() var visited = new HashSet(); foreach (var key in GetOwnPropertyKeys(Types.String)) { - var desc = GetOwnProperty(key); - if (desc != PropertyDescriptor.Undefined) + var probe = ProbeOwnProperty(key); + if (probe != OwnPropertyProbe.Missing) { visited.Add(key); - if (desc.Enumerable) + if (probe == OwnPropertyProbe.Enumerable) { yield return key; } diff --git a/Jint/Native/Object/ObjectPrototype.cs b/Jint/Native/Object/ObjectPrototype.cs index f4ce55ae6..7ed9f01c4 100644 --- a/Jint/Native/Object/ObjectPrototype.cs +++ b/Jint/Native/Object/ObjectPrototype.cs @@ -186,12 +186,7 @@ private JsValue PropertyIsEnumerable(JsValue thisObject, JsValue v) { var p = TypeConverter.ToPropertyKey(v); var o = TypeConverter.ToObject(_realm, thisObject); - var desc = o.GetOwnProperty(p); - if (desc == PropertyDescriptor.Undefined) - { - return JsBoolean.False; - } - return desc.Enumerable; + return o.ProbeOwnProperty(p) == OwnPropertyProbe.Enumerable; } [JsFunction] @@ -284,7 +279,6 @@ private JsValue HasOwnProperty(JsValue thisObject, JsValue v) { var p = TypeConverter.ToPropertyKey(v); var o = TypeConverter.ToObject(_realm, thisObject); - var desc = o.GetOwnProperty(p); - return desc != PropertyDescriptor.Undefined; + return o.ProbeOwnProperty(p) != OwnPropertyProbe.Missing; } } diff --git a/Jint/Native/Object/OwnPropertyProbe.cs b/Jint/Native/Object/OwnPropertyProbe.cs new file mode 100644 index 000000000..6a3a4acbb --- /dev/null +++ b/Jint/Native/Object/OwnPropertyProbe.cs @@ -0,0 +1,12 @@ +namespace Jint.Native.Object; + +/// +/// Result of : the own property's existence and +/// enumerability without materializing a . +/// +internal enum OwnPropertyProbe +{ + Missing, + NonEnumerable, + Enumerable, +}