Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Jint/Native/Array/ArrayInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,50 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property)
return base.GetOwnProperty(property);
}

/// <summary>
/// Element probe without the <see cref="TryGetDescriptor"/> 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 <c>_sparse</c> shadow dictionary
/// beside <c>_dense</c>. Presence and flags answer here allocation-free; a previously
/// materialized descriptor (if any) stays authoritative for its flags.
/// </summary>
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))
Expand Down
3 changes: 1 addition & 2 deletions Jint/Native/Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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--;
Expand Down
6 changes: 2 additions & 4 deletions Jint/Native/Object/ObjectConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
44 changes: 34 additions & 10 deletions Jint/Native/Object/ObjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -666,6 +666,33 @@ public virtual PropertyDescriptor GetOwnProperty(JsValue property)
return descriptor ?? PropertyDescriptor.Undefined;
}

/// <summary>
/// Answers whether the named own property exists and is enumerable without materializing a
/// <see cref="PropertyDescriptor"/>. Shape-mode objects (sealed <see cref="JsObject"/>,
/// 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 <see cref="GetOwnProperty"/>. Read-only callers that don't need the descriptor's
/// value (existence checks, enumerability filters) should prefer this over GetOwnProperty.
/// </summary>
internal virtual OwnPropertyProbe ProbeOwnProperty(JsValue property)
{
if ((_type & InternalTypes.ShapeMode) != InternalTypes.Empty && property is JsString jsString)
{
return Unsafe.As<JsObject>(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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -2023,11 +2047,11 @@ internal IEnumerable<JsValue> GetKeys()
var visited = new HashSet<JsValue>();
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;
}
Expand Down
10 changes: 2 additions & 8 deletions Jint/Native/Object/ObjectPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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;
}
}
12 changes: 12 additions & 0 deletions Jint/Native/Object/OwnPropertyProbe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Jint.Native.Object;

/// <summary>
/// Result of <see cref="ObjectInstance.ProbeOwnProperty"/>: the own property's existence and
/// enumerability without materializing a <see cref="Runtime.Descriptors.PropertyDescriptor"/>.
/// </summary>
internal enum OwnPropertyProbe
{
Missing,
NonEnumerable,
Enumerable,
}