Browse Source

Logic to discard recursion if needed is introduced + simple test

auz34 11 years ago
parent
commit
959c4b4ac2
4 changed files with 70 additions and 2 deletions
  1. 17 0
      Jint.Tests/Runtime/EngineTests.cs
  2. 11 0
      Jint/Engine.cs
  3. 23 0
      Jint/Options.cs
  4. 19 2
      Jint/Runtime/ExpressionIntepreter.cs

+ 17 - 0
Jint.Tests/Runtime/EngineTests.cs

@@ -605,6 +605,23 @@ namespace Jint.Tests.Runtime
             );
             );
         }
         }
 
 
+        [Fact]
+        public void ShouldThrowRecursionDiscarded()
+        {
+            var script = @"var factorial = function(n) {
+                if (n>1) {
+                    return n * factorial(n - 1);
+                }
+            };
+
+            var result = factorial(500);
+            ";
+
+            Assert.Throws<RecursionDiscardedException>(
+                () => new Engine(cfg => cfg.DiscardRecursion()).Execute(script)
+            );
+        }
+
         [Fact]
         [Fact]
         public void ShouldConvertDoubleToStringWithoutLosingPrecision()
         public void ShouldConvertDoubleToStringWithoutLosingPrecision()
         {
         {

+ 11 - 0
Jint/Engine.cs

@@ -37,6 +37,8 @@ namespace Jint
         // cache of types used when resolving CLR type names
         // cache of types used when resolving CLR type names
         internal Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(); 
         internal Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(); 
 
 
+        internal Stack<string> CallStack = new Stack<string>(); 
+
         public Engine() : this(null)
         public Engine() : this(null)
         {
         {
         }
         }
@@ -216,6 +218,14 @@ namespace Jint
             _statementsCount = 0;
             _statementsCount = 0;
         }
         }
 
 
+        /// <summary>
+        /// Initializes list of references of called functions
+        /// </summary>
+        public void ResetCallStack()
+        {
+            CallStack.Clear();
+        }
+
         public Engine Execute(string source)
         public Engine Execute(string source)
         {
         {
             var parser = new JavaScriptParser();
             var parser = new JavaScriptParser();
@@ -232,6 +242,7 @@ namespace Jint
         {
         {
             ResetStatementsCount();
             ResetStatementsCount();
             ResetLastStatement();
             ResetLastStatement();
+            ResetCallStack();
 
 
             using (new StrictModeScope(Options.IsStrict() || program.Strict))
             using (new StrictModeScope(Options.IsStrict() || program.Strict))
             {
             {

+ 23 - 0
Jint/Options.cs

@@ -10,12 +10,14 @@ namespace Jint
     public class Options
     public class Options
     {
     {
         private bool _discardGlobal;
         private bool _discardGlobal;
+        private bool _discardRecursion;
         private bool _strict;
         private bool _strict;
         private bool _allowDebuggerStatement;
         private bool _allowDebuggerStatement;
         private bool _allowClr;
         private bool _allowClr;
         private ITypeConverter _typeConverter = new DefaultTypeConverter();
         private ITypeConverter _typeConverter = new DefaultTypeConverter();
         private readonly List<IObjectConverter> _objectConverters = new List<IObjectConverter>();
         private readonly List<IObjectConverter> _objectConverters = new List<IObjectConverter>();
         private int _maxStatements;
         private int _maxStatements;
+        private int _maxRecursionDepth;
         private CultureInfo _culture = CultureInfo.CurrentCulture;
         private CultureInfo _culture = CultureInfo.CurrentCulture;
         private List<Assembly> _lookupAssemblies = new List<Assembly>(); 
         private List<Assembly> _lookupAssemblies = new List<Assembly>(); 
 
 
@@ -29,6 +31,16 @@ namespace Jint
             return this;
             return this;
         }
         }
 
 
+        /// <summary>
+        /// When called, doesn't allow to use recursion.
+        /// Can be useful when you can not trust to author of the script and safety has higher priority. 
+        /// </summary>
+        public Options DiscardRecursion(bool discard = true)
+        {
+            _discardRecursion = discard;
+            return this;
+        }
+
         /// <summary>
         /// <summary>
         /// Run the script in strict mode.
         /// Run the script in strict mode.
         /// </summary>
         /// </summary>
@@ -86,6 +98,12 @@ namespace Jint
             return this;
             return this;
         }
         }
 
 
+        public Options MaxRecursionDepth(int maxRecursionDepth = 0)
+        {
+            _maxRecursionDepth = maxRecursionDepth;
+            return this;
+        }
+
         public Options Culture(CultureInfo cultureInfo)
         public Options Culture(CultureInfo cultureInfo)
         {
         {
             _culture = cultureInfo;
             _culture = cultureInfo;
@@ -132,6 +150,11 @@ namespace Jint
             return _maxStatements;
             return _maxStatements;
         }
         }
 
 
+        internal bool IsRecursionAllowed()
+        {
+            return !_discardRecursion;
+        }
+
         internal CultureInfo GetCulture()
         internal CultureInfo GetCulture()
         {
         {
             return _culture;
             return _culture;

+ 19 - 2
Jint/Runtime/ExpressionIntepreter.cs

@@ -795,9 +795,22 @@ namespace Jint.Runtime
             var arguments = callExpression.Arguments.Select(EvaluateExpression).Select(_engine.GetValue).ToArray();
             var arguments = callExpression.Arguments.Select(EvaluateExpression).Select(_engine.GetValue).ToArray();
 
 
             var func = _engine.GetValue(callee);
             var func = _engine.GetValue(callee);
-
+            
             var r = callee as Reference;
             var r = callee as Reference;
 
 
+            var funcName = r.GetReferencedName();
+            var recursionDepth = _engine.CallStack.Count(s => s == funcName);
+
+            if (recursionDepth > 0)
+            {
+                if (!_engine.Options.IsRecursionAllowed())
+                {
+                    throw new RecursionDiscardedException();
+                }
+            }
+            
+            _engine.CallStack.Push(funcName);
+
             if (func == Undefined.Instance)
             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}'", (callee as Reference).GetReferencedName()));
@@ -837,7 +850,11 @@ namespace Jint.Runtime
                 return ((EvalFunctionInstance) callable).Call(thisObject, arguments, true);
                 return ((EvalFunctionInstance) callable).Call(thisObject, arguments, true);
             }
             }
             
             
-            return callable.Call(thisObject, arguments);
+            var result = callable.Call(thisObject, arguments);
+
+            _engine.CallStack.Pop();
+
+            return result;
         }
         }
 
 
         public JsValue EvaluateSequenceExpression(SequenceExpression sequenceExpression)
         public JsValue EvaluateSequenceExpression(SequenceExpression sequenceExpression)