Browse Source

Merge pull request #415 from sebastienros/dev

Merging dev
Sébastien Ros 8 years ago
parent
commit
fe76ca7620

+ 1 - 7
Jint.Tests/Jint.Tests.csproj

@@ -1,5 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
   <PropertyGroup>
     <TargetFrameworks>netcoreapp1.0;net451</TargetFrameworks>
     <AssemblyName>Jint.Tests</AssemblyName>
@@ -15,25 +14,20 @@
     <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
   </PropertyGroup>
-
   <ItemGroup>
     <EmbeddedResource Include="Runtime\Scripts\*.*;Parser\Scripts\*.*" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
   </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
     <PackageReference Include="xunit" Version="2.2.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
     <PackageReference Include="xunit.analyzers" Version="0.3.0" />
   </ItemGroup>
-
   <ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
     <Reference Include="System" />
     <Reference Include="Microsoft.CSharp" />
   </ItemGroup>
-
-</Project>
+</Project>

+ 68 - 0
Jint.Tests/Runtime/ErrorTests.cs

@@ -0,0 +1,68 @@
+using System;
+using Jint.Parser;
+using Jint.Runtime;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class ErrorTests
+    {
+        [Fact]
+        public void CanReturnCorrectErrorMessageAndLocation1()
+        {
+            var script = @"
+var a = {};
+
+var b = a.user.name;
+";
+
+            var engine = new Engine();
+            var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
+            Assert.Equal("user is undefined", e.Message);
+            Assert.Equal(4, e.Location.Start.Line);
+            Assert.Equal(8, e.Location.Start.Column);
+        }
+
+        [Fact]
+        public void CanReturnCorrectErrorMessageAndLocation2()
+        {
+            var script = @"
+ test();
+";
+
+            var engine = new Engine();
+            var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
+            Assert.Equal("test is not defined", e.Message);
+            Assert.Equal(2, e.Location.Start.Line);
+            Assert.Equal(1, e.Location.Start.Column);
+        }
+
+        [Fact]
+        public void CanProduceCorrectStackTrace()
+        {
+            var engine = new Engine(options => options.LimitRecursion(1000));
+
+            engine.Execute(@"var a = function(v) {
+	return v.xxx.yyy;
+}
+
+var b = function(v) {
+	return a(v);
+}", new ParserOptions
+            {
+                Source = "custom.js"
+            });
+
+            var e = Assert.Throws<JavaScriptException>(() => engine.Execute("var x = b(7);", new ParserOptions { Source = "main.js"}));
+            Assert.Equal("xxx is undefined", e.Message);
+            Assert.Equal(2, e.Location.Start.Line);
+            Assert.Equal(8, e.Location.Start.Column);
+            Assert.Equal("custom.js", e.Location.Source);
+
+            var stack = e.CallStack;
+            Assert.Equal(@" at a(v) @ custom.js 8:6
+ at b(7) @ main.js 8:1
+".Replace("\r\n", "\n"), stack.Replace("\r\n", "\n"));
+        }
+    }
+}

+ 128 - 0
Jint.Tests/Runtime/NullPropogation.cs

