Browse Source

Add convenience API methods for ShadowRealm (#1574)

Alexander Marek 1 year ago
parent
commit
f4e0ce3f4c

+ 61 - 0
Jint.Benchmark/ShadowRealmBenchmark.cs

@@ -0,0 +1,61 @@
+using BenchmarkDotNet.Attributes;
+using Esprima.Ast;
+
+namespace Jint.Benchmark;
+
+[MemoryDiagnoser]
+[BenchmarkCategory("ShadowRealm")]
+public class ShadowRealmBenchmark
+{
+    private const string sourceCode = @"
+(function (){return 'some string'})();
+";
+
+    private Engine engine;
+    private Script parsedScript;
+
+    [GlobalSetup]
+    public void Setup()
+    {
+        engine = new Engine();
+        parsedScript = Engine.PrepareScript(sourceCode);
+    }
+
+    [Benchmark]
+    public void ReusingEngine()
+    {
+        engine.Evaluate(sourceCode);
+    }
+
+    [Benchmark]
+    public void NewEngineInstance()
+    {
+        new Engine().Evaluate(sourceCode);
+    }
+
+    [Benchmark]
+    public void ShadowRealm()
+    {
+        var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm.Evaluate(sourceCode);
+    }
+
+    [Benchmark]
+    public void ReusingEngine_ParsedScript()
+    {
+        engine.Evaluate(parsedScript);
+    }
+
+    [Benchmark]
+    public void NewEngineInstance_ParsedScript()
+    {
+        new Engine().Evaluate(parsedScript);
+    }
+
+    [Benchmark]
+    public void ShadowRealm_ParsedScript()
+    {
+        var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm.Evaluate(parsedScript);
+    }
+}

+ 65 - 0
Jint.Tests.PublicInterface/ShadowRealmTests.cs

@@ -1,3 +1,4 @@
+using Jint.Native;
 using Jint.Native.Object;
 using Jint.Native.Object;
 
 
 namespace Jint.Tests.PublicInterface;
 namespace Jint.Tests.PublicInterface;
@@ -28,6 +29,70 @@ public class ShadowRealmTests
         Assert.Equal("John Doe", result);
         Assert.Equal("John Doe", result);
     }
     }
 
 
+    [Fact]
+    public void MultipleShadowRealmsDoNotInterfere()
+    {
+        var engine = new Engine(options => options.EnableModules(GetBasePath()));
+        engine.SetValue("message", "world");
+        engine.Evaluate("function hello() {return message}");
+
+        Assert.Equal("world",engine.Evaluate("hello();"));
+
+        var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm.SetValue("message", "realm 1");
+        shadowRealm.Evaluate("function hello() {return message}");
+
+        var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm2.SetValue("message", "realm 2");
+        shadowRealm2.Evaluate("function hello() {return message}");
+
+        // Act & Assert
+        Assert.Equal("realm 1", shadowRealm.Evaluate("hello();"));
+        Assert.Equal("realm 2", shadowRealm2.Evaluate("hello();"));
+    }
+
+    [Fact]
+    public void MultipleShadowRealm_SettingGlobalVariable_DoNotInterfere()
+    {
+        var engine = new Engine(options => options.EnableModules(GetBasePath()));
+        engine.SetValue("message", "hello ");
+        engine.Evaluate("(function hello() {message += \"engine\"})();");
+
+        var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm.SetValue("message", "hello ");
+        shadowRealm.Evaluate("(function hello() {message += \"realm 1\"})();");
+
+        var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm2.SetValue("message", "hello ");
+        shadowRealm2.Evaluate("(function hello() {message += \"realm 2\"})();");
+
+        // Act & Assert
+        Assert.Equal("hello engine", engine.Evaluate("message"));
+        Assert.Equal("hello realm 1", shadowRealm.Evaluate("message"));
+        Assert.Equal("hello realm 2", shadowRealm2.Evaluate("message"));
+    }
+
+    [Fact]
+    public void CanReuseScriptWithShadowRealm()
+    {
+        var engine = new Engine(options => options.EnableModules(GetBasePath()));
+        engine.SetValue("message", "engine");
+
+        var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm.SetValue("message", "realm 1");
+
+        var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct();
+        shadowRealm2.SetValue("message", "realm 2");
+
+        var parser = new Esprima.JavaScriptParser();
+        var script = parser.ParseScript("(function hello() {return \"hello \" + message})();");
+
+        // Act & Assert
+        Assert.Equal("hello engine", engine.Evaluate(script));
+        Assert.Equal("hello realm 1", shadowRealm.Evaluate(script));
+        Assert.Equal("hello realm 2", shadowRealm2.Evaluate(script));
+    }
+
     private static string GetBasePath()
     private static string GetBasePath()
     {
     {
         var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
         var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);

+ 65 - 0
Jint/Native/ShadowRealm/ShadowRealm.cs

@@ -5,7 +5,9 @@ using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.Promise;
 using Jint.Native.Promise;
 using Jint.Runtime;
 using Jint.Runtime;
+using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Environments;
+using Jint.Runtime.Interop;
 using Jint.Runtime.Interpreter;
 using Jint.Runtime.Interpreter;
 using Jint.Runtime.Interpreter.Statements;
 using Jint.Runtime.Interpreter.Statements;
 using Jint.Runtime.Modules;
 using Jint.Runtime.Modules;
@@ -38,6 +40,12 @@ public sealed class ShadowRealm : ObjectInstance
         return PerformShadowRealmEval(sourceText, callerRealm);
         return PerformShadowRealmEval(sourceText, callerRealm);
     }
     }
 
 
