From 1b9be70b6c04570b0affb83bf62e2bbfb3c900bc Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 27 Aug 2026 05:55:08 +0300 Subject: [PATCH 1/4] Host objects: the enumeration hook is the one enumeration uses, and two names say what they do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ObjectInstance.GetOwnProperties` was a second `virtual` whose name read like the enumeration hook and which nothing script-visible called. It is derived from `GetOwnPropertyKeys` + `GetOwnProperty` now, and non-virtual, so a host declares its keys once and every consumer — script, the CLR conversion behind `ToObject()`, the debugger, the debug view — reads the same answer. Thirteen in-box overrides go with it. Two of them disagreed with their own key enumeration: a function reported `prototype` first where `GetOwnPropertyKeys` reports `length`, `name`, `prototype`, and a `String` object omitted its character indices. A lazily created `f.prototype` declared `constructor` to `GetOwnProperties` alone, so `Object.getOwnPropertyNames(f.prototype)` answered `[]`; it now declares it through `GetInitialOwnStringPropertyKeys` and answers `["constructor"]`. `FastSetProperty` / `FastSetDataProperty` are `DefineOwnPropertyUnchecked` / `DefineOwnDataPropertyUnchecked`. The old names claimed a speed the methods do not have — a loop of them is the slow way to project host records — and hid what they do: always an own property, shadowing the prototype chain, no inherited setter, no `[[DefineOwnProperty]]` validation and therefore no possible `TypeError`. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S --- AGENTS.md | 4 +- Jint.Benchmark/HostLayoutLazySlotBenchmark.cs | 8 +- Jint.Benchmark/HostObjectAccessBenchmark.cs | 10 - Jint.Benchmark/HostPrototypeShapeBenchmark.cs | 12 +- .../GlobalSnapshotTests.cs | 6 +- .../HostDescriptorEnvelopeTests.cs | 7 +- .../HostEnumerationHookTests.cs | 261 ++++++++++++++++++ .../HostObjectSemanticsTests.cs | 9 - .../HostPrototypeShapeTests.cs | 4 +- .../HostUncheckedDefineTests.cs | 118 ++++++++ .../LazyPropertyDescriptorTests.cs | 4 +- .../RavenApiUsageTests.cs | 2 +- .../SharedShapeTests.cs | 4 +- .../Verify/PublicApiTest_net10.0.verified.txt | 16 +- .../Verify/PublicApiTest_net472.verified.txt | 16 +- .../Verify/PublicApiTest_net8.0.verified.txt | 16 +- .../PublicApiTest_netstandard2.0.verified.txt | 16 +- .../PublicApiTest_netstandard2.1.verified.txt | 16 +- Jint.Tests/Runtime/Domain/UuidConstructor.cs | 4 +- Jint.Tests/Runtime/Domain/UuidPrototype.cs | 6 +- .../Runtime/GlobalSnapshotInternalsTests.cs | 2 +- Jint.Tests/Runtime/InteropTests.cs | 8 +- Jint.Tests/Runtime/ObjectInstanceTests.cs | 4 +- Jint.Tests/Runtime/PropertyDescriptorTests.cs | 12 +- Jint.Tests/Runtime/SharedObjectShapeTests.cs | 2 +- .../Runtime/WebApi/WorkerMechanismTests.cs | 4 +- Jint/Engine.GlobalSnapshot.cs | 8 +- Jint/GlobalValueRegistration.cs | 2 +- Jint/Native/Array/ArrayInstance.cs | 43 --- Jint/Native/Array/ArrayPrototype.cs | 32 +-- Jint/Native/Function/ClassDefinition.cs | 14 +- Jint/Native/Function/Function.cs | 71 +---- Jint/Native/Iterator/IteratorResult.cs | 20 -- Jint/Native/JsArguments.cs | 10 +- Jint/Native/JsError.cs | 7 - Jint/Native/JsObject.Create.cs | 8 +- Jint/Native/JsObjectLayout.cs | 2 +- Jint/Native/JsObjectShape.cs | 2 +- Jint/Native/JsRegExp.cs | 13 - Jint/Native/Json/JsonParser.cs | 2 +- Jint/Native/Object/AGENTS.md | 11 +- Jint/Native/Object/ArrayLikeObject.cs | 42 --- Jint/Native/Object/NamedPropertyObject.cs | 27 -- Jint/Native/Object/ObjectConstructor.cs | 4 +- Jint/Native/Object/ObjectInstance.Fast.cs | 122 -------- .../Native/Object/ObjectInstance.Unchecked.cs | 126 +++++++++ Jint/Native/Object/ObjectInstance.cs | 100 ++----- Jint/Native/Promise/PromiseConstructor.cs | 16 +- Jint/Native/RegExp/RegExpPrototype.cs | 4 +- Jint/Native/String/StringInstance.cs | 13 - Jint/NodeCompat/NodeQueryStringModule.cs | 2 +- .../Runtime/Descriptors/PropertyDescriptor.cs | 4 +- .../Runtime/Environments/GlobalEnvironment.cs | 4 +- .../Runtime/Environments/ObjectEnvironment.cs | 15 +- Jint/Runtime/Interop/ObjectWrapper.cs | 8 - Jint/Runtime/JavaScriptException.cs | 2 +- Jint/Test262AgentManager.cs | 24 +- Jint/Test262Object.cs | 14 +- Jint/WebApi/Events/JsEvent.cs | 13 - Jint/WebApi/Storage/JsStorage.cs | 29 -- README.md | 16 +- docs/v5-migration.md | 110 ++++++++ 62 files changed, 825 insertions(+), 686 deletions(-) create mode 100644 Jint.Tests.PublicInterface/HostEnumerationHookTests.cs create mode 100644 Jint.Tests.PublicInterface/HostUncheckedDefineTests.cs delete mode 100644 Jint/Native/Object/ObjectInstance.Fast.cs create mode 100644 Jint/Native/Object/ObjectInstance.Unchecked.cs diff --git a/AGENTS.md b/AGENTS.md index 7c503940fc..8ca9d93883 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,8 +92,8 @@ Its rules are split across the files in the index above, and every one of them i Each of these cost a real integrator or a real bug. - **Constraints bound one entry into the engine, never a host-driven sequence of them.** Every public entry that runs script — `Execute`, `Evaluate`, `Invoke`, `Engine.Call`, the `JsValue.Call` extension helpers — funnels through `Engine.ExecuteWithConstraints` (`Jint/Engine.cs`), which calls `ResetConstraints()` before the callback and again in its `finally` for any entry that is not nested (nesting is `_hostEntryDepth > 0 || _executionContexts.Count > 1`). So `foreach (var row in rows) predicate.Call(row);` — the single most common embedding shape — hands every element a fresh statement budget, a fresh allocation budget and, worst, a **freshly armed timeout deadline**. Measured: `LimitStatements(100)` does not stop 1000 host `Call`s, `LimitExecutionTime(200ms)` does not fire across 3 s of continuous host-driven execution, and `LimitMemory` never sees more than one call's allocations — while the identical work inside one `Execute` throws in every case. The reset itself is not the mistake: per-run reset is exactly what `Constraint.Reset`'s doc promises and what makes a reused engine usable, and the nested case is handled deliberately (a host callback re-entering the engine from inside a running script does *not* re-arm, or `while (true) hostCallback()` would run forever). What no embedder expects is that a single function call is a **run**. `Engine.Constraints.Check()` from the host loop does not close the gap — `TimeConstraint` re-arms its deadline on the way *out* of every run, so a host-side check measures the time since the last call returned. All of it is pinned from the embedder's side in `Jint.Tests.PublicInterface/HostCallLoopConstraintTests.cs` and `HostMemoryLimitTests.cs`; those tests assert the behaviour as it is, so changing it is a deliberate act that updates them. **What an embedder must do instead — the host-side bound, the in-script loop, and the two in-box constraints that survive the per-entry reset — is [`Jint/Constraints/AGENTS.md`](Jint/Constraints/AGENTS.md#bounding-a-host-driven-sequence), which is also the file to read before changing any of it.** -- **`FastSetProperty` / `FastSetDataProperty` always create an *own* property.** They shadow anything of that name on the prototype chain, invoke no inherited setter, and run no `[[DefineOwnProperty]]` validation (so they can never raise `TypeError`). Storing a raw descriptor under a string key is a dictionary-mode operation, so a shape-mode receiver is permanently deoptimized and forfeits the shape inline cache. Two refinements: symbol keys do not deopt, and a `BuiltinShapeMode` receiver can survive an in-place slot replacement. Use them for setup-time writes only — `Set` for steady-state mutation, `JsObject.Create` to build objects. -- **`GetOwnProperties` is not the enumeration hook.** `Object.keys`/`values`/`entries`, `for..in`, object spread and rest, `Object.assign`, `JSON.stringify` and `JsonSerializer` all list keys through `GetOwnPropertyKeys` and filter them with `ProbeOwnProperty`; none of them calls `GetOwnProperties`. Its real callers are the CLR conversion path (`ToObject` under `Options.Interop.CreateClrObject`), `GetSmallestIndex`, the debugger's `GetAllBindingNames`, and the debug view. A host that overrides only `GetOwnProperties` to expose projected properties therefore ships an object whose keys are invisible to every script-visible enumeration — a real integrator did exactly that. Overriding `GetOwnPropertyKeys` (plus `ProbeOwnProperty`, so existence and enumerability are answered without materializing a descriptor) is the pair that works. +- **`DefineOwnPropertyUnchecked` / `DefineOwnDataPropertyUnchecked` always create an *own* property.** They shadow anything of that name on the prototype chain, invoke no inherited setter, and run no `[[DefineOwnProperty]]` validation (so they can never raise `TypeError`). Storing a raw descriptor under a string key is a dictionary-mode operation, so a shape-mode receiver is permanently deoptimized and forfeits the shape inline cache. Two refinements: symbol keys do not deopt, and a `BuiltinShapeMode` receiver can survive an in-place slot replacement. Use them for setup-time writes only — `Set` for steady-state mutation, `JsObject.Create` to build objects. +- **The enumeration hook is `GetOwnPropertyKeys`, and it is the only one.** `Object.keys`/`values`/`entries`, `for..in`, object spread and rest, `Object.assign`, `JSON.stringify` and `JsonSerializer` all list keys through it and filter them with `ProbeOwnProperty`, and so — since [#3455](https://github.com/sebastienros/jint/pull/3455) — do `GetOwnProperties`' own consumers, the CLR conversion path (`ToObject` under `Options.Interop.CreateClrObject`), the debugger's `GetAllBindingNames` and the debug view. `GetOwnProperties` was a second `virtual` whose name read like the hook, and a real integrator overrode only that and shipped an object whose keys were invisible to every script-visible enumeration; it is now derived from `GetOwnPropertyKeys` + `GetOwnProperty` and non-virtual, so a host declares its keys once. Override `GetOwnPropertyKeys` (plus `ProbeOwnProperty`, so existence and enumerability are answered without materializing a descriptor), or derive from `NamedPropertyObject` / `ArrayLikeObject` and write neither. - **Sharing a `JsValue` across engines is unsupported**, and nothing validates or guards it. An `ObjectInstance` holds a hard reference to its creating engine and realm. README.md's "Embedding performance" section states this for embedders; there is still no XML doc on `JsValue` / `Engine.SetValue` saying it, and no test pins it. The other seventeen are in the files indexed above. **Do not add a new gotcha here.** Add it to the file for the area it governs; if none fits, say so in the pull request rather than growing this one. diff --git a/Jint.Benchmark/HostLayoutLazySlotBenchmark.cs b/Jint.Benchmark/HostLayoutLazySlotBenchmark.cs index 1892902e60..e59f9f7686 100644 --- a/Jint.Benchmark/HostLayoutLazySlotBenchmark.cs +++ b/Jint.Benchmark/HostLayoutLazySlotBenchmark.cs @@ -33,8 +33,8 @@ namespace Jint.Benchmark; /// /// /// is what a host had to write before: the same 11 cheap -/// members through FastSetDataProperty and the same 4 lazy ones as -/// descriptors through FastSetProperty. It decodes exactly +/// members through DefineOwnDataPropertyUnchecked and the same 4 lazy ones as +/// descriptors through DefineOwnPropertyUnchecked. It decodes exactly /// as little as the lazy layout does, so its distance from is purely /// the price of the dictionary representation: a per-item property dictionary and 15 descriptors on the /// build side, and a batch that never shares a hidden class — so the projection loop's member sites see a @@ -278,12 +278,12 @@ private static JsObject CreateDictionary(Engine engine, RawEnvelope raw) FillEager(values, raw); for (var i = 0; i < EagerNames.Length; i++) { - obj.FastSetDataProperty(EagerNames[i], values[i]!); + obj.DefineOwnDataPropertyUnchecked(EagerNames[i], values[i]!); } for (var i = 0; i < LazyNames.Length; i++) { - obj.FastSetProperty(LazyNames[i], new LazyMemberDescriptor(obj, raw, i)); + obj.DefineOwnPropertyUnchecked(LazyNames[i], new LazyMemberDescriptor(obj, raw, i)); } return obj; diff --git a/Jint.Benchmark/HostObjectAccessBenchmark.cs b/Jint.Benchmark/HostObjectAccessBenchmark.cs index 9e15bbf19e..9b2413a9b7 100644 --- a/Jint.Benchmark/HostObjectAccessBenchmark.cs +++ b/Jint.Benchmark/HostObjectAccessBenchmark.cs @@ -452,16 +452,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return keys; } - public override IEnumerable> GetOwnProperties() - { - for (var slot = 0; slot < SlotCount; slot++) - { - yield return new KeyValuePair( - _keys[slot], - new PropertyDescriptor(Project(slot), writable: true, enumerable: true, configurable: true)); - } - } - protected JsValue Project(int slot) { var projected = _projected[slot]; diff --git a/Jint.Benchmark/HostPrototypeShapeBenchmark.cs b/Jint.Benchmark/HostPrototypeShapeBenchmark.cs index 7fdcdc2bec..17d8385dd5 100644 --- a/Jint.Benchmark/HostPrototypeShapeBenchmark.cs +++ b/Jint.Benchmark/HostPrototypeShapeBenchmark.cs @@ -33,7 +33,7 @@ namespace Jint.Benchmark; /// /// — per-engine setup, the headline. /// is today's pattern: a plain populated through -/// with one eagerly-created +/// with one eagerly-created /// per operation and a getter/setter pair per attribute. /// is the same members declared once per process as a /// and instantiated per engine. The Allocated column is the point: the @@ -353,7 +353,7 @@ private static ObjectInstance BuildDictionaryPrototype(Engine engine, MemberTabl switch (member.Kind) { case MemberKind.Operation: - prototype.FastSetProperty( + prototype.DefineOwnPropertyUnchecked( member.Name, new PropertyDescriptor( new ClrFunction(engine, member.Name, member.Implementation!, 0), @@ -361,7 +361,7 @@ private static ObjectInstance BuildDictionaryPrototype(Engine engine, MemberTabl break; case MemberKind.Attribute: - prototype.FastSetProperty( + prototype.DefineOwnPropertyUnchecked( member.Name, new GetSetPropertyDescriptor( new ClrFunction(engine, "get " + member.Name, member.Implementation!, 0), @@ -371,15 +371,15 @@ private static ObjectInstance BuildDictionaryPrototype(Engine engine, MemberTabl break; default: - prototype.FastSetProperty( + prototype.DefineOwnPropertyUnchecked( member.Name, new PropertyDescriptor(member.Constant!, PropertyFlag.OnlyEnumerable)); break; } } - prototype.FastSetProperty("constructor", new PropertyDescriptor(JsValue.Undefined, PropertyFlag.NonEnumerable)); - prototype.FastSetProperty( + prototype.DefineOwnPropertyUnchecked("constructor", new PropertyDescriptor(JsValue.Undefined, PropertyFlag.NonEnumerable)); + prototype.DefineOwnPropertyUnchecked( ToStringTagKey, new PropertyDescriptor(new JsString(table.Name), PropertyFlag.Configurable)); diff --git a/Jint.Tests.PublicInterface/GlobalSnapshotTests.cs b/Jint.Tests.PublicInterface/GlobalSnapshotTests.cs index 8e01adc602..d3f1c50b50 100644 --- a/Jint.Tests.PublicInterface/GlobalSnapshotTests.cs +++ b/Jint.Tests.PublicInterface/GlobalSnapshotTests.cs @@ -585,7 +585,7 @@ protected override JsValue? CustomValue public void AHostDescriptorHoldingItsValueOutsideTheEngineIsNotReverted() { var engine = new Engine(); - engine.Global.FastSetProperty("cfg", new HostStateDescriptor(JsNumber.Create(1))); + engine.Global.DefineOwnPropertyUnchecked("cfg", new HostStateDescriptor(JsNumber.Create(1))); var snapshot = engine.Advanced.CaptureGlobalSnapshot(); engine.Evaluate("cfg = 5;"); @@ -609,7 +609,7 @@ public void AHostDescriptorHoldingItsValueOutsideTheEngineIsNotReverted() public void AHostDescriptorsAttributeFlagsAreStillReverted() { var engine = new Engine(); - engine.Global.FastSetProperty("cfg", new HostStateDescriptor(JsNumber.Create(1))); + engine.Global.DefineOwnPropertyUnchecked("cfg", new HostStateDescriptor(JsNumber.Create(1))); var snapshot = engine.Advanced.CaptureGlobalSnapshot(); engine.Evaluate("Object.defineProperty(globalThis, 'cfg', { enumerable: false, configurable: false });"); @@ -856,7 +856,7 @@ public void MutationsInsideAnObjectGraphBehindARestoredBindingSurvive() { var engine = new Engine(); var holder = new JsObject(engine); - holder.FastSetDataProperty("x", 1); + holder.DefineOwnDataPropertyUnchecked("x", 1); engine.SetValue("holder", holder); var snapshot = engine.Advanced.CaptureGlobalSnapshot(); diff --git a/Jint.Tests.PublicInterface/HostDescriptorEnvelopeTests.cs b/Jint.Tests.PublicInterface/HostDescriptorEnvelopeTests.cs index 58820414fd..cde4e4a675 100644 --- a/Jint.Tests.PublicInterface/HostDescriptorEnvelopeTests.cs +++ b/Jint.Tests.PublicInterface/HostDescriptorEnvelopeTests.cs @@ -210,7 +210,7 @@ public void APlainObjectCarryingAHostOwnedDescriptorObservesEveryRefill() var engine = new Engine(); var body = new PropertyDescriptor(JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable); var envelope = new JsObject(engine); - envelope.FastSetProperty("body", body); + envelope.DefineOwnPropertyUnchecked("body", body); engine.SetValue("envelope", envelope); engine.SetValue("refill", new Action(value => body.Value = value)); @@ -288,11 +288,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return keys; } - public override IEnumerable> GetOwnProperties() - { - _handedOut.Add(_body); - yield return new KeyValuePair(new JsString(BodyName), _body); - } } /// diff --git a/Jint.Tests.PublicInterface/HostEnumerationHookTests.cs b/Jint.Tests.PublicInterface/HostEnumerationHookTests.cs new file mode 100644 index 0000000000..dbe49be772 --- /dev/null +++ b/Jint.Tests.PublicInterface/HostEnumerationHookTests.cs @@ -0,0 +1,261 @@ +#nullable enable + +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Debugger; +using Jint.Runtime.Descriptors; + +namespace Jint.Tests.PublicInterface; + +/// +/// Pins that the enumeration hook is the one enumeration uses. A host object whose properties live +/// outside the engine's property tables declares them by overriding GetOwnPropertyKeys — and +/// ProbeOwnProperty beside it, so existence and enumerability are answered without materializing a +/// descriptor per key — and that one pair now answers every consumer. +/// +/// +/// It used not to. ObjectInstance.GetOwnProperties was a second, independent virtual, and it +/// was the one whose name reads like the enumeration hook. A real integrator overrode only that and shipped +/// an object whose keys were invisible to Object.keys, for..in, spread, Object.assign +/// and JSON.stringify; the reverse mistake — overriding the key hooks alone, which is the correct +/// thing to do — left the same object invisible to GetOwnProperties' own consumers, of which the CLR +/// conversion path and the debugger are the two an embedder meets. GetOwnProperties is derived from +/// GetOwnPropertyKeys + GetOwnProperty now, and is no longer overridable, so neither half of +/// that trap exists. +/// +/// +/// +/// These live in the public-interface suite on purpose: the project references Jint without any internals +/// access, so everything below is proven reachable by a third-party host. +/// +/// +public class HostEnumerationHookTests +{ + [Test] + public void TheKeyHooksAloneMakeAHostEnumerableFromScript() + { + // The control. This half already worked: script-visible enumeration has always gone through + // GetOwnPropertyKeys and ProbeOwnProperty. + var engine = new Engine(); + engine.SetValue("record", Sample(engine)); + + engine.Evaluate("Object.keys(record).join(',')").Should().Be("zulu,alpha,7"); + engine.Evaluate("Object.values(record).join(',')").Should().Be("z,a,seven"); + engine.Evaluate("var seen = []; for (var k in record) seen.push(k); seen.join(',')").Should().Be("zulu,alpha,7"); + // spread and assign copy into an ordinary target, whose own-key order hoists the integer-like key + engine.Evaluate("""JSON.stringify({...record})""").Should().Be("""{"7":"seven","zulu":"z","alpha":"a"}"""); + engine.Evaluate("""JSON.stringify(Object.assign({}, record))""").Should().Be("""{"7":"seven","zulu":"z","alpha":"a"}"""); + engine.Evaluate("""JSON.stringify(record)""").Should().Be("""{"zulu":"z","alpha":"a","7":"seven"}"""); + } + + [Test] + public void TheKeyHooksAloneMakeAHostEnumerableThroughGetOwnProperties() + { + // The half that did not work. Before GetOwnProperties was derived, this host — which declares + // nothing but the key hooks, exactly as the documentation tells it to — enumerated as empty here. + var engine = new Engine(); + var record = Sample(engine); + + var enumerated = record.GetOwnProperties() + .Select(pair => pair.Key.ToString() + "=" + pair.Value.Value.ToString()) + .ToList(); + + enumerated.Should().Equal("zulu=z", "alpha=a", "7=seven"); + } + + [Test] + public void TheKeyHooksAloneMakeAHostConvertibleToAClrObject() + { + // GetOwnProperties' most-reached consumer: JsValue.ToObject() under Options.Interop.CreateClrObject. + var engine = new Engine(); + var record = Sample(engine); + + var converted = record.ToObject() as IDictionary; + + converted.Should().NotBeNull(); + converted!.Keys.Should().Equal("zulu", "alpha", "7"); + converted["zulu"].Should().Be("z"); + converted["alpha"].Should().Be("a"); + converted["7"].Should().Be("seven"); + } + + [Test] + public void TheKeyHooksAloneMakeAHostVisibleToTheDebugger() + { + // The other consumer: the debugger's binding-name enumeration, reached through a `with` scope, whose + // binding object is the host itself. + var engine = new Engine(options => + { + options.Debugger.Enabled = true; + options.Debugger.StatementHandling = DebuggerStatementHandling.Script; + }); + engine.SetValue("record", Sample(engine)); + + IReadOnlyList? withScopeBindings = null; + engine.Debugger.Break += (_, info) => + { + withScopeBindings = info.CurrentScopeChain + .First(scope => scope.ScopeType == DebugScopeType.With) + .BindingNames; + return StepMode.None; + }; + + engine.Execute("with (record) { debugger; }"); + + withScopeBindings.Should().NotBeNull(); + withScopeBindings.Should().Equal("zulu", "alpha", "7"); + } + + [Test] + public void GetOwnPropertiesReportsTheOrderGetOwnPropertyKeysReports() + { + // The host's own key order is the enumeration order, whatever it is: this one is deliberately neither + // sorted nor index-first, and both surfaces agree on it. + var engine = new Engine(); + var record = Sample(engine); + engine.SetValue("record", record); + + var fromScript = engine.Evaluate("Object.getOwnPropertyNames(record).join(',')").AsString(); + var fromHost = string.Join(",", record.GetOwnProperties().Select(pair => pair.Key.ToString())); + + fromHost.Should().Be(fromScript); + fromHost.Should().Be("zulu,alpha,7"); + } + + [Test] + public void AnExpandoWrittenByScriptJoinsTheSameEnumeration() + { + // The projected names and the property bag are one enumeration, in the one order both surfaces agree + // on — the host chains to base.GetOwnPropertyKeys, and nothing else is needed to keep them together. + var engine = new Engine(); + var record = Sample(engine); + engine.SetValue("record", record); + + engine.Execute("record.written = 'w';"); + + engine.Evaluate("Object.keys(record).join(',')").Should().Be("zulu,alpha,7,written"); + record.GetOwnProperties().Select(pair => pair.Key.ToString()) + .Should().Equal("zulu", "alpha", "7", "written"); + } + + [Test] + public void AKeyWhoseDescriptorIsAbsentIsNotReported() + { + // GetOwnProperties never hands back PropertyDescriptor.Undefined: a key the list still names but + // GetOwnProperty no longer answers for is skipped, which is what every script-visible enumeration + // does with the same key. + var engine = new Engine(); + var record = Sample(engine); + record.Retract("alpha"); + engine.SetValue("record", record); + + record.GetOwnProperties().Select(pair => pair.Key.ToString()).Should().Equal("zulu", "7"); + engine.Evaluate("Object.keys(record).join(',')").Should().Be("zulu,7"); + } + + [Test] + public void GetOwnPropertiesIsNotAnExtensionPoint() + { + // The whole point: there is no second hook to get wrong. A host that wants to be enumerated overrides + // GetOwnPropertyKeys, and this one is derived from it. + var method = typeof(ObjectInstance).GetMethod(nameof(ObjectInstance.GetOwnProperties), Type.EmptyTypes); + + method.Should().NotBeNull(); + method!.IsVirtual.Should().BeFalse(); + } + + [Test] + public void AFunctionPrototypeReportsTheConstructorItOwns() + { + // An in-box object that had the very defect this change exists to make impossible: a lazily created + // f.prototype keeps `constructor` in a field, and used to declare it to GetOwnProperties alone — so + // hasOwnProperty and getOwnPropertyDescriptor both saw it while getOwnPropertyNames and + // Reflect.ownKeys answered an empty list. + var engine = new Engine(); + + engine.Evaluate("function f() {} Object.getOwnPropertyNames(f.prototype).join(',')").Should().Be("constructor"); + engine.Evaluate("function g() {} Reflect.ownKeys(g.prototype).join(',')").Should().Be("constructor"); + engine.Evaluate("function h() {} h.prototype.hasOwnProperty('constructor')").Should().Be(true); + // still non-enumerable, so the enumerations that filter on that keep skipping it + engine.Evaluate("function i() {} Object.keys(i.prototype).length").Should().Be(0); + engine.Evaluate("function j() {} delete j.prototype.constructor; Object.getOwnPropertyNames(j.prototype).length").Should().Be(0); + } + + private static ProjectedRecordObject Sample(Engine engine) + { + var record = new ProjectedRecordObject(engine); + record.Project("zulu", "z"); + record.Project("alpha", "a"); + record.Project("7", "seven"); + return record; + } +} + +/// +/// A host record projected from native state: not one of its fields lives in the engine's property tables, so +/// every question about them is answered by an override. It declares exactly the three the documentation asks +/// for — GetOwnProperty for the descriptor, GetOwnPropertyKeys for the keys and +/// ProbeOwnProperty so existence and enumerability cost no descriptor — and nothing else. +/// +internal sealed class ProjectedRecordObject : ObjectInstance +{ + private readonly List> _fields = new List>(); + + public ProjectedRecordObject(Engine engine) : base(engine) + { + } + + public void Project(string name, JsValue value) => _fields.Add(new KeyValuePair(name, value)); + + public void Retract(string name) => _fields.RemoveAll(field => string.Equals(field.Key, name, StringComparison.Ordinal)); + + public override PropertyDescriptor GetOwnProperty(JsValue property) + { + if (TryProject(property, out var value)) + { + return new PropertyDescriptor(value, writable: true, enumerable: true, configurable: true); + } + + return base.GetOwnProperty(property); + } + + public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) + { + var keys = new List(_fields.Count); + if ((types & Types.String) != Types.Empty) + { + foreach (var field in _fields) + { + keys.Add(new JsString(field.Key)); + } + } + + keys.AddRange(base.GetOwnPropertyKeys(types)); + return keys; + } + + protected override OwnPropertyProbe ProbeOwnProperty(JsValue property) + { + return TryProject(property, out _) ? OwnPropertyProbe.Enumerable : base.ProbeOwnProperty(property); + } + + private bool TryProject(JsValue property, out JsValue value) + { + if (property.IsString()) + { + var name = property.ToString(); + foreach (var field in _fields) + { + if (string.Equals(field.Key, name, StringComparison.Ordinal)) + { + value = field.Value; + return true; + } + } + } + + value = Undefined; + return false; + } +} diff --git a/Jint.Tests.PublicInterface/HostObjectSemanticsTests.cs b/Jint.Tests.PublicInterface/HostObjectSemanticsTests.cs index 8bfd58d989..e496b78ecb 100644 --- a/Jint.Tests.PublicInterface/HostObjectSemanticsTests.cs +++ b/Jint.Tests.PublicInterface/HostObjectSemanticsTests.cs @@ -549,15 +549,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return keys; } - public override IEnumerable> GetOwnProperties() - { - foreach (var field in _fields) - { - yield return new KeyValuePair( - new JsString(field.Key), - new PropertyDescriptor(field.Value, writable: true, enumerable: true, configurable: true)); - } - } } /// diff --git a/Jint.Tests.PublicInterface/HostPrototypeShapeTests.cs b/Jint.Tests.PublicInterface/HostPrototypeShapeTests.cs index 4c59d408a0..7d1a6506f9 100644 --- a/Jint.Tests.PublicInterface/HostPrototypeShapeTests.cs +++ b/Jint.Tests.PublicInterface/HostPrototypeShapeTests.cs @@ -290,8 +290,8 @@ public void APerRealmValueSlotStartsUndefinedAndIsFilledPerEngine() engineA.Evaluate("proto.constructor").Should().Be(JsValue.Undefined); // The setup-time fill: a raw descriptor store into a declared slot, which replaces it in place. - protoA.FastSetProperty("constructor", new PropertyDescriptor(new JsString("ctor-A"), PropertyFlag.NonEnumerable)); - protoB.FastSetProperty("constructor", new PropertyDescriptor(new JsString("ctor-B"), PropertyFlag.NonEnumerable)); + protoA.DefineOwnPropertyUnchecked("constructor", new PropertyDescriptor(new JsString("ctor-A"), PropertyFlag.NonEnumerable)); + protoB.DefineOwnPropertyUnchecked("constructor", new PropertyDescriptor(new JsString("ctor-B"), PropertyFlag.NonEnumerable)); engineA.Evaluate("proto.constructor").Should().Be("ctor-A"); engineB.Evaluate("proto.constructor").Should().Be("ctor-B"); diff --git a/Jint.Tests.PublicInterface/HostUncheckedDefineTests.cs b/Jint.Tests.PublicInterface/HostUncheckedDefineTests.cs new file mode 100644 index 0000000000..a8e89aa5c9 --- /dev/null +++ b/Jint.Tests.PublicInterface/HostUncheckedDefineTests.cs @@ -0,0 +1,118 @@ +#nullable enable + +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Tests.PublicInterface; + +/// +/// Pins what DefineOwnPropertyUnchecked / DefineOwnDataPropertyUnchecked promise, which is what +/// their old names — FastSetProperty / FastSetDataProperty — did not: the write always creates +/// an own property, it shadows the prototype chain, it invokes no inherited setter, and it runs no +/// [[DefineOwnProperty]] validation, so it can never raise a TypeError. +/// +/// +/// Every assertion below is the difference from Set or DefineOwnProperty, which is the choice a +/// host is actually making when it reaches for one of these. +/// +/// +public class HostUncheckedDefineTests +{ + [Test] + public void AnUncheckedDefineShadowsThePrototypeAndInvokesNoInheritedSetter() + { + var engine = new Engine(); + engine.Execute(""" + var proto = {}; + var setterCalls = 0; + Object.defineProperty(proto, 'member', { + get: function () { return 'from-prototype'; }, + set: function (v) { setterCalls++; }, + configurable: true + }); + var target = Object.create(proto); + """); + + var target = engine.Evaluate("target").AsObject(); + target.Get("member").Should().Be("from-prototype"); + + target.DefineOwnDataPropertyUnchecked("member", "own"); + + engine.Evaluate("setterCalls").Should().Be(0); + engine.Evaluate("target.member").Should().Be("own"); + engine.Evaluate("target.hasOwnProperty('member')").Should().Be(true); + // the accessor is untouched and reappears once the shadow is deleted + engine.Evaluate("delete target.member; target.member").Should().Be("from-prototype"); + } + + [Test] + public void AnUncheckedDefineRedefinesANonConfigurableNonWritablePropertyWithoutThrowing() + { + var engine = new Engine(); + engine.Execute("var frozen = Object.freeze({ member: 'original' });"); + var frozen = engine.Evaluate("frozen").AsObject(); + + // the checked route refuses, exactly as the specification says + engine.Evaluate(""" + (function () { + try { Object.defineProperty(frozen, 'member', { value: 'checked' }); return 'no throw'; } + catch (e) { return e.constructor.name; } + })() + """).Should().Be("TypeError"); + + frozen.DefineOwnPropertyUnchecked("member", new PropertyDescriptor("unchecked", writable: false, enumerable: true, configurable: false)); + + engine.Evaluate("frozen.member").Should().Be("unchecked"); + engine.Evaluate("Object.isFrozen(frozen)").Should().Be(true); + } + + [Test] + public void AnUncheckedDefineAddsAPropertyToANonExtensibleObject() + { + var engine = new Engine(); + engine.Execute("var sealed_ = Object.preventExtensions({ existing: 1 });"); + var target = engine.Evaluate("sealed_").AsObject(); + + engine.Evaluate(""" + (function () { + 'use strict'; + try { sealed_.added = 2; return 'no throw'; } + catch (e) { return e.constructor.name; } + })() + """).Should().Be("TypeError"); + + target.DefineOwnDataPropertyUnchecked("added", 2); + + engine.Evaluate("sealed_.added").Should().Be(2); + engine.Evaluate("Object.isExtensible(sealed_)").Should().Be(false); + } + + [Test] + public void AnUncheckedDataDefineIsConfigurableEnumerableAndWritable() + { + var engine = new Engine(); + var target = new JsObject(engine); + engine.SetValue("target", target); + + target.DefineOwnDataPropertyUnchecked("member", "value"); + + engine.Evaluate("""JSON.stringify(Object.getOwnPropertyDescriptor(target, 'member'))""") + .Should().Be("""{"value":"value","writable":true,"enumerable":true,"configurable":true}"""); + } + + [Test] + public void TheKeyedOverloadTakesASymbol() + { + var engine = new Engine(); + var target = new JsObject(engine); + engine.SetValue("target", target); + + var tag = engine.Evaluate("Symbol.toStringTag"); + target.DefineOwnPropertyUnchecked(tag, new PropertyDescriptor("Widget", writable: false, enumerable: false, configurable: true)); + + engine.Evaluate("Object.prototype.toString.call(target)").Should().Be("[object Widget]"); + engine.Evaluate("Object.keys(target).length").Should().Be(0); + } +} diff --git a/Jint.Tests.PublicInterface/LazyPropertyDescriptorTests.cs b/Jint.Tests.PublicInterface/LazyPropertyDescriptorTests.cs index d39e421c87..d2ef534c5d 100644 --- a/Jint.Tests.PublicInterface/LazyPropertyDescriptorTests.cs +++ b/Jint.Tests.PublicInterface/LazyPropertyDescriptorTests.cs @@ -294,7 +294,7 @@ public void AHostInstalledLazyGlobalIsReadCorrectlyByAWarmedSite() { var calls = 0; var engine = new Engine(); - engine.Global.FastSetProperty("hostValue", PropertyDescriptor.CreateLazy(() => + engine.Global.DefineOwnPropertyUnchecked("hostValue", PropertyDescriptor.CreateLazy(() => { calls++; return "built"; @@ -320,7 +320,7 @@ public void AHostInstalledLazyGlobalIsReadCorrectlyByAWarmedSite() public void AHostInstalledLazyGlobalCanBeOverwrittenByScript() { var engine = new Engine(); - engine.Global.FastSetProperty("hostValue", PropertyDescriptor.CreateLazy( + engine.Global.DefineOwnPropertyUnchecked("hostValue", PropertyDescriptor.CreateLazy( static () => "built", PropertyFlag.NonEnumerable)); diff --git a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs index 82a162cb5f..cbd98f614e 100644 --- a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs +++ b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs @@ -66,7 +66,7 @@ public void CanInjectConstructedObjects() { var engine = new Engine(); var obj = new JsObject(engine); - obj.FastSetDataProperty("name", "test"); + obj.DefineOwnDataPropertyUnchecked("name", "test"); var array1 = new JsArray(engine, [ JsNumber.Create(1), diff --git a/Jint.Tests.PublicInterface/SharedShapeTests.cs b/Jint.Tests.PublicInterface/SharedShapeTests.cs index 92cf3148ff..5d6f482e9e 100644 --- a/Jint.Tests.PublicInterface/SharedShapeTests.cs +++ b/Jint.Tests.PublicInterface/SharedShapeTests.cs @@ -141,12 +141,12 @@ public void AnInstantiatedShapeObjectHasASharedShape() public void AShapedObjectDeoptedByARawDescriptorWriteNoLongerHasASharedShape() { // Storing a raw descriptor under a string key is a dictionary-mode operation, so it permanently - // deoptimizes a shape-mode receiver — the documented FastSetProperty caveat, witnessed. + // deoptimizes a shape-mode receiver — the documented DefineOwnPropertyUnchecked caveat, witnessed. var engine = new Engine(); var obj = CreateSample(engine); engine.Advanced.HasSharedShape(obj).Should().BeTrue(); - obj.FastSetProperty("extra", new PropertyDescriptor(JsNumber.Create(7), writable: true, enumerable: true, configurable: true)); + obj.DefineOwnPropertyUnchecked("extra", new PropertyDescriptor(JsNumber.Create(7), writable: true, enumerable: true, configurable: true)); engine.Advanced.HasSharedShape(obj).Should().BeFalse(); diff --git a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net10.0.verified.txt b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net10.0.verified.txt index e315ab42de..5d6d5995e1 100644 --- a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net10.0.verified.txt +++ b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net10.0.verified.txt @@ -878,7 +878,6 @@ namespace Jint.Native.Array public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public System.Collections.Generic.IEnumerable> GetEntries(bool includeLength = false) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override sealed bool HasProperty(Jint.Native.JsValue property) { } @@ -936,7 +935,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void Initialize() { } @@ -992,7 +990,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool Set(Jint.Native.JsValue property, Jint.Native.JsValue value, Jint.Native.JsValue receiver) { } @@ -1096,7 +1093,6 @@ namespace Jint.Native public bool Unicode { get; } public bool UnicodeSets { get; } public System.Text.RegularExpressions.Regex Value { get; set; } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1314,7 +1310,6 @@ namespace Jint.Native.Function public Acornima.Ast.IFunction? FunctionDeclaration { get; } public bool Strict { get; } protected abstract Jint.Native.JsValue Call(Jint.Native.JsValue thisObject, Jint.Native.JsValue[] arguments); - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override void RemoveOwnProperty(Jint.Native.JsValue property) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1468,7 +1463,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasIndex(uint index) { } @@ -1491,7 +1485,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasName(string name) { } @@ -1525,18 +1518,18 @@ namespace Jint.Native.Object public Jint.Native.JsValue this[Jint.Native.JsValue property] { get; set; } public Jint.Native.Object.ObjectInstance? Prototype { get; set; } public bool CreateDataProperty(Jint.Native.JsValue p, Jint.Native.JsValue v) { } + public void DefineOwnDataPropertyUnchecked(string name, Jint.Native.JsValue value) { } public virtual bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } + public void DefineOwnPropertyUnchecked(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } + public void DefineOwnPropertyUnchecked(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public virtual bool Delete(Jint.Native.JsValue property) { } protected void EnsureInitialized() { } public override bool Equals(Jint.Native.JsValue? other) { } public bool Equals(Jint.Native.Object.ObjectInstance? other) { } public override bool Equals(object? obj) { } - public void FastSetDataProperty(string name, Jint.Native.JsValue value) { } - public void FastSetProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } - public void FastSetProperty(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public virtual System.Collections.Generic.IEnumerable> GetOwnProperties() { } + public System.Collections.Generic.IEnumerable> GetOwnProperties() { } public virtual Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public virtual System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual Jint.Native.Object.ObjectInstance? GetPrototypeOf() { } @@ -2329,7 +2322,6 @@ namespace Jint.Runtime.Interop public override bool Equals(object? obj) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool HasProperty(Jint.Native.JsValue property) { } diff --git a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net472.verified.txt b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net472.verified.txt index a6f80d28a5..a1905ce1c8 100644 --- a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net472.verified.txt +++ b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net472.verified.txt @@ -726,7 +726,6 @@ namespace Jint.Native.Array public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public System.Collections.Generic.IEnumerable> GetEntries(bool includeLength = false) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override sealed bool HasProperty(Jint.Native.JsValue property) { } @@ -784,7 +783,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void Initialize() { } @@ -840,7 +838,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool Set(Jint.Native.JsValue property, Jint.Native.JsValue value, Jint.Native.JsValue receiver) { } @@ -944,7 +941,6 @@ namespace Jint.Native public bool Unicode { get; } public bool UnicodeSets { get; } public System.Text.RegularExpressions.Regex Value { get; set; } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1161,7 +1157,6 @@ namespace Jint.Native.Function public Acornima.Ast.IFunction? FunctionDeclaration { get; } public bool Strict { get; } protected abstract Jint.Native.JsValue Call(Jint.Native.JsValue thisObject, Jint.Native.JsValue[] arguments); - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override void RemoveOwnProperty(Jint.Native.JsValue property) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1315,7 +1310,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasIndex(uint index) { } @@ -1338,7 +1332,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasName(string name) { } @@ -1372,18 +1365,18 @@ namespace Jint.Native.Object public Jint.Native.JsValue this[Jint.Native.JsValue property] { get; set; } public Jint.Native.Object.ObjectInstance? Prototype { get; set; } public bool CreateDataProperty(Jint.Native.JsValue p, Jint.Native.JsValue v) { } + public void DefineOwnDataPropertyUnchecked(string name, Jint.Native.JsValue value) { } public virtual bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } + public void DefineOwnPropertyUnchecked(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } + public void DefineOwnPropertyUnchecked(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public virtual bool Delete(Jint.Native.JsValue property) { } protected void EnsureInitialized() { } public override bool Equals(Jint.Native.JsValue? other) { } public bool Equals(Jint.Native.Object.ObjectInstance? other) { } public override bool Equals(object? obj) { } - public void FastSetDataProperty(string name, Jint.Native.JsValue value) { } - public void FastSetProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } - public void FastSetProperty(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public virtual System.Collections.Generic.IEnumerable> GetOwnProperties() { } + public System.Collections.Generic.IEnumerable> GetOwnProperties() { } public virtual Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public virtual System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual Jint.Native.Object.ObjectInstance? GetPrototypeOf() { } @@ -2170,7 +2163,6 @@ namespace Jint.Runtime.Interop public override bool Equals(object? obj) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool HasProperty(Jint.Native.JsValue property) { } diff --git a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net8.0.verified.txt b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net8.0.verified.txt index e315ab42de..5d6d5995e1 100644 --- a/Jint.Tests.PublicInterface/Verify/PublicApiTest_net8.0.verified.txt +++ b/Jint.Tests.PublicInterface/Verify/PublicApiTest_net8.0.verified.txt @@ -878,7 +878,6 @@ namespace Jint.Native.Array public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public System.Collections.Generic.IEnumerable> GetEntries(bool includeLength = false) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override sealed bool HasProperty(Jint.Native.JsValue property) { } @@ -936,7 +935,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void Initialize() { } @@ -992,7 +990,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool Set(Jint.Native.JsValue property, Jint.Native.JsValue value, Jint.Native.JsValue receiver) { } @@ -1096,7 +1093,6 @@ namespace Jint.Native public bool Unicode { get; } public bool UnicodeSets { get; } public System.Text.RegularExpressions.Regex Value { get; set; } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1314,7 +1310,6 @@ namespace Jint.Native.Function public Acornima.Ast.IFunction? FunctionDeclaration { get; } public bool Strict { get; } protected abstract Jint.Native.JsValue Call(Jint.Native.JsValue thisObject, Jint.Native.JsValue[] arguments); - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override void RemoveOwnProperty(Jint.Native.JsValue property) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1468,7 +1463,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasIndex(uint index) { } @@ -1491,7 +1485,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasName(string name) { } @@ -1525,18 +1518,18 @@ namespace Jint.Native.Object public Jint.Native.JsValue this[Jint.Native.JsValue property] { get; set; } public Jint.Native.Object.ObjectInstance? Prototype { get; set; } public bool CreateDataProperty(Jint.Native.JsValue p, Jint.Native.JsValue v) { } + public void DefineOwnDataPropertyUnchecked(string name, Jint.Native.JsValue value) { } public virtual bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } + public void DefineOwnPropertyUnchecked(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } + public void DefineOwnPropertyUnchecked(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public virtual bool Delete(Jint.Native.JsValue property) { } protected void EnsureInitialized() { } public override bool Equals(Jint.Native.JsValue? other) { } public bool Equals(Jint.Native.Object.ObjectInstance? other) { } public override bool Equals(object? obj) { } - public void FastSetDataProperty(string name, Jint.Native.JsValue value) { } - public void FastSetProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } - public void FastSetProperty(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public virtual System.Collections.Generic.IEnumerable> GetOwnProperties() { } + public System.Collections.Generic.IEnumerable> GetOwnProperties() { } public virtual Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public virtual System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual Jint.Native.Object.ObjectInstance? GetPrototypeOf() { } @@ -2329,7 +2322,6 @@ namespace Jint.Runtime.Interop public override bool Equals(object? obj) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool HasProperty(Jint.Native.JsValue property) { } diff --git a/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.0.verified.txt b/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.0.verified.txt index a6f80d28a5..a1905ce1c8 100644 --- a/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.0.verified.txt +++ b/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.0.verified.txt @@ -726,7 +726,6 @@ namespace Jint.Native.Array public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public System.Collections.Generic.IEnumerable> GetEntries(bool includeLength = false) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override sealed bool HasProperty(Jint.Native.JsValue property) { } @@ -784,7 +783,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void Initialize() { } @@ -840,7 +838,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool Set(Jint.Native.JsValue property, Jint.Native.JsValue value, Jint.Native.JsValue receiver) { } @@ -944,7 +941,6 @@ namespace Jint.Native public bool Unicode { get; } public bool UnicodeSets { get; } public System.Text.RegularExpressions.Regex Value { get; set; } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1161,7 +1157,6 @@ namespace Jint.Native.Function public Acornima.Ast.IFunction? FunctionDeclaration { get; } public bool Strict { get; } protected abstract Jint.Native.JsValue Call(Jint.Native.JsValue thisObject, Jint.Native.JsValue[] arguments); - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override void RemoveOwnProperty(Jint.Native.JsValue property) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1315,7 +1310,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasIndex(uint index) { } @@ -1338,7 +1332,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasName(string name) { } @@ -1372,18 +1365,18 @@ namespace Jint.Native.Object public Jint.Native.JsValue this[Jint.Native.JsValue property] { get; set; } public Jint.Native.Object.ObjectInstance? Prototype { get; set; } public bool CreateDataProperty(Jint.Native.JsValue p, Jint.Native.JsValue v) { } + public void DefineOwnDataPropertyUnchecked(string name, Jint.Native.JsValue value) { } public virtual bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } + public void DefineOwnPropertyUnchecked(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } + public void DefineOwnPropertyUnchecked(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public virtual bool Delete(Jint.Native.JsValue property) { } protected void EnsureInitialized() { } public override bool Equals(Jint.Native.JsValue? other) { } public bool Equals(Jint.Native.Object.ObjectInstance? other) { } public override bool Equals(object? obj) { } - public void FastSetDataProperty(string name, Jint.Native.JsValue value) { } - public void FastSetProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } - public void FastSetProperty(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public virtual System.Collections.Generic.IEnumerable> GetOwnProperties() { } + public System.Collections.Generic.IEnumerable> GetOwnProperties() { } public virtual Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public virtual System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual Jint.Native.Object.ObjectInstance? GetPrototypeOf() { } @@ -2170,7 +2163,6 @@ namespace Jint.Runtime.Interop public override bool Equals(object? obj) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool HasProperty(Jint.Native.JsValue property) { } diff --git a/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.1.verified.txt b/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.1.verified.txt index a6f80d28a5..a1905ce1c8 100644 --- a/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.1.verified.txt +++ b/Jint.Tests.PublicInterface/Verify/PublicApiTest_netstandard2.1.verified.txt @@ -726,7 +726,6 @@ namespace Jint.Native.Array public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public System.Collections.Generic.IEnumerable> GetEntries(bool includeLength = false) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override sealed bool HasProperty(Jint.Native.JsValue property) { } @@ -784,7 +783,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void Initialize() { } @@ -840,7 +838,6 @@ namespace Jint.Native public override bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override bool Delete(Jint.Native.JsValue property) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool Set(Jint.Native.JsValue property, Jint.Native.JsValue value, Jint.Native.JsValue receiver) { } @@ -944,7 +941,6 @@ namespace Jint.Native public bool Unicode { get; } public bool UnicodeSets { get; } public System.Text.RegularExpressions.Regex Value { get; set; } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1161,7 +1157,6 @@ namespace Jint.Native.Function public Acornima.Ast.IFunction? FunctionDeclaration { get; } public bool Strict { get; } protected abstract Jint.Native.JsValue Call(Jint.Native.JsValue thisObject, Jint.Native.JsValue[] arguments); - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override void RemoveOwnProperty(Jint.Native.JsValue property) { } protected override void SetOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } @@ -1315,7 +1310,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasIndex(uint index) { } @@ -1338,7 +1332,6 @@ namespace Jint.Native.Object public override sealed bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } public override sealed bool Delete(Jint.Native.JsValue property) { } public override sealed Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } - public override sealed System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override sealed Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override sealed System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual bool HasName(string name) { } @@ -1372,18 +1365,18 @@ namespace Jint.Native.Object public Jint.Native.JsValue this[Jint.Native.JsValue property] { get; set; } public Jint.Native.Object.ObjectInstance? Prototype { get; set; } public bool CreateDataProperty(Jint.Native.JsValue p, Jint.Native.JsValue v) { } + public void DefineOwnDataPropertyUnchecked(string name, Jint.Native.JsValue value) { } public virtual bool DefineOwnProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor desc) { } + public void DefineOwnPropertyUnchecked(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } + public void DefineOwnPropertyUnchecked(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public virtual bool Delete(Jint.Native.JsValue property) { } protected void EnsureInitialized() { } public override bool Equals(Jint.Native.JsValue? other) { } public bool Equals(Jint.Native.Object.ObjectInstance? other) { } public override bool Equals(object? obj) { } - public void FastSetDataProperty(string name, Jint.Native.JsValue value) { } - public void FastSetProperty(Jint.Native.JsValue property, Jint.Runtime.Descriptors.PropertyDescriptor value) { } - public void FastSetProperty(string name, Jint.Runtime.Descriptors.PropertyDescriptor value) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public virtual System.Collections.Generic.IEnumerable> GetOwnProperties() { } + public System.Collections.Generic.IEnumerable> GetOwnProperties() { } public virtual Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public virtual System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } protected virtual Jint.Native.Object.ObjectInstance? GetPrototypeOf() { } @@ -2170,7 +2163,6 @@ namespace Jint.Runtime.Interop public override bool Equals(object? obj) { } public override Jint.Native.JsValue Get(Jint.Native.JsValue property, Jint.Native.JsValue receiver) { } public override int GetHashCode() { } - public override System.Collections.Generic.IEnumerable> GetOwnProperties() { } public override Jint.Runtime.Descriptors.PropertyDescriptor GetOwnProperty(Jint.Native.JsValue property) { } public override System.Collections.Generic.List GetOwnPropertyKeys(Jint.Runtime.Types types = 72) { } public override bool HasProperty(Jint.Native.JsValue property) { } diff --git a/Jint.Tests/Runtime/Domain/UuidConstructor.cs b/Jint.Tests/Runtime/Domain/UuidConstructor.cs index c05885535f..9a8613387f 100644 --- a/Jint.Tests/Runtime/Domain/UuidConstructor.cs +++ b/Jint.Tests/Runtime/Domain/UuidConstructor.cs @@ -57,8 +57,8 @@ public static UuidConstructor CreateUuidConstructor(Engine engine) public void Configure() { - FastSetProperty("parse", new PropertyDescriptor(new ClrFunction(Engine, "parse", Parse), true, false, true)); - FastSetProperty("Empty", new PropertyDescriptor(JsUuid.Empty, true, false, true)); + DefineOwnPropertyUnchecked("parse", new PropertyDescriptor(new ClrFunction(Engine, "parse", Parse), true, false, true)); + DefineOwnPropertyUnchecked("Empty", new PropertyDescriptor(JsUuid.Empty, true, false, true)); } public UuidInstance Construct(JsUuid uuid) => new UuidInstance(Engine) { PrimitiveValue = uuid, _prototype = PrototypeObject }; diff --git a/Jint.Tests/Runtime/Domain/UuidPrototype.cs b/Jint.Tests/Runtime/Domain/UuidPrototype.cs index eacd9c14a6..76e714c62b 100644 --- a/Jint.Tests/Runtime/Domain/UuidPrototype.cs +++ b/Jint.Tests/Runtime/Domain/UuidPrototype.cs @@ -33,14 +33,14 @@ public static UuidPrototype CreatePrototypeObject(Engine engine, UuidConstructor _prototype = engine.Realm.Intrinsics.Object.PrototypeObject, }; - obj.FastSetProperty("constructor", new PropertyDescriptor(ctor, false, false, true)); + obj.DefineOwnPropertyUnchecked("constructor", new PropertyDescriptor(ctor, false, false, true)); return obj; } public void Configure() { - FastSetProperty("toString", new PropertyDescriptor(new ClrFunction(Engine, "toString", ToGuidString), true, false, true)); - FastSetProperty("valueOf", new PropertyDescriptor(new ClrFunction(Engine, "valueOf", ValueOf), true, false, true)); + DefineOwnPropertyUnchecked("toString", new PropertyDescriptor(new ClrFunction(Engine, "toString", ToGuidString), true, false, true)); + DefineOwnPropertyUnchecked("valueOf", new PropertyDescriptor(new ClrFunction(Engine, "valueOf", ValueOf), true, false, true)); } } \ No newline at end of file diff --git a/Jint.Tests/Runtime/GlobalSnapshotInternalsTests.cs b/Jint.Tests/Runtime/GlobalSnapshotInternalsTests.cs index f82b406444..3ca662aa83 100644 --- a/Jint.Tests/Runtime/GlobalSnapshotInternalsTests.cs +++ b/Jint.Tests/Runtime/GlobalSnapshotInternalsTests.cs @@ -269,7 +269,7 @@ public void AHostCustomValueDescriptorIsNotDesynchronizedByRestore() { var engine = new Engine(); var descriptor = new HostStateDescriptor(JsNumber.Create(1)); - engine.Realm.GlobalObject.FastSetProperty("cfg", descriptor); + engine.Realm.GlobalObject.DefineOwnPropertyUnchecked("cfg", descriptor); var snapshot = engine.Advanced.CaptureGlobalSnapshot(); engine.Evaluate("cfg = 5;"); diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 70f3d0fc2a..cca7ebe614 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -907,7 +907,7 @@ public void NonWritableArrayElement_ThrowsOnIndexWrite_StrictMode() var contextValue = JsValue.FromObjectWithType(engine, context, typeof(IReadOnlyDictionary)); // Lock the context value using the same pattern as the issue reporter: - // iterate properties, set writable=false, recurse into values, and call FastSetProperty + // iterate properties, set writable=false, recurse into values, and call DefineOwnPropertyUnchecked LockDescriptorHelper(contextValue, []); var contextDescriptor = new Jint.Runtime.Descriptors.PropertyDescriptor @@ -982,7 +982,7 @@ private static void LockDescriptorHelper(JsValue jsValue, HashSet visit LockDescriptorHelper(property.Value.Value, visited); - obj.FastSetProperty(property.Key, property.Value); + obj.DefineOwnPropertyUnchecked(property.Key, property.Value); } obj.PreventExtensions(); @@ -3620,8 +3620,8 @@ static void Test(string message, object value) Console.WriteLine(message); } - engine.Realm.GlobalObject.FastSetDataProperty("global", engine.Realm.GlobalObject); - engine.Realm.GlobalObject.FastSetDataProperty("test", new DelegateWrapper(engine, (Action) Test)); + engine.Realm.GlobalObject.DefineOwnDataPropertyUnchecked("global", engine.Realm.GlobalObject); + engine.Realm.GlobalObject.DefineOwnDataPropertyUnchecked("test", new DelegateWrapper(engine, (Action) Test)); { var ex = Invoking(() => engine.Realm.GlobalObject.ToObject()).Should().ThrowExactly().Which; diff --git a/Jint.Tests/Runtime/ObjectInstanceTests.cs b/Jint.Tests/Runtime/ObjectInstanceTests.cs index b49b0e1dee..475b77ea32 100644 --- a/Jint.Tests/Runtime/ObjectInstanceTests.cs +++ b/Jint.Tests/Runtime/ObjectInstanceTests.cs @@ -64,8 +64,8 @@ public void RemovingFirstPropertyFromObjectInstancePropertiesBucketAndEnumeratin { var engine = new Engine(); var instance = new JsObject(engine); - instance.FastSetDataProperty("bare", JsValue.Null); - instance.FastSetDataProperty("scope", JsValue.Null); + instance.DefineOwnDataPropertyUnchecked("bare", JsValue.Null); + instance.DefineOwnDataPropertyUnchecked("scope", JsValue.Null); instance.RemoveOwnProperty("bare"); var propertyNames = instance.GetOwnProperties().Select(x => x.Key).ToList(); propertyNames.Should().Equal(new JsValue[] { "scope" }); diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs index dd099e4394..af587af753 100644 --- a/Jint.Tests/Runtime/PropertyDescriptorTests.cs +++ b/Jint.Tests/Runtime/PropertyDescriptorTests.cs @@ -103,14 +103,14 @@ public void AllForbiddenDescriptor() } [Test] - public void FastSetPropertyIsVisibleOnBuiltinShapedHost() + public void DefineOwnPropertyUncheckedIsVisibleOnBuiltinShapedHost() { // Math uses builtin-shape storage; a raw property store of a non-shape name joins the // hybrid side dictionary, which every read/enumeration surface must consult — without // that, the property silently doesn't exist. _engine.Evaluate("Math.floor(4.7)").AsNumber().Should().Be(4); // initialize the shaped host var math = _engine.Evaluate("Math").AsObject(); - math.FastSetProperty("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); + math.DefineOwnPropertyUnchecked("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); _engine.Evaluate("Math.custom").AsNumber().Should().Be(42); _engine.Evaluate("Object.getOwnPropertyNames(Math).includes('custom')").AsBoolean().Should().BeTrue(); @@ -118,23 +118,23 @@ public void FastSetPropertyIsVisibleOnBuiltinShapedHost() } [Test] - public void FastSetPropertyBeforeLazyInitializationSurvivesOnDictionaryHost() + public void DefineOwnPropertyUncheckedBeforeLazyInitializationSurvivesOnDictionaryHost() { // a raw store before the host's first property access used to land in _properties and get // wiped when the read-triggered Initialize() replaced the bag; RegExp is a dictionary-path host var regExp = _engine.Evaluate("RegExp").AsObject(); - regExp.FastSetProperty("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); + regExp.DefineOwnPropertyUnchecked("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); _engine.Evaluate("RegExp.custom").AsNumber().Should().Be(42); _engine.Evaluate("typeof RegExp.escape === 'function'").AsBoolean().Should().BeTrue(); } [Test] - public void FastSetPropertyBeforeLazyInitializationSurvivesOnBuiltinShapedHost() + public void DefineOwnPropertyUncheckedBeforeLazyInitializationSurvivesOnBuiltinShapedHost() { // the builtin-shape sibling of the dictionary-host case above var math = _engine.Evaluate("Math").AsObject(); - math.FastSetProperty("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); + math.DefineOwnPropertyUnchecked("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); _engine.Evaluate("Math.custom").AsNumber().Should().Be(42); _engine.Evaluate("Object.getOwnPropertyNames(Math).includes('custom')").AsBoolean().Should().BeTrue(); diff --git a/Jint.Tests/Runtime/SharedObjectShapeTests.cs b/Jint.Tests/Runtime/SharedObjectShapeTests.cs index 8133bfe644..e13af26d29 100644 --- a/Jint.Tests/Runtime/SharedObjectShapeTests.cs +++ b/Jint.Tests/Runtime/SharedObjectShapeTests.cs @@ -414,7 +414,7 @@ static WeakReference RunOnceAndForget(JsObjectShape shape) { var engine = new Engine(); var proto = shape.Instantiate(engine); - proto.FastSetProperty("constructor", new Jint.Runtime.Descriptors.PropertyDescriptor(new JsString("ctor"), Jint.Runtime.Descriptors.PropertyFlag.NonEnumerable)); + proto.DefineOwnPropertyUnchecked("constructor", new Jint.Runtime.Descriptors.PropertyDescriptor(new JsString("ctor"), Jint.Runtime.Descriptors.PropertyFlag.NonEnumerable)); engine.SetValue("proto", proto); // Materialize every kind of member, so anything a materialization could have parked on the shape // would be parked by the time the engine is dropped. diff --git a/Jint.Tests/Runtime/WebApi/WorkerMechanismTests.cs b/Jint.Tests/Runtime/WebApi/WorkerMechanismTests.cs index 8e00316d87..0b8b5d21f7 100644 --- a/Jint.Tests/Runtime/WebApi/WorkerMechanismTests.cs +++ b/Jint.Tests/Runtime/WebApi/WorkerMechanismTests.cs @@ -1146,7 +1146,7 @@ public void AHostThatInstalledItsOwnSelfKeepsIt() { factoryCalls++; var value = new JsObject(engine); - value.FastSetDataProperty("marker", new JsString("host")); + value.DefineOwnDataPropertyUnchecked("marker", new JsString("host")); return value; }), }; @@ -1655,7 +1655,7 @@ protected override ObjectInstance CreateGlobalObject(Realm realm) { var global = base.CreateGlobalObject(realm); var marker = new JsObject(Engine); - marker.FastSetDataProperty("marker", new JsString("host")); + marker.DefineOwnDataPropertyUnchecked("marker", new JsString("host")); global.Prototype = marker; return global; } diff --git a/Jint/Engine.GlobalSnapshot.cs b/Jint/Engine.GlobalSnapshot.cs index f67506bf15..0e307dc332 100644 --- a/Jint/Engine.GlobalSnapshot.cs +++ b/Jint/Engine.GlobalSnapshot.cs @@ -434,9 +434,10 @@ internal static GlobalSnapshot Capture(Engine engine) /// The names are matched against the base definition of each declared override, so a member merely /// named like one of these does not trip the check and an override declared in an intermediate /// class does. Initialize is deliberately absent — the in-box GlobalObject overrides it, and - /// capture forces it to have run before reading anything. get_Extensible used to be listed, for the - /// same reason still is; it left the list when the getter - /// stopped being virtual, so restore's write to the field is now the whole answer by construction. + /// capture forces it to have run before reading anything. get_Extensible and GetOwnProperties used to be + /// listed, for the same reason still is; each left the list + /// when it stopped being virtual, and the enumeration a global overrides is + /// , which is still listed. /// /// private static readonly string[] _storageVirtuals = @@ -445,7 +446,6 @@ internal static GlobalSnapshot Capture(Engine engine) nameof(ObjectInstance.SetOwnProperty), nameof(ObjectInstance.TryGetOwnPropertyValue), nameof(ObjectInstance.ProbeOwnProperty), - nameof(ObjectInstance.GetOwnProperties), nameof(ObjectInstance.GetOwnPropertyKeys), nameof(ObjectInstance.DefineOwnProperty), nameof(ObjectInstance.RemoveOwnProperty), diff --git a/Jint/GlobalValueRegistration.cs b/Jint/GlobalValueRegistration.cs index e8461e76bc..f2f5b0d596 100644 --- a/Jint/GlobalValueRegistration.cs +++ b/Jint/GlobalValueRegistration.cs @@ -58,7 +58,7 @@ internal static void Register(ObjectInstance globalObject, string name, JsValue /// internal static void RegisterDelegate(Engine engine, ObjectInstance globalObject, string name, Delegate value) { - globalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(engine, value), PropertyFlag.NonEnumerable)); + globalObject.DefineOwnPropertyUnchecked(name, new PropertyDescriptor(new DelegateWrapper(engine, value), PropertyFlag.NonEnumerable)); } /// diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index 228b7bd60c..22689ae3ce 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -437,49 +437,6 @@ public IEnumerable> GetEntries(bool includeLength } } - public sealed override IEnumerable> GetOwnProperties() - { - var temp = _dense; - if (temp != null) - { - var length = System.Math.Min(temp.Length, GetLength()); - for (uint i = 0; i < length; i++) - { - var value = temp[i]; - if (value is not null) - { - if (_sparse is null || !_sparse.TryGetValue(i, out var descriptor) || descriptor is null) - { - _sparse ??= new Dictionary(); - _sparse[i] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); - } - yield return new KeyValuePair(TypeConverter.ToString(i), descriptor); - } - } - } - else if (_sparse != null) - { - foreach (var entry in _sparse) - { - var value = entry.Value; - if (value is not null) - { - yield return new KeyValuePair(TypeConverter.ToString(entry.Key), value); - } - } - } - - if (GetLengthDescriptor() is { } lengthDescriptor) - { - yield return new KeyValuePair(CommonProperties.Length, lengthDescriptor); - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { if (CommonProperties.Length.Equals(property)) diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 70424da0ac..27f00726e7 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -70,22 +70,22 @@ protected override void Initialize() _prototype = null }; - unscopables.FastSetDataProperty("at", JsBoolean.True); - unscopables.FastSetDataProperty("copyWithin", JsBoolean.True); - unscopables.FastSetDataProperty("entries", JsBoolean.True); - unscopables.FastSetDataProperty("fill", JsBoolean.True); - unscopables.FastSetDataProperty("find", JsBoolean.True); - unscopables.FastSetDataProperty("findIndex", JsBoolean.True); - unscopables.FastSetDataProperty("findLast", JsBoolean.True); - unscopables.FastSetDataProperty("findLastIndex", JsBoolean.True); - unscopables.FastSetDataProperty("flat", JsBoolean.True); - unscopables.FastSetDataProperty("flatMap", JsBoolean.True); - unscopables.FastSetDataProperty("includes", JsBoolean.True); - unscopables.FastSetDataProperty("keys", JsBoolean.True); - unscopables.FastSetDataProperty("toReversed", JsBoolean.True); - unscopables.FastSetDataProperty("toSorted", JsBoolean.True); - unscopables.FastSetDataProperty("toSpliced", JsBoolean.True); - unscopables.FastSetDataProperty("values", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("at", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("copyWithin", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("entries", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("fill", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("find", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("findIndex", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("findLast", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("findLastIndex", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("flat", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("flatMap", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("includes", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("keys", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("toReversed", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("toSorted", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("toSpliced", JsBoolean.True); + unscopables.DefineOwnDataPropertyUnchecked("values", JsBoolean.True); return unscopables; }, PropertyFlag.Configurable)); diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index b2f82e58a1..00ab68bf8d 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -967,10 +967,10 @@ private static JsObject CreateDecoratorContext( List extraInitializers) { var context = new JsObject(engine); - context.FastSetDataProperty("kind", new JsString(kind)); - context.FastSetDataProperty("name", isPrivate && name is PrivateName pn ? (JsValue) new JsString(pn.Description) : name); - context.FastSetDataProperty("static", isStatic ? JsBoolean.True : JsBoolean.False); - context.FastSetDataProperty("private", isPrivate ? JsBoolean.True : JsBoolean.False); + context.DefineOwnDataPropertyUnchecked("kind", new JsString(kind)); + context.DefineOwnDataPropertyUnchecked("name", isPrivate && name is PrivateName pn ? (JsValue) new JsString(pn.Description) : name); + context.DefineOwnDataPropertyUnchecked("static", isStatic ? JsBoolean.True : JsBoolean.False); + context.DefineOwnDataPropertyUnchecked("private", isPrivate ? JsBoolean.True : JsBoolean.False); var addInitializer = new ClrFunction(engine, "addInitializer", (_, args) => { @@ -984,7 +984,7 @@ private static JsObject CreateDecoratorContext( return JsValue.Undefined; }, 1, PropertyFlag.Configurable); - context.FastSetDataProperty("addInitializer", addInitializer); + context.DefineOwnDataPropertyUnchecked("addInitializer", addInitializer); return context; } @@ -1130,8 +1130,8 @@ private static AccessorDecoratorResult ApplyAccessorDecorators( var context = CreateDecoratorContext(engine, "accessor", name, isStatic, isPrivate, extraInitializers); var valueObj = ObjectInstance.OrdinaryObjectCreate(engine, engine.Realm.Intrinsics.Object.PrototypeObject); - valueObj.FastSetDataProperty("get", currentGetter); - valueObj.FastSetDataProperty("set", currentSetter); + valueObj.DefineOwnDataPropertyUnchecked("get", currentGetter); + valueObj.DefineOwnDataPropertyUnchecked("set", currentSetter); var result = engine.Call((JsValue) callable, JsValue.Undefined, new JsValue[] { valueObj, context }); diff --git a/Jint/Native/Function/Function.cs b/Jint/Native/Function/Function.cs index 0115b3c2ce..62dd84302c 100644 --- a/Jint/Native/Function/Function.cs +++ b/Jint/Native/Function/Function.cs @@ -206,51 +206,6 @@ internal virtual JsValue CallFast(JsValue thisObject, JsValue arg0, JsValue arg1 internal virtual JsValue CallFastVariadic(JsValue thisObject, ReadOnlySpan arguments) => throw new InvalidOperationException($"{GetType()} does not implement CallFastVariadic; GetFastCallShape must not report Variadic."); - public override IEnumerable> GetOwnProperties() - { - var prototypeDescriptor = ReferenceEquals(_prototypeDescriptor, _pendingDescriptor) - ? MaterializePrototypeDescriptor() - : _prototypeDescriptor; - if (prototypeDescriptor != null) - { - yield return new KeyValuePair(CommonProperties.Prototype, prototypeDescriptor); - } - - var length = ReferenceEquals(_length, _pendingDescriptor) ? MaterializeLengthDescriptor() : _length; - if (length != null) - { - yield return new KeyValuePair(CommonProperties.Length, length); - } - if (_nameDescriptor != null) - { - yield return new KeyValuePair(CommonProperties.Name, GetOwnProperty(CommonProperties.Name)); - } - - if (this is ScriptFunction scriptFunction) - { - var argumentsDescriptor = ReferenceEquals(scriptFunction._argumentsDescriptor, _pendingDescriptor) - ? scriptFunction.MaterializeArgumentsDescriptor() - : scriptFunction._argumentsDescriptor; - if (argumentsDescriptor is not null) - { - yield return new KeyValuePair(CommonProperties.Arguments, argumentsDescriptor); - } - - var callerDescriptor = ReferenceEquals(scriptFunction._callerDescriptor, _pendingDescriptor) - ? scriptFunction.MaterializeCallerDescriptor() - : scriptFunction._callerDescriptor; - if (callerDescriptor is not null) - { - yield return new KeyValuePair(CommonProperties.Caller, callerDescriptor); - } - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() { if (_length != null) @@ -761,27 +716,29 @@ public ObjectInstanceWithConstructor(Engine engine, ObjectInstance thisObj) : ba _constructor = new PropertyDescriptor(thisObj, PropertyFlag.NonEnumerable); } - public override IEnumerable> GetOwnProperties() + public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (_constructor != null) + if (CommonProperties.Constructor.Equals(property)) { - yield return new KeyValuePair(CommonProperties.Constructor, _constructor); + return _constructor ?? PropertyDescriptor.Undefined; } - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } + return base.GetOwnProperty(property); } - public override PropertyDescriptor GetOwnProperty(JsValue property) + /// + /// constructor lives in a field rather than the property bag, so the key enumerations have to + /// be told about it the way tells them about length/name: without + /// this, Object.getOwnPropertyNames(f.prototype) and Reflect.ownKeys(f.prototype) + /// answered an empty list for a property hasOwnProperty and + /// Object.getOwnPropertyDescriptor both reported. + /// + internal override IEnumerable GetInitialOwnStringPropertyKeys() { - if (CommonProperties.Constructor.Equals(property)) + if (_constructor != null) { - return _constructor ?? PropertyDescriptor.Undefined; + yield return CommonProperties.Constructor; } - - return base.GetOwnProperty(property); } protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) diff --git a/Jint/Native/Iterator/IteratorResult.cs b/Jint/Native/Iterator/IteratorResult.cs index 0d8ab1bde7..2dcf9d3d36 100644 --- a/Jint/Native/Iterator/IteratorResult.cs +++ b/Jint/Native/Iterator/IteratorResult.cs @@ -185,25 +185,5 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return keys; } - public override IEnumerable> GetOwnProperties() - { - if (_value is not null || _valueDesc is not null) - { - _valueDesc ??= new PropertyDescriptor(_value!, PropertyFlag.ConfigurableEnumerableWritable); - yield return new KeyValuePair(CommonProperties.Value, _valueDesc); - } - - if (_done is not null || _doneDesc is not null) - { - _doneDesc ??= new PropertyDescriptor(_done!, PropertyFlag.ConfigurableEnumerableWritable); - yield return new KeyValuePair(CommonProperties.Done, _doneDesc); - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - public override object ToObject() => this; } diff --git a/Jint/Native/JsArguments.cs b/Jint/Native/JsArguments.cs index 8542d5f910..f08a807651 100644 --- a/Jint/Native/JsArguments.cs +++ b/Jint/Native/JsArguments.cs @@ -237,12 +237,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return base.GetOwnPropertyKeys(types); } - public override IEnumerable> GetOwnProperties() - { - EnsureInitialized(); - return base.GetOwnProperties(); - } - public override PropertyDescriptor GetOwnProperty(JsValue property) { EnsureInitialized(); @@ -284,8 +278,8 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) /// pair, non-enumerable either way) and @@iterator (Writable | Configurable), and nothing else. /// The premise of "and nothing else" is checked rather than assumed: the property bag must still be /// empty, because every property-adding path routes through EnsureInitialized first but - /// FastSetProperty is public and does not, and this object is reachable from a host as a plain - /// ObjectInstance. A non-empty bag falls through to the materializing path, i.e. to the previous + /// DefineOwnPropertyUnchecked is public and does not, and this object is reachable from a host as a + /// plain ObjectInstance. A non-empty bag falls through to the materializing path, i.e. to the previous /// behaviour. /// /// diff --git a/Jint/Native/JsError.cs b/Jint/Native/JsError.cs index 9828eeb8a4..36852c88a3 100644 --- a/Jint/Native/JsError.cs +++ b/Jint/Native/JsError.cs @@ -140,13 +140,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return base.GetOwnPropertyKeys(types); } - public override IEnumerable> GetOwnProperties() - { - // Rare path; materialize for correct insertion ordering and descriptor exposure, then defer to base. - MaterializeMessage(); - return base.GetOwnProperties(); - } - /// /// Deopts the virtual message into ordinary dictionary storage, reproducing the exact /// construction-time descriptor. No-op once the message is already materialized or was never set. diff --git a/Jint/Native/JsObject.Create.cs b/Jint/Native/JsObject.Create.cs index a17d5a52f6..b8fad529a6 100644 --- a/Jint/Native/JsObject.Create.cs +++ b/Jint/Native/JsObject.Create.cs @@ -16,7 +16,7 @@ public sealed partial class JsObject /// indistinguishable from the equivalent object literal, including own-key order. /// /// Unlike populating a fresh through - /// — which stores raw descriptors and + /// — which stores raw descriptors and /// therefore builds a property dictionary — this resolves the layout to an interned hidden class once per /// (engine, layout) and then only fills value slots. Every object created from the same layout in the /// same engine shares that hidden class, so a script reading the same property across a batch of such @@ -130,11 +130,11 @@ public static JsObject Create(Engine engine, JsObjectLayout layout, ReadOnlySpan { if (fallbackSentinel is not null && layout.GetFactory(i) is not null) { - obj.FastSetProperty(keys[i].Name, new LazySlotPropertyDescriptor(obj, fallbackSentinel, i)); + obj.DefineOwnPropertyUnchecked(keys[i].Name, new LazySlotPropertyDescriptor(obj, fallbackSentinel, i)); } else { - obj.FastSetDataProperty(keys[i].Name, values[i] ?? Undefined); + obj.DefineOwnDataPropertyUnchecked(keys[i].Name, values[i] ?? Undefined); } } @@ -316,6 +316,6 @@ private static void AddEntry(JsObject obj, Engine engine, string? name, JsValue? state.Shaped = false; } - obj.FastSetDataProperty(name, v); + obj.DefineOwnDataPropertyUnchecked(name, v); } } diff --git a/Jint/Native/JsObjectLayout.cs b/Jint/Native/JsObjectLayout.cs index c599dcbf17..d7b9ab90ed 100644 --- a/Jint/Native/JsObjectLayout.cs +++ b/Jint/Native/JsObjectLayout.cs @@ -50,7 +50,7 @@ namespace Jint.Native; /// reading item.name in a loop over the batch keeps a monomorphic inline cache and no /// per-property descriptor or property dictionary is allocated. Contrast with populating a fresh /// through -/// , which stores raw descriptors and +/// , which stores raw descriptors and /// therefore forces the dictionary representation. /// /// diff --git a/Jint/Native/JsObjectShape.cs b/Jint/Native/JsObjectShape.cs index eca05a8c4e..9dc25e3b8e 100644 --- a/Jint/Native/JsObjectShape.cs +++ b/Jint/Native/JsObjectShape.cs @@ -442,7 +442,7 @@ public Builder Constant(string name, JsValue value, bool enumerable = true) /// Declares a data property whose value differs per engine — the canonical case being /// constructor. The slot starts as undefined with the declared attributes on every /// instantiated object; the host fills it after creating the object, either with - /// (a setup-time in-place + /// (a setup-time in-place /// slot replacement — on a shaped object a declared name never falls back to a dictionary) or with /// DefineOwnProperty / Set, which are spec-validated and therefore need the declared /// attributes to permit the write. That is why the slot must be declared writable or configurable. diff --git a/Jint/Native/JsRegExp.cs b/Jint/Native/JsRegExp.cs index a942781565..afd329af0d 100644 --- a/Jint/Native/JsRegExp.cs +++ b/Jint/Native/JsRegExp.cs @@ -209,19 +209,6 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri base.SetOwnProperty(property, desc); } - public override IEnumerable> GetOwnProperties() - { - if (_prototypeDescriptor != null) - { - yield return new KeyValuePair(PropertyLastIndex, _prototypeDescriptor); - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = new List(); diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index 04936d760c..77be947002 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -1053,7 +1053,7 @@ private void AddJsonMember(JsObject obj, string name, JsValue value, ref bool sh shaped = false; } - obj.FastSetDataProperty(name, value); + obj.DefineOwnDataPropertyUnchecked(name, value); } /// diff --git a/Jint/Native/Object/AGENTS.md b/Jint/Native/Object/AGENTS.md index 073aefa7e9..e4fc324670 100644 --- a/Jint/Native/Object/AGENTS.md +++ b/Jint/Native/Object/AGENTS.md @@ -50,8 +50,8 @@ Eight further levers: - Override `ProbeOwnProperty` so existence/enumerability questions (`in`, `hasOwnProperty`, `propertyIsEnumerable`, `Object.keys`/`values`/`entries`, `Object.assign`, spread, `JSON.stringify`) are answered without materializing a descriptor at all. The override must agree with `GetOwnProperty` at the same instant — the engine trusts it on the hot path, and a wrong `Missing` silently drops the key from every enumeration above. Precisely because that failure is so quiet, it now has a verifier: with host-contract verification on, every probe the engine *consumes* is checked against `GetOwnProperty` and the first disagreement throws. The engine reaches the hook through the internal non-virtual `ProbeOwnPropertyChecked`, so a new consumption site must call that, not the virtual; a `base.ProbeOwnProperty` inside an override is not a consumption site. The check runs only for an override declared outside the Jint assembly (in-box ones are covered by in-repo tests, and `ArrayLikeObject`'s through its own `HasIndex` check), decided by a per-`Type` cache mirroring the `PropertyAccessSemantics` derivation. The cost for a host that overrides it: with verification on, an existence question costs one descriptor per probe — the one the probe existed to avoid. - Where the data is a fixed record, do not subclass at all: `JsObject.Create(engine, layout, values)` and `JsObject.CreateFromEntries` build straight into the hidden-class representation, so objects sharing a layout share one shape and a script reading a batch of them keeps a monomorphic inline cache. `Engine.Advanced.HasSharedShape` lets a test prove the shaping actually happened, since both fall back to the dictionary representation silently; it is a contract, so a host may commit that assertion to its own suite (`GetObjectRepresentation` is the non-contract diagnostic to reach for when a `false` needs explaining). - Where the object is a **prototype** rather than a record, do not subclass either: `JsObjectShape` declares the members once per process and `Instantiate` creates one such object per engine, in the same shared-layout representation the engine's own intrinsics use. Members materialize per member on first access in that engine, and two properties follow from the representation rather than from anything the host declares — existence and enumerability questions are answered from the layout without creating the member's function (the one exception is the factory form of `PerRealmSlot`, whose flags are not knowable until its factory has run, so it defers), and the object is a valid *holder* for the prototype-method inline cache, which an `ObjectInstance` subclass used as a prototype can never be (below). None of this changes the counts above: those are about the receiver, and a host instance in front of a shaped prototype still pays exactly what its own semantics cost it. Because `Instantiate` returns a type the host cannot derive from, per-prototype bookkeeping goes on the object through `JsObjectShape.SetHostState` / `GetHostState` rather than in a field — an `object?` the engine never reads. It is per *instance*, not per shape, so unlike the delegates and constants inside a shape it **may** be engine-affine, and it dies with the object; a member implementation reaches it by walking up from its receiver, and `GetHostState` answers `null` for a `null` argument so that walk is one expression. Attaching it after creation rather than at `Instantiate` is what lets it hold a reference to the constructor whose `prototype` *is* that object. -- Where the data is a **live indexed collection** (a DOM `NodeList`, a result window, any lazily computed list), derive from `ArrayLikeObject` instead of assembling the property model by hand. A subclass supplies only `uint Length` and `bool TryGetIndex(uint, out JsValue)`; the base class derives the whole coherence matrix from them — the two hooks above, `GetOwnProperty`, both key enumerations, and WebIDL-shaped `Delete`/`DefineOwnProperty` refusals — and two engine lanes key on the *type*: `ArrayOperations` (so `Array.prototype` generics, the array iterator behind `for-of`/spread/`Array.from`/destructuring, and `apply`-spreading cost **one** `TryGetIndex` per element with no key object) and the interpreter's computed-read branch (so `list[i]` costs one `TryGetIndex` with no `Reference` and no descriptor). It is deliberately array-*like*: `Array.isArray` stays `false`, exactly as a browser answers for a `NodeList`. Its elements are deliberately read-only, and **every member it derives is sealed** — `Get`, `GetOwnProperty`, `GetOwnPropertyKeys`, `GetOwnProperties`, the probe, `Set`, `Delete`, `DefineOwnProperty` — so a host needing to intercept every named read stays on plain `ObjectInstance`, and a host wanting a *named member* declares one through the named-projection hooks below rather than by overriding three of those and keeping them consistent by hand. It is a host subclass like any other, so it is a receiver the caches may serve and never a holder they may trust — a `NodeList.prototype` carrying the collection's methods wants `JsObjectShape` above. A third, optional `protected virtual bool HasIndex(uint)` answers the existence-only questions (`in`, `hasOwnProperty`, the key enumerations, `delete`, the generics' per-element hole test) without projecting an element to discard; its default is exactly `TryGetIndex`-and-discard, so overriding it is pure opt-in, and it carries the same agree-with-`TryGetIndex`-at-the-same-instant obligation the other hooks carry, verified on every probe whenever host-contract verification is on. -- Where the data is a **named record** projected from native state — a settings bag, a row, a document, a live view over a host struct — derive from `NamedPropertyObject`, the sibling of `ArrayLikeObject` for the string-keyed case. It exists because that host used to write **five to nine** mutually-consistent hooks by hand (`GetOwnProperty`, `ProbeOwnProperty`, `GetOwnPropertyKeys`, `TryGetOwnPropertyValue`, `Set`, `Delete`, `DefineOwnProperty`, and — the one that actually shipped broken — `GetOwnProperties`, which nothing script-visible enumerates through). A subclass now supplies `int NameCount`, `string NameAt(int)` and `bool TryGetNamedValue(string, out JsValue)`, and the base class **seals** all eight of those plus `Get` and `HasProperty`. Because the base overrides `TryGetOwnPropertyValue`, a host reaches the zero-descriptor column above **without knowing it exists**: every read row costs no descriptor and no probe. Symbols and names the projection does not carry stay entirely ordinary, so `Symbol.toStringTag`, `Symbol.iterator` and expandos work as they always did. Sealing `Get` is what makes the `Ordinary` claim its constructor makes true by construction: a host needing to observe reads that resolve on its *prototype* stays on plain `ObjectInstance` — every *own* read is already `TryGetNamedValue`. +- Where the data is a **live indexed collection** (a DOM `NodeList`, a result window, any lazily computed list), derive from `ArrayLikeObject` instead of assembling the property model by hand. A subclass supplies only `uint Length` and `bool TryGetIndex(uint, out JsValue)`; the base class derives the whole coherence matrix from them — the two hooks above, `GetOwnProperty`, both key enumerations, and WebIDL-shaped `Delete`/`DefineOwnProperty` refusals — and two engine lanes key on the *type*: `ArrayOperations` (so `Array.prototype` generics, the array iterator behind `for-of`/spread/`Array.from`/destructuring, and `apply`-spreading cost **one** `TryGetIndex` per element with no key object) and the interpreter's computed-read branch (so `list[i]` costs one `TryGetIndex` with no `Reference` and no descriptor). It is deliberately array-*like*: `Array.isArray` stays `false`, exactly as a browser answers for a `NodeList`. Its elements are deliberately read-only, and **every member it derives is sealed** — `Get`, `GetOwnProperty`, `GetOwnPropertyKeys`, the probe, `Set`, `Delete`, `DefineOwnProperty` — so a host needing to intercept every named read stays on plain `ObjectInstance`, and a host wanting a *named member* declares one through the named-projection hooks below rather than by overriding three of those and keeping them consistent by hand. It is a host subclass like any other, so it is a receiver the caches may serve and never a holder they may trust — a `NodeList.prototype` carrying the collection's methods wants `JsObjectShape` above. A third, optional `protected virtual bool HasIndex(uint)` answers the existence-only questions (`in`, `hasOwnProperty`, the key enumerations, `delete`, the generics' per-element hole test) without projecting an element to discard; its default is exactly `TryGetIndex`-and-discard, so overriding it is pure opt-in, and it carries the same agree-with-`TryGetIndex`-at-the-same-instant obligation the other hooks carry, verified on every probe whenever host-contract verification is on. +- Where the data is a **named record** projected from native state — a settings bag, a row, a document, a live view over a host struct — derive from `NamedPropertyObject`, the sibling of `ArrayLikeObject` for the string-keyed case. It exists because that host used to write **five to eight** mutually-consistent hooks by hand (`GetOwnProperty`, `ProbeOwnProperty`, `GetOwnPropertyKeys`, `TryGetOwnPropertyValue`, `Set`, `Delete`, `DefineOwnProperty`). A subclass now supplies `int NameCount`, `string NameAt(int)` and `bool TryGetNamedValue(string, out JsValue)`, and the base class **seals** all seven of those plus `Get` and `HasProperty`. Because the base overrides `TryGetOwnPropertyValue`, a host reaches the zero-descriptor column above **without knowing it exists**: every read row costs no descriptor and no probe. Symbols and names the projection does not carry stay entirely ordinary, so `Symbol.toStringTag`, `Symbol.iterator` and expandos work as they always did. Sealing `Get` is what makes the `Ordinary` claim its constructor makes true by construction: a host needing to observe reads that resolve on its *prototype* stays on plain `ObjectInstance` — every *own* read is already `TryGetNamedValue`. - **The named-projection hooks are one set, and both classes publish the same one.** `NamedPropertyObject` declares the first three `abstract`, because a record with no names is not a record; `ArrayLikeObject` declares all eight `virtual` with empty defaults, because a collection with no named members is the common case — and it derives from the runtime type once, cached per `Type` the way `PropertyAccessSemantics` is, whether the type declared any of them at all, so a collection that declared none is never asked. One shared implementation (`NamedProjection.cs`, reached through the internal `INamedProjection` both classes implement explicitly) derives the model from them for both, because two hand-kept copies would reproduce *inside the engine* exactly the incoherence these classes exist to spare a host. @@ -84,7 +84,9 @@ when it breaks nothing at compile time — and the engine-wide rows are in | Surface | Location | | --- | --- | -| `ObjectInstance` overridable virtuals — `GetOwnProperty`, `HasProperty`, `Delete`, `DefineOwnProperty`, `GetOwnPropertyKeys`, `GetOwnProperties`, `RemoveOwnProperty`, `PreventExtensions`, `TryGetProperty`, `Initialize`, plus the `protected internal` `TryGetOwnPropertyValue`, `ProbeOwnProperty`, `SetOwnProperty`, `GetPrototypeOf` | `Jint/Native/Object/ObjectInstance.cs` | +| `ObjectInstance` overridable virtuals — `GetOwnProperty`, `HasProperty`, `Delete`, `DefineOwnProperty`, `GetOwnPropertyKeys`, `RemoveOwnProperty`, `PreventExtensions`, `TryGetProperty`, `Initialize`, plus the `protected internal` `TryGetOwnPropertyValue`, `ProbeOwnProperty`, `SetOwnProperty`, `GetPrototypeOf` | `Jint/Native/Object/ObjectInstance.cs` | +| `ObjectInstance.GetOwnProperties` — public, **non-virtual**, derived from `GetOwnPropertyKeys` + `GetOwnProperty` | `Jint/Native/Object/ObjectInstance.cs` | +| `ObjectInstance.DefineOwnPropertyUnchecked` (two overloads) and `DefineOwnDataPropertyUnchecked` | `Jint/Native/Object/ObjectInstance.Unchecked.cs` | | `ProbeOwnProperty` / `OwnPropertyProbe` | `Jint/Native/Object/` | | `PropertyAccessSemantics` + `ObjectInstance.SetPropertyAccessSemantics` | `Jint/Native/Object/PropertyAccessSemantics.cs` | | `ArrayLikeObject` — public abstract class, exactly two abstract members (`Length`, `TryGetIndex`) | `Jint/Native/Object/ArrayLikeObject.cs` | @@ -95,12 +97,13 @@ when it breaks nothing at compile time — and the engine-wide rows are in ### Lazy values: which mechanism owns which storage world -The three lazy mechanisms each own one storage world and are not interchangeable: `JsObjectLayout.Builder.AddLazy` for a member of a fixed-shape **record**, `JsObjectShape` for a member of a **prototype**, [`PropertyDescriptor.CreateLazy`](../../Runtime/Descriptors/AGENTS.md#lazy-values) for a **descriptor** a host stores or returns itself (`SetOwnProperty`/`GetOwnProperty` on a subclass, `FastSetProperty` on a dictionary-mode object, a hand-rolled global install). The factory does not change the `FastSetProperty` rule in the repository-root [`AGENTS.md`](../../../AGENTS.md) — a string-keyed `FastSetProperty` of any raw descriptor still deopts a shape-mode receiver to dictionary — so reaching for it inside a shaped world buys laziness at the cost of the shape. +The three lazy mechanisms each own one storage world and are not interchangeable: `JsObjectLayout.Builder.AddLazy` for a member of a fixed-shape **record**, `JsObjectShape` for a member of a **prototype**, [`PropertyDescriptor.CreateLazy`](../../Runtime/Descriptors/AGENTS.md#lazy-values) for a **descriptor** a host stores or returns itself (`SetOwnProperty`/`GetOwnProperty` on a subclass, `DefineOwnPropertyUnchecked` on a dictionary-mode object, a hand-rolled global install). The factory does not change the `DefineOwnPropertyUnchecked` rule in the repository-root [`AGENTS.md`](../../../AGENTS.md) — a string-keyed `DefineOwnPropertyUnchecked` of any raw descriptor still deopts a shape-mode receiver to dictionary — so reaching for it inside a shaped world buys laziness at the cost of the shape. ### Gotchas Each of these cost a real integrator or a real bug. These are the ones that bite in this area; the rest of the list is split across the files indexed from the repository-root [`AGENTS.md`](../../../AGENTS.md). +- **An own property kept in a field has to be declared to the key side too.** `GetOwnProperty` answering a name is half a property: the key enumerations read `GetOwnPropertyKeys`, which for an in-box type means overriding it (`JsError`, `IteratorResult`, `JsRegExp`) or the internal `GetInitialOwnStringPropertyKeys` (`Function`'s `length`/`name`/`prototype`, `StringInstance`'s `length`, `JsEvent`'s `isTrusted`). Forgetting the key side ships a property `hasOwnProperty` and `Object.getOwnPropertyDescriptor` both report while `Object.getOwnPropertyNames` and `Reflect.ownKeys` answer an empty list — which is what `f.prototype`'s `constructor` did until [#3455](https://github.com/sebastienros/jint/pull/3455), because it declared itself to `GetOwnProperties` alone. Now that `GetOwnProperties` is derived from the key list, the same omission also hides the property from the CLR conversion path and the debugger, so there is one place to get right instead of two to keep in step. - **Extensibility is said through `PreventExtensions`, never through the `Extensible` getter.** `Extensible` was `public virtual` over an `internal` setter, which is a shape that cannot be got right: a subclass overriding the getter to a constant keeps the setter, and the setter simply stops feeding the getter. `Object.preventExtensions` then returned the object, `Object.isExtensible` went on answering `true`, strict mode threw nothing and `Object.seal`/`Object.freeze` became no-ops — a `[[PreventExtensions]]` invariant violation no script can detect. Squidex shipped exactly that, twice (`ContentDataObject`, `ContentFieldObject`: `public override bool Extensible => true;`). The getter is non-virtual now, so the shape does not compile; the intent behind it — an object nothing may make non-extensible — is `public override bool PreventExtensions() => false;`, which is an answer `[[PreventExtensions]]` is allowed to give and which makes `Object.preventExtensions` raise a `TypeError` instead of lying. `JsStorage` and a length-tracking `JsTypedArray` are the in-box worked examples. The exotic `[[IsExtensible]]` hook survives as the **internal** `ObjectInstance.IsExtensible()`, for the only two objects that legitimately deviate: a Proxy, which must run the `isExtensible` trap on every read, and a module namespace. The one lie left reachable — `PreventExtensions() => true` without reaching `base` — has a verifier, gated on the override being declared outside the Jint assembly precisely so that reading a Proxy's extensibility (a user trap, with side effects) is never something a verifier does. - **A global installed *after* construction must invalidate; one installed during it need not.** `Engine.AddLazyGlobal` is the options-time registration's body invoked on a live engine, and the difference is that the live engine's handler trees may already hold a resolved binding for that name — a warmed identifier site keeps the previous descriptor by reference and revalidates it against `GlobalObject._propertiesVersion` alone. `SetProperty` bumps that counter on every path it can take (shared-layout slot replacement, hybrid side-dictionary add, plain dictionary store, deopt fallback), which is exactly why the install goes through it and not through a direct `_properties` write. It correctly leaves `_lexicalMutations` and `_envBindingInjectionEpoch` alone: a global *property* is not a lexical binding and injects nothing into an existing environment. Any future API that installs or replaces a global after construction inherits this; `Jint.Tests/Runtime/LazyGlobalInstallationTests.cs` pins both halves. diff --git a/Jint/Native/Object/ArrayLikeObject.cs b/Jint/Native/Object/ArrayLikeObject.cs index e203b00cdd..1498b44307 100644 --- a/Jint/Native/Object/ArrayLikeObject.cs +++ b/Jint/Native/Object/ArrayLikeObject.cs @@ -489,48 +489,6 @@ public sealed override List GetOwnPropertyKeys(Types types = Types.Stri return keys; } - /// - /// The same order as , with the descriptors materialized. - /// - public sealed override IEnumerable> GetOwnProperties() - { - var length = CheckedLength(); - for (uint i = 0; i < length; i++) - { - if (i > 0 && i % Engine.ConstraintCheckInterval == 0) - { - _engine.Constraints.Check(); - } - - if (ReadIndex(i, out var value)) - { - yield return new KeyValuePair(JsString.Create(i), new PropertyDescriptor(value, IndexFlags)); - } - } - - yield return new KeyValuePair(CommonProperties.Length, new PropertyDescriptor(JsNumber.Create(length), LengthFlags)); - - var names = CollectNames(); - foreach (var key in names) - { - var name = key.ToString(); - if (NamedProjection.Read(this, name, out var value)) - { - yield return new KeyValuePair(key, NamedProjection.DescriptorFor(this, name, value)); - } - } - - foreach (var entry in base.GetOwnProperties()) - { - if (NamedProjection.ShadowsBagKey(this, entry.Key, names.Count)) - { - continue; - } - - yield return entry; - } - } - /// /// Routes an assignment to a name claims to . /// Indices and length never reach it — they are non-writable, so the ordinary path refuses them with diff --git a/Jint/Native/Object/NamedPropertyObject.cs b/Jint/Native/Object/NamedPropertyObject.cs index d9e11313af..b69cbd2d9c 100644 --- a/Jint/Native/Object/NamedPropertyObject.cs +++ b/Jint/Native/Object/NamedPropertyObject.cs @@ -327,33 +327,6 @@ public sealed override List GetOwnPropertyKeys(Types types = Types.Stri return keys; } - /// - /// The same order as , with the descriptors materialized. - /// - public sealed override IEnumerable> GetOwnProperties() - { - var names = NamedProjection.CollectNames(this, _engine, NamedProjection.NameOrder.IndexNamesFirst); - foreach (var key in names) - { - var name = key.ToString(); - if (NamedProjection.Read(this, name, out var value)) - { - yield return new KeyValuePair(key, NamedProjection.DescriptorFor(this, name, value)); - } - } - - foreach (var entry in base.GetOwnProperties()) - { - // Shadowed bag entries are skipped, exactly as in GetOwnPropertyKeys. - if (NamedProjection.ShadowsBagKey(this, entry.Key, names.Count)) - { - continue; - } - - yield return entry; - } - } - /// /// Routes an assignment to a name claims to /// , and leaves every other key entirely ordinary. A refused write raises a diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index d24c9afe78..e4bd8124b1 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -191,7 +191,7 @@ internal ObjectInstance Construct(int propertyCount) /// subsequent addition flows through CreateDataProperty, so each add transitions a shared interned /// hidden class instead of filling a per-object dictionary; objects built from the same source /// layout end up referencing the very same . Callers that populate the result - /// through raw descriptor stores (FastSetProperty / FastSetDataProperty, descriptor bags) must keep + /// through raw descriptor stores (DefineOwnPropertyUnchecked / DefineOwnDataPropertyUnchecked, descriptor bags) must keep /// using — those stores would immediately deopt a shaped target. /// internal JsObject ConstructShapeBuilding() @@ -570,7 +570,7 @@ private JsObject GroupBy(JsValue thisObject, JsValue items, JsValue callbackfn) var obj = OrdinaryObjectCreate(_engine, null); foreach (var pair in grouping) { - obj.FastSetProperty(pair.Key, new PropertyDescriptor(pair.Value, PropertyFlag.ConfigurableEnumerableWritable)); + obj.DefineOwnPropertyUnchecked(pair.Key, new PropertyDescriptor(pair.Value, PropertyFlag.ConfigurableEnumerableWritable)); } return obj; diff --git a/Jint/Native/Object/ObjectInstance.Fast.cs b/Jint/Native/Object/ObjectInstance.Fast.cs deleted file mode 100644 index 7f915185b3..0000000000 --- a/Jint/Native/Object/ObjectInstance.Fast.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Runtime.CompilerServices; -using Jint.Runtime.Descriptors; - -namespace Jint.Native.Object; - -/// -/// Raw own-property write helpers that bypass the ECMAScript [[Set]] / [[DefineOwnProperty]] -/// pipeline. They are intended for building up an object the host fully controls — populating a freshly -/// created object, or installing members on a host-provided global — and are not a general substitute for -/// or -/// . See the individual members for what is skipped. -/// -/// For building a new plain object out of host data, prefer -/// or -/// over -/// populating a fresh through these helpers: those build directly into the -/// hidden-class representation instead of deoptimizing out of it. -/// -/// -public partial class ObjectInstance -{ - /// - /// Stores as an own property named on this object, - /// replacing any own property of that name. - /// - /// - /// - /// The write always lands as an own property on the receiver and therefore shadows - /// anything of that name inherited from the prototype chain. If the member is — or later becomes — - /// prototype-resident, this silently creates a shadow rather than updating the inherited member, and a - /// data descriptor can end up shadowing an inherited accessor. (Error.prototype.stack is a - /// concrete example of a member that lives on the prototype as an accessor.) - /// - /// - /// No inherited setter is invoked, and no [[DefineOwnProperty]] validation runs — extensibility, - /// the configurable/writable flags of an existing property, and the data/accessor compatibility rules - /// are all ignored, and no TypeError can be raised. That is what "fast" means here. - /// - /// - /// Storing a raw is a dictionary-mode operation, so a shape-mode - /// receiver is deoptimized and permanently forfeits the shape inline cache used for property reads and - /// writes. Prefer this for setup-time writes only; for steady-state mutation of an existing property use - /// , which stores through the existing - /// descriptor and leaves the receiver's layout intact. - /// - /// - public void FastSetProperty(string name, PropertyDescriptor value) - { - SetProperty(name, value); - } - - /// - /// Stores as an own property keyed by on this - /// object, replacing any own property of that name. - /// - /// - /// - /// The write always lands as an own property on the receiver and therefore shadows - /// anything of that name inherited from the prototype chain. If the member is — or later becomes — - /// prototype-resident, this silently creates a shadow rather than updating the inherited member, and a - /// data descriptor can end up shadowing an inherited accessor. (Error.prototype.stack is a - /// concrete example of a member that lives on the prototype as an accessor.) - /// - /// - /// No inherited setter is invoked, and no [[DefineOwnProperty]] validation runs — extensibility, - /// the configurable/writable flags of an existing property, and the data/accessor compatibility rules - /// are all ignored, and no TypeError can be raised. That is what "fast" means here. - /// - /// - /// Storing a raw under a string key is a dictionary-mode operation, so a - /// shape-mode receiver is deoptimized and permanently forfeits the shape inline cache used for property - /// reads and writes. Prefer this for setup-time writes only; for steady-state mutation of an existing - /// property use , which stores through the - /// existing descriptor and leaves the receiver's layout intact. - /// - /// - public void FastSetProperty(JsValue property, PropertyDescriptor value) - { - SetProperty(property, value); - } - - /// - /// Stores as a configurable, enumerable, writable own data property named - /// on this object, replacing any own property of that name. - /// - /// - /// - /// The write always lands as an own property on the receiver and therefore shadows - /// anything of that name inherited from the prototype chain. If the member is — or later becomes — - /// prototype-resident, this silently creates a shadow rather than updating the inherited member, and - /// the data property written here can end up shadowing an inherited accessor. (Error.prototype.stack - /// is a concrete example of a member that lives on the prototype as an accessor.) - /// - /// - /// No inherited setter is invoked, and no [[DefineOwnProperty]] validation runs — extensibility, - /// the configurable/writable flags of an existing property, and the data/accessor compatibility rules - /// are all ignored, and no TypeError can be raised. That is what "fast" means here. - /// - /// - /// It stores a raw , which is a dictionary-mode operation, so a shape-mode - /// receiver is deoptimized and permanently forfeits the shape inline cache used for property reads and - /// writes. Prefer this for setup-time writes only; for steady-state mutation of an existing property use - /// , which stores through the existing - /// descriptor and leaves the receiver's layout intact. - /// - /// - /// Despite the name, a loop of these calls is not the fastest way to project host data into a - /// new object, precisely because of that deopt: each object gets a descriptor per property and a - /// property dictionary, and the script reading a batch of them never keeps a monomorphic inline cache. - /// Use when the property - /// names are known up front, or - /// when - /// they are only known at runtime; both build straight into the shaped representation, so objects - /// sharing a layout share one hidden class. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FastSetDataProperty(string name, JsValue value) - { - SetProperty(name, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); - } -} diff --git a/Jint/Native/Object/ObjectInstance.Unchecked.cs b/Jint/Native/Object/ObjectInstance.Unchecked.cs new file mode 100644 index 0000000000..be3e3112da --- /dev/null +++ b/Jint/Native/Object/ObjectInstance.Unchecked.cs @@ -0,0 +1,126 @@ +using System.Runtime.CompilerServices; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Object; + +/// +/// Raw own-property write helpers that bypass the ECMAScript [[Set]] / [[DefineOwnProperty]] +/// pipeline. They are intended for building up an object the host fully controls — populating a freshly +/// created object, or installing members on a host-provided global — and are not a general substitute for +/// or +/// . See the individual members for what is skipped. +/// +/// For building a new plain object out of host data, prefer +/// or +/// over +/// populating a fresh through these helpers: those build directly into the +/// hidden-class representation instead of deoptimizing out of it. +/// +/// +public partial class ObjectInstance +{ + /// + /// Stores as an own property named , replacing any own + /// property of that name and validating nothing. + /// + /// + /// + /// The write always lands as an own property on the receiver and therefore shadows + /// anything of that name inherited from the prototype chain; no inherited setter is invoked. If the + /// member is — or later becomes — prototype-resident, this silently creates a shadow rather than + /// updating the inherited member, and a data descriptor can end up shadowing an inherited accessor. + /// (Error.prototype.stack is a concrete example of a member that lives on the prototype as an + /// accessor.) + /// + /// + /// No [[DefineOwnProperty]] validation runs — extensibility, the configurable/writable flags of + /// an existing property, and the data/accessor compatibility rules are all ignored, so the call always + /// succeeds and can never raise a TypeError. That is what Unchecked names. + /// + /// + /// Storing a raw under a string key is a dictionary-mode operation, so + /// a shape-mode receiver is deoptimized and permanently forfeits the shape inline cache used for + /// property reads and writes. Prefer this for setup-time writes only; for steady-state mutation of an + /// existing property use , which stores through + /// the existing descriptor and leaves the receiver's layout intact. + /// + /// + public void DefineOwnPropertyUnchecked(string name, PropertyDescriptor value) + { + SetProperty(name, value); + } + + /// + /// Stores as an own property keyed by , replacing + /// any own property of that name and validating nothing. + /// + /// + /// + /// The write always lands as an own property on the receiver and therefore shadows + /// anything of that name inherited from the prototype chain; no inherited setter is invoked. If the + /// member is — or later becomes — prototype-resident, this silently creates a shadow rather than + /// updating the inherited member, and a data descriptor can end up shadowing an inherited accessor. + /// (Error.prototype.stack is a concrete example of a member that lives on the prototype as an + /// accessor.) + /// + /// + /// No [[DefineOwnProperty]] validation runs — extensibility, the configurable/writable flags of + /// an existing property, and the data/accessor compatibility rules are all ignored, so the call always + /// succeeds and can never raise a TypeError. That is what Unchecked names. + /// + /// + /// Storing a raw under a string key is a dictionary-mode operation, so + /// a shape-mode receiver is deoptimized and permanently forfeits the shape inline cache used for + /// property reads and writes; a symbol key does not deopt. Prefer this for setup-time writes only; for + /// steady-state mutation of an existing property use + /// , which stores through the existing + /// descriptor and leaves the receiver's layout intact. + /// + /// + public void DefineOwnPropertyUnchecked(JsValue property, PropertyDescriptor value) + { + SetProperty(property, value); + } + + /// + /// Stores as a configurable, enumerable, writable own data property named + /// , replacing any own property of that name and validating nothing. + /// + /// + /// + /// The write always lands as an own property on the receiver and therefore shadows + /// anything of that name inherited from the prototype chain; no inherited setter is invoked. If the + /// member is — or later becomes — prototype-resident, this silently creates a shadow rather than + /// updating the inherited member, and the data property written here can end up shadowing an inherited + /// accessor. (Error.prototype.stack is a concrete example of a member that lives on the prototype + /// as an accessor.) + /// + /// + /// No [[DefineOwnProperty]] validation runs — extensibility, the configurable/writable flags of + /// an existing property, and the data/accessor compatibility rules are all ignored, so the call always + /// succeeds and can never raise a TypeError. That is what Unchecked names. + /// + /// + /// It stores a raw , which is a dictionary-mode operation, so a + /// shape-mode receiver is deoptimized and permanently forfeits the shape inline cache used for property + /// reads and writes. Prefer this for setup-time writes only; for steady-state mutation of an existing + /// property use , which stores through the + /// existing descriptor and leaves the receiver's layout intact. + /// + /// + /// A loop of these calls is therefore not the way to project a batch of host records into script: each + /// object gets a descriptor per property and a property dictionary, and the script reading the batch + /// never keeps a monomorphic inline cache. Use + /// when the property names + /// are known up front, or + /// when + /// they are only known at runtime; both build straight into the shaped representation, so objects + /// sharing a layout share one hidden class. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DefineOwnDataPropertyUnchecked(string name, JsValue value) + { + SetProperty(name, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); + } +} diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index dd88c46922..8fd0fda073 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -379,83 +379,40 @@ internal void ClearProperties() } /// - /// Enumerates this object's own properties as key/descriptor pairs. The base implementation yields the - /// stored string keys first, then the symbols; the string keys come out in storage order (slot order - /// when shaped, insertion order otherwise), which is not the specification's own-key order — - /// integer-like keys are not hoisted and sorted the way does it. + /// Enumerates this object's own properties as key/descriptor pairs, in + /// order, each descriptor read through . /// /// /// - /// Script-visible enumeration does not go through this method. Object.keys / - /// values / entries, for..in, object spread and rest, Object.assign, - /// JSON.stringify and all list keys with - /// and then filter them with - /// — neither of which consults GetOwnProperties. Overriding only - /// this method therefore leaves every one of those seeing whatever the base - /// reports, which for a host object projecting its properties from - /// native state is the engine's own (usually empty) property tables. A host that wants its properties - /// enumerable to script must override , and should override - /// alongside it so existence and enumerability are answered without - /// materializing a descriptor per key. + /// It is derived from those two and is not itself an extension point: a host that overrides + /// — and alongside it, so existence and + /// enumerability are answered without materializing a descriptor per key — is enumerated correctly here + /// with nothing further to write, and is enumerated correctly by script at the same time. /// /// - /// What does route through it: converting this object to a CLR value - /// ( when Options.Interop.CreateClrObject is configured), - /// GetSmallestIndex on the array-like operation path, the debugger's binding-name enumeration - /// (GlobalEnvironment / ObjectEnvironment), and the debug view. Overrides in the box chain - /// to base.GetOwnProperties() to combine their exotic own properties with the stored ones. + /// Script-visible enumeration does not come through here at all. Object.keys / values / + /// entries, for..in, object spread and rest, Object.assign, JSON.stringify + /// and list keys with and + /// filter them with . What does come through here is converting this + /// object to a CLR value ( when Options.Interop.CreateClrObject + /// is configured) and the debug view. + /// + /// + /// It materializes one descriptor per key, so prefer when only the keys + /// are wanted and when only existence or enumerability is. The key list + /// is taken before the first descriptor, so a caller may store back into the object while enumerating. /// /// - public virtual IEnumerable> GetOwnProperties() + public IEnumerable> GetOwnProperties() { - EnsureInitialized(); - - if ((_type & InternalTypes.ShapeMode) != InternalTypes.Empty) - { - var jo = Unsafe.As(this); - var shape = jo.ShapeOf; - var slotCount = shape.SlotCount; - if (slotCount > 0) - { - var keys = new Key[slotCount]; - shape.CollectKeys(keys); - for (var i = 0; i < slotCount; i++) - { - yield return new KeyValuePair(new JsString(keys[i].Name), new SlotPropertyDescriptor(jo, i)); - } - } - } - else if ((_type & InternalTypes.BuiltinShapeMode) != InternalTypes.Empty) - { - var shaped = Unsafe.As(this); - var names = shaped.BuiltinShape.Names; - for (var i = 0; i < names.Length; i++) - { - yield return new KeyValuePair(JsString.Create(names[i].Name), MaterializeBuiltinSlot(shaped, i)); - } - - // hybrid additions (added after every shape name, preserving insertion order) - if (_properties != null) - { - foreach (var pair in _properties) - { - yield return new KeyValuePair(new JsString(pair.Key), pair.Value); - } - } - } - else if (_properties != null) - { - foreach (var pair in _properties) - { - yield return new KeyValuePair(new JsString(pair.Key), pair.Value); - } - } - - if (_symbols != null) + var keys = GetOwnPropertyKeys(); + for (var i = 0; i < keys.Count; i++) { - foreach (var pair in _symbols) + var key = keys[i]; + var descriptor = GetOwnProperty(key); + if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) { - yield return new KeyValuePair(pair.Key, pair.Value); + yield return new KeyValuePair(key, descriptor); } } } @@ -3078,10 +3035,11 @@ internal virtual ulong GetSmallestIndex(ulong length) return 0; } + // Keys only: the descriptors GetOwnProperties would materialize are all discarded here. var min = length; - foreach (var entry in GetOwnProperties()) + foreach (var key in GetOwnPropertyKeys(Types.String)) { - if (ulong.TryParse(entry.Key.ToString(), out var index)) + if (ulong.TryParse(key.ToString(), out var index)) { min = System.Math.Min(index, min); } @@ -3089,9 +3047,9 @@ internal virtual ulong GetSmallestIndex(ulong length) if (Prototype?.Properties != null) { - foreach (var entry in Prototype.GetOwnProperties()) + foreach (var key in Prototype.GetOwnPropertyKeys(Types.String)) { - if (ulong.TryParse(entry.Key.ToString(), out var index)) + if (ulong.TryParse(key.ToString(), out var index)) { min = System.Math.Min(index, min); } diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index 83fd9fda18..486ff9ea4a 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -316,8 +316,8 @@ private void PerformPromiseAllKeyed( { alreadyCalled = true; var res = _engine.Realm.Intrinsics.Object.Construct(2); - res.FastSetDataProperty("status", "fulfilled"); - res.FastSetDataProperty("value", args.At(0)); + res.DefineOwnDataPropertyUnchecked("status", "fulfilled"); + res.DefineOwnDataPropertyUnchecked("value", args.At(0)); values[capturedIndex] = res; remainingElementsCount--; if (remainingElementsCount == 0) @@ -335,8 +335,8 @@ private void PerformPromiseAllKeyed( { alreadyCalled = true; var res = _engine.Realm.Intrinsics.Object.Construct(2); - res.FastSetDataProperty("status", "rejected"); - res.FastSetDataProperty("reason", args.At(0)); + res.DefineOwnDataPropertyUnchecked("status", "rejected"); + res.DefineOwnDataPropertyUnchecked("reason", args.At(0)); values[capturedIndex] = res; remainingElementsCount--; if (remainingElementsCount == 0) @@ -640,8 +640,8 @@ void ResolveIfFinished() alreadyCalled = true; var res = Engine.Realm.Intrinsics.Object.Construct(2); - res.FastSetDataProperty("status", "fulfilled"); - res.FastSetDataProperty("value", args.At(0)); + res.DefineOwnDataPropertyUnchecked("status", "fulfilled"); + res.DefineOwnDataPropertyUnchecked("value", args.At(0)); results[capturedIndex] = res; ResolveIfFinished(); @@ -657,8 +657,8 @@ void ResolveIfFinished() alreadyCalled = true; var res = Engine.Realm.Intrinsics.Object.Construct(2); - res.FastSetDataProperty("status", "rejected"); - res.FastSetDataProperty("reason", args.At(0)); + res.DefineOwnDataPropertyUnchecked("status", "rejected"); + res.DefineOwnDataPropertyUnchecked("reason", args.At(0)); results[capturedIndex] = res; ResolveIfFinished(); diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index 54d80da05f..72aedd1e1c 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -1373,8 +1373,8 @@ internal static JsValue RegExpBuiltinExec(JsRegExp R, string s) // "aaa".match() => [ '', index: 0, input: 'aaa' ] var array = R.Engine.Realm.Intrinsics.Array.ArrayCreate(1); - array.FastSetDataProperty(PropertyIndex._value, lastIndex); - array.FastSetDataProperty(PropertyInput._value, s); + array.DefineOwnDataPropertyUnchecked(PropertyIndex._value, lastIndex); + array.DefineOwnDataPropertyUnchecked(PropertyInput._value, s); array.SetIndexValue(0, JsString.Empty, updateLength: false); return array; } diff --git a/Jint/Native/String/StringInstance.cs b/Jint/Native/String/StringInstance.cs index 19b29dabd6..6dabceba45 100644 --- a/Jint/Native/String/StringInstance.cs +++ b/Jint/Native/String/StringInstance.cs @@ -128,19 +128,6 @@ protected internal sealed override OwnPropertyProbe ProbeOwnProperty(JsValue pro return OwnPropertyProbe.Enumerable; } - public sealed override IEnumerable> GetOwnProperties() - { - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - - if (_length != null) - { - yield return new KeyValuePair(CommonProperties.Length, _length); - } - } - internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() { yield return JsString.LengthString; diff --git a/Jint/NodeCompat/NodeQueryStringModule.cs b/Jint/NodeCompat/NodeQueryStringModule.cs index 5aa89793b3..1e61fdeb52 100644 --- a/Jint/NodeCompat/NodeQueryStringModule.cs +++ b/Jint/NodeCompat/NodeQueryStringModule.cs @@ -322,7 +322,7 @@ private static JsObject Parse(Engine engine, JsObject moduleObject, JsValue orig for (var i = 0; i < accumulated.Count; i++) { - result.FastSetDataProperty(accumulated[i].Key, accumulated[i].Value); + result.DefineOwnDataPropertyUnchecked(accumulated[i].Key, accumulated[i].Value); } return result; diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index a356a03b99..d5356dfc2a 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -281,10 +281,10 @@ internal PropertyFlag Flags /// /// Where this belongs. It describes one property, so it fits wherever a host stores or returns /// descriptors: SetOwnProperty and GetOwnProperty on an - /// subclass, on a + /// subclass, on a /// dictionary-mode object, a hand-rolled global or prototype install. It is not the tool for the /// two shaped storage worlds, and storing one into them is a deoptimization rather than a lazy member: - /// a string-keyed FastSetProperty of any raw descriptor moves a shape-mode object to the + /// a string-keyed DefineOwnPropertyUnchecked of any raw descriptor moves a shape-mode object to the /// dictionary representation permanently. Use JsObjectLayout.CreateBuilder().AddLazy for a lazy /// member of a fixed-shape record and JsObjectShape for a lazily materialized member of a /// prototype; each of the three mechanisms owns one storage world. diff --git a/Jint/Runtime/Environments/GlobalEnvironment.cs b/Jint/Runtime/Environments/GlobalEnvironment.cs index 03d7e5f5b7..44d1e15b36 100644 --- a/Jint/Runtime/Environments/GlobalEnvironment.cs +++ b/Jint/Runtime/Environments/GlobalEnvironment.cs @@ -554,9 +554,9 @@ internal override string[] GetAllBindingNames() // which - in spite of the very general name - is actually only used by the debugger // at this point. var names = new List((_global._properties?.Count ?? 0) + (_declarativeRecord._dictionary?.Count ?? 0)); - foreach (var name in _global.GetOwnProperties()) + foreach (var name in _global.GetOwnPropertyKeys(Types.String)) { - names.Add(name.Key.ToString()); + names.Add(name.ToString()); } foreach (var name in _declarativeRecord.GetAllBindingNames()) diff --git a/Jint/Runtime/Environments/ObjectEnvironment.cs b/Jint/Runtime/Environments/ObjectEnvironment.cs index 30de2fdc22..37088ade90 100644 --- a/Jint/Runtime/Environments/ObjectEnvironment.cs +++ b/Jint/Runtime/Environments/ObjectEnvironment.cs @@ -163,16 +163,25 @@ internal override JsValue GetBindingValue(Key name, bool strict) internal override JsValue WithBaseObject() => _withEnvironment ? _bindingObject : Undefined; - internal override bool HasBindings() => _bindingObject._properties?.Count > 0 || (_bindingObject._type & (InternalTypes.ShapeMode | InternalTypes.BuiltinShapeMode)) != InternalTypes.Empty; + /// + /// Whether the debugger should show this scope at all. The two storage tests answer every in-box object + /// without asking it anything; a host object keeping its own-property set outside those tables answers + /// neither, and would otherwise be reported as an empty with scope, so the last clause asks it the + /// same question would. + /// + internal override bool HasBindings() + => _bindingObject._properties?.Count > 0 + || (_bindingObject._type & (InternalTypes.ShapeMode | InternalTypes.BuiltinShapeMode)) != InternalTypes.Empty + || _bindingObject.GetOwnPropertyKeys(Types.String).Count > 0; internal override string[] GetAllBindingNames() { if (_bindingObject is not null) { var names = new List(_bindingObject._properties?.Count ?? 0); - foreach (var name in _bindingObject.GetOwnProperties()) + foreach (var name in _bindingObject.GetOwnPropertyKeys(Types.String)) { - names.Add(name.Key.ToString()); + names.Add(name.ToString()); } return names.ToArray(); } diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index e288e8d2e8..dd2965a2b7 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -779,14 +779,6 @@ public override List GetOwnPropertyKeys(Types types = Types.Empty | Typ return [.. EnumerateOwnPropertyKeys(types)]; } - public override IEnumerable> GetOwnProperties() - { - foreach (var key in EnumerateOwnPropertyKeys(Types.String | Types.Symbol)) - { - yield return new KeyValuePair(key, GetOwnProperty(key)); - } - } - private IEnumerable EnumerateOwnPropertyKeys(Types types) { // prefer object order, add possible other properties after diff --git a/Jint/Runtime/JavaScriptException.cs b/Jint/Runtime/JavaScriptException.cs index 3a4d52c15e..1d19090d7d 100644 --- a/Jint/Runtime/JavaScriptException.cs +++ b/Jint/Runtime/JavaScriptException.cs @@ -205,7 +205,7 @@ internal void SetCallstack(Engine engine, in SourceLocation location, bool overw jsError.EnsureMessageMaterialized(); } - errObj.FastSetProperty(CommonProperties.Stack._value, new PropertyDescriptor(_callStack, false, false, false)); + errObj.DefineOwnPropertyUnchecked(CommonProperties.Stack._value, new PropertyDescriptor(_callStack, false, false, false)); } } diff --git a/Jint/Test262AgentManager.cs b/Jint/Test262AgentManager.cs index 6ca08504b3..2b323c80e2 100644 --- a/Jint/Test262AgentManager.cs +++ b/Jint/Test262AgentManager.cs @@ -35,7 +35,7 @@ public void InstallAgent(Engine engine, ObjectInstance container) var agent = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); // start(scriptSource) - spawn a new agent running the given script - agent.FastSetProperty("start", new PropertyDescriptor(new ClrFunction(engine, "start", + agent.DefineOwnPropertyUnchecked("start", new PropertyDescriptor(new ClrFunction(engine, "start", (_, args) => { var scriptSource = args.At(0).AsString(); @@ -44,7 +44,7 @@ public void InstallAgent(Engine engine, ObjectInstance container) }), true, true, true)); // broadcast(sab) - share SharedArrayBuffer with all agents - agent.FastSetProperty("broadcast", new PropertyDescriptor(new ClrFunction(engine, "broadcast", + agent.DefineOwnPropertyUnchecked("broadcast", new PropertyDescriptor(new ClrFunction(engine, "broadcast", (_, args) => { var buffer = args.At(0) switch @@ -58,7 +58,7 @@ public void InstallAgent(Engine engine, ObjectInstance container) }), true, true, true)); // getReport() - get a report from the queue (returns null if none) - agent.FastSetProperty("getReport", new PropertyDescriptor(new ClrFunction(engine, "getReport", + agent.DefineOwnPropertyUnchecked("getReport", new PropertyDescriptor(new ClrFunction(engine, "getReport", (_, _) => { var report = GetReport(); @@ -66,7 +66,7 @@ public void InstallAgent(Engine engine, ObjectInstance container) }), true, true, true)); // sleep(ms) - sleep for ms milliseconds - agent.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", + agent.DefineOwnPropertyUnchecked("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", (_, args) => { var ms = (int) TypeConverter.ToNumber(args.At(0)); @@ -75,13 +75,13 @@ public void InstallAgent(Engine engine, ObjectInstance container) }), true, true, true)); // monotonicNow() - high-resolution timestamp in milliseconds - agent.FastSetProperty("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", + agent.DefineOwnPropertyUnchecked("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", (_, _) => { return MonotonicNow(); }), true, true, true)); - container.FastSetProperty("agent", new PropertyDescriptor(agent, true, true, true)); + container.DefineOwnPropertyUnchecked("agent", new PropertyDescriptor(agent, true, true, true)); } private void StartAgent(string scriptSource) @@ -204,7 +204,7 @@ private void Run() var agentObj = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); // receiveBroadcast(callback) - wait for broadcast and call callback with SAB - agentObj.FastSetProperty("receiveBroadcast", new PropertyDescriptor(new ClrFunction(engine, "receiveBroadcast", + agentObj.DefineOwnPropertyUnchecked("receiveBroadcast", new PropertyDescriptor(new ClrFunction(engine, "receiveBroadcast", (thisValue, args) => { var callback = args.At(0); @@ -213,7 +213,7 @@ private void Run() }), true, true, true)); // report(value) - send a report back to main thread - agentObj.FastSetProperty("report", new PropertyDescriptor(new ClrFunction(engine, "report", + agentObj.DefineOwnPropertyUnchecked("report", new PropertyDescriptor(new ClrFunction(engine, "report", (_, args) => { var value = TypeConverter.ToString(args.At(0)); @@ -222,7 +222,7 @@ private void Run() }), true, true, true)); // sleep(ms) - sleep for ms milliseconds - agentObj.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", + agentObj.DefineOwnPropertyUnchecked("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", (_, args) => { var ms = (int) TypeConverter.ToNumber(args.At(0)); @@ -231,7 +231,7 @@ private void Run() }), true, true, true)); // leaving() - signal agent is done - agentObj.FastSetProperty("leaving", new PropertyDescriptor(new ClrFunction(engine, "leaving", + agentObj.DefineOwnPropertyUnchecked("leaving", new PropertyDescriptor(new ClrFunction(engine, "leaving", (_, _) => { _leaving = true; @@ -239,14 +239,14 @@ private void Run() }), true, true, true)); // monotonicNow() - high-resolution timestamp in milliseconds - agentObj.FastSetProperty("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", + agentObj.DefineOwnPropertyUnchecked("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", (_, _) => { return _manager.MonotonicNow(); }), true, true, true)); var container = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); - container.FastSetProperty("agent", new PropertyDescriptor(agentObj, true, true, true)); + container.DefineOwnPropertyUnchecked("agent", new PropertyDescriptor(agentObj, true, true, true)); engine.SetValue("$262", container); // Execute the agent script diff --git a/Jint/Test262Object.cs b/Jint/Test262Object.cs index 7d9e80087b..e28b46e82f 100644 --- a/Jint/Test262Object.cs +++ b/Jint/Test262Object.cs @@ -36,12 +36,12 @@ public static ObjectInstance Install(Engine engine, Realm realm) var o = ObjectInstance.OrdinaryObjectCreate(engine, realm.Intrinsics.Object.PrototypeObject); // "A reference to the global object on which the $262 object lives." - o.FastSetProperty("global", new PropertyDescriptor(realm.GlobalObject, true, true, true)); + o.DefineOwnPropertyUnchecked("global", new PropertyDescriptor(realm.GlobalObject, true, true, true)); // %AbstractModuleSource% intrinsic - exposed via $262 for source-phase-imports tests - o.FastSetProperty("AbstractModuleSource", new PropertyDescriptor(CreateAbstractModuleSource(engine, realm), true, true, true)); + o.DefineOwnPropertyUnchecked("AbstractModuleSource", new PropertyDescriptor(CreateAbstractModuleSource(engine, realm), true, true, true)); - o.FastSetProperty("evalScript", new PropertyDescriptor(new ClrFunction(engine, realm, "evalScript", + o.DefineOwnPropertyUnchecked("evalScript", new PropertyDescriptor(new ClrFunction(engine, realm, "evalScript", (_, args) => { if (args.Length > 1) @@ -56,10 +56,10 @@ public static ObjectInstance Install(Engine engine, Realm realm) // the new realm's global object, and returns the $262 property of the new realm's global object". // So the whole API - createRealm included, so realms can nest - goes onto the new global, and the // new realm's own $262 is what comes back. - o.FastSetProperty("createRealm", new PropertyDescriptor(new ClrFunction(engine, realm, "createRealm", + o.DefineOwnPropertyUnchecked("createRealm", new PropertyDescriptor(new ClrFunction(engine, realm, "createRealm", (_, _) => Install(engine, engine._host.CreateRealm()), 0), true, true, true)); - o.FastSetProperty("detachArrayBuffer", new PropertyDescriptor(new ClrFunction(engine, realm, "detachArrayBuffer", + o.DefineOwnPropertyUnchecked("detachArrayBuffer", new PropertyDescriptor(new ClrFunction(engine, realm, "detachArrayBuffer", (_, args) => { var buffer = (JsArrayBuffer) args.At(0); @@ -67,7 +67,7 @@ public static ObjectInstance Install(Engine engine, Realm realm) return JsValue.Undefined; }, 0), true, true, true)); - o.FastSetProperty("gc", new PropertyDescriptor(new ClrFunction(engine, realm, "gc", + o.DefineOwnPropertyUnchecked("gc", new PropertyDescriptor(new ClrFunction(engine, realm, "gc", (_, _) => { GC.Collect(); @@ -75,7 +75,7 @@ public static ObjectInstance Install(Engine engine, Realm realm) return JsValue.Undefined; }, 0), true, true, true)); - o.FastSetProperty("IsHTMLDDA", new PropertyDescriptor(new IsHTMLDDA(engine, realm), true, true, true)); + o.DefineOwnPropertyUnchecked("IsHTMLDDA", new PropertyDescriptor(new IsHTMLDDA(engine, realm), true, true, true)); realm.GlobalObject.Set("$262", o); return o; diff --git a/Jint/WebApi/Events/JsEvent.cs b/Jint/WebApi/Events/JsEvent.cs index af4bb95a50..9f20954ef4 100644 --- a/Jint/WebApi/Events/JsEvent.cs +++ b/Jint/WebApi/Events/JsEvent.cs @@ -227,19 +227,6 @@ internal override IEnumerable GetInitialOwnStringPropertyKeys() } } - public override IEnumerable> GetOwnProperties() - { - if (!HasStoredIsTrusted) - { - yield return new KeyValuePair(_isTrusted, IsTrustedDescriptor); - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - private bool HasStoredIsTrusted => _properties?.ContainsKey(_isTrustedKey) == true; private PropertyDescriptor IsTrustedDescriptor => _engine.Realm.Intrinsics.Event.PrototypeObject.IsTrustedDescriptor; diff --git a/Jint/WebApi/Storage/JsStorage.cs b/Jint/WebApi/Storage/JsStorage.cs index 028c723deb..31219d3233 100644 --- a/Jint/WebApi/Storage/JsStorage.cs +++ b/Jint/WebApi/Storage/JsStorage.cs @@ -319,35 +319,6 @@ public override List GetOwnPropertyKeys(Types types = Types.String | Ty return keys; } - /// - /// The same order as , with the descriptors materialized. The key list - /// is snapshotted first because this is a lazy sequence whose consumer may mutate the store between two - /// elements. - /// - public override IEnumerable> GetOwnProperties() - { - var live = Provider.Keys; - var names = new string[live.Count]; - for (var i = 0; i < names.Length; i++) - { - names[i] = live[i]; - } - - foreach (var name in names) - { - var key = JsString.Create(name); - if (TryGetVisibleNamedProperty(key, out var value)) - { - yield return new KeyValuePair(key, new PropertyDescriptor(JsString.Create(value), NamedPropertyFlags)); - } - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - /// /// https://webidl.spec.whatwg.org/#legacy-platform-object-preventextensions — a legacy platform object /// refuses, which keeps it extensible forever and makes Object.freeze(storage) and diff --git a/README.md b/README.md index a88871a0d4..233b9b2200 100644 --- a/README.md +++ b/README.md @@ -2140,10 +2140,8 @@ receiver gets no own-property inline caching — every own read reaches your `Ge have names rather than indices — derive from `NamedPropertyObject`, the string-keyed sibling of `ArrayLikeObject`. You implement three members, `int NameCount`, `string NameAt(int index)` and `bool TryGetNamedValue(string name, out JsValue value)`; the base class derives and seals everything else, - so the five hooks this shape used to need kept mutually consistent cannot disagree — including - `GetOwnProperties`, which nothing script-visible enumerates through and which is the one embedders reach for - by mistake, shipping an object invisible to `Object.keys`, `for..in`, spread, `Object.assign` and - `JSON.stringify`. Because the base overrides `TryGetOwnPropertyValue` for you, reads cost **no descriptor + so the five hooks this shape used to need kept mutually consistent cannot disagree. Because the base + overrides `TryGetOwnPropertyValue` for you, reads cost **no descriptor and no probe** without your having to know that lane exists. Five optional hooks refine it, each defaulting to what a projection with no native support for that question answers, so adding one is pure opt-in and adding none leaves a read-only record: @@ -2176,7 +2174,13 @@ receiver gets no own-property inline caching — every own read reaches your `Ge `ClrFunction` — which stays the right choice when the body really is a lambda. A host function is not a constructor: `new` raises a `TypeError`, and a callable that wants `new` derives from `Constructor` and implements `Construct` instead. -- If you must subclass, override `TryGetOwnPropertyValue` so an own read hands the value over with no +- If you must subclass, declare your keys with `GetOwnPropertyKeys`. It is the *only* enumeration hook: + `Object.keys` / `values` / `entries`, `for..in`, spread and rest, `Object.assign`, `JSON.stringify`, the CLR + conversion behind `ToObject()`, the debugger and `GetOwnProperties` all list keys through it. + (`GetOwnProperties` used to be a second `virtual` whose name read like the hook and which none of those + script-visible paths called; it is derived from `GetOwnPropertyKeys` and `GetOwnProperty` now, and is no + longer overridable, so there is nothing left to keep in step by hand.) +- Then override `TryGetOwnPropertyValue` so an own read hands the value over with no descriptor at all, and `ProbeOwnProperty` so existence and enumerability questions (`in`, `Object.keys`, spread, `JSON.stringify`) are answered without materializing one either. Both carry an obligation to agree with `GetOwnProperty`, and neither is re-verified on the hot path — a `ProbeOwnProperty` that wrongly reports @@ -2208,7 +2212,7 @@ than a value snapshot. When the value is lazy only *once*, use `PropertyDescript instead — it memoizes the produced value and then stops being custom-valued, which readmits the property to the member-write fast path and the global-identifier cache that a permanently custom-valued descriptor is declined by; store it wherever you store descriptors (`SetOwnProperty` or `GetOwnProperty` on a host subclass, -`FastSetProperty`, a hand-rolled global). It is the descriptor-shaped member of the same family as +`DefineOwnPropertyUnchecked`, a hand-rolled global). It is the descriptor-shaped member of the same family as `JsObjectLayout.AddLazy` (records) and `JsObjectShape` (prototypes), and it does not exempt you from the rule above them: storing any raw descriptor under a string key still moves a shape-mode object to the dictionary representation. For a whole global that may never be touched, `Options.AddLazyGlobal` defers building diff --git a/docs/v5-migration.md b/docs/v5-migration.md index e94f1a851f..c14c8cb6f9 100644 --- a/docs/v5-migration.md +++ b/docs/v5-migration.md @@ -73,6 +73,7 @@ This table is filled by the pull request that removes the member. A member that | `JsonSerializer.Serialize(JsValue, JsValue, JsValue, ResultLimits)` | `new JsonSerializer(engine, limits).Serialize(value, replacer, space)` — see [2.6](#26-a-json-serializers-limits-are-its-own-not-an-argument-to-every-call) | [#3459](https://github.com/sebastienros/jint/pull/3459) | | `JsonSerializer.Serialize(JsValue, IBufferWriter, ResultLimits)` | `new JsonSerializer(engine, limits).Serialize(value, writer)` — see [2.6](#26-a-json-serializers-limits-are-its-own-not-an-argument-to-every-call) | [#3459](https://github.com/sebastienros/jint/pull/3459) | | `JsonSerializer.Serialize(JsValue, JsValue, JsValue, IBufferWriter, ResultLimits)` | `new JsonSerializer(engine, limits).Serialize(value, replacer, space, writer)` — see [2.6](#26-a-json-serializers-limits-are-its-own-not-an-argument-to-every-call) | [#3459](https://github.com/sebastienros/jint/pull/3459) | +| `ObjectInstance.GetOwnProperties()` → **no longer `virtual`** | nothing to call instead — the method is still there, and still public. What is gone is the ability to *override* it: it is derived from `GetOwnPropertyKeys` + `GetOwnProperty` now. A host that overrode it declares the same properties by overriding `GetOwnPropertyKeys` (and `ProbeOwnProperty` beside it), which is what every script-visible enumeration already read — see [2.6](#27-getownproperties-is-derived-not-overridden) | [#3461](https://github.com/sebastienros/jint/pull/3461) | | `ICldrProvider.GetSupportedCalendars` (and `DefaultCldrProvider`'s override) | `ICalendarProvider.GetSupportedCalendars`. ECMA-402 has one list of calendars, not two, and defines it as the calendars the implementation can format — which in Jint means the ones it can *convert*, because that is what formatting a non-ISO calendar goes through. A calendar with conversions and no names still formats numerically; one with names and no conversions cannot be formatted at all. Adding a calendar was already three overrides on `ICalendarProvider`, and it now reaches `Intl` as well as `Temporal` | [#3404](https://github.com/sebastienros/jint/issues/3404) | ### 2.1 Sealed types @@ -248,6 +249,58 @@ once, at construction, instead of once per call. That is not observable — an e by the time anything can serialize through it — but it is why one instance can no longer serve two policies. A host that used the trailing argument to vary limits per call holds one serializer per policy instead, or constructs one per call as `JSON.stringify` does. +### 2.7 `GetOwnProperties` is derived, not overridden ([#3461](https://github.com/sebastienros/jint/pull/3461)) + +`ObjectInstance.GetOwnProperties()` used to be a `virtual` that a host could override to declare the +properties it projects out of native state. It was the wrong one to reach for, and its name is why: +**nothing script-visible ever called it.** `Object.keys` / `values` / `entries`, `for..in`, object spread +and rest, `Object.assign`, `JSON.stringify` and `JsonSerializer` list keys through `GetOwnPropertyKeys` and +filter them with `ProbeOwnProperty`. A host that overrode `GetOwnProperties` alone therefore shipped an +object whose properties script could not enumerate — and a host that did the right thing and overrode the +key hooks alone shipped one that `GetOwnProperties`' own consumers could not see, of which the CLR +conversion behind `ToObject()` and the debugger are the two an embedder meets. + +It is now non-virtual and derived: the keys come from `GetOwnPropertyKeys`, each descriptor from +`GetOwnProperty`, and a key whose descriptor is absent is skipped. One pair of overrides answers everything. + +```c# +// 4.16.x — two independent declarations of the same fact, and only one of them was read by script +public override IEnumerable> GetOwnProperties() +{ + foreach (var field in _fields) + { + yield return new KeyValuePair( + new JsString(field.Key), + new PropertyDescriptor(field.Value, PropertyFlag.ConfigurableEnumerableWritable)); + } +} + +// 5.x — declare the keys; GetOwnProperties, the CLR conversion and the debugger all follow +public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) +{ + var keys = new List(); + if ((types & Types.String) != Types.Empty) + { + foreach (var field in _fields) + { + keys.Add(new JsString(field.Key)); + } + } + + keys.AddRange(base.GetOwnPropertyKeys(types)); + return keys; +} + +// and, so existence and enumerability cost no descriptor +protected override OwnPropertyProbe ProbeOwnProperty(JsValue property) => /* ... */; +``` + +Better still, do not write either: `NamedPropertyObject` (a named record) and `ArrayLikeObject` (a live +indexed collection) derive the whole coherence matrix from two or three members and seal the rest. + +The in-box overrides are gone with it — `ArrayInstance`, `Function`, `JsRegExp`, `StringInstance`, +`ObjectWrapper`, `ArrayLikeObject`, `NamedPropertyObject` and five others each declared their keys twice, and +now declare them once. Two of those pairs did not agree; see [4.44](#445-getownproperties-reports-what-the-key-enumerations-report). ## 3. Renamed and reshaped API @@ -1002,6 +1055,34 @@ are records, so they have value equality and a `ToString` that prints every dime `UntrustedCodeLimits.BeginOperation` still requires the **instance** the engine was configured with: a `with` expression produces a value-equal but different object, so configure the engine with the one the scope will use. +### 3.17 The two raw-write helpers say what they do ([#3461](https://github.com/sebastienros/jint/pull/3461)) + +| 4.16.x | 5.x | +| --- | --- | +| `ObjectInstance.FastSetProperty(string, PropertyDescriptor)` | `ObjectInstance.DefineOwnPropertyUnchecked(string, PropertyDescriptor)` | +| `ObjectInstance.FastSetProperty(JsValue, PropertyDescriptor)` | `ObjectInstance.DefineOwnPropertyUnchecked(JsValue, PropertyDescriptor)` | +| `ObjectInstance.FastSetDataProperty(string, JsValue)` | `ObjectInstance.DefineOwnDataPropertyUnchecked(string, JsValue)` | + +Same bodies, same behaviour — a mechanical rename, and the compiler finds every call site. + +The old names claimed a speed the methods do not have and hid the four things they actually do. They are +`[[DefineOwnProperty]]` with the checks taken out, which is what the new names say: + +- the write always creates an **own** property, so it *shadows* anything of that name on the prototype chain; +- no inherited setter runs, so a data write can end up shadowing an inherited accessor + (`Error.prototype.stack` is the concrete one); +- no `[[DefineOwnProperty]]` validation runs — extensibility, an existing property's configurable/writable + flags and the data/accessor compatibility rules are all ignored, so the call always succeeds and can never + raise a `TypeError`; +- and storing a raw descriptor under a string key is a dictionary-mode operation, so a shape-mode receiver is + permanently deoptimized and forfeits the shape inline cache. + +"Fast" was the opposite of that last point: a loop of `FastSetDataProperty` calls is the *slow* way to project +a batch of host records, because every object gets its own descriptors and its own property dictionary and the +script reading them never keeps a monomorphic inline cache. `JsObject.Create` and `JsObject.CreateFromEntries` +are the fast ones, and they were already what the doc comment pointed at. + +Use these for setup-time writes on an object you fully control; use `Set` for steady-state mutation. ## 4. Breaking without a signature change @@ -2205,6 +2286,35 @@ exactly what it wrote before. It moves for an embedder who has installed a CLDR- `ICldrProvider` — `Intl.RelativeTimeFormat` and `Intl.DurationFormat` now write that provider's digits for a locale whose default is not Latin, the way `Intl.NumberFormat` and `Intl.DateTimeFormat` already did. To keep Latin digits for such a locale, ask for them: `{ numberingSystem: 'latn' }`, or a `-u-nu-latn` subtag. +### 4.45 `GetOwnProperties` reports what the key enumerations report ([#3461](https://github.com/sebastienros/jint/pull/3461)) + +Deriving `GetOwnProperties` from `GetOwnPropertyKeys` ([2.6](#27-getownproperties-is-derived-not-overridden)) +makes it agree with every other enumeration, and for three object shapes the two used to differ. Nothing in +script changes; what changes is what a host reading `GetOwnProperties`, converting an object with +`ToObject()`, or inspecting a scope in the debugger sees. + +| Object | `GetOwnProperties()` in 4.16.x | in 5.x | +| --- | --- | --- | +| a function | `prototype`, `length`, `name`, [`arguments`, `caller`], own keys | `length`, `name`, `prototype`, [`arguments`, `caller`], own keys — the order `Object.getOwnPropertyNames` always reported, and the order the specification creates them in | +| a `String` object | own keys, symbols, `length` | `"0"`…`"n-1"`, `length`, own keys, symbols — the character indices are own properties of a `String` object, and every script-visible enumeration already listed them | +| a host object overriding only `GetOwnPropertyKeys` | the engine's own (usually empty) property tables | the keys the host declares | + +A plain object with integer-like keys is also reported in `[[OwnPropertyKeys]]` order now — integer indices +ascending, then strings in insertion order, then symbols — rather than in storage order. Again, that is the +order `Object.keys` already used. + +One in-box object gained a key rather than reordering its own: a lazily created `f.prototype` keeps +`constructor` in a field and used to declare it to `GetOwnProperties` alone, so +`Object.getOwnPropertyNames(f.prototype)` and `Reflect.ownKeys(f.prototype)` answered `[]` for a property +`hasOwnProperty` and `Object.getOwnPropertyDescriptor` both reported. Both now answer `["constructor"]`. It +stays non-enumerable, so `Object.keys` and `for..in` are unchanged. + +Two smaller consequences of the same derivation, both debugger-only: + +- `DebugScope.BindingNames` lists string keys only. A symbol-keyed own property of the binding object used to + appear in that list as its `Symbol(...)` description; a symbol is not a binding name. +- A `with` scope over a host object whose properties live outside the engine's tables is no longer reported as + an empty scope and dropped from `DebugInformation.CurrentScopeChain`. ### 4.44 Calendar arithmetic is answered by whoever answers for the calendar ([#3403](https://github.com/sebastienros/jint/issues/3403)) From 905efe7ba51dec73d5a706887fc1e362e3a6c04a Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 27 Aug 2026 06:02:53 +0300 Subject: [PATCH 2/4] Docs: point the migration and instruction rows at the pull request Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S --- AGENTS.md | 2 +- Jint/Native/Object/AGENTS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8ca9d93883..9869de5b33 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,7 +93,7 @@ Each of these cost a real integrator or a real bug. - **Constraints bound one entry into the engine, never a host-driven sequence of them.** Every public entry that runs script — `Execute`, `Evaluate`, `Invoke`, `Engine.Call`, the `JsValue.Call` extension helpers — funnels through `Engine.ExecuteWithConstraints` (`Jint/Engine.cs`), which calls `ResetConstraints()` before the callback and again in its `finally` for any entry that is not nested (nesting is `_hostEntryDepth > 0 || _executionContexts.Count > 1`). So `foreach (var row in rows) predicate.Call(row);` — the single most common embedding shape — hands every element a fresh statement budget, a fresh allocation budget and, worst, a **freshly armed timeout deadline**. Measured: `LimitStatements(100)` does not stop 1000 host `Call`s, `LimitExecutionTime(200ms)` does not fire across 3 s of continuous host-driven execution, and `LimitMemory` never sees more than one call's allocations — while the identical work inside one `Execute` throws in every case. The reset itself is not the mistake: per-run reset is exactly what `Constraint.Reset`'s doc promises and what makes a reused engine usable, and the nested case is handled deliberately (a host callback re-entering the engine from inside a running script does *not* re-arm, or `while (true) hostCallback()` would run forever). What no embedder expects is that a single function call is a **run**. `Engine.Constraints.Check()` from the host loop does not close the gap — `TimeConstraint` re-arms its deadline on the way *out* of every run, so a host-side check measures the time since the last call returned. All of it is pinned from the embedder's side in `Jint.Tests.PublicInterface/HostCallLoopConstraintTests.cs` and `HostMemoryLimitTests.cs`; those tests assert the behaviour as it is, so changing it is a deliberate act that updates them. **What an embedder must do instead — the host-side bound, the in-script loop, and the two in-box constraints that survive the per-entry reset — is [`Jint/Constraints/AGENTS.md`](Jint/Constraints/AGENTS.md#bounding-a-host-driven-sequence), which is also the file to read before changing any of it.** - **`DefineOwnPropertyUnchecked` / `DefineOwnDataPropertyUnchecked` always create an *own* property.** They shadow anything of that name on the prototype chain, invoke no inherited setter, and run no `[[DefineOwnProperty]]` validation (so they can never raise `TypeError`). Storing a raw descriptor under a string key is a dictionary-mode operation, so a shape-mode receiver is permanently deoptimized and forfeits the shape inline cache. Two refinements: symbol keys do not deopt, and a `BuiltinShapeMode` receiver can survive an in-place slot replacement. Use them for setup-time writes only — `Set` for steady-state mutation, `JsObject.Create` to build objects. -- **The enumeration hook is `GetOwnPropertyKeys`, and it is the only one.** `Object.keys`/`values`/`entries`, `for..in`, object spread and rest, `Object.assign`, `JSON.stringify` and `JsonSerializer` all list keys through it and filter them with `ProbeOwnProperty`, and so — since [#3455](https://github.com/sebastienros/jint/pull/3455) — do `GetOwnProperties`' own consumers, the CLR conversion path (`ToObject` under `Options.Interop.CreateClrObject`), the debugger's `GetAllBindingNames` and the debug view. `GetOwnProperties` was a second `virtual` whose name read like the hook, and a real integrator overrode only that and shipped an object whose keys were invisible to every script-visible enumeration; it is now derived from `GetOwnPropertyKeys` + `GetOwnProperty` and non-virtual, so a host declares its keys once. Override `GetOwnPropertyKeys` (plus `ProbeOwnProperty`, so existence and enumerability are answered without materializing a descriptor), or derive from `NamedPropertyObject` / `ArrayLikeObject` and write neither. +- **The enumeration hook is `GetOwnPropertyKeys`, and it is the only one.** `Object.keys`/`values`/`entries`, `for..in`, object spread and rest, `Object.assign`, `JSON.stringify` and `JsonSerializer` all list keys through it and filter them with `ProbeOwnProperty`, and so — since [#3461](https://github.com/sebastienros/jint/pull/3461) — do `GetOwnProperties`' own consumers, the CLR conversion path (`ToObject` under `Options.Interop.CreateClrObject`), the debugger's `GetAllBindingNames` and the debug view. `GetOwnProperties` was a second `virtual` whose name read like the hook, and a real integrator overrode only that and shipped an object whose keys were invisible to every script-visible enumeration; it is now derived from `GetOwnPropertyKeys` + `GetOwnProperty` and non-virtual, so a host declares its keys once. Override `GetOwnPropertyKeys` (plus `ProbeOwnProperty`, so existence and enumerability are answered without materializing a descriptor), or derive from `NamedPropertyObject` / `ArrayLikeObject` and write neither. - **Sharing a `JsValue` across engines is unsupported**, and nothing validates or guards it. An `ObjectInstance` holds a hard reference to its creating engine and realm. README.md's "Embedding performance" section states this for embedders; there is still no XML doc on `JsValue` / `Engine.SetValue` saying it, and no test pins it. The other seventeen are in the files indexed above. **Do not add a new gotcha here.** Add it to the file for the area it governs; if none fits, say so in the pull request rather than growing this one. diff --git a/Jint/Native/Object/AGENTS.md b/Jint/Native/Object/AGENTS.md index e4fc324670..b3e195e205 100644 --- a/Jint/Native/Object/AGENTS.md +++ b/Jint/Native/Object/AGENTS.md @@ -104,6 +104,6 @@ The three lazy mechanisms each own one storage world and are not interchangeable Each of these cost a real integrator or a real bug. These are the ones that bite in this area; the rest of the list is split across the files indexed from the repository-root [`AGENTS.md`](../../../AGENTS.md). -- **An own property kept in a field has to be declared to the key side too.** `GetOwnProperty` answering a name is half a property: the key enumerations read `GetOwnPropertyKeys`, which for an in-box type means overriding it (`JsError`, `IteratorResult`, `JsRegExp`) or the internal `GetInitialOwnStringPropertyKeys` (`Function`'s `length`/`name`/`prototype`, `StringInstance`'s `length`, `JsEvent`'s `isTrusted`). Forgetting the key side ships a property `hasOwnProperty` and `Object.getOwnPropertyDescriptor` both report while `Object.getOwnPropertyNames` and `Reflect.ownKeys` answer an empty list — which is what `f.prototype`'s `constructor` did until [#3455](https://github.com/sebastienros/jint/pull/3455), because it declared itself to `GetOwnProperties` alone. Now that `GetOwnProperties` is derived from the key list, the same omission also hides the property from the CLR conversion path and the debugger, so there is one place to get right instead of two to keep in step. +- **An own property kept in a field has to be declared to the key side too.** `GetOwnProperty` answering a name is half a property: the key enumerations read `GetOwnPropertyKeys`, which for an in-box type means overriding it (`JsError`, `IteratorResult`, `JsRegExp`) or the internal `GetInitialOwnStringPropertyKeys` (`Function`'s `length`/`name`/`prototype`, `StringInstance`'s `length`, `JsEvent`'s `isTrusted`). Forgetting the key side ships a property `hasOwnProperty` and `Object.getOwnPropertyDescriptor` both report while `Object.getOwnPropertyNames` and `Reflect.ownKeys` answer an empty list — which is what `f.prototype`'s `constructor` did until [#3461](https://github.com/sebastienros/jint/pull/3461), because it declared itself to `GetOwnProperties` alone. Now that `GetOwnProperties` is derived from the key list, the same omission also hides the property from the CLR conversion path and the debugger, so there is one place to get right instead of two to keep in step. - **Extensibility is said through `PreventExtensions`, never through the `Extensible` getter.** `Extensible` was `public virtual` over an `internal` setter, which is a shape that cannot be got right: a subclass overriding the getter to a constant keeps the setter, and the setter simply stops feeding the getter. `Object.preventExtensions` then returned the object, `Object.isExtensible` went on answering `true`, strict mode threw nothing and `Object.seal`/`Object.freeze` became no-ops — a `[[PreventExtensions]]` invariant violation no script can detect. Squidex shipped exactly that, twice (`ContentDataObject`, `ContentFieldObject`: `public override bool Extensible => true;`). The getter is non-virtual now, so the shape does not compile; the intent behind it — an object nothing may make non-extensible — is `public override bool PreventExtensions() => false;`, which is an answer `[[PreventExtensions]]` is allowed to give and which makes `Object.preventExtensions` raise a `TypeError` instead of lying. `JsStorage` and a length-tracking `JsTypedArray` are the in-box worked examples. The exotic `[[IsExtensible]]` hook survives as the **internal** `ObjectInstance.IsExtensible()`, for the only two objects that legitimately deviate: a Proxy, which must run the `isExtensible` trap on every read, and a module namespace. The one lie left reachable — `PreventExtensions() => true` without reaching `base` — has a verifier, gated on the override being declared outside the Jint assembly precisely so that reading a Proxy's extensibility (a user trap, with side effects) is never something a verifier does. - **A global installed *after* construction must invalidate; one installed during it need not.** `Engine.AddLazyGlobal` is the options-time registration's body invoked on a live engine, and the difference is that the live engine's handler trees may already hold a resolved binding for that name — a warmed identifier site keeps the previous descriptor by reference and revalidates it against `GlobalObject._propertiesVersion` alone. `SetProperty` bumps that counter on every path it can take (shared-layout slot replacement, hybrid side-dictionary add, plain dictionary store, deopt fallback), which is exactly why the install goes through it and not through a direct `_properties` write. It correctly leaves `_lexicalMutations` and `_envBindingInjectionEpoch` alone: a global *property* is not a lexical binding and injects nothing into an existing environment. Any future API that installs or replaces a global after construction inherits this; `Jint.Tests/Runtime/LazyGlobalInstallationTests.cs` pins both halves. From 9994629d7a48342e825f8bbe741207958dfc60a8 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 27 Aug 2026 06:03:25 +0300 Subject: [PATCH 3/4] Docs: six others, not five Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S --- docs/v5-migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v5-migration.md b/docs/v5-migration.md index c14c8cb6f9..317554a17b 100644 --- a/docs/v5-migration.md +++ b/docs/v5-migration.md @@ -299,8 +299,8 @@ Better still, do not write either: `NamedPropertyObject` (a named record) and `A indexed collection) derive the whole coherence matrix from two or three members and seal the rest. The in-box overrides are gone with it — `ArrayInstance`, `Function`, `JsRegExp`, `StringInstance`, -`ObjectWrapper`, `ArrayLikeObject`, `NamedPropertyObject` and five others each declared their keys twice, and -now declare them once. Two of those pairs did not agree; see [4.44](#445-getownproperties-reports-what-the-key-enumerations-report). +`ObjectWrapper`, `ArrayLikeObject`, `NamedPropertyObject` and six others each declared their keys twice, and +now declare them once. Two of those pairs did not agree; see [4.45](#445-getownproperties-reports-what-the-key-enumerations-report). ## 3. Renamed and reshaped API From f061f4f57dd475e66a487f01eb6cc485b72822cc Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 27 Aug 2026 07:34:59 +0300 Subject: [PATCH 4/4] Docs: the new section appends after the one that merged before it #3429 merged with 4.44 while this branch carried 4.45, and the rebase kept this section where it had been -- so the file read 4.43, 4.45, 4.44 and the ordering check added by #3454 failed: line 2217: 4.44 follows 4.45 at line 2187 The numbers are right; the position was not. Moved this section to the end of part 4 rather than renumbering, so the section that merged first keeps the number it merged with. That is the rule the guide states, applied the way round it is meant: whoever lands second moves. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S --- docs/v5-migration.md | 61 ++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/v5-migration.md b/docs/v5-migration.md index 317554a17b..e5dfa19015 100644 --- a/docs/v5-migration.md +++ b/docs/v5-migration.md @@ -2286,36 +2286,6 @@ exactly what it wrote before. It moves for an embedder who has installed a CLDR- `ICldrProvider` — `Intl.RelativeTimeFormat` and `Intl.DurationFormat` now write that provider's digits for a locale whose default is not Latin, the way `Intl.NumberFormat` and `Intl.DateTimeFormat` already did. To keep Latin digits for such a locale, ask for them: `{ numberingSystem: 'latn' }`, or a `-u-nu-latn` subtag. -### 4.45 `GetOwnProperties` reports what the key enumerations report ([#3461](https://github.com/sebastienros/jint/pull/3461)) - -Deriving `GetOwnProperties` from `GetOwnPropertyKeys` ([2.6](#27-getownproperties-is-derived-not-overridden)) -makes it agree with every other enumeration, and for three object shapes the two used to differ. Nothing in -script changes; what changes is what a host reading `GetOwnProperties`, converting an object with -`ToObject()`, or inspecting a scope in the debugger sees. - -| Object | `GetOwnProperties()` in 4.16.x | in 5.x | -| --- | --- | --- | -| a function | `prototype`, `length`, `name`, [`arguments`, `caller`], own keys | `length`, `name`, `prototype`, [`arguments`, `caller`], own keys — the order `Object.getOwnPropertyNames` always reported, and the order the specification creates them in | -| a `String` object | own keys, symbols, `length` | `"0"`…`"n-1"`, `length`, own keys, symbols — the character indices are own properties of a `String` object, and every script-visible enumeration already listed them | -| a host object overriding only `GetOwnPropertyKeys` | the engine's own (usually empty) property tables | the keys the host declares | - -A plain object with integer-like keys is also reported in `[[OwnPropertyKeys]]` order now — integer indices -ascending, then strings in insertion order, then symbols — rather than in storage order. Again, that is the -order `Object.keys` already used. - -One in-box object gained a key rather than reordering its own: a lazily created `f.prototype` keeps -`constructor` in a field and used to declare it to `GetOwnProperties` alone, so -`Object.getOwnPropertyNames(f.prototype)` and `Reflect.ownKeys(f.prototype)` answered `[]` for a property -`hasOwnProperty` and `Object.getOwnPropertyDescriptor` both reported. Both now answer `["constructor"]`. It -stays non-enumerable, so `Object.keys` and `for..in` are unchanged. - -Two smaller consequences of the same derivation, both debugger-only: - -- `DebugScope.BindingNames` lists string keys only. A symbol-keyed own property of the binding object used to - appear in that list as its `Symbol(...)` description; a symbol is not a binding name. -- A `with` scope over a host object whose properties live outside the engine's tables is no longer reported as - an empty scope and dropped from `DebugInformation.CurrentScopeChain`. - ### 4.44 Calendar arithmetic is answered by whoever answers for the calendar ([#3403](https://github.com/sebastienros/jint/issues/3403)) `ICalendarProvider` supplies two conversions, ISO ↔ calendar fields, and the engine consulted them for the @@ -2367,6 +2337,37 @@ Two refusals that used to escape as CLR exceptions are now `RangeError`s a scrip `DifferenceISODateTime` reached `CalendarDateUntil` with no realm to report against, and an out-of-range difference threw out of `Engine.Evaluate`. + +### 4.45 `GetOwnProperties` reports what the key enumerations report ([#3461](https://github.com/sebastienros/jint/pull/3461)) + +Deriving `GetOwnProperties` from `GetOwnPropertyKeys` ([2.6](#27-getownproperties-is-derived-not-overridden)) +makes it agree with every other enumeration, and for three object shapes the two used to differ. Nothing in +script changes; what changes is what a host reading `GetOwnProperties`, converting an object with +`ToObject()`, or inspecting a scope in the debugger sees. + +| Object | `GetOwnProperties()` in 4.16.x | in 5.x | +| --- | --- | --- | +| a function | `prototype`, `length`, `name`, [`arguments`, `caller`], own keys | `length`, `name`, `prototype`, [`arguments`, `caller`], own keys — the order `Object.getOwnPropertyNames` always reported, and the order the specification creates them in | +| a `String` object | own keys, symbols, `length` | `"0"`…`"n-1"`, `length`, own keys, symbols — the character indices are own properties of a `String` object, and every script-visible enumeration already listed them | +| a host object overriding only `GetOwnPropertyKeys` | the engine's own (usually empty) property tables | the keys the host declares | + +A plain object with integer-like keys is also reported in `[[OwnPropertyKeys]]` order now — integer indices +ascending, then strings in insertion order, then symbols — rather than in storage order. Again, that is the +order `Object.keys` already used. + +One in-box object gained a key rather than reordering its own: a lazily created `f.prototype` keeps +`constructor` in a field and used to declare it to `GetOwnProperties` alone, so +`Object.getOwnPropertyNames(f.prototype)` and `Reflect.ownKeys(f.prototype)` answered `[]` for a property +`hasOwnProperty` and `Object.getOwnPropertyDescriptor` both reported. Both now answer `["constructor"]`. It +stays non-enumerable, so `Object.keys` and `for..in` are unchanged. + +Two smaller consequences of the same derivation, both debugger-only: + +- `DebugScope.BindingNames` lists string keys only. A symbol-keyed own property of the binding object used to + appear in that list as its `Symbol(...)` description; a symbol is not a binding name. +- A `with` scope over a host object whose properties live outside the engine's tables is no longer reported as + an empty scope and dropped from `DebugInformation.CurrentScopeChain`. + ## 5. New in v5 Everything in the table below is opt-in: nothing in it is installed unless the host asks for it, so