Browse Source

Ensure Acornima ParseErrorException is exposed as JavasScriptException (#1924)

Marko Lahma 1 year ago
parent
commit
91755e9949

+ 5 - 2
Jint.Tests/Parser/JavascriptParserTests.cs

@@ -1,4 +1,6 @@
-namespace Jint.Tests.Parsing;
+using Jint.Runtime;
+
+namespace Jint.Tests.Parsing;
 
 
 public class JavascriptParserTests
 public class JavascriptParserTests
 {
 {
@@ -163,7 +165,8 @@ public class JavascriptParserTests
     [Fact]
     [Fact]
     public void ShouldThrowErrorForInvalidLeftHandOperation()
     public void ShouldThrowErrorForInvalidLeftHandOperation()
     {
     {
-        Assert.Throws<SyntaxErrorException>(() => new Engine().Execute("~ (WE0=1)--- l('1');"));
+        var ex = Assert.Throws<JavaScriptException>(() => new Engine().Execute("~ (WE0=1)--- l('1');"));
+        Assert.Equal("Invalid left-hand side expression in postfix operation (<anonymous>:1:4)", ex.Message);
     }
     }
 
 
 
 

+ 30 - 0
Jint.Tests/Runtime/ClassTests.cs

@@ -1,3 +1,5 @@
+using Jint.Runtime;
+
 namespace Jint.Tests.Runtime;
 namespace Jint.Tests.Runtime;
 
 
 public class ClassTests
 public class ClassTests
@@ -45,4 +47,32 @@ public class ClassTests
         Assert.Equal(10, engine.Evaluate("board.width"));
         Assert.Equal(10, engine.Evaluate("board.width"));
         Assert.Equal(20, engine.Evaluate("board.doubleWidth "));
         Assert.Equal(20, engine.Evaluate("board.doubleWidth "));
     }
     }
+
+    [Fact]
+    public void PrivateMemberAccessOutsideOfClass()
+    {
+        var ex = Assert.Throws<JavaScriptException>(() => new Engine().Evaluate
+        (
+            """
+            class A { }
+            new A().#nonexistent = 1;
+            """
+        ));
+
+        Assert.Equal("Private field '#nonexistent' must be declared in an enclosing class (<anonymous>:2:9)", ex.Message);
+    }
+
+    [Fact]
+    public void PrivateMemberAccessAgainstUnknownMemberInConstructor()
+    {
+        var ex = Assert.Throws<JavaScriptException>(() => new Engine().Evaluate
+        (
+            """
+            class A { constructor() { #nonexistent = 2; } }
+            new A();
+            """
+        ));
+
+        Assert.Equal("Unexpected identifier '#nonexistent' (<anonymous>:1:27)", ex.Message);
+    }
 }
 }

+ 7 - 7
Jint.Tests/Runtime/EngineTests.cs

@@ -1046,11 +1046,11 @@ namespace Jint.Tests.Runtime
             {
             {
                 engine.Evaluate("1.2+ new", "jQuery.js");
                 engine.Evaluate("1.2+ new", "jQuery.js");
             }
             }
-            catch (SyntaxErrorException e)
+            catch (JavaScriptException e)
             {
             {
-                Assert.Equal(1, e.LineNumber);
-                Assert.Equal(8, e.Column);
-                Assert.Equal("jQuery.js", e.SourceFile);
+                Assert.Equal(1, e.Location.Start.Line);
+                Assert.Equal(8, e.Location.Start.Column);
+                Assert.Equal("jQuery.js", e.Location.SourceFile);
             }
             }
         }
         }
         #region DateParsingAndStrings
         #region DateParsingAndStrings
@@ -1314,7 +1314,7 @@ var prep = function (fn) { fn(); };
         {
         {
             var code = "if({ __proto__: [], __proto__:[] } instanceof Array) {}";
             var code = "if({ __proto__: [], __proto__:[] } instanceof Array) {}";
 
 
-            Exception ex = Assert.Throws<SyntaxErrorException>(() => _engine.Execute(code, new ScriptParsingOptions { Tolerant = false }));
+            Exception ex = Assert.Throws<JavaScriptException>(() => _engine.Execute(code, new ScriptParsingOptions { Tolerant = false }));
             Assert.Contains("Duplicate __proto__ fields are not allowed in object literals", ex.Message);
             Assert.Contains("Duplicate __proto__ fields are not allowed in object literals", ex.Message);
 
 
             ex = Assert.Throws<JavaScriptException>(() => _engine.Execute($"eval('{code}')"));
             ex = Assert.Throws<JavaScriptException>(() => _engine.Execute($"eval('{code}')"));
@@ -2865,8 +2865,8 @@ x.test = {
             Assert.Equal("Cannot delete property 'prototype' of function Boolean() { [native code] }", ex.Message);
             Assert.Equal("Cannot delete property 'prototype' of function Boolean() { [native code] }", ex.Message);
 
 
             const string source2 = "'use strict'; delete foobar;";
             const string source2 = "'use strict'; delete foobar;";
-            var ex2 = Assert.Throws<SyntaxErrorException>(() => engine.Evaluate(source2));
-            Assert.Equal("Delete of an unqualified identifier in strict mode", ex2.Description);
+            ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate(source2));
+            Assert.Equal("Delete of an unqualified identifier in strict mode (<anonymous>:1:22)", ex.Message);
         }
         }
 
 
         [Fact]
         [Fact]

