Skip to content

Commit

Permalink
Add memory usage limit option to Engine (#482)
Browse files Browse the repository at this point in the history
Fixes #480
  • Loading branch information
Nihal Talur authored and sebastienros committed Jun 18, 2018
1 parent e12d443 commit f90d6c1
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 4 deletions.
8 changes: 8 additions & 0 deletions Jint.Tests/Runtime/EngineTests.cs
Expand Up @@ -647,6 +647,14 @@ public void ShouldThrowStatementCountOverflow()
);
}

[Fact]
public void ShouldThrowMemoryLimitExceeded()
{
Assert.Throws<MemoryLimitExceededException>(
() => new Engine(cfg => cfg.LimitMemory(2048)).Execute("a=[]; while(true){ a.push(0); }")
);
}

[Fact]
public void ShouldThrowTimeout()
{
Expand Down
47 changes: 45 additions & 2 deletions Jint/Engine.cs
Expand Up @@ -42,6 +42,7 @@ public class Engine
private readonly ExecutionContextStack _executionContexts;
private JsValue _completionValue = JsValue.Undefined;
private int _statementsCount;
private long _initialMemoryUsage;
private long _timeoutTicks;
private INode _lastSyntaxNode;

Expand Down Expand Up @@ -79,6 +80,16 @@ public class Engine

internal JintCallStack CallStack = new JintCallStack();

static Engine()
{
var methodInfo = typeof(GC).GetMethod("GetAllocatedBytesForCurrentThread");

if (methodInfo != null)
{
GetAllocatedBytesForCurrentThread = (Func<long>)Delegate.CreateDelegate(typeof(Func<long>), null, methodInfo);
}
}

public Engine() : this(null)
{
}
Expand Down Expand Up @@ -163,7 +174,7 @@ public Engine(Action<Options> options)
// gather some options as fields for faster checks
_isDebugMode = Options.IsDebugMode;
_isStrict = Options.IsStrict;
_maxStatements = Options.MaxStatementCount;
_maxStatements = Options._MaxStatements;
_referenceResolver = Options.ReferenceResolver;

ReferencePool = new ReferencePool();
Expand Down Expand Up @@ -251,6 +262,8 @@ internal ReferencePool ReferencePool
}
#endregion

private static readonly Func<long> GetAllocatedBytesForCurrentThread;

public void EnterExecutionContext(
LexicalEnvironment lexicalEnvironment,
LexicalEnvironment variableEnvironment,
Expand Down Expand Up @@ -314,6 +327,14 @@ public void ResetStatementsCount()
_statementsCount = 0;
}

public void ResetMemoryUsage()
{
if (GetAllocatedBytesForCurrentThread != null)
{
_initialMemoryUsage = GetAllocatedBytesForCurrentThread();
}
}

public void ResetTimeoutTicks()
{
var timeoutIntervalTicks = Options._TimeoutInterval.Ticks;
Expand Down Expand Up @@ -342,6 +363,12 @@ public Engine Execute(string source, ParserOptions parserOptions)
public Engine Execute(Program program)
{
ResetStatementsCount();

if (Options._MemoryLimit > 0)
{
ResetMemoryUsage();
}

ResetTimeoutTicks();
ResetLastStatement();
ResetCallStack();
Expand Down Expand Up @@ -388,6 +415,22 @@ public Completion ExecuteStatement(Statement statement)
ThrowTimeoutException();
}

if (Options._MemoryLimit > 0)
{
if (GetAllocatedBytesForCurrentThread != null)
{
var memoryUsage = GetAllocatedBytesForCurrentThread() - _initialMemoryUsage;
if (memoryUsage > Options._MemoryLimit)
{
throw new MemoryLimitExceededException($"Script has allocated {memoryUsage} but is limited to {Options._MemoryLimit}");
}
}
else
{
throw new PlatformNotSupportedException("The current platform doesn't support MemoryLimit.");
}
}

_lastSyntaxNode = statement;

if (_isDebugMode)
Expand Down Expand Up @@ -980,4 +1023,4 @@ private void ThrowTypeError()
throw new JavaScriptException(TypeError);
}
}
}
}
2 changes: 1 addition & 1 deletion Jint/Jint.csproj
Expand Up @@ -9,4 +9,4 @@
<ItemGroup>
<PackageReference Include="Esprima" Version="1.0.0-beta-1026" />
</ItemGroup>
</Project>
</Project>
15 changes: 14 additions & 1 deletion Jint/Options.cs
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Jint.Native;
using Jint.Runtime.Interop;

Expand All @@ -16,6 +17,7 @@ public class Options
private bool _allowClr;
private readonly List<IObjectConverter> _objectConverters = new List<IObjectConverter>();
private int _maxStatements;
private long _memoryLimit;
private int _maxRecursionDepth = -1;
private TimeSpan _timeoutInterval;
private CultureInfo _culture = CultureInfo.CurrentCulture;
Expand Down Expand Up @@ -112,6 +114,11 @@ public Options MaxStatements(int maxStatements = 0)
_maxStatements = maxStatements;
return this;
}
public Options LimitMemory(long memoryLimit)
{
_memoryLimit = memoryLimit;
return this;
}

public Options TimeoutInterval(TimeSpan timeoutInterval)
{
Expand Down Expand Up @@ -168,7 +175,13 @@ public Options SetReferencesResolver(IReferenceResolver resolver)

internal List<IObjectConverter> _ObjectConverters => _objectConverters;

internal int MaxStatementCount => _maxStatements;
internal long _MemoryLimit => _memoryLimit;

internal int _MaxStatements
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _maxStatements; }
}

internal int MaxRecursionDepth => _maxRecursionDepth;

Expand Down
15 changes: 15 additions & 0 deletions Jint/Runtime/MemoryLimitExceededException.cs
@@ -0,0 +1,15 @@
using System;

namespace Jint.Runtime
{
public class MemoryLimitExceededException : Exception
{
public MemoryLimitExceededException() : base()
{
}

public MemoryLimitExceededException(string message) : base(message)
{
}
}
}

0 comments on commit f90d6c1

Please sign in to comment.