diff --git a/Jint.Benchmark/ShadowRealmBenchmark.cs b/Jint.Benchmark/ShadowRealmBenchmark.cs new file mode 100644 index 0000000000..754a4916a2 --- /dev/null +++ b/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); + } +} diff --git a/Jint.Tests.PublicInterface/ShadowRealmTests.cs b/Jint.Tests.PublicInterface/ShadowRealmTests.cs index ca98ac163e..1740def1c5 100644 --- a/Jint.Tests.PublicInterface/ShadowRealmTests.cs +++ b/Jint.Tests.PublicInterface/ShadowRealmTests.cs @@ -1,3 +1,4 @@ +using Jint.Native; using Jint.Native.Object; namespace Jint.Tests.PublicInterface; @@ -28,6 +29,70 @@ public void CanUseViaEngineMethods() 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() { var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index 2ab25f2732..e9e9f20051 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -5,7 +5,9 @@ using Jint.Native.Object; using Jint.Native.Promise; using Jint.Runtime; +using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; +using Jint.Runtime.Interop; using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Statements; using Jint.Runtime.Modules; @@ -38,6 +40,12 @@ public JsValue Evaluate(string sourceText) return PerformShadowRealmEval(sourceText, callerRealm); } + public JsValue Evaluate(Script script) + { + var callerRealm = _engine.Realm; + return PerformShadowRealmEval(script, callerRealm); + } + public JsValue ImportValue(string specifier, string exportName) { var callerRealm = _engine.Realm; @@ -45,6 +53,47 @@ public JsValue ImportValue(string specifier, string exportName) _engine.RunAvailableContinuations(); 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); + } + /// /// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval @@ -74,6 +123,22 @@ internal JsValue PerformShadowRealmEval(string sourceText, Realm callerRealm) 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; if (body.Count == 0) {