+ 4 - 4
Jint/Engine.cs

@@ -341,7 +341,7 @@ namespace Jint
         /// </summary>
         /// </summary>
         public JsValue Evaluate(string code, string? source = null)
         public JsValue Evaluate(string code, string? source = null)
         {
         {
-            var script = _defaultParser.ParseScript(code, source ?? "<anonymous>", _isStrict);
+            var script = _defaultParser.ParseScriptGuarded(Realm, code, source ?? "<anonymous>", _isStrict);
             return Evaluate(new Prepared<Script>(script, _defaultParser.Options));
             return Evaluate(new Prepared<Script>(script, _defaultParser.Options));
         }
         }
 
 
@@ -357,7 +357,7 @@ namespace Jint
         public JsValue Evaluate(string code, string source, ScriptParsingOptions parsingOptions)
         public JsValue Evaluate(string code, string source, ScriptParsingOptions parsingOptions)
         {
         {
             var parser = GetParserFor(parsingOptions);
             var parser = GetParserFor(parsingOptions);
-            var script = parser.ParseScript(code, source, _isStrict);
+            var script = parser.ParseScriptGuarded(Realm, code, source, _isStrict);
             return Evaluate(new Prepared<Script>(script, parser.Options));
             return Evaluate(new Prepared<Script>(script, parser.Options));
         }
         }
 
 
@@ -372,7 +372,7 @@ namespace Jint
         /// </summary>
         /// </summary>
         public Engine Execute(string code, string? source = null)
         public Engine Execute(string code, string? source = null)
         {
         {
-            var script = _defaultParser.ParseScript(code, source ?? "<anonymous>", _isStrict);
+            var script = _defaultParser.ParseScriptGuarded(Realm, code, source ?? "<anonymous>", _isStrict);
             return Execute(new Prepared<Script>(script, _defaultParser.Options));
             return Execute(new Prepared<Script>(script, _defaultParser.Options));
         }
         }
 
 
@@ -388,7 +388,7 @@ namespace Jint
         public Engine Execute(string code, string source, ScriptParsingOptions parsingOptions)
         public Engine Execute(string code, string source, ScriptParsingOptions parsingOptions)
         {
         {
             var parser = GetParserFor(parsingOptions);
             var parser = GetParserFor(parsingOptions);
-            var script = parser.ParseScript(code, source, _isStrict);
+            var script = parser.ParseScriptGuarded(Realm, code, source, _isStrict);
             return Execute(new Prepared<Script>(script, parser.Options));
             return Execute(new Prepared<Script>(script, parser.Options));
         }
         }
 
 

+ 42 - 0
Jint/Extensions/AcornimaExtensions.cs

@@ -0,0 +1,42 @@
+using Jint.Runtime;
+
+namespace Jint;
+
+internal static class AcornimaExtensions
+{
+    public static Script ParseScriptGuarded(this Parser parser, Realm realm, string code, string? source = null, bool strict = false)
+    {
+        try
+        {
+            return parser.ParseScript(code, source, strict);
+        }
+        catch (ParseErrorException e)
+        {
+            ExceptionHelper.ThrowSyntaxError(realm, e.Message, ToLocation(e, source));
+            return default;
+        }
+    }
+
+    public static Module ParseModuleGuarded(this Parser parser, Engine engine, string code, string? source = null)
+    {
+        try
+        {
+            return parser.ParseModule(code, source);
+        }
+        catch (ParseErrorException ex)
+        {
+            ExceptionHelper.ThrowSyntaxError(engine.Realm, $"Error while loading module: error in module '{source}': {ex.Error}", ToLocation(ex, source));
+            return default;
+        }
+        catch (Exception)
+        {
+            ExceptionHelper.ThrowJavaScriptException(engine, $"Could not load module {source}", AstExtensions.DefaultLocation);
+            return default;
+        }
+    }
+
+    private static SourceLocation ToLocation(ParseErrorException ex, string? source)
+    {
+        return SourceLocation.From(Position.From(ex.LineNumber, ex.Column), Position.From(ex.LineNumber, ex.Column), source);
+    }
+}

+ 1 - 1
Jint/Native/Function/ClassDefinition.cs

