Quellcode durchsuchen

Fix missing NewTarget handling for basic types (#875)

Marko Lahma vor 4 Jahren
Ursprung
Commit
170c72c5f1

+ 17 - 3
Jint.Tests/Runtime/ErrorTests.cs

@@ -155,10 +155,10 @@ var x = b(7);";
    at a (v) <anonymous>:2:18
    at b (v) <anonymous>:6:12
    at <anonymous>:9:9";
-            
+
             EqualIgnoringNewLineDifferences(expected, ex.ToString());
         }
-        
+
         [Fact]
         public void StackTraceCollectedForImmediatelyInvokedFunctionExpression()
         {
@@ -188,10 +188,24 @@ var x = b(7);";
    at getItem (items, itemIndex) get-item.js:2:22
    at (anonymous) (getItem) get-item.js:9:16
    at get-item.js:13:2";
-            
+
             EqualIgnoringNewLineDifferences(expected, ex.ToString());
         }
 
+        [Theory]
+        [InlineData("Error")]
+        [InlineData("EvalError")]
+        [InlineData("RangeError")]
+        [InlineData("SyntaxError")]
+        [InlineData("TypeError")]
+        [InlineData("ReferenceError")]
+        public void ErrorsHaveCorrectConstructor(string type)
+        {
+            var engine = new Engine();
+            engine.Execute($"const o = new {type}();");
+            Assert.True(engine.Execute($"o.constructor === {type}").GetCompletionValue().AsBoolean());
+            Assert.Equal(type, engine.Execute("o.constructor.name").GetCompletionValue().AsString());
+        }
 
         private static void EqualIgnoringNewLineDifferences(string expected, string actual)
         {

+ 23 - 0
Jint.Tests/Runtime/ObjectInstanceTests.cs

@@ -18,5 +18,28 @@ namespace Jint.Tests.Runtime
             var propertyNames = instance.GetOwnProperties().Select(x => x.Key).ToList();
             Assert.Equal(new JsValue[] { "scope" }, propertyNames);
         }
+
+        [Theory]
+        [InlineData("Array")]
+        [InlineData("Boolean")]
+        [InlineData("Date")]
+        [InlineData("Error")]
+        [InlineData("Number")]
+        [InlineData("Object")]
+        [InlineData("String")]
+        public void ExtendingObjects(string baseType)
+        {
+            var code = $@"
+                class My{baseType} extends {baseType} {{
+                    constructor(...args) {{
+                        super(...args);
+                    }} 
+                }}
+                const o = new My{baseType}();
+                o.constructor === My{baseType}";
+
+            var engine = new Engine();
+            Assert.True(engine.Execute(code).GetCompletionValue().AsBoolean());
+        }
     }
 }

+ 35 - 11
Jint/Native/Array/ArrayConstructor.cs

@@ -101,9 +101,9 @@ namespace Jint.Native.Array
             }
             else
             {
-                instance = _engine.Array.ConstructFast(0);                
+                instance = _engine.Array.ConstructFast(0);
             }
-            
+
             if (objectInstance.TryGetIterator(_engine, out var iterator))
             {
                 var protocol = new ArrayProtocol(_engine, thisArg, instance, iterator, callable);
@@ -115,8 +115,8 @@ namespace Jint.Native.Array
 
         private ObjectInstance ConstructArrayFromArrayLike(
             JsValue thisObj,
-            ObjectInstance objectInstance, 
-            ICallable callable, 
+            ObjectInstance objectInstance,
+            ICallable callable,
             JsValue thisArg)
         {
             var source = ArrayOperations.For(objectInstance);
@@ -133,9 +133,9 @@ namespace Jint.Native.Array
             }
             else
             {
-                a = _engine.Array.ConstructFast(length);                
+                a = _engine.Array.ConstructFast(length);
             }
-            
+
             var args = !ReferenceEquals(callable, null)
                 ? _engine._jsValueArrayPool.RentArray(2)
                 : null;
@@ -181,7 +181,7 @@ namespace Jint.Native.Array
             private long _index = -1;
 
             public ArrayProtocol(
-                Engine engine, 
+                Engine engine,
                 JsValue thisArg,
                 ObjectInstance instance,
                 IIterator iterator,
@@ -200,7 +200,7 @@ namespace Jint.Native.Array
                 if (!ReferenceEquals(_callable, null))
                 {
                     args[0] = sourceValue;
-                    args[1] = _index; 
+                    args[1] = _index;
                     jsValue = _callable.Call(_thisArg, args);
                 }
                 else
@@ -286,6 +286,13 @@ namespace Jint.Native.Array
 
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
+            if (newTarget.IsUndefined())
+            {
+                newTarget = this;
+            }
+
+            var proto = GetPrototypeFromConstructor(newTarget, PrototypeObject);
+
             // check if we can figure out good size
             var capacity = arguments.Length > 0 ? (uint) arguments.Length : 0;
             if (arguments.Length == 1 && arguments[0].IsNumber())
@@ -294,7 +301,7 @@ namespace Jint.Native.Array
                 ValidateLength(number);
                 capacity = (uint) number;
             }
-            return Construct(arguments, capacity);
+            return Construct(arguments, capacity, proto);
         }
 
         public ArrayInstance Construct(int capacity)
