Skip to content

Commit e1e2d6d

Browse files
authored
Prevent users from passing arbitrary parser options to Jint (#1831)
* Reduce parsing options for end users (replace ParserOptions with Script/ModuleParseOptions in the public API) * Enforce that pre-parsed scripts can be passed to Jint only if they were created using Engine.PrepareScript/PrepareModule * Avoid copying beefy ExecutionContext struct where possible * Propagate ParserOptions via ExecutionContext * Introduce alias for Esprima.Ast.Module
1 parent b6f99b9 commit e1e2d6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+754
-229
lines changed

Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Using Include="Esprima.Utils" />
3333

3434
<Using Include="Esprima.Ast.BindingPattern" Alias="DestructuringPattern" />
35+
<Using Include="Esprima.Ast.Module" Alias="AstModule" />
3536
<Using Include="Esprima.Ast.Nodes" Alias="NodeType" />
3637
<Using Include="Esprima.JavaScriptParser" Alias="Parser" />
3738
<Using Include="Esprima.Location" Alias="SourceLocation" />

Directory.Packages.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ItemGroup>
77
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
88
<PackageVersion Include="BenchmarkDotNet.TestAdapter" Version="0.13.12" />
9-
<PackageVersion Include="Esprima" Version="3.0.4" />
9+
<PackageVersion Include="Esprima" Version="3.0.5" />
1010
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
1111
<PackageVersion Include="Flurl.Http.Signed" Version="3.2.4" />
1212
<PackageVersion Include="Jurassic" Version="3.2.7" />

Jint.Benchmark/DromaeoBenchmark.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class DromaeoBenchmark
1515
{"dromaeo-string-base64", null}
1616
};
1717

18-
private readonly Dictionary<string, Script> _prepared = new();
18+
private readonly Dictionary<string, Prepared<Script>> _prepared = new();
1919

2020
private Engine engine;
2121