@@ -26,7 +26,7 @@ internal sealed class ClassDefinition
         // generate missing constructor AST only once
         // generate missing constructor AST only once
         static MethodDefinition CreateConstructorMethodDefinition(Parser parser, string source)
         static MethodDefinition CreateConstructorMethodDefinition(Parser parser, string source)
         {
         {
-            var script = parser.ParseScript(source);
+            var script = parser.ParseScriptGuarded(new Engine().Realm, source);
             var classDeclaration = (ClassDeclaration) script.Body[0];
             var classDeclaration = (ClassDeclaration) script.Body[0];
             return (MethodDefinition) classDeclaration.Body.Body[0];
             return (MethodDefinition) classDeclaration.Body.Body[0];
         }
         }

+ 1 - 15
Jint/Native/Function/EvalFunction.cs

@@ -84,21 +84,7 @@ public sealed class EvalFunction : Function
             CheckPrivateFields = false
             CheckPrivateFields = false
         };
         };
         var parser = _engine.GetParserFor(adjustedParserOptions);
         var parser = _engine.GetParserFor(adjustedParserOptions);
-        try
-        {
-            script = parser.ParseScript(x.ToString(), strict: strictCaller);
-        }
-        catch (ParseErrorException e)
-        {
-            if (string.Equals(e.Error.Code, "InvalidLhsInAssignment", StringComparison.Ordinal))
-            {
-                ExceptionHelper.ThrowReferenceError(callerRealm, (string?) null);
-            }
-            else
-            {
-                ExceptionHelper.ThrowSyntaxError(callerRealm, e.Message);
-            }
-        }
+        script = parser.ParseScriptGuarded(_engine.Realm, x.ToString(), strict: strictCaller);
 
 
         var body = script.Body;
         var body = script.Body;
         if (body.Count == 0)
         if (body.Count == 0)

+ 1 - 1
Jint/Native/Function/FunctionInstance.Dynamic.cs

@@ -150,7 +150,7 @@ public partial class Function
                 parserOptions = parserOptions with { AllowReturnOutsideFunction = true };
                 parserOptions = parserOptions with { AllowReturnOutsideFunction = true };
             }
             }
             Parser parser = new(parserOptions);
             Parser parser = new(parserOptions);
-            function = (IFunction) parser.ParseScript(functionExpression, strict: _engine._isStrict).Body[0];
+            function = (IFunction) parser.ParseScriptGuarded(callerRealm, functionExpression, strict: _engine._isStrict).Body[0];
         }
         }
         catch (ParseErrorException ex)
         catch (ParseErrorException ex)
         {
         {

+ 1 - 1
Jint/Native/ShadowRealm/ShadowRealm.cs

@@ -110,7 +110,7 @@ public sealed class ShadowRealm : ObjectInstance
         Script script;
         Script script;
         try
         try
         {
         {
-            script = parser.ParseScript(sourceText, strict: _engine._isStrict);
+            script = parser.ParseScriptGuarded(callerRealm, sourceText, strict: _engine._isStrict);
         }
         }
         catch (ParseErrorException e)
         catch (ParseErrorException e)
         {
         {

+ 1 - 15
Jint/Runtime/Modules/ModuleFactory.cs

@@ -22,23 +22,9 @@ public static class ModuleFactory
     public static Module BuildSourceTextModule(Engine engine, ResolvedSpecifier resolved, string code, ModuleParsingOptions? parsingOptions = null)
     public static Module BuildSourceTextModule(Engine engine, ResolvedSpecifier resolved, string code, ModuleParsingOptions? parsingOptions = null)
     {
     {
         var source = resolved.Uri?.LocalPath ?? resolved.Key;
         var source = resolved.Uri?.LocalPath ?? resolved.Key;
-        AstModule module;
         var parserOptions = (parsingOptions ?? ModuleParsingOptions.Default).GetParserOptions();
         var parserOptions = (parsingOptions ?? ModuleParsingOptions.Default).GetParserOptions();
         var parser = new Parser(parserOptions);
         var parser = new Parser(parserOptions);
-        try
-        {
-            module = parser.ParseModule(code, source);
-        }
-        catch (ParseErrorException ex)
-        {
-            ExceptionHelper.ThrowSyntaxError(engine.Realm, $"Error while loading module: error in module '{source}': {ex.Error}");
-            module = null;
-        }
-        catch (Exception)
-        {
-            ExceptionHelper.ThrowJavaScriptException(engine, $"Could not load module {source}", AstExtensions.DefaultLocation);
-            module = null;
-        }
+        var module = parser.ParseModuleGuarded(engine, code, source);
 
 
         return BuildSourceTextModule(engine, new Prepared<AstModule>(module, parserOptions));
         return BuildSourceTextModule(engine, new Prepared<AstModule>(module, parserOptions));
     }
     }