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,
+}