@@ -309,8 +316,12 @@ namespace Jint.Native.Array
 
         public ArrayInstance Construct(JsValue[] arguments, uint capacity)
         {
-            var instance = new ArrayInstance(Engine, capacity);
-            instance._prototype = PrototypeObject;
+            return Construct(arguments, capacity, PrototypeObject);
+        }
+
+        private ArrayInstance Construct(JsValue[] arguments, uint capacity, ObjectInstance prototypeObject)
+        {
+            var instance = ArrayCreate(capacity, prototypeObject);
 
             if (arguments.Length == 1 && arguments.At(0).IsNumber())
             {
@@ -342,6 +353,19 @@ namespace Jint.Native.Array
             return instance;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-arraycreate
+        /// </summary>
+        private ArrayInstance ArrayCreate(uint capacity, ObjectInstance proto)
+        {
+            proto ??= PrototypeObject;
+            var instance = new ArrayInstance(Engine, capacity)
+            {
+                _prototype = proto
+            };
+            return instance;
+        }
+
         private ArrayInstance ConstructArrayFromIEnumerable(IEnumerable enumerable)
         {
             var jsArray = (ArrayInstance) Construct(Arguments.Empty);

+ 3 - 3
Jint/Native/Boolean/BooleanConstructor.cs

@@ -13,7 +13,7 @@ namespace Jint.Native.Boolean
             : base(engine, _functionName)
         {
         }
-        
+
         public BooleanPrototype PrototypeObject { get; private set; }
 
         public static BooleanConstructor CreateBooleanConstructor(Engine engine)
@@ -47,7 +47,7 @@ namespace Jint.Native.Boolean
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            var b = TypeConverter.ToBoolean(arguments.At(0)) 
+            var b = TypeConverter.ToBoolean(arguments.At(0))
                 ? JsBoolean.True
                 : JsBoolean.False;
 
@@ -57,7 +57,7 @@ namespace Jint.Native.Boolean
             }
 
             var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, state) => new BooleanInstance(engine, (JsBoolean) state), b);
-            return Construct(b);
+            return o;
         }
 
         public BooleanInstance Construct(JsBoolean value)

+ 14 - 15
Jint/Native/Date/DateConstructor.cs

@@ -150,9 +150,10 @@ namespace Jint.Native.Date
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            if (arguments.Length == 0)
+            double dv;
+            if (arguments.Length == 0 || newTarget.IsUndefined())
             {
-                return Construct(DateTime.UtcNow);
+                dv = FromDateTime(DateTime.UtcNow);
             }
             else if (arguments.Length == 1)
             {
@@ -160,14 +161,14 @@ namespace Jint.Native.Date
                 {
                     return Construct(date.PrimitiveValue);
                 }
-                
+
                 var v = TypeConverter.ToPrimitive(arguments[0]);
                 if (v.IsString())
                 {
                     return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
                 }
 
-                return Construct(TimeClip(TypeConverter.ToNumber(v)));
+                dv = TimeClip(TypeConverter.ToNumber(v));
             }
             else
             {
@@ -189,8 +190,12 @@ namespace Jint.Native.Date
                     DatePrototype.MakeDay(y, m, dt),
                     DatePrototype.MakeTime(h, min, s, milli));
 
-                return Construct(TimeClip(PrototypeObject.Utc(finalDate)));
+                dv = TimeClip(PrototypeObject.Utc(finalDate));
             }
+
+            var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, _) => new DateInstance(engine));
+            o.PrimitiveValue = dv;
+            return o;
         }
 
         public DatePrototype PrototypeObject { get; private set; }
