Skip to content

Commit d650cc0

Browse files
committed
Add support for isolated execution context
1 parent 30f26ce commit d650cc0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+667
-56
lines changed

Jint.Tests/Jint.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Reference Include="Microsoft.CSharp" Condition=" '$(TargetFramework)' == 'net461' " />
1313
</ItemGroup>
1414
<ItemGroup>
15+
<PackageReference Include="FluentAssertions" Version="5.10.3" />
1516
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
1617
<PackageReference Include="xunit" Version="2.4.1" />
1718
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace Jint.Tests.Runtime
6+
{
7+
public class IsolatedContextTests
8+
{
9+
[Fact]
10+
public void IsolatedContextCanBeUsedAndDisposed()
11+
{
12+
var engine = new Engine(options => options.Strict());
13+
engine.SetValue("assert", new Action<bool>(Assert.True));
14+
15+
// Set outer variable in global scope
16+
engine.SetValue("outer", 123);
17+
engine.Execute("assert(outer === 123)");
18+
engine.GetValue("outer").ToObject().Should().Be(123);
19+
20+
// Enter new execution context
21+
using (engine.EnterIsolatedContext())
22+
{
23+
// Can see global scope
24+
engine.Execute("assert(outer === 123)");
25+
26+
// Can modify global scope
27+
engine.Execute("outer = 321");
28+
engine.GetValue("outer").ToObject().Should().Be(321);
29+
30+
// Create variable in new context
31+
engine.Execute("var inner = 456");
32+
var value = engine.Execute("inner").GetCompletionValue();
33+
engine.Execute("assert(inner === 456)");
34+
35+
// cannot break anything
36+
Assert.Throws<NotSupportedException>(() => engine.Execute("Math.max = null;"));
37+
}
38+
39+
// The new variable is no longer visible
40+
engine.Execute("assert(typeof inner === 'undefined')");
41+
42+
// The new variable is not in the global scope
43+
engine.GetValue("inner").ToObject().Should().BeNull();
44+
45+
var max = engine.Execute("Math.max").GetCompletionValue();
46+
max.ToObject().Should().NotBeNull();
47+
48+
// but can again break things
49+
engine.Execute("Math.max = null;");
50+
engine.GetValue("Math.max").ToObject().Should().BeNull();
51+
}
52+
}
53+
}

Jint/Engine.cs

+69-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Runtime.CompilerServices;
44
using Esprima;
55
using Esprima.Ast;
6+
using Jint.Collections;
67
using Jint.Native;
78
using Jint.Native.Argument;
89
using Jint.Native.Array;
@@ -215,8 +216,8 @@ public Engine(Action<Engine, Options> options)
215216
}
216217

217218

218-
internal LexicalEnvironment GlobalEnvironment { get; }
219-
public GlobalObject Global { get; }
219+
internal LexicalEnvironment GlobalEnvironment { get; set; }
220+
public GlobalObject Global { get; internal set; }
220221
public ObjectConstructor Object { get; }
221222
public FunctionConstructor Function { get; }
222223
public ArrayConstructor Array { get; }
@@ -255,7 +256,6 @@ public ref readonly ExecutionContext ExecutionContext
255256

256257
internal Options Options { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }
257258

258-
#region Debugger
259259
public delegate StepMode DebugStepDelegate(object sender, DebugInformation e);
260260
public delegate StepMode BreakDelegate(object sender, DebugInformation e);
261261
public event DebugStepDelegate Step;
@@ -274,7 +274,6 @@ public ref readonly ExecutionContext ExecutionContext
274274
{
275275
return Break?.Invoke(this, info);
276276
}
277-
#endregion
278277

