diff --git a/Jint.Tests/Runtime/RegExpTests.cs b/Jint.Tests/Runtime/RegExpTests.cs index 650812353..91f39c8e9 100644 --- a/Jint.Tests/Runtime/RegExpTests.cs +++ b/Jint.Tests/Runtime/RegExpTests.cs @@ -1,5 +1,10 @@ using System.Text.RegularExpressions; +using Acornima; +using Acornima.Ast; using Jint.Native; +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Runtime.Descriptors; namespace Jint.Tests.Runtime; @@ -139,4 +144,50 @@ public void Issue506() var result = engine.Evaluate("/[^]?(:[rp][el]a[\\w-]+)[^]/.test(':reagent-')").AsBoolean(); Assert.True(result); } + + [Fact] + public void RegExpLiteralInGetterShouldWork() + { + // Reproduces issue where using RegExp literal notation in getters parsed via Parser().ParseExpression() + // resulted in NullReferenceException due to uncompiled regex + var engine = new Engine(); + var obj = new JsObject(engine); + + // This should not throw NullReferenceException + var getterFunction = (IFunction)new Parser().ParseExpression( + "function() { return 'test'.match(/[a-z]/)?.[0]; }" + ); + var getter = new ScriptFunction(engine, getterFunction, true); + + obj.FastSetProperty("name", new GetSetPropertyDescriptor(getter, null, true, false)); + engine.SetValue("customObj", obj); + + engine.Execute(string.Empty); + var result = engine.GetValue("customObj").AsObject(); + var name = result.Get("name").ToString(); + + Assert.Equal("t", name); + } + + [Fact] + public void RegExpLiteralInGetterWorksWithConstructorToo() + { + // Ensure the alternative RegExp constructor approach continues to work + var engine = new Engine(); + var obj = new JsObject(engine); + + var getterFunction = (IFunction)new Parser().ParseExpression( + "function() { return 'test'.match(new RegExp('[a-z]'))?.[0]; }" + ); + var getter = new ScriptFunction(engine, getterFunction, true); + + obj.FastSetProperty("name", new GetSetPropertyDescriptor(getter, null, true, false)); + engine.SetValue("customObj", obj); + + engine.Execute(string.Empty); + var result = engine.GetValue("customObj").AsObject(); + var name = result.Get("name").ToString(); + + Assert.Equal("t", name); + } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs index 148da2cfb..aa60df928 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs @@ -68,7 +68,28 @@ private JsValue ResolveValue(EvaluationContext context) var regExpParseResult = regExpLiteral.ParseResult; if (regExpParseResult.Success) { - var regex = regExpLiteral.UserData as Regex ?? regExpParseResult.Regex!; + var regex = regExpLiteral.UserData as Regex ?? regExpParseResult.Regex; + if (regex is null) + { + // Regex was not compiled during parsing, compile it now + var parserOptions = context.Engine.GetActiveParserOptions(); + var compiledResult = Tokenizer.AdaptRegExp( + regExpLiteral.RegExp.Pattern, + regExpLiteral.RegExp.Flags, + compiled: false, + parserOptions.RegexTimeout, + ecmaVersion: parserOptions.EcmaVersion, + experimentalESFeatures: parserOptions.ExperimentalESFeatures); + + if (!compiledResult.Success) + { + Throw.SyntaxError(context.Engine.Realm, $"Unsupported regular expression. {compiledResult.ConversionError!.Description}"); + } + + regex = compiledResult.Regex!; + regExpParseResult = compiledResult; + } + return context.Engine.Realm.Intrinsics.RegExp.Construct(regex, regExpLiteral.RegExp.Pattern, regExpLiteral.RegExp.Flags, regExpParseResult); }