@@ -202,18 +207,12 @@ namespace Jint.Native.Date
 
         public DateInstance Construct(DateTime value)
         {
-            var instance = new DateInstance(Engine)
-            {
-                _prototype = PrototypeObject,
-                PrimitiveValue = FromDateTime(value)
-            };
-
-            return instance;
+            return Construct(FromDateTime(value));
         }
 
         public DateInstance Construct(double time)
         {
-            var instance = new DateInstance(Engine)
+            var instance = new DateInstance(_engine)
             {
                 _prototype = PrototypeObject,
                 PrimitiveValue = TimeClip(time)
@@ -222,7 +221,7 @@ namespace Jint.Native.Date
             return instance;
         }
 
-        public static double TimeClip(double time)
+        private static double TimeClip(double time)
         {
             if (double.IsInfinity(time) || double.IsNaN(time))
             {
@@ -237,7 +236,7 @@ namespace Jint.Native.Date
             return TypeConverter.ToInteger(time) + 0;
         }
 
-        public double FromDateTime(DateTime dt)
+        private double FromDateTime(DateTime dt)
         {
             var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);
 

+ 21 - 24
Jint/Native/Date/DatePrototype.cs

@@ -20,7 +20,7 @@ namespace Jint.Native.Date
         private const double MaxYear = -MinYear;
         private const double MinMonth = -10000000.0;
         private const double MaxMonth = -MinMonth;
-        
+
         private DateConstructor _dateConstructor;
 
         private DatePrototype(Engine engine)
@@ -144,7 +144,7 @@ namespace Jint.Native.Date
         /// </summary>
         private DateInstance EnsureDateInstance(JsValue thisObj)
         {
-            return thisObj as DateInstance 
+            return thisObj as DateInstance
                    ?? ExceptionHelper.ThrowTypeError<DateInstance>(_engine, "this is not a Date object");
         }
 
@@ -155,18 +155,18 @@ namespace Jint.Native.Date
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            var t = ToLocalTime(dateInstance.ToDateTime());
+            var t = ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone);
             return t.ToString("ddd MMM dd yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + TimeZoneString(t);
         }
 
-        private JsValue ToDateString(JsValue thisObj, JsValue[] arguments)
+        internal JsValue ToDateString(JsValue thisObj, JsValue[] arguments)
         {
             var dateInstance = EnsureDateInstance(thisObj);
 
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            return ToLocalTime(dateInstance.ToDateTime()).ToString("ddd MMM dd yyyy", CultureInfo.InvariantCulture);
+            return ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone).ToString("ddd MMM dd yyyy", CultureInfo.InvariantCulture);
         }
 
         private JsValue ToTimeString(JsValue thisObj, JsValue[] arguments)
@@ -176,7 +176,7 @@ namespace Jint.Native.Date
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            var t = ToLocalTime(dateInstance.ToDateTime());
+            var t = ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone);
 
             var timeString = t.ToString("HH:mm:ss ", CultureInfo.InvariantCulture);
             var timeZoneString = TimeZoneString(t);
@@ -195,7 +195,7 @@ namespace Jint.Native.Date
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            return ToLocalTime(dateInstance.ToDateTime()).ToString("F", Engine.Options._Culture);
+            return ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone).ToString("F", Engine.Options._Culture);
         }
 
         private JsValue ToLocaleDateString(JsValue thisObj, JsValue[] arguments)
@@ -205,7 +205,7 @@ namespace Jint.Native.Date
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            return ToLocalTime(dateInstance.ToDateTime()).ToString("D", Engine.Options._Culture);
+            return ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone).ToString("D", Engine.Options._Culture);
         }
 
         private JsValue ToLocaleTimeString(JsValue thisObj, JsValue[] arguments)
@@ -215,7 +215,7 @@ namespace Jint.Native.Date
             if (double.IsNaN(dateInstance.PrimitiveValue))
                 return "Invalid Date";
 
-            return ToLocalTime(dateInstance.ToDateTime()).ToString("T", Engine.Options._Culture);
+            return ToLocalTime(dateInstance.ToDateTime(), Engine.Options._LocalTimeZone).ToString("T", Engine.Options._Culture);
         }
 
         private JsValue GetTime(JsValue thisObj, JsValue[] arguments)
