From 3d5c8720e3b0bcd06cf3e920cef64e9e1cf9d154 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 24 Jul 2024 12:05:56 +0300 Subject: [PATCH] Ensure script preparation exposes only Jint's exception type (#1927) --- ...dulePreparationTests.ScriptPreparation.cs} | 47 +++++++++++++------ Jint/Engine.Ast.cs | 32 +++++++++---- Jint/JintException.cs | 15 ++++++ Jint/Runtime/JintException.cs | 20 -------- Jint/ScriptPreparationException.cs | 8 ++++ 5 files changed, 79 insertions(+), 43 deletions(-) rename Jint.Tests/Runtime/{EngineTests.ScriptPreparation.cs => ScriptModulePreparationTests.ScriptPreparation.cs} (54%) create mode 100644 Jint/JintException.cs delete mode 100644 Jint/Runtime/JintException.cs create mode 100644 Jint/ScriptPreparationException.cs diff --git a/Jint.Tests/Runtime/EngineTests.ScriptPreparation.cs b/Jint.Tests/Runtime/ScriptModulePreparationTests.ScriptPreparation.cs similarity index 54% rename from Jint.Tests/Runtime/EngineTests.ScriptPreparation.cs rename to Jint.Tests/Runtime/ScriptModulePreparationTests.ScriptPreparation.cs index 16a37a13ef..5070bcb082 100644 --- a/Jint.Tests/Runtime/EngineTests.ScriptPreparation.cs +++ b/Jint.Tests/Runtime/ScriptModulePreparationTests.ScriptPreparation.cs @@ -6,13 +6,13 @@ namespace Jint.Tests.Runtime; -public partial class EngineTests +public class ScriptModulePreparationTests { [Fact] public void ScriptPreparationAcceptsReturnOutsideOfFunctions() { var preparedScript = Engine.PrepareScript("return 1;"); - Assert.IsType(preparedScript.Program.Body[0]); + preparedScript.Program.Body[0].Should().BeOfType(); } [Fact] @@ -21,10 +21,10 @@ public void CanPreCompileRegex() var script = Engine.PrepareScript("var x = /[cgt]/ig; var y = /[cgt]/ig; 'g'.match(x).length;"); var declaration = Assert.IsType(script.Program.Body[0]); var init = Assert.IsType(declaration.Declarations[0].Init); - Assert.Equal("[cgt]", init.Value.ToString()); - Assert.Equal(RegexOptions.Compiled, init.Value.Options & RegexOptions.Compiled); - Assert.Equal(1, _engine.Evaluate(script)); + init.Value.ToString().Should().Be("[cgt]"); + (init.Value.Options & RegexOptions.Compiled).Should().Be(RegexOptions.Compiled); + new Engine().Evaluate(script).AsNumber().Should().Be(1); } [Fact] @@ -33,9 +33,9 @@ public void ScriptPreparationFoldsConstants() var preparedScript = Engine.PrepareScript("return 1 + 2;"); var returnStatement = Assert.IsType(preparedScript.Program.Body[0]); var constant = Assert.IsType(returnStatement.Argument?.UserData); - Assert.Equal(3, constant.GetValue(null!)); - Assert.Equal(3, _engine.Evaluate(preparedScript)); + constant.GetValue(null!).AsNumber().Should().Be(3); + new Engine().Evaluate(preparedScript).AsNumber().Should().Be(3); } [Fact] @@ -46,8 +46,8 @@ public void ScriptPreparationOptimizesNegatingUnaryExpression() var unaryExpression = Assert.IsType(expression.Expression); var constant = Assert.IsType(unaryExpression.UserData); - Assert.Equal(-1, constant.GetValue(null!)); - Assert.Equal(-1, _engine.Evaluate(preparedScript)); + constant.GetValue(null!).AsNumber().Should().Be(-1); + new Engine().Evaluate(preparedScript).AsNumber().Should().Be(-1); } [Fact] @@ -58,10 +58,10 @@ public void ScriptPreparationOptimizesConstantReturn() var returnStatement = Assert.IsType(statement.UserData); var builtStatement = JintStatement.Build(statement); - Assert.Same(returnStatement, builtStatement); + returnStatement.Should().BeSameAs(builtStatement); - var result = builtStatement.Execute(new EvaluationContext(_engine)).Value; - Assert.Equal(JsBoolean.False, result); + var result = builtStatement.Execute(new EvaluationContext( new Engine())).Value; + result.Should().Be(JsBoolean.False); } [Fact] @@ -69,9 +69,26 @@ public void CompiledRegexShouldProduceSameResultAsNonCompiled() { const string Script = """JSON.stringify(/(.*?)a(?!(a+)b\2c)\2(.*)/.exec("baaabaac"))"""; - var nonCompiled = _engine.Evaluate(Script); - var compiled = _engine.Evaluate(Engine.PrepareScript(Script)); + var engine = new Engine(); + var nonCompiledResult = engine.Evaluate(Script); + var compiledResult = engine.Evaluate(Engine.PrepareScript(Script)); - Assert.Equal(nonCompiled, compiled); + nonCompiledResult.Should().Be(compiledResult); + } + + [Fact] + public void PrepareScriptShouldNotLeakAcornimaException() + { + var ex = Assert.Throws(() => Engine.PrepareScript("class A { } A().#nonexistent = 1;")); + ex.Message.Should().Be("Could not prepare script: Private field '#nonexistent' must be declared in an enclosing class (1:17)"); + ex.InnerException.Should().BeOfType(); + } + + [Fact] + public void PrepareModuleShouldNotLeakAcornimaException() + { + var ex = Assert.Throws(() => Engine.PrepareModule("class A { } A().#nonexistent = 1;")); + ex.Message.Should().Be("Could not prepare script: Private field '#nonexistent' must be declared in an enclosing class (1:17)"); + ex.InnerException.Should().BeOfType(); } } diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 78b92ea6e6..0c6d7a703d 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -1,5 +1,4 @@ using System.Runtime.InteropServices; -using System.Text.RegularExpressions; using Jint.Native; using Jint.Runtime; using Jint.Runtime.Interpreter; @@ -20,10 +19,20 @@ public partial class Engine public static Prepared