Jint.Benchmark/EngineComparisonBenchmark.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Jint.Benchmark;
1212
[BenchmarkCategory("EngineComparison")]
1313
public class EngineComparisonBenchmark
1414
{
15-
private static readonly Dictionary<string, Script> _parsedScripts = new();
15+
private static readonly Dictionary<string, Prepared<Script>> _parsedScripts = new();
1616

1717
private static readonly Dictionary<string, string> _files = new()
1818
{
@@ -38,7 +38,6 @@ public class EngineComparisonBenchmark
3838
[GlobalSetup]
3939
public void Setup()
4040
{
41-
var parser = new Parser();
4241
foreach (var fileName in _files.Keys.ToList())
4342
{
4443
var script = File.ReadAllText($"Scripts/{fileName}.js");
@@ -47,7 +46,7 @@ public void Setup()
4746
script = _dromaeoHelpers + Environment.NewLine + Environment.NewLine + script;
4847
}
4948
_files[fileName] = script;
50-
_parsedScripts[fileName] = parser.ParseScript(script, strict: true);
49+
_parsedScripts[fileName] = Engine.PrepareScript(script, strict: true);
5150
}
5251
}
5352

Jint.Benchmark/EngineConstructionBenchmark.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ namespace Jint.Benchmark;
66
[MemoryDiagnoser]
77
public class EngineConstructionBenchmark
88
{
9-
private Script _program;
10-
private Script _simple;
9+
private Prepared<Script> _program;
10+
private Prepared<Script> _simple;
1111

1212
[GlobalSetup]
1313
public void GlobalSetup()
1414
{
15-
var parser = new Parser();
16-
_program = parser.ParseScript("([].length + ''.length)");
17-
_simple = parser.ParseScript("1");
15+
_program = Engine.PrepareScript("([].length + ''.length)");
16+
_simple = Engine.PrepareScript("1");
1817
new Engine().Evaluate(_program);
1918
}
2019

Jint.Benchmark/ObjectAccessBenchmark.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Jint.Benchmark;
55
[MemoryDiagnoser]
66
public class ObjectAccessBenchmark
77
{
8-
private readonly Script _script;
8+
private readonly Prepared<Script> _script;
99

1010
public ObjectAccessBenchmark()
1111
{

Jint.Benchmark/ShadowRealmBenchmark.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ShadowRealmBenchmark
1111
";
1212

1313
private Engine engine;
14-
private Script parsedScript;
14+
private Prepared<Script> parsedScript;
1515

1616
[GlobalSetup]
1717
public void Setup()

Jint.Benchmark/SingleScriptBenchmark.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Jint.Benchmark;
66
public abstract class SingleScriptBenchmark
77
{
88
private string _script;
9-
private Script _parsedScript;
9+
private Prepared<Script> _parsedScript;
1010

1111
protected abstract string FileName { get; }
1212

Jint.Repl/Program.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
Console.WriteLine();
4141

4242
var defaultColor = Console.ForegroundColor;
43-
var parserOptions = new ParserOptions
43+
var parsingOptions = new ScriptParsingOptions
4444
{
4545
Tolerant = true,
46-
RegExpParseMode = RegExpParseMode.AdaptToInterpreted
46+
CompileRegex = false,
4747
};
4848

4949
var serializer = new JsonSerializer(engine);
@@ -60,7 +60,7 @@
6060

6161
try
6262
{
63-
var result = engine.Evaluate(input, parserOptions);
63+
var result = engine.Evaluate(input, parsingOptions);
6464
JsValue str = result;
6565
if (!result.IsPrimitive() && result is not IJsPrimitive)
6666
{

Jint.Tests.CommonScripts/ConcurrencyTest.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@ namespace Jint.Tests.CommonScripts;
44
public class ConcurrencyTest
55
{
66
[Test]
7-
[TestCase(true)]
8-
[TestCase(false)]
9-
public void ConcurrentEnginesCanUseSameAst(bool prepared)
7+
public void ConcurrentEnginesCanUseSameAst()
108
{
119
var scriptContents = SunSpiderTests.GetEmbeddedFile("babel-standalone.js");
12-
var script = prepared
13-
? Engine.PrepareScript(scriptContents)
14-
: new Parser().ParseScript(scriptContents);
10+
var script = Engine.PrepareScript(scriptContents);
1511

1612
Parallel.ForEach(Enumerable.Range(0, 3), x =>
1713
{

Jint.Tests.PublicInterface/ModuleLoaderTests.cs

+12-8
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ModuleScript RunModule(string code)
5151
public void CustomModuleLoaderWithCachingSupport()
5252
{
5353
// Different engines use the same module loader.
54-
// The module loader caches the parsed Esprima.Ast.Module
54+
// The module loader caches the parsed Module
5555
// which allows to re-use these for different engine runs.
5656
var store = new ModuleStore(new Dictionary<string, string>()
5757
{
@@ -177,7 +177,7 @@ Module IModuleLoader.LoadModule(Engine engine, ResolvedSpecifier resolved)
177177
/// <summary>
178178
/// <para>
179179
/// A simple <see cref="IModuleLoader"/> implementation which will
180-
/// re-use prepared <see cref="Esprima.Ast.Module"/> or <see cref="JsValue"/> modules to
180+
/// re-use prepared <see cref="AstModule"/> or <see cref="JsValue"/> modules to
181181
/// produce <see cref="Jint.Runtime.Modules.Module"/>.
182182
/// </para>
183183
/// <para>
@@ -231,20 +231,24 @@ private ParsedModule GetParsedModule(Uri uri, ResolvedSpecifier resolved)
231231

232232
private sealed class ParsedModule
233233
{
234-
private readonly Esprima.Ast.Module? _textModule;
234+
private readonly Prepared<AstModule>? _textModule;
235235
private readonly (JsValue Json, string Location)? _jsonModule;
236236

237-
private ParsedModule(Esprima.Ast.Module? textModule, (JsValue Json, string Location)? jsonModule)
237+
private ParsedModule(in Prepared<AstModule> textModule)
238238
{
239239
_textModule = textModule;
240-
_jsonModule = jsonModule;
240+
}
241+
242+
private ParsedModule(JsValue json, string location)
243+
{
244+
_jsonModule = (json, location);
241245
}
242246

243247
public static ParsedModule TextModule(string script, string location)
244-
=> new(Engine.PrepareModule(script, location), null);
248+
=> new(Engine.PrepareModule(script, location));
245249

246250
public static ParsedModule JsonModule(string json, string location)
247-
=> new(null, (ParseJson(json), location));
251+
=> new(ParseJson(json), location);
248252

249253
private static JsValue ParseJson(string json)
250254
{
@@ -258,7 +262,7 @@ public Module ToModule(Engine engine)
258262
if (_jsonModule is not null)
259263
return ModuleFactory.BuildJsonModule(engine, _jsonModule.Value.Json, _jsonModule.Value.Location);
260264
if (_textModule is not null)
261-
return ModuleFactory.BuildSourceTextModule(engine, _textModule);
265+
return ModuleFactory.BuildSourceTextModule(engine, _textModule.Value);
262266
throw new InvalidOperationException("Unexpected state - no module type available");
263267
}
264268
}

Jint.Tests.PublicInterface/ShadowRealmTests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ public void CanReuseScriptWithShadowRealm()
8383
var shadowRealm2 = engine.Intrinsics.ShadowRealm.Construct();
8484
shadowRealm2.SetValue("message", "realm 2");
8585

86-
var parser = new Parser();
87-
var script = parser.ParseScript("(function hello() {return \"hello \" + message})();");
86+
var script = Engine.PrepareScript("(function hello() {return \"hello \" + message})();");
8887

8988
// Act & Assert
9089
Assert.Equal("hello engine", engine.Evaluate(script));

Jint.Tests.Test262/State.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ public static partial class State
88
/// <summary>
99
/// Pre-compiled scripts for faster execution.
1010
/// </summary>
11-
public static readonly Dictionary<string, Script> Sources = new(StringComparer.OrdinalIgnoreCase);
11+
public static readonly Dictionary<string, Prepared<Script>> Sources = new(StringComparer.OrdinalIgnoreCase);
1212
}

Jint.Tests.Test262/Test262Test.cs

+10-4
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ private Engine BuildTestExecutor(Test262File file)
3737
throw new Exception("only script parsing supported");
3838
}
3939

40-
var options = new ParserOptions { RegExpParseMode = RegExpParseMode.AdaptToInterpreted, Tolerant = false };
41-
var parser = new Parser(options);
42-
var script = parser.ParseScript(args.At(0).AsString());
40+
var script = Engine.PrepareScript(args.At(0).AsString(), options: new ScriptPreparationOptions
41+
{
42+
ParsingOptions = ScriptParsingOptions.Default with { CompileRegex = false, Tolerant = false },
43+
});
4344

4445
return engine.Evaluate(script);
4546
}), true, true, true));
@@ -93,7 +94,12 @@ private static void ExecuteTest(Engine engine, Test262File file)
9394
}
9495
else
9596
{
96-
engine.Execute(new Parser().ParseScript(file.Program, source: file.FileName));
97+
var script = Engine.PrepareScript(file.Program, source: file.FileName, options: new ScriptPreparationOptions
98+
{
99+
ParsingOptions = ScriptParsingOptions.Default with { CompileRegex = false, Tolerant = false },
100+
});
101+
102+
engine.Execute(script);
97103
}
98104
}
99105

Jint.Tests/Runtime/EngineTests.ScriptPreparation.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ public partial class EngineTests
1212
public void ScriptPreparationAcceptsReturnOutsideOfFunctions()
1313
{
1414
var preparedScript = Engine.PrepareScript("return 1;");
15-
Assert.IsType<ReturnStatement>(preparedScript.Body[0]);
15+
Assert.IsType<ReturnStatement>(preparedScript.Program.Body[0]);
1616
}
1717

1818
[Fact]
1919
public void CanPreCompileRegex()
2020
{
2121
var script = Engine.PrepareScript("var x = /[cgt]/ig; var y = /[cgt]/ig; 'g'.match(x).length;");
22-
var declaration = Assert.IsType<VariableDeclaration>(script.Body[0]);
22+
var declaration = Assert.IsType<VariableDeclaration>(script.Program.Body[0]);
2323
var init = Assert.IsType<RegExpLiteral>(declaration.Declarations[0].Init);
24-
var regex = Assert.IsType<Regex>(init.AssociatedData);
24+
var regex = Assert.IsType<Regex>(init.Value);
2525
Assert.Equal("[cgt]", regex.ToString());
2626
Assert.Equal(RegexOptions.Compiled, regex.Options & RegexOptions.Compiled);
2727

@@ -32,7 +32,7 @@ public void CanPreCompileRegex()
3232
public void ScriptPreparationFoldsConstants()
3333
{
3434
var preparedScript = Engine.PrepareScript("return 1 + 2;");
35-
var returnStatement = Assert.IsType<ReturnStatement>(preparedScript.Body[0]);
35+
var returnStatement = Assert.IsType<ReturnStatement>(preparedScript.Program.Body[0]);
3636
var constant = Assert.IsType<JintConstantExpression>(returnStatement.Argument?.AssociatedData);
3737
Assert.Equal(3, constant.GetValue(null!));
3838

@@ -43,7 +43,7 @@ public void ScriptPreparationFoldsConstants()
4343
public void ScriptPreparationOptimizesNegatingUnaryExpression()
4444
{
4545
var preparedScript = Engine.PrepareScript("-1");
46-
var expression = Assert.IsType<ExpressionStatement>(preparedScript.Body[0]);
46+
var expression = Assert.IsType<ExpressionStatement>(preparedScript.Program.Body[0]);
4747
var unaryExpression = Assert.IsType<UnaryExpression>(expression.Expression);
4848
var constant = Assert.IsType<JintConstantExpression>(unaryExpression.AssociatedData);
4949

@@ -55,7 +55,7 @@ public void ScriptPreparationOptimizesNegatingUnaryExpression()
5555
public void ScriptPreparationOptimizesConstantReturn()
5656
{
5757
var preparedScript = Engine.PrepareScript("return false;");
58-
var statement = Assert.IsType<ReturnStatement>(preparedScript.Body[0]);
58+
var statement = Assert.IsType<ReturnStatement>(preparedScript.Program.Body[0]);
5959
var returnStatement = Assert.IsType<ConstantStatement>(statement.AssociatedData);
6060

6161
var builtStatement = JintStatement.Build(statement);

Jint.Tests/Runtime/EngineTests.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1296,8 +1296,8 @@ public void ShouldExecuteDromaeoBase64()
12961296
public void ShouldExecuteKnockoutWithoutErrorWhetherTolerantOrIntolerant()
12971297
{
12981298
var content = GetEmbeddedFile("knockout-3.4.0.js");
1299-
_engine.Execute(content, new ParserOptions { Tolerant = true });
1300-
_engine.Execute(content, new ParserOptions { Tolerant = false });
1299+
_engine.Execute(content, new ScriptParsingOptions { Tolerant = true });
1300+
_engine.Execute(content, new ScriptParsingOptions { Tolerant = false });
13011301
}
13021302

13031303
[Fact]
@@ -1314,7 +1314,7 @@ public void ShouldNotAllowDuplicateProtoProperty()
13141314
{
13151315
var code = "if({ __proto__: [], __proto__:[] } instanceof Array) {}";
13161316

1317-
Exception ex = Assert.Throws<ParseErrorException>(() => _engine.Execute(code, new ParserOptions { Tolerant = false }));
1317+
Exception ex = Assert.Throws<ParseErrorException>(() => _engine.Execute(code, new ScriptParsingOptions { Tolerant = false }));
13181318
Assert.Contains("Duplicate __proto__ fields are not allowed in object literals", ex.Message);
13191319

13201320
ex = Assert.Throws<JavaScriptException>(() => _engine.Execute($"eval('{code}')"));
@@ -2944,7 +2944,7 @@ public void ExecuteWithSourceShouldTriggerBeforeEvaluateEvent()
29442944
public void ExecuteWithParserOptionsShouldTriggerBeforeEvaluateEvent()
29452945
{
29462946
TestBeforeEvaluateEvent(
2947-
(engine, code) => engine.Execute(code, ParserOptions.Default),
2947+
(engine, code) => engine.Execute(code, ScriptParsingOptions.Default),
29482948
expectedSource: "<anonymous>"
29492949
);
29502950
}
@@ -2953,7 +2953,7 @@ public void ExecuteWithParserOptionsShouldTriggerBeforeEvaluateEvent()
29532953
public void ExecuteWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
29542954
{
29552955
TestBeforeEvaluateEvent(
2956-
(engine, code) => engine.Execute(code, "mysource", ParserOptions.Default),
2956+
(engine, code) => engine.Execute(code, "mysource", ScriptParsingOptions.Default),
29572957
expectedSource: "mysource"
29582958
);
29592959
}
@@ -2980,7 +2980,7 @@ public void EvaluateWithSourceShouldTriggerBeforeEvaluateEvent()
29802980
public void EvaluateWithParserOptionsShouldTriggerBeforeEvaluateEvent()
29812981
{
29822982
TestBeforeEvaluateEvent(
2983-
(engine, code) => engine.Evaluate(code, ParserOptions.Default),
2983+
(engine, code) => engine.Evaluate(code, ScriptParsingOptions.Default),
29842984
expectedSource: "<anonymous>"
29852985
);
29862986
}
@@ -2989,7 +2989,7 @@ public void EvaluateWithParserOptionsShouldTriggerBeforeEvaluateEvent()
29892989
public void EvaluateWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
29902990
{
29912991
TestBeforeEvaluateEvent(
2992-
(engine, code) => engine.Evaluate(code, "mysource", ParserOptions.Default),
2992+
(engine, code) => engine.Evaluate(code, "mysource", ScriptParsingOptions.Default),
29932993
expectedSource: "mysource"
29942994
);
29952995
}

Jint.Tests/Runtime/ErrorTests.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,12 @@ public void StackTraceCollectedForImmediatelyInvokedFunctionExpression()
294294
return item;
295295
})(getItem);";
296296

297-
var parserOptions = new ParserOptions
297+
var parsingOptions = new ScriptParsingOptions
298298
{
299-
RegExpParseMode = RegExpParseMode.AdaptToInterpreted,
299+
CompileRegex = false,
300300
Tolerant = true
301301
};
302-
var ex = Assert.Throws<JavaScriptException>(() => engine.Execute(script, "get-item.js", parserOptions));
302+
var ex = Assert.Throws<JavaScriptException>(() => engine.Execute(script, "get-item.js", parsingOptions));
303303

304304
const string expected = @"Error: Cannot read property '5' of null
305305
at getItem (items, itemIndex) get-item.js:2:22
@@ -383,11 +383,11 @@ public void ErrorsHaveCorrectConstructor(string type)
383383
[Fact]
384384
public void CallStackWorksWithRecursiveCalls()
385385
{
386-
static ParserOptions CreateParserOptions()
386+
static ScriptParsingOptions CreateParsingOptions()
387387
{
388-
return new ParserOptions
388+
return new ScriptParsingOptions
389389
{
390-
RegExpParseMode = RegExpParseMode.AdaptToInterpreted,
390+
CompileRegex = false,
391391
Tolerant = true
392392
};
393393
}
@@ -406,13 +406,13 @@ static ParserOptions CreateParserOptions()
406406
nuм -= 3;",
407407
_ => throw new FileNotFoundException($"File '{path}' not exist.", path)
408408
};
409-
engine.Execute(content, path, CreateParserOptions());
409+
engine.Execute(content, path, CreateParsingOptions());
410410
}));
411411
engine.Execute(
412412
@"var num = 5;
413413
executeFile(""first-file.js"");",
414414
"main-file.js",
415-
CreateParserOptions()
415+
CreateParsingOptions()
416416
);
417417
});
418418

0 commit comments

Comments
 (0)