+    public JsValue Evaluate(Script script)
+    {
+        var callerRealm = _engine.Realm;
+        return PerformShadowRealmEval(script, callerRealm);
+    }
+
     public JsValue ImportValue(string specifier, string exportName)
     public JsValue ImportValue(string specifier, string exportName)
     {
     {
         var callerRealm = _engine.Realm;
         var callerRealm = _engine.Realm;
@@ -45,6 +53,47 @@ public sealed class ShadowRealm : ObjectInstance
         _engine.RunAvailableContinuations();
         _engine.RunAvailableContinuations();
         return value;
         return value;
     }
     }
+    public ShadowRealm SetValue(string name, Delegate value)
+    {
+        _shadowRealm.GlobalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(_engine, value), true, false, true));
+        return this;
+    }
+
+    public ShadowRealm SetValue(string name, string value)
+    {
+        return SetValue(name, JsString.Create(value));
+    }
+
+    public ShadowRealm SetValue(string name, double value)
+    {
+        return SetValue(name, JsNumber.Create(value));
+    }
+
+    public ShadowRealm SetValue(string name, int value)
+    {
+        return SetValue(name, JsNumber.Create(value));
+    }
+
+    public ShadowRealm SetValue(string name, bool value)
+    {
+        return SetValue(name, value ? JsBoolean.True : JsBoolean.False);
+    }
+
+    public ShadowRealm SetValue(string name, JsValue value)
+    {
+        _shadowRealm.GlobalObject.Set(name, value);
+        return this;
+    }
+
+    public ShadowRealm SetValue(string name, object obj)
+    {
+        var value = obj is Type t
+            ? TypeReference.CreateTypeReference(_engine, t)
+            : JsValue.FromObject(_engine, obj);
+
+        return SetValue(name, value);
+    }
+
 
 
     /// <summary>
     /// <summary>
     /// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval
     /// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval
@@ -74,6 +123,22 @@ public sealed class ShadowRealm : ObjectInstance
             return default;
             return default;
         }
         }
 
 
+        return PerformShadowRealmEvalInternal(script, callerRealm);
+    }
+
+    internal JsValue PerformShadowRealmEval(Script script, Realm callerRealm)
+    {
+        var evalRealm = _shadowRealm;
+
+        _engine._host.EnsureCanCompileStrings(callerRealm, evalRealm);
+
+        return PerformShadowRealmEvalInternal(script, callerRealm);
+    }
+
+    internal JsValue PerformShadowRealmEvalInternal(Script script, Realm callerRealm)
+    {
+        var evalRealm = _shadowRealm;
+
         ref readonly var body = ref script.Body;
         ref readonly var body = ref script.Body;
         if (body.Count == 0)
         if (body.Count == 0)
         {
         {