diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index 0bbc024b27..d906454cec 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -48,7 +48,7 @@ private static void Main(string[] args) var parserOptions = new ParserOptions { Tolerant = true, - AdaptRegexp = true + RegExpParseMode = RegExpParseMode.AdaptToInterpreted }; var serializer = new JsonSerializer(engine); diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 63d84c6d58..0140de4ee3 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -30,26 +30,20 @@ "intl402" ], "ExcludedFiles": [ - // Esprima problem for regex, https://github.com/sebastienros/esprima-dotnet/pull/364 - "built-ins/RegExp/S15.10.4.1_A9_T3.js", // Currently quite impossible to detect if assignment target is CoverParenthesizedExpression "language/expressions/assignment/fn-name-lhs-cover.js", - // Unicode support not built-in to .NET the same way, requires more work - "built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js", - "built-ins/RegExp/prototype/Symbol.search/u-lastindex-advance.js", - "built-ins/RegExp/prototype/exec/u-lastindex-adv.js", - "built-ins/RegExp/unicode_character_class_backspace_escape.js", - "built-ins/RegExp/unicode_restricted_identity_escape_alpha.js", - "built-ins/RegExp/unicode_restricted_identity_escape_c.js", - "built-ins/RegExp/unicode_restricted_identity_escape_u.js", - "built-ins/RegExp/unicode_restricted_identity_escape_x.js", - "language/literals/regexp/u-astral-char-class-invert.js", - "language/literals/regexp/u-astral.js", - "language/literals/regexp/u-case-mapping.js", + // RegExp conversion limitations + "built-ins/RegExp/S15.10.2.11_A1_T5.js", + "built-ins/RegExp/S15.10.2.11_A1_T7.js", + "built-ins/RegExp/S15.10.2.5_A1_T4.js", + "built-ins/RegExp/named-groups/non-unicode-references.js", + "built-ins/RegExp/named-groups/unicode-references.js", + "built-ins/RegExp/quantifier-integer-limit.js", + "language/literals/regexp/named-groups/forward-reference.js", - // cannot have characters like 𝒜 as group name or something starting with $ in .NET, other .NET limitations + // RegExp handling problems "built-ins/RegExp/match-indices/indices-array-unicode-property-names.js", "built-ins/RegExp/named-groups/non-unicode-match.js", "built-ins/RegExp/named-groups/non-unicode-property-names-valid.js", @@ -58,17 +52,10 @@ "built-ins/RegExp/named-groups/unicode-property-names-valid.js", "built-ins/RegExp/named-groups/unicode-property-names.js", "built-ins/RegExp/prototype/Symbol.replace/named-groups.js", - "built-ins/RegExp/quantifier-integer-limit.js", - - // more validation and cleanup needed - "built-ins/RegExp/S15.10.2.13_A1_T1.js", - "built-ins/RegExp/S15.10.2.13_A1_T2.js", - "built-ins/RegExp/character-class-escape-non-whitespace.js", - "built-ins/RegExp/unicode_character_class_backspace_escape.js", - "built-ins/RegExp/unicode_identity_escape.js", - "built-ins/RegExp/unicode_restricted_character_class_escape.js", - "built-ins/RegExp/unicode_restricted_identity_escape.js", - "built-ins/RegExp/unicode_restricted_quantifiable_assertion.js", + "built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js", + "built-ins/String/prototype/split/separator-regexp.js", + "language/literals/regexp/u-case-mapping.js", + "language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js", // requires investigation how to process complex function name evaluation for property "built-ins/Function/prototype/toString/method-computed-property-name.js", @@ -77,13 +64,6 @@ // http://www.ecma-international.org/ecma-262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics not implemented (block level functions) "language/statements/let/block-local-closure-set-before-initialization.js", - // Logic difference in .NET RegExp / skipped in ECMA tests too - "built-ins/RegExp/S15.10.2.11_A1_T5.js", - "built-ins/RegExp/S15.10.2.11_A1_T7.js", - "built-ins/RegExp/S15.10.4.1_A8_T2.js", - "built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js", - "built-ins/RegExp/S15.10.2.5_A1_T4.js", - // Windows line ending differences "built-ins/String/raw/special-characters.js", @@ -128,14 +108,11 @@ "language/module-code/instn-local-bndng-export-gen.js", // Esprima problem - "built-ins/String/prototype/split/separator-regexp.js", "language/expressions/object/let-non-strict-access.js", "language/expressions/object/let-non-strict-syntax.js", "language/expressions/object/yield-non-strict-access.js", "language/expressions/object/yield-non-strict-syntax.js", "language/expressions/tagged-template/invalid-escape-sequences.js", - "language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js", - "language/literals/regexp/u-unicode-esc.js", "language/statements/for-of/dstr-obj-id-identifier-yield-ident-valid.js", "language/statements/for/head-lhs-let.js", diff --git a/Jint.Tests.Test262/Test262ModuleLoader.cs b/Jint.Tests.Test262/Test262ModuleLoader.cs index 4baa4ce271..0ec50ddb0f 100644 --- a/Jint.Tests.Test262/Test262ModuleLoader.cs +++ b/Jint.Tests.Test262/Test262ModuleLoader.cs @@ -39,7 +39,7 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) var parserOptions = new ParserOptions { - AdaptRegexp = true, + RegExpParseMode = RegExpParseMode.AdaptToInterpreted, Tolerant = true }; diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index 6bbac100e4..18abc38a04 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -38,7 +38,7 @@ private Engine BuildTestExecutor(Test262File file) throw new Exception("only script parsing supported"); } - var options = new ParserOptions { AdaptRegexp = true, Tolerant = false }; + var options = new ParserOptions { RegExpParseMode = RegExpParseMode.AdaptToInterpreted, Tolerant = false }; var parser = new JavaScriptParser(options); var script = parser.ParseScript(args.At(0).AsString()); diff --git a/Jint.Tests/Runtime/ErrorTests.cs b/Jint.Tests/Runtime/ErrorTests.cs index 05639ec6e4..0f9e04c840 100644 --- a/Jint.Tests/Runtime/ErrorTests.cs +++ b/Jint.Tests/Runtime/ErrorTests.cs @@ -297,7 +297,7 @@ public void StackTraceCollectedForImmediatelyInvokedFunctionExpression() var parserOptions = new ParserOptions { - AdaptRegexp = true, + RegExpParseMode = RegExpParseMode.AdaptToInterpreted, Tolerant = true }; var ex = Assert.Throws(() => engine.Execute(script, "get-item.js", parserOptions)); @@ -388,7 +388,7 @@ static ParserOptions CreateParserOptions() { return new ParserOptions { - AdaptRegexp = true, + RegExpParseMode = RegExpParseMode.AdaptToInterpreted, Tolerant = true }; } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 0cbad545f1..0d58d105c6 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -26,11 +26,8 @@ namespace Jint /// public sealed partial class Engine : IDisposable { - private static readonly ParserOptions _defaultParserOptions = ParserOptions.Default with - { - AllowReturnOutsideFunction = true - }; - private readonly JavaScriptParser _defaultParser = new(_defaultParserOptions); + private readonly ParserOptions _defaultParserOptions; + private readonly JavaScriptParser _defaultParser; internal readonly ExecutionContextStack _executionContexts; private JsValue _completionValue = JsValue.Undefined; @@ -131,6 +128,14 @@ public Engine(Action options) CallStack = new JintCallStack(Options.Constraints.MaxRecursionDepth >= 0); _stackGuard = new StackGuard(this); + + _defaultParserOptions = ParserOptions.Default with + { + AllowReturnOutsideFunction = true, + RegexTimeout = Options.Constraints.RegexTimeout + }; + + _defaultParser = new JavaScriptParser(_defaultParserOptions); } private void Reset() diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 01b7f6da55..5dfab6951e 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -16,7 +16,7 @@ - + diff --git a/Jint/ModuleBuilder.cs b/Jint/ModuleBuilder.cs index e1c18e5ee3..08c26dda8c 100644 --- a/Jint/ModuleBuilder.cs +++ b/Jint/ModuleBuilder.cs @@ -20,7 +20,10 @@ internal ModuleBuilder(Engine engine, string specifier) { _engine = engine; _specifier = specifier; - _options = new ParserOptions(); + _options = new ParserOptions + { + RegexTimeout = engine.Options.Constraints.RegexTimeout + }; } public ModuleBuilder AddSource(string code) diff --git a/Jint/Native/Function/FunctionInstance.Dynamic.cs b/Jint/Native/Function/FunctionInstance.Dynamic.cs index fb6317005a..162428b981 100644 --- a/Jint/Native/Function/FunctionInstance.Dynamic.cs +++ b/Jint/Native/Function/FunctionInstance.Dynamic.cs @@ -138,7 +138,11 @@ internal FunctionInstance CreateDynamicFunction( } } - JavaScriptParser parser = new(new ParserOptions { Tolerant = false }); + JavaScriptParser parser = new(new ParserOptions + { + Tolerant = false, + RegexTimeout = _engine.Options.Constraints.RegexTimeout + }); function = (IFunction) parser.ParseScript(functionExpression, source: null, _engine._isStrict).Body[0]; } catch (ParserException ex) diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index 8142cf9427..3a339fc672 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -104,17 +104,14 @@ private ObjectInstance RegExpInitialize(JsRegExp r, JsValue pattern, JsValue fla try { - var options = new ScannerOptions(); - var scanner = new Scanner("/" + p + "/" + flags, options); + var regExp = Scanner.AdaptRegExp(p, f, compiled: false, _engine.Options.Constraints.RegexTimeout); - // seems valid - r.Value = scanner.ParseRegex(p, f, options.RegexTimeout); - - var timeout = _engine.Options.Constraints.RegexTimeout; - if (timeout.Ticks > 0) + if (regExp is null) { - r.Value = new Regex(r.Value.ToString(), r.Value.Options, timeout); + ExceptionHelper.ThrowSyntaxError(_realm, $"Unsupported regular expression: '/{p}/{flags}'"); } + + r.Value = regExp; } catch (Exception ex) { @@ -140,21 +137,10 @@ private JsRegExp RegExpAlloc(JsValue newTarget) public JsRegExp Construct(Regex regExp, string source, string flags) { - var r = new JsRegExp(Engine); - r._prototype = PrototypeObject; - - r.Flags = flags; + var r = RegExpAlloc(this); + r.Value = regExp; r.Source = source; - - var timeout = _engine.Options.Constraints.RegexTimeout; - if (timeout.Ticks > 0) - { - r.Value = new Regex(regExp.ToString(), regExp.Options, timeout); - } - else - { - r.Value = regExp; - } + r.Flags = flags; RegExpInitialize(r); diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index a41cf456c4..1794feae5a 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -17,12 +17,17 @@ namespace Jint.Native.ShadowRealm; /// public sealed class ShadowRealm : ObjectInstance { - private readonly JavaScriptParser _parser = new(new ParserOptions { Tolerant = false }); + private readonly JavaScriptParser _parser; internal readonly Realm _shadowRealm; private readonly ExecutionContext _executionContext; internal ShadowRealm(Engine engine, ExecutionContext executionContext, Realm shadowRealm) : base(engine) { + _parser = new(new ParserOptions + { + Tolerant = false, + RegexTimeout = engine.Options.Constraints.RegexTimeout + }); _executionContext = executionContext; _shadowRealm = shadowRealm; } diff --git a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs index 20cd2cd70d..de5de54832 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs @@ -76,7 +76,12 @@ private JsValue ResolveValue(EvaluationContext context) if (expression.TokenType == TokenType.RegularExpression) { var regExpLiteral = (RegExpLiteral) _expression; - return context.Engine.Realm.Intrinsics.RegExp.Construct((System.Text.RegularExpressions.Regex) regExpLiteral.Value!, regExpLiteral.Regex.Pattern, regExpLiteral.Regex.Flags); + if (regExpLiteral.Value is System.Text.RegularExpressions.Regex regex) + { + return context.Engine.Realm.Intrinsics.RegExp.Construct(regex, regExpLiteral.Regex.Pattern, regExpLiteral.Regex.Flags); + } + + ExceptionHelper.ThrowSyntaxError(context.Engine.Realm, $"Unsupported regular expression: '{regExpLiteral.Regex.Pattern}/{regExpLiteral.Regex.Flags}'"); } return JsValue.FromObject(context.Engine, expression.Value);