@@ -0,0 +1,128 @@
+using Jint.Native;
+using Jint.Native.Object;
+using Jint.Parser;
+using Jint.Runtime;
+using Jint.Runtime.Interop;
+using Jint.Runtime.References;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class NullPropogation
+    {
+        public class NullPropgationReferenceResolver : IReferenceResolver
+        {
+            public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
+            {
+                value = reference.GetBase();
+                return true;
+            }
+
+            public bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value)
+            {
+                return value.IsNull() || value.IsUndefined();
+            }
+
+            public bool TryGetCallable(Engine engine, object reference, out JsValue value)
+            {
+                value = new JsValue(
+                    new ClrFunctionInstance(engine, (thisObj, values) => thisObj)
+                );
+                return true;
+            }
+            
+            public bool CheckCoercible(JsValue value)
+            {
+                return true;
+            }
+        }
+
+        [Fact]
+        public void NullPropagation()
+        {
+            var engine = new Engine(cfg => cfg.SetReferencesResolver(new NullPropgationReferenceResolver()));
+
+            const string Script = @"
+var input = { 
+	Address : null 
+};
+
+var address = input.Address;
+var city = input.Address.City;
+var length = input.Address.City.length;
+
+var output = {
+	Count1 : input.Address.City.length,
+	Count2 : this.XYZ.length
+};
+";
+
+            engine.Execute(Script);
+
+            var address = engine.GetValue("address");
+            var city = engine.GetValue("city");
+            var length = engine.GetValue("length");
+            var output = engine.GetValue("output").AsObject();
+
+            Assert.Equal(Null.Instance, address);
+            Assert.Equal(Null.Instance, city);
+            Assert.Equal(Null.Instance, length);
+
+            Assert.Equal(Null.Instance, output.Get("Count1"));
+            Assert.Equal(Undefined.Instance, output.Get("Count2"));
+        }
+
+        [Fact]
+        public void NullPropagationFromArg()
+        {
+            var engine = new Engine(cfg => cfg.SetReferencesResolver(new NullPropgationReferenceResolver()));
+
+
+            const string Script = @"
+function test(arg) {
+    return arg.Name;
+}
+
+function test2(arg) {
+    return arg.Name.toUpperCase();
+}
+";
+            engine.Execute(Script);
+            var result = engine.Invoke("test", Null.Instance);
+
+            Assert.Equal(Null.Instance, result);
+
+            result = engine.Invoke("test2", Null.Instance);
+
+            Assert.Equal(Null.Instance, result);
+        }
+
+        [Fact]
+        public void NullPropagationShouldNotAffectOperators()
+        {
+            var engine = new Engine(cfg => cfg.SetReferencesResolver(new NullPropgationReferenceResolver()));
+
+            var jsObject = engine.Object.Construct(Arguments.Empty);
+            jsObject.Put("NullField", JsValue.Null, true);
+
+            var script = @"
+this.is_nullfield_not_null = this.NullField !== null;
+this.is_notnullfield_not_null = this.NotNullField !== null;
+this.has_emptyfield_not_null = this.EmptyField !== null;
+";
+
+            var wrapperScript = string.Format(@"function ExecutePatchScript(docInner){{ (function(doc){{ {0} }}).apply(docInner); }};", script);
+
+            engine.Execute(wrapperScript, new ParserOptions
+            {
+                Source = "main.js"
+            });
+
+            engine.Invoke("ExecutePatchScript", jsObject);
+
+            Assert.False(jsObject.Get("is_nullfield_not_null").AsBoolean());
+            Assert.True(jsObject.Get("is_notnullfield_not_null").AsBoolean());
+            Assert.True(jsObject.Get("has_emptyfield_not_null").AsBoolean());
+        }
+    }
+}

+ 12 - 3
Jint/Engine.cs

@@ -317,9 +317,7 @@ namespace Jint
                 if (result.Type == Completion.Throw)
                 {
                     throw new JavaScriptException(result.GetValueOrDefault())
-                    {
-                        Location = result.Location
-                    };
+                        .SetCallstack(this, result.Location);
                 }
 
                 _completionValue = result.GetValueOrDefault();
@@ -513,6 +511,11 @@ namespace Jint
 
             if (reference.IsUnresolvableReference())
             {
+                if (Options._ReferenceResolver != null &&
+                    Options._ReferenceResolver.TryUnresolvableReference(this, reference, out JsValue val))
+                {
+                    return val;
+                }
                 throw new JavaScriptException(ReferenceError, reference.GetReferencedName() + " is not defined");
             }
 