@@ -429,7 +429,7 @@ namespace Jint.Native.Date
         private JsValue SetUTCMilliseconds(JsValue thisObj, JsValue[] arguments)
         {
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
-            
+
             if (!IsFinite(t))
             {
                 return double.NaN;
@@ -666,7 +666,7 @@ namespace Jint.Native.Date
 
             if (thisTime.DateTimeRangeValid)
             {
-                // shortcut 
+                // shortcut
                 var dt = thisTime.ToDateTime();
                 return $"{dt.Year:0000}-{dt.Month:00}-{dt.Day:00}T{dt.Hour:00}:{dt.Minute:00}:{dt.Second:00}.{dt.Millisecond:000}Z";
             }
@@ -996,17 +996,14 @@ namespace Jint.Native.Date
             return Engine.Options._LocalTimeZone.IsDaylightSavingTime(dateTime) ? MsPerHour : 0;
         }
 
-        public DateTimeOffset ToLocalTime(DateTime t)
+        private static DateTimeOffset ToLocalTime(DateTime t, TimeZoneInfo timeZone)
         {
-            switch (t.Kind)
+            return t.Kind switch
             {
-                case DateTimeKind.Local:
-                    return new DateTimeOffset(TimeZoneInfo.ConvertTime(t.ToUniversalTime(), Engine.Options._LocalTimeZone), Engine.Options._LocalTimeZone.GetUtcOffset(t));
-                case DateTimeKind.Utc:
-                    return new DateTimeOffset(TimeZoneInfo.ConvertTime(t, Engine.Options._LocalTimeZone), Engine.Options._LocalTimeZone.GetUtcOffset(t));
-                default:
-                    return t;
-            }
+                DateTimeKind.Local => new DateTimeOffset(TimeZoneInfo.ConvertTime(t.ToUniversalTime(), timeZone), timeZone.GetUtcOffset(t)),
+                DateTimeKind.Utc => new DateTimeOffset(TimeZoneInfo.ConvertTime(t, timeZone), timeZone.GetUtcOffset(t)),
+                _ => t
+            };
         }
 
         public double LocalTime(double t)
@@ -1015,7 +1012,7 @@ namespace Jint.Native.Date
             {
                 return double.NaN;
             }
-            
+
             return (long) (t + LocalTza + DaylightSavingTa((long) t));
         }
 
@@ -1103,7 +1100,7 @@ namespace Jint.Native.Date
                 m += 12;
                 y -= 1;
             }
-            
+
             // kYearDelta is an arbitrary number such that:
             // a) kYearDelta = -1 (mod 400)
             // b) year + kYearDelta > 0 for years in the range defined by
@@ -1116,7 +1113,7 @@ namespace Jint.Native.Date
             const int kBaseDay =
                 365 * (1970 + kYearDelta) + (1970 + kYearDelta) / 4 -
                 (1970 + kYearDelta) / 100 + (1970 + kYearDelta) / 400;
-            
+
             long dayFromYear = 365 * (y + kYearDelta) + (y + kYearDelta) / 4 -
                                 (y + kYearDelta) / 100 + (y + kYearDelta) / 400 -  kBaseDay;
 
@@ -1270,7 +1267,7 @@ namespace Jint.Native.Date
 
             return new Date((int) year, month, day);
         }