279278
public ExecutionContext EnterExecutionContext(
280279
LexicalEnvironment lexicalEnvironment,
@@ -288,6 +287,72 @@ public ExecutionContext EnterExecutionContext(
288287
return context;
289288
}
290289

290+
public IDisposable EnterIsolatedContext()
291+
{
292+
var originalGlobalEnvironment = GlobalEnvironment;
293+
if (originalGlobalEnvironment._record.GetType() != typeof(GlobalEnvironmentRecord))
294+
{
295+
ExceptionHelper.ThrowInvalidOperationException("Cannot enter isolated context when global environment is not default, did you already enter?");
296+
}
297+
298+
if (_executionContexts.Count != 1)
299+
{
300+
ExceptionHelper.ThrowInvalidOperationException("Cannot enter isolated context when execution context stack is not on root level");
301+
}
302+
303+
if (!_isStrict)
304+
{
305+
ExceptionHelper.ThrowInvalidOperationException("Cannot enter isolated context when engine is not in strict mode");
306+
}
307+
308+
var originalGlobal = Global;
309+
var propertyLessGlobal = new GlobalObject(this)
310+
{
311+
_properties = new PropertyDictionary()
312+
};
313+
var globalEnvironmentRecord = (GlobalEnvironmentRecord) originalGlobalEnvironment._record;
314+
315+
var newGlobal = new LexicalEnvironment(this, new IsolatedEnvironmentRecord(this, propertyLessGlobal, globalEnvironmentRecord), null);
316+
GlobalEnvironment = newGlobal;
317+
Global = propertyLessGlobal;
318+
319+
var context = new ExecutionContext(newGlobal, newGlobal);
320+
_executionContexts.Push(context);
321+
322+
return new ContextRestorer(this, originalGlobalEnvironment, originalGlobal);
323+
}
324+
325+
private readonly struct ContextRestorer : IDisposable
326+
{
327+
private readonly Engine _engine;
328+
private readonly LexicalEnvironment _originalGlobalEnvironment;
329+
private readonly GlobalObject _originalGlobal;
330+
331+
public ContextRestorer(Engine engine, LexicalEnvironment originalGlobalEnvironment, GlobalObject originalGlobal)
332+
{
333+
_engine = engine;
334+
_originalGlobalEnvironment = originalGlobalEnvironment;
335+
_originalGlobal = originalGlobal;
336+
}
337+
338+
public void Dispose()
339+
{
340+
if (_engine.GlobalEnvironment._record.GetType() != typeof(IsolatedEnvironmentRecord))
341+
{
342+
ExceptionHelper.ThrowInvalidOperationException("Cannot leave isolated context when global environment is not isolated, did you already dispose?");
343+
}
344+
345+
if (_engine._executionContexts.Count != 2)
346+
{
347+
ExceptionHelper.ThrowInvalidOperationException("Cannot enter isolated context when execution context stack is not on root level");
348+
}
349+
350+
_engine.GlobalEnvironment = _originalGlobalEnvironment;
351+
_engine.Global = _originalGlobal;
352+
_engine._executionContexts.Pop();
353+
}
354+
}
355+
291356
public Engine SetValue(JsValue name, Delegate value)
292357
{
293358
Global.FastAddProperty(name, new DelegateWrapper(this, value), true, false, true);

Jint/Native/Argument/ArgumentsInstance.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal void Prepare(
4848
ClearProperties();
4949
}
5050

51-
protected override void Initialize()
51+
protected internal override void Initialize()
5252
{
5353
_canReturnToPool = false;
5454
var args = _args;

Jint/Native/Array/ArrayConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static ArrayConstructor CreateArrayConstructor(Engine engine)
4141
return obj;
4242
}
4343

44-
protected override void Initialize()
44+
protected internal override void Initialize()
4545
{
4646
var properties = new PropertyDictionary(3, checkExistingKeys: false)
4747
{

Jint/Native/Array/ArrayInstance.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ protected override void AddProperty(JsValue property, PropertyDescriptor descrip
241241
base.AddProperty(property, descriptor);
242242
}
243243

244-
protected override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
244+
protected internal override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
245245
{
246246
if (property == CommonProperties.Length)
247247
{

Jint/Native/Array/ArrayPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static ArrayPrototype CreatePrototypeObject(Engine engine, ArrayConstruct
3535
return obj;
3636
}
3737

38-
protected override void Initialize()
38+
protected internal override void Initialize()
3939
{
4040
var unscopables = new ObjectInstance(_engine)
4141
{

Jint/Native/Boolean/BooleanPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static BooleanPrototype CreatePrototypeObject(Engine engine, BooleanConst
2828
return obj;
2929
}
3030

31-
protected override void Initialize()
31+
protected internal override void Initialize()
3232
{
3333
var properties = new PropertyDictionary(3, checkExistingKeys: false)
3434
{

Jint/Native/Date/DateConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static DateConstructor CreateDateConstructor(Engine engine)
7676
return obj;
7777
}
7878

79-
protected override void Initialize()
79+
protected internal override void Initialize()
8080
{
8181
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
8282
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;

Jint/Native/Date/DatePrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static DatePrototype CreatePrototypeObject(Engine engine, DateConstructor
3939
return obj;
4040
}
4141

42-
protected override void Initialize()
42+
protected internal override void Initialize()
4343
{
4444
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
4545
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;

Jint/Native/Error/ErrorConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
6161

6262
public ErrorPrototype PrototypeObject { get; private set; }
6363

64-
protected override ObjectInstance GetPrototypeOf()
64+
protected internal override ObjectInstance GetPrototypeOf()
6565
{
6666
return _name._value != "Error" ? _engine.Error : _prototype;
6767
}

Jint/Native/Error/ErrorPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static ErrorPrototype CreatePrototypeObject(Engine engine, ErrorConstruct
3737
return obj;
3838
}
3939

40-
protected override void Initialize()
40+
protected internal override void Initialize()
4141
{
4242
var properties = new PropertyDictionary(3, checkExistingKeys: false)
4343
{

Jint/Native/Function/FunctionPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static FunctionPrototype CreatePrototypeObject(Engine engine)
3030
return obj;
3131
}
3232

33-
protected override void Initialize()
33+
protected internal override void Initialize()
3434
{
3535
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
3636
const PropertyFlag lengthFlags = PropertyFlag.Configurable;

Jint/Native/Global/GlobalObject.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public sealed class GlobalObject : ObjectInstance
1818
{
1919
private readonly StringBuilder _stringBuilder = new StringBuilder();
2020

21-
private GlobalObject(Engine engine) : base(engine)
21+
internal GlobalObject(Engine engine) : base(engine)
2222
{
2323
}
2424

@@ -27,8 +27,14 @@ public static GlobalObject CreateGlobalObject(Engine engine)
2727
return new GlobalObject(engine);
2828
}
2929

30-
protected override void Initialize()
30+
protected internal override void Initialize()
3131
{
32+
if (_properties != null)
33+
{
34+
// already forced
35+
return;
36+
}
37+
3238
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
3339
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
3440
var properties = new PropertyDictionary(40, checkExistingKeys: false)

Jint/Native/Iterator/IteratorPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static IteratorPrototype CreatePrototypeObject(Engine engine, string name
2525
return obj;
2626
}
2727

28-
protected override void Initialize()
28+
protected internal override void Initialize()
2929
{
3030
var properties = new PropertyDictionary(2, checkExistingKeys: false)
3131
{

Jint/Native/Json/JsonInstance.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static JsonInstance CreateJsonObject(Engine engine)
2424
return json;
2525
}
2626

27-
protected override void Initialize()
27+
protected internal override void Initialize()
2828
{
2929
var properties = new PropertyDictionary(2, checkExistingKeys: false)
3030
{

Jint/Native/Map/MapConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static MapConstructor CreateMapConstructor(Engine engine)
3939
return obj;
4040
}
4141

42-
protected override void Initialize()
42+
protected internal override void Initialize()
4343
{
4444
var symbols = new SymbolDictionary(1)
4545
{

Jint/Native/Map/MapInstance.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
2525
return base.GetOwnProperty(property);
2626
}
2727

28-
protected override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
28+
protected internal override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
2929
{
3030
if (property == CommonProperties.Size)
3131
{

Jint/Native/Map/MapPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static MapPrototype CreatePrototypeObject(Engine engine, MapConstructor m
3030
return obj;
3131
}
3232

33-
protected override void Initialize()
33+
protected internal override void Initialize()
3434
{
3535
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
3636
var properties = new PropertyDictionary(12, checkExistingKeys: false)

Jint/Native/Math/MathInstance.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static MathInstance CreateMathObject(Engine engine)
2727
return math;
2828
}
2929

30-
protected override void Initialize()
30+
protected internal override void Initialize()
3131
{
3232
var properties = new PropertyDictionary(45, checkExistingKeys: false)
3333
{

Jint/Native/Number/NumberConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static NumberConstructor CreateNumberConstructor(Engine engine)
3939
return obj;
4040
}
4141

42-
protected override void Initialize()
42+
protected internal override void Initialize()
4343
{
4444
var properties = new PropertyDictionary(15, checkExistingKeys: false)
4545
{

Jint/Native/Number/NumberPrototype.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static NumberPrototype CreatePrototypeObject(Engine engine, NumberConstru
3535
return obj;
3636
}
3737

38-
protected override void Initialize()
38+
protected internal override void Initialize()
3939
{
4040
var properties = new PropertyDictionary(8, checkExistingKeys: false)
4141
{

Jint/Native/Object/ObjectConstructor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static ObjectConstructor CreateObjectConstructor(Engine engine)
2929
return obj;
3030
}
3131

32-
protected override void Initialize()
32+
protected internal override void Initialize()
3333
{
3434
_prototype = Engine.Function.PrototypeObject;
3535

Jint/Native/Object/ObjectInstance.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ protected virtual void AddProperty(JsValue property, PropertyDescriptor descript
263263
SetProperty(property, descriptor);
264264
}
265265

266-
protected virtual bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
266+
protected internal virtual bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
267267
{
268268
descriptor = null;
269269

@@ -873,7 +873,7 @@ protected void EnsureInitialized()
873873
Initialize();
874874
}
875875

876-
protected virtual void Initialize()
876+
protected internal virtual void Initialize()
877877
{
878878
}
879879

@@ -1104,7 +1104,7 @@ public virtual JsValue PreventExtensions()
11041104
return JsBoolean.True;
11051105
}
11061106

1107-
protected virtual ObjectInstance GetPrototypeOf()
1107+
protected internal virtual ObjectInstance GetPrototypeOf()
11081108
{
11091109
return _prototype;
11101110
}

0 commit comments

Comments
 (0)