diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs index 7c2c7ac23..a953138c5 100644 --- a/Jint.Tests/Runtime/PropertyDescriptorTests.cs +++ b/Jint.Tests/Runtime/PropertyDescriptorTests.cs @@ -98,6 +98,21 @@ public void AllForbiddenDescriptor() Assert.Equal(true, pd.IsDataDescriptor()); } + [Fact] + public void FastSetPropertyIsVisibleOnBuiltinShapedHost() + { + // Math uses builtin-shape storage; a raw property store must deopt it to dictionary + // mode — without that, the write lands in a side dictionary the shape-mode read + // paths never consult and the property silently doesn't exist. + Assert.Equal(4, _engine.Evaluate("Math.floor(4.7)").AsNumber()); // initialize the shaped host + var math = _engine.Evaluate("Math").AsObject(); + math.FastSetProperty("custom", new PropertyDescriptor(42, PropertyFlag.ConfigurableEnumerableWritable)); + + Assert.Equal(42, _engine.Evaluate("Math.custom").AsNumber()); + Assert.True(_engine.Evaluate("Object.getOwnPropertyNames(Math).includes('custom')").AsBoolean()); + Assert.Equal(4, _engine.Evaluate("Math.floor(4.7)").AsNumber()); + } + [Fact] public void LazyPropertyDescriptor() { diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 68c886dc2..53940a743 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -245,10 +245,16 @@ internal void SetProperty(string property, PropertyDescriptor value) internal void SetProperty(Key property, PropertyDescriptor value) { // Storing a raw descriptor is a dictionary-mode operation; deopt first if needed. + // Without the builtin-shape deopt, the write would land in a side dictionary that the + // shape-mode read paths never consult — the property would silently not exist. if ((_type & InternalTypes.ShapeMode) != InternalTypes.Empty) { ConvertToDictionaryMode(); } + else if ((_type & InternalTypes.BuiltinShapeMode) != InternalTypes.Empty) + { + DeoptBuiltinShape(); + } _properties ??= new PropertyDictionary(); _properties[property] = value; unchecked { _propertiesVersion++; }