-        
+
         public override string ToString()
         {
             return "Date.prototype";

+ 3 - 3
Jint/Native/Error/ErrorConstructor.cs

@@ -10,13 +10,13 @@ namespace Jint.Native.Error
         private JsString _name;
         private static readonly JsString _functionName = new JsString("Error");
 
-        public ErrorConstructor(Engine engine) : base(engine, _functionName)
+        public ErrorConstructor(Engine engine, JsString functionName) : base(engine, functionName)
         {
         }
 
         public static ErrorConstructor CreateErrorConstructor(Engine engine, JsString name)
         {
-            var obj = new ErrorConstructor(engine)
+            var obj = new ErrorConstructor(engine, name)
             {
                 _name = name,
                 _prototype = engine.Function.PrototypeObject
@@ -47,7 +47,7 @@ namespace Jint.Native.Error
         {
             var o = OrdinaryCreateFromConstructor(
                 newTarget,
-                PrototypeObject, 
+                PrototypeObject,
                 static (e, state) => new ErrorInstance(e, (JsString) state),
                 _name);
 

+ 5 - 5
Jint/Native/Function/FunctionInstance.cs

@@ -54,7 +54,7 @@ namespace Jint.Native.Function
             : this(engine, name, FunctionThisMode.Global, ObjectClass.Function)
         {
         }
-        
+
         // for example RavenDB wants to inspect this
         public IFunction FunctionDeclaration => _functionDefinition.Function;
 
@@ -257,7 +257,7 @@ namespace Jint.Native.Function
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static ObjectInstance GetPrototypeFromConstructor(JsValue constructor, ObjectInstance intrinsicDefaultProto)
+        internal static ObjectInstance GetPrototypeFromConstructor(JsValue constructor, ObjectInstance intrinsicDefaultProto)
         {
             var proto = constructor.Get(CommonProperties.Prototype, constructor) as ObjectInstance;
             // If Type(proto) is not Object, then
@@ -265,12 +265,12 @@ namespace Jint.Native.Function
             //    Set proto to realm's intrinsic object named intrinsicDefaultProto.
             return proto ?? intrinsicDefaultProto;
         }
-        
+
         internal void MakeMethod(ObjectInstance homeObject)
         {
             _homeObject = homeObject;
         }
-        
+
         /// <summary>
         /// https://tc39.es/ecma262/#sec-ordinarycallbindthis
         /// </summary>
@@ -285,7 +285,7 @@ namespace Jint.Native.Function
             // Let calleeRealm be F.[[Realm]].
 
             var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record;
-            
+
             JsValue thisValue;
             if (_thisMode == FunctionThisMode.Strict)
             {

+ 42 - 20
Jint/Native/String/StringConstructor.cs

@@ -3,6 +3,7 @@ using Jint.Collections;
 using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
@@ -20,6 +21,8 @@ namespace Jint.Native.String
         {
         }
 
+        public StringPrototype PrototypeObject { get; private set; }
+
         public static StringConstructor CreateStringConstructor(Engine engine)
         {
             var obj = new StringConstructor(engine)
@@ -64,9 +67,9 @@ namespace Jint.Native.String
         {
             var codeUnits = new List<JsValue>();
             string result = "";
-            for (var i = 0; i < arguments.Length; i++ )
+            foreach (var a in arguments)
             {
-                var codePoint = TypeConverter.ToNumber(arguments[i]);
+                var codePoint = TypeConverter.ToNumber(a);
                 if (codePoint < 0
                     || codePoint > 0x10FFFF
                     || double.IsInfinity(codePoint)
@@ -116,23 +119,21 @@ namespace Jint.Native.String
                 return JsString.Empty;
             }
 
-            using (var result = StringBuilderPool.Rent())
+            using var result = StringBuilderPool.Rent();
+            for (var i = 0; i < length; i++)
             {
-                for (var i = 0; i < length; i++)
+                if (i > 0)
                 {
-                    if (i > 0)
+                    if (i < arguments.Length && !arguments[i].IsUndefined())
                     {
-                        if (i < arguments.Length && !arguments[i].IsUndefined())
-                        {
-                            result.Builder.Append(TypeConverter.ToString(arguments[i]));
-                        }
+                        result.Builder.Append(TypeConverter.ToString(arguments[i]));
                     }
-
-                    result.Builder.Append(TypeConverter.ToString(operations.Get((ulong) i)));
                 }
 
-                return result.ToString();
+                result.Builder.Append(TypeConverter.ToString(operations.Get((ulong) i)));
             }
+
+            return result.ToString();
         }
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
@@ -151,19 +152,32 @@ namespace Jint.Native.String
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.2.1
+        /// https://tc39.es/ecma262/#sec-string-constructor-string-value
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            string value = "";
-            if (arguments.Length > 0)
+            JsString s;
+            if (arguments.Length == 0)
             {
-                value = TypeConverter.ToString(arguments[0]);
+                s = JsString.Empty;
+            }
+            else
+            {
+                var value = arguments.At(0);
+                if (newTarget.IsUndefined() && value.IsSymbol())
+                {
+                    return StringCreate(JsString.Create(SymbolPrototype.SymbolDescriptiveString((JsSymbol) value)), PrototypeObject);
+                }
+                s = TypeConverter.ToJsString(arguments[0]);
             }
-            return Construct(value);
-        }
 
-        public StringPrototype PrototypeObject { get; private set; }
+            if (newTarget.IsUndefined())
+            {
+                return StringCreate(s, PrototypeObject);
+            }
+
+            return StringCreate(s, GetPrototypeFromConstructor(newTarget, PrototypeObject));
+        }
 
         public StringInstance Construct(string value)
         {
@@ -171,10 +185,18 @@ namespace Jint.Native.String
         }
 
         public StringInstance Construct(JsString value)
+        {
+            return StringCreate(value, PrototypeObject);
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-stringcreate
+        /// </summary>
+        private StringInstance StringCreate(JsString value, ObjectInstance prototype)
         {
             var instance = new StringInstance(Engine)
             {
-                _prototype = PrototypeObject,
+                _prototype = prototype,
                 PrimitiveValue = value,
                 _length = PropertyDescriptor.AllForbiddenDescriptor.ForNumber(value.Length)
             };

+ 1 - 1
Jint/Native/Symbol/SymbolPrototype.cs

@@ -71,7 +71,7 @@ namespace Jint.Native.Symbol
             return ThisSymbolValue(thisObject);
         }
 
-        private static string SymbolDescriptiveString(JsSymbol symbol) => symbol.ToString();
+        internal static string SymbolDescriptiveString(JsSymbol symbol) => symbol.ToString();
 
         private JsSymbol ThisSymbolValue(JsValue thisObject)
         {