@@ -520,6 +523,12 @@ namespace Jint
 
             if (reference.IsPropertyReference())
             {
+                if (Options._ReferenceResolver != null &&
+                    Options._ReferenceResolver.TryPropertyReference(this, reference, ref baseValue))
+                {
+                    return baseValue;
+                }
+                
                 if (reference.HasPrimitiveBase() == false)
                 {
                     var o = TypeConverter.ToObject(this, baseValue);

+ 2 - 1
Jint/Native/Function/EvalFunctionInstance.cs

@@ -58,7 +58,8 @@ namespace Jint.Native.Function
 
                             if (result.Type == Completion.Throw)
                             {
-                                throw new JavaScriptException(result.GetValueOrDefault());
+                                throw new JavaScriptException(result.GetValueOrDefault())
+                                    .SetCallstack(_engine, result.Location);
                             }
                             else
                             {

+ 2 - 2
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -94,8 +94,8 @@ namespace Jint.Native.Function
 
                     if (result.Type == Completion.Throw)
                     {
-                        JavaScriptException ex = new JavaScriptException(result.GetValueOrDefault());
-                        ex.Location = result.Location;
+                        JavaScriptException ex = new JavaScriptException(result.GetValueOrDefault())
+                            .SetCallstack(Engine, result.Location);
                         throw ex;
                     }
 

+ 10 - 0
Jint/Options.cs

@@ -22,6 +22,7 @@ namespace Jint
         private TimeZoneInfo _localTimeZone = TimeZoneInfo.Local;
         private List<Assembly> _lookupAssemblies = new List<Assembly>();
         private Predicate<Exception> _clrExceptionsHandler;
+        private IReferenceResolver _referenceResolver;
 
         /// <summary>
         /// When called, doesn't initialize the global scope.
@@ -145,6 +146,12 @@ namespace Jint
             return this;
         }
 
+        public Options SetReferencesResolver(IReferenceResolver resolver)
+        {
+            _referenceResolver = resolver;
+            return this;
+        }
+
         internal bool _IsGlobalDiscarded => _discardGlobal;
 
         internal bool _IsStrict => _strict;
@@ -170,5 +177,8 @@ namespace Jint
         internal CultureInfo _Culture => _culture;
 
         internal TimeZoneInfo _LocalTimeZone => _localTimeZone;
+
+        internal IReferenceResolver  _ReferenceResolver => _referenceResolver;
+
     }
 }

+ 14 - 2
Jint/Runtime/CallStack/JintCallStack.cs

@@ -1,9 +1,11 @@
-namespace Jint.Runtime.CallStack
+using System.Collections;
+
+namespace Jint.Runtime.CallStack
 {
     using System.Collections.Generic;
     using System.Linq;
 
-    public class JintCallStack
+    public class JintCallStack : IEnumerable<CallStackElement>
     {
         private Stack<CallStackElement> _stack = new Stack<CallStackElement>();
 
@@ -45,9 +47,19 @@
             _statistics.Clear();
         }
 
+        public IEnumerator<CallStackElement> GetEnumerator()
+        {
+            return _stack.GetEnumerator();
+        }
+
         public override string ToString()
         {
             return string.Join("->", _stack.Select(cse => cse.ToString()).Reverse());
         }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
     }
 }

+ 10 - 3
Jint/Runtime/ExpressionIntepreter.cs

@@ -3,6 +3,7 @@ using System.Linq;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Number;
+using Jint.Native.Object;
 using Jint.Parser.Ast;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
@@ -757,7 +758,7 @@ namespace Jint.Runtime
 
             var propertyNameReference = EvaluateExpression(expression);
             var propertyNameValue = _engine.GetValue(propertyNameReference);
-            TypeConverter.CheckObjectCoercible(_engine, baseValue);
+            TypeConverter.CheckObjectCoercible(_engine, baseValue, memberExpression, baseReference);
             var propertyNameString = TypeConverter.ToString(propertyNameValue);
 
             return new Reference(baseValue, propertyNameString, StrictModeScope.IsStrictModeCode);
@@ -846,12 +847,18 @@ namespace Jint.Runtime
 
             if (func == Undefined.Instance)
             {
-                throw new JavaScriptException(_engine.TypeError, r == null ? "" : string.Format("Object has no method '{0}'", (callee as Reference).GetReferencedName()));
+                throw new JavaScriptException(_engine.TypeError, r == null ? "" : string.Format("Object has no method '{0}'", r.GetReferencedName()));
             }
 
             if (!func.IsObject())
             {
-                throw new JavaScriptException(_engine.TypeError, r == null ? "" : string.Format("Property '{0}' of object is not a function", (callee as Reference).GetReferencedName()));
+
+                if (_engine.Options._ReferenceResolver == null ||
+                    !_engine.Options._ReferenceResolver.TryGetCallable(_engine, callee, out func))
+                {
+                    throw new JavaScriptException(_engine.TypeError,
+                        r == null ? "" : string.Format("Property '{0}' of object is not a function", r.GetReferencedName()));
+                }
             }
 
             var callable = func.TryCast<ICallable>();

+ 13 - 0
Jint/Runtime/Interop/IReferenceResolver.cs

@@ -0,0 +1,13 @@
+using Jint.Native;
+using Jint.Runtime.References;
+
+namespace Jint.Runtime.Interop
+{
+    public interface IReferenceResolver
+    {
+        bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value);
+        bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value);
+        bool TryGetCallable(Engine engine, object callee, out JsValue value);
+        bool CheckCoercible(JsValue value);
+    }
+}

+ 72 - 2
Jint/Runtime/JavaScriptException.cs

@@ -1,12 +1,20 @@
 using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
 using Jint.Native;
 using Jint.Native.Error;
+using Jint.Parser;
+using Jint.Parser.Ast;
+using Jint.Runtime.CallStack;
+using Jint.Runtime.Descriptors;
 
 namespace Jint.Runtime
 {
     public class JavaScriptException : Exception
     {
         private readonly JsValue _errorObject;
+        private string _callStack;
 
         public JavaScriptException(ErrorConstructor errorConstructor) : base("")
         {
@@ -25,6 +33,40 @@ namespace Jint.Runtime
             _errorObject = error;
         }
 
+        public JavaScriptException SetCallstack(Engine engine, Location location = null)
+        {
+            Location = location;
+            var sb = new StringBuilder();
+            foreach (var cse in engine.CallStack)
+            {
+                sb.Append(" at ")
+                    .Append(cse)
+                    .Append("(");
+
+                for (var index = 0; index < cse.CallExpression.Arguments.Count; index++)
+                {
+                    if (index != 0)
+                        sb.Append(", ");
+                    var arg = cse.CallExpression.Arguments[index];
+                    if (arg is IPropertyKeyExpression pke)
+                        sb.Append(pke.GetKey());
+                    else
+                        sb.Append(arg);
+                }
+
+
+                sb.Append(") @ ")
+                    .Append(cse.CallExpression.Location.Source)
+                    .Append(" ")
+                    .Append(cse.CallExpression.Location.Start.Column)
+                    .Append(":")
+                    .Append(cse.CallExpression.Location.Start.Line)
+                    .AppendLine();
+            }
+            CallStack = sb.ToString();
+            return this;
+        }
+
         private static string GetErrorMessage(JsValue error) 
         {
             if (error.IsObject())
@@ -33,8 +75,10 @@ namespace Jint.Runtime
                 var message = oi.Get("message").AsString();
                 return message;
             }
-            else
-                return string.Empty;            
+            if (error.IsString())
+                return error.AsString();
+            
+            return error.ToString();
         }
 
         public JsValue Error { get { return _errorObject; } }
@@ -44,6 +88,32 @@ namespace Jint.Runtime
             return _errorObject.ToString();
         }
 
+        public string CallStack
+        {
+            get
+            {
+                if (_callStack != null)
+                    return _callStack;
+                if (_errorObject == null)
+                    return null;
+                if (_errorObject.IsObject() == false)
+                    return null;
+                var callstack = _errorObject.AsObject().Get("callstack");
+                if (callstack == JsValue.Undefined)
+                    return null;
+                return callstack.AsString();
+            }
+            set
+            {
+                _callStack = value;
+                if (value != null && _errorObject.IsObject())
+                {
+                    _errorObject.AsObject()
+                        .FastAddProperty("callstack", new JsValue(value), false, false, false);
+                }
+            }
+        }
+
         public Jint.Parser.Location Location { get; set; }
 
         public int LineNumber { get { return null == Location ? 0 : Location.Start.Line; } }

+ 21 - 0
Jint/Runtime/TypeConverter.cs

@@ -7,6 +7,8 @@ using Jint.Native;
 using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.String;
+using Jint.Parser.Ast;
+using Jint.Runtime.References;
 
 namespace Jint.Runtime
 {
@@ -339,6 +341,25 @@ namespace Jint.Runtime
             return value.Type;
         }
 
+        public static void CheckObjectCoercible(Engine engine, JsValue o, MemberExpression expression,
+            object baseReference)
+        {
+            if (o != Undefined.Instance && o != Null.Instance)
+                return;
+
+            if (engine.Options._ReferenceResolver != null && 
+                engine.Options._ReferenceResolver.CheckCoercible(o))
+                return;
+
+            var message = string.Empty;
+            var reference = baseReference as Reference;
+            if (reference != null)
+                message = $"{reference.GetReferencedName()} is {o}";
+
+            throw new JavaScriptException(engine.TypeError, message)
+                .SetCallstack(engine, expression.Location);
+        }
+
         public static void CheckObjectCoercible(Engine engine, JsValue o)
         {
             if (o == Undefined.Instance || o == Null.Instance)