diff --git a/.editorconfig b/.editorconfig index 758216668c..b01871a449 100644 --- a/.editorconfig +++ b/.editorconfig @@ -36,6 +36,48 @@ indent_size = 2 # IDE0055: Fix formatting dotnet_diagnostic.IDE0055.severity = warning +# Error CA1051 : Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = none + +# Error CA1720 : Identifiers should not contain type names +dotnet_diagnostic.CA1720.severity = none + +# Error CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1711.severity = none + +# Error CA1710: Identifiers should have correct suffix +dotnet_diagnostic.CA1710.severity = none + +# Error CA1716: Identifiers should have correct suffix +dotnet_diagnostic.CA1716.severity = none + +# Error MA0026: TODO +dotnet_diagnostic.MA0026.severity = none + +# Error MA0048 : File name must match type name +dotnet_diagnostic.MA0048.severity = none + +# Error MA0016 : Prefer using collection abstraction instead of implementation +dotnet_diagnostic.MA0016.severity = none + +# Error MA0017 : Abstract types should not have public or internal constructors +dotnet_diagnostic.MA0017.severity = none + +# Error MA0051 : Method is too long +dotnet_diagnostic.MA0051.severity = none + +# Error MA0046 : The delegate must return void +dotnet_diagnostic.MA0046.severity = none + +# Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +dotnet_diagnostic.MA0097.severity = none + +# Error MA0025 : Implement the functionality (or raise NotSupportedException or PlatformNotSupportedException) +dotnet_diagnostic.MA0025.severity = none + +# Error MA0091 : Sender parameter should be 'this' for instance events +dotnet_diagnostic.MA0091.severity = none + # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..d4159e07cc --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + + true + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000000..62210fa4e6 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,33 @@ + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Jint.Benchmark/EngineComparisonBenchmark.cs b/Jint.Benchmark/EngineComparisonBenchmark.cs index ecaf7d2aa6..eab0dfebe4 100644 --- a/Jint.Benchmark/EngineComparisonBenchmark.cs +++ b/Jint.Benchmark/EngineComparisonBenchmark.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; using Esprima; using Esprima.Ast; @@ -7,6 +8,7 @@ namespace Jint.Benchmark; [RankColumn] [MemoryDiagnoser] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByParams)] [HideColumns("Error", "Gen0", "Gen1", "Gen2")] [BenchmarkCategory("EngineComparison")] @@ -91,6 +93,9 @@ public void NilJS() public void YantraJS() { var engine = new YantraJS.Core.JSContext(); - engine.Eval(_files[FileName]); + // By default YantraJS is strict mode only, in strict mode + // we need to pass `this` explicitly in global context + // if script is expecting global context as `this` + engine.Eval(_files[FileName], null, engine); } } diff --git a/Jint.Benchmark/EngineConstructionBenchmark.cs b/Jint.Benchmark/EngineConstructionBenchmark.cs index 56522b1307..dc1d79fd6b 100644 --- a/Jint.Benchmark/EngineConstructionBenchmark.cs +++ b/Jint.Benchmark/EngineConstructionBenchmark.cs @@ -1,24 +1,36 @@ using BenchmarkDotNet.Attributes; using Esprima; using Esprima.Ast; +using Jint.Native; namespace Jint.Benchmark; [MemoryDiagnoser] public class EngineConstructionBenchmark { - private readonly Script _program; + private Script _program; + private Script _simple; - public EngineConstructionBenchmark() + [GlobalSetup] + public void GlobalSetup() { var parser = new JavaScriptParser(); - _program = parser.ParseScript("return [].length + ''.length"); + _program = parser.ParseScript("([].length + ''.length)"); + _simple = parser.ParseScript("1"); + new Engine().Evaluate(_program); } [Benchmark] - public double BuildEngine() + public Engine BuildEngine() { var engine = new Engine(); - return engine.Evaluate(_program).AsNumber(); + return engine; + } + + [Benchmark] + public JsValue EvaluateSimple() + { + var engine = new Engine(); + return engine.Evaluate(_simple); } } diff --git a/Jint.Benchmark/Jint.Benchmark.csproj b/Jint.Benchmark/Jint.Benchmark.csproj index a29e3894d7..dc63a5db7b 100644 --- a/Jint.Benchmark/Jint.Benchmark.csproj +++ b/Jint.Benchmark/Jint.Benchmark.csproj @@ -1,4 +1,4 @@ - + net6.0 Exe @@ -24,10 +24,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/Jint.Benchmark/README.md b/Jint.Benchmark/README.md index a2867e9a37..2f1a8eaf8b 100644 --- a/Jint.Benchmark/README.md +++ b/Jint.Benchmark/README.md @@ -9,91 +9,87 @@ dotnet run -c Release --allCategories EngineComparison * tests are run in global engine strict mode, as YantraJS always uses strict mode which improves performance * `Jint` and `Jint_ParsedScript` shows the difference between always parsing the script source file and reusing parsed `Script` instance. -Last updated 2023-03-26 +Last updated 2023-11-05 * Jint main -* Jurassic 3.2.6 -* NiL.JS 2.5.1650 -* YantraJS.Core 1.2.117 +* Jurassic 3.2.7 +* NiL.JS 2.5.1674 +* YantraJS.Core 1.2.203 +``` -``` ini - -BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1483/22H2/2022Update/SunValley2) +BenchmarkDotNet v0.13.10, Windows 11 (10.0.23580.1000) AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores -.NET SDK=7.0.202 - [Host] : .NET 6.0.15 (6.0.1523.11507), X64 RyuJIT AVX2 - DefaultJob : .NET 6.0.15 (6.0.1523.11507), X64 RyuJIT AVX2 +.NET SDK 8.0.100-rc.2.23502.2 + [Host] : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2 + DefaultJob : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2 ``` -| Method | FileName | Mean | StdDev | Rank | Allocated | -|------------------ |--------------------- |-----------------:|---------------:|-----:|--------------:| -| **Jint** | **array-stress** | **10,799.249 μs** | **24.4948 μs** | **4** | **7473326 B** | -| Jint_ParsedScript | array-stress | 10,568.342 μs | 44.6133 μs | 3 | 7446494 B | -| Jurassic | array-stress | 11,280.104 μs | 27.9486 μs | 5 | 11926463 B | -| NilJS | array-stress | 5,600.761 μs | 74.2339 μs | 2 | 4241527 B | -| YantraJS | array-stress | 4,891.973 μs | 18.8130 μs | 1 | 6518316 B | -| | | | | | | -| **Jint** | **dromaeo-3d-cube** | **25,234.322 μs** | **28.3103 μs** | **4** | **6307004 B** | -| Jint_ParsedScript | dromaeo-3d-cube | 24,227.022 μs | 30.4230 μs | 3 | 6000208 B | -| Jurassic | dromaeo-3d-cube | 40,155.874 μs | 83.9446 μs | 5 | 10925739 B | -| NilJS | dromaeo-3d-cube | 9,203.608 μs | 18.2137 μs | 2 | 4671638 B | -| YantraJS | dromaeo-3d-cube | 4,478.599 μs | 35.6857 μs | 1 | 8885293 B | -| | | | | | | -| **Jint** | **dromaeo-core-eval** | **5,496.472 μs** | **20.8861 μs** | **2** | **359439 B** | -| Jint_ParsedScript | dromaeo-core-eval | 5,555.814 μs | 29.8913 μs | 2 | 339647 B | -| Jurassic | dromaeo-core-eval | 13,739.640 μs | 52.5257 μs | 4 | 2971062 B | -| NilJS | dromaeo-core-eval | 2,099.576 μs | 13.3939 μs | 1 | 1637011 B | -| YantraJS | dromaeo-core-eval | 8,068.664 μs | 41.8267 μs | 3 | 37131162 B | -| | | | | | | -| **Jint** | **dromaeo-object-array** | **68,157.350 μs** | **106.5991 μs** | **4** | **103974805 B** | -| Jint_ParsedScript | dromaeo-object-array | 70,379.819 μs | 412.9971 μs | 5 | 103928047 B | -| Jurassic | dromaeo-object-array | 41,917.036 μs | 147.6689 μs | 1 | 26433545 B | -| NilJS | dromaeo-object-array | 50,941.149 μs | 116.1891 μs | 3 | 16518497 B | -| YantraJS | dromaeo-object-array | 43,952.908 μs | 52.7966 μs | 2 | 25538081 B | -| | | | | | | -| **Jint** | **droma(...)egexp [21]** | **202,513.639 μs** | **6,280.4683 μs** | **2** | **169538072 B** | -| Jint_ParsedScript | droma(...)egexp [21] | 194,203.576 μs | 2,648.5440 μs | 1 | 176401621 B | -| Jurassic | droma(...)egexp [21] | 673,099.103 μs | 19,449.6381 μs | 4 | 845159056 B | -| NilJS | droma(...)egexp [21] | 498,404.131 μs | 9,389.0173 μs | 3 | 785342384 B | -| YantraJS | droma(...)egexp [21] | 1,021,021.213 μs | 10,171.5386 μs | 5 | 965363992 B | -| | | | | | | -| **Jint** | **droma(...)tring [21]** | **513,139.116 μs** | **21,598.0545 μs** | **2** | **1353627632 B** | -| Jint_ParsedScript | droma(...)tring [21] | 598,346.054 μs | 62,241.8961 μs | 4 | 1353603760 B | -| Jurassic | droma(...)tring [21] | 547,882.282 μs | 15,131.8012 μs | 3 | 1492956848 B | -| NilJS | droma(...)tring [21] | 380,880.402 μs | 17,953.6040 μs | 1 | 1410785672 B | -| YantraJS | droma(...)tring [21] | 3,146,801.586 μs | 75,534.4740 μs | 5 | 16097426568 B | -| | | | | | | -| **Jint** | **droma(...)ase64 [21]** | **54,972.605 μs** | **123.8465 μs** | **3** | **6764151 B** | -| Jint_ParsedScript | droma(...)ase64 [21] | 53,534.261 μs | 272.9451 μs | 2 | 6665682 B | -| Jurassic | droma(...)ase64 [21] | 69,111.283 μs | 166.1184 μs | 4 | 76105181 B | -| NilJS | droma(...)ase64 [21] | 40,131.392 μs | 385.5791 μs | 1 | 20074818 B | -| YantraJS | droma(...)ase64 [21] | 108,860.979 μs | 697.7800 μs | 5 | 778591469 B | -| | | | | | | -| **Jint** | **evaluation** | **28.773 μs** | **0.0494 μs** | **2** | **36792 B** | -| Jint_ParsedScript | evaluation | 11.296 μs | 0.0295 μs | 1 | 27072 B | -| Jurassic | evaluation | 1,291.843 μs | 3.3295 μs | 5 | 430510 B | -| NilJS | evaluation | 45.473 μs | 0.1385 μs | 3 | 24032 B | -| YantraJS | evaluation | 120.995 μs | 0.3959 μs | 4 | 177876 B | -| | | | | | | -| **Jint** | **linq-js** | **1,774.118 μs** | **8.6162 μs** | **2** | **1303058 B** | -| Jint_ParsedScript | linq-js | 90.567 μs | 0.2234 μs | 1 | 213218 B | -| Jurassic | linq-js | 39,199.309 μs | 614.5447 μs | 4 | 9525761 B | -| NilJS | linq-js | 7,630.382 μs | 11.3679 μs | 3 | 4226480 B | -| YantraJS | linq-js | NA | NA | ? | - | -| | | | | | | -| **Jint** | **minimal** | **4.937 μs** | **0.0103 μs** | **3** | **14664 B** | -| Jint_ParsedScript | minimal | 3.124 μs | 0.0123 μs | 1 | 13168 B | -| Jurassic | minimal | 238.384 μs | 0.6775 μs | 5 | 395506 B | -| NilJS | minimal | 4.751 μs | 0.0103 μs | 2 | 4928 B | -| YantraJS | minimal | 117.579 μs | 0.5920 μs | 4 | 173770 B | -| | | | | | | -| **Jint** | **stopwatch** | **368,697.936 μs** | **1,043.7863 μs** | **4** | **52946080 B** | -| Jint_ParsedScript | stopwatch | 408,149.533 μs | 1,052.2644 μs | 5 | 52912288 B | -| Jurassic | stopwatch | 211,731.362 μs | 544.2606 μs | 2 | 160704435 B | -| NilJS | stopwatch | 238,188.624 μs | 480.6546 μs | 3 | 76378157 B | -| YantraJS | stopwatch | 78,043.391 μs | 228.5660 μs | 1 | 264535415 B | - -Benchmarks with issues: -EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=linq-js] \ No newline at end of file +| Method | FileName | Mean | StdDev | Rank | Allocated | +|------------------ |--------------------- |-----------------:|----------------:|-----:|---------------:| +| NilJS | array-stress | 7,618.410 μs | 9.6261 μs | 1 | 4533.76 KB | +| YantraJS | array-stress | 7,634.348 μs | 154.1204 μs | 1 | 8080.23 KB | +| Jint | array-stress | 10,189.119 μs | 100.0517 μs | 2 | 7112.02 KB | +| Jint_ParsedScript | array-stress | 10,829.737 μs | 29.7168 μs | 3 | 7090.84 KB | +| Jurassic | array-stress | 13,478.434 μs | 134.6721 μs | 4 | 11646.94 KB | +| | | | | | | +| YantraJS | dromaeo-3d-cube | 6,903.873 μs | 29.0353 μs | 1 | 11426.85 KB | +| NilJS | dromaeo-3d-cube | 11,520.118 μs | 12.9132 μs | 2 | 4694.63 KB | +| Jint_ParsedScript | dromaeo-3d-cube | 25,403.787 μs | 76.2460 μs | 3 | 5934.53 KB | +| Jint | dromaeo-3d-cube | 26,214.383 μs | 19.9661 μs | 4 | 6191.84 KB | +| Jurassic | dromaeo-3d-cube | 49,227.408 μs | 112.1937 μs | 5 | 10670.73 KB | +| | | | | | | +| NilJS | dromaeo-core-eval | 2,688.083 μs | 7.5997 μs | 1 | 1598.78 KB | +| Jint_ParsedScript | dromaeo-core-eval | 5,404.308 μs | 11.9911 μs | 2 | 333.82 KB | +| Jint | dromaeo-core-eval | 5,745.384 μs | 10.9482 μs | 3 | 350.86 KB | +| YantraJS | dromaeo-core-eval | 10,070.410 μs | 26.0609 μs | 4 | 70662.5 KB | +| Jurassic | dromaeo-core-eval | 12,145.734 μs | 44.3013 μs | 5 | 2884.85 KB | +| | | | | | | +| Jurassic | dromaeo-object-array | 50,713.787 μs | 105.2239 μs | 1 | 25814.89 KB | +| YantraJS | dromaeo-object-array | 63,932.521 μs | 830.2901 μs | 2 | 29485.54 KB | +| Jint | dromaeo-object-array | 65,413.838 μs | 508.2830 μs | 3 | 100793.32 KB | +| Jint_ParsedScript | dromaeo-object-array | 66,591.951 μs | 140.1417 μs | 4 | 100754.12 KB | +| NilJS | dromaeo-object-array | 75,914.511 μs | 126.7804 μs | 5 | 17698.17 KB | +| | | | | | | +| Jint | droma(...)egexp [21] | 285,685.732 μs | 7,488.3067 μs | 1 | 170207.95 KB | +| Jint_ParsedScript | droma(...)egexp [21] | 289,626.408 μs | 1,896.8981 μs | 1 | 167049.52 KB | +| NilJS | droma(...)egexp [21] | 614,280.120 μs | 7,612.4833 μs | 2 | 766193.08 KB | +| Jurassic | droma(...)egexp [21] | 807,573.300 μs | 14,825.6620 μs | 3 | 825805.82 KB | +| YantraJS | droma(...)egexp [21] | 1,229,769.600 μs | 16,340.3960 μs | 4 | 1254750.68 KB | +| | | | | | | +| NilJS | droma(...)tring [21] | 426,125.000 μs | 7,684.4889 μs | 1 | 1377812.93 KB | +| Jint_ParsedScript | droma(...)tring [21] | 608,204.631 μs | 58,027.2350 μs | 2 | 1322143.68 KB | +| Jint | droma(...)tring [21] | 617,734.935 μs | 39,573.9870 μs | 2 | 1322326.65 KB | +| Jurassic | droma(...)tring [21] | 619,546.013 μs | 9,041.0940 μs | 2 | 1458038.6 KB | +| YantraJS | droma(...)tring [21] | 4,197,768.766 μs | 343,801.2590 μs | 3 | 29822200.57 KB | +| | | | | | | +| NilJS | droma(...)ase64 [21] | 49,243.377 μs | 108.4628 μs | 1 | 19604.8 KB | +| Jint_ParsedScript | droma(...)ase64 [21] | 59,677.164 μs | 177.0683 μs | 2 | 6722.79 KB | +| Jint | droma(...)ase64 [21] | 63,301.808 μs | 318.1747 μs | 3 | 6806.58 KB | +| YantraJS | droma(...)ase64 [21] | 78,283.727 μs | 991.5219 μs | 4 | 1492785.6 KB | +| Jurassic | droma(...)ase64 [21] | 83,485.030 μs | 198.9519 μs | 5 | 74319.52 KB | +| | | | | | | +| Jint_ParsedScript | evaluation | 13.500 μs | 0.0247 μs | 1 | 27.48 KB | +| Jint | evaluation | 33.419 μs | 0.0412 μs | 2 | 36.05 KB | +| NilJS | evaluation | 59.750 μs | 0.1541 μs | 3 | 23.47 KB | +| YantraJS | evaluation | 159.699 μs | 0.6218 μs | 4 | 931 KB | +| Jurassic | evaluation | 1,582.755 μs | 2.5897 μs | 5 | 420.41 KB | +| | | | | | | +| Jint_ParsedScript | linq-js | 111.636 μs | 0.2424 μs | 1 | 226.52 KB | +| YantraJS | linq-js | 461.542 μs | 0.4869 μs | 2 | 1453.9 KB | +| Jint | linq-js | 2,208.746 μs | 12.8004 μs | 3 | 1276.1 KB | +| NilJS | linq-js | 10,450.782 μs | 73.1973 μs | 4 | 4127.79 KB | +| Jurassic | linq-js | 44,781.543 μs | 394.7200 μs | 5 | 9302.34 KB | +| | | | | | | +| Jint_ParsedScript | minimal | 3.382 μs | 0.0078 μs | 1 | 12.99 KB | +| Jint | minimal | 5.334 μs | 0.0174 μs | 2 | 14.38 KB | +| NilJS | minimal | 5.891 μs | 0.0096 μs | 3 | 4.81 KB | +| YantraJS | minimal | 153.890 μs | 0.8023 μs | 4 | 925.48 KB | +| Jurassic | minimal | 294.178 μs | 0.3525 μs | 5 | 386.24 KB | +| | | | | | | +| YantraJS | stopwatch | 112,865.841 μs | 223.3235 μs | 1 | 224277.02 KB | +| Jurassic | stopwatch | 254,529.440 μs | 839.9853 μs | 2 | 156937.17 KB | +| NilJS | stopwatch | 297,948.830 μs | 1,345.9144 μs | 3 | 97363.1 KB | +| Jint_ParsedScript | stopwatch | 394,136.840 μs | 3,882.0490 μs | 4 | 53015.98 KB | +| Jint | stopwatch | 457,053.953 μs | 1,630.9870 μs | 5 | 53045.23 KB | diff --git a/Jint.Benchmark/ShadowRealmBenchmark.cs b/Jint.Benchmark/ShadowRealmBenchmark.cs new file mode 100644 index 0000000000..754a4916a2 --- /dev/null +++ b/Jint.Benchmark/ShadowRealmBenchmark.cs @@ -0,0 +1,61 @@ +using BenchmarkDotNet.Attributes; +using Esprima.Ast; + +namespace Jint.Benchmark; + +[MemoryDiagnoser] +[BenchmarkCategory("ShadowRealm")] +public class ShadowRealmBenchmark +{ + private const string sourceCode = @" +(function (){return 'some string'})(); +"; + + private Engine engine; + private Script parsedScript; + + [GlobalSetup] + public void Setup() + { + engine = new Engine(); + parsedScript = Engine.PrepareScript(sourceCode); + } + + [Benchmark] + public void ReusingEngine() + { + engine.Evaluate(sourceCode); + } + + [Benchmark] + public void NewEngineInstance() + { + new Engine().Evaluate(sourceCode); + } + + [Benchmark] + public void ShadowRealm() + { + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.Evaluate(sourceCode); + } + + [Benchmark] + public void ReusingEngine_ParsedScript() + { + engine.Evaluate(parsedScript); + } + + [Benchmark] + public void NewEngineInstance_ParsedScript() + { + new Engine().Evaluate(parsedScript); + } + + [Benchmark] + public void ShadowRealm_ParsedScript() + { + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.Evaluate(parsedScript); + } +} diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index d52bdfbd41..fda7d8dc71 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -1,97 +1,89 @@ using System.Diagnostics; using System.Reflection; using Esprima; +using Jint; using Jint.Native; using Jint.Native.Json; using Jint.Runtime; -namespace Jint.Repl +var engine = new Engine(cfg => cfg + .AllowClr() +); + +engine + .SetValue("print", new Action(Console.WriteLine)) + .SetValue("load", new Func( + path => engine.Evaluate(File.ReadAllText(path))) + ); + +var filename = args.Length > 0 ? args[0] : ""; +if (!string.IsNullOrEmpty(filename)) { - internal static class Program + if (!File.Exists(filename)) { - private static void Main(string[] args) - { - var engine = new Engine(cfg => cfg - .AllowClr() - ); - - engine - .SetValue("print", new Action(Console.WriteLine)) - .SetValue("load", new Func( - path => engine.Evaluate(File.ReadAllText(path))) - ); + Console.WriteLine("Could not find file: {0}", filename); + } - var filename = args.Length > 0 ? args[0] : ""; - if (!string.IsNullOrEmpty(filename)) - { - if (!File.Exists(filename)) - { - Console.WriteLine("Could not find file: {0}", filename); - } + var script = File.ReadAllText(filename); + engine.Evaluate(script, "repl"); + return; +} - var script = File.ReadAllText(filename); - engine.Evaluate(script, "repl"); - return; - } +var assembly = Assembly.GetExecutingAssembly(); +var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); +var version = fvi.FileVersion; - var assembly = Assembly.GetExecutingAssembly(); - var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); - var version = fvi.FileVersion; +Console.WriteLine("Welcome to Jint ({0})", version); +Console.WriteLine("Type 'exit' to leave, " + + "'print()' to write on the console, " + + "'load()' to load scripts."); +Console.WriteLine(); - Console.WriteLine("Welcome to Jint ({0})", version); - Console.WriteLine("Type 'exit' to leave, " + - "'print()' to write on the console, " + - "'load()' to load scripts."); - Console.WriteLine(); +var defaultColor = Console.ForegroundColor; +var parserOptions = new ParserOptions +{ + Tolerant = true, + RegExpParseMode = RegExpParseMode.AdaptToInterpreted +}; - var defaultColor = Console.ForegroundColor; - var parserOptions = new ParserOptions - { - Tolerant = true, - RegExpParseMode = RegExpParseMode.AdaptToInterpreted - }; +var serializer = new JsonSerializer(engine); - var serializer = new JsonSerializer(engine); +while (true) +{ + Console.ForegroundColor = defaultColor; + Console.Write("jint> "); + var input = Console.ReadLine(); + if (input is "exit" or ".exit") + { + return; + } - while (true) + try + { + var result = engine.Evaluate(input, parserOptions); + JsValue str = result; + if (!result.IsPrimitive() && result is not IPrimitiveInstance) + { + str = serializer.Serialize(result, JsValue.Undefined, " "); + if (str == JsValue.Undefined) { - Console.ForegroundColor = defaultColor; - Console.Write("jint> "); - var input = Console.ReadLine(); - if (input is "exit" or ".exit") - { - return; - } - - try - { - var result = engine.Evaluate(input, parserOptions); - JsValue str = result; - if (!result.IsPrimitive() && result is not IPrimitiveInstance) - { - str = serializer.Serialize(result, JsValue.Undefined, " "); - if (str == JsValue.Undefined) - { - str = result; - } - } - else if (result.IsString()) - { - str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined); - } - Console.WriteLine(str); - } - catch (JavaScriptException je) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(je.ToString()); - } - catch (Exception e) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e.Message); - } + str = result; } } + else if (result.IsString()) + { + str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined); + } + Console.WriteLine(str); + } + catch (JavaScriptException je) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(je.ToString()); + } + catch (Exception e) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e.Message); } } diff --git a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj index e7a235e631..68d2fd1997 100644 --- a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj +++ b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -16,10 +16,9 @@ - - - - + + + diff --git a/Jint.Tests.CommonScripts/SunSpiderTests.cs b/Jint.Tests.CommonScripts/SunSpiderTests.cs index 0c4d2e27c7..2025738ade 100644 --- a/Jint.Tests.CommonScripts/SunSpiderTests.cs +++ b/Jint.Tests.CommonScripts/SunSpiderTests.cs @@ -10,7 +10,7 @@ private static void RunTest(string source) { var engine = new Engine() .SetValue("log", new Action(Console.WriteLine)) - .SetValue("assert", new Action((condition, message) => Assert.True(condition, message))); + .SetValue("assert", new Action((condition, message) => Assert.That(condition, message))); try { diff --git a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj index 272dd0c4dc..e077c88377 100644 --- a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj +++ b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj @@ -21,14 +21,13 @@ - - - - - - - - + + + + + + + diff --git a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs index 5143642fa3..9ceb48f621 100644 --- a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs +++ b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs @@ -3,7 +3,6 @@ using Jint.Native; using Jint.Native.Function; using Jint.Runtime; -using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Tests.PublicInterface; @@ -57,20 +56,6 @@ public void CanChangeMaxStatementValue() Assert.Equal(321, constraint.MaxStatements); } - [Fact] - public void CanConstructArrayInstanceFromDescriptorArray() - { - var descriptors = new[] - { - new PropertyDescriptor(42, writable: false, enumerable: false, configurable: false), - }; - - var engine = new Engine(); - var array = new JsArray(engine, descriptors); - Assert.Equal(1L, array.Length); - Assert.Equal(42, array[0]); - } - [Fact] public void CanGetPropertyDescriptor() { @@ -97,15 +82,6 @@ public void CanInjectConstructedObjects() TestArrayAccess(engine, array1, "array1"); - var array3 = new JsArray(engine, new[] - { - new PropertyDescriptor(JsNumber.Create(1), true, true, true), - new PropertyDescriptor(JsNumber.Create(2), true, true, true), - new PropertyDescriptor(JsNumber.Create(3), true, true, true), - }); - engine.SetValue("array3", array3); - TestArrayAccess(engine, array3, "array3"); - engine.SetValue("obj", obj); Assert.Equal("test", engine.Evaluate("obj.name")); @@ -117,10 +93,6 @@ public void CanInjectConstructedObjects() Assert.Equal(0, engine.Evaluate("emptyArray.length")); Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("emptyArray", new JsArray(engine, Array.Empty())); - Assert.Equal(0, engine.Evaluate("emptyArray.length")); - Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("date", new JsDate(engine, new DateTime(2022, 10, 20))); Assert.Equal(2022, engine.Evaluate("date.getFullYear()")); } diff --git a/Jint.Tests.PublicInterface/ShadowRealmTests.cs b/Jint.Tests.PublicInterface/ShadowRealmTests.cs index ca98ac163e..f0d062d506 100644 --- a/Jint.Tests.PublicInterface/ShadowRealmTests.cs +++ b/Jint.Tests.PublicInterface/ShadowRealmTests.cs @@ -28,6 +28,70 @@ public void CanUseViaEngineMethods() Assert.Equal("John Doe", result); } + [Fact] + public void MultipleShadowRealmsDoNotInterfere() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "world"); + engine.Evaluate("function hello() {return message}"); + + Assert.Equal("world",engine.Evaluate("hello();")); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "realm 1"); + shadowRealm.Evaluate("function hello() {return message}"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "realm 2"); + shadowRealm2.Evaluate("function hello() {return message}"); + + // Act & Assert + Assert.Equal("realm 1", shadowRealm.Evaluate("hello();")); + Assert.Equal("realm 2", shadowRealm2.Evaluate("hello();")); + } + + [Fact] + public void MultipleShadowRealm_SettingGlobalVariable_DoNotInterfere() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "hello "); + engine.Evaluate("(function hello() {message += \"engine\"})();"); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "hello "); + shadowRealm.Evaluate("(function hello() {message += \"realm 1\"})();"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "hello "); + shadowRealm2.Evaluate("(function hello() {message += \"realm 2\"})();"); + + // Act & Assert + Assert.Equal("hello engine", engine.Evaluate("message")); + Assert.Equal("hello realm 1", shadowRealm.Evaluate("message")); + Assert.Equal("hello realm 2", shadowRealm2.Evaluate("message")); + } + + [Fact] + public void CanReuseScriptWithShadowRealm() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "engine"); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "realm 1"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "realm 2"); + + var parser = new Esprima.JavaScriptParser(); + var script = parser.ParseScript("(function hello() {return \"hello \" + message})();"); + + // Act & Assert + Assert.Equal("hello engine", engine.Evaluate(script)); + Assert.Equal("hello realm 1", shadowRealm.Evaluate(script)); + Assert.Equal("hello realm 2", shadowRealm2.Evaluate(script)); + } + private static string GetBasePath() { var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); diff --git a/Jint.Tests.Test262/Jint.Tests.Test262.csproj b/Jint.Tests.Test262/Jint.Tests.Test262.csproj index e7e7e5d8ab..c149935d98 100644 --- a/Jint.Tests.Test262/Jint.Tests.Test262.csproj +++ b/Jint.Tests.Test262/Jint.Tests.Test262.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -16,11 +16,10 @@ - - - - - + + + + diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 88fa50bd24..f6ac59bf09 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -1,5 +1,5 @@ { - "SuiteGitSha": "9437cab774ab2f22c5cb971b11b8512eca705721", + "SuiteGitSha": "6396ebde0316639292530460d1ef961fd9bbe0d4", //"SuiteDirectory": "//mnt/c/work/test262", "TargetPath": "./Generated", "Namespace": "Jint.Tests.Test262", @@ -46,6 +46,7 @@ // RegExp handling problems "built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js", "language/literals/regexp/u-case-mapping.js", + "built-ins/RegExp/lookahead-quantifier-match-groups.js", // requires investigation how to process complex function name evaluation for property "built-ins/Function/prototype/toString/method-computed-property-name.js", diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 549f914646..baf45fd5a6 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -24,14 +24,14 @@ - - - - - - - - + + + + + + + + diff --git a/Jint.Tests/Runtime/AwaitTests.cs b/Jint.Tests/Runtime/AwaitTests.cs index c1fa20d28e..f3a840e3cd 100644 --- a/Jint.Tests/Runtime/AwaitTests.cs +++ b/Jint.Tests/Runtime/AwaitTests.cs @@ -10,4 +10,72 @@ public void AwaitPropagationAgainstPrimitiveValue() result = result.UnwrapIfPromise(); Assert.Equal("1", result); } + + [Fact] + public void ShouldTaskConvertedToPromiseInJS() + { + Engine engine = new(); + engine.SetValue("callable", Callable); + var result = engine.Evaluate("callable().then(x=>x*2)"); + result = result.UnwrapIfPromise(); + Assert.Equal(2, result); + + static async Task Callable() + { + await Task.Delay(10); + Assert.True(true); + return 1; + } + } + + [Fact] + public void ShouldTaskCatchWhenCancelled() + { + Engine engine = new(); + CancellationTokenSource cancel = new(); + cancel.Cancel(); + engine.SetValue("token", cancel.Token); + engine.SetValue("callable", Callable); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))"); + result = result.UnwrapIfPromise(); + static async Task Callable(CancellationToken token) + { + await Task.FromCanceled(token); + } + } + + [Fact] + public void ShouldTaskCatchWhenThrowError() + { + Engine engine = new(); + engine.SetValue("callable", Callable); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))"); + + static async Task Callable() + { + await Task.Delay(10); + throw new Exception(); + } + } + + [Fact] + public void ShouldTaskAwaitCurrentStack() + { + //https://github.com/sebastienros/jint/issues/514#issuecomment-1507127509 + Engine engine = new(); + string log = ""; + engine.SetValue("myAsyncMethod", new Func(async () => + { + await Task.Delay(1000); + log += "1"; + })); + engine.SetValue("myAsyncMethod2", new Action(() => + { + log += "2"; + })); + engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();"); + Assert.Equal("12", log); + } } diff --git a/Jint.Tests/Runtime/DateTests.cs b/Jint.Tests/Runtime/DateTests.cs index 41d78646ab..6e94d7e144 100644 --- a/Jint.Tests/Runtime/DateTests.cs +++ b/Jint.Tests/Runtime/DateTests.cs @@ -107,4 +107,10 @@ public void CanUseMoment() var parsedDate = _engine.Evaluate("moment().format('yyyy')").ToString(); Assert.Equal(DateTime.Now.Year.ToString(),parsedDate); } + + [Fact] + public void CanParseEmptyDate() + { + Assert.True(double.IsNaN(_engine.Evaluate("Date.parse('')").AsNumber())); + } } diff --git a/Jint.Tests/Runtime/DestructuringTests.cs b/Jint.Tests/Runtime/DestructuringTests.cs index 4c1ac13cea..f62264cc7b 100644 --- a/Jint.Tests/Runtime/DestructuringTests.cs +++ b/Jint.Tests/Runtime/DestructuringTests.cs @@ -62,4 +62,10 @@ public void WithNestedRest() { _engine.Execute("return function([x, ...[y, ...z]]) { equal(1, x); equal(2, y); equal('3,4', z + ''); }([1, 2, 3, 4]);"); } + + [Fact] + public void EmptyRest() + { + _engine.Execute("function test({ ...props }){}; test({});"); + } } diff --git a/Jint.Tests/Runtime/Domain/Dimensional.cs b/Jint.Tests/Runtime/Domain/Dimensional.cs index 9c338836f0..7e0069a253 100644 --- a/Jint.Tests/Runtime/Domain/Dimensional.cs +++ b/Jint.Tests/Runtime/Domain/Dimensional.cs @@ -1,6 +1,6 @@ namespace Jint.Tests.Runtime.Domain { - public class Dimensional : IComparable + public class Dimensional : IComparable, IEquatable { private readonly MeasureUnit[] PossibleMeasureUnits = new MeasureUnit[] { new MeasureUnit("Mass", "kg", 1.0), new MeasureUnit("Mass", "gr", 0.001), new MeasureUnit("Count", "piece", 1.0) }; @@ -47,19 +47,69 @@ public override string ToString() { return Value + " " + MeasureUnit.Name; } + + public bool Equals(Dimensional other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Value.Equals(other.Value) && Equals(MeasureUnit, other.MeasureUnit); + } + + public override bool Equals(object obj) + { + return Equals(obj as Dimensional); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } } - public class MeasureUnit + public class MeasureUnit : IEquatable { public string MeasureType { get; set; } public string Name { get; set; } public double RelativeValue { get; set; } - public MeasureUnit(string measureType, string Name, double relativeValue) + public MeasureUnit(string measureType, string name, double relativeValue) { this.MeasureType = measureType; - this.Name = Name; + this.Name = name; this.RelativeValue = relativeValue; } + + public bool Equals(MeasureUnit other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return MeasureType == other.MeasureType && Name == other.Name && RelativeValue.Equals(other.RelativeValue); + } + + public override bool Equals(object obj) + { + return Equals(obj as MeasureUnit); + } + + public override int GetHashCode() + { + return MeasureType.GetHashCode() ^ Name.GetHashCode() ^ RelativeValue.GetHashCode(); + } } } diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index e4ec411d0f..ef6f84c3ba 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -4,6 +4,7 @@ using Esprima.Ast; using Jint.Native; using Jint.Native.Array; +using Jint.Native.Number; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Debugger; @@ -823,8 +824,8 @@ public void ShouldHandleEscapedSlashesInRegex() [Fact] public void ShouldComputeFractionInBase() { - Assert.Equal("011", _engine.Realm.Intrinsics.Number.PrototypeObject.ToFractionBase(0.375, 2)); - Assert.Equal("14141414141414141414141414141414141414141414141414", _engine.Realm.Intrinsics.Number.PrototypeObject.ToFractionBase(0.375, 5)); + Assert.Equal("011", NumberPrototype.ToFractionBase(0.375, 2)); + Assert.Equal("14141414141414141414141414141414141414141414141414", NumberPrototype.ToFractionBase(0.375, 5)); } [Fact] @@ -910,7 +911,7 @@ public void ShouldNotAllowModifyingSharedUndefinedDescriptor() [InlineData("2qgpckvng1s", 10000000000000000L, 36)] public void ShouldConvertNumbersToDifferentBase(string expected, long number, int radix) { - var result = _engine.Realm.Intrinsics.Number.PrototypeObject.ToBase(number, radix); + var result = NumberPrototype.ToBase(number, radix); Assert.Equal(expected, result); } @@ -3037,6 +3038,21 @@ public void ImportModuleShouldTriggerBeforeEvaluateEvents() Assert.Equal(2, beforeEvaluateTriggeredCount); } + [Fact] + public void ShouldConvertJsTypedArraysCorrectly() + { + var engine = new Engine(); + + var float32 = new float [] { 42f, 23 }; + + engine.SetValue("float32", float32); + engine.SetValue("testFloat32Array", new Action(v => Assert.Equal(v, float32))); + + engine.Evaluate(@" + testFloat32Array(new Float32Array(float32)); + "); + } + private static void TestBeforeEvaluateEvent(Action call, string expectedSource) { var engine = new Engine(); diff --git a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs index 859294dec1..f202249511 100644 --- a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs +++ b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs @@ -1,5 +1,6 @@ namespace Jint.Tests.Runtime; +using Jint.Native; using Jint.Runtime.Interop; public class InteropExplicitTypeTests @@ -86,10 +87,7 @@ public InteropExplicitTypeTests() public void EqualTest() { Assert.Equal(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.i1")); - Assert.NotEqual(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.ci1")); - Assert.Equal(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.super")); - Assert.NotEqual(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.ci1")); } [Fact] @@ -154,9 +152,9 @@ public NullabeStruct() } public string name = "NullabeStruct"; - public string Name { get => name; } + public string Name => name; - string I1.Name { get => "NullabeStruct as I1"; } + string I1.Name => "NullabeStruct as I1"; } public class NullableHolder @@ -172,7 +170,7 @@ public void TypedObjectWrapperForNullableType() _engine.SetValue("nullableHolder", nullableHolder); _engine.SetValue("nullabeStruct", new NullabeStruct()); - Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null); + Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), JsValue.Null); _engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct"); Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name); } @@ -288,4 +286,57 @@ public void ClrHelperObjectToType() }); Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message); } + + public interface ICallObjectMethodFromInterface + { + ICallObjectMethodFromInterface Instance { get; } + // hide Object.GetHashCode + string GetHashCode(); + // overload Object.Equals + string Equals(); + } + public class CallObjectMethodFromInterface : ICallObjectMethodFromInterface + { + public ICallObjectMethodFromInterface Instance => this; + public override string ToString() => nameof(CallObjectMethodFromInterface); + public new string GetHashCode() => "new GetHashCode, hide Object.GetHashCode"; + public string Equals() => "overload Object.Equals"; + } + + // issue#1626 ToString method is now unavailable in some CLR Interop scenarios + [Fact] + public void CallObjectMethodFromInterfaceWrapper() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.ToString(), _engine.Evaluate("inst.Instance.ToString()")); + } + + [Fact] + public void CallInterfaceMethodWhichHideObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.GetHashCode(), _engine.Evaluate("inst.Instance.GetHashCode()")); + } + + [Fact(Skip = "TODO, no solution now.")] + public void CallObjectMethodHiddenByInterface() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal( + (inst.Instance as object).GetHashCode(), + _engine.Evaluate("clrHelper.unwrap(inst.Instance).GetHashCode()") + ); + } + + [Fact] + public void CallInterfaceMethodWhichOverloadObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.Equals(), _engine.Evaluate("inst.Instance.Equals()")); + Assert.Equal(inst.Instance.Equals(inst), _engine.Evaluate("inst.Instance.Equals(inst)")); + } } diff --git a/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs b/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs new file mode 100644 index 0000000000..fc50064581 --- /dev/null +++ b/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs @@ -0,0 +1,151 @@ +using Jint.Runtime; + +namespace Jint.Tests.Runtime; + +public class InterfaceTests +{ + public interface I0 + { + string NameI0 { get; } + string OverloadSuperMethod(); + string SubPropertySuperMethod(); + } + + public interface I1 : I0 + { + string Name { get; } + string OverloadSuperMethod(int x); + new string SubPropertySuperMethod { get; } + } + + public class Super + { + public string Name { get; } = "Super"; + } + + public class CI1 : Super, I1 + { + public new string Name { get; } = "CI1"; + + public string NameI0 { get; } = "I0.Name"; + + string I1.Name { get; } = "CI1 as I1"; + + string I1.SubPropertySuperMethod { get; } = "I1.SubPropertySuperMethod"; + + public string OverloadSuperMethod() + { + return "I0.OverloadSuperMethod()"; + } + + public string OverloadSuperMethod(int x) + { + return $"I1.OverloadSuperMethod(int {x})"; + } + + public string SubPropertySuperMethod() + { + return "I0.SubPropertySuperMethod()"; + } + } + + public class Indexer + { + private readonly T t; + + public Indexer(T t) + { + this.t = t; + } + + public T this[int index] + { + get { return t; } + } + } + + public class InterfaceHolder + { + public InterfaceHolder() + { + var ci1 = new CI1(); + this.ci1 = ci1; + this.i1 = ci1; + this.super = ci1; + + this.IndexerCI1 = new Indexer(ci1); + this.IndexerI1 = new Indexer(ci1); + this.IndexerSuper = new Indexer(ci1); + } + + public readonly CI1 ci1; + public readonly I1 i1; + public readonly Super super; + + public CI1 CI1 { get => ci1; } + public I1 I1 { get => i1; } + public Super Super { get => super; } + + public CI1 GetCI1() => ci1; + public I1 GetI1() => i1; + public Super GetSuper() => super; + + public Indexer IndexerCI1 { get; } + public Indexer IndexerI1 { get; } + public Indexer IndexerSuper { get; } + } + + private readonly Engine _engine; + private readonly InterfaceHolder holder; + + public InterfaceTests() + { + holder = new InterfaceHolder(); + _engine = new Engine(cfg => cfg.AllowClr( + typeof(CI1).Assembly, + typeof(Console).Assembly, + typeof(File).Assembly)) + .SetValue("log", new Action(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)) + .SetValue("holder", holder) + ; + } + + [Fact] + public void CallSuperPropertyFromInterface() + { + Assert.Equal(holder.I1.NameI0, _engine.Evaluate("holder.I1.NameI0")); + } + + [Fact] + public void CallOverloadSuperMethod() + { + Assert.Equal( + holder.I1.OverloadSuperMethod(1), + _engine.Evaluate("holder.I1.OverloadSuperMethod(1)")); + Assert.Equal( + holder.I1.OverloadSuperMethod(), + _engine.Evaluate("holder.I1.OverloadSuperMethod()")); + } + + [Fact] + public void CallSubPropertySuperMethod_SubProperty() + { + Assert.Equal( + holder.I1.SubPropertySuperMethod, + _engine.Evaluate("holder.I1.SubPropertySuperMethod")); + } + + [Fact] + public void CallSubPropertySuperMethod_SuperMethod() + { + var ex = Assert.Throws(() => + { + Assert.Equal( + holder.I1.SubPropertySuperMethod(), + _engine.Evaluate("holder.I1.SubPropertySuperMethod()")); + }); + Assert.Equal("Property 'SubPropertySuperMethod' of object is not a function", ex.Message); + } +} diff --git a/Jint.Tests/Runtime/InteropTests.Dynamic.cs b/Jint.Tests/Runtime/InteropTests.Dynamic.cs index d8563d6502..d0de054323 100644 --- a/Jint.Tests/Runtime/InteropTests.Dynamic.cs +++ b/Jint.Tests/Runtime/InteropTests.Dynamic.cs @@ -1,4 +1,7 @@ using System.Dynamic; +using Jint.Native; +using Jint.Native.Symbol; +using Jint.Tests.Runtime.Domain; namespace Jint.Tests.Runtime { @@ -14,6 +17,36 @@ public void CanAccessExpandoObject() Assert.Equal("test", engine.Evaluate("expando.Name").ToString()); } + [Fact] + public void DebugView() + { + // allows displaying different local variables under debugger + + var engine = new Engine(); + var boolNet = true; + var boolJint = (JsBoolean) boolNet; + var doubleNet = 12.34; + var doubleJint = (JsNumber) doubleNet; + var integerNet = 42; + var integerJint = (JsNumber) integerNet; + var stringNet = "ABC"; + var stringJint = (JsString) stringNet; + var arrayNet = new[] { 1, 2, 3 }; + var arrayListNet = new List { 1, 2, 3 }; + var arrayJint = new JsArray(engine, arrayNet.Select(x => (JsNumber) x).ToArray()); + + var objectNet = new Person { Name = "name", Age = 12 }; + var objectJint = new JsObject(engine); + objectJint["name"] = "name"; + objectJint["age"] = 12; + objectJint[GlobalSymbolRegistry.ToStringTag] = "Object"; + + var dictionaryNet = new Dictionary(); + dictionaryNet["name"] = "name"; + dictionaryNet["age"] = 12; + dictionaryNet[GlobalSymbolRegistry.ToStringTag] = "Object"; + } + [Fact] public void CanAccessMemberNamedItemThroughExpando() { diff --git a/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs new file mode 100644 index 0000000000..4f0df7741c --- /dev/null +++ b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs @@ -0,0 +1,104 @@ +using System.Reflection; +using System.Text.Json.Nodes; +using Jint.Runtime.Interop; + +namespace Jint.Tests.Runtime; + +public partial class InteropTests +{ + [Fact] + public void AccessingJsonNodeShouldWork() + { + const string Json = """ + { + "employees": { + "type": "array", + "value": [ + { + "firstName": "John", + "lastName": "Doe" + }, + { + "firstName": "Jane", + "lastName": "Doe" + } + ] + } + } + """; + + var variables = JsonNode.Parse(Json); + + var engine = new Engine(options => + { + // make JsonArray behave like JS array + options.Interop.WrapObjectHandler = static (e, target, type) => + { + var wrapped = new ObjectWrapper(e, target); + if (target is JsonArray) + { + wrapped.SetPrototypeOf(e.Realm.Intrinsics.Array.PrototypeObject); + } + return wrapped; + }; + + // we cannot access this[string] with anything else than JsonObject, otherwise itw will throw + options.Interop.TypeResolver = new TypeResolver + { + MemberFilter = static info => + { + if (info.ReflectedType != typeof(JsonObject) && info.Name == "Item" && info is PropertyInfo p) + { + var parameters = p.GetIndexParameters(); + return parameters.Length != 1 || parameters[0].ParameterType != typeof(string); + } + + return true; + } + }; + }); + + engine + .SetValue("variables", variables) + .Execute(""" + function populateFullName() { + return variables['employees'].value.map(item => { + var newItem = + { + "firstName": item.firstName, + "lastName": item.lastName, + "fullName": item.firstName + ' ' + item.lastName + }; + + return newItem; + }); + } + """); + + // reading data + var result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("John Doe", result[0].AsObject()["fullName"]); + Assert.Equal("Jane Doe", result[1].AsObject()["fullName"]); + + // mutating data via JS + engine.Evaluate("variables.employees.type = 'array2'"); + engine.Evaluate("variables.employees.value[0].firstName = 'Jake'"); + + Assert.Equal("array2", engine.Evaluate("variables['employees']['type']").ToString()); + + result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("Jake Doe", result[0].AsObject()["fullName"]); + + // mutating original object that is wrapped inside the engine + variables["employees"]["type"] = "array"; + variables["employees"]["value"][0]["firstName"] = "John"; + + Assert.Equal("array", engine.Evaluate("variables['employees']['type']").ToString()); + + result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("John Doe", result[0].AsObject()["fullName"]); + } +} diff --git a/Jint.Tests/Runtime/InteropTests.TypeReference.cs b/Jint.Tests/Runtime/InteropTests.TypeReference.cs index 01d2995bbb..dc1557f26f 100644 --- a/Jint.Tests/Runtime/InteropTests.TypeReference.cs +++ b/Jint.Tests/Runtime/InteropTests.TypeReference.cs @@ -1,4 +1,6 @@ using Jint.Native; +using Jint.Native.Symbol; +using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; using Jint.Tests.Runtime.Domain; using Microsoft.Extensions.DependencyInjection; @@ -181,6 +183,25 @@ public void CanConfigureCustomInstanceCreator() Assert.Equal("Hello world", engine.Evaluate("new Injectable(123, 'abc').getInjectedValue();")); } + [Fact] + public void CanRegisterToStringTag() + { + var reference = TypeReference.CreateTypeReference(_engine); + reference.FastSetProperty(GlobalSymbolRegistry.ToStringTag, new PropertyDescriptor(nameof(Dependency), false, false, true)); + reference.FastSetDataProperty("abc", 123); + + _engine.SetValue("MyClass", reference); + _engine.Execute("var c = new MyClass();"); + + Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);")); + Assert.Equal(123, _engine.Evaluate("c.abc")); + + // engine uses registered type reference + _engine.SetValue("c2", new Dependency()); + Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c2);")); + Assert.Equal(123, _engine.Evaluate("c2.abc")); + } + private class Injectable { private readonly Dependency _dependency; diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index d8b0ac5368..a62b514872 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -1697,10 +1697,7 @@ public void ShouldConvertToEnum() public void ShouldUseExplicitPropertyGetter() { _engine.SetValue("c", new Company("ACME")); - - RunTest(@" - assert(c.Name === 'ACME'); - "); + Assert.Equal("ACME", _engine.Evaluate("c.Name")); } [Fact] @@ -1709,21 +1706,14 @@ public void ShouldUseExplicitIndexerPropertyGetter() var company = new Company("ACME"); ((ICompany) company)["Foo"] = "Bar"; _engine.SetValue("c", company); - - RunTest(@" - assert(c.Foo === 'Bar'); - "); + Assert.Equal("Bar", _engine.Evaluate("c.Foo")); } [Fact] public void ShouldUseExplicitPropertySetter() { _engine.SetValue("c", new Company("ACME")); - - RunTest(@" - c.Name = 'Foo'; - assert(c.Name === 'Foo'); - "); + Assert.Equal("Foo", _engine.Evaluate("c.Name = 'Foo'; c.Name;")); } [Fact] @@ -3263,5 +3253,173 @@ public void CanPassDateTimeMinAndMaxViaInterop() engine.Execute("capture(maxDate);"); Assert.Equal(DateTime.MaxValue, dt); } + + private class Container + { + private readonly Child _child = new(); + public Child Child => _child; + public BaseClass Get() => _child; + } + + private class BaseClass { } + + private class Child : BaseClass { } + + [Fact] + public void AccessingBaseTypeShouldBeEqualToAccessingDerivedType() + { + var engine = new Engine().SetValue("container", new Container()); + var res = engine.Evaluate("container.Child === container.Get()"); // These two should be the same object. But this PR makes `container.Get()` return a different object + + Assert.True(res.AsBoolean()); + } + + public interface IIndexer + { + T this[int index] { get; } + } + + public interface ICountable + { + int Count { get; } + } + + public interface IStringCollection : IIndexer, ICountable + { + string this[string name] { get; } + } + + public class Strings : IStringCollection + { + private readonly string[] _strings; + public Strings(string[] strings) + { + _strings = strings; + } + + public string this[string name] => null; + public string this[int index] => _strings[index]; + public int Count => _strings.Length; + } + + public class Utils + { + public IStringCollection GetStrings() => new Strings(new [] { "a", "b", "c" }); + } + + [Fact] + public void AccessingInterfaceShouldContainExtendedInterfaces() + { + var engine = new Engine(); + engine.SetValue("Utils", new Utils()); + var result = engine.Evaluate("const strings = Utils.GetStrings(); strings.Count;").AsNumber(); + Assert.Equal(3, result); + } + + [Fact] + public void IntegerIndexerIfPreferredOverStringIndexerWhenFound() + { + var engine = new Engine(); + engine.SetValue("Utils", new Utils()); + var result = engine.Evaluate("const strings = Utils.GetStrings(); strings[2];"); + Assert.Equal("c", result); + } + + [Fact] + public void CanDestructureInteropTargetMethod() + { + var engine = new Engine(); + engine.SetValue("test", new Utils()); + var result = engine.Evaluate("const { getStrings } = test; getStrings().Count;"); + Assert.Equal(3, result); + } + + private class MetadataWrapper : IDictionary + { + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(KeyValuePair item) => throw new NotImplementedException(); + public void Clear() => throw new NotImplementedException(); + public bool Contains(KeyValuePair item) => throw new NotImplementedException(); + public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); + public bool Remove(KeyValuePair item) => throw new NotImplementedException(); + public int Count { get; set; } + public bool IsReadOnly { get; set; } + public bool ContainsKey(string key) => throw new NotImplementedException(); + public void Add(string key, object value) => throw new NotImplementedException(); + public bool Remove(string key) => throw new NotImplementedException(); + public bool TryGetValue(string key, out object value) + { + value = "from-wrapper"; + return true; + } + + public object this[string key] + { + get => "from-wrapper"; + set + { + } + } + + public ICollection Keys { get; set; } + public ICollection Values { get; set; } + } + + private class ShadowedGetter : IReadOnlyDictionary + { + private Dictionary _dictionary = new(); + + public void SetInitial(object value, string key) + { + _dictionary[key] = value; + } + + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count { get; } + public bool ContainsKey(string key) => _dictionary.ContainsKey(key); + + public bool TryGetValue(string key, out object value) => _dictionary.TryGetValue(key, out value); + + public object this[string key] + { + get + { + _dictionary.TryGetValue(key, out var value); + return value; + } + } + + public IEnumerable Keys { get; set; } + public IEnumerable Values { get; set; } + } + + private class ShadowingSetter : ShadowedGetter + { + public Dictionary Metadata + { + set + { + SetInitial(new MetadataWrapper(), "metadata"); + } + } + } + + [Fact] + public void CanSelectShadowedPropertiesBasedOnReadableAndWritable() + { + var engine = new Engine(); + engine.SetValue("test", new ShadowingSetter + { + Metadata = null + }); + + engine.Evaluate("test.metadata['abc'] = 123"); + var result = engine.Evaluate("test.metadata['abc']"); + Assert.Equal("from-wrapper", result); + } } } diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs new file mode 100644 index 0000000000..1561a29869 --- /dev/null +++ b/Jint.Tests/Runtime/PropertyDescriptorTests.cs @@ -0,0 +1,353 @@ +using Jint.Native; +using Jint.Native.Argument; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Descriptors.Specialized; +using Jint.Runtime.Interop; +using Jint.Tests.TestClasses; + +namespace Jint.Tests.Runtime; + +public class PropertyDescriptorTests +{ + public class TestClass + { + public static readonly TestClass Instance = new TestClass(); + public string Method() => "Method"; + public class NestedType { } + + public readonly int fieldReadOnly = 8; + public int field = 42; + + public string PropertyReadOnly => "PropertyReadOnly"; + public string PropertyWriteOnly { set { } } + public string PropertyReadWrite { get; set; } = "PropertyReadWrite"; + + public IndexedPropertyReadOnly IndexerReadOnly { get; } + = new((idx) => 42); + public IndexedPropertyWriteOnly IndexerWriteOnly { get; } + = new((idx, v) => { }); + public IndexedProperty IndexerReadWrite { get; } + = new((idx) => 42, (idx, v) => { }); + } + + private readonly Engine _engine; + + private readonly bool checkType = true; + + public PropertyDescriptorTests() + { + _engine = new Engine(cfg => cfg.AllowClr( + typeof(TestClass).Assembly, + typeof(Console).Assembly, + typeof(File).Assembly)) + .SetValue("log", new Action(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)) + .SetValue("testClass", TestClass.Instance) + ; + } + + [Fact] + public void PropertyDescriptorReadOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + value: 42, + writable: false + }) + """).AsObject().GetOwnProperty("value"); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + Assert.Equal(false, pd.Writable); + Assert.Null(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void PropertyDescriptorReadWrite() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + value: 42, + writable: true + }) + """).AsObject().GetOwnProperty("value"); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + Assert.Equal(true, pd.Writable); + Assert.Null(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void UndefinedPropertyDescriptor() + { + var pd = PropertyDescriptor.Undefined; + // PropertyDescriptor.UndefinedPropertyDescriptor is private + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void AllForbiddenDescriptor() + { + var pd = _engine.Evaluate("Object.getPrototypeOf('s')").AsObject().GetOwnProperty("length"); + if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void LazyPropertyDescriptor() + { + var pd = _engine.Evaluate("globalThis").AsObject().GetOwnProperty("decodeURI"); + if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void ThrowerPropertyDescriptor() + { + var pd = _engine.Evaluate("Object.getPrototypeOf(function() {})").AsObject().GetOwnProperty("arguments"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void GetSetPropertyDescriptorGetOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + get() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.NotNull(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void GetSetPropertyDescriptorSetOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + set() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.Null(pd.Get); + Assert.NotNull(pd.Set); + } + + [Fact] + public void GetSetPropertyDescriptorGetSet() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + get() {}, + set() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.NotNull(pd.Get); + Assert.NotNull(pd.Set); + } + + [Fact] + public void ClrAccessDescriptor() + { + JsValue ExtractClrAccessDescriptor(JsValue jsArugments) + { + var pd = ((ArgumentsInstance) jsArugments).ParameterMap.GetOwnProperty("0"); + return new ObjectWrapper(_engine, pd); + } + _engine.SetValue("ExtractClrAccessDescriptor", ExtractClrAccessDescriptor); + var pdobj = _engine.Evaluate(""" + (function(a) { + return ExtractClrAccessDescriptor(arguments); + })(42) + """); + var pd = (PropertyDescriptor) ((ObjectWrapper) pdobj).Target; + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void PropertyDescriptorMethod() + { + var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'Method')"); + CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("Method"); + // use PropertyDescriptor to wrap method directly + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void PropertyDescriptorNestedType() + { + var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'NestedType')"); + CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("NestedType"); + // use PropertyDescriptor to wrap nested type directly + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorFieldReadOnly() + { + var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'fieldReadOnly')"); + CheckPropertyDescriptor(pdField, false, true, false, false, true, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("fieldReadOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorField() + { + var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'field')"); + CheckPropertyDescriptor(pdField, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("field"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyReadOnly() + { + var pdPropertyReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadOnly')"); + CheckPropertyDescriptor(pdPropertyReadOnly, false, true, false, false, true, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyWriteOnly() + { + var pdPropertyWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyWriteOnly')"); + CheckPropertyDescriptor(pdPropertyWriteOnly, false, true, true, false, false, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyWriteOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyReadWrite() + { + var pdPropertyReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadWrite')"); + CheckPropertyDescriptor(pdPropertyReadWrite, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadWrite"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerReadOnly() + { + var pdIndexerReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadOnly, '1')"); + CheckPropertyDescriptor(pdIndexerReadOnly, false, true, false, false, true, false); + + var pd1 = _engine.Evaluate("testClass.IndexerReadOnly"); + var pd = pd1.AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerWriteOnly() + { + var pdIndexerWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerWriteOnly, '1')"); + CheckPropertyDescriptor(pdIndexerWriteOnly, false, true, true, false, false, true); + + var pd = _engine.Evaluate("testClass.IndexerWriteOnly").AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerReadWrite() + { + var pdIndexerReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadWrite, 1)"); + CheckPropertyDescriptor(pdIndexerReadWrite, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass.IndexerReadWrite").AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + private void CheckPropertyDescriptor( + JsValue jsPropertyDescriptor, + bool configurable, + bool enumerable, + bool writable, + bool hasValue, + bool hasGet, + bool hasSet + ) + { + var pd = jsPropertyDescriptor.AsObject(); + + Assert.Equal(configurable, pd["configurable"].AsBoolean()); + Assert.Equal(enumerable, pd["enumerable"].AsBoolean()); + if (writable) + { + var writableActual = pd["writable"]; + if (!writableActual.IsUndefined()) + { + Assert.True(writableActual.AsBoolean()); + } + } + + Assert.Equal(hasValue, !pd["value"].IsUndefined()); + Assert.Equal(hasGet, !pd["get"].IsUndefined()); + Assert.Equal(hasSet, !pd["set"].IsUndefined()); + } + + [Fact] + public void DefinePropertyFromAccesorToData() + { + var pd = _engine.Evaluate(""" + let o = {}; + Object.defineProperty(o, 'foo', { + get() { return 1; }, + configurable: true + }); + Object.defineProperty(o, 'foo', { + value: 101 + }); + return Object.getOwnPropertyDescriptor(o, 'foo'); + """); + Assert.Equal(101, pd.AsObject().Get("value").AsInteger()); + CheckPropertyDescriptor(pd, true, false, false, true, false, false); + } +} diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index 25d71f3bbb..f86e61cd55 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -1,4 +1,7 @@ -namespace Jint.Tests.Runtime; +using Jint.Native.Error; +using Jint.Runtime; + +namespace Jint.Tests.Runtime; public class ProxyTests { @@ -183,4 +186,300 @@ public void ConstructHandlerInvariant() Assert.True(_engine.Evaluate(Script).AsBoolean()); } + + [Fact] + public void ProxyHandlerGetDataPropertyShouldNotUseReferenceEquals() + { + // There are two JsString which should be treat as same value, + // but they are not ReferenceEquals. + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + value: 'in', + }); + const handler = { + get(target, property, receiver) { + return 'Jint'.substring(1,3); + } + }; + let p = new Proxy(o, handler); + let pv = p.value; + """); + } + + [Fact] + public void ProxyHandlerGetDataPropertyShouldNotCheckClrType() + { + // There are a JsString and a ConcatenatedString which should be treat as same value, + // but they are different CLR Type. + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + value: 'Jint', + }); + const handler = { + get(target, property, receiver) { + return 'Ji'.concat('nt'); + } + }; + let p = new Proxy(o, handler); + let pv = p.value; + """); + } + + class TestClass + { + public static readonly TestClass Instance = new TestClass(); + public string StringValue => "StringValue"; + public int IntValue => 42424242; // avoid small numbers cache + public TestClass ObjectWrapper => Instance; + + private int x = 1; + public int PropertySideEffect => x++; + + public string Name => "My Name is Test"; + + public void SayHello() + { + } + + public int Add(int a, int b) + { + return a + b; + } + } + + [Fact] + public void ProxyClrPropertyPrimitiveString() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.StringValue; + """); + Assert.Equal(TestClass.Instance.StringValue, result.AsString()); + } + + [Fact] + public void ProxyClrPropertyPrimitiveInt() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.IntValue; + """); + Assert.Equal(TestClass.Instance.IntValue, result.AsInteger()); + } + + [Fact] + public void ProxyClrPropertyObjectWrapper() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.ObjectWrapper; + """); + } + + private static ErrorPrototype TypeErrorPrototype(Engine engine) + => engine.Realm.Intrinsics.TypeError.PrototypeObject; + + private static void AssertJsTypeError(Engine engine, JavaScriptException ex, string msg) + { + Assert.Same(TypeErrorPrototype(engine), ex.Error.AsObject().Prototype); + Assert.Equal(msg, ex.Message); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get#invariants + // The value reported for a property must be the same as + // the value ofthe corresponding target object property, + // if the target object property is + // a non-writable, non-configurable own data property. + [Fact] + public void ProxyHandlerGetInvariantsDataPropertyReturnsDifferentValue() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + writable: false, + configurable: false, + value: 42, + }); + const handler = { + get(target, property, receiver) { + return 32; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value")); + AssertJsTypeError(_engine, ex, "'get' on proxy: property 'value' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '42' but got '32')"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get#invariants + // The value reported for a property must be undefined, + // if the corresponding target object property is + // a non-configurable own accessor property + // that has undefined as its [[Get]] attribute. + [Fact] + public void ProxyHandlerGetInvariantsAccessorPropertyWithoutGetButReturnsValue() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + set() {}, + }); + const handler = { + get(target, property, receiver) { + return 32; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value")); + AssertJsTypeError(_engine, ex, "'get' on proxy: property 'value' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '32')"); + } + + private const string ScriptProxyHandlerSetInvariantsDataPropertyImmutable = """ + let o = Object.defineProperty({}, 'value', { + writable: false, + configurable: false, + value: 42, + }); + const handler = { + set(target, property, value, receiver) { + return true; + } + }; + let p = new Proxy(o, handler); + """; + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // Cannot change the value of a property to be different from + // the value of the corresponding target object property, + // if the corresponding target object property is + // a non-writable, non-configurable data property. + [Fact] + public void ProxyHandlerSetInvariantsDataPropertyImmutableChangeValue() + { + _engine.Execute(ScriptProxyHandlerSetInvariantsDataPropertyImmutable); + var ex = Assert.Throws(() => _engine.Evaluate("p.value = 32")); + AssertJsTypeError(_engine, ex, "'set' on proxy: trap returned truish for property 'value' which exists in the proxy target as a non-configurable and non-writable data property with a different value"); + } + + [Fact] + public void ProxyHandlerSetInvariantsDataPropertyImmutableSetSameValue() + { + _engine.Execute(ScriptProxyHandlerSetInvariantsDataPropertyImmutable); + _engine.Evaluate("p.value = 42"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // Cannot set the value of a property, + // if the corresponding target object property is + // a non-configurable accessor property + // that has undefined as its [[Set]] attribute. + [Fact] + public void ProxyHandlerSetInvariantsAccessorPropertyWithoutSetChange() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + get() { return 42; }, + }); + const handler = { + set(target, property, value, receiver) { + return true; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value = 42")); + AssertJsTypeError(_engine, ex, "'set' on proxy: trap returned truish for property 'value' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // In strict mode, a false return value from the set() handler + // will throw a TypeError exception. + [Fact] + public void ProxyHandlerSetInvariantsReturnsFalseInStrictMode() + { + var ex = Assert.Throws(() => _engine.Evaluate(""" + 'use strict'; + let p = new Proxy({}, { set: () => false }); + p.value = 42; + """)); + // V8: "'set' on proxy: trap returned falsish for property 'value'", + AssertJsTypeError(_engine, ex, "Cannot assign to read only property 'value' of [object Object]"); + } + + [Fact] + public void ProxyHandlerSetInvariantsReturnsFalseInNonStrictMode() + { + _engine.Evaluate(""" + // 'use strict'; + let p = new Proxy({}, { set: () => false }); + p.value = 42; + """); + } + + [Fact] + public void ClrPropertySideEffect() + { + _engine.SetValue("testClass", TestClass.Instance); + _engine.Execute(""" + const handler = { + get(target, property, receiver) { + return 2; + } + }; + const p = new Proxy(testClass, handler); + """); + + Assert.Equal(1, TestClass.Instance.PropertySideEffect); // first call to PropertySideEffect + Assert.Equal(2, _engine.Evaluate("p.PropertySideEffect").AsInteger()); // no call to PropertySideEffect + Assert.Equal(2, TestClass.Instance.PropertySideEffect); // second call to PropertySideEffect + } + + [Fact] + public void ToObjectReturnsProxiedToObject() + { + _engine + .SetValue("T", new TestClass()) + .Execute(""" + const handler = { + get(target, property, receiver) { + + if (!target[property]) { + return (...args) => "Not available"; + } + + // return Reflect.get(target, property, receiver); + return Reflect.get(...arguments); + } + }; + + const p = new Proxy(T, handler); + const name = p.Name; // works + const s = p.GetX(); // works because method does NOT exist on clr object + + p.SayHello(); // throws System.Reflection.TargetException: 'Object does not match target type.' + const t = p.Add(5,3); // throws System.Reflection.TargetException: 'Object does not match target type.' + """); + + } } diff --git a/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs new file mode 100644 index 0000000000..899e3cfa51 --- /dev/null +++ b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs @@ -0,0 +1,49 @@ +namespace Jint.Tests.TestClasses; + +public class IndexedProperty +{ + Action Setter { get; } + Func Getter { get; } + + public IndexedProperty(Func getter, Action setter) + { + Getter = getter; + Setter = setter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + set => Setter(i, value); + } +} + +public class IndexedPropertyReadOnly +{ + Func Getter { get; } + + public IndexedPropertyReadOnly(Func getter) + { + Getter = getter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + } +} + +public class IndexedPropertyWriteOnly +{ + Action Setter { get; } + + public IndexedPropertyWriteOnly(Action setter) + { + Setter = setter; + } + + public TValue this[TIndex i] + { + set => Setter(i, value); + } +} diff --git a/Jint.Tests/Runtime/TypedArrayInteropTests.cs b/Jint.Tests/Runtime/TypedArrayInteropTests.cs index 772936d11c..1fee21b382 100644 --- a/Jint.Tests/Runtime/TypedArrayInteropTests.cs +++ b/Jint.Tests/Runtime/TypedArrayInteropTests.cs @@ -120,6 +120,34 @@ public void CanInteropWithBigUint64() Assert.Equal(source, fromEngine.AsBigUint64Array()); } + [Fact] + public void CanInteropWithFloat32() + { + var engine = new Engine(); + var source = new float[] { 42f, 12f }; + + engine.SetValue("testSubject", engine.Realm.Intrinsics.Float32Array.Construct(source)); + ValidateCreatedTypeArray(engine, "Float32Array"); + + var fromEngine = engine.GetValue("testSubject"); + Assert.True(fromEngine.IsFloat32Array()); + Assert.Equal(source, fromEngine.AsFloat32Array()); + } + + [Fact] + public void CanInteropWithFloat64() + { + var engine = new Engine(); + var source = new double[] { 42f, 12f }; + + engine.SetValue("testSubject", engine.Realm.Intrinsics.Float64Array.Construct(source)); + ValidateCreatedTypeArray(engine, "Float64Array"); + + var fromEngine = engine.GetValue("testSubject"); + Assert.True(fromEngine.IsFloat64Array()); + Assert.Equal(source, fromEngine.AsFloat64Array()); + } + private static void ValidateCreatedTypeArray(Engine engine, string arrayName) { Assert.Equal(arrayName, engine.Evaluate("testSubject.constructor.name").AsString()); diff --git a/Jint.sln b/Jint.sln index c8f58b6724..fad3255cfe 100644 --- a/Jint.sln +++ b/Jint.sln @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject README.md = README.md .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Global diff --git a/Jint/Collections/DictionarySlim.cs b/Jint/Collections/DictionarySlim.cs index e4547720d0..03729cd2d3 100644 --- a/Jint/Collections/DictionarySlim.cs +++ b/Jint/Collections/DictionarySlim.cs @@ -7,11 +7,12 @@ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Jint.Collections { /// - /// DictionarySlim is similar to Dictionary but optimized in three ways: + /// DictionarySlim<string, TValue> is similar to Dictionary<TKey, TValue> but optimized in three ways: /// 1) It allows access to the value by ref replacing the common TryGetValue and Add pattern. /// 2) It does not store the hash code (assumes it is cheap to equate values). /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient). @@ -32,6 +33,7 @@ internal class DictionarySlim : IReadOnlyCollection{next}")] + [StructLayout(LayoutKind.Auto)] private struct Entry { public TKey key; diff --git a/Jint/Collections/HybridDictionary.cs b/Jint/Collections/HybridDictionary.cs index 618e6883b8..f629d0600e 100644 --- a/Jint/Collections/HybridDictionary.cs +++ b/Jint/Collections/HybridDictionary.cs @@ -28,6 +28,12 @@ public HybridDictionary(int initialSize, bool checkExistingKeys) } } + protected HybridDictionary(StringDictionarySlim dictionary) + { + _checkExistingKeys = true; + _dictionary = dictionary; + } + public TValue this[Key key] { get @@ -45,7 +51,7 @@ public TValue this[Key key] { if (_list.Count >= CutoverPoint - 1) { - SwitchToDictionary(key, value); + SwitchToDictionary(key, value, tryAdd: false); } else { @@ -91,7 +97,7 @@ public void SetOrUpdateValue(Key key, Func updat } } - private void SwitchToDictionary(Key key, TValue value) + private bool SwitchToDictionary(Key key, TValue value, bool tryAdd) { var dictionary = new StringDictionarySlim(InitialDictionarySize); foreach (var pair in _list) @@ -99,9 +105,19 @@ private void SwitchToDictionary(Key key, TValue value) dictionary[pair.Key] = pair.Value; } - dictionary[key] = value; + bool result; + if (tryAdd) + { + result = dictionary.TryAdd(key, value); + } + else + { + dictionary[key] = value; + result = true; + } _dictionary = dictionary; _list = null; + return result; } public int Count @@ -110,6 +126,27 @@ public int Count get => _dictionary?.Count ?? _list?.Count ?? 0; } + public bool TryAdd(Key key, TValue value) + { + if (_dictionary != null) + { + return _dictionary.TryAdd(key, value); + } + else + { + _list ??= new ListDictionary(key, value, _checkExistingKeys); + + if (_list.Count + 1 >= CutoverPoint) + { + return SwitchToDictionary(key, value, tryAdd: true); + } + else + { + return _list.Add(key, value, tryAdd: true); + } + } + } + public void Add(Key key, TValue value) { if (_dictionary != null) @@ -126,7 +163,7 @@ public void Add(Key key, TValue value) { if (_list.Count + 1 >= CutoverPoint) { - SwitchToDictionary(key, value); + SwitchToDictionary(key, value, tryAdd: false); } else { diff --git a/Jint/Collections/ListDictionary.cs b/Jint/Collections/ListDictionary.cs index a1fdd1db1a..51e72c98ed 100644 --- a/Jint/Collections/ListDictionary.cs +++ b/Jint/Collections/ListDictionary.cs @@ -99,7 +99,7 @@ public int Count get => _count; } - public void Add(Key key, TValue value) + public bool Add(Key key, TValue value, bool tryAdd = false) { DictionaryNode last = null; DictionaryNode node; @@ -109,6 +109,10 @@ public void Add(Key key, TValue value) var oldKey = node.Key; if (checkExistingKeys && oldKey == key) { + if (tryAdd) + { + return false; + } ExceptionHelper.ThrowArgumentException(); } @@ -116,6 +120,7 @@ public void Add(Key key, TValue value) } AddNode(key, value, last); + return true; } private void AddNode(Key key, TValue value, DictionaryNode last) diff --git a/Jint/Collections/PropertyDictionary.cs b/Jint/Collections/PropertyDictionary.cs index daea0f27fa..41a8c54397 100644 --- a/Jint/Collections/PropertyDictionary.cs +++ b/Jint/Collections/PropertyDictionary.cs @@ -11,5 +11,9 @@ public PropertyDictionary() public PropertyDictionary(int capacity, bool checkExistingKeys) : base(capacity, checkExistingKeys) { } + + public PropertyDictionary(StringDictionarySlim properties) : base(properties) + { + } } } diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index fab269533a..2ff194f1ee 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -1,3 +1,6 @@ +#pragma warning disable MA0006 +#pragma warning disable MA0008 + #nullable disable // Licensed to the .NET Foundation under one or more agreements. @@ -12,7 +15,7 @@ namespace Jint.Collections { /// - /// DictionarySlim is similar to Dictionary but optimized in three ways: + /// DictionarySlim<string, TValue> is similar to Dictionary<TKey, TValue> but optimized in three ways: /// 1) It allows access to the value by ref replacing the common TryGetValue and Add pattern. /// 2) It does not store the hash code (assumes it is cheap to equate values). /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient). @@ -171,6 +174,32 @@ public ref TValue GetOrAddValueRef(Key key) return ref AddKey(key, bucketIndex); } + public bool TryAdd(Key key, TValue value) + { + Entry[] entries = _entries; + int bucketIndex = key.HashCode & (_buckets.Length - 1); + for (int i = _buckets[bucketIndex] - 1; + (uint)i < (uint)entries.Length; i = entries[i].next) + { + if (key.Name == entries[i].key.Name) + { + return false; + } + } + + AddKey(key, bucketIndex) = value; + return true; + } + + /// + /// Adds a new item and expects key to not to exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddDangerous(in Key key, TValue value) + { + AddKey(key, key.HashCode & (_buckets.Length - 1)) = value; + } + public ref TValue this[Key key] { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Jint/Constraints/TimeConstraint.cs b/Jint/Constraints/TimeConstraint.cs index a6b052b48d..b7f295c671 100644 --- a/Jint/Constraints/TimeConstraint.cs +++ b/Jint/Constraints/TimeConstraint.cs @@ -3,7 +3,9 @@ namespace Jint.Constraints; +#pragma warning disable CA1001 internal sealed class TimeConstraint : Constraint +#pragma warning restore CA1001 { private readonly TimeSpan _timeout; private CancellationTokenSource? _cts; diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 17df3b232a..589fa61f8c 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -1,5 +1,6 @@ using Esprima; using Esprima.Ast; +using Jint.Native; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Expressions; @@ -8,7 +9,6 @@ namespace Jint; public partial class Engine { - /// /// Prepares a script for the engine that includes static analysis data to speed up execution during run-time. /// @@ -43,7 +43,7 @@ public static Module PrepareModule(string script, string? source = null) private sealed class AstAnalyzer { - private readonly Dictionary _bindingNames = new(); + private readonly Dictionary _bindingNames = new(StringComparer.Ordinal); public void NodeVisitor(Node node) { @@ -55,7 +55,7 @@ public void NodeVisitor(Node node) if (!_bindingNames.TryGetValue(name, out var bindingName)) { - _bindingNames[name] = bindingName = new EnvironmentRecord.BindingName(name); + _bindingNames[name] = bindingName = new EnvironmentRecord.BindingName(JsString.CachedCreate(name)); } node.AssociatedData = bindingName; @@ -64,13 +64,111 @@ public void NodeVisitor(Node node) case Nodes.Literal: node.AssociatedData = JintLiteralExpression.ConvertToJsValue((Literal) node); break; + case Nodes.MemberExpression: + node.AssociatedData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true); + break; case Nodes.ArrowFunctionExpression: case Nodes.FunctionDeclaration: case Nodes.FunctionExpression: var function = (IFunction) node; node.AssociatedData = JintFunctionDefinition.BuildState(function); break; + case Nodes.Program: + node.AssociatedData = new CachedHoistingScope((Program) node); + break; + } + } + } +} + +internal sealed class CachedHoistingScope +{ + public CachedHoistingScope(Program program) + { + Scope = HoistingScope.GetProgramLevelDeclarations(program); + + VarNames = new List(); + GatherVarNames(Scope, VarNames); + + LexNames = new List(); + GatherLexNames(Scope, LexNames); + } + + internal static void GatherVarNames(HoistingScope scope, List boundNames) + { + var varDeclarations = scope._variablesDeclarations; + if (varDeclarations != null) + { + for (var i = 0; i < varDeclarations.Count; i++) + { + var d = varDeclarations[i]; + d.GetBoundNames(boundNames); + } + } + } + + internal static void GatherLexNames(HoistingScope scope, List boundNames) + { + var lexDeclarations = scope._lexicalDeclarations; + if (lexDeclarations != null) + { + var temp = new List(); + for (var i = 0; i < lexDeclarations.Count; i++) + { + var d = lexDeclarations[i]; + temp.Clear(); + d.GetBoundNames(temp); + foreach (var name in temp) + { + boundNames.Add(new CachedLexicalName(name, d.IsConstantDeclaration())); + } } } } + + internal readonly record struct CachedLexicalName(string Name, bool Constant); + + public HoistingScope Scope { get; } + public List VarNames { get; } + public List LexNames { get; } +} + +internal static class AstPreparationExtensions +{ + internal static HoistingScope GetHoistingScope(this Program program) + { + return program.AssociatedData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program); + } + + internal static List GetVarNames(this Program program, HoistingScope hoistingScope) + { + List boundNames; + if (program.AssociatedData is CachedHoistingScope cached) + { + boundNames = cached.VarNames; + } + else + { + boundNames = new List(); + CachedHoistingScope.GatherVarNames(hoistingScope, boundNames); + } + + return boundNames; + } + + internal static List GetLexNames(this Program program, HoistingScope hoistingScope) + { + List boundNames; + if (program.AssociatedData is CachedHoistingScope cached) + { + boundNames = cached.LexNames; + } + else + { + boundNames = new List(); + CachedHoistingScope.GatherLexNames(hoistingScope, boundNames); + } + + return boundNames; + } } diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index 19690bf324..d79bc9ae12 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -12,8 +12,8 @@ public partial class Engine { internal IModuleLoader ModuleLoader { get; set; } = null!; - private readonly Dictionary _modules = new(); - private readonly Dictionary _builders = new(); + private readonly Dictionary _modules = new(StringComparer.Ordinal); + private readonly Dictionary _builders = new(StringComparer.Ordinal); /// /// https://tc39.es/ecma262/#sec-getactivescriptormodule @@ -49,7 +49,7 @@ internal ModuleRecord LoadModule(string? referencingModuleLocation, string speci return module; } - private CyclicModuleRecord LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution) + private BuilderModuleRecord LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution) { var parsedModule = moduleBuilder.Parse(); var module = new BuilderModuleRecord(this, Realm, parsedModule, null, false); @@ -59,7 +59,7 @@ private CyclicModuleRecord LoadFromBuilder(string specifier, ModuleBuilder modul return module; } - private CyclicModuleRecord LoaderFromModuleLoader(ResolvedSpecifier moduleResolution) + private SourceTextModuleRecord LoaderFromModuleLoader(ResolvedSpecifier moduleResolution) { var parsedModule = ModuleLoader.LoadModule(this, moduleResolution); var module = new SourceTextModuleRecord(this, Realm, parsedModule, moduleResolution.Uri?.LocalPath, false); diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 7fa5e8317d..772b7bbafd 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Esprima; using Esprima.Ast; using Jint.Native; @@ -24,18 +25,22 @@ namespace Jint /// /// Engine is the main API to JavaScript interpretation. Engine instances are not thread-safe. /// + [DebuggerTypeProxy(typeof(EngineDebugView))] public sealed partial class Engine : IDisposable { + private static readonly Options _defaultEngineOptions = new(); + private readonly ParserOptions _defaultParserOptions; private readonly JavaScriptParser _defaultParser; - internal readonly ExecutionContextStack _executionContexts; + private readonly ExecutionContextStack _executionContexts; private JsValue _completionValue = JsValue.Undefined; internal EvaluationContext? _activeEvaluationContext; + internal ErrorDispatchInfo? _error; private readonly EventLoop _eventLoop = new(); - private readonly Agent _agent = new Agent(); + private readonly Agent _agent = new(); // lazy properties private DebugHandler? _debugHandler; @@ -51,10 +56,13 @@ public sealed partial class Engine : IDisposable internal readonly JsValueArrayPool _jsValueArrayPool; internal readonly ExtensionMethodCache _extensionMethods; - public ITypeConverter ClrTypeConverter { get; internal set; } = null!; + public ITypeConverter ClrTypeConverter { get; internal set; } // cache of types used when resolving CLR type names - internal readonly Dictionary TypeCache = new(); + internal readonly Dictionary TypeCache = new(StringComparer.Ordinal); + + // we use registered type reference as prototype if it's known + internal Dictionary? _typeReferences; // cache for already wrapped CLR objects to keep object identity internal ConditionalWeakTable? _objectWrapperCache; @@ -73,7 +81,7 @@ public sealed partial class Engine : IDisposable /// /// Constructs a new engine instance. /// - public Engine() : this((Action?) null) + public Engine() : this(null, null) { } @@ -81,14 +89,14 @@ public Engine() : this((Action?) null) /// Constructs a new engine instance and allows customizing options. /// public Engine(Action? options) - : this((engine, opts) => options?.Invoke(opts)) + : this(null, options != null ? (_, opts) => options.Invoke(opts) : null) { } /// /// Constructs a new engine with a custom instance. /// - public Engine(Options options) : this((e, o) => e.Options = options) + public Engine(Options options) : this(options, null) { } @@ -96,14 +104,21 @@ public Engine(Options options) : this((e, o) => e.Options = options) /// Constructs a new engine instance and allows customizing options. /// /// The provided engine instance in callback is not guaranteed to be fully configured - public Engine(Action options) + public Engine(Action options) : this(null, options) + { + } + + private Engine(Options? options, Action? configure) { Advanced = new AdvancedOperations(this); + ClrTypeConverter = new DefaultTypeConverter(this); _executionContexts = new ExecutionContextStack(2); - Options = new Options(); - options?.Invoke(this, Options); + // we can use default options if there's no action to modify it + Options = options ?? (configure is not null ? new Options() : _defaultEngineOptions); + + configure?.Invoke(this, Options); _extensionMethods = ExtensionMethodCache.Build(Options.Interop.ExtensionMethodTypes); @@ -538,7 +553,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) ExceptionHelper.ThrowReferenceError(Realm, reference); } - if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0 + if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == InternalTypes.None && _referenceResolver.TryPropertyReference(this, reference, ref baseValue)) { return baseValue; @@ -569,7 +584,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) // check if we are accessing a string, boxing operation can be costly to do index access // we have good chance to have fast path with integer or string indexer ObjectInstance? o = null; - if ((property._type & (InternalTypes.String | InternalTypes.Integer)) != 0 + if ((property._type & (InternalTypes.String | InternalTypes.Integer)) != InternalTypes.None && baseValue is JsString s && TryHandleStringValue(property, s, ref o, out var jsValue)) { @@ -603,7 +618,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) return JsValue.Undefined; } - var callable = (ICallable) getter.AsObject(); + var callable = (ICallable) getter; return callable.Call(baseValue, Arguments.Empty); } } @@ -626,7 +641,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) private bool TryHandleStringValue(JsValue property, JsString s, ref ObjectInstance? o, out JsValue jsValue) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { jsValue = JsNumber.Create((uint) s.Length); return true; @@ -902,14 +917,12 @@ private void GlobalDeclarationInstantiation( Script script, GlobalEnvironmentRecord env) { - var strict = _isStrict || StrictModeScope.IsStrictModeCode; - var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script); + var hoistingScope = script.GetHoistingScope(); var functionDeclarations = hoistingScope._functionDeclarations; - var varDeclarations = hoistingScope._variablesDeclarations; var lexDeclarations = hoistingScope._lexicalDeclarations; - var functionToInitialize = new LinkedList(); - var declaredFunctionNames = new HashSet(); + var functionToInitialize = new List(); + var declaredFunctionNames = new HashSet(StringComparer.Ordinal); var declaredVarNames = new List(); var realm = Realm; @@ -929,74 +942,56 @@ private void GlobalDeclarationInstantiation( } declaredFunctionNames.Add(fn); - functionToInitialize.AddFirst(new JintFunctionDefinition(d)); + functionToInitialize.Add(new JintFunctionDefinition(d)); } } } - var boundNames = new List(); - if (varDeclarations != null) + var varNames = script.GetVarNames(hoistingScope); + for (var j = 0; j < varNames.Count; j++) { - for (var i = 0; i < varDeclarations.Count; i++) + var vn = varNames[j]; + if (env.HasLexicalDeclaration(vn)) { - var d = varDeclarations[i]; - boundNames.Clear(); - d.GetBoundNames(boundNames); - for (var j = 0; j < boundNames.Count; j++) - { - var vn = boundNames[j]; - - if (env.HasLexicalDeclaration(vn)) - { - ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared"); - } - - if (!declaredFunctionNames.Contains(vn)) - { - var vnDefinable = env.CanDeclareGlobalVar(vn); - if (!vnDefinable) - { - ExceptionHelper.ThrowTypeError(realm); - } + ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared"); + } - declaredVarNames.Add(vn); - } + if (!declaredFunctionNames.Contains(vn)) + { + var vnDefinable = env.CanDeclareGlobalVar(vn); + if (!vnDefinable) + { + ExceptionHelper.ThrowTypeError(realm); } + + declaredVarNames.Add(vn); } } PrivateEnvironmentRecord? privateEnv = null; - if (lexDeclarations != null) + var lexNames = script.GetLexNames(hoistingScope); + for (var i = 0; i < lexNames.Count; i++) { - for (var i = 0; i < lexDeclarations.Count; i++) + var (dn, constant) = lexNames[i]; + if (env.HasVarDeclaration(dn) || env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn)) { - var d = lexDeclarations[i]; - boundNames.Clear(); - d.GetBoundNames(boundNames); - for (var j = 0; j < boundNames.Count; j++) - { - var dn = boundNames[j]; - if (env.HasVarDeclaration(dn) - || env.HasLexicalDeclaration(dn) - || env.HasRestrictedGlobalProperty(dn)) - { - ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared"); - } + ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared"); + } - if (d.IsConstantDeclaration()) - { - env.CreateImmutableBinding(dn, strict: true); - } - else - { - env.CreateMutableBinding(dn, canBeDeleted: false); - } - } + if (constant) + { + env.CreateImmutableBinding(dn, strict: true); + } + else + { + env.CreateMutableBinding(dn, canBeDeleted: false); } } - foreach (var f in functionToInitialize) + // we need to go trough in reverse order to handle the hoisting correctly + for (var i = functionToInitialize.Count - 1; i > -1; i--) { + var f = functionToInitialize[i]; var fn = f.Name!; if (env.HasLexicalDeclaration(fn)) @@ -1008,11 +1003,7 @@ private void GlobalDeclarationInstantiation( env.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false); } - for (var i = 0; i < declaredVarNames.Count; i++) - { - var vn = declaredVarNames[i]; - env.CreateGlobalVarBinding(vn, canBeDeleted: false); - } + env.CreateGlobalVarBindings(declaredVarNames, canBeDeleted: false); } /// @@ -1184,7 +1175,7 @@ internal void EvalDeclarationInstantiation( PrivateEnvironmentRecord? privateEnv, bool strict) { - var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script); + var hoistingScope = HoistingScope.GetProgramLevelDeclarations(script); var lexEnvRec = (DeclarativeEnvironmentRecord) lexEnv; var varEnvRec = varEnv; @@ -1246,7 +1237,7 @@ internal void EvalDeclarationInstantiation( var functionDeclarations = hoistingScope._functionDeclarations; var functionsToInitialize = new LinkedList(); - var declaredFunctionNames = new HashSet(); + var declaredFunctionNames = new HashSet(StringComparer.Ordinal); if (functionDeclarations != null) { @@ -1545,6 +1536,17 @@ private ObjectInstance Construct( return result; } + internal void SignalError(ErrorDispatchInfo error) + { + _error = error; + } + + internal void RegisterTypeReference(TypeReference reference) + { + _typeReferences ??= new Dictionary(); + _typeReferences[reference.ReferenceType] = reference; + } + public void Dispose() { if (_objectWrapperCache is null) @@ -1560,5 +1562,22 @@ public void Dispose() clearMethod?.Invoke(_objectWrapperCache, Array.Empty()); #endif } + + [DebuggerDisplay("Engine")] + private sealed class EngineDebugView + { + private readonly Engine _engine; + + public EngineDebugView(Engine engine) + { + _engine = engine; + } + + public ObjectInstance Globals => _engine.Realm.GlobalObject; + public Options Options => _engine.Options; + + public EnvironmentRecord VariableEnvironment => _engine.ExecutionContext.VariableEnvironment; + public EnvironmentRecord LexicalEnvironment => _engine.ExecutionContext.LexicalEnvironment; + } } } diff --git a/Jint/EsprimaExtensions.cs b/Jint/EsprimaExtensions.cs index dd0f8047da..c3cc87f85f 100644 --- a/Jint/EsprimaExtensions.cs +++ b/Jint/EsprimaExtensions.cs @@ -91,7 +91,6 @@ internal static bool IsFunctionDefinition(this T node) where T : Node return type is Nodes.FunctionExpression or Nodes.ArrowFunctionExpression - or Nodes.ArrowParameterPlaceHolder or Nodes.ClassExpression; } diff --git a/Jint/Extensions/Polyfills.cs b/Jint/Extensions/Polyfills.cs new file mode 100644 index 0000000000..f818922b0f --- /dev/null +++ b/Jint/Extensions/Polyfills.cs @@ -0,0 +1,12 @@ +namespace Jint; + +internal static class Polyfills +{ +#if NETFRAMEWORK || NETSTANDARD2_0 + internal static bool Contains(this string source, char c) => source.IndexOf(c) != -1; +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 + internal static bool StartsWith(this string source, char c) => source.Length > 0 && source[0] == c; +#endif +} diff --git a/Jint/Extensions/ReflectionExtensions.cs b/Jint/Extensions/ReflectionExtensions.cs index 05e81c8aac..2645875bd7 100644 --- a/Jint/Extensions/ReflectionExtensions.cs +++ b/Jint/Extensions/ReflectionExtensions.cs @@ -139,7 +139,7 @@ public static bool TryConvertViaTypeCoercion( return true; } - if (memberType == typeof(bool) && (valueCoercionType & ValueCoercionType.Boolean) != 0) + if (memberType == typeof(bool) && (valueCoercionType & ValueCoercionType.Boolean) != ValueCoercionType.None) { converted = TypeConverter.ToBoolean(value); return true; @@ -147,7 +147,7 @@ public static bool TryConvertViaTypeCoercion( if (memberType == typeof(string) && !value.IsNullOrUndefined() - && (valueCoercionType & ValueCoercionType.String) != 0) + && (valueCoercionType & ValueCoercionType.String) != ValueCoercionType.None) { // we know how to print out correct string presentation for primitives // that are non-null and non-undefined @@ -155,7 +155,7 @@ public static bool TryConvertViaTypeCoercion( return true; } - if (memberType is not null && memberType.IsClrNumericCoercible() && (valueCoercionType & ValueCoercionType.Number) != 0) + if (memberType is not null && memberType.IsClrNumericCoercible() && (valueCoercionType & ValueCoercionType.Number) != ValueCoercionType.None) { // we know how to print out correct string presentation for primitives // that are non-null and non-undefined diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 7d3a1fc0d8..9c396f2c86 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -28,12 +28,11 @@ private HoistingScope( } public static HoistingScope GetProgramLevelDeclarations( - bool strict, Program script, bool collectVarNames = false, bool collectLexicalNames = false) { - var treeWalker = new ScriptWalker(strict, collectVarNames, collectLexicalNames); + var treeWalker = new ScriptWalker(collectVarNames, collectLexicalNames); treeWalker.Visit(script, null); return new HoistingScope( @@ -46,7 +45,7 @@ public static HoistingScope GetProgramLevelDeclarations( public static HoistingScope GetFunctionLevelDeclarations(bool strict, IFunction node) { - var treeWalker = new ScriptWalker(strict, collectVarNames: true, collectLexicalNames: true); + var treeWalker = new ScriptWalker(collectVarNames: true, collectLexicalNames: true); treeWalker.Visit(node.Body, null); return new HoistingScope( @@ -63,7 +62,7 @@ public static HoistingScope GetModuleLevelDeclarations( bool collectLexicalNames = false) { // modules area always strict - var treeWalker = new ScriptWalker(strict: true, collectVarNames, collectLexicalNames); + var treeWalker = new ScriptWalker(collectVarNames, collectLexicalNames); treeWalker.Visit(module, null); return new HoistingScope( treeWalker._functions, @@ -134,8 +133,8 @@ public static void GetImportsAndExports( treeWalker.Visit(module); importEntries = treeWalker._importEntries; - requestedModules = treeWalker._requestedModules ?? new(); - var importedBoundNames = new HashSet(); + requestedModules = treeWalker._requestedModules ?? new(StringComparer.Ordinal); + var importedBoundNames = new HashSet(StringComparer.Ordinal); if (importEntries != null) { @@ -172,9 +171,9 @@ public static void GetImportsAndExports( for (var j = 0; j < importEntries!.Count; j++) { var ie = importEntries[j]; - if (ie.LocalName == ee.LocalName) + if (string.Equals(ie.LocalName, ee.LocalName, StringComparison.Ordinal)) { - if (ie.ImportName == "*") + if (string.Equals(ie.ImportName, "*", StringComparison.Ordinal)) { localExportEntries.Add(ee); } @@ -188,7 +187,7 @@ public static void GetImportsAndExports( } } } - else if (ee.ImportName == "*" && ee.ExportName is null) + else if (string.Equals(ee.ImportName, "*", StringComparison.Ordinal) && ee.ExportName is null) { starExportEntries.Add(ee); } @@ -204,7 +203,6 @@ private sealed class ScriptWalker { internal List? _functions; - private readonly bool _strict; private readonly bool _collectVarNames; internal List? _variableDeclarations; internal List? _varNames; @@ -213,9 +211,8 @@ private sealed class ScriptWalker internal List? _lexicalDeclarations; internal List? _lexicalNames; - public ScriptWalker(bool strict, bool collectVarNames, bool collectLexicalNames) + public ScriptWalker(bool collectVarNames, bool collectLexicalNames) { - _strict = strict; _collectVarNames = collectVarNames; _collectLexicalNames = collectLexicalNames; } @@ -281,7 +278,6 @@ public void Visit(Node node, Node? parent) if (childType != Nodes.FunctionDeclaration && childType != Nodes.ArrowFunctionExpression - && childType != Nodes.ArrowParameterPlaceHolder && childType != Nodes.FunctionExpression && !childNode.ChildNodes.IsEmpty()) { @@ -304,14 +300,14 @@ internal void Visit(Node node) if (childNode.Type == Nodes.ImportDeclaration) { _importEntries ??= new(); - _requestedModules ??= new(); + _requestedModules ??= new(StringComparer.Ordinal); var import = (ImportDeclaration) childNode; import.GetImportEntries(_importEntries, _requestedModules); } else if (childNode.Type is Nodes.ExportAllDeclaration or Nodes.ExportDefaultDeclaration or Nodes.ExportNamedDeclaration) { _exportEntries ??= new(); - _requestedModules ??= new(); + _requestedModules ??= new(StringComparer.Ordinal); var export = (ExportDeclaration) childNode; export.GetExportEntries(_exportEntries, _requestedModules); } diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index cc591bc37b..bcc00fb46c 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -1,14 +1,24 @@ - + en-US net462;netstandard2.0;netstandard2.1;net6.0 + Jint.snk true - latest true + + latest enable enable true + + latest-Recommended + + true + README.md + + $(NoWarn);1591 + @@ -16,13 +26,12 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index ab861f194a..c115cf8cf6 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -19,7 +19,7 @@ public static class JsValueExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimitive(this JsValue value) { - return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != 0; + return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != InternalTypes.None; } [Pure] @@ -76,28 +76,28 @@ public static bool IsRegExp(this JsValue value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsObject(this JsValue value) { - return (value._type & InternalTypes.Object) != 0; + return (value._type & InternalTypes.Object) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsString(this JsValue value) { - return (value._type & InternalTypes.String) != 0; + return (value._type & InternalTypes.String) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNumber(this JsValue value) { - return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != 0; + return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBigInt(this JsValue value) { - return (value._type & InternalTypes.BigInt) != 0; + return (value._type & InternalTypes.BigInt) != InternalTypes.None; } [Pure] @@ -391,6 +391,40 @@ public static ulong[] AsBigUint64Array(this JsValue value) return ((JsTypedArray) value).ToNativeArray(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFloat32Array(this JsValue value) + { + return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float32 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float[] AsFloat32Array(this JsValue value) + { + if (!value.IsFloat32Array()) + { + ThrowWrongTypeException(value, "Float32Array"); + } + + return ((JsTypedArray) value).ToNativeArray(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFloat64Array(this JsValue value) + { + return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float64 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double[] AsFloat64Array(this JsValue value) + { + if (!value.IsFloat64Array()) + { + ThrowWrongTypeException(value, "Float64Array"); + } + + return ((JsTypedArray) value).ToNativeArray(); + } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Jint/Key.cs b/Jint/Key.cs index 20057126e4..f987842831 100644 --- a/Jint/Key.cs +++ b/Jint/Key.cs @@ -13,7 +13,7 @@ namespace Jint private Key(string name) { Name = name; - HashCode = name.GetHashCode(); + HashCode = StringComparer.Ordinal.GetHashCode(name); } internal readonly string Name; @@ -29,28 +29,28 @@ public static implicit operator Key(string name) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(in Key a, in Key b) { - return a.HashCode == b.HashCode && a.Name == b.Name; + return a.HashCode == b.HashCode && string.Equals(a.Name, b.Name, StringComparison.Ordinal); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(in Key a, in Key b) { - return a.HashCode != b.HashCode || a.Name != b.Name; + return a.HashCode != b.HashCode || !string.Equals(a.Name, b.Name, StringComparison.Ordinal); } public static bool operator ==(in Key a, string b) { - return a.Name == b; + return string.Equals(a.Name, b, StringComparison.Ordinal); } public static bool operator !=(in Key a, string b) { - return a.Name != b; + return !string.Equals(a.Name, b, StringComparison.Ordinal); } public bool Equals(Key other) { - return HashCode == other.HashCode && Name == other.Name; + return HashCode == other.HashCode && string.Equals(Name, other.Name, StringComparison.Ordinal); } public override bool Equals(object? obj) diff --git a/Jint/ModuleBuilder.cs b/Jint/ModuleBuilder.cs index 08c26dda8c..634f4bc2c2 100644 --- a/Jint/ModuleBuilder.cs +++ b/Jint/ModuleBuilder.cs @@ -13,7 +13,7 @@ public sealed class ModuleBuilder private readonly string _specifier; private Module? _module; private readonly List _sourceRaw = new(); - private readonly Dictionary _exports = new(); + private readonly Dictionary _exports = new(StringComparer.Ordinal); private readonly ParserOptions _options; internal ModuleBuilder(Engine engine, string specifier) diff --git a/Jint/Native/Argument/ArgumentsInstance.cs b/Jint/Native/Argument/ArgumentsInstance.cs index 41d83967a9..43693b1eda 100644 --- a/Jint/Native/Argument/ArgumentsInstance.cs +++ b/Jint/Native/Argument/ArgumentsInstance.cs @@ -16,7 +16,7 @@ namespace Jint.Native.Argument internal sealed class ArgumentsInstance : ObjectInstance { // cache property container for array iteration for less allocations - private static readonly ThreadLocal> _mappedNamed = new(() => new HashSet()); + private static readonly ThreadLocal> _mappedNamed = new(() => new HashSet(StringComparer.Ordinal)); private FunctionInstance _func = null!; private Key[] _names = null!; @@ -66,7 +66,7 @@ protected override void Initialize() CreateDataProperty(JsString.Create(i), val); } - DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.CustomJsValue)); + DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None)); } else { diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index 52de03cb64..204e0156fc 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using System.Collections; using Jint.Collections; using Jint.Native.Function; diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index f81b528179..2d894c2197 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -15,34 +15,32 @@ public class ArrayInstance : ObjectInstance, IEnumerable private const int MaxDenseArrayLength = 10_000_000; // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary - // entries are lazy and can be either of type PropertyDescriptor or plain JsValue while there is no need for extra info - internal object?[]? _dense; - private Dictionary? _sparse; + // when we have plain JsValues, _denseValues is used - if any operation occurs which requires setting more property flags + // we convert to _sparse and _denseValues is set to null - it will be a slow array + internal JsValue?[]? _dense; + + private Dictionary? _sparse; private ObjectChangeFlags _objectChangeFlags; - private bool _isObjectArray = true; + + private ArrayConstructor? _constructor; private protected ArrayInstance(Engine engine, InternalTypes type) : base(engine, type: type) { - _dense = System.Array.Empty(); + _dense = System.Array.Empty(); } private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = 0) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - - if (capacity > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, capacity); - } + InitializePrototypeAndValidateCapacity(engine, capacity); if (capacity < MaxDenseArrayLength) { - _dense = capacity > 0 ? new object?[capacity] : System.Array.Empty(); + _dense = capacity > 0 ? new JsValue?[capacity] : System.Array.Empty(); } else { - _sparse = new Dictionary(1024); + _sparse = new Dictionary(1024); } _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); @@ -50,42 +48,21 @@ private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = private protected ArrayInstance(Engine engine, JsValue[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - Initialize(engine, items); - } + InitializePrototypeAndValidateCapacity(engine, capacity: 0); - private protected ArrayInstance(Engine engine, PropertyDescriptor[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) - { - Initialize(engine, items); + _dense = items; + _length = new PropertyDescriptor(items.Length, PropertyFlag.OnlyWritable); } - private protected ArrayInstance(Engine engine, object[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) + private void InitializePrototypeAndValidateCapacity(Engine engine, uint capacity) { - Initialize(engine, items); - } + _constructor = engine.Realm.Intrinsics.Array; + _prototype = _constructor.PrototypeObject; - private void Initialize(Engine engine, T[] items) where T : class - { - if (items.Length > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, (uint) items.Length); - } - - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - _isObjectArray = typeof(T) == typeof(object); - - int length; - if (items == null || items.Length == 0) - { - _dense = System.Array.Empty(); - length = 0; - } - else + if (capacity > 0 && capacity > engine.Options.Constraints.MaxArraySize) { - _dense = items; - length = items.Length; + ThrowMaximumArraySizeReachedException(engine, capacity); } - - _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); } public sealed override bool IsArrayLike => true; @@ -93,7 +70,7 @@ private void Initialize(Engine engine, T[] items) where T : class public sealed override bool IsArray() => true; internal sealed override bool HasOriginalIterator - => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _engine.Realm.Intrinsics.Array.PrototypeObject._originalIteratorFunction); + => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _constructor?.PrototypeObject._originalIteratorFunction); /// /// Checks whether there have been changes to object prototype chain which could render fast access patterns impossible. @@ -102,180 +79,186 @@ internal bool CanUseFastAccess { get { - if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != 0) + if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != ObjectChangeFlags.None) { // could be a mutating property for example, length might change, not safe anymore return false; } if (_prototype is not ArrayPrototype arrayPrototype - || !ReferenceEquals(_prototype, _engine.Realm.Intrinsics.Array.PrototypeObject)) + || !ReferenceEquals(_prototype, _constructor?.PrototypeObject)) { // somebody has switched prototype return false; } - if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != 0) + if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != ObjectChangeFlags.None) { // maybe somebody moved integer property to prototype? not safe anymore return false; } if (arrayPrototype.Prototype is not ObjectPrototype arrayPrototypePrototype - || !ReferenceEquals(arrayPrototypePrototype, _engine.Realm.Intrinsics.Array.PrototypeObject.Prototype)) + || !ReferenceEquals(arrayPrototypePrototype, _constructor.PrototypeObject.Prototype)) { return false; } - return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == 0; + return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == ObjectChangeFlags.None; } } public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { + if (CommonProperties.Length.Equals(property)) + { + return DefineLength(desc); + } + var isArrayIndex = IsArrayIndex(property, out var index); TrackChanges(property, desc, isArrayIndex); if (isArrayIndex) { + ConvertToSparse(); return DefineOwnProperty(index, desc); } - if (property == CommonProperties.Length) + return base.DefineOwnProperty(property, desc); + } + + private bool DefineLength(PropertyDescriptor desc) + { + var value = desc.Value; + if (ReferenceEquals(value, null)) { - var value = desc.Value; - if (ReferenceEquals(value, null)) - { - return base.DefineOwnProperty(CommonProperties.Length, desc); - } + return base.DefineOwnProperty(CommonProperties.Length, desc); + } - var newLenDesc = new PropertyDescriptor(desc); - uint newLen = TypeConverter.ToUint32(value); - if (newLen != TypeConverter.ToNumber(value)) - { - ExceptionHelper.ThrowRangeError(_engine.Realm); - } + var newLenDesc = new PropertyDescriptor(desc); + uint newLen = TypeConverter.ToUint32(value); + if (newLen != TypeConverter.ToNumber(value)) + { + ExceptionHelper.ThrowRangeError(_engine.Realm); + } - var oldLenDesc = _length; - var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); + var oldLenDesc = _length; + var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); - newLenDesc.Value = newLen; - if (newLen >= oldLen) - { - return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - } + newLenDesc.Value = newLen; + if (newLen >= oldLen) + { + return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + } - if (!oldLenDesc.Writable) - { - return false; - } + if (!oldLenDesc.Writable) + { + return false; + } - bool newWritable; - if (!newLenDesc.WritableSet || newLenDesc.Writable) - { - newWritable = true; - } - else - { - newWritable = false; - newLenDesc.Writable = true; - } + bool newWritable; + if (!newLenDesc.WritableSet || newLenDesc.Writable) + { + newWritable = true; + } + else + { + newWritable = false; + newLenDesc.Writable = true; + } - var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - if (!succeeded) - { - return false; - } + var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + if (!succeeded) + { + return false; + } - var count = _dense?.Length ?? _sparse!.Count; - if (count < oldLen - newLen) + var count = _dense?.Length ?? _sparse!.Count; + if (count < oldLen - newLen) + { + if (_dense != null) { - if (_dense != null) + for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) { - for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) + if (_dense[keyIndex] is null) { - if (_dense[keyIndex] == null) - { - continue; - } + continue; + } - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(keyIndex); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(keyIndex); - if (!deleteSucceeded) + newLenDesc.Value = keyIndex + 1; + if (!newWritable) { - newLenDesc.Value = keyIndex + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } - else + } + else + { + // in the case of sparse arrays, treat each concrete element instead of + // iterating over all indexes + var keys = new List(_sparse!.Keys); + var keysCount = keys.Count; + for (var i = 0; i < keysCount; i++) { - // in the case of sparse arrays, treat each concrete element instead of - // iterating over all indexes - var keys = new List(_sparse!.Keys); - var keysCount = keys.Count; - for (var i = 0; i < keysCount; i++) - { - var keyIndex = keys[i]; + var keyIndex = keys[i]; - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); - if (!deleteSucceeded) + newLenDesc.Value = JsNumber.Create(keyIndex + 1); + if (!newWritable) { - newLenDesc.Value = JsNumber.Create(keyIndex + 1); - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } } - else + } + else + { + while (newLen < oldLen) { - while (newLen < oldLen) + // algorithm as per the spec + oldLen--; + var deleteSucceeded = Delete(oldLen); + if (!deleteSucceeded) { - // algorithm as per the spec - oldLen--; - var deleteSucceeded = Delete(oldLen); - if (!deleteSucceeded) + newLenDesc.Value = oldLen + 1; + if (!newWritable) { - newLenDesc.Value = oldLen + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } - } - } - if (!newWritable) - { - base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; + } } + } - return true; + if (!newWritable) + { + base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); } - return base.DefineOwnProperty(property, desc); + return true; } private bool DefineOwnProperty(uint index, PropertyDescriptor desc) @@ -304,19 +287,14 @@ private bool DefineOwnProperty(uint index, PropertyDescriptor desc) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint GetLength() - { - if (_length is null) - { - return 0; - } + internal uint GetLength() => (uint) GetJsNumberLength()._value; - return (uint) ((JsNumber) _length._value!)._value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JsNumber GetJsNumberLength() => _length is null ? JsNumber.PositiveZero : (JsNumber) _length._value!; protected sealed override void AddProperty(JsValue property, PropertyDescriptor descriptor) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property )) { _length = descriptor; return; @@ -327,7 +305,7 @@ protected sealed override void AddProperty(JsValue property, PropertyDescriptor protected sealed override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { descriptor = _length; return _length != null; @@ -338,7 +316,7 @@ protected sealed override bool TryGetProperty(JsValue property, [NotNullWhen(tru public sealed override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) { - if ((types & Types.String) == 0) + if ((types & Types.String) == Types.None) { return base.GetOwnPropertyKeys(types); } @@ -350,7 +328,7 @@ public sealed override List GetOwnPropertyKeys(Types types = Types.None var length = System.Math.Min(temp.Length, GetLength()); for (var i = 0; i < length; i++) { - if (temp[i] != null) + if (temp[i] is not null) { properties.Add(JsString.Create(i)); } @@ -397,39 +375,28 @@ public sealed override IEnumerable> Ge if (temp != null) { var length = System.Math.Min(temp.Length, GetLength()); - for (var i = 0; i < length; i++) + for (uint i = 0; i < length; i++) { var value = temp[i]; - if (value != null) + if (value is not null) { - if (value is not PropertyDescriptor descriptor) + if (_sparse is null || !_sparse.TryGetValue(i, out var descriptor) || descriptor is null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - temp[i] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); + _sparse ??= new Dictionary(); + _sparse[i] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } yield return new KeyValuePair(TypeConverter.ToString(i), descriptor); } } } - else + else if (_sparse != null) { - foreach (var entry in _sparse!) + foreach (var entry in _sparse) { var value = entry.Value; if (value is not null) { - if (value is not PropertyDescriptor descriptor) - { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - _sparse[entry.Key] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); - } - yield return new KeyValuePair(TypeConverter.ToString(entry.Key), descriptor); + yield return new KeyValuePair(TypeConverter.ToString(entry.Key), value); } } } @@ -447,14 +414,14 @@ public sealed override IEnumerable> Ge public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } if (IsArrayIndex(property, out var index)) { - if (TryGetDescriptor(index, out var result)) + if (TryGetDescriptor(index, createIfMissing: true, out var result)) { return result; } @@ -482,7 +449,7 @@ public sealed override JsValue Get(JsValue property, JsValue receiver) return value; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { var length = _length?._value; if (length is not null) @@ -497,33 +464,23 @@ public sealed override JsValue Get(JsValue property, JsValue receiver) public sealed override bool Set(JsValue property, JsValue value, JsValue receiver) { var isSafeSelfTarget = IsSafeSelfTarget(receiver); - if (isSafeSelfTarget && IsArrayIndex(property, out var index)) + if (isSafeSelfTarget && CanUseFastAccess) { - var temp = _dense; - if (temp is not null && CanUseFastAccess) + if (!ReferenceEquals(property, CommonProperties.Length) && IsArrayIndex(property, out var index)) { - var current = index < temp.Length ? temp[index] : null; - if (current is not PropertyDescriptor p || p.IsDefaultArrayValueDescriptor()) - { - SetIndexValue(index, value, true); - return true; - } + SetIndexValue(index, value, updateLength: true); + return true; } - // slower and more allocating - if (TryGetDescriptor(index, out var descriptor)) - { - if (descriptor.IsDefaultArrayValueDescriptor()) - { - // fast path with direct write without allocations - descriptor.Value = value; - return true; - } - } - else if (CanUseFastAccess) + if (CommonProperties.Length.Equals(property) + && _length is { Writable: true } + && value is JsNumber jsNumber + && jsNumber.IsInteger() + && jsNumber._value <= MaxDenseArrayLength + && jsNumber._value >= GetLength()) { - // we know it's to be written to own array backing field as new value - SetIndexValue(index, value, true); + // we don't need explicit resize + _length.Value = jsNumber; return true; } } @@ -544,6 +501,27 @@ public sealed override bool HasProperty(JsValue property) return base.HasProperty(property); } + internal bool HasProperty(ulong index) + { + if (index < uint.MaxValue) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length && temp[index] is not null) + { + return true; + } + } + else if (_sparse!.ContainsKey((uint) index)) + { + return true; + } + } + + return base.HasProperty(index); + } + protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { var isArrayIndex = IsArrayIndex(property, out var index); @@ -552,7 +530,7 @@ protected internal sealed override void SetOwnProperty(JsValue property, Propert { WriteArrayValue(index, desc); } - else if (property == CommonProperties.Length) + else if (CommonProperties.Length.Equals(property)) { _length = desc; } @@ -568,11 +546,15 @@ private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArra if (isArrayIndex) { - if (!desc.IsDefaultArrayValueDescriptor()) + if (!desc.IsDefaultArrayValueDescriptor() && desc.Flags != PropertyFlag.None) { _objectChangeFlags |= ObjectChangeFlags.NonDefaultDataDescriptorUsage; } - _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + + if (GetType() != typeof(JsArray)) + { + _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + } } else { @@ -580,57 +562,43 @@ private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArra } } - public sealed override void RemoveOwnProperty(JsValue p) + public sealed override void RemoveOwnProperty(JsValue property) { - if (IsArrayIndex(p, out var index)) + if (IsArrayIndex(property, out var index)) { Delete(index); } - if (p == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } - base.RemoveOwnProperty(p); + base.RemoveOwnProperty(property); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsArrayIndex(JsValue p, out uint index) { - if (p is JsNumber number) + if (p.IsNumber()) { - var value = number._value; + var value = ((JsNumber) p)._value; var intValue = (uint) value; index = intValue; return value == intValue && intValue != uint.MaxValue; } - index = ParseArrayIndex(p.ToString()); + index = !p.IsSymbol() ? ParseArrayIndex(p.ToString()) : uint.MaxValue; return index != uint.MaxValue; // 15.4 - Use an optimized version of the specification // return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ParseArrayIndex(string p) { - if (p.Length == 0) - { - return uint.MaxValue; - } - - if (p.Length > 1 && p[0] == '0') - { - // If p is a number that start with '0' and is not '0' then - // its ToString representation can't be the same a p. This is - // not a valid array index. '01' !== ToString(ToUInt32('01')) - // http://www.ecma-international.org/ecma-262/5.1/#sec-15.4 - - return uint.MaxValue; - } - - if (!uint.TryParse(p, out var d)) + if (p.Length == 0 || p.Length > 1 && !IsInRange(p[0], '1', '9') || !uint.TryParse(p, out var d)) { return uint.MaxValue; } @@ -638,6 +606,9 @@ internal static uint ParseArrayIndex(string p) return d; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInRange(char c, char min, char max) => c - (uint) min <= max - (uint) min; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetIndexValue(uint index, JsValue? value, bool updateLength) { @@ -660,17 +631,19 @@ private void EnsureCorrectLength(uint index) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetLength(ulong length) + internal void SetLength(ulong length) => SetLength(JsNumber.Create(length)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetLength(JsNumber length) { - var number = JsNumber.Create(length); if (Extensible && _length!._flags == PropertyFlag.OnlyWritable) { - _length!.Value = number; + _length!.Value = length; } else { // slow path - Set(CommonProperties.Length, number, true); + Set(CommonProperties.Length, length, true); } } @@ -704,34 +677,44 @@ internal bool DeletePropertyOrThrow(uint index) return true; } - internal bool Delete(uint index) + private bool Delete(uint index) => Delete(index, unwrapFromNonDataDescriptor: false, out _); + + private bool Delete(uint index, bool unwrapFromNonDataDescriptor, out JsValue? deletedValue) { + TryGetDescriptor(index, createIfMissing: false, out var desc); + // check fast path var temp = _dense; if (temp != null) { if (index < (uint) temp.Length) { - var value = temp[index]; - if (value is JsValue || value is PropertyDescriptor { Configurable: true }) + if (desc is null || desc.Configurable) { + deletedValue = temp[index]; temp[index] = null; return true; } } } - if (!TryGetDescriptor(index, out var desc)) + if (desc is null) { + deletedValue = null; return true; } if (desc.Configurable) { - DeleteAt(index); + _sparse!.Remove(index); + deletedValue = desc.IsDataDescriptor() || unwrapFromNonDataDescriptor + ? UnwrapJsValue(desc) + : null; + return true; } + deletedValue = null; return false; } @@ -754,8 +737,14 @@ internal bool DeleteAt(uint index) return false; } - private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescriptor? descriptor) + private bool TryGetDescriptor(uint index, bool createIfMissing, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { + if (!createIfMissing && _sparse is null) + { + descriptor = null; + return false; + } + descriptor = null; var temp = _dense; if (temp != null) @@ -763,37 +752,26 @@ private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescri if (index < (uint) temp.Length) { var value = temp[index]; - if (value is JsValue jsValue) + if (value is not null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) + if (_sparse is null || !_sparse.TryGetValue(index, out descriptor) || descriptor is null) { - temp = _dense!; + _sparse ??= new Dictionary(); + _sparse[index] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } - temp[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (value is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; - } - } - return descriptor != null; - } - if (_sparse!.TryGetValue(index, out var sparseValue)) - { - if (sparseValue is JsValue jsValue) - { - _sparse[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (sparseValue is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; + descriptor.Value = value; + return true; + } } + return false; } + _sparse?.TryGetValue(index, out descriptor); return descriptor is not null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryGetValue(uint index, out JsValue value) { value = GetValue(index, unwrapFromNonDataDescriptor: true)!; @@ -803,6 +781,12 @@ internal bool TryGetValue(uint index, out JsValue value) return true; } + return TryGetValueUnlikely(index, out value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryGetValueUnlikely(uint index, out JsValue value) + { if (!CanUseFastAccess) { // slow path must be checked for prototype @@ -825,54 +809,75 @@ internal bool TryGetValue(uint index, out JsValue value) return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private JsValue? GetValue(uint index, bool unwrapFromNonDataDescriptor) { - object? value = null; var temp = _dense; if (temp != null) { if (index < (uint) temp.Length) { - value = temp[index]; + return temp[index]; } - } - else - { - _sparse!.TryGetValue(index, out value); + return null; } - if (value is JsValue jsValue) - { - return jsValue; - } + return GetValueUnlikely(index, unwrapFromNonDataDescriptor); + } - if (value is PropertyDescriptor propertyDescriptor) + [MethodImpl(MethodImplOptions.NoInlining)] + private JsValue? GetValueUnlikely(uint index, bool unwrapFromNonDataDescriptor) + { + JsValue? value = null; + if (_sparse!.TryGetValue(index, out var descriptor) && descriptor != null) { - return propertyDescriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor ? UnwrapJsValue(propertyDescriptor) : null; + value = descriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor + ? UnwrapJsValue(descriptor) + : null; } - return null; + return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteArrayValue(uint index, object? value) + private void WriteArrayValue(uint index, PropertyDescriptor descriptor) { - var dense = _dense; - if (dense != null && index < (uint) dense.Length) + var temp = _dense; + if (temp != null && descriptor.IsDefaultArrayValueDescriptor()) { - if (value is not null && !_isObjectArray && EnsureCompatibleDense(value is JsValue ? typeof(JsValue) : typeof(PropertyDescriptor))) + if (index < (uint) temp.Length) { - dense = _dense; + temp[index] = descriptor.Value; + } + else + { + WriteArrayValueUnlikely(index, descriptor.Value); } - dense![index] = value; } else { - WriteArrayValueUnlikely(index, value); + WriteArrayValueUnlikely(index, descriptor); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteArrayValue(uint index, JsValue? value) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length) + { + temp[index] = value; + return; + } } + + WriteArrayValueUnlikely(index, value); } - private void WriteArrayValueUnlikely(uint index, object? value) + [MethodImpl(MethodImplOptions.NoInlining)] + private void WriteArrayValueUnlikely(uint index, JsValue? value) { // calculate eagerly so we know if we outgrow var dense = _dense; @@ -892,50 +897,45 @@ private void WriteArrayValueUnlikely(uint index, object? value) } else { - if (dense != null) - { - ConvertToSparse(); - } - - _sparse![index] = value; + ConvertToSparse(); + _sparse![index] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } } - /// - /// Converts to object array when needed. Returns true if conversion was made. - /// - private bool EnsureCompatibleDense(Type expectedElementType) + private void WriteArrayValueUnlikely(uint index, PropertyDescriptor? value) { - if (!_isObjectArray) + if (_sparse == null) { - return CheckConversionUnlikely(expectedElementType); + ConvertToSparse(); } - return false; + _sparse![index] = value; } - private bool CheckConversionUnlikely(Type expectedElementType) + private void ConvertToSparse() { - var currentElementType = _dense!.GetType().GetElementType(); - if (currentElementType != typeof(object) && !expectedElementType.IsAssignableFrom(currentElementType)) + // need to move data + var temp = _dense; + + if (temp is null) { - // triggers conversion for array - EnsureCapacity((uint) _dense.Length, force: true); - return true; + return; } - return false; - } - - private void ConvertToSparse() - { - _sparse = new Dictionary(_dense!.Length <= 1024 ? _dense.Length : 0); - // need to move data - for (uint i = 0; i < (uint) _dense.Length; ++i) + _sparse ??= new Dictionary(); + for (uint i = 0; i < (uint) temp.Length; ++i) { - if (_dense[i] != null) + var value = temp[i]; + if (value is not null) + { + _sparse.TryGetValue(i, out var descriptor); + descriptor ??= new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); + descriptor.Value = value; + _sparse[i] = descriptor; + } + else { - _sparse[i] = _dense[i]; + _sparse.Remove(i); } } @@ -962,10 +962,9 @@ internal void EnsureCapacity(uint capacity, bool force = false) } // need to grow - var newArray = new object[capacity]; + var newArray = new JsValue[capacity]; System.Array.Copy(dense, newArray, dense.Length); _dense = newArray; - _isObjectArray = true; } public JsValue[] ToArray() @@ -1018,16 +1017,9 @@ private IEnumerable Enumerate() for (var i = 0; i < length; i++) { var value = temp[i]; - if (value != null) + if (value is not null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry(i, (JsValue) value); - } - else - { - yield return new IndexedEntry(i, descriptor.Value); - } + yield return new IndexedEntry(i, value); } } } @@ -1035,17 +1027,10 @@ private IEnumerable Enumerate() { foreach (var entry in _sparse!) { - var value = entry.Value; - if (value is not null) + var descriptor = entry.Value; + if (descriptor is not null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry((int) entry.Key, (JsValue) value); - } - else - { - yield return new IndexedEntry((int) entry.Key, descriptor.Value); - } + yield return new IndexedEntry((int) entry.Key, descriptor.Value); } } } @@ -1069,7 +1054,7 @@ public void Push(JsValue value) } else { - WriteValueSlow(n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); + WriteValueSlow(n, value); } // check if we can set length fast without breaking ECMA specification @@ -1135,6 +1120,27 @@ public uint Push(JsValue[] values) return (uint) n; } + public JsValue Pop() + { + var len = GetJsNumberLength(); + if (JsNumber.PositiveZero.Equals(len)) + { + SetLength(len); + return Undefined; + } + + var newLength = (uint) len._value - 1; + + if (!Delete(newLength, unwrapFromNonDataDescriptor: true, out var element)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm); + } + + SetLength(newLength); + + return element ?? Undefined; + } + private bool CanSetLength() { if (!_length!.IsAccessorDescriptor()) @@ -1146,15 +1152,15 @@ private bool CanSetLength() } [MethodImpl(MethodImplOptions.NoInlining)] - private void WriteValueSlow(double n, PropertyDescriptor desc) + private void WriteValueSlow(double n, JsValue value) { - if (n < uint.MaxValue) + if (n < ArrayOperations.MaxArrayLength) { - WriteArrayValue((uint) n, desc); + WriteArrayValue((uint) n, value); } else { - DefinePropertyOrThrow((uint) n, desc); + DefinePropertyOrThrow((uint) n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); } } @@ -1166,7 +1172,7 @@ internal JsArray Map(JsValue[] arguments) var len = GetLength(); var callable = GetCallable(callbackfn); - var a = Engine.Realm.Intrinsics.Array.ArrayCreate(len); + var a = _engine.Realm.Intrinsics.Array.ArrayCreate(len); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = this; for (uint k = 0; k < len; k++) @@ -1325,21 +1331,18 @@ internal void CopyValues(JsArray source, uint sourceStartIndex, uint targetStart } var dense = _dense; - if (dense != null && sourceDense != null - && (uint) dense.Length >= targetStartIndex + length - && dense[targetStartIndex] is null) + if (dense != null + && sourceDense != null + && (uint) dense.Length >= targetStartIndex + length + && dense[targetStartIndex] is null) { uint j = 0; for (var i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++) { - object? sourceValue; - if (i < (uint) sourceDense.Length && sourceDense[i] != null) + JsValue? sourceValue; + if (i < (uint) sourceDense.Length && sourceDense[i] is not null) { sourceValue = sourceDense[i]; - if (sourceValue is PropertyDescriptor propertyDescriptor) - { - sourceValue = UnwrapJsValue(propertyDescriptor); - } } else { diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 19493e32f9..f8d709e1b7 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -3,7 +3,6 @@ using Jint.Native.Object; using Jint.Native.TypedArray; using Jint.Runtime; -using Jint.Runtime.Descriptors; namespace Jint.Native.Array { @@ -57,7 +56,7 @@ public virtual JsValue[] GetAll( for (uint i = 0; i < (uint) jsValues.Length; i++) { var jsValue = skipHoles && !HasProperty(i) ? JsValue.Undefined : Get(i); - if ((jsValue.Type & elementTypes) == 0) + if ((jsValue.Type & elementTypes) == Types.None) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } @@ -70,7 +69,7 @@ public virtual JsValue[] GetAll( public abstract bool TryGetValue(ulong index, out JsValue value); - public bool HasProperty(ulong index) => Target.HasProperty(index); + public abstract bool HasProperty(ulong index); public abstract void CreateDataPropertyOrThrow(ulong index, JsValue value); @@ -201,6 +200,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(JsString.Create(index)); + + public override bool HasProperty(ulong index) => Target.HasProperty(index); } private sealed class ArrayInstanceOperations : ArrayOperations @@ -230,7 +231,7 @@ public override bool TryGetValue(ulong index, out JsValue value) public override JsValue Get(ulong index) => _target.Get((uint) index); - public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) + public override JsValue[] GetAll(Types elementTypes = Types.Undefined | Types.Null | Types.Boolean | Types.String | Types.Symbol | Types.Number | Types.Object, bool skipHoles = false) { var n = _target.GetLength(); @@ -244,22 +245,18 @@ public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) var jsValues = new JsValue[n]; for (uint i = 0; i < (uint) jsValues.Length; i++) { - var prop = _target._dense[i]; - if (prop is null) - { - prop = _target.Prototype?.Get(i) ?? JsValue.Undefined; - } - else if (prop is not JsValue) + var value = _target._dense[i]; + if (value is null) { - prop = _target.UnwrapJsValue((PropertyDescriptor) prop); + value = _target.Prototype?.Get(i) ?? JsValue.Undefined; } - if (prop is JsValue jsValue && (jsValue.Type & elementTypes) == 0) + if ((value.Type & elementTypes) == Types.None) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } - jsValues[writeIndex++] = (JsValue?) prop ?? JsValue.Undefined; + jsValues[writeIndex++] = (JsValue?) value ?? JsValue.Undefined; } return jsValues; @@ -273,6 +270,8 @@ public override void CreateDataPropertyOrThrow(ulong index, JsValue value) public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true) => _target.SetIndexValue((uint) index, value, updateLength); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } private sealed class TypedArrayInstanceOperations : ArrayOperations @@ -336,6 +335,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(index); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 1d48f6e3eb..f1e16039d6 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Linq; using Jint.Collections; using Jint.Native.Iterator; @@ -487,7 +489,7 @@ private JsValue Map(JsValue thisObject, JsValue[] arguments) if (len > ArrayOperations.MaxArrayLength) { - ExceptionHelper.ThrowRangeError(_realm, "Invalid array length");; + ExceptionHelper.ThrowRangeError(_realm, "Invalid array length"); } var callbackfn = arguments.At(0); @@ -1136,7 +1138,7 @@ private JsValue Slice(JsValue thisObject, JsValue[] arguments) if (k < final && final - k > ArrayOperations.MaxArrayLength) { - ExceptionHelper.ThrowRangeError(_realm, "Invalid array length");; + ExceptionHelper.ThrowRangeError(_realm, "Invalid array length"); } var length = (uint) System.Math.Max(0, (long) final - (long) k); @@ -1633,6 +1635,11 @@ public JsValue Push(JsValue thisObject, JsValue[] arguments) public JsValue Pop(JsValue thisObject, JsValue[] arguments) { + if (thisObject is JsArray { CanUseFastAccess: true } array) + { + return array.Pop(); + } + var o = ArrayOperations.For(_realm, thisObject); ulong len = o.GetLongLength(); if (len == 0) @@ -1641,17 +1648,17 @@ public JsValue Pop(JsValue thisObject, JsValue[] arguments) return Undefined; } - len = len - 1; + len -= 1; JsValue element = o.Get(len); o.DeletePropertyOrThrow(len); o.SetLength(len); return element; } - private object[] CreateBackingArray(ulong length) + private JsValue[] CreateBackingArray(ulong length) { ValidateArrayLength(length); - return new object[length]; + return new JsValue[length]; } private void ValidateArrayLength(ulong length) diff --git a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs index ae7aab0533..d89f20738e 100644 --- a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs +++ b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs index c745a66ce6..1c84164be5 100644 --- a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs +++ b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs @@ -37,7 +37,9 @@ private byte[] CreateByteDataBlock(ulong byteLength) internal byte[]? ArrayBufferData => _arrayBufferData; internal bool IsDetachedBuffer => _arrayBufferData is null; +#pragma warning disable CA1822 internal bool IsSharedArrayBuffer => false; // TODO SharedArrayBuffer +#pragma warning restore CA1822 /// /// https://tc39.es/ecma262/#sec-detacharraybuffer diff --git a/Jint/Native/BigInt/BigIntPrototype.cs b/Jint/Native/BigInt/BigIntPrototype.cs index 8d801fce9a..86875afaed 100644 --- a/Jint/Native/BigInt/BigIntPrototype.cs +++ b/Jint/Native/BigInt/BigIntPrototype.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Numerics; using Jint.Collections; using Jint.Native.Object; @@ -55,7 +56,7 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) var x = ThisBigIntValue(thisObject); //var numberFormat = (NumberFormat) Construct(_realm.Intrinsics.NumberFormat, new[] { locales, options }); // numberFormat.FormatNumeric(x); - return x._value.ToString("R"); + return x._value.ToString("R", CultureInfo.InvariantCulture); } /// @@ -103,7 +104,7 @@ private JsValue ToBigIntString(JsValue thisObject, JsValue[] arguments) if (radixMV == 10) { - return value.ToString("R"); + return value.ToString("R", CultureInfo.InvariantCulture); } var negative = value < 0; diff --git a/Jint/Native/Boolean/BooleanPrototype.cs b/Jint/Native/Boolean/BooleanPrototype.cs index 2379426429..303fed71ac 100644 --- a/Jint/Native/Boolean/BooleanPrototype.cs +++ b/Jint/Native/Boolean/BooleanPrototype.cs @@ -52,7 +52,7 @@ private JsValue ValueOf(JsValue thisObject, JsValue[] arguments) return Undefined; } - private JsValue ToBooleanString(JsValue thisObject, JsValue[] arguments) + private JsString ToBooleanString(JsValue thisObject, JsValue[] arguments) { var b = ValueOf(thisObject, Arguments.Empty); return ((JsBoolean) b)._value ? JsString.TrueString : JsString.FalseString; diff --git a/Jint/Native/DataView/DataViewPrototype.cs b/Jint/Native/DataView/DataViewPrototype.cs index 304de07084..342e539af7 100644 --- a/Jint/Native/DataView/DataViewPrototype.cs +++ b/Jint/Native/DataView/DataViewPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.ArrayBuffer; using Jint.Native.Object; diff --git a/Jint/Native/Date/DateConstructor.cs b/Jint/Native/Date/DateConstructor.cs index 144162d420..ac89e19ccb 100644 --- a/Jint/Native/Date/DateConstructor.cs +++ b/Jint/Native/Date/DateConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/Date/DatePrototype.cs b/Jint/Native/Date/DatePrototype.cs index 16796707d9..68459a1f9e 100644 --- a/Jint/Native/Date/DatePrototype.cs +++ b/Jint/Native/Date/DatePrototype.cs @@ -1,4 +1,8 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + +using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; @@ -115,11 +119,11 @@ private JsValue ToPrimitive(JsValue thisObject, JsValue[] arguments) var hintString = hint.ToString(); var tryFirst = Types.None; - if (hintString == "default" || hintString == "string") + if (string.Equals(hintString, "default", StringComparison.Ordinal) || string.Equals(hintString, "string", StringComparison.Ordinal)) { tryFirst = Types.String; } - else if (hintString == "number") + else if (string.Equals(hintString, "number", StringComparison.Ordinal)) { tryFirst = Types.Number; } @@ -775,9 +779,9 @@ private JsValue ToUtcString(JsValue thisObject, JsValue[] arguments) var weekday = _dayNames[WeekDay(tv)]; var month = _monthNames[MonthFromTime(tv)]; - var day = DateFromTime(tv).ToString("00"); + var day = DateFromTime(tv).ToString("00", CultureInfo.InvariantCulture); var yv = YearFromTime(tv); - var paddedYear = yv.ToString("0000"); + var paddedYear = yv.ToString("0000", CultureInfo.InvariantCulture); return $"{weekday}, {day} {month} {paddedYear} {TimeString(tv)}"; } @@ -1261,6 +1265,7 @@ private static bool AreFinite(double value1, double value2, double value3) private static bool AreFinite(double value1, double value2, double value3, double value4) => IsFinite(value1) && IsFinite(value2) && IsFinite(value3) && IsFinite(value4); + [StructLayout(LayoutKind.Auto)] private readonly record struct Date(int Year, int Month, int Day); private static readonly int[] kDaysInMonths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; @@ -1355,11 +1360,11 @@ private static string DateString(DatePresentation tv) var month = _monthNames[MonthFromTime(tv)]; var dateFromTime = DateFromTime(tv); - var day = System.Math.Max(1, dateFromTime).ToString("00"); + var day = System.Math.Max(1, dateFromTime).ToString("00", CultureInfo.InvariantCulture); var yv = YearFromTime(tv); var yearSign = yv < 0 ? "-" : ""; var year = System.Math.Abs(yv); - var paddedYear = year.ToString("0000"); + var paddedYear = year.ToString("0000", CultureInfo.InvariantCulture); return weekday + " " + month + " " + day + " " + yearSign + paddedYear; } @@ -1369,9 +1374,9 @@ private static string DateString(DatePresentation tv) /// private static string TimeString(DatePresentation t) { - var hour = HourFromTime(t).ToString("00"); - var minute = MinFromTime(t).ToString("00"); - var second = SecFromTime(t).ToString("00"); + var hour = HourFromTime(t).ToString("00", CultureInfo.InvariantCulture); + var minute = MinFromTime(t).ToString("00", CultureInfo.InvariantCulture); + var second = SecFromTime(t).ToString("00", CultureInfo.InvariantCulture); return hour + ":" + minute + ":" + second + " GMT"; } @@ -1396,8 +1401,8 @@ private string TimeZoneString(DatePresentation tv) absOffset = -1 * offset; } - var offsetMin = MinFromTime(absOffset).ToString("00"); - var offsetHour = HourFromTime(absOffset).ToString("00"); + var offsetMin = MinFromTime(absOffset).ToString("00", CultureInfo.InvariantCulture); + var offsetHour = HourFromTime(absOffset).ToString("00", CultureInfo.InvariantCulture); var tzName = " (" + _timeSystem.DefaultTimeZone.StandardName + ")"; diff --git a/Jint/Native/Date/MimeKit.cs b/Jint/Native/Date/MimeKit.cs index 3672ac480d..39d28e075c 100644 --- a/Jint/Native/Date/MimeKit.cs +++ b/Jint/Native/Date/MimeKit.cs @@ -1,4 +1,5 @@ #nullable disable +#pragma warning disable CA1510 namespace Jint.Native.Date; @@ -31,6 +32,7 @@ namespace Jint.Native.Date; using System; using System.Text; using System.Collections.Generic; +using System.Runtime.InteropServices; [Flags] internal enum DateTokenFlags : byte @@ -46,6 +48,7 @@ internal enum DateTokenFlags : byte HasSign = (1 << 7), } +[StructLayout(LayoutKind.Auto)] internal readonly struct DateToken { public DateToken(DateTokenFlags flags, int start, int length) @@ -63,32 +66,32 @@ public DateToken(DateTokenFlags flags, int start, int length) public bool IsNumeric { - get { return (Flags & DateTokenFlags.NonNumeric) == 0; } + get { return (Flags & DateTokenFlags.NonNumeric) == DateTokenFlags.None; } } public bool IsWeekday { - get { return (Flags & DateTokenFlags.NonWeekday) == 0; } + get { return (Flags & DateTokenFlags.NonWeekday) == DateTokenFlags.None; } } public bool IsMonth { - get { return (Flags & DateTokenFlags.NonMonth) == 0; } + get { return (Flags & DateTokenFlags.NonMonth) == DateTokenFlags.None; } } public bool IsTimeOfDay { - get { return (Flags & DateTokenFlags.NonTime) == 0 && (Flags & DateTokenFlags.HasColon) != 0; } + get { return (Flags & DateTokenFlags.NonTime) == DateTokenFlags.None && (Flags & DateTokenFlags.HasColon) != DateTokenFlags.None; } } public bool IsNumericZone { - get { return (Flags & DateTokenFlags.NonNumericZone) == 0 && (Flags & DateTokenFlags.HasSign) != 0; } + get { return (Flags & DateTokenFlags.NonNumericZone) == DateTokenFlags.None && (Flags & DateTokenFlags.HasSign) != DateTokenFlags.None; } } public bool IsAlphaZone { - get { return (Flags & DateTokenFlags.NonAlphaZone) == 0; } + get { return (Flags & DateTokenFlags.NonAlphaZone) == DateTokenFlags.None; } } public bool IsTimeZone @@ -180,6 +183,7 @@ static DateUtils() any[1] = (char) c; } +#pragma warning disable CA2249 if (NumericZoneCharacters.IndexOf((char) c) == -1) datetok[c] |= DateTokenFlags.NonNumericZone; if (AlphaZoneCharacters.IndexOf((char) c) == -1) @@ -192,6 +196,7 @@ static DateUtils() datetok[c] |= DateTokenFlags.NonMonth; if (TimeCharacters.IndexOf((char) c) == -1) datetok[c] |= DateTokenFlags.NonTime; +#pragma warning restore CA2249 } datetok[':'] |= DateTokenFlags.HasColon; @@ -518,7 +523,9 @@ private static bool TryParseUnknownDateFormat(IList tokens, byte[] te int endIndex = tokens[i].Start + tokens[i].Length; int index = tokens[i].Start; +#pragma warning disable CA1806 ParseUtils.TryParseInt32(text, ref index, endIndex, out value); +#pragma warning restore CA1806 if (month == null && value > 0 && value <= 12) { @@ -727,7 +734,11 @@ public static bool SkipCommentsAndWhiteSpace(byte[] text, ref int index, int end if (!SkipComment(text, ref index, endIndex)) { if (throwOnError) - throw new Exception($"Incomplete comment token at offset {startIndex}"); + { +#pragma warning disable MA0015 + throw new ArgumentException($"Incomplete comment token at offset {startIndex}"); +#pragma warning restore MA0015 + } return false; } @@ -808,7 +819,7 @@ private static void SetFlags(string values, CharType bit, CharType bitcopy, bool { for (i = 0; i < 256; i++) { - if ((table[i] & bitcopy) != 0) + if ((table[i] & bitcopy) != CharType.None) table[i] |= bit; } } @@ -865,6 +876,6 @@ static ByteExtensions() public static bool IsWhitespace(this byte c) { - return (table[c] & CharType.IsWhitespace) != 0; + return (table[c] & CharType.IsWhitespace) != CharType.None; } } diff --git a/Jint/Native/DatePresentation.cs b/Jint/Native/DatePresentation.cs index bd0d814ef1..76ea1783f4 100644 --- a/Jint/Native/DatePresentation.cs +++ b/Jint/Native/DatePresentation.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Jint.Native.Date; namespace Jint.Native; @@ -12,6 +13,7 @@ internal enum DateFlags : byte DateTimeMaxValue = 8 } +[StructLayout(LayoutKind.Auto)] internal readonly record struct DatePresentation(long Value, DateFlags Flags) { public static readonly DatePresentation NaN = new(0, DateFlags.NaN); @@ -19,9 +21,9 @@ internal readonly record struct DatePresentation(long Value, DateFlags Flags) public static readonly DatePresentation MaxValue = new(JsDate.Max, DateFlags.DateTimeMaxValue); public bool DateTimeRangeValid => IsFinite && Value <= JsDate.Max && Value >= JsDate.Min; - public bool IsNaN => (Flags & DateFlags.NaN) != 0; - public bool IsInfinity => (Flags & DateFlags.Infinity) != 0; - public bool IsFinite => (Flags & (DateFlags.NaN | DateFlags.Infinity)) == 0; + public bool IsNaN => (Flags & DateFlags.NaN) != DateFlags.None; + public bool IsInfinity => (Flags & DateFlags.Infinity) != DateFlags.None; + public bool IsFinite => (Flags & (DateFlags.NaN | DateFlags.Infinity)) == DateFlags.None; public DateTime ToDateTime() { @@ -64,7 +66,7 @@ public static implicit operator DatePresentation(double value) internal DatePresentation TimeClip() { - if ((Flags & (DateFlags.NaN | DateFlags.Infinity)) != 0) + if ((Flags & (DateFlags.NaN | DateFlags.Infinity)) != DateFlags.None) { return NaN; } diff --git a/Jint/Native/Error/ErrorInstance.cs b/Jint/Native/Error/ErrorInstance.cs index 508348193b..20fcdf4108 100644 --- a/Jint/Native/Error/ErrorInstance.cs +++ b/Jint/Native/Error/ErrorInstance.cs @@ -31,6 +31,6 @@ internal void InstallErrorCause(JsValue options) public override string ToString() { - return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject().ToString() ?? ""; + return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject()?.ToString() ?? ""; } } diff --git a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs index f6b1529b32..5d51d09a76 100644 --- a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs +++ b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs @@ -19,7 +19,7 @@ public FinalizationRegistryInstance(Engine engine, Realm realm, ICallable cleanu _callable = engine._host.MakeJobCallBack(cleanupCallback); } - public void CleanupFinalizationRegistry(ICallable? callback) + public static void CleanupFinalizationRegistry(ICallable? callback) { } @@ -60,7 +60,9 @@ public Observer(JobCallback callable) _callable = callable; } +#pragma warning disable MA0055 ~Observer() +#pragma warning restore MA0055 { _callable.Callback.Call(Undefined); } diff --git a/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs b/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs index 0f39c5e6dd..22df9c2c35 100644 --- a/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs +++ b/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs @@ -98,7 +98,7 @@ private JsValue CleanupSome(JsValue thisObject, JsValue[] arguments) ExceptionHelper.ThrowTypeError(_realm, callback + " must be callable"); } - finalizationRegistry.CleanupFinalizationRegistry(callback as ICallable); + FinalizationRegistryInstance.CleanupFinalizationRegistry(callback as ICallable); return Undefined; } diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index 24a8fa7f4a..d19a8982ec 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -169,7 +169,7 @@ public JsValue BuildConstructor(EvaluationContext context, EnvironmentRecord env if (element is PrivateElement privateElement) { var container = !isStatic ? instancePrivateMethods : staticPrivateMethods; - var index = container.FindIndex(x => x.Key.Description == privateElement.Key.Description); + var index = container.FindIndex(x => string.Equals(x.Key.Description, privateElement.Key.Description, StringComparison.Ordinal)); if (index != -1) { var pe = container[index]; @@ -276,7 +276,7 @@ private static ClassFieldDefinition ClassFieldDefinitionEvaluation(Engine engine private sealed class ClassFieldFunction : Node, IFunction { - private readonly NodeList _nodeList = new(); + private readonly NodeList _nodeList; private readonly BlockStatement _statement; public ClassFieldFunction(Expression expression) : base(Nodes.ExpressionStatement) diff --git a/Jint/Native/Function/EvalFunctionInstance.cs b/Jint/Native/Function/EvalFunctionInstance.cs index 7ec449ef1a..5be0789fee 100644 --- a/Jint/Native/Function/EvalFunctionInstance.cs +++ b/Jint/Native/Function/EvalFunctionInstance.cs @@ -83,7 +83,7 @@ public JsValue PerformEval(JsValue x, Realm callerRealm, bool strictCaller, bool } catch (ParserException e) { - if (e.Description == Messages.InvalidLHSInAssignment) + if (string.Equals(e.Description, Messages.InvalidLHSInAssignment, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceError(callerRealm, (string?) null); } @@ -201,13 +201,13 @@ private sealed class EvalScriptAnalyzer : AstVisitor protected override object VisitIdentifier(Identifier identifier) { - _containsArguments |= identifier.Name == "arguments"; + _containsArguments |= string.Equals(identifier.Name, "arguments", StringComparison.Ordinal); return identifier; } protected override object VisitMetaProperty(MetaProperty metaProperty) { - _containsNewTarget |= metaProperty.Meta.Name == "new" && metaProperty.Property.Name == "target"; + _containsNewTarget |= string.Equals(metaProperty.Meta.Name, "new", StringComparison.Ordinal) && string.Equals(metaProperty.Property.Name, "target", StringComparison.Ordinal); return metaProperty; } diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index e362f75265..c9637dbed6 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -67,7 +67,7 @@ internal FunctionInstance InstantiateFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncfunctionobject /// - private FunctionInstance InstantiateAsyncFunctionObject( + private ScriptFunctionInstance InstantiateAsyncFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord env, PrivateEnvironmentRecord? privateEnv) @@ -87,7 +87,7 @@ private FunctionInstance InstantiateAsyncFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionobject /// - private FunctionInstance InstantiateOrdinaryFunctionObject( + private ScriptFunctionInstance InstantiateOrdinaryFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord env, PrivateEnvironmentRecord? privateEnv) @@ -108,7 +108,7 @@ private FunctionInstance InstantiateOrdinaryFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiategeneratorfunctionobject /// - private FunctionInstance InstantiateGeneratorFunctionObject( + private ScriptFunctionInstance InstantiateGeneratorFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord scope, PrivateEnvironmentRecord? privateScope) diff --git a/Jint/Native/Function/FunctionInstance.Dynamic.cs b/Jint/Native/Function/FunctionInstance.Dynamic.cs index b3537b833c..57714f8429 100644 --- a/Jint/Native/Function/FunctionInstance.Dynamic.cs +++ b/Jint/Native/Function/FunctionInstance.Dynamic.cs @@ -115,7 +115,7 @@ internal FunctionInstance CreateDynamicFunction( break; } - if (p.IndexOf('/') != -1) + if (p.Contains('/')) { // ensure comments don't screw up things functionExpression += "\n" + p + "\n"; @@ -127,7 +127,7 @@ internal FunctionInstance CreateDynamicFunction( functionExpression += ")"; - if (body.IndexOf('/') != -1) + if (body.Contains('/')) { // ensure comments don't screw up things functionExpression += "{\n" + body + "\n}"; diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 44ec6525ac..3e6b897288 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native.Object; @@ -9,6 +10,7 @@ namespace Jint.Native.Function { + [DebuggerDisplay("{ToString(),nq}")] public abstract partial class FunctionInstance : ObjectInstance, ICallable { protected PropertyDescriptor? _prototypeDescriptor; @@ -124,15 +126,15 @@ internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } - if (property == CommonProperties.Name) + if (CommonProperties.Name.Equals(property)) { return _nameDescriptor ?? PropertyDescriptor.Undefined; } @@ -142,15 +144,15 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = desc; } - else if (property == CommonProperties.Length) + else if (CommonProperties.Length.Equals(property)) { _length = desc; } - else if (property == CommonProperties.Name) + else if (CommonProperties.Name.Equals(property)) { _nameDescriptor = desc; } @@ -162,15 +164,15 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri public override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = null; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } - if (property == CommonProperties.Name) + if (CommonProperties.Name.Equals(property)) { _nameDescriptor = null; } @@ -399,7 +401,7 @@ public override IEnumerable> GetOwnPro public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { return _constructor ?? PropertyDescriptor.Undefined; } @@ -409,7 +411,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { _constructor = desc; } @@ -421,7 +423,7 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri public override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { _constructor = null; } diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 7ea20ac7e5..37f1c4b943 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Array; using Jint.Native.Object; @@ -34,8 +36,8 @@ protected override void Initialize() ["apply"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "apply", Apply, 2, lengthFlags), propertyFlags), ["call"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "call", CallImpl, 1, lengthFlags), propertyFlags), ["bind"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "bind", Bind, 1, lengthFlags), propertyFlags), - ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue), - ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue) + ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable), + ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable) }; SetProperties(properties); diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index c284bdf750..b01c08f7ee 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -49,7 +49,7 @@ internal ScriptFunctionInstance( && function.Function is not ArrowFunctionExpression && !function.Function.Generator) { - SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue)); + SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable)); SetProperty(KnownKeys.Caller, new PropertyDescriptor(Undefined, PropertyFlag.Configurable)); } } @@ -57,7 +57,7 @@ internal ScriptFunctionInstance( /// /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist /// - protected internal override JsValue Call(JsValue thisArgument, JsValue[] arguments) + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { var strict = _functionDefinition.Strict || _thisMode == FunctionThisMode.Strict; using (new StrictModeScope(strict, true)) @@ -71,7 +71,7 @@ protected internal override JsValue Call(JsValue thisArgument, JsValue[] argumen ExceptionHelper.ThrowTypeError(calleeContext.Realm, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'"); } - OrdinaryCallBindThis(calleeContext, thisArgument); + OrdinaryCallBindThis(calleeContext, thisObject); // actual call var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); diff --git a/Jint/Native/Global/GlobalObject.Properties.cs b/Jint/Native/Global/GlobalObject.Properties.cs new file mode 100644 index 0000000000..a8ae037981 --- /dev/null +++ b/Jint/Native/Global/GlobalObject.Properties.cs @@ -0,0 +1,188 @@ +using Jint.Collections; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Descriptors.Specialized; +using Jint.Runtime.Interop; + +namespace Jint.Native.Global; + +public partial class GlobalObject +{ + private static readonly Key propertyAggregateError = "AggregateError"; + private static readonly Key propertyArray = "Array"; + private static readonly Key propertyArrayBuffer = "ArrayBuffer"; + private static readonly Key propertyAtomics = "Atomics"; + private static readonly Key propertyBigInt = "BigInt"; + private static readonly Key propertyBigInt64Array = "BigInt64Array"; + private static readonly Key propertyBigUint64Array = "BigUint64Array"; + private static readonly Key propertyBoolean = "Boolean"; + private static readonly Key propertyDataView = "DataView"; + private static readonly Key propertyDate = "Date"; + private static readonly Key propertyError = "Error"; + private static readonly Key propertyEvalError = "EvalError"; + private static readonly Key propertyFinalizationRegistry = "FinalizationRegistry"; + private static readonly Key propertyFloat32Array = "Float32Array"; + private static readonly Key propertyFloat64Array = "Float64Array"; + private static readonly Key propertyFunction = "Function"; + private static readonly Key propertyInt16Array = "Int16Array"; + private static readonly Key propertyInt32Array = "Int32Array"; + private static readonly Key propertyInt8Array = "Int8Array"; + //private static readonly Key propertyIntl = "Intl"; + private static readonly Key propertyJSON = "JSON"; + private static readonly Key propertyMap = "Map"; + private static readonly Key propertyMath = "Math"; + private static readonly Key propertyNumber = "Number"; + private static readonly Key propertyObject = "Object"; + private static readonly Key propertyPromise = "Promise"; + private static readonly Key propertyProxy = "Proxy"; + private static readonly Key propertyRangeError = "RangeError"; + private static readonly Key propertyReferenceError = "ReferenceError"; + private static readonly Key propertyReflect = "Reflect"; + private static readonly Key propertyRegExp = "RegExp"; + private static readonly Key propertySet = "Set"; + private static readonly Key propertyShadowRealm = "ShadowRealm"; + private static readonly Key propertySharedArrayBuffer = "SharedArrayBuffer"; + private static readonly Key propertyString = "String"; + private static readonly Key propertySymbol = "Symbol"; + private static readonly Key propertySyntaxError = "SyntaxError"; + private static readonly Key propertyTypeError = "TypeError"; + private static readonly Key propertyTypedArray = "TypedArray"; + private static readonly Key propertyURIError = "URIError"; + private static readonly Key propertyUint16Array = "Uint16Array"; + private static readonly Key propertyUint32Array = "Uint32Array"; + private static readonly Key propertyUint8Array = "Uint8Array"; + private static readonly Key propertyUint8ClampedArray = "Uint8ClampedArray"; + private static readonly Key propertyWeakMap = "WeakMap"; + private static readonly Key propertyWeakRef = "WeakRef"; + private static readonly Key propertyWeakSet = "WeakSet"; + private static readonly Key propertyNaN = "NaN"; + private static readonly Key propertyInfinity = "Infinity"; + private static readonly Key propertyUndefined = "undefined"; + private static readonly Key propertyParseInt = "parseInt"; + private static readonly Key propertyParseFloat = "parseFloat"; + private static readonly Key propertyIsNaN = "isNaN"; + private static readonly Key propertyIsFinite = "isFinite"; + private static readonly Key propertyDecodeURI = "decodeURI"; + private static readonly Key propertyDecodeURIComponent = "decodeURIComponent"; + private static readonly Key propertyEncodeURI = "encodeURI"; + private static readonly Key propertyEncodeURIComponent = "encodeURIComponent"; + private static readonly Key propertyEscape = "escape"; + private static readonly Key propertyUnescape = "unescape"; + private static readonly Key propertyGlobalThis = "globalThis"; + private static readonly Key propertyEval = "eval"; + private static readonly Key propertyToString = "toString"; + + private static readonly PropertyDescriptor _propertyDescriptorNan = new(JsNumber.DoubleNaN, PropertyFlag.AllForbidden); + private static readonly PropertyDescriptor _propertyDescriptorPositiveInfinity = new(JsNumber.DoublePositiveInfinity, PropertyFlag.AllForbidden); + private static readonly PropertyDescriptor _propertyDescriptorUndefined = new(Undefined, PropertyFlag.AllForbidden); + + protected override void Initialize() + { + const PropertyFlag LengthFlags = PropertyFlag.Configurable; + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + + var properties = new StringDictionarySlim(64); + properties.AddDangerous(propertyAggregateError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, PropertyFlags)); + properties.AddDangerous(propertyArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, PropertyFlags)); + properties.AddDangerous(propertyArrayBuffer, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, PropertyFlags)); + properties.AddDangerous(propertyAtomics, new LazyPropertyDescriptor(this, static state => Undefined, PropertyFlags)); + properties.AddDangerous(propertyBigInt, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt, PropertyFlags)); + properties.AddDangerous(propertyBigInt64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt64Array, PropertyFlags)); + properties.AddDangerous(propertyBigUint64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigUint64Array, PropertyFlags)); + properties.AddDangerous(propertyBoolean, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Boolean, PropertyFlags)); + properties.AddDangerous(propertyDataView, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.DataView, PropertyFlags)); + properties.AddDangerous(propertyDate, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, PropertyFlags)); + properties.AddDangerous(propertyError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, PropertyFlags)); + properties.AddDangerous(propertyEvalError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, PropertyFlags)); + properties.AddDangerous(propertyFinalizationRegistry, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, PropertyFlags)); + properties.AddDangerous(propertyFloat32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, PropertyFlags)); + properties.AddDangerous(propertyFloat64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, PropertyFlags)); + properties.AddDangerous(propertyFunction, new PropertyDescriptor(_realm.Intrinsics.Function, PropertyFlags)); + properties.AddDangerous(propertyInt16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, PropertyFlags)); + properties.AddDangerous(propertyInt32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, PropertyFlags)); + properties.AddDangerous(propertyInt8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, PropertyFlags)); + // TODO properties.AddDapropertygerous(propertyIntl, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Intl, propertyFlags)); + properties.AddDangerous(propertyJSON, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Json, PropertyFlags)); + properties.AddDangerous(propertyMap, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Map, PropertyFlags)); + properties.AddDangerous(propertyMath, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Math, PropertyFlags)); + properties.AddDangerous(propertyNumber, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Number, PropertyFlags)); + properties.AddDangerous(propertyObject, new PropertyDescriptor(_realm.Intrinsics.Object, PropertyFlags)); + properties.AddDangerous(propertyPromise, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Promise, PropertyFlags)); + properties.AddDangerous(propertyProxy, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Proxy, PropertyFlags)); + properties.AddDangerous(propertyRangeError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RangeError, PropertyFlags)); + properties.AddDangerous(propertyReferenceError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ReferenceError, PropertyFlags)); + properties.AddDangerous(propertyReflect, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Reflect, PropertyFlags)); + properties.AddDangerous(propertyRegExp, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RegExp, PropertyFlags)); + properties.AddDangerous(propertySet, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Set, PropertyFlags)); + properties.AddDangerous(propertyShadowRealm, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ShadowRealm, PropertyFlags)); + properties.AddDangerous(propertySharedArrayBuffer, new LazyPropertyDescriptor(this, static state => Undefined, PropertyFlags)); + properties.AddDangerous(propertyString, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.String, PropertyFlags)); + properties.AddDangerous(propertySymbol, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Symbol, PropertyFlags)); + properties.AddDangerous(propertySyntaxError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.SyntaxError, PropertyFlags)); + properties.AddDangerous(propertyTypeError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypeError, PropertyFlags)); + properties.AddDangerous(propertyTypedArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypedArray, PropertyFlags)); + properties.AddDangerous(propertyURIError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.UriError, PropertyFlags)); + properties.AddDangerous(propertyUint16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint16Array, PropertyFlags)); + properties.AddDangerous(propertyUint32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint32Array, PropertyFlags)); + properties.AddDangerous(propertyUint8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8Array, PropertyFlags)); + properties.AddDangerous(propertyUint8ClampedArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8ClampedArray, PropertyFlags)); + properties.AddDangerous(propertyWeakMap, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakMap, PropertyFlags)); + properties.AddDangerous(propertyWeakRef, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakRef, PropertyFlags)); + properties.AddDangerous(propertyWeakSet, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakSet, PropertyFlags)); + + properties.AddDangerous(propertyNaN, _propertyDescriptorNan); + properties.AddDangerous(propertyInfinity, _propertyDescriptorPositiveInfinity); + properties.AddDangerous(propertyUndefined, _propertyDescriptorUndefined); + properties.AddDangerous(propertyParseInt, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseInt", ParseInt, 2, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyParseFloat, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseFloat", ParseFloat, 1, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyIsNaN, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isNaN", IsNaN, 1, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyIsFinite, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isFinite", IsFinite, 1, LengthFlags), PropertyFlags)); + + properties.AddDangerous(propertyDecodeURI, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "decodeURI", global.DecodeUri, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyDecodeURIComponent, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "decodeURIComponent", global.DecodeUriComponent, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEncodeURI, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "encodeURI", global.EncodeUri, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEncodeURIComponent, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "encodeURIComponent", global.EncodeUriComponent, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEscape, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "escape", global.Escape, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyUnescape, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "unescape", global.Unescape, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyGlobalThis, new PropertyDescriptor(this, PropertyFlags)); + properties.AddDangerous(propertyEval, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Eval, PropertyFlag.Configurable | PropertyFlag.Writable)); + + // toString is not mentioned or actually required in spec, but some tests rely on it + properties.AddDangerous(propertyToString, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "toString", global.ToStringString, 1); + }, PropertyFlags)); + + SetProperties(properties); + } +} diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 5390fc0e05..5bb0bb41aa 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -3,17 +3,14 @@ using System.Runtime.CompilerServices; using System.Text; using Esprima; -using Jint.Collections; using Jint.Native.Object; using Jint.Native.String; using Jint.Runtime; using Jint.Runtime.Descriptors; -using Jint.Runtime.Descriptors.Specialized; -using Jint.Runtime.Interop; namespace Jint.Native.Global { - public sealed class GlobalObject : ObjectInstance + public sealed partial class GlobalObject : ObjectInstance { private readonly Realm _realm; private readonly StringBuilder _stringBuilder = new(); @@ -25,113 +22,6 @@ internal GlobalObject( _realm = realm; } - protected override void Initialize() - { - const PropertyFlag lengthFlags = PropertyFlag.Configurable; - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - - var properties = new PropertyDictionary(56, checkExistingKeys: false) - { - ["AggregateError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, propertyFlags), - ["Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, propertyFlags), - ["ArrayBuffer"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, propertyFlags), - ["Atomics"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags), - ["BigInt"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt, propertyFlags), - ["BigInt64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt64Array, propertyFlags), - ["BigUint64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigUint64Array, propertyFlags), - ["Boolean"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Boolean, propertyFlags), - ["DataView"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.DataView, propertyFlags), - ["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, propertyFlags), - ["Error"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, propertyFlags), - ["EvalError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, propertyFlags), - ["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, propertyFlags), - ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags), - ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags), - ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags), - ["Int16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, propertyFlags), - ["Int32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, propertyFlags), - ["Int8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, propertyFlags), - // TODO ["Intl"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Intl, propertyFlags), - ["JSON"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Json, propertyFlags), - ["Map"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Map, propertyFlags), - ["Math"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Math, propertyFlags), - ["Number"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Number, propertyFlags), - ["Object"] = new PropertyDescriptor(_realm.Intrinsics.Object, propertyFlags), - ["Promise"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Promise, propertyFlags), - ["Proxy"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Proxy, propertyFlags), - ["RangeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RangeError, propertyFlags), - ["ReferenceError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ReferenceError, propertyFlags), - ["Reflect"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Reflect, propertyFlags), - ["RegExp"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RegExp, propertyFlags), - ["Set"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Set, propertyFlags), - ["ShadowRealm"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ShadowRealm, propertyFlags), - ["SharedArrayBuffer"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags), - ["String"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.String, propertyFlags), - ["Symbol"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Symbol, propertyFlags), - ["SyntaxError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.SyntaxError, propertyFlags), - ["TypeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypeError, propertyFlags), - ["TypedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypedArray, propertyFlags), - ["URIError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.UriError, propertyFlags), - ["Uint16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint16Array, propertyFlags), - ["Uint32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint32Array, propertyFlags), - ["Uint8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8Array, propertyFlags), - ["Uint8ClampedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8ClampedArray, propertyFlags), - ["WeakMap"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakMap, propertyFlags), - ["WeakRef"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakRef, propertyFlags), - ["WeakSet"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakSet, propertyFlags), - - - ["NaN"] = new PropertyDescriptor(double.NaN, PropertyFlag.AllForbidden), - ["Infinity"] = new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden), - ["undefined"] = new PropertyDescriptor(Undefined, PropertyFlag.AllForbidden), - ["parseInt"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseInt", ParseInt, 2, lengthFlags), propertyFlags), - ["parseFloat"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseFloat", ParseFloat, 1, lengthFlags), propertyFlags), - ["isNaN"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isNaN", IsNaN, 1, lengthFlags), propertyFlags), - ["isFinite"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isFinite", IsFinite, 1, lengthFlags), propertyFlags), - ["decodeURI"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "decodeURI", global.DecodeUri, 1, lengthFlags); - }, propertyFlags), - ["decodeURIComponent"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "decodeURIComponent", global.DecodeUriComponent, 1, lengthFlags); - }, propertyFlags), - ["encodeURI"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "encodeURI", global.EncodeUri, 1, lengthFlags); - }, propertyFlags), - ["encodeURIComponent"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "encodeURIComponent", global.EncodeUriComponent, 1, lengthFlags); - }, propertyFlags), - ["escape"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "escape", global.Escape, 1, lengthFlags); - }, propertyFlags), - ["unescape"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "unescape", global.Unescape, 1, lengthFlags); - }, propertyFlags), - ["globalThis"] = new PropertyDescriptor(this, propertyFlags), - ["eval"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Eval, PropertyFlag.Configurable | PropertyFlag.Writable), - - // toString is not mentioned or actually required in spec, but some tests rely on it - ["toString"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "toString", global.ToStringString, 1); - }, propertyFlags) - }; - - SetProperties(properties); - } - private JsValue ToStringString(JsValue thisObject, JsValue[] arguments) { return _realm.Intrinsics.Object.PrototypeObject.ToObjectString(thisObject, Arguments.Empty); @@ -164,7 +54,7 @@ public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments) } // check fast case - if (radix == 10 && int.TryParse(trimmed, out var number)) + if (radix == 10 && int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number)) { return JsNumber.Create(number); } @@ -248,7 +138,7 @@ public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments) if (trimmedString[0] == '-') { i++; - if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity")) + if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity", StringComparison.Ordinal)) { return JsNumber.DoubleNegativeInfinity; } @@ -257,18 +147,18 @@ public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments) if (trimmedString[0] == '+') { i++; - if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity")) + if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity", StringComparison.Ordinal)) { return JsNumber.DoublePositiveInfinity; } } - if (trimmedString.StartsWith("Infinity")) + if (trimmedString.StartsWith("Infinity", StringComparison.Ordinal)) { return JsNumber.DoublePositiveInfinity; } - if (trimmedString.StartsWith("NaN")) + if (trimmedString.StartsWith("NaN", StringComparison.Ordinal)) { return JsNumber.DoubleNaN; } @@ -382,21 +272,10 @@ public static JsValue IsFinite(JsValue thisObject, JsValue[] arguments) return true; } - private static readonly HashSet UriReserved = new HashSet - { - ';', '/', '?', ':', '@', '&', '=', '+', '$', ',' - }; - - private static readonly HashSet UriUnescaped = new HashSet - { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', '.', '!', - '~', '*', '\'', '(', ')' - }; - - private static readonly HashSet UnescapedUriSet = new HashSet(UriReserved.Concat(UriUnescaped).Concat(new[] { '#' })); - private static readonly HashSet ReservedUriSet = new HashSet(UriReserved.Concat(new[] { '#' })); + private static readonly string UriReserved = new (new [] { ';', '/', '?', ':', '@', '&', '=', '+', '$', ',' }); + private static readonly string UriUnescaped = new(new [] { '-', '_', '.', '!', '~', '*', '\'', '(', ')' }); + private static readonly string UnescapedUriSet = UriReserved + UriUnescaped + '#'; + private static readonly string ReservedUriSet = UriReserved + '#'; private const string HexaMap = "0123456789ABCDEF"; @@ -430,17 +309,18 @@ public JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments) return Encode(uriString, UriUnescaped); } - private string Encode(string uriString, HashSet unescapedUriSet) + private JsValue Encode(string uriString, string unescapedUriSet) { var strLen = uriString.Length; _stringBuilder.EnsureCapacity(uriString.Length); _stringBuilder.Clear(); + var buffer = new byte[4]; for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (unescapedUriSet != null && unescapedUriSet.Contains(c)) + if (c is >= 'a' and <= 'z' || c is >= 'A' and <= 'Z' || c is >= '0' and <= '9' || unescapedUriSet.Contains(c)) { _stringBuilder.Append(c); } @@ -448,7 +328,7 @@ private string Encode(string uriString, HashSet unescapedUriSet) { if (c >= 0xDC00 && c <= 0xDBFF) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } int v; @@ -461,70 +341,58 @@ private string Encode(string uriString, HashSet unescapedUriSet) k++; if (k == strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - var kChar = (int)uriString[k]; - if (kChar < 0xDC00 || kChar > 0xDFFF) + var kChar = (int) uriString[k]; + if (kChar is < 0xDC00 or > 0xDFFF) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } v = (c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000; } - byte[] octets = System.Array.Empty(); - - if (v >= 0 && v <= 0x007F) - { - // 00000000 0zzzzzzz -> 0zzzzzzz - octets = new[] { (byte)v }; - } - else if (v <= 0x07FF) + var length = 1; + switch (v) { - // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz - octets = new[] - { - (byte)(0xC0 | (v >> 6)), - (byte)(0x80 | (v & 0x3F)) - }; - } - else if (v <= 0xD7FF) - { - // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz - octets = new[] - { - (byte)(0xE0 | (v >> 12)), - (byte)(0x80 | ((v >> 6) & 0x3F)), - (byte)(0x80 | (v & 0x3F)) - }; - } - else if (v <= 0xDFFF) - { - ExceptionHelper.ThrowUriError(_realm); - } - else if (v <= 0xFFFF) - { - octets = new[] - { - (byte) (0xE0 | (v >> 12)), - (byte) (0x80 | ((v >> 6) & 0x3F)), - (byte) (0x80 | (v & 0x3F)) - }; - } - else - { - octets = new[] - { - (byte) (0xF0 | (v >> 18)), - (byte) (0x80 | (v >> 12 & 0x3F)), - (byte) (0x80 | (v >> 6 & 0x3F)), - (byte) (0x80 | (v >> 0 & 0x3F)) - }; + case >= 0 and <= 0x007F: + // 00000000 0zzzzzzz -> 0zzzzzzz + buffer[0] = (byte) v; + break; + case <= 0x07FF: + // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz + length = 2; + buffer[0] = (byte) (0xC0 | (v >> 6)); + buffer[1] = (byte) (0x80 | (v & 0x3F)); + break; + case <= 0xD7FF: + // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz + length = 3; + buffer[0] = (byte) (0xE0 | (v >> 12)); + buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F)); + buffer[2] = (byte) (0x80 | (v & 0x3F)); + break; + case <= 0xDFFF: + goto uriError; + case <= 0xFFFF: + length = 3; + buffer[0] = (byte) (0xE0 | (v >> 12)); + buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F)); + buffer[2] = (byte) (0x80 | (v & 0x3F)); + break; + default: + length = 4; + buffer[0] = (byte) (0xF0 | (v >> 18)); + buffer[1] = (byte) (0x80 | (v >> 12 & 0x3F)); + buffer[2] = (byte) (0x80 | (v >> 6 & 0x3F)); + buffer[3] = (byte) (0x80 | (v >> 0 & 0x3F)); + break; } - foreach (var octet in octets) + for (var i = 0; i < length; i++) { + var octet = buffer[i]; var x1 = HexaMap[octet / 16]; var x2 = HexaMap[octet % 16]; _stringBuilder.Append('%').Append(x1).Append(x2); @@ -533,6 +401,10 @@ private string Encode(string uriString, HashSet unescapedUriSet) } return _stringBuilder.ToString(); + +uriError: + _engine.SignalError(ExceptionHelper.CreateUriError(_realm, "URI malformed")); + return null!; } public JsValue DecodeUri(JsValue thisObject, JsValue[] arguments) @@ -549,14 +421,18 @@ public JsValue DecodeUriComponent(JsValue thisObject, JsValue[] arguments) return Decode(componentString, null); } - private string Decode(string uriString, HashSet? reservedSet) + private JsValue Decode(string uriString, string? reservedSet) { var strLen = uriString.Length; _stringBuilder.EnsureCapacity(strLen); _stringBuilder.Clear(); - var octets = System.Array.Empty(); +#if SUPPORTS_SPAN_PARSE + Span octets = stackalloc byte[4]; +#else + var octets = new byte[4]; +#endif for (var k = 0; k < strLen; k++) { @@ -570,21 +446,25 @@ private string Decode(string uriString, HashSet? reservedSet) var start = k; if (k + 2 >= strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) + var c1 = uriString[k + 1]; + var c2 = uriString[k + 2]; + if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2)) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - var B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); + var B = StringToIntBase16(uriString.AsSpan(k + 1, 2)); k += 2; if ((B & 0x80) == 0) { C = (char)B; - if (reservedSet == null || !reservedSet.Contains(C)) +#pragma warning disable CA2249 + if (reservedSet == null || reservedSet.IndexOf(C) == -1) +#pragma warning restore CA2249 { _stringBuilder.Append(C); } @@ -596,22 +476,20 @@ private string Decode(string uriString, HashSet? reservedSet) else { var n = 0; - for (; ((B << n) & 0x80) != 0; n++) ; + for (; ((B << n) & 0x80) != 0; n++) + { + } if (n == 1 || n > 4) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - octets = octets.Length == n - ? octets - : new byte[n]; - octets[0] = B; if (k + (3 * (n - 1)) >= strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } for (var j = 1; j < n; j++) @@ -619,20 +497,22 @@ private string Decode(string uriString, HashSet? reservedSet) k++; if (uriString[k] != '%') { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) + c1 = uriString[k + 1]; + c2 = uriString[k + 2]; + if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2)) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); + B = StringToIntBase16(uriString.AsSpan(k + 1, 2)); // B & 11000000 != 10000000 if ((B & 0xC0) != 0x80) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } k += 2; @@ -640,12 +520,73 @@ private string Decode(string uriString, HashSet? reservedSet) octets[j] = B; } - _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, octets.Length)); +#if SUPPORTS_SPAN_PARSE + _stringBuilder.Append(Encoding.UTF8.GetString(octets.Slice(0, n))); +#else + _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, n)); +#endif } } } return _stringBuilder.ToString(); + +uriError: + _engine.SignalError(ExceptionHelper.CreateUriError(_realm, "URI malformed")); + return null!; + } + + private static byte StringToIntBase16(ReadOnlySpan s) + { + var i = 0; + var length = s.Length; + + if (s[i] == '+') + { + i++; + } + + if (i + 1 < length && s[i] == '0') + { + if (s[i + 1] == 'x' || s[i + 1] == 'X') + { + i += 2; + } + } + + uint result = 0; + while (i < s.Length && IsDigit(s[i], 16, out var value)) + { + result = result * 16 + (uint) value; + i++; + } + + return (byte) (int) result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDigit(char c, int radix, out int result) + { + int tmp; + if ((uint)(c - '0') <= 9) + { + result = tmp = c - '0'; + } + else if ((uint)(c - 'A') <= 'Z' - 'A') + { + result = tmp = c - 'A' + 10; + } + else if ((uint)(c - 'a') <= 'z' - 'a') + { + result = tmp = c - 'a' + 10; + } + else + { + result = -1; + return false; + } + + return tmp < radix; } /// @@ -653,7 +594,7 @@ private string Decode(string uriString, HashSet? reservedSet) /// public JsValue Escape(JsValue thisObject, JsValue[] arguments) { - const string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; + const string AllowList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; var uriString = TypeConverter.ToString(arguments.At(0)); var strLen = uriString.Length; @@ -664,17 +605,17 @@ public JsValue Escape(JsValue thisObject, JsValue[] arguments) for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (whiteList.IndexOf(c) != -1) + if (AllowList.Contains(c)) { _stringBuilder.Append(c); } else if (c < 256) { - _stringBuilder.Append($"%{((int) c):X2}"); + _stringBuilder.Append('%').AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", (int) c); } else { - _stringBuilder.Append($"%u{((int) c):X4}"); + _stringBuilder.Append("%u").AppendFormat(CultureInfo.InvariantCulture, "{0:X4}", (int) c); } } @@ -702,18 +643,16 @@ public JsValue Unescape(JsValue thisObject, JsValue[] arguments) && uriString[k + 1] == 'u' && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar)) { - c = (char)int.Parse( - string.Join(string.Empty, uriString.Skip(k + 2).Take(4)), - NumberStyles.AllowHexSpecifier); + var joined = string.Join(string.Empty, uriString.Skip(k + 2).Take(4)); + c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); k += 5; } else if (k <= strLen - 3 - && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar)) + && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar)) { - c = (char)int.Parse( - string.Join(string.Empty, uriString.Skip(k + 1).Take(2)), - NumberStyles.AllowHexSpecifier); + var joined = string.Join(string.Empty, uriString.Skip(k + 1).Take(2)); + c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); k += 2; } @@ -752,7 +691,7 @@ internal bool DefineOwnProperty(Key property, PropertyDescriptor desc) } // check fast path - if ((current._flags & PropertyFlag.MutableBinding) != 0) + if ((current._flags & PropertyFlag.MutableBinding) != PropertyFlag.None) { current._value = desc.Value; return true; @@ -791,7 +730,7 @@ internal bool SetFromMutableBinding(Key property, JsValue value, bool strict) } // check fast path - if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != 0) + if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != PropertyFlag.None) { existingDescriptor._value = value; return true; diff --git a/Jint/Native/Intl/DateTimeFormatConstructor.cs b/Jint/Native/Intl/DateTimeFormatConstructor.cs index bffd0a861a..11c1d4d5ee 100644 --- a/Jint/Native/Intl/DateTimeFormatConstructor.cs +++ b/Jint/Native/Intl/DateTimeFormatConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-initializedatetimeformat /// - private void InitializeDateTimeFormat(JsObject dateTimeFormat, JsValue locales, JsValue options) + private static void InitializeDateTimeFormat(JsObject dateTimeFormat, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/Intl/IntlInstance.cs b/Jint/Native/Intl/IntlInstance.cs index 1e7e9f97e2..0853bff8a4 100644 --- a/Jint/Native/Intl/IntlInstance.cs +++ b/Jint/Native/Intl/IntlInstance.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/Intl/NumberFormatConstructor.cs b/Jint/Native/Intl/NumberFormatConstructor.cs index 1589b1b889..89b705400f 100644 --- a/Jint/Native/Intl/NumberFormatConstructor.cs +++ b/Jint/Native/Intl/NumberFormatConstructor.cs @@ -201,7 +201,7 @@ private void SetNumberFormatUnitOptions(JsObject intlObj, JsValue options) /// /// https://tc39.es/ecma402/#sec-iswellformedunitidentifier /// - private bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) + private static bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) { var value = unitIdentifier.ToString(); if (IsSanctionedSingleUnitIdentifier(value)) @@ -209,8 +209,8 @@ private bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) return true; } - var i = value.IndexOf("-per-"); - if (i == -1 || value.IndexOf("-per-", i + 1) != -1) + var i = value.IndexOf("-per-", StringComparison.Ordinal); + if (i == -1 || value.IndexOf("-per-", i + 1, StringComparison.Ordinal) != -1) { return false; } @@ -225,7 +225,7 @@ private bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) return false; } - private static readonly HashSet _sanctionedSingleUnitIdentifiers = new() + private static readonly HashSet _sanctionedSingleUnitIdentifiers = new(StringComparer.Ordinal) { "acre", "bit", diff --git a/Jint/Native/Intl/PluralRulesConstructor.cs b/Jint/Native/Intl/PluralRulesConstructor.cs index 66b0052304..877ebe7488 100644 --- a/Jint/Native/Intl/PluralRulesConstructor.cs +++ b/Jint/Native/Intl/PluralRulesConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-initializepluralrules /// - private void InitializePluralRules(JsObject pluralRules, JsValue locales, JsValue options) + private static void InitializePluralRules(JsObject pluralRules, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/Intl/RelativeTimeFormatConstructor.cs b/Jint/Native/Intl/RelativeTimeFormatConstructor.cs index 0afb13dca2..0679add371 100644 --- a/Jint/Native/Intl/RelativeTimeFormatConstructor.cs +++ b/Jint/Native/Intl/RelativeTimeFormatConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-InitializeRelativeTimeFormat /// - private void InitializeRelativeTimeFormat(JsObject relativeTimeFormat, JsValue locales, JsValue options) + private static void InitializeRelativeTimeFormat(JsObject relativeTimeFormat, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/Iterator/IteratorInstance.cs b/Jint/Native/Iterator/IteratorInstance.cs index fd8c86ef4e..0aaa57907d 100644 --- a/Jint/Native/Iterator/IteratorInstance.cs +++ b/Jint/Native/Iterator/IteratorInstance.cs @@ -27,7 +27,7 @@ public virtual void Close(CompletionType completion) /// /// https://tc39.es/ecma262/#sec-createiterresultobject /// - private ObjectInstance CreateIterResultObject(JsValue value, bool done) + private IteratorResult CreateIterResultObject(JsValue value, bool done) { return new IteratorResult(_engine, value, JsBoolean.Create(done)); } diff --git a/Jint/Native/Iterator/IteratorResult.cs b/Jint/Native/Iterator/IteratorResult.cs index ca802fe0d0..471108e378 100644 --- a/Jint/Native/Iterator/IteratorResult.cs +++ b/Jint/Native/Iterator/IteratorResult.cs @@ -32,12 +32,12 @@ public static IteratorResult CreateKeyValueIteratorPosition(Engine engine, JsVal public override JsValue Get(JsValue property, JsValue receiver) { - if (property == CommonProperties.Value) + if (CommonProperties.Value.Equals(property)) { return _value; } - if (property == CommonProperties.Done) + if (CommonProperties.Done.Equals(property)) { return _done; } diff --git a/Jint/Native/JsArray.cs b/Jint/Native/JsArray.cs index f17d82c622..7dc4473e78 100644 --- a/Jint/Native/JsArray.cs +++ b/Jint/Native/JsArray.cs @@ -1,8 +1,10 @@ +using System.Diagnostics; using Jint.Native.Array; -using Jint.Runtime.Descriptors; namespace Jint.Native; +[DebuggerTypeProxy(typeof(JsArrayDebugView))] +[DebuggerDisplay("Count = {Length}")] public sealed class JsArray : ArrayInstance { /// @@ -22,16 +24,29 @@ public JsArray(Engine engine, uint capacity = 0, uint length = 0) : base(engine, public JsArray(Engine engine, JsValue[] items) : base(engine, items) { } - - /// - /// Possibility to construct valid array fast, requires that supplied array does not have holes. - /// The array will be owned and modified by Jint afterwards. - /// - public JsArray(Engine engine, PropertyDescriptor[] items) : base(engine, items) + + private sealed class JsArrayDebugView { - } + private readonly JsArray _array; - internal JsArray(Engine engine, object[] items) : base(engine, items) - { + public JsArrayDebugView(JsArray array) + { + _array = array; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public JsValue[] Values + { + get + { + var values = new JsValue[_array.Length]; + var i = 0; + foreach (var value in _array) + { + values[i++] = value; + } + return values; + } + } } } diff --git a/Jint/Native/JsBigInt.cs b/Jint/Native/JsBigInt.cs index a9d2867b2f..18cbe8e0fd 100644 --- a/Jint/Native/JsBigInt.cs +++ b/Jint/Native/JsBigInt.cs @@ -95,15 +95,9 @@ public override bool IsLooselyEqual(JsValue value) return false; } - public override bool Equals(object? other) - { - return Equals(other as JsBigInt); - } + public override bool Equals(object? obj) => Equals(obj as JsBigInt); - public override bool Equals(JsValue? other) - { - return Equals(other as JsBigInt); - } + public override bool Equals(JsValue? other) => Equals(other as JsBigInt); public bool Equals(JsBigInt? other) { @@ -115,8 +109,5 @@ public bool Equals(JsBigInt? other) return ReferenceEquals(this, other) || _value == other._value; } - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => _value.GetHashCode(); } diff --git a/Jint/Native/JsBoolean.cs b/Jint/Native/JsBoolean.cs index e735c5d94a..c74866a672 100644 --- a/Jint/Native/JsBoolean.cs +++ b/Jint/Native/JsBoolean.cs @@ -38,11 +38,16 @@ public override bool IsLooselyEqual(JsValue value) return !value.IsNullOrUndefined() && base.IsLooselyEqual(value); } - public override bool Equals(JsValue? obj) + public override bool Equals(object? obj) { return Equals(obj as JsBoolean); } + public override bool Equals(JsValue? other) + { + return Equals(other as JsBoolean); + } + public bool Equals(JsBoolean? other) { if (ReferenceEquals(this, other)) diff --git a/Jint/Native/JsNull.cs b/Jint/Native/JsNull.cs index b08b12c09e..96179300f4 100644 --- a/Jint/Native/JsNull.cs +++ b/Jint/Native/JsNull.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Runtime; namespace Jint.Native; @@ -8,7 +9,7 @@ internal JsNull() : base(Types.Null) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "null"; @@ -17,13 +18,11 @@ public override bool IsLooselyEqual(JsValue value) return ReferenceEquals(Null, value) || ReferenceEquals(Undefined, value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsNull); - } + public override bool Equals(object? obj) => Equals(obj as JsNull); - public bool Equals(JsNull? other) - { - return other is not null; - } + public override bool Equals(JsValue? other) => Equals(other as JsNull); + + public bool Equals(JsNull? other) => other is not null; + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsNumber.cs b/Jint/Native/JsNumber.cs index 5306bd2b4e..e4cb66833d 100644 --- a/Jint/Native/JsNumber.cs +++ b/Jint/Native/JsNumber.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native.Number; @@ -5,11 +7,13 @@ namespace Jint.Native; +[DebuggerDisplay("{_value}", Type = "string")] public sealed class JsNumber : JsValue, IEquatable { // .NET double epsilon and JS epsilon have different values internal const double JavaScriptEpsilon = 2.2204460492503130808472633361816E-16; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly double _value; // how many decimals to check when determining if double is actually an int @@ -87,10 +91,10 @@ internal static JsNumber Create(object value) var underlyingType = System.Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); return underlyingType switch { - TypeCode.Int64 => Create(Convert.ToInt64(value)), - TypeCode.UInt32 => Create(Convert.ToUInt64(value)), - TypeCode.UInt64 => Create(Convert.ToUInt64(value)), - _ => Create(Convert.ToInt32(value)) + TypeCode.Int64 => Create(Convert.ToInt64(value, CultureInfo.InvariantCulture)), + TypeCode.UInt32 => Create(Convert.ToUInt64(value, CultureInfo.InvariantCulture)), + TypeCode.UInt64 => Create(Convert.ToUInt64(value, CultureInfo.InvariantCulture)), + _ => Create(Convert.ToInt32(value, CultureInfo.InvariantCulture)) }; } @@ -262,10 +266,9 @@ public override bool IsLooselyEqual(JsValue value) return base.IsLooselyEqual(value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsNumber); - } + public override bool Equals(object? obj) => Equals(obj as JsNumber); + + public override bool Equals(JsValue? other) => Equals(other as JsNumber); public bool Equals(JsNumber? other) { @@ -287,8 +290,5 @@ public bool Equals(JsNumber? other) return _value == other._value; } - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => _value.GetHashCode(); } diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index b418ca5924..6a43ac3657 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -1,8 +1,11 @@ +using System.Collections.Concurrent; +using System.Diagnostics; using System.Text; using Jint.Runtime; namespace Jint.Native; +[DebuggerDisplay("{ToString()}")] public class JsString : JsValue, IEquatable, IEquatable { private const int AsciiMax = 126; @@ -10,26 +13,29 @@ public class JsString : JsValue, IEquatable, IEquatable private static readonly JsString[] _charToStringJsValue; private static readonly JsString[] _intToStringJsValue; - public static readonly JsString Empty = new JsString(""); - internal static readonly JsString NullString = new JsString("null"); - internal static readonly JsString UndefinedString = new JsString("undefined"); - internal static readonly JsString ObjectString = new JsString("object"); - internal static readonly JsString FunctionString = new JsString("function"); - internal static readonly JsString BooleanString = new JsString("boolean"); - internal static readonly JsString StringString = new JsString("string"); - internal static readonly JsString NumberString = new JsString("number"); - internal static readonly JsString BigIntString = new JsString("bigint"); - internal static readonly JsString SymbolString = new JsString("symbol"); - internal static readonly JsString DefaultString = new JsString("default"); - internal static readonly JsString NumberZeroString = new JsString("0"); - internal static readonly JsString NumberOneString = new JsString("1"); - internal static readonly JsString TrueString = new JsString("true"); - internal static readonly JsString FalseString = new JsString("false"); - internal static readonly JsString LengthString = new JsString("length"); - internal static readonly JsValue CommaString = new JsString(","); - + public static readonly JsString Empty; + internal static readonly JsString NullString; + internal static readonly JsString UndefinedString; + internal static readonly JsString ObjectString; + internal static readonly JsString FunctionString; + internal static readonly JsString BooleanString; + internal static readonly JsString StringString; + internal static readonly JsString NumberString; + internal static readonly JsString BigIntString; + internal static readonly JsString SymbolString; + internal static readonly JsString DefaultString; + internal static readonly JsString NumberZeroString; + internal static readonly JsString NumberOneString; + internal static readonly JsString TrueString; + internal static readonly JsString FalseString; + internal static readonly JsString LengthString; + internal static readonly JsValue CommaString; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal string _value; + private static ConcurrentDictionary _stringCache; + static JsString() { _charToJsValue = new JsString[AsciiMax + 1]; @@ -46,6 +52,26 @@ static JsString() { _intToStringJsValue[i] = new JsString(TypeConverter.ToString(i)); } + + + _stringCache = new ConcurrentDictionary(StringComparer.Ordinal); + Empty = new JsString("", InternalTypes.String); + NullString = CachedCreate("null"); + UndefinedString = CachedCreate("undefined"); + ObjectString = CachedCreate("object"); + FunctionString = CachedCreate("function"); + BooleanString = CachedCreate("boolean"); + StringString = CachedCreate("string"); + NumberString = CachedCreate("number"); + BigIntString = CachedCreate("bigint"); + SymbolString = CachedCreate("symbol"); + DefaultString = CachedCreate("default"); + NumberZeroString = CachedCreate("0"); + NumberOneString = CachedCreate("1"); + TrueString = CachedCreate("true"); + FalseString = CachedCreate("false"); + LengthString = CachedCreate("length"); + CommaString = CachedCreate(","); } public JsString(string value) : this(value, InternalTypes.String) @@ -143,6 +169,16 @@ internal static JsString Create(string value) return new JsString(value); } + internal static JsString CachedCreate(string value) + { + if (value.Length is < 2 or > 10) + { + return Create(value); + } + + return _stringCache.GetOrAdd(value, static x => new JsString(x)); + } + internal static JsString Create(char value) { var temp = _charToJsValue; @@ -231,13 +267,13 @@ internal int IndexOf(char value) internal bool StartsWith(string value, int start = 0) { - return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan()); + return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan(), StringComparison.Ordinal); } internal bool EndsWith(string value, int end = 0) { var start = end - value.Length; - return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan()); + return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan(), StringComparison.Ordinal); } internal string Substring(int startIndex, int length) @@ -250,15 +286,11 @@ internal string Substring(int startIndex) return ToString().Substring(startIndex); } - public sealed override bool Equals(JsValue? obj) - { - return Equals(obj as JsString); - } + public sealed override bool Equals(object? obj) => Equals(obj as JsString); - public virtual bool Equals(string? s) - { - return s != null && ToString() == s; - } + public sealed override bool Equals(JsValue? other) => Equals(other as JsString); + + public virtual bool Equals(string? other) => other != null && string.Equals(ToString(), other, StringComparison.Ordinal); public virtual bool Equals(JsString? other) { @@ -272,7 +304,7 @@ public virtual bool Equals(JsString? other) return true; } - return _value == other.ToString(); + return string.Equals(_value, other.ToString(), StringComparison.Ordinal); } public override bool IsLooselyEqual(JsValue value) @@ -290,15 +322,7 @@ public override bool IsLooselyEqual(JsValue value) return base.IsLooselyEqual(value); } - public sealed override bool Equals(object? obj) - { - return Equals(obj as JsString); - } - - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(_value); internal sealed class ConcatenatedString : JsString { @@ -374,7 +398,7 @@ public override bool Equals(string? s) return true; } - return _value == s; + return string.Equals(_value, s, StringComparison.Ordinal); } public override bool Equals(JsString? other) @@ -398,7 +422,7 @@ public override bool Equals(JsString? other) return true; } - return ToString() == cs.ToString(); + return string.Equals(ToString(), cs.ToString(), StringComparison.Ordinal); } if (other is null || other.Length != Length) @@ -406,13 +430,10 @@ public override bool Equals(JsString? other) return false; } - return ToString() == other.ToString(); + return string.Equals(ToString(), other.ToString(), StringComparison.Ordinal); } - public override int GetHashCode() - { - return _stringBuilder?.GetHashCode() ?? _value.GetHashCode(); - } + public override int GetHashCode() => _stringBuilder?.GetHashCode() ?? StringComparer.Ordinal.GetHashCode(_value); internal override JsValue DoClone() { diff --git a/Jint/Native/JsSymbol.cs b/Jint/Native/JsSymbol.cs index d2f3262891..664d1a3383 100644 --- a/Jint/Native/JsSymbol.cs +++ b/Jint/Native/JsSymbol.cs @@ -27,18 +27,11 @@ public override string ToString() return "Symbol(" + value + ")"; } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsSymbol); - } + public override bool Equals(object? obj) => Equals(obj as JsSymbol); - public bool Equals(JsSymbol? other) - { - return ReferenceEquals(this, other); - } + public override bool Equals(JsValue? other) => Equals(other as JsSymbol); - public override int GetHashCode() - { - return RuntimeHelpers.GetHashCode(this); - } + public bool Equals(JsSymbol? other) => ReferenceEquals(this, other); + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsUndefined.cs b/Jint/Native/JsUndefined.cs index 9772944b93..ea7ab6a75d 100644 --- a/Jint/Native/JsUndefined.cs +++ b/Jint/Native/JsUndefined.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Runtime; namespace Jint.Native; @@ -8,7 +9,7 @@ internal JsUndefined() : base(Types.Undefined) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "undefined"; @@ -17,13 +18,11 @@ public override bool IsLooselyEqual(JsValue value) return ReferenceEquals(Undefined, value) || ReferenceEquals(Null, value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsUndefined); - } + public override bool Equals(object? obj) => Equals(obj as JsUndefined); - public bool Equals(JsUndefined? other) - { - return !ReferenceEquals(null, other); - } + public override bool Equals(JsValue? other) => Equals(other as JsUndefined); + + public bool Equals(JsUndefined? other) => !ReferenceEquals(null, other); + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 6499e0d58d..cfab33dbab 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -9,14 +9,16 @@ using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; +using Jint.Runtime.Interop; namespace Jint.Native { - [DebuggerTypeProxy(typeof(JsValueDebugView))] public abstract class JsValue : IEquatable { public static readonly JsValue Undefined = new JsUndefined(); public static readonly JsValue Null = new JsNull(); + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly InternalTypes _type; protected JsValue(Types type) @@ -32,8 +34,10 @@ internal JsValue(InternalTypes type) [Pure] public virtual bool IsArray() => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsIntegerIndexedArray => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsConstructor => false; [Pure] @@ -98,6 +102,7 @@ internal bool TryGetIterator(Realm realm, [NotNullWhen(true)] out IteratorInstan return true; } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public Types Type { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -152,7 +157,7 @@ public static JsValue FromObjectWithType(Engine engine, object? value, Type? typ /// Converts a to its underlying CLR value. /// /// The underlying CLR value of the instance. - public abstract object ToObject(); + public abstract object? ToObject(); /// /// Coerces boolean value from instance. @@ -329,12 +334,12 @@ public virtual bool IsLooselyEqual(JsValue value) return x.IsLooselyEqual(TypeConverter.ToNumber(y)); } - if (y.IsObject() && (x._type & InternalTypes.Primitive) != 0) + if (y.IsObject() && (x._type & InternalTypes.Primitive) != InternalTypes.None) { return x.IsLooselyEqual(TypeConverter.ToPrimitive(y)); } - if (x.IsObject() && (y._type & InternalTypes.Primitive) != 0) + if (x.IsObject() && (y._type & InternalTypes.Primitive) != InternalTypes.None) { return y.IsLooselyEqual(TypeConverter.ToPrimitive(x)); } @@ -345,66 +350,14 @@ public virtual bool IsLooselyEqual(JsValue value) /// /// Strict equality. /// - public override bool Equals(object? obj) - { - return Equals(obj as JsValue); - } + public override bool Equals(object? obj) => Equals(obj as JsValue); /// /// Strict equality. /// - public virtual bool Equals(JsValue? other) - { - return ReferenceEquals(this, other); - } + public virtual bool Equals(JsValue? other) => ReferenceEquals(this, other); - public override int GetHashCode() - { - return _type.GetHashCode(); - } - - internal sealed class JsValueDebugView - { - public string Value; - - public JsValueDebugView(JsValue value) - { - switch (value.Type) - { - case Types.None: - Value = "None"; - break; - case Types.Undefined: - Value = "undefined"; - break; - case Types.Null: - Value = "null"; - break; - case Types.Boolean: - Value = ((JsBoolean) value)._value + " (bool)"; - break; - case Types.String: - Value = value.ToString() + " (string)"; - break; - case Types.Number: - Value = ((JsNumber) value)._value + " (number)"; - break; - case Types.BigInt: - Value = ((JsBigInt) value)._value + " (bigint)"; - break; - case Types.Object: - Value = value.AsObject().GetType().Name; - break; - case Types.Symbol: - var jsValue = ((JsSymbol) value)._value; - Value = (jsValue.IsUndefined() ? "" : jsValue.ToString()) + " (symbol)"; - break; - default: - Value = "Unknown"; - break; - } - } - } + public override int GetHashCode() => _type.GetHashCode(); /// /// Some values need to be cloned in order to be assigned, like ConcatenatedString. @@ -413,16 +366,14 @@ public JsValueDebugView(JsValue value) internal JsValue Clone() { // concatenated string and arguments currently may require cloning - return (_type & InternalTypes.RequiresCloning) == 0 + return (_type & InternalTypes.RequiresCloning) == InternalTypes.None ? this : DoClone(); } - internal virtual JsValue DoClone() - { - return this; - } + internal virtual JsValue DoClone() => this; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsCallable => this is ICallable; /// @@ -465,6 +416,11 @@ internal virtual bool OrdinaryHasInstance(JsValue v) internal static bool SameValue(JsValue x, JsValue y) { + if (ReferenceEquals(x, y)) + { + return true; + } + var typea = x.Type; var typeb = y.Type; @@ -502,7 +458,7 @@ internal static bool SameValue(JsValue x, JsValue y) return false; case Types.String: - return TypeConverter.ToString(x) == TypeConverter.ToString(y); + return string.Equals(TypeConverter.ToString(x), TypeConverter.ToString(y), StringComparison.Ordinal); case Types.Boolean: return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y); case Types.Undefined: @@ -510,8 +466,10 @@ internal static bool SameValue(JsValue x, JsValue y) return true; case Types.Symbol: return x == y; + case Types.Object: + return x is ObjectWrapper xo && y is ObjectWrapper yo && ReferenceEquals(xo.Target, yo.Target); default: - return ReferenceEquals(x, y); + return false; } } diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index f23e9aae49..c0a559b05d 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Esprima; using Jint.Native.Object; @@ -251,7 +252,7 @@ private Token ScanNumericLiteral(ref State state) sb.Clear(); JsNumber value; - if (canBeInteger && long.TryParse(number, out var longResult) && longResult != -0) + if (canBeInteger && long.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longResult) && longResult != -0) { value = JsNumber.Create(longResult); } @@ -460,7 +461,7 @@ private void ThrowError(Token token, string messageFormat, params object[] argum [DoesNotReturn] private void ThrowError(int position, string messageFormat, params object[] arguments) { - string msg = System.String.Format(messageFormat, arguments); + var msg = string.Format(CultureInfo.InvariantCulture, messageFormat, arguments); ExceptionHelper.ThrowSyntaxError(_engine.Realm, $"{msg} at position {position}"); } @@ -505,7 +506,7 @@ public bool Match(char value) return _lookahead.Type == Tokens.Punctuator && value == _lookahead.FirstCharacter; } - private ObjectInstance ParseJsonArray(ref State state) + private JsArray ParseJsonArray(ref State state) { if ((++state.CurrentDepth) > _maxDepth) { @@ -602,7 +603,7 @@ when its full. return result ?? new JsArray(_engine, elements!.ToArray()); } - private ObjectInstance ParseJsonObject(ref State state) + private JsObject ParseJsonObject(ref State state) { if ((++state.CurrentDepth) > _maxDepth) { @@ -756,9 +757,10 @@ private sealed class Token public char FirstCharacter; public JsValue Value = JsValue.Undefined; public string Text = null!; - public TextRange Range = default; + public TextRange Range; } + [StructLayout(LayoutKind.Auto)] private readonly struct TextRange { public TextRange(int start, int end) diff --git a/Jint/Native/Json/JsonSerializer.cs b/Jint/Native/Json/JsonSerializer.cs index fe17770993..c13da3cb97 100644 --- a/Jint/Native/Json/JsonSerializer.cs +++ b/Jint/Native/Json/JsonSerializer.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using Jint.Collections; @@ -398,7 +399,7 @@ private static void AppendJsonStringCharacter(string value, ref int index, Strin else if (c < 0x20 || char.IsSurrogate(c)) { target.Append("\\u"); - target.Append(((int) c).ToString("x4")); + target.Append(((int) c).ToString("x4", CultureInfo.InvariantCulture)); } else { diff --git a/Jint/Native/Map/JsMap.cs b/Jint/Native/Map/JsMap.cs index 546244c98a..00bb401539 100644 --- a/Jint/Native/Map/JsMap.cs +++ b/Jint/Native/Map/JsMap.cs @@ -18,7 +18,7 @@ public JsMap(Engine engine, Realm realm) : base(engine) public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { return new PropertyDescriptor(_map.Count, PropertyFlag.AllForbidden); } @@ -28,7 +28,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { descriptor = new PropertyDescriptor(_map.Count, PropertyFlag.AllForbidden); return true; diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index 26d39f334b..a77d97993e 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Iterator; diff --git a/Jint/Native/Map/MapPrototype.cs b/Jint/Native/Map/MapPrototype.cs index ff77ac3f70..2135387e04 100644 --- a/Jint/Native/Map/MapPrototype.cs +++ b/Jint/Native/Map/MapPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/Number/Dtoa/Bignum.cs b/Jint/Native/Number/Dtoa/Bignum.cs index ac874ccbe3..8c8196e469 100644 --- a/Jint/Native/Number/Dtoa/Bignum.cs +++ b/Jint/Native/Number/Dtoa/Bignum.cs @@ -108,7 +108,7 @@ void Align(Bignum other) // We replace some of the hidden digits (X) of a with 0 digits. // a: aaaaaa000X or a: aaaaa0XX int zero_digits = exponent_ - other.exponent_; - EnsureCapacity(used_digits_ + zero_digits); + ValidateCapacity(used_digits_ + zero_digits); for (int i = used_digits_ - 1; i >= 0; --i) { bigits_[i + zero_digits] = bigits_[i]; @@ -126,7 +126,7 @@ void Align(Bignum other) } } - void EnsureCapacity(int size) + private static void ValidateCapacity(int size) { if (size > kBigitCapacity) { @@ -161,7 +161,7 @@ internal void AssignUInt16(uint value) Zero(); if (value == 0) return; - EnsureCapacity(1); + ValidateCapacity(1); bigits_[0] = value; used_digits_ = 1; } @@ -174,7 +174,7 @@ internal void AssignUInt64(ulong value) if (value == 0) return; int needed_bigits = kUInt64Size / kBigitSize + 1; - EnsureCapacity(needed_bigits); + ValidateCapacity(needed_bigits); for (int i = 0; i < needed_bigits; ++i) { bigits_[i] = (uint) (value & kBigitMask); @@ -419,7 +419,7 @@ internal void MultiplyByUInt32(uint factor) while (carry != 0) { - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); bigits_[used_digits_] = (uint) (carry & kBigitMask); used_digits_++; carry >>= kBigitSize; @@ -449,7 +449,7 @@ internal void MultiplyByUInt64(ulong factor) } while (carry != 0) { - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); bigits_[used_digits_] = (uint) (carry & kBigitMask); used_digits_++; carry >>= kBigitSize; @@ -461,7 +461,7 @@ internal void ShiftLeft(int shift_amount) if (used_digits_ == 0) return; exponent_ += shift_amount / kBigitSize; int local_shift = shift_amount % kBigitSize; - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); BigitsShiftLeft(local_shift); } @@ -516,7 +516,7 @@ internal void AssignPowerUInt16(uint baseValue, int power_exponent) int final_size = bit_size * power_exponent; // 1 extra bigit for the shifting, and one for rounded final_size. - EnsureCapacity(final_size / kBigitSize + 2); + ValidateCapacity(final_size / kBigitSize + 2); // Left to Right exponentiation. int mask = 1; @@ -578,7 +578,7 @@ void Square() { Debug.Assert(IsClamped()); int product_length = 2 * used_digits_; - EnsureCapacity(product_length); + ValidateCapacity(product_length); // Comba multiplication: compute each column separately. // Example: r = a2a1a0 * b2b1b0. diff --git a/Jint/Native/Number/Dtoa/CachePowers.cs b/Jint/Native/Number/Dtoa/CachePowers.cs index 159708f567..ffb948a79d 100644 --- a/Jint/Native/Number/Dtoa/CachePowers.cs +++ b/Jint/Native/Number/Dtoa/CachePowers.cs @@ -31,6 +31,7 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { @@ -52,6 +53,7 @@ internal CachedPower(ulong significand, short binaryExponent, short decimalExpon } } + [StructLayout(LayoutKind.Auto)] internal readonly struct GetCachedPowerResult { public GetCachedPowerResult(short decimalExponent, DiyFp cMk) @@ -180,4 +182,4 @@ internal static GetCachedPowerResult GetCachedPowerForBinaryExponentRange(int mi const int kMinDecimalExponent = -348; const int kMaxDecimalExponent = 340; } -} \ No newline at end of file +} diff --git a/Jint/Native/Number/Dtoa/DiyFp.cs b/Jint/Native/Number/Dtoa/DiyFp.cs index 27bc299de5..7ffc720b34 100644 --- a/Jint/Native/Number/Dtoa/DiyFp.cs +++ b/Jint/Native/Number/Dtoa/DiyFp.cs @@ -31,15 +31,17 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { -// This "Do It Yourself Floating Point" class implements a floating-point number -// with a uint64 significand and an int exponent. Normalized DiyFp numbers will -// have the most significant bit of the significand set. -// Multiplication and Subtraction do not normalize their results. -// DiyFp are not designed to contain special doubles (NaN and Infinity). + // This "Do It Yourself Floating Point" class implements a floating-point number + // with a uint64 significand and an int exponent. Normalized DiyFp numbers will + // have the most significant bit of the significand set. + // Multiplication and Subtraction do not normalize their results. + // DiyFp are not designed to contain special doubles (NaN and Infinity). + [StructLayout(LayoutKind.Auto)] internal readonly struct DiyFp { internal const int KSignificandSize = 64; @@ -114,4 +116,4 @@ public override string ToString() return "[DiyFp f:" + F + ", e:" + E + "]"; } } -} \ No newline at end of file +} diff --git a/Jint/Native/Number/Dtoa/DoubleHelper.cs b/Jint/Native/Number/Dtoa/DoubleHelper.cs index 91a4c2a48d..bc2773bc93 100644 --- a/Jint/Native/Number/Dtoa/DoubleHelper.cs +++ b/Jint/Native/Number/Dtoa/DoubleHelper.cs @@ -31,6 +31,7 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { @@ -112,6 +113,7 @@ private static bool IsSpecial(ulong d64) return (d64 & KExponentMask) == KExponentMask; } + [StructLayout(LayoutKind.Auto)] internal readonly struct NormalizedBoundariesResult { public NormalizedBoundariesResult(DiyFp minus, DiyFp plus) @@ -155,4 +157,4 @@ internal static NormalizedBoundariesResult NormalizedBoundaries(ulong d64) private const int KExponentBias = 0x3FF + KSignificandSize; private const int KDenormalExponent = -KExponentBias + 1; } -} \ No newline at end of file +} diff --git a/Jint/Native/Number/NumberPrototype.cs b/Jint/Native/Number/NumberPrototype.cs index 5a7ad49811..050107f1e5 100644 --- a/Jint/Native/Number/NumberPrototype.cs +++ b/Jint/Native/Number/NumberPrototype.cs @@ -345,7 +345,7 @@ private JsValue ToPrecision(JsValue thisObject, JsValue[] arguments) } } - private string CreateExponentialRepresentation( + private static string CreateExponentialRepresentation( DtoaBuilder buffer, int exponent, bool negative, @@ -426,63 +426,59 @@ private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments) var integer = (long) x; var fraction = x - integer; - string result = ToBase(integer, radix); + string result = NumberPrototype.ToBase(integer, radix); if (fraction != 0) { - result += "." + ToFractionBase(fraction, radix); + result += "." + NumberPrototype.ToFractionBase(fraction, radix); } return result; } - public string ToBase(long n, int radix) + internal static string ToBase(long n, int radix) { - const string digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + const string Digits = "0123456789abcdefghijklmnopqrstuvwxyz"; if (n == 0) { return "0"; } - using (var result = StringBuilderPool.Rent()) + using var result = StringBuilderPool.Rent(); + while (n > 0) { - while (n > 0) - { - var digit = (int) (n % radix); - n = n / radix; - result.Builder.Insert(0, digits[digit]); - } - - return result.ToString(); + var digit = (int) (n % radix); + n = n / radix; + result.Builder.Insert(0, Digits[digit]); } + + return result.ToString(); } - public string ToFractionBase(double n, int radix) + internal static string ToFractionBase(double n, int radix) { // based on the repeated multiplication method // http://www.mathpath.org/concepts/Num/frac.htm - const string digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + const string Digits = "0123456789abcdefghijklmnopqrstuvwxyz"; if (n == 0) { return "0"; } - using (var result = StringBuilderPool.Rent()) + using var result = StringBuilderPool.Rent(); + while (n > 0 && result.Length < 50) // arbitrary limit { - while (n > 0 && result.Length < 50) // arbitrary limit - { - var c = n*radix; - var d = (int) c; - n = c - d; - - result.Builder.Append(digits[d]); - } + var c = n*radix; + var d = (int) c; + n = c - d; - return result.ToString(); + result.Builder.Append(Digits[d]); } + + return result.ToString(); } - private string ToNumberString(double m) + private static string ToNumberString(double m) { using var stringBuilder = StringBuilderPool.Rent(); NumberToString(m, new DtoaBuilder(), stringBuilder.Builder); diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index 0b9a17513c..9e5a651221 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Iterator; using Jint.Runtime; @@ -552,7 +554,7 @@ private CreateDataPropertyOnObject() { } - public JsValue Call(JsValue thisObject, JsValue[] arguments) + public JsValue Call(JsValue thisObject, params JsValue[] arguments) { var o = (ObjectInstance) thisObject; var key = arguments.At(0); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 4bdb5fba63..c1124297c7 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -6,16 +6,19 @@ using Jint.Native.BigInt; using Jint.Native.Boolean; using Jint.Native.Function; +using Jint.Native.Json; using Jint.Native.Number; using Jint.Native.RegExp; using Jint.Native.String; using Jint.Native.Symbol; +using Jint.Native.TypedArray; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.Object { + [DebuggerTypeProxy(typeof(ObjectInstanceDebugView))] public partial class ObjectInstance : JsValue, IEquatable { private bool _initialized; @@ -41,7 +44,9 @@ internal ObjectInstance( _class = objectClass; // if engine is ready, we can take default prototype for object _prototype = engine.Realm.Intrinsics?.Object?.PrototypeObject; +#pragma warning disable MA0056 Extensible = true; +#pragma warning restore MA0056 } public Engine Engine @@ -130,6 +135,8 @@ internal static IConstructor SpeciesConstructor(ObjectInstance o, IConstructor d return null; } + internal void SetProperties(StringDictionarySlim properties) => SetProperties(new PropertyDictionary(properties)); + internal void SetProperties(PropertyDictionary? properties) { if (properties != null) @@ -225,11 +232,11 @@ public virtual List GetOwnPropertyKeys(Types types = Types.String | Typ { EnsureInitialized(); - var returningSymbols = (types & Types.Symbol) != 0 && _symbols?.Count > 0; - var returningStringKeys = (types & Types.String) != 0 && _properties?.Count > 0; + var returningSymbols = (types & Types.Symbol) != Types.None && _symbols?.Count > 0; + var returningStringKeys = (types & Types.String) != Types.None && _properties?.Count > 0; var propertyKeys = new List(); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { var initialOwnStringPropertyKeys = GetInitialOwnStringPropertyKeys(); if (!ReferenceEquals(initialOwnStringPropertyKeys, System.Linq.Enumerable.Empty())) @@ -265,7 +272,7 @@ public virtual List GetOwnPropertyKeys(Types types = Types.String | Typ return propertyKeys; } - if ((types & Types.String) == 0 && (types & Types.Symbol) != 0) + if ((types & Types.String) == Types.None && (types & Types.Symbol) != Types.None) { // only symbols requested if (_symbols != null) @@ -358,7 +365,7 @@ public virtual void RemoveOwnProperty(JsValue property) public override JsValue Get(JsValue property, JsValue receiver) { - if ((_type & InternalTypes.PlainObject) != 0 && ReferenceEquals(this, receiver) && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && ReferenceEquals(this, receiver) && property is JsString jsString) { EnsureInitialized(); if (_properties?.TryGetValue(jsString.ToString(), out var ownDesc) == true) @@ -386,12 +393,12 @@ internal JsValue UnwrapJsValue(PropertyDescriptor desc) internal static JsValue UnwrapJsValue(PropertyDescriptor desc, JsValue thisObject) { - var value = (desc._flags & PropertyFlag.CustomJsValue) != 0 + var value = (desc._flags & PropertyFlag.CustomJsValue) != PropertyFlag.None ? desc.CustomValue : desc._value; // IsDataDescriptor inlined - if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 || value is not null) + if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None || value is not null) { return value ?? Undefined; } @@ -514,12 +521,12 @@ public bool Set(JsValue p, JsValue v, bool throwOnError) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Set(JsValue property, JsValue value) { - if ((_type & InternalTypes.PlainObject) != 0 && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && property is JsString jsString) { var key = (Key) jsString.ToString(); if (_properties?.TryGetValue(key, out var ownDesc) == true) { - if ((ownDesc._flags & PropertyFlag.Writable) != 0) + if ((ownDesc._flags & PropertyFlag.Writable) != PropertyFlag.None) { ownDesc._value = value; return true; @@ -537,12 +544,12 @@ public bool Set(JsValue property, JsValue value) /// public override bool Set(JsValue property, JsValue value, JsValue receiver) { - if ((_type & InternalTypes.PlainObject) != 0 && ReferenceEquals(this, receiver) && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && ReferenceEquals(this, receiver) && property is JsString jsString) { var key = (Key) jsString.ToString(); if (_properties?.TryGetValue(key, out var ownDesc) == true) { - if ((ownDesc._flags & PropertyFlag.Writable) != 0) + if ((ownDesc._flags & PropertyFlag.Writable) != PropertyFlag.None) { ownDesc._value = value; return true; @@ -553,6 +560,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) return SetUnlikely(property, value, receiver); } + [MethodImpl(MethodImplOptions.NoInlining)] private bool SetUnlikely(JsValue property, JsValue value, JsValue receiver) { var ownDesc = GetOwnProperty(property); @@ -775,7 +783,7 @@ protected static bool ValidateAndApplyPropertyDescriptor(ObjectInstance? o, JsVa { propertyDescriptor = new PropertyDescriptor(descValue ?? Undefined, PropertyFlag.ConfigurableEnumerableWritable); } - else if ((desc._flags & PropertyFlag.ConfigurableEnumerableWritable) == 0) + else if ((desc._flags & PropertyFlag.ConfigurableEnumerableWritable) == PropertyFlag.None) { propertyDescriptor = new PropertyDescriptor(descValue ?? Undefined, PropertyFlag.AllForbidden); } @@ -810,7 +818,7 @@ protected static bool ValidateAndApplyPropertyDescriptor(ObjectInstance? o, JsVa var currentValue = current.Value; // 4. If every field in Desc is absent, return true. - if ((current._flags & (PropertyFlag.ConfigurableSet | PropertyFlag.EnumerableSet | PropertyFlag.WritableSet)) == 0 && + if ((current._flags & (PropertyFlag.ConfigurableSet | PropertyFlag.EnumerableSet | PropertyFlag.WritableSet)) == PropertyFlag.None && ReferenceEquals(currentGet, null) && ReferenceEquals(currentSet, null) && ReferenceEquals(currentValue, null)) @@ -1051,6 +1059,27 @@ private object ToObject(ObjectTraverseStack stack) converted = result; break; } + + if (this is JsTypedArray typedArrayInstance) + { + converted = typedArrayInstance._arrayElementType switch + { + TypedArrayElementType.Int8 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Int16 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Int32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.BigInt64 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Float32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Float64 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint8 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint8C => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint16 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.BigUint64 => typedArrayInstance.ToNativeArray(), + _ => throw new NotSupportedException("cannot handle element type") + }; + + break; + } if (this is BigIntInstance bigIntInstance) { @@ -1175,6 +1204,7 @@ bool TryGetValue(ulong idx, out JsValue jsValue) internal ICallable GetCallable(JsValue source) => source.GetCallable(_engine.Realm); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal bool IsConcatSpreadable { get @@ -1188,15 +1218,18 @@ internal bool IsConcatSpreadable } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public virtual bool IsArrayLike => TryGetValue(CommonProperties.Length, out var lengthValue) && lengthValue.IsNumber() && ((JsNumber) lengthValue)._value >= 0; // safe default + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool HasOriginalIterator => false; internal override bool IsIntegerIndexedArray => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public virtual uint Length => (uint) TypeConverter.ToLength(Get(CommonProperties.Length)); public virtual bool PreventExtensions() @@ -1444,10 +1477,9 @@ private void ThrowIncompatibleReceiver(JsValue value, string methodName) ExceptionHelper.ThrowTypeError(_engine.Realm, $"Method {methodName} called on incompatible receiver {value}"); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as ObjectInstance); - } + public override bool Equals(object? obj) => Equals(obj as ObjectInstance); + + public override bool Equals(JsValue? other) => Equals(other as ObjectInstance); public bool Equals(ObjectInstance? other) { @@ -1464,6 +1496,8 @@ public bool Equals(ObjectInstance? other) return false; } + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); + internal IEnumerable GetKeys() { var visited = new HashSet(); @@ -1508,7 +1542,7 @@ internal virtual ulong GetSmallestIndex(ulong length) } var min = length; - foreach (var entry in Properties) + foreach (var entry in GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { @@ -1518,7 +1552,7 @@ internal virtual ulong GetSmallestIndex(ulong length) if (Prototype?.Properties != null) { - foreach (var entry in Prototype.Properties) + foreach (var entry in Prototype.GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { @@ -1624,5 +1658,45 @@ internal enum IntegrityLevel Sealed, Frozen } + + private sealed class ObjectInstanceDebugView + { + private readonly ObjectInstance _obj; + + public ObjectInstanceDebugView(ObjectInstance obj) + { + _obj = obj; + } + + public ObjectInstance? Prototype => _obj.Prototype; + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Entries + { + get + { + var keys = new KeyValuePair[(_obj._properties?.Count ?? 0) + (_obj._symbols?.Count ?? 0)]; + + var i = 0; + if (_obj._properties is not null) + { + foreach(var key in _obj._properties) + { + keys[i++] = new KeyValuePair(key.Key.Name, UnwrapJsValue(key.Value, _obj)); + } + } + if (_obj._symbols is not null) + { + foreach(var key in _obj._symbols) + { + keys[i++] = new KeyValuePair(key.Key, UnwrapJsValue(key.Value, _obj)); + } + } + return keys; + } + } + + private string DebugToString() => new JsonSerializer(_obj._engine).Serialize(_obj, Undefined, " ").ToString(); + } } } diff --git a/Jint/Native/Object/ObjectPrototype.cs b/Jint/Native/Object/ObjectPrototype.cs index 9dbaa826df..7b1655397b 100644 --- a/Jint/Native/Object/ObjectPrototype.cs +++ b/Jint/Native/Object/ObjectPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Array; using Jint.Native.Proxy; diff --git a/Jint/Native/PrivateName.cs b/Jint/Native/PrivateName.cs index 1340330c7c..8a0396452e 100644 --- a/Jint/Native/PrivateName.cs +++ b/Jint/Native/PrivateName.cs @@ -50,7 +50,7 @@ public bool Equals(PrivateName? other) public override int GetHashCode() { - return _identifier.Name.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(_identifier.Name); } } @@ -61,14 +61,8 @@ internal sealed class PrivateIdentifierNameComparer : IEqualityComparer string.Equals(x?.Name, y?.Name, StringComparison.Ordinal); - public int GetHashCode(PrivateIdentifier obj) - { - return obj.Name.GetHashCode(); - } + public int GetHashCode(PrivateIdentifier obj) => StringComparer.Ordinal.GetHashCode(obj.Name); } diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index 63d7d3b568..da3da29b27 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -21,8 +21,6 @@ internal sealed class PromiseConstructor : Constructor { private static readonly JsString _functionName = new JsString("Promise"); - internal PromisePrototype PrototypeObject { get; private set; } - internal PromiseConstructor( Engine engine, Realm realm, @@ -36,18 +34,21 @@ internal PromiseConstructor( _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } + internal PromisePrototype PrototypeObject { get; } + protected override void Initialize() { - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - const PropertyFlag lengthFlags = PropertyFlag.Configurable; + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag LengthFlags = PropertyFlag.Configurable; var properties = new PropertyDictionary(6, checkExistingKeys: false) { - ["resolve"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, lengthFlags), propertyFlags)), - ["reject"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, lengthFlags), propertyFlags)), - ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags), propertyFlags)), - ["allSettled"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "allSettled", AllSettled, 1, lengthFlags), propertyFlags)), - ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, lengthFlags), propertyFlags)), - ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags), propertyFlags)), + ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, LengthFlags), PropertyFlags)), + ["allSettled"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "allSettled", AllSettled, 1, LengthFlags), PropertyFlags)), + ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, LengthFlags), PropertyFlags)), + ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, LengthFlags), PropertyFlags)), + ["reject"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, LengthFlags), PropertyFlags)), + ["resolve"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, LengthFlags), PropertyFlags)), + ["withResolvers"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "withResolvers", WithResolvers , 0, LengthFlags), PropertyFlags)), }; SetProperties(properties); @@ -113,6 +114,16 @@ internal JsValue Resolve(JsValue thisObject, JsValue[] arguments) return PromiseResolve(thisObject, x); } + private JsObject WithResolvers(JsValue thisObject, JsValue[] arguments) + { + var promiseCapability = NewPromiseCapability(_engine, thisObject); + var obj = OrdinaryObjectCreate(_engine, _engine.Realm.Intrinsics.Object.PrototypeObject); + obj.CreateDataPropertyOrThrow("promise", promiseCapability.PromiseInstance); + obj.CreateDataPropertyOrThrow("resolve", promiseCapability.ResolveObj); + obj.CreateDataPropertyOrThrow("reject", promiseCapability.RejectObj); + return obj; + } + /// /// https://tc39.es/ecma262/#sec-promise-resolve /// diff --git a/Jint/Native/Promise/PromiseOperations.cs b/Jint/Native/Promise/PromiseOperations.cs index ea7100642b..7791befbd5 100644 --- a/Jint/Native/Promise/PromiseOperations.cs +++ b/Jint/Native/Promise/PromiseOperations.cs @@ -58,7 +58,7 @@ private static Action NewPromiseReactionJob(PromiseReaction reaction, JsValue va break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(reaction), "Unknown reaction type"); } } }; diff --git a/Jint/Native/Promise/PromisePrototype.cs b/Jint/Native/Promise/PromisePrototype.cs index c81f91c7ef..5150ccebe9 100644 --- a/Jint/Native/Promise/PromisePrototype.cs +++ b/Jint/Native/Promise/PromisePrototype.cs @@ -118,7 +118,7 @@ private JsValue Finally(JsValue thisValue, JsValue[] arguments) } // https://tc39.es/ecma262/#sec-thenfinallyfunctions - private JsValue ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => + private ClrFunctionInstance ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => new ClrFunctionInstance(_engine, "", (_, args) => { var value = args.At(0); @@ -137,7 +137,7 @@ private JsValue ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => }, 1, PropertyFlag.Configurable); // https://tc39.es/ecma262/#sec-catchfinallyfunctions - private JsValue CatchFinallyFunctions(ICallable onFinally, IConstructor ctor) => + private ClrFunctionInstance CatchFinallyFunctions(ICallable onFinally, IConstructor ctor) => new ClrFunctionInstance(_engine, "", (_, args) => { var reason = args.At(0); diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index 463cf05529..76be0210dc 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -100,6 +100,8 @@ public override bool IsArray() return _target.IsArray(); } + public override object ToObject() => _target.ToObject(); + internal override bool IsConstructor { get @@ -126,7 +128,7 @@ public override JsValue Get(JsValue property, JsValue receiver) AssertTargetNotRevoked(property); var target = _target; - if (property == KeyFunctionRevoke || !TryCallHandler(TrapGet, new[] { target, TypeConverter.ToPropertyKey(property), receiver }, out var result)) + if (KeyFunctionRevoke.Equals(property) || !TryCallHandler(TrapGet, new[] { target, TypeConverter.ToPropertyKey(property), receiver }, out var result)) { return target.Get(property, receiver); } @@ -134,13 +136,21 @@ public override JsValue Get(JsValue property, JsValue receiver) var targetDesc = target.GetOwnProperty(property); if (targetDesc != PropertyDescriptor.Undefined) { - if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable && !ReferenceEquals(result, targetDesc._value)) + if (targetDesc.IsDataDescriptor()) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + var targetValue = targetDesc.Value; + if (!targetDesc.Configurable && !targetDesc.Writable && !SameValue(result, targetValue)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '{targetValue}' but got '{result}')"); + } } - if (targetDesc.IsAccessorDescriptor() && !targetDesc.Configurable && (targetDesc.Get ?? Undefined).IsUndefined() && !result.IsUndefined()) + + if (targetDesc.IsAccessorDescriptor()) { - ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{result}')"); + if (!targetDesc.Configurable && (targetDesc.Get ?? Undefined).IsUndefined() && !result.IsUndefined()) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{result}')"); + } } } @@ -152,7 +162,7 @@ public override JsValue Get(JsValue property, JsValue receiver) /// public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) { - if (!TryCallHandler(TrapOwnKeys, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapOwnKeys, new[] { _target }, out var result)) { return _target.GetOwnPropertyKeys(types); } @@ -278,7 +288,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) /// /// https://tc39.es/ecma262/#sec-completepropertydescriptor /// - private void CompletePropertyDescriptor(PropertyDescriptor desc) + private static void CompletePropertyDescriptor(PropertyDescriptor desc) { if (desc.IsGenericDescriptor() || desc.IsDataDescriptor()) { @@ -321,14 +331,15 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) return false; } - var targetDesc = _target.GetOwnProperty(property); + var targetDesc = _target.GetOwnProperty(property); if (targetDesc != PropertyDescriptor.Undefined) { if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable) { - if (targetDesc.Value != value) + var targetValue = targetDesc.Value; + if (!SameValue(targetValue, value)) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable data property with a different value"); } } @@ -336,7 +347,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) { if ((targetDesc.Set ?? Undefined).IsUndefined()) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter"); } } } @@ -405,7 +416,7 @@ private static bool IsCompatiblePropertyDescriptor(bool extensible, PropertyDesc /// public override bool HasProperty(JsValue property) { - if (!TryCallHandler(TrapHas, new [] { _target, TypeConverter.ToPropertyKey(property) }, out var jsValue)) + if (!TryCallHandler(TrapHas, new[] { _target, TypeConverter.ToPropertyKey(property) }, out var jsValue)) { return _target.HasProperty(property); } @@ -474,7 +485,7 @@ public override bool Delete(JsValue property) /// public override bool PreventExtensions() { - if (!TryCallHandler(TrapPreventExtensions, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapPreventExtensions, new[] { _target }, out var result)) { return _target.PreventExtensions(); } @@ -496,7 +507,7 @@ public override bool Extensible { get { - if (!TryCallHandler(TrapIsExtensible, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapIsExtensible, new[] { _target }, out var result)) { return _target.Extensible; } @@ -516,7 +527,7 @@ public override bool Extensible /// protected internal override ObjectInstance? GetPrototypeOf() { - if (!TryCallHandler(TrapGetProtoTypeOf, new JsValue[] { _target }, out var handlerProto )) + if (!TryCallHandler(TrapGetProtoTypeOf, new[] { _target }, out var handlerProto)) { return _target.Prototype; } diff --git a/Jint/Native/Proxy/ProxyConstructor.cs b/Jint/Native/Proxy/ProxyConstructor.cs index d06fdcd237..be8e81ec98 100644 --- a/Jint/Native/Proxy/ProxyConstructor.cs +++ b/Jint/Native/Proxy/ProxyConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Runtime; diff --git a/Jint/Native/Reflect/ReflectInstance.cs b/Jint/Native/Reflect/ReflectInstance.cs index 4713fe1516..a12f063ca1 100644 --- a/Jint/Native/Reflect/ReflectInstance.cs +++ b/Jint/Native/Reflect/ReflectInstance.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/RegExp/JsRegExp.cs b/Jint/Native/RegExp/JsRegExp.cs index 629fabae0f..e0ee9df23a 100644 --- a/Jint/Native/RegExp/JsRegExp.cs +++ b/Jint/Native/RegExp/JsRegExp.cs @@ -74,9 +74,11 @@ public string Flags public bool FullUnicode { get; private set; } public bool UnicodeSets { get; private set; } + internal bool HasDefaultRegExpExec => Properties == null && Prototype is RegExpPrototype { HasDefaultExec: true }; + public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == PropertyLastIndex) + if (PropertyLastIndex.Equals(property)) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } @@ -86,7 +88,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == PropertyLastIndex) + if (PropertyLastIndex.Equals(property)) { _prototypeDescriptor = desc; return; @@ -108,7 +110,7 @@ public override IEnumerable> GetOwnPro } } - public override List GetOwnPropertyKeys(Types types) + public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = new List(); if (_prototypeDescriptor != null) diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index 9e0a174377..1c1db22dbc 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -92,7 +92,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return RegExpInitialize(r, p, f); } - private ObjectInstance RegExpInitialize(JsRegExp r, JsValue pattern, JsValue flags) + private JsRegExp RegExpInitialize(JsRegExp r, JsValue pattern, JsValue flags) { var p = pattern.IsUndefined() ? "" : TypeConverter.ToString(pattern); if (string.IsNullOrEmpty(p)) diff --git a/Jint/Native/RegExp/RegExpExtensions.cs b/Jint/Native/RegExp/RegExpExtensions.cs index 927f8be297..aa27b6bb8e 100644 --- a/Jint/Native/RegExp/RegExpExtensions.cs +++ b/Jint/Native/RegExp/RegExpExtensions.cs @@ -1,26 +1,5 @@ -using System.Diagnostics.CodeAnalysis; -using Jint.Native.Object; +namespace Jint.Native.RegExp; -namespace Jint.Native.RegExp +internal static class RegExpExtensions { - internal static class RegExpExtensions - { - internal static bool TryGetDefaultRegExpExec(this ObjectInstance? o, [NotNullWhen(true)] out Func? exec) - { - if (o is RegExpPrototype prototype) - { - return prototype.TryGetDefaultExec(prototype, out exec); - } - - if (o is JsRegExp instance) - { - exec = default; - return instance.Properties == null - && TryGetDefaultRegExpExec(instance.Prototype, out exec); - } - - exec = default; - return false; - } - } } diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index 7912a64859..ab1d50ff82 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Text.RegularExpressions; using Jint.Collections; using Jint.Native.Number; @@ -140,17 +141,17 @@ private JsValue Replace(JsValue thisObject, JsValue[] arguments) { var value = TypeConverter.ToString(replaceValue); replaceValue = value; - mayHaveNamedCaptures = value.IndexOf('$') != -1; + mayHaveNamedCaptures = value.Contains('$'); } var flags = TypeConverter.ToString(rx.Get(PropertyFlags)); - var global = flags.IndexOf('g') != -1; + var global = flags.Contains('g'); var fullUnicode = false; if (global) { - fullUnicode = flags.IndexOf('u') != -1; + fullUnicode = flags.Contains('u'); rx.Set(JsRegExp.PropertyLastIndex, 0, true); } @@ -158,7 +159,7 @@ private JsValue Replace(JsValue thisObject, JsValue[] arguments) if (!fullUnicode && !mayHaveNamedCaptures && !TypeConverter.ToBoolean(rx.Get(PropertySticky)) - && rx is JsRegExp rei && rei.TryGetDefaultRegExpExec(out _)) + && rx is JsRegExp rei && rei.HasDefaultRegExpExec) { var count = global ? int.MaxValue : 1; @@ -287,9 +288,11 @@ string Evaluator(Match match) if (position >= nextSourcePosition) { +#pragma warning disable CA1845 accumulatedResult = accumulatedResult + s.Substring(nextSourcePosition, position - nextSourcePosition) + replacement; +#pragma warning restore CA1845 nextSourcePosition = position + matchLength; } @@ -300,7 +303,9 @@ string Evaluator(Match match) return accumulatedResult; } +#pragma warning disable CA1845 return accumulatedResult + s.Substring(nextSourcePosition); +#pragma warning restore CA1845 } private static string CallFunctionalReplace(JsValue replacer, List replacerArgs) @@ -348,12 +353,14 @@ internal static string GetSubstitution( case '&': sb.Append(matched); break; +#pragma warning disable CA1846 case '`': sb.Append(str.Substring(0, position)); break; case '\'': sb.Append(str.Substring(position + matched.Length)); break; +#pragma warning restore CA1846 case '<': var gtPos = replacement.IndexOf('>', i + 1); if (gtPos == -1 || namedCaptures.IsUndefined()) @@ -464,11 +471,11 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) return a; } - if (!unicodeMatching && rx is JsRegExp R && R.TryGetDefaultRegExpExec(out _)) + if (!unicodeMatching && rx is JsRegExp R && R.HasDefaultRegExpExec) { // we can take faster path - if (R.Source == JsRegExp.regExpForMatchingAllCharacters) + if (string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) { // if empty string, just a string split return StringPrototype.SplitWithStringSeparator(_realm, "", s, (uint) s.Length); @@ -522,7 +529,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) return SplitSlow(s, splitter, unicodeMatching, lengthA, lim); } - private JsValue SplitSlow(string s, ObjectInstance splitter, bool unicodeMatching, uint lengthA, long lim) + private JsArray SplitSlow(string s, ObjectInstance splitter, bool unicodeMatching, uint lengthA, long lim) { var a = _realm.Intrinsics.Array.ArrayCreate(0); ulong previousStringIndex = 0; @@ -679,18 +686,18 @@ private JsValue Match(JsValue thisObject, JsValue[] arguments) var s = TypeConverter.ToString(arguments.At(0)); var flags = TypeConverter.ToString(rx.Get(PropertyFlags)); - var global = flags.IndexOf('g') != -1; + var global = flags.Contains('g'); if (!global) { return RegExpExec(rx, s); } - var fullUnicode = flags.IndexOf('u') != -1; + var fullUnicode = flags.Contains('u'); rx.Set(JsRegExp.PropertyLastIndex, JsNumber.PositiveZero, true); if (!fullUnicode && rx is JsRegExp rei - && rei.TryGetDefaultRegExpExec(out _)) + && rei.HasDefaultRegExpExec) { // fast path var a = _realm.Intrinsics.Array.ArrayCreate(0); @@ -812,8 +819,9 @@ private static ulong AdvanceStringIndex(string s, ulong index, bool unicode) internal static JsValue RegExpExec(ObjectInstance r, string s) { - var exec = r.Get(PropertyExec); - if (exec is ICallable callable) + var ri = r as JsRegExp; + + if ((ri is null || !ri.HasDefaultRegExpExec) && r.Get(PropertyExec) is ICallable callable) { var result = callable.Call(r, new JsValue[] { s }); if (!result.IsNull() && !result.IsObject()) @@ -824,7 +832,6 @@ internal static JsValue RegExpExec(ObjectInstance r, string s) return result; } - var ri = r as JsRegExp; if (ri is null) { ExceptionHelper.ThrowTypeError(r.Engine.Realm); @@ -833,17 +840,7 @@ internal static JsValue RegExpExec(ObjectInstance r, string s) return RegExpBuiltinExec(ri, s); } - internal bool TryGetDefaultExec(ObjectInstance o, [NotNullWhen((true))] out Func? exec) - { - if (o.Get(PropertyExec) is ClrFunctionInstance functionInstance && functionInstance._func == _defaultExec) - { - exec = _defaultExec; - return true; - } - - exec = default; - return false; - } + internal bool HasDefaultExec => Get(PropertyExec) is ClrFunctionInstance functionInstance && functionInstance._func == _defaultExec; /// /// https://tc39.es/ecma262/#sec-regexpbuiltinexec @@ -860,7 +857,7 @@ private static JsValue RegExpBuiltinExec(JsRegExp R, string s) lastIndex = 0; } - if (R.Source == JsRegExp.regExpForMatchingAllCharacters) // Reg Exp is really "" + if (string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) // Reg Exp is really "" { if (lastIndex > (ulong) s.Length) { diff --git a/Jint/Native/Set/JsSet.cs b/Jint/Native/Set/JsSet.cs index d13d5ef417..b5fb4d4553 100644 --- a/Jint/Native/Set/JsSet.cs +++ b/Jint/Native/Set/JsSet.cs @@ -16,7 +16,7 @@ public JsSet(Engine engine) : base(engine) public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { return new PropertyDescriptor(_set.Count, PropertyFlag.AllForbidden); } @@ -26,7 +26,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { descriptor = new PropertyDescriptor(_set.Count, PropertyFlag.AllForbidden); return true; diff --git a/Jint/Native/Set/SetPrototype.cs b/Jint/Native/Set/SetPrototype.cs index 5f97ca8c0b..11e0753c52 100644 --- a/Jint/Native/Set/SetPrototype.cs +++ b/Jint/Native/Set/SetPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index 2ab25f2732..116af5c0c8 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -5,7 +5,9 @@ using Jint.Native.Object; using Jint.Native.Promise; using Jint.Runtime; +using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; +using Jint.Runtime.Interop; using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Statements; using Jint.Runtime.Modules; @@ -15,7 +17,9 @@ namespace Jint.Native.ShadowRealm; /// /// https://tc39.es/proposal-shadowrealm/#sec-properties-of-shadowrealm-instances /// +#pragma warning disable MA0049 public sealed class ShadowRealm : ObjectInstance +#pragma warning restore MA0049 { private readonly JavaScriptParser _parser; internal readonly Realm _shadowRealm; @@ -38,6 +42,12 @@ public JsValue Evaluate(string sourceText) return PerformShadowRealmEval(sourceText, callerRealm); } + public JsValue Evaluate(Script script) + { + var callerRealm = _engine.Realm; + return PerformShadowRealmEval(script, callerRealm); + } + public JsValue ImportValue(string specifier, string exportName) { var callerRealm = _engine.Realm; @@ -45,6 +55,47 @@ public JsValue ImportValue(string specifier, string exportName) _engine.RunAvailableContinuations(); return value; } + public ShadowRealm SetValue(string name, Delegate value) + { + _shadowRealm.GlobalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(_engine, value), true, false, true)); + return this; + } + + public ShadowRealm SetValue(string name, string value) + { + return SetValue(name, JsString.Create(value)); + } + + public ShadowRealm SetValue(string name, double value) + { + return SetValue(name, JsNumber.Create(value)); + } + + public ShadowRealm SetValue(string name, int value) + { + return SetValue(name, JsNumber.Create(value)); + } + + public ShadowRealm SetValue(string name, bool value) + { + return SetValue(name, value ? JsBoolean.True : JsBoolean.False); + } + + public ShadowRealm SetValue(string name, JsValue value) + { + _shadowRealm.GlobalObject.Set(name, value); + return this; + } + + public ShadowRealm SetValue(string name, object obj) + { + var value = obj is Type t + ? TypeReference.CreateTypeReference(_engine, t) + : JsValue.FromObject(_engine, obj); + + return SetValue(name, value); + } + /// /// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval @@ -62,7 +113,7 @@ internal JsValue PerformShadowRealmEval(string sourceText, Realm callerRealm) } catch (ParserException e) { - if (e.Description == Messages.InvalidLHSInAssignment) + if (string.Equals(e.Description, Messages.InvalidLHSInAssignment, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceError(callerRealm, Messages.InvalidLHSInAssignment); } @@ -74,6 +125,22 @@ internal JsValue PerformShadowRealmEval(string sourceText, Realm callerRealm) return default; } + return PerformShadowRealmEvalInternal(script, callerRealm); + } + + internal JsValue PerformShadowRealmEval(Script script, Realm callerRealm) + { + var evalRealm = _shadowRealm; + + _engine._host.EnsureCanCompileStrings(callerRealm, evalRealm); + + return PerformShadowRealmEvalInternal(script, callerRealm); + } + + internal JsValue PerformShadowRealmEvalInternal(Script script, Realm callerRealm) + { + var evalRealm = _shadowRealm; + ref readonly var body = ref script.Body; if (body.Count == 0) { @@ -143,7 +210,7 @@ private static JsValue GetWrappedValue(Realm throwerRealm, Realm callerRealm, Js /// /// https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate /// - private static JsValue WrappedFunctionCreate(Realm throwerRealm, Realm callerRealm, ObjectInstance target) + private static WrappedFunction WrappedFunctionCreate(Realm throwerRealm, Realm callerRealm, ObjectInstance target) { var wrapped = new WrappedFunction(callerRealm.GlobalEnv._engine, callerRealm, target); try diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index 28caa2cc20..5a30c3df72 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -1,4 +1,6 @@ -using Jint.Collections; +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + +using Jint.Collections; using Jint.Native.Array; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/String/StringInstance.cs b/Jint/Native/String/StringInstance.cs index 73b80d8556..f98990df27 100644 --- a/Jint/Native/String/StringInstance.cs +++ b/Jint/Native/String/StringInstance.cs @@ -35,12 +35,12 @@ private static bool IsInt32(double d, out int intValue) public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Infinity) + if (CommonProperties.Infinity.Equals(property)) { return PropertyDescriptor.Undefined; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } @@ -51,7 +51,7 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property) return desc; } - if ((property._type & (InternalTypes.Number | InternalTypes.Integer | InternalTypes.String)) == 0) + if ((property._type & (InternalTypes.Number | InternalTypes.Integer | InternalTypes.String)) == InternalTypes.None) { return PropertyDescriptor.Undefined; } @@ -84,10 +84,10 @@ internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() yield return JsString.LengthString; } - public sealed override List GetOwnPropertyKeys(Types types) + public sealed override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = new List(StringData.Length + 1); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { for (uint i = 0; i < StringData.Length; ++i) { @@ -97,7 +97,7 @@ public sealed override List GetOwnPropertyKeys(Types types) keys.AddRange(base.GetOwnPropertyKeys(Types.String)); } - if ((types & Types.Symbol) != 0) + if ((types & Types.Symbol) != Types.None) { keys.AddRange(base.GetOwnPropertyKeys(Types.Symbol)); } @@ -107,7 +107,7 @@ public sealed override List GetOwnPropertyKeys(Types types) protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = desc; } @@ -119,7 +119,7 @@ protected internal sealed override void SetOwnProperty(JsValue property, Propert public sealed override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs index c781875a96..2beeece784 100644 --- a/Jint/Native/String/StringPrototype.cs +++ b/Jint/Native/String/StringPrototype.cs @@ -1,4 +1,8 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + +using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Jint.Collections; using Jint.Native.Json; @@ -228,7 +232,7 @@ private JsValue ToLocaleUpperCase(JsValue thisObject, JsValue[] arguments) { TypeConverter.CheckObjectCoercible(_engine, thisObject); var s = TypeConverter.ToString(thisObject); - return new JsString(s.ToUpper()); + return new JsString(s.ToUpper(CultureInfo.InvariantCulture)); } private JsValue ToUpperCase(JsValue thisObject, JsValue[] arguments) @@ -242,7 +246,7 @@ private JsValue ToLocaleLowerCase(JsValue thisObject, JsValue[] arguments) { TypeConverter.CheckObjectCoercible(_engine, thisObject); var s = TypeConverter.ToString(thisObject); - return new JsString(s.ToLower()); + return new JsString(s.ToLower(CultureInfo.InvariantCulture)); } private JsValue ToLowerCase(JsValue thisObject, JsValue[] arguments) @@ -350,7 +354,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) var limit = arguments.At(1); // fast path for empty regexp - if (separator is JsRegExp R && R.Source == JsRegExp.regExpForMatchingAllCharacters) + if (separator is JsRegExp R && string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) { separator = JsString.Empty; } @@ -666,7 +670,11 @@ static int StringIndexOf(string s, string search, int fromIndex) if (endOfLastMatch < thisString.Length) { +#if NETFRAMEWORK result.Append(thisString.Substring(endOfLastMatch)); +#else + result.Append(thisString[endOfLastMatch..]); +#endif } return result.ToString(); @@ -868,6 +876,7 @@ private JsValue CodePointAt(JsValue thisObject, JsValue[] arguments) return CodePointAt(s, position).CodePoint; } + [StructLayout(LayoutKind.Auto)] private readonly record struct CodePointResult(int CodePoint, int CodeUnitCount, bool IsUnpairedSurrogate); private static CodePointResult CodePointAt(string s, int position) @@ -1168,7 +1177,8 @@ private JsValue ToWellFormed(JsValue thisObject, JsValue[] arguments) var cp = CodePointAt(s, k); if (cp.IsUnpairedSurrogate) { - result.Append("\uFFFD"); + // \uFFFD + result.Append('�'); } else { diff --git a/Jint/Native/Symbol/SymbolConstructor.cs b/Jint/Native/Symbol/SymbolConstructor.cs index 0252db8164..44dc27d33e 100644 --- a/Jint/Native/Symbol/SymbolConstructor.cs +++ b/Jint/Native/Symbol/SymbolConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/Symbol/SymbolPrototype.cs b/Jint/Native/Symbol/SymbolPrototype.cs index 7c29ecd05d..97e3522102 100644 --- a/Jint/Native/Symbol/SymbolPrototype.cs +++ b/Jint/Native/Symbol/SymbolPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Runtime; diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs index 9b019a099c..7695d36029 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index 039b84a97d..dff4978054 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Linq; using Jint.Collections; using Jint.Native.Array; diff --git a/Jint/Native/TypedArray/JsTypedArray.cs b/Jint/Native/TypedArray/JsTypedArray.cs index 3991cc84d7..f13fa8ceb7 100644 --- a/Jint/Native/TypedArray/JsTypedArray.cs +++ b/Jint/Native/TypedArray/JsTypedArray.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using Esprima; using Jint.Native.ArrayBuffer; @@ -376,7 +377,7 @@ internal T[] ToNativeArray() { var indexedPosition = i * elementSize + byteOffset; var value = buffer.RawBytesToNumeric(_arrayElementType, indexedPosition, BitConverter.IsLittleEndian); - array[i] = (T) Convert.ChangeType(value, conversionType); + array[i] = (T) Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture); } return array; diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index 7871980228..0d1fb2060c 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Jint.Collections; using Jint.Native.Array; using Jint.Native.ArrayBuffer; @@ -271,7 +272,7 @@ internal static void FillTypedArrayInstance(JsTypedArray target, T[] values) { for (var i = 0; i < values.Length; ++i) { - target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values[i])); + target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values[i], CultureInfo.InvariantCulture)); } } diff --git a/Jint/Native/TypedArray/TypedArrayValue.cs b/Jint/Native/TypedArray/TypedArrayValue.cs index a09c329079..1096007299 100644 --- a/Jint/Native/TypedArray/TypedArrayValue.cs +++ b/Jint/Native/TypedArray/TypedArrayValue.cs @@ -1,4 +1,5 @@ using System.Numerics; +using System.Runtime.InteropServices; using Jint.Runtime; namespace Jint.Native.TypedArray; @@ -6,6 +7,7 @@ namespace Jint.Native.TypedArray; /// /// Container for either double or BigInteger. /// +[StructLayout(LayoutKind.Auto)] internal readonly record struct TypedArrayValue(Types Type, double DoubleValue, BigInteger BigInteger) : IConvertible { public static implicit operator TypedArrayValue(double value) diff --git a/Jint/Native/WeakMap/WeakMapPrototype.cs b/Jint/Native/WeakMap/WeakMapPrototype.cs index 528328567f..822854c5b0 100644 --- a/Jint/Native/WeakMap/WeakMapPrototype.cs +++ b/Jint/Native/WeakMap/WeakMapPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/WeakSet/WeakSetPrototype.cs b/Jint/Native/WeakSet/WeakSetPrototype.cs index 43e8ea2c02..15e8e038bb 100644 --- a/Jint/Native/WeakSet/WeakSetPrototype.cs +++ b/Jint/Native/WeakSet/WeakSetPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Options.Extensions.cs b/Jint/Options.Extensions.cs index 6c682fb1a2..c103dac322 100644 --- a/Jint/Options.Extensions.cs +++ b/Jint/Options.Extensions.cs @@ -75,6 +75,7 @@ public static Options AddObjectConverter(this Options options, IObjectConverter /// /// Sets maximum allowed depth of recursion. /// + /// Options to modify /// /// The allowed depth. /// a) In case max depth is zero no recursion is allowed. @@ -140,6 +141,7 @@ public static Options SetTypeConverter(this Options options, Func + /// Options to modify /// /// The delegate to invoke for each CLR member. If the delegate /// returns null, the standard evaluation is performed. @@ -246,6 +248,7 @@ public static Options SetTypeResolver(this Options options, TypeResolver resolve /// Registers some custom logic to apply on an instance when the options /// are loaded. /// + /// Options to modify /// The action to register. public static Options Configure(this Options options, Action configuration) { diff --git a/Jint/Options.cs b/Jint/Options.cs index 4224613a3a..7c5a119e9d 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -21,8 +21,10 @@ namespace Jint public class Options { - private ITimeSystem? _timeSystem; + private static readonly CultureInfo _defaultCulture = CultureInfo.CurrentCulture; + private static readonly TimeZoneInfo _defaultTimeZone = TimeZoneInfo.Local; + private ITimeSystem? _timeSystem; internal List> _configurations { get; } = new(); /// @@ -58,7 +60,7 @@ public class Options /// /// The culture the engine runs on, defaults to current culture. /// - public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture; + public CultureInfo Culture { get; set; } = _defaultCulture; /// @@ -73,7 +75,7 @@ public ITimeSystem TimeSystem /// /// The time zone the engine runs on, defaults to local. Same as setting DefaultTimeSystem with the time zone. /// - public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Local; + public TimeZoneInfo TimeZone { get; set; } = _defaultTimeZone; /// /// Reference resolver allows customizing behavior for reference resolving. This can be useful in cases where @@ -106,7 +108,7 @@ internal void Apply(Engine engine) { foreach (var configuration in _configurations) { - configuration?.Invoke(engine); + configuration(engine); } // add missing bits if needed @@ -145,9 +147,6 @@ internal void Apply(Engine engine) } engine.ModuleLoader = Modules.ModuleLoader; - - // ensure defaults - engine.ClrTypeConverter ??= new DefaultTypeConverter(engine); } private static void AttachExtensionMethodsToPrototypes(Engine engine) @@ -168,12 +167,18 @@ private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInsta return; } - foreach (var overloads in methods.GroupBy(x => x.Name)) + foreach (var overloads in methods.GroupBy(x => x.Name, StringComparer.Ordinal)) { - string name = overloads.Key; PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function) { - var instance = new MethodInfoFunctionInstance(engine, objectType, name, MethodDescriptor.Build(overloads.ToList()), function); + var instance = new MethodInfoFunctionInstance( + engine, + objectType, + target: null, + overloads.Key, + methods: MethodDescriptor.Build(overloads.ToList()), + function); + return new PropertyDescriptor(instance, PropertyFlag.AllForbidden); } @@ -196,7 +201,7 @@ PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? f // make sure we register both lower case and upper case if (char.IsUpper(overloads.Key[0])) { - key = char.ToLower(overloads.Key[0]) + overloads.Key.Substring(1); + key = char.ToLower(overloads.Key[0], CultureInfo.InvariantCulture) + overloads.Key.Substring(1); if (prototype.HasOwnProperty(key) && prototype.GetOwnProperty(key).Value is ClrFunctionInstance lowerclrFunctionInstance) @@ -276,7 +281,7 @@ public class InteropOptions /// memory usage to grow when targeting large set and freeing of memory can be delayed due to ConditionalWeakTable semantics. /// Defaults to false. /// - public bool TrackObjectWrapperIdentity { get; set; } = false; + public bool TrackObjectWrapperIdentity { get; set; } /// /// If no known type could be guessed, objects are by default wrapped as an @@ -296,7 +301,7 @@ public class InteropOptions /// to the CLR host and interrupt the script execution. If handler returns true these exceptions are converted /// to JS errors that can be caught by the script. /// - public ExceptionHandlerDelegate ExceptionHandler { get; set; } = static exception => false; + public ExceptionHandlerDelegate ExceptionHandler { get; set; } = _defaultExceptionHandler; /// /// Assemblies to allow scripts to call CLR types directly like System.IO.File. @@ -329,6 +334,8 @@ public class InteropOptions /// public Func CreateTypeReferenceObject = (_, _, _) => null; + internal static readonly ExceptionHandlerDelegate _defaultExceptionHandler = static exception => false; + /// /// When not null, is used to serialize any CLR object in an /// passing through 'JSON.stringify'. @@ -393,7 +400,7 @@ public class ConstraintOptions /// /// Chrome and V8 based engines (ClearScript) that can handle 13955. /// When set to a different value except -1, it can reduce slight performance/stack trace readability drawback. (after hitting the engine's own limit), - /// When max stack size to be exceeded, Engine throws an exception . + /// When max stack size to be exceeded, Engine throws an exception . /// public int MaxExecutionStackCount { get; set; } = StackGuard.Disabled; diff --git a/Jint/Pooling/ArgumentsInstancePool.cs b/Jint/Pooling/ArgumentsInstancePool.cs index 2f7e98fc35..2562b4ed4e 100644 --- a/Jint/Pooling/ArgumentsInstancePool.cs +++ b/Jint/Pooling/ArgumentsInstancePool.cs @@ -49,7 +49,7 @@ public void Return(ArgumentsInstance instance) { return; } - _pool.Free(instance);; + _pool.Free(instance); } } } diff --git a/Jint/Pooling/ConcurrentObjectPool.cs b/Jint/Pooling/ConcurrentObjectPool.cs index 596579c364..72582b9a67 100644 --- a/Jint/Pooling/ConcurrentObjectPool.cs +++ b/Jint/Pooling/ConcurrentObjectPool.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1822 + #nullable disable // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. diff --git a/Jint/Pooling/ObjectPool.cs b/Jint/Pooling/ObjectPool.cs index 7eb41caeea..050c43b63e 100644 --- a/Jint/Pooling/ObjectPool.cs +++ b/Jint/Pooling/ObjectPool.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1822 + #nullable disable // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. diff --git a/Jint/Pooling/ReferencePool.cs b/Jint/Pooling/ReferencePool.cs index 7d6420bd93..24e10fdd77 100644 --- a/Jint/Pooling/ReferencePool.cs +++ b/Jint/Pooling/ReferencePool.cs @@ -32,7 +32,7 @@ public void Return(Reference? reference) { return; } - _pool.Free(reference);; + _pool.Free(reference); } } } diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index eb19b289cc..d6a234791d 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -62,7 +62,11 @@ public int Push(FunctionInstance functionInstance, JintExpression? expression, i _stack.Push(item); if (_statistics is not null) { +#pragma warning disable CA1854 +#pragma warning disable CA1864 if (_statistics.ContainsKey(item)) +#pragma warning restore CA1854 +#pragma warning restore CA1864 { return ++_statistics[item]; } @@ -126,7 +130,7 @@ static void AppendLocation( if (!string.IsNullOrWhiteSpace(shortDescription)) { sb - .Append(" ") + .Append(' ') .Append(shortDescription); } @@ -144,15 +148,15 @@ static void AppendLocation( var arg = element.Value.Arguments.Value[index]; sb.Append(GetPropertyKey(arg)); } - sb.Append(")"); + sb.Append(')'); } sb - .Append(" ") + .Append(' ') .Append(loc.Source) - .Append(":") + .Append(':') .Append(loc.End.Line) - .Append(":") + .Append(':') .Append(loc.Start.Column + 1) // report column number instead of index .AppendLine(); } diff --git a/Jint/Runtime/CallStack/StackGuard.cs b/Jint/Runtime/CallStack/StackGuard.cs index 2ddf5946a7..e815e02e32 100644 --- a/Jint/Runtime/CallStack/StackGuard.cs +++ b/Jint/Runtime/CallStack/StackGuard.cs @@ -50,7 +50,7 @@ public bool TryEnterOnCurrentStack() return false; } - public TR RunOnEmptyStack(Func action, T1 arg1) + public static TR RunOnEmptyStack(Func action, T1 arg1) { #if NETFRAMEWORK || NETSTANDARD2_0 return RunOnEmptyStackCore(static s => @@ -69,7 +69,7 @@ public TR RunOnEmptyStack(Func action, T1 arg1) } - private R RunOnEmptyStackCore(Func action, object state) + private static R RunOnEmptyStackCore(Func action, object state) { // Using default scheduler rather than picking up the current scheduler. Task task = Task.Factory.StartNew((Func) action, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); diff --git a/Jint/Runtime/CommonProperties.cs b/Jint/Runtime/CommonProperties.cs index 6605e206cb..ee2e6bcd08 100644 --- a/Jint/Runtime/CommonProperties.cs +++ b/Jint/Runtime/CommonProperties.cs @@ -4,26 +4,26 @@ namespace Jint.Runtime { internal static class CommonProperties { - internal static readonly JsString Arguments = new JsString("arguments"); - internal static readonly JsString Caller = new JsString("caller"); - internal static readonly JsString Callee = new JsString("callee"); - internal static readonly JsString Constructor = new JsString("constructor"); - internal static readonly JsString Eval = new JsString("eval"); - internal static readonly JsString Infinity = new JsString("Infinity"); - internal static readonly JsString Length = new JsString("length"); - internal static readonly JsString Name = new JsString("name"); - internal static readonly JsString Prototype = new JsString("prototype"); - internal static readonly JsString Size = new JsString("size"); - internal static readonly JsString Next = new JsString("next"); - internal static readonly JsString Done = new JsString("done"); - internal static readonly JsString Value = new JsString("value"); - internal static readonly JsString Return = new JsString("return"); - internal static readonly JsString Set = new JsString("set"); - internal static readonly JsString Get = new JsString("get"); - internal static readonly JsString Writable = new JsString("writable"); - internal static readonly JsString Enumerable = new JsString("enumerable"); - internal static readonly JsString Configurable = new JsString("configurable"); - internal static readonly JsString Stack = new JsString("stack"); - internal static readonly JsString Message = new JsString("message"); + internal static readonly JsString Arguments = JsString.CachedCreate("arguments"); + internal static readonly JsString Caller = JsString.CachedCreate("caller"); + internal static readonly JsString Callee = JsString.CachedCreate("callee"); + internal static readonly JsString Constructor = JsString.CachedCreate("constructor"); + internal static readonly JsString Eval = JsString.CachedCreate("eval"); + internal static readonly JsString Infinity = JsString.CachedCreate("Infinity"); + internal static readonly JsString Length = JsString.CachedCreate("length"); + internal static readonly JsString Name = JsString.CachedCreate("name"); + internal static readonly JsString Prototype = JsString.CachedCreate("prototype"); + internal static readonly JsString Size = JsString.CachedCreate("size"); + internal static readonly JsString Next = JsString.CachedCreate("next"); + internal static readonly JsString Done = JsString.CachedCreate("done"); + internal static readonly JsString Value = JsString.CachedCreate("value"); + internal static readonly JsString Return = JsString.CachedCreate("return"); + internal static readonly JsString Set = JsString.CachedCreate("set"); + internal static readonly JsString Get = JsString.CachedCreate("get"); + internal static readonly JsString Writable = JsString.CachedCreate("writable"); + internal static readonly JsString Enumerable = JsString.CachedCreate("enumerable"); + internal static readonly JsString Configurable = JsString.CachedCreate("configurable"); + internal static readonly JsString Stack = JsString.CachedCreate("stack"); + internal static readonly JsString Message = JsString.CachedCreate("message"); } } diff --git a/Jint/Runtime/Debugger/BreakPointCollection.cs b/Jint/Runtime/Debugger/BreakPointCollection.cs index 7407b63e5f..4a660e0629 100644 --- a/Jint/Runtime/Debugger/BreakPointCollection.cs +++ b/Jint/Runtime/Debugger/BreakPointCollection.cs @@ -25,8 +25,6 @@ public BreakPointCollection() public int Count => _breakPoints.Count; - public bool IsReadOnly => false; - /// /// Sets a new breakpoint. Note that this will replace any breakpoint at the same location (source/column/line). /// diff --git a/Jint/Runtime/Debugger/DebuggerStatementHandling.cs b/Jint/Runtime/Debugger/DebuggerStatementHandling.cs index ff756eb121..33452f7c2e 100644 --- a/Jint/Runtime/Debugger/DebuggerStatementHandling.cs +++ b/Jint/Runtime/Debugger/DebuggerStatementHandling.cs @@ -16,7 +16,7 @@ public enum DebuggerStatementHandling Clr, /// - /// debugger statements will trigger a break in Jint's DebugHandler. See . + /// debugger statements will trigger a break in Jint's DebugHandler. See . /// Script } diff --git a/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs b/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs index c7e7e9ade3..8f3544a831 100644 --- a/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs +++ b/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs @@ -24,7 +24,7 @@ public bool Equals(BreakLocation? x, BreakLocation? y) return x.Line == y.Line && x.Column == y.Column && - (x.Source == null || y.Source == null || x.Source == y.Source); + (x.Source == null || y.Source == null || string.Equals(x.Source, y.Source, StringComparison.Ordinal)); } public int GetHashCode(BreakLocation? obj) diff --git a/Jint/Runtime/DefaultTimeSystem.cs b/Jint/Runtime/DefaultTimeSystem.cs index 9a287d34d3..72f348abe2 100644 --- a/Jint/Runtime/DefaultTimeSystem.cs +++ b/Jint/Runtime/DefaultTimeSystem.cs @@ -57,6 +57,11 @@ public virtual bool TryParse(string date, out long epochMilliseconds) { epochMilliseconds = long.MinValue; + if (string.IsNullOrEmpty(date)) + { + return false; + } + // special check for large years that always require + or - in front and have 6 digit year if ((date[0] == '+'|| date[0] == '-') && date.IndexOf('-', 1) == 7) { @@ -84,7 +89,9 @@ public virtual bool TryParse(string date, out long epochMilliseconds) if (DateUtils.TryParse(date, out var mimeKitResult)) { var dateAsUtc = mimeKitResult.ToUniversalTime(); +#pragma warning disable MA0132 epochMilliseconds = (long) Math.Floor((dateAsUtc - DateConstructor.Epoch).TotalMilliseconds); +#pragma warning restore MA0132 return true; } @@ -120,7 +127,7 @@ private static bool TryParseLargeYear(string date, out long epochMilliseconds) epochMilliseconds = long.MinValue; var yearString = date.Substring(0, 7); - if (!int.TryParse(yearString, out var year)) + if (!int.TryParse(yearString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { return false; } @@ -132,7 +139,9 @@ private static bool TryParseLargeYear(string date, out long epochMilliseconds) } // create replacement string +#pragma warning disable CA1845 var dateToParse = "2000" + date.Substring(7); +#pragma warning restore CA1845 if (!DateTime.TryParse(dateToParse, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed)) { return false; diff --git a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs index 8a3b51ea07..76a576e4e1 100644 --- a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs @@ -10,6 +10,7 @@ public sealed class GetSetPropertyDescriptor : PropertyDescriptor public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = null, bool? configurable = null) : base(null, writable: null, enumerable: enumerable, configurable: configurable) { + _flags |= PropertyFlag.NonData; _get = get; _set = set; } @@ -17,12 +18,18 @@ public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = n internal GetSetPropertyDescriptor(JsValue? get, JsValue? set, PropertyFlag flags) : base(null, flags) { + _flags |= PropertyFlag.NonData; + _flags &= ~PropertyFlag.WritableSet; + _flags &= ~PropertyFlag.Writable; _get = get; _set = set; } public GetSetPropertyDescriptor(PropertyDescriptor descriptor) : base(descriptor) { + _flags |= PropertyFlag.NonData; + _flags &= ~PropertyFlag.WritableSet; + _flags &= ~PropertyFlag.Writable; _get = descriptor.Get; _set = descriptor.Set; } @@ -45,8 +52,10 @@ internal sealed class ThrowerPropertyDescriptor : PropertyDescriptor private readonly Engine _engine; private JsValue? _thrower; - public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags) : base(flags) + public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags) + : base(flags | PropertyFlag.CustomJsValue) { + _flags |= PropertyFlag.NonData; _engine = engine; } diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index 9f32fa7030..3b3f290dd9 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -18,25 +18,32 @@ public PropertyDescriptor() : this(PropertyFlag.None) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected PropertyDescriptor(PropertyFlag flags) { - _flags = flags; + _flags = flags & ~PropertyFlag.NonData; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal PropertyDescriptor(JsValue? value, PropertyFlag flags) : this(flags) { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { +#pragma warning disable MA0056 CustomValue = value; +#pragma warning restore MA0056 } _value = value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PropertyDescriptor(JsValue? value, bool? writable, bool? enumerable, bool? configurable) { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { +#pragma warning disable MA0056 CustomValue = value; +#pragma warning restore MA0056 } _value = value; @@ -79,7 +86,7 @@ public PropertyDescriptor(PropertyDescriptor descriptor) public bool Enumerable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Enumerable) != 0; + get => (_flags & PropertyFlag.Enumerable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -98,7 +105,7 @@ public bool Enumerable public bool EnumerableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.EnumerableSet | PropertyFlag.Enumerable)) != 0; + get => (_flags & (PropertyFlag.EnumerableSet | PropertyFlag.Enumerable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -116,7 +123,7 @@ private set public bool Writable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Writable) != 0; + get => (_flags & PropertyFlag.Writable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -135,7 +142,7 @@ public bool Writable public bool WritableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0; + get => (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -153,7 +160,7 @@ private set public bool Configurable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Configurable) != 0; + get => (_flags & PropertyFlag.Configurable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -172,7 +179,7 @@ public bool Configurable public bool ConfigurableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.ConfigurableSet | PropertyFlag.Configurable)) != 0; + get => (_flags & (PropertyFlag.ConfigurableSet | PropertyFlag.Configurable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -192,7 +199,7 @@ public JsValue Value [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { return CustomValue!; } @@ -202,7 +209,7 @@ public JsValue Value [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { CustomValue = value; } @@ -354,7 +361,7 @@ public static JsValue FromPropertyDescriptor(Engine engine, PropertyDescriptor d if (desc.IsDataDescriptor()) { - properties["value"] = new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable); + properties["value"] = new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable); if (desc._flags != PropertyFlag.None || desc.WritableSet) { properties["writable"] = new PropertyDescriptor(desc.Writable, PropertyFlag.ConfigurableEnumerableWritable); @@ -389,8 +396,12 @@ public bool IsAccessorDescriptor() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsDataDescriptor() { - return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 - || (_flags & PropertyFlag.CustomJsValue) != 0 && !ReferenceEquals(CustomValue, null) + if (_flags.HasFlag(PropertyFlag.NonData)) + { + return false; + } + return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None + || (_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None && !ReferenceEquals(CustomValue, null) || !ReferenceEquals(_value, null); } @@ -410,9 +421,9 @@ internal bool TryGetValue(ObjectInstance thisArg, out JsValue value) value = JsValue.Undefined; // IsDataDescriptor logic inlined - if ((_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0) + if ((_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None) { - var val = (_flags & PropertyFlag.CustomJsValue) != 0 + var val = (_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None ? CustomValue : _value; diff --git a/Jint/Runtime/Descriptors/PropertyFlag.cs b/Jint/Runtime/Descriptors/PropertyFlag.cs index ac1a6231e3..926d787ec0 100644 --- a/Jint/Runtime/Descriptors/PropertyFlag.cs +++ b/Jint/Runtime/Descriptors/PropertyFlag.cs @@ -16,6 +16,9 @@ public enum PropertyFlag // we can check for mutable binding and do some fast assignments MutableBinding = 512, + // mark PropertyDescriptor as non data to accelerate IsDataDescriptor and avoid the side effect of CustomValue + NonData = 1024, + // common helpers AllForbidden = ConfigurableSet | EnumerableSet | WritableSet, ConfigurableEnumerableWritable = Configurable | Enumerable | Writable, diff --git a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs index 3dee3f8192..85f41a0340 100644 --- a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs @@ -19,6 +19,7 @@ public ClrAccessDescriptor( string name) : base(value: null, PropertyFlag.Configurable) { + _flags |= PropertyFlag.NonData; _env = env; _engine = engine; _name = new EnvironmentRecord.BindingName(name); diff --git a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs index 7c2eaec1df..e427546bad 100644 --- a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Native; namespace Jint.Runtime.Descriptors.Specialized @@ -7,9 +8,11 @@ internal sealed class LazyPropertyDescriptor : PropertyDescriptor private readonly object? _state; private readonly Func _resolver; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal LazyPropertyDescriptor(object? state, Func resolver, PropertyFlag flags) : base(null, flags | PropertyFlag.CustomJsValue) { + _flags &= ~PropertyFlag.NonData; _state = state; _resolver = resolver; } diff --git a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs index 4885b4f52f..bec4af0ce7 100644 --- a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs @@ -1,5 +1,6 @@ using System.Reflection; using Jint.Native; +using Jint.Runtime.Interop; using Jint.Runtime.Interop.Reflection; namespace Jint.Runtime.Descriptors.Specialized @@ -17,31 +18,47 @@ public ReflectionDescriptor( bool enumerable) : base((enumerable ? PropertyFlag.Enumerable : PropertyFlag.None) | PropertyFlag.CustomJsValue) { + _flags |= PropertyFlag.NonData; _engine = engine; _reflectionAccessor = reflectionAccessor; _target = target; - Writable = reflectionAccessor.Writable && engine.Options.Interop.AllowWrite; + + if (reflectionAccessor.Writable && engine.Options.Interop.AllowWrite) + { + Set = new SetterFunctionInstance(_engine, DoSet); + } + if (reflectionAccessor.Readable) + { + Get = new GetterFunctionInstance(_engine, DoGet); + } } + public override JsValue? Get { get; } + public override JsValue? Set { get; } + protected internal override JsValue? CustomValue { - get + get => DoGet(null); + set => DoSet(null, value); + } + + private JsValue DoGet(JsValue? thisObj) + { + var value = _reflectionAccessor.GetValue(_engine, _target); + var type = _reflectionAccessor.MemberType; + return JsValue.FromObjectWithType(_engine, value, type); + } + + private void DoSet(JsValue? thisObj, JsValue? v) + { + try { - var value = _reflectionAccessor.GetValue(_engine, _target); - var type = _reflectionAccessor.MemberType; - return JsValue.FromObjectWithType(_engine, value, type); + _reflectionAccessor.SetValue(_engine, _target, v!); } - set + catch (TargetInvocationException exception) { - try - { - _reflectionAccessor.SetValue(_engine, _target, value!); - } - catch (TargetInvocationException exception) - { - ExceptionHelper.ThrowMeaningfulException(_engine, exception); - } + ExceptionHelper.ThrowMeaningfulException(_engine, exception); } } } diff --git a/Jint/Runtime/Environments/EnvironmentRecord.cs b/Jint/Runtime/Environments/EnvironmentRecord.cs index 143a5ebaf4..d4db2b8f93 100644 --- a/Jint/Runtime/Environments/EnvironmentRecord.cs +++ b/Jint/Runtime/Environments/EnvironmentRecord.cs @@ -8,6 +8,7 @@ namespace Jint.Runtime.Environments /// Base implementation of an Environment Record /// https://tc39.es/ecma262/#sec-environment-records /// + [DebuggerTypeProxy(typeof(EnvironmentRecordDebugView))] public abstract class EnvironmentRecord : JsValue { protected internal readonly Engine _engine; @@ -117,7 +118,6 @@ internal sealed class BindingName { public readonly Key Key; public readonly JsString Value; - public readonly bool HasEvalOrArguments; public readonly JsValue? CalculatedValue; public BindingName(string value) @@ -125,12 +125,48 @@ public BindingName(string value) var key = (Key) value; Key = key; Value = JsString.Create(value); - HasEvalOrArguments = key == KnownKeys.Eval || key == KnownKeys.Arguments; if (key == KnownKeys.Undefined) { CalculatedValue = Undefined; } } + + public BindingName(JsString value) + { + var key = (Key) value.ToString(); + Key = key; + Value = value; + if (key == KnownKeys.Undefined) + { + CalculatedValue = Undefined; + } + } + } + + private sealed class EnvironmentRecordDebugView + { + private readonly EnvironmentRecord _record; + + public EnvironmentRecordDebugView(EnvironmentRecord record) + { + _record = record; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Entries + { + get + { + var bindingNames = _record.GetAllBindingNames(); + var bindings = new KeyValuePair[bindingNames.Length]; + var i = 0; + foreach (var key in bindingNames) + { + bindings[i++] = new KeyValuePair(key, _record.GetBindingValue(key, false)); + } + return bindings; + } + } } } } diff --git a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs index ed5ade0f2f..1423e4474f 100644 --- a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs @@ -221,7 +221,7 @@ private void HandleObjectPattern(EvaluationContext context, bool initiallyEmpty, { if (((RestElement) property).Argument is Identifier restIdentifier) { - var rest = _engine.Realm.Intrinsics.Object.Construct(argumentObject.Properties!.Count - processedProperties!.Count); + var rest = _engine.Realm.Intrinsics.Object.Construct((argumentObject.Properties?.Count ?? 0) - processedProperties!.Count); argumentObject.CopyDataProperties(rest, processedProperties); SetItemSafely(restIdentifier.Name, rest, initiallyEmpty); } @@ -320,7 +320,7 @@ private void HandleAssignmentPatternOrExpression( var idLeft = left as Identifier; if (idLeft != null && right is Identifier idRight - && idLeft.Name == idRight.Name) + && string.Equals(idLeft.Name, idRight.Name, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceNameError(_functionObject._realm, idRight.Name); } diff --git a/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs b/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs index 1a0ae5f574..295811bb9a 100644 --- a/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs @@ -12,20 +12,30 @@ namespace Jint.Runtime.Environments /// public sealed class GlobalEnvironmentRecord : EnvironmentRecord { + /// + /// A sealed class for global usage. + /// + internal sealed class GlobalDeclarativeEnvironmentRecord : DeclarativeEnvironmentRecord + { + public GlobalDeclarativeEnvironmentRecord(Engine engine) : base(engine) + { + } + } + internal readonly ObjectInstance _global; // we expect it to be GlobalObject, but need to allow to something host-defined, like Window private readonly GlobalObject? _globalObject; // Environment records are needed by debugger - internal readonly DeclarativeEnvironmentRecord _declarativeRecord; - private readonly HashSet _varNames = new(); + internal readonly GlobalDeclarativeEnvironmentRecord _declarativeRecord; + private readonly HashSet _varNames = new(StringComparer.Ordinal); public GlobalEnvironmentRecord(Engine engine, ObjectInstance global) : base(engine) { _global = global; _globalObject = global as GlobalObject; - _declarativeRecord = new DeclarativeEnvironmentRecord(engine); + _declarativeRecord = new GlobalDeclarativeEnvironmentRecord(engine); } public ObjectInstance GlobalThisValue => _global; @@ -66,7 +76,7 @@ internal override bool TryGetBinding( out Binding binding, [NotNullWhen(true)] out JsValue? value) { - if (_declarativeRecord.TryGetBinding(name, strict, out binding, out value)) + if (_declarativeRecord._dictionary is not null && _declarativeRecord.TryGetBinding(name, strict, out binding, out value)) { return true; } @@ -343,14 +353,31 @@ public bool CanDeclareGlobalFunction(string name) public void CreateGlobalVarBinding(string name, bool canBeDeleted) { Key key = name; - if (!_global._properties!.ContainsKey(key) && _global.Extensible) - { - _global._properties[key] = new PropertyDescriptor(Undefined, canBeDeleted + if (_global.Extensible && _global._properties!.TryAdd(key, new PropertyDescriptor(Undefined, canBeDeleted ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding - : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding); + : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding))) + { + _varNames.Add(name); + } + } + + internal void CreateGlobalVarBindings(List names, bool canBeDeleted) + { + if (!_global.Extensible) + { + return; } - _varNames.Add(name); + for (var i = 0; i < names.Count; i++) + { + var name = names[i]; + + _global._properties!.TryAdd(name,new PropertyDescriptor(Undefined, canBeDeleted + ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding + : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding)); + + _varNames.Add(name); + } } /// diff --git a/Jint/Runtime/Environments/JintEnvironment.cs b/Jint/Runtime/Environments/JintEnvironment.cs index d007a85abc..b8cb54afe4 100644 --- a/Jint/Runtime/Environments/JintEnvironment.cs +++ b/Jint/Runtime/Environments/JintEnvironment.cs @@ -44,7 +44,7 @@ record = env; if (env._outerEnv is null) { - return env.TryGetBinding(name, strict, out _, out value); + return ((GlobalEnvironmentRecord) env).TryGetBinding(name, strict, out _, out value); } while (!ReferenceEquals(record, null)) diff --git a/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs b/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs index 3afb57a1ee..7c782f8d48 100644 --- a/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs @@ -23,7 +23,7 @@ public PrivateEnvironmentRecord(PrivateEnvironmentRecord? outerPrivEnv) { foreach (var pn in Names) { - if (pn.Value.Description == identifier) + if (string.Equals(pn.Value.Description, identifier, StringComparison.Ordinal)) { return pn.Value; } diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index 14eaee8130..ea71268655 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -11,6 +11,11 @@ namespace Jint.Runtime { + /// + /// Wraps known runtime type error information. + /// + internal sealed record ErrorDispatchInfo(ErrorConstructor ErrorConstructor, string? Message = null); + internal static class ExceptionHelper { [DoesNotReturn] @@ -77,11 +82,9 @@ public static void ThrowRangeError(Realm realm, string? message = null) throw new JavaScriptException(realm.Intrinsics.RangeError, message).SetJavaScriptLocation(location); } - [DoesNotReturn] - public static void ThrowUriError(Realm realm) + public static ErrorDispatchInfo CreateUriError(Realm realm, string message) { - var location = realm.GlobalObject.Engine.GetLastSyntaxElement()?.Location ?? default; - throw new JavaScriptException(realm.Intrinsics.UriError).SetJavaScriptLocation(location); + return new ErrorDispatchInfo(realm.Intrinsics.UriError, message); } [DoesNotReturn] @@ -111,7 +114,9 @@ public static void ThrowStatementsCountOverflowException() [DoesNotReturn] public static void ThrowArgumentOutOfRangeException() { +#pragma warning disable MA0015 throw new ArgumentOutOfRangeException(); +#pragma warning restore MA0015 } [DoesNotReturn] @@ -132,12 +137,6 @@ public static void ThrowPromiseRejectedException(JsValue error) throw new PromiseRejectedException(error); } - [DoesNotReturn] - public static void ThrowJavaScriptException(JsValue value) - { - throw new JavaScriptException(value); - } - [DoesNotReturn] public static void ThrowJavaScriptException(Engine engine, JsValue value, in Completion result) { diff --git a/Jint/Runtime/Host.cs b/Jint/Runtime/Host.cs index 2c28a64a8f..0f2881eef9 100644 --- a/Jint/Runtime/Host.cs +++ b/Jint/Runtime/Host.cs @@ -211,6 +211,8 @@ internal void HostEnqueuePromiseJob(Action job, Realm realm) Engine.AddToEventLoop(job); } } + + internal sealed record JobCallback(ICallable Callback, object? HostDefined); } -internal sealed record JobCallback(ICallable Callback, object? HostDefined); + diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.cs b/Jint/Runtime/Interop/ClrFunctionInstance.cs index d777dc5a05..f36efceff6 100644 --- a/Jint/Runtime/Interop/ClrFunctionInstance.cs +++ b/Jint/Runtime/Interop/ClrFunctionInstance.cs @@ -1,78 +1,85 @@ +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using Jint.Native; using Jint.Native.Function; using Jint.Runtime.Descriptors; -namespace Jint.Runtime.Interop +namespace Jint.Runtime.Interop; + +/// +/// Wraps a Clr method into a FunctionInstance +/// +public sealed class ClrFunctionInstance : FunctionInstance, IEquatable { - /// - /// Wraps a Clr method into a FunctionInstance - /// - public sealed class ClrFunctionInstance : FunctionInstance, IEquatable + internal readonly Func _func; + private readonly bool _bubbleExceptions; + + public ClrFunctionInstance( + Engine engine, + string name, + Func func, + int length = 0, + PropertyFlag lengthFlags = PropertyFlag.AllForbidden) + : base(engine, engine.Realm, new JsString(name)) { - internal readonly Func _func; - - public ClrFunctionInstance( - Engine engine, - string name, - Func func, - int length = 0, - PropertyFlag lengthFlags = PropertyFlag.AllForbidden) - : base(engine, engine.Realm, new JsString(name)) - { - _func = func; + _func = func; - _prototype = engine._originalIntrinsics.Function.PrototypeObject; + _prototype = engine._originalIntrinsics.Function.PrototypeObject; - _length = lengthFlags == PropertyFlag.AllForbidden - ? PropertyDescriptor.AllForbiddenDescriptor.ForNumber(length) - : new PropertyDescriptor(JsNumber.Create(length), lengthFlags); - } + _length = lengthFlags == PropertyFlag.AllForbidden + ? PropertyDescriptor.AllForbiddenDescriptor.ForNumber(length) + : new PropertyDescriptor(JsNumber.Create(length), lengthFlags); - protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) + _bubbleExceptions = _engine.Options.Interop.ExceptionHandler == InteropOptions._defaultExceptionHandler; + } + + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) => _bubbleExceptions ? _func(thisObject, arguments) : CallSlow(thisObject, arguments); + + [MethodImpl(MethodImplOptions.NoInlining)] + private JsValue CallSlow(JsValue thisObject, JsValue[] arguments) + { + try { - try + return _func(thisObject, arguments); + } + catch (Exception e) when (e is not JavaScriptException) + { + if (_engine.Options.Interop.ExceptionHandler(e)) { - return _func(thisObject, arguments); + ExceptionHelper.ThrowJavaScriptException(_realm.Intrinsics.Error, e.Message); } - catch (Exception e) when (e is not JavaScriptException) + else { - if (_engine.Options.Interop.ExceptionHandler(e)) - { - ExceptionHelper.ThrowJavaScriptException(_realm.Intrinsics.Error, e.Message); - } - else - { - ExceptionDispatchInfo.Capture(e).Throw(); - } - - return Undefined; + ExceptionDispatchInfo.Capture(e).Throw(); } + + return Undefined; } + } - public override bool Equals(JsValue? obj) + public override bool Equals(JsValue? other) => Equals(other as ClrFunctionInstance); + + public override bool Equals(object? obj) => Equals(obj as ClrFunctionInstance); + + public bool Equals(ClrFunctionInstance? other) + { + if (ReferenceEquals(null, other)) { - return Equals(obj as ClrFunctionInstance); + return false; } - public bool Equals(ClrFunctionInstance? other) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (_func == other._func) - { - return true; - } + return true; + } - return false; + if (_func == other._func) + { + return true; } + + return false; } + + public override int GetHashCode() => _func.GetHashCode(); } diff --git a/Jint/Runtime/Interop/ClrHelper.cs b/Jint/Runtime/Interop/ClrHelper.cs index e11763216c..f59fcc1faa 100644 --- a/Jint/Runtime/Interop/ClrHelper.cs +++ b/Jint/Runtime/Interop/ClrHelper.cs @@ -14,7 +14,9 @@ internal ClrHelper(InteropOptions interopOptions) /// /// Call JsValue.ToString(), mainly for NamespaceReference. /// +#pragma warning disable CA1822 public JsValue ToString(JsValue value) +#pragma warning restore CA1822 { return value.ToString(); } @@ -22,7 +24,9 @@ public JsValue ToString(JsValue value) /// /// Cast `obj as ISomeInterface` to `obj` /// +#pragma warning disable CA1822 public JsValue Unwrap(ObjectWrapper obj) +#pragma warning restore CA1822 { return new ObjectWrapper(obj.Engine, obj.Target); } @@ -30,7 +34,9 @@ public JsValue Unwrap(ObjectWrapper obj) /// /// Cast `obj` to `obj as ISomeInterface` /// +#pragma warning disable CA1822 public JsValue Wrap(ObjectWrapper obj, TypeReference type) +#pragma warning restore CA1822 { if (!type.ReferenceType.IsInstanceOfType(obj.Target)) { @@ -70,7 +76,7 @@ public JsValue ObjectToType(ObjectWrapper obj) } else { - ExceptionHelper.ThrowArgumentException("Must be an ObjectWrapper of Type", "obj"); + ExceptionHelper.ThrowArgumentException("Must be an ObjectWrapper of Type", nameof(obj)); } return JsValue.Undefined; } diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index f9739bbfb2..e7cc6654d4 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Runtime.CompilerServices; using System.Threading; using Jint.Native; @@ -74,10 +75,10 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW var t = value.GetType(); if (!engine.Options.Interop.AllowSystemReflection - && t.Namespace?.StartsWith("System.Reflection") == true) + && t.Namespace?.StartsWith("System.Reflection", StringComparison.Ordinal) == true) { - const string message = "Cannot access System.Reflection namespace, check Engine's interop options"; - ExceptionHelper.ThrowInvalidOperationException(message); + const string Message = "Cannot access System.Reflection namespace, check Engine's interop options"; + ExceptionHelper.ThrowInvalidOperationException(Message); } if (t.IsEnum) @@ -86,17 +87,17 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW if (ut == typeof(ulong)) { - result = JsNumber.Create(Convert.ToDouble(value)); + result = JsNumber.Create(Convert.ToDouble(value, CultureInfo.InvariantCulture)); } else { if (ut == typeof(uint) || ut == typeof(long)) { - result = JsNumber.Create(Convert.ToInt64(value)); + result = JsNumber.Create(Convert.ToInt64(value, CultureInfo.InvariantCulture)); } else { - result = JsNumber.Create(Convert.ToInt32(value)); + result = JsNumber.Create(Convert.ToInt32(value, CultureInfo.InvariantCulture)); } } } @@ -110,6 +111,13 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW else { var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type); + + if (ReferenceEquals(wrapped?.GetPrototypeOf(), engine.Realm.Intrinsics.Object.PrototypeObject) + && engine._typeReferences?.TryGetValue(t, out var typeReference) == true) + { + wrapped.SetPrototypeOf(typeReference); + } + result = wrapped; if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null) @@ -154,7 +162,7 @@ private static bool TryConvertConvertible(Engine engine, IConvertible convertibl return result is not null; } - private static JsValue ConvertArray(Engine e, object v) + private static JsArray ConvertArray(Engine e, object v) { var array = (Array) v; var arrayLength = (uint) array.Length; diff --git a/Jint/Runtime/Interop/DelegateWrapper.cs b/Jint/Runtime/Interop/DelegateWrapper.cs index 4a33e324a0..4a6c1e1f4e 100644 --- a/Jint/Runtime/Interop/DelegateWrapper.cs +++ b/Jint/Runtime/Interop/DelegateWrapper.cs @@ -36,7 +36,7 @@ public DelegateWrapper( } } - protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArguments) + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { var parameterInfos = _d.Method.GetParameters(); @@ -53,7 +53,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen int delegateArgumentsCount = parameterInfos.Length; int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount; - int jsArgumentsCount = jsArguments.Length; + int jsArgumentsCount = arguments.Length; int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount); var clrTypeConverter = Engine.ClrTypeConverter; @@ -64,7 +64,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen for (var i = 0; i < jsArgumentsWithoutParamsCount; i++) { var parameterType = parameterInfos[i].ParameterType; - var value = jsArguments[i]; + var value = arguments[i]; object? converted; if (parameterType == typeof(JsValue)) @@ -107,7 +107,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++) { var paramsIndex = i - paramsArgumentIndex; - var value = jsArguments[i]; + var value = arguments[i]; object? converted; if (paramsParameterType == typeof(JsValue)) @@ -130,13 +130,65 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen try { - return FromObject(Engine, _d.DynamicInvoke(parameters)); + var result = _d.DynamicInvoke(parameters); + if (result is not Task task) + { + return FromObject(Engine, result); + } + return ConvertTaskToPromise(task); } catch (TargetInvocationException exception) { - ExceptionHelper.ThrowMeaningfulException(_engine, exception); + ExceptionHelper.ThrowMeaningfulException(Engine, exception); throw; } } + + private JsValue ConvertTaskToPromise(Task task) + { + var (promise, resolve, reject) = Engine.RegisterPromise(); + task = task.ContinueWith(continuationAction => + { + if (continuationAction.IsFaulted) + { + reject(FromObject(Engine, continuationAction.Exception)); + } + else if (continuationAction.IsCanceled) + { + reject(FromObject(Engine, new ExecutionCanceledException())); + } + else + { + // Special case: Marshal `async Task` as undefined, as this is `Task` at runtime + // See https://github.com/sebastienros/jint/pull/1567#issuecomment-1681987702 + if (Task.CompletedTask.Equals(continuationAction)) + { + resolve(FromObject(Engine, JsValue.Undefined)); + return; + } + + var result = continuationAction.GetType().GetProperty(nameof(Task.Result)); + if (result is not null) + { + resolve(FromObject(Engine, result.GetValue(continuationAction))); + } + else + { + resolve(FromObject(Engine, JsValue.Undefined)); + } + } + }); + + Engine.AddToEventLoop(() => + { + if (!task.IsCompleted) + { + // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this. + ((IAsyncResult) task).AsyncWaitHandle.WaitOne(); + } + }); + + return promise; + } } } diff --git a/Jint/Runtime/Interop/MethodDescriptor.cs b/Jint/Runtime/Interop/MethodDescriptor.cs index 831cbe60ad..21002c2549 100644 --- a/Jint/Runtime/Interop/MethodDescriptor.cs +++ b/Jint/Runtime/Interop/MethodDescriptor.cs @@ -148,7 +148,7 @@ public JsValue Call(Engine engine, object? instance, JsValue[] arguments) } else { - throw new Exception("Method is unknown type"); + throw new NotSupportedException("Method is unknown type"); } } catch (TargetInvocationException exception) diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index 8d7385b2a0..264af018ea 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs @@ -10,6 +10,7 @@ namespace Jint.Runtime.Interop internal sealed class MethodInfoFunctionInstance : FunctionInstance { private readonly Type _targetType; + private readonly object? _target; private readonly string _name; private readonly MethodDescriptor[] _methods; private readonly ClrFunctionInstance? _fallbackClrFunctionInstance; @@ -17,19 +18,21 @@ internal sealed class MethodInfoFunctionInstance : FunctionInstance public MethodInfoFunctionInstance( Engine engine, Type targetType, + object? target, string name, MethodDescriptor[] methods, ClrFunctionInstance? fallbackClrFunctionInstance = null) : base(engine, engine.Realm, new JsString(name)) { _targetType = targetType; + _target = target; _name = name; _methods = methods; _fallbackClrFunctionInstance = fallbackClrFunctionInstance; _prototype = engine.Realm.Intrinsics.Function.PrototypeObject; } - private static bool IsGenericParameter(object argObj, Type parameterType) + private static bool IsGenericParameter(object? argObj, Type parameterType) { if (argObj is null) { @@ -49,7 +52,7 @@ private static bool IsGenericParameter(object argObj, Type parameterType) return false; } - private static void HandleGenericParameter(object argObj, Type parameterType, Type[] genericArgTypes) + private static void HandleGenericParameter(object? argObj, Type parameterType, Type[] genericArgTypes) { if (argObj is null) { @@ -94,7 +97,7 @@ private static void HandleGenericParameter(object argObj, Type parameterType, Ty } } - private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, object thisObj, JsValue[] arguments) + private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, JsValue[] arguments) { if (!method.IsGenericMethod) { @@ -156,7 +159,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var converter = Engine.ClrTypeConverter; - var thisObj = thisObject.ToObject(); + var thisObj = thisObject.ToObject() ?? _target; object?[]? parameters = null; foreach (var (method, arguments, _) in TypeConverter.FindBestMatch(_engine, _methods, ArgumentProvider)) { @@ -167,7 +170,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var argumentsMatch = true; - var resolvedMethod = ResolveMethod(method.Method, methodParameters, thisObj, arguments); + var resolvedMethod = ResolveMethod(method.Method, methodParameters, arguments); // TPC: if we're concerned about cost of MethodInfo.GetParameters() - we could only invoke it if this ends up being a generic method (i.e. they will be different in that scenario) methodParameters = resolvedMethod.GetParameters(); for (var i = 0; i < parameters.Length; i++) diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index e72bb9a124..46dfe9515a 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -117,14 +117,14 @@ public JsValue GetPath(string path) return TypeReference.CreateTypeReference(_engine, type); } - var lastPeriodPos = path.LastIndexOf(".", StringComparison.Ordinal); + var lastPeriodPos = path.LastIndexOf('.'); var trimPath = path.Substring(0, lastPeriodPos); type = GetType(assembly, trimPath); if (type != null) { foreach (Type nType in GetAllNestedTypes(type)) { - if (nType.FullName != null && nType.FullName.Replace("+", ".").Equals(comparedPath)) + if (nType.FullName != null && nType.FullName.Replace("+", ".").Equals(comparedPath, StringComparison.Ordinal)) { _engine.TypeCache.Add(comparedPath, nType); return TypeReference.CreateTypeReference(_engine, nType); @@ -159,7 +159,7 @@ public JsValue GetPath(string path) Type[] types = assembly.GetTypes(); foreach (Type t in types) { - if (t.FullName?.Replace("+", ".") == compared) + if (string.Equals(t.FullName?.Replace("+", "."), compared, StringComparison.Ordinal)) { return t; } diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 2b76866843..7703613325 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Globalization; using System.Reflection; @@ -51,7 +50,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) if (_properties is null || !_properties.ContainsKey(member)) { // can try utilize fast path - var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, forWrite: true); + var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, mustBeReadable: false, mustBeWritable: true); if (ReferenceEquals(accessor, ConstantValueAccessor.NullAccessor)) { @@ -117,7 +116,13 @@ public override JsValue Get(JsValue property, JsValue receiver) return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined; } - return base.Get(property, receiver); + var desc = GetOwnProperty(property, mustBeReadable: true, mustBeWritable: false); + if (desc != PropertyDescriptor.Undefined) + { + return UnwrapJsValue(desc, receiver); + } + + return Prototype?.Get(property, receiver) ?? Undefined; } public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) @@ -136,7 +141,7 @@ public override IEnumerable> GetOwnPro private IEnumerable EnumerateOwnPropertyKeys(Types types) { // prefer object order, add possible other properties after - var includeStrings = (types & Types.String) != 0; + var includeStrings = (types & Types.String) != Types.None; if (includeStrings && _typeDescriptor.IsStringKeyedGenericDictionary) // expando object for instance { var keys = _typeDescriptor.GetKeys(Target); @@ -183,6 +188,12 @@ private IEnumerable EnumerateOwnPropertyKeys(Types types) } public override PropertyDescriptor GetOwnProperty(JsValue property) + { + // we do not know if we need to read or write + return GetOwnProperty(property, mustBeReadable: false, mustBeWritable: false); + } + + private PropertyDescriptor GetOwnProperty(JsValue property, bool mustBeReadable, bool mustBeWritable) { if (TryGetProperty(property, out var x)) { @@ -235,13 +246,17 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable); } - var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member); + var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, mustBeReadable, mustBeWritable); var descriptor = accessor.CreatePropertyDescriptor(_engine, Target, enumerable: !isDictionary); - if (!isDictionary && !ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + if (!isDictionary + && !ReferenceEquals(descriptor, PropertyDescriptor.Undefined) + && (!mustBeReadable || accessor.Readable) + && (!mustBeWritable || accessor.Writable)) { // cache the accessor for faster subsequent accesses SetProperty(member, descriptor); } + return descriptor; } @@ -259,7 +274,9 @@ public static PropertyDescriptor GetPropertyDescriptor(Engine engine, object tar _ => null }; } - return engine.Options.Interop.TypeResolver.GetAccessor(engine, target.GetType(), member.Name, Factory).CreatePropertyDescriptor(engine, target); + + var accessor = engine.Options.Interop.TypeResolver.GetAccessor(engine, target.GetType(), member.Name, mustBeReadable: false, mustBeWritable: false, Factory); + return accessor.CreatePropertyDescriptor(engine, target); } internal static Type GetClrType(object obj, Type? type) @@ -291,7 +308,7 @@ private static JsValue Iterator(JsValue thisObject, JsValue[] arguments) : new EnumerableIterator(wrapper._engine, (IEnumerable) wrapper.Target); } - private static JsValue GetLength(JsValue thisObject, JsValue[] arguments) + private static JsNumber GetLength(JsValue thisObject, JsValue[] arguments) { var wrapper = (ObjectWrapper) thisObject; return JsNumber.Create((int) (wrapper._typeDescriptor.LengthProperty?.GetValue(wrapper.Target) ?? 0)); @@ -302,10 +319,9 @@ internal override ulong GetSmallestIndex(ulong length) return Target is ICollection ? 0 : base.GetSmallestIndex(length); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as ObjectWrapper); - } + public override bool Equals(object? obj) => Equals(obj as ObjectWrapper); + + public override bool Equals(JsValue? other) => Equals(other as ObjectWrapper); public bool Equals(ObjectWrapper? other) { @@ -319,16 +335,10 @@ public bool Equals(ObjectWrapper? other) return true; } - return Equals(Target, other.Target) && Equals(ClrType, other.ClrType); + return Equals(Target, other.Target); } - public override int GetHashCode() - { - var hashCode = -1468639730; - hashCode = hashCode * -1521134295 + Target.GetHashCode(); - hashCode = hashCode * -1521134295 + ClrType.GetHashCode(); - return hashCode; - } + public override int GetHashCode() => Target.GetHashCode(); private sealed class DictionaryIterator : IteratorInstance { diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 162f496117..3ad306f3ab 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -12,7 +12,7 @@ internal sealed class IndexerAccessor : ReflectionAccessor { private readonly object _key; - private readonly PropertyInfo _indexer; + internal readonly PropertyInfo _indexer; private readonly MethodInfo? _getter; private readonly MethodInfo? _setter; private readonly MethodInfo? _containsKey; @@ -37,18 +37,25 @@ internal static bool TryFindIndexer( [NotNullWhen(true)] out IndexerAccessor? indexerAccessor, [NotNullWhen(true)] out PropertyInfo? indexer) { + indexerAccessor = null; + indexer = null; var paramTypeArray = new Type[1]; + // integer keys can be ambiguous as we only know string keys + int? integerKey = null; + + if (int.TryParse(propertyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intKeyTemp)) + { + integerKey = intKeyTemp; + } + IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType) { object? key = null; // int key is quite common case - if (paramType == typeof(int)) + if (paramType == typeof(int) && integerKey is not null) { - if (int.TryParse(propertyName, out var intValue)) - { - key = intValue; - } + key = integerKey; } else { @@ -89,6 +96,7 @@ internal static bool TryFindIndexer( } // try to find first indexer having either public getter or setter with matching argument type + PropertyInfo? fallbackIndexer = null; foreach (var candidate in targetType.GetProperties()) { if (!filter(candidate)) @@ -108,17 +116,37 @@ internal static bool TryFindIndexer( indexerAccessor = ComposeIndexerFactory(candidate, paramType); if (indexerAccessor != null) { - indexer = candidate; - return true; + if (paramType != typeof(string) || integerKey is null) + { + // exact match, we don't need to check for integer key + indexer = candidate; + return true; + } + + if (fallbackIndexer is null) + { + // our fallback + fallbackIndexer = candidate; + } } } } + if (fallbackIndexer is not null) + { + indexer = fallbackIndexer; + // just to keep compiler happy, we know we have a value + indexerAccessor = indexerAccessor ?? new IndexerAccessor(indexer, null, null!); + return true; + } + indexerAccessor = default; indexer = default; return false; } + public override bool Readable => _indexer.CanRead; + public override bool Writable => _indexer.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 3a7a8b80df..e8823d3843 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs @@ -18,18 +18,13 @@ public MethodAccessor(Type targetType, string name, MethodDescriptor[] methods) public override bool Writable => false; - protected override object? DoGetValue(object target) - { - return null; - } + protected override object? DoGetValue(object target) => null; - protected override void DoSetValue(object target, object? value) - { - } + protected override void DoSetValue(object target, object? value) { } public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { - return new(new MethodInfoFunctionInstance(engine, _targetType, _name, _methods), PropertyFlag.AllForbidden); + return new(new MethodInfoFunctionInstance(engine, _targetType, target, _name, _methods), PropertyFlag.AllForbidden); } } } diff --git a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs index c4d7a66e65..6d68e40083 100644 --- a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs @@ -1,3 +1,5 @@ +using Jint.Runtime.Descriptors; + namespace Jint.Runtime.Interop.Reflection; internal sealed class NestedTypeAccessor : ReflectionAccessor @@ -11,12 +13,12 @@ public NestedTypeAccessor(TypeReference typeReference, string name) : base(typeo public override bool Writable => false; - protected override object? DoGetValue(object target) - { - return _typeReference; - } + protected override object? DoGetValue(object target) => null; + + protected override void DoSetValue(object target, object? value) { } - protected override void DoSetValue(object target, object? value) + public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { + return new(_typeReference, PropertyFlag.AllForbidden); } } diff --git a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs index 0c220ea2be..9f33768947 100644 --- a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs @@ -15,6 +15,8 @@ public PropertyAccessor( _propertyInfo = propertyInfo; } + public override bool Readable => _propertyInfo.CanRead; + public override bool Writable => _propertyInfo.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs index 233f6f05ed..b768f55b3e 100644 --- a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs @@ -28,6 +28,8 @@ protected ReflectionAccessor( _indexer = indexer; } + public virtual bool Readable => true; + public abstract bool Writable { get; } protected abstract object? DoGetValue(object target); diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index c4c99b3f22..40f14cbbb5 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -40,7 +40,9 @@ public static TypeReference CreateTypeReference(Engine engine) public static TypeReference CreateTypeReference(Engine engine, Type type) { - return new TypeReference(engine, type); + var reference = new TypeReference(engine, type); + engine.RegisterTypeReference(reference); + return reference; } protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) @@ -202,14 +204,14 @@ static ObjectInstance ObjectCreator(Engine engine, Realm realm, ObjectCreateStat private readonly record struct ObjectCreateState(TypeReference TypeReference, JsValue[] Arguments); - public override bool Equals(JsValue? obj) + public override bool Equals(JsValue? other) { - if (obj is TypeReference typeReference) + if (other is TypeReference typeReference) { return this.ReferenceType == typeReference.ReferenceType; } - return base.Equals(obj); + return base.Equals(other); } internal override bool OrdinaryHasInstance(JsValue v) @@ -261,20 +263,20 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) SetProperty(GlobalSymbolRegistry.HasInstance, hasInstanceProperty); return hasInstanceProperty; } - - return PropertyDescriptor.Undefined; } - - var key = jsString._value; - - if (_properties?.TryGetValue(key, out var descriptor) != true) + else { - descriptor = CreatePropertyDescriptor(key); - if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + var key = jsString._value; + + if (_properties?.TryGetValue(key, out var descriptor) != true) { - _properties ??= new PropertyDictionary(); - _properties[key] = descriptor; - return descriptor; + descriptor = CreatePropertyDescriptor(key); + if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + { + _properties ??= new PropertyDictionary(); + _properties[key] = descriptor; + return descriptor; + } } } @@ -325,7 +327,7 @@ private static ReflectionAccessor ResolveMemberAccessor(Engine engine, Type type public object Target => ReferenceType; - private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) + private static JsBoolean HasInstance(JsValue thisObject, JsValue[] arguments) { var typeReference = thisObject as TypeReference; var other = arguments.At(0); @@ -344,7 +346,9 @@ private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) _ => null }; - return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)); + return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)) + ? JsBoolean.True + : JsBoolean.False; } public override string ToString() diff --git a/Jint/Runtime/Interop/TypeReferencePrototype.cs b/Jint/Runtime/Interop/TypeReferencePrototype.cs index 562dc0fe52..51cd02fb3b 100644 --- a/Jint/Runtime/Interop/TypeReferencePrototype.cs +++ b/Jint/Runtime/Interop/TypeReferencePrototype.cs @@ -1,4 +1,6 @@ -using Jint.Native.Object; +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime.Descriptors; namespace Jint.Runtime.Interop; @@ -11,4 +13,14 @@ public TypeReferencePrototype(Engine engine, TypeReference typeReference) : base } public TypeReference TypeReference { get; } + + public override PropertyDescriptor GetOwnProperty(JsValue property) + { + var descriptor = TypeReference.GetOwnProperty(property); + if (descriptor != PropertyDescriptor.Undefined) + { + return descriptor; + } + return base.GetOwnProperty(property); + } } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index af40e876ac..eb212b5bf0 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Dynamic; +using System.Globalization; using System.Reflection; using System.Threading; using Jint.Runtime.Interop.Reflection; @@ -22,7 +23,7 @@ public sealed class TypeResolver internal bool Filter(Engine engine, MemberInfo m) { - return (engine.Options.Interop.AllowGetType || m.Name != nameof(GetType)) && MemberFilter(m); + return (engine.Options.Interop.AllowGetType || !string.Equals(m.Name, nameof(GetType), StringComparison.Ordinal)) && MemberFilter(m); } /// @@ -46,8 +47,9 @@ internal ReflectionAccessor GetAccessor( Engine engine, Type type, string member, - Func? accessorFactory = null, - bool forWrite = false) + bool mustBeReadable, + bool mustBeWritable, + Func? accessorFactory = null) { var key = new Engine.ClrPropertyDescriptorFactoriesKey(type, member); @@ -57,7 +59,7 @@ internal ReflectionAccessor GetAccessor( return accessor; } - accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member, forWrite); + accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member, mustBeReadable, mustBeWritable); // racy, we don't care, worst case we'll catch up later Interlocked.CompareExchange(ref engine._reflectionAccessors, @@ -73,9 +75,10 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( Engine engine, Type type, string memberName, - bool forWrite) + bool mustBeReadable, + bool mustBeWritable) { - var isNumber = uint.TryParse(memberName, out _); + var isInteger = long.TryParse(memberName, NumberStyles.Integer, CultureInfo.InvariantCulture, out _); // we can always check indexer if there's one, and then fall back to properties if indexer returns null IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer); @@ -83,9 +86,10 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( const BindingFlags BindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public; // properties and fields cannot be numbers - if (!isNumber + if (!isInteger && TryFindMemberAccessor(engine, type, memberName, BindingFlags, indexer, out var temp) - && (!forWrite || temp.Writable)) + && (!mustBeReadable || temp.Readable) + && (!mustBeWritable || temp.Writable)) { return temp; } @@ -95,84 +99,106 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( return new DynamicObjectAccessor(type, memberName); } - // if no methods are found check if target implemented indexing - if (indexerAccessor != null) - { - return indexerAccessor; - } - - // try to find a single explicit property implementation - List? list = null; var typeResolverMemberNameComparer = MemberNameComparer; var typeResolverMemberNameCreator = MemberNameCreator; - foreach (var iface in type.GetInterfaces()) + + if (!isInteger) { - foreach (var iprop in iface.GetProperties()) + // try to find a single explicit property implementation + List? list = null; + foreach (var iface in type.GetInterfaces()) { - if (!Filter(engine, iprop)) + foreach (var iprop in iface.GetProperties()) { - continue; - } + if (!Filter(engine, iprop)) + { + continue; + } - if (iprop.Name == "Item" && iprop.GetIndexParameters().Length == 1) - { - // never take indexers, should use the actual indexer - continue; - } + if (string.Equals(iprop.Name, "Item", StringComparison.Ordinal) && iprop.GetIndexParameters().Length == 1) + { + // never take indexers, should use the actual indexer + continue; + } - foreach (var name in typeResolverMemberNameCreator(iprop)) - { - if (typeResolverMemberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(iprop)) { - list ??= new List(); - list.Add(iprop); + if (typeResolverMemberNameComparer.Equals(name, memberName)) + { + list ??= new List(); + list.Add(iprop); + } } } } - } - if (list?.Count == 1) - { - return new PropertyAccessor(memberName, list[0]); - } + if (list?.Count == 1) + { + return new PropertyAccessor(memberName, list[0]); + } - // try to find explicit method implementations - List? explicitMethods = null; - foreach (var iface in type.GetInterfaces()) - { - foreach (var imethod in iface.GetMethods()) + // try to find explicit method implementations + List? explicitMethods = null; + foreach (var iface in type.GetInterfaces()) { - if (!Filter(engine, imethod)) + foreach (var imethod in iface.GetMethods()) { - continue; - } + if (!Filter(engine, imethod)) + { + continue; + } - foreach (var name in typeResolverMemberNameCreator(imethod)) - { - if (typeResolverMemberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(imethod)) { - explicitMethods ??= new List(); - explicitMethods.Add(imethod); + if (typeResolverMemberNameComparer.Equals(name, memberName)) + { + explicitMethods ??= new List(); + explicitMethods.Add(imethod); + } } } } + + if (explicitMethods?.Count > 0) + { + return new MethodAccessor(type, memberName, MethodDescriptor.Build(explicitMethods)); + } } - if (explicitMethods?.Count > 0) + // if no methods are found check if target implemented indexing + var score = int.MaxValue; + if (indexerAccessor != null) { - return new MethodAccessor(type, memberName, MethodDescriptor.Build(explicitMethods)); + var parameter = indexerAccessor._indexer.GetIndexParameters()[0]; + score = CalculateIndexerScore(parameter, isInteger); } - // try to find explicit indexer implementations - foreach (var interfaceType in type.GetInterfaces()) + if (score != 0) { - if (IndexerAccessor.TryFindIndexer(engine, interfaceType, memberName, out var accessor, out _)) + // try to find explicit indexer implementations that has a better score than earlier + foreach (var interfaceType in type.GetInterfaces()) { - return accessor; + if (IndexerAccessor.TryFindIndexer(engine, interfaceType, memberName, out var accessor, out _)) + { + var parameter = accessor._indexer.GetIndexParameters()[0]; + var newScore = CalculateIndexerScore(parameter, isInteger); + if (newScore < score) + { + // found a better one + indexerAccessor = accessor; + score = newScore; + } + } } } - if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) + // use the best indexer we were able to find + if (indexerAccessor != null) + { + return indexerAccessor; + } + + if (!isInteger && engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) { var matches = new List(); foreach (var method in extensionMethods) @@ -200,6 +226,23 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( return ConstantValueAccessor.NullAccessor; } + private static int CalculateIndexerScore(ParameterInfo parameter, bool isInteger) + { + var paramType = parameter.ParameterType; + + if (paramType == typeof(int)) + { + return isInteger ? 0 : 10; + } + + if (paramType == typeof(string)) + { + return 1; + } + + return 5; + } + internal bool TryFindMemberAccessor( Engine engine, Type type, @@ -212,29 +255,50 @@ internal bool TryFindMemberAccessor( PropertyInfo? property = null; var memberNameComparer = MemberNameComparer; var typeResolverMemberNameCreator = MemberNameCreator; - foreach (var p in type.GetProperties(bindingFlags)) + + PropertyInfo? GetProperty(Type t) { - if (!Filter(engine, p)) + foreach (var p in t.GetProperties(bindingFlags)) { - continue; - } + if (!Filter(engine, p)) + { + continue; + } - // only if it's not an indexer, we can do case-ignoring matches - var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item"; - if (!isStandardIndexer) - { - foreach (var name in typeResolverMemberNameCreator(p)) + // only if it's not an indexer, we can do case-ignoring matches + var isStandardIndexer = p.GetIndexParameters().Length == 1 && string.Equals(p.Name, "Item", StringComparison.Ordinal); + if (!isStandardIndexer) { - if (memberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(p)) { - property = p; - break; + if (memberNameComparer.Equals(name, memberName)) + { + property = p; + break; + } } } } + + return property; } - if (property != null) + property = GetProperty(type); + + if (property is null && type.IsInterface) + { + // check inherited interfaces + foreach (var iface in type.GetInterfaces()) + { + property = GetProperty(iface); + if (property is not null) + { + break; + } + } + } + + if (property is not null) { accessor = new PropertyAccessor(memberName, property, indexerToTry); return true; @@ -259,7 +323,7 @@ internal bool TryFindMemberAccessor( } } - if (field != null) + if (field is not null) { accessor = new FieldAccessor(field, memberName, indexerToTry); return true; @@ -267,11 +331,11 @@ internal bool TryFindMemberAccessor( // if no properties were found then look for a method List? methods = null; - foreach (var m in type.GetMethods(bindingFlags)) + void AddMethod(MethodInfo m) { if (!Filter(engine, m)) { - continue; + return; } foreach (var name in typeResolverMemberNameCreator(m)) @@ -284,16 +348,34 @@ internal bool TryFindMemberAccessor( } } + foreach (var m in type.GetMethods(bindingFlags)) + { + AddMethod(m); + } + + foreach (var iface in type.GetInterfaces()) + { + foreach (var m in iface.GetMethods()) + { + AddMethod(m); + } + } + // TPC: need to grab the extension methods here - for overloads if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) { foreach (var methodInfo in extensionMethods) { - if (memberNameComparer.Equals(methodInfo.Name, memberName)) - { - methods ??= new List(); - methods.Add(methodInfo); - } + AddMethod(methodInfo); + } + } + + // Add Object methods to interface + if (type.IsInterface) + { + foreach (var m in typeof(object).GetMethods(bindingFlags)) + { + AddMethod(m); } } @@ -353,7 +435,7 @@ public override bool Equals(string? x, string? y) #if SUPPORTS_SPAN_PARSE equals = x.AsSpan(1).SequenceEqual(y.AsSpan(1)); #else - equals = x.Substring(1) == y.Substring(1); + equals = string.Equals(x.Substring(1), y.Substring(1), StringComparison.Ordinal); #endif } diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index b6e2964f3d..4b98329267 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -12,20 +12,21 @@ internal sealed class BindingPatternAssignmentExpression : JintExpression { private readonly BindingPattern _pattern; private JintExpression _right = null!; + private bool _initialized; public BindingPatternAssignmentExpression(AssignmentExpression expression) : base(expression) { _pattern = (BindingPattern) expression.Left; - _initialized = false; - } - - protected override void Initialize(EvaluationContext context) - { - _right = Build(((AssignmentExpression) _expression).Right); } protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + _right = Build(((AssignmentExpression) _expression).Right); + _initialized = true; + } + var rightValue = _right.GetValue(context); if (context.IsAbrupt()) { @@ -264,8 +265,7 @@ private static JsValue HandleArrayPattern( } else { - ExceptionHelper.ThrowArgumentOutOfRangeException("pattern", - "Unable to determine how to handle array pattern element " + left); + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(pattern), $"Unable to determine how to handle array pattern element {left}"); break; } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs index 0a67535406..230b9ff4b8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs @@ -8,10 +8,10 @@ internal sealed class JintArrayExpression : JintExpression { private JintExpression?[] _expressions = Array.Empty(); private bool _hasSpreads; + private bool _initialized; private JintArrayExpression(ArrayExpression expression) : base(expression) { - _initialized = false; } public static JintExpression Build(ArrayExpression expression) @@ -21,7 +21,7 @@ public static JintExpression Build(ArrayExpression expression) : new JintArrayExpression(expression); } - protected override void Initialize(EvaluationContext context) + private void Initialize() { ref readonly var elements = ref ((ArrayExpression) _expression).Elements; var expressions = _expressions = new JintExpression[((ArrayExpression) _expression).Elements.Count]; @@ -42,6 +42,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var a = engine.Realm.Intrinsics.Array.ArrayCreate(_hasSpreads ? 0 : (uint) _expressions.Length); diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs index 74ca100e36..4689ec45e5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs @@ -199,10 +199,6 @@ protected override object EvaluateInternal(EvaluationContext context) { newLeftValue = JsValue.Undefined; } - else if (!AreIntegerOperands(originalLeftValue, rval)) - { - newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval); - } else { newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval); @@ -343,13 +339,13 @@ internal sealed class SimpleAssignmentExpression : JintExpression private JintIdentifierExpression? _leftIdentifier; private bool _evalOrArguments; + private bool _initialized; public SimpleAssignmentExpression(AssignmentExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var assignmentExpression = (AssignmentExpression) _expression; _left = Build((Expression) assignmentExpression.Left); @@ -361,6 +357,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + object? completion = null; if (_leftIdentifier != null) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs index baaaa6b6e7..600b7cbb55 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs @@ -6,19 +6,21 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintAwaitExpression : JintExpression { private JintExpression _awaitExpression = null!; + private bool _initialized; public JintAwaitExpression(AwaitExpression expression) : base(expression) { _initialized = false; } - protected override void Initialize(EvaluationContext context) - { - _awaitExpression = Build(((AwaitExpression) _expression).Argument); - } - protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + _awaitExpression = Build(((AwaitExpression) _expression).Argument); + _initialized = true; + } + var engine = context.Engine; var asyncContext = engine.ExecutionContext; diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 9828dd6446..76a9abeb12 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -20,18 +20,25 @@ internal abstract class JintBinaryExpression : JintExpression private JintExpression _left = null!; private JintExpression _right = null!; + private bool _initialized; private JintBinaryExpression(BinaryExpression expression) : base(expression) { // TODO check https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator - _initialized = false; } - protected override void Initialize(EvaluationContext context) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() { + if (_initialized) + { + return; + } + var expression = (BinaryExpression) _expression; _left = Build(expression.Left); _right = Build(expression.Right); + _initialized = true; } internal static bool TryOperatorOverloading( @@ -56,7 +63,7 @@ internal static bool TryOperatorOverloading( var leftMethods = leftType.GetOperatorOverloadMethods(); var rightMethods = rightType.GetOperatorOverloadMethods(); - var methods = leftMethods.Concat(rightMethods).Where(x => x.Name == clrName && x.GetParameters().Length == 2); + var methods = leftMethods.Concat(rightMethods).Where(x => string.Equals(x.Name, clrName, StringComparison.Ordinal) && x.GetParameters().Length == 2); var methodDescriptors = MethodDescriptor.Build(methods.ToArray()); return TypeConverter.FindBestMatch(context.Engine, methodDescriptors, _ => arguments).FirstOrDefault().Method; @@ -196,6 +203,8 @@ public StrictlyEqualBinaryExpression(BinaryExpression expression) : base(express protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); var equal = left == right; @@ -211,6 +220,8 @@ public StrictlyNotEqualBinaryExpression(BinaryExpression expression) : base(expr protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); return left == right ? JsBoolean.False : JsBoolean.True; @@ -225,6 +236,8 @@ public LessBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -248,6 +261,8 @@ public GreaterBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -271,6 +286,8 @@ public PlusBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -314,6 +331,8 @@ public MinusBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -352,6 +371,8 @@ public TimesBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -393,6 +414,8 @@ public DivideBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -419,6 +442,8 @@ public EqualBinaryExpression(BinaryExpression expression, bool invert = false) : protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -448,6 +473,8 @@ public CompareBinaryExpression(BinaryExpression expression, bool leftFirst) : ba protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftValue = _left.GetValue(context); var rightValue = _right.GetValue(context); @@ -473,6 +500,8 @@ public InstanceOfBinaryExpression(BinaryExpression expression) : base(expression protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftValue = _left.GetValue(context); var rightValue = _right.GetValue(context); return leftValue.InstanceofOperator(rightValue) ? JsBoolean.True : JsBoolean.False; @@ -487,6 +516,8 @@ public ExponentiationBinaryExpression(BinaryExpression expression) : base(expres protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftReference = _left.GetValue(context); var rightReference = _right.GetValue(context); @@ -612,6 +643,8 @@ public InBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -640,6 +673,8 @@ public ModuloBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -754,6 +789,8 @@ public BitwiseBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var lval = _left.GetValue(context); var rval = _right.GetValue(context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index c1f1e0abfc..53c8e1241f 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -4,6 +4,7 @@ using Jint.Native; using Jint.Native.Function; using Jint.Native.Object; +using Jint.Runtime.CallStack; using Jint.Runtime.Environments; using Jint.Runtime.References; @@ -16,13 +17,13 @@ internal sealed class JintCallExpression : JintExpression private JintExpression _calleeExpression = null!; private bool _hasSpreads; + private bool _initialized; public JintCallExpression(CallExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize(EvaluationContext context) { var expression = (CallExpression) _expression; ref readonly var expressionArguments = ref expression.Arguments; @@ -78,9 +79,15 @@ static bool CanSpread(Node? e) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(context); + _initialized = true; + } + if (!context.Engine._stackGuard.TryEnterOnCurrentStack()) { - return context.Engine._stackGuard.RunOnEmptyStack(EvaluateInternal, context); + return StackGuard.RunOnEmptyStack(EvaluateInternal, context); } if (_calleeExpression._expression.Type == Nodes.Super) @@ -109,7 +116,7 @@ protected override object EvaluateInternal(EvaluationContext context) if (ReferenceEquals(func, engine.Realm.Intrinsics.Eval) && referenceRecord != null && !referenceRecord.IsPropertyReference - && referenceRecord.ReferencedName == CommonProperties.Eval) + && CommonProperties.Eval.Equals(referenceRecord.ReferencedName)) { return HandleEval(context, func, engine, referenceRecord); } @@ -244,7 +251,7 @@ private JsValue HandleEval(EvaluationContext context, JsValue func, Engine engin return value; } - private JsValue SuperCall(EvaluationContext context) + private ObjectInstance SuperCall(EvaluationContext context) { var engine = context.Engine; var thisEnvironment = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index 51e42f766a..fe78d83ef8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -10,9 +10,6 @@ namespace Jint.Runtime.Interpreter.Expressions { internal abstract class JintExpression { - // require sub-classes to set to false explicitly to skip virtual call - protected bool _initialized = true; - protected internal readonly Expression _expression; protected JintExpression(Expression expression) @@ -43,12 +40,6 @@ public object Evaluate(EvaluationContext context) var oldSyntaxElement = context.LastSyntaxElement; context.PrepareFor(_expression); - if (!_initialized) - { - Initialize(context); - _initialized = true; - } - var result = EvaluateInternal(context); context.LastSyntaxElement = oldSyntaxElement; @@ -59,23 +50,9 @@ public object Evaluate(EvaluationContext context) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal object EvaluateWithoutNodeTracking(EvaluationContext context) { - if (!_initialized) - { - Initialize(context); - _initialized = true; - } - return EvaluateInternal(context); } - /// - /// Opportunity to build one-time structures and caching based on lexical context. - /// - /// - protected virtual void Initialize(EvaluationContext context) - { - } - protected abstract object EvaluateInternal(EvaluationContext context); /// diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index 24620560af..864a17584f 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -9,28 +9,58 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintIdentifierExpression : JintExpression { + private EnvironmentRecord.BindingName _identifier = null!; + private bool _initialized; + public JintIdentifierExpression(Identifier expression) : base(expression) { } - internal EnvironmentRecord.BindingName Identifier + public EnvironmentRecord.BindingName Identifier { - get => (EnvironmentRecord.BindingName) (_expression.AssociatedData ??= new EnvironmentRecord.BindingName(((Identifier) _expression).Name)); + get + { + EnsureIdentifier(); + return _identifier; + } } - public bool HasEvalOrArguments => Identifier.HasEvalOrArguments; + private void Initialize() + { + EnsureIdentifier(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureIdentifier() + { + _identifier ??= _expression.AssociatedData as EnvironmentRecord.BindingName ?? new EnvironmentRecord.BindingName(((Identifier) _expression).Name); + } + + public bool HasEvalOrArguments + { + get + { + var name = ((Identifier) _expression).Name; + return name is "eval" or "arguments"; + } + } protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var env = engine.ExecutionContext.LexicalEnvironment; var strict = StrictModeScope.IsStrictModeCode; - var identifier = Identifier; - var identifierEnvironment = JintEnvironment.TryGetIdentifierEnvironmentWithBinding(env, identifier, out var temp) + var identifierEnvironment = JintEnvironment.TryGetIdentifierEnvironmentWithBinding(env, _identifier, out var temp) ? temp : JsValue.Undefined; - return engine._referencePool.Rent(identifierEnvironment, identifier.Value, strict, thisValue: null); + return engine._referencePool.Rent(identifierEnvironment, _identifier.Value, strict, thisValue: null); } public override JsValue GetValue(EvaluationContext context) @@ -79,6 +109,6 @@ public override JsValue GetValue(EvaluationContext context) [MethodImpl(MethodImplOptions.NoInlining)] private void ThrowNotInitialized(Engine engine) { - ExceptionHelper.ThrowReferenceError(engine.Realm, Identifier.Key.Name + " has not been initialized"); + ExceptionHelper.ThrowReferenceError(engine.Realm, $"{_identifier.Key.Name} has not been initialized"); } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs index 5f03d8667e..c90affcf9b 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs @@ -7,14 +7,14 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintImportExpression : JintExpression { private JintExpression _importExpression; + private bool _initialized; public JintImportExpression(ImportExpression expression) : base(expression) { - _initialized = false; _importExpression = null!; } - protected override void Initialize(EvaluationContext context) + private void Initialize(EvaluationContext context) { var expression = ((ImportExpression) _expression).Source; _importExpression = Build(expression); @@ -25,6 +25,12 @@ protected override void Initialize(EvaluationContext context) /// protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(context); + _initialized = true; + } + var referencingScriptOrModule = context.Engine.GetActiveScriptOrModule(); var argRef = _importExpression.Evaluate(context); var specifier = context.Engine.GetValue(argRef); //.UnwrapIfPromise(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs index a7531617b4..8a091bdb40 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs @@ -7,13 +7,13 @@ internal sealed class JintLogicalAndExpression : JintExpression { private JintExpression _left = null!; private JintExpression _right = null!; + private bool _initialized; public JintLogicalAndExpression(BinaryExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (BinaryExpression) _expression; _left = Build(expression.Left); @@ -22,6 +22,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var left = _left.GetValue(context); if (left is JsBoolean b && !b._value) diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index f2480d2774..29251b90a0 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -14,37 +14,50 @@ internal sealed class JintMemberExpression : JintExpression private JintExpression _objectExpression = null!; private JintExpression? _propertyExpression; private JsValue? _determinedProperty; + private bool _initialized; + + private static readonly JsValue _nullMarker = new JsString("NULL MARKER"); public JintMemberExpression(MemberExpression expression) : base(expression) { - _initialized = false; _memberExpression = (MemberExpression) _expression; } - protected override void Initialize(EvaluationContext context) + internal static JsValue InitializeDeterminedProperty(MemberExpression expression, bool cache) { - _objectExpression = Build(_memberExpression.Object); - - if (!_memberExpression.Computed) + JsValue? property = null; + if (!expression.Computed) { - if (_memberExpression.Property is Identifier identifier) + if (expression.Property is Identifier identifier) { - _determinedProperty = identifier.Name; + property = cache ? JsString.CachedCreate(identifier.Name) : JsString.Create(identifier.Name); } } - else if (_memberExpression.Property.Type == Nodes.Literal) + else if (expression.Property.Type == Nodes.Literal) { - _determinedProperty = JintLiteralExpression.ConvertToJsValue((Literal) _memberExpression.Property); + property = JintLiteralExpression.ConvertToJsValue((Literal) expression.Property); } - if (_determinedProperty is null) - { - _propertyExpression = Build(_memberExpression.Property); - } + return property ?? _nullMarker; } protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + _objectExpression = Build(_memberExpression.Object); + + _determinedProperty ??= _expression.AssociatedData as JsValue ?? InitializeDeterminedProperty(_memberExpression, cache: false); + + if (ReferenceEquals(_determinedProperty, _nullMarker)) + { + _propertyExpression = Build(_memberExpression.Property); + _determinedProperty = null; + } + + _initialized = true; + } + JsValue? actualThis = null; string? baseReferenceName = null; JsValue? baseValue = null; @@ -127,7 +140,7 @@ protected override object EvaluateInternal(EvaluationContext context) /// /// https://tc39.es/ecma262/#sec-makeprivatereference /// - private object MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) + private static Reference MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) { var privEnv = engine.ExecutionContext.PrivateEnvironment; var privateName = privEnv!.ResolvePrivateIdentifier(privateIdentifier.ToString()); diff --git a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs index e4d302efd1..da6e7f87ca 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs @@ -15,12 +15,12 @@ public JintMetaPropertyExpression(MetaProperty expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { var expression = (MetaProperty) _expression; - if (expression.Meta.Name == "new" && expression.Property.Name == "target") + if (string.Equals(expression.Meta.Name, "new", StringComparison.Ordinal) && string.Equals(expression.Property.Name, "target", StringComparison.Ordinal)) { return context.Engine.GetNewTarget(); } - if (expression.Meta.Name == "import" && expression.Property.Name == "meta") + if (string.Equals(expression.Meta.Name, "import", StringComparison.Ordinal) && string.Equals(expression.Property.Name, "meta", StringComparison.Ordinal)) { var module = (SourceTextModuleRecord) context.Engine.ExecutionContext.ScriptOrModule!; var importMeta = module.ImportMeta; diff --git a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs index 3b9a91e571..20d6c51aa8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs @@ -8,13 +8,13 @@ internal sealed class JintNewExpression : JintExpression private JintExpression _calleeExpression = null!; private JintExpression[] _jintArguments = Array.Empty(); private bool _hasSpreads; + private bool _initialized; public JintNewExpression(NewExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (NewExpression) _expression; _calleeExpression = Build(expression.Callee); @@ -35,6 +35,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; // todo: optimize by defining a common abstract class or interface diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index b02d0af123..9b7c3d32e7 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -18,6 +18,7 @@ internal sealed class JintObjectExpression : JintExpression // check if we can do a shortcut when all are object properties // and don't require duplicate checking private bool _canBuildFast; + private bool _initialized; private sealed class ObjectProperty { @@ -54,7 +55,6 @@ public JintFunctionDefinition GetFunctionDefinition(Engine engine) private JintObjectExpression(ObjectExpression expression) : base(expression) { - _initialized = false; } public static JintExpression Build(ObjectExpression expression) @@ -64,7 +64,7 @@ public static JintExpression Build(ObjectExpression expression) : new JintObjectExpression(expression); } - protected override void Initialize(EvaluationContext context) + private void Initialize() { _canBuildFast = true; var expression = (ObjectExpression) _expression; @@ -87,7 +87,7 @@ protected override void Initialize(EvaluationContext context) if (!p.Computed && p.Key is Identifier identifier) { propName = identifier.Name; - _canBuildFast &= propName != "__proto__"; + _canBuildFast &= !string.Equals(propName, "__proto__", StringComparison.Ordinal); } _properties[i] = new ObjectProperty(propName, p); @@ -120,6 +120,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + return _canBuildFast ? BuildObjectFast(context) : BuildObjectNormal(context); @@ -128,7 +134,7 @@ protected override object EvaluateInternal(EvaluationContext context) /// /// Version that can safely build plain object with only normal init/data fields fast. /// - private object BuildObjectFast(EvaluationContext context) + private JsObject BuildObjectFast(EvaluationContext context) { var obj = new JsObject(context.Engine); var properties = new PropertyDictionary(_properties.Length, checkExistingKeys: true); @@ -200,7 +206,7 @@ private object BuildObjectNormal(EvaluationContext context) } var propValue = completion.Clone(); - if (objectProperty._key == "__proto__" && !objectProperty._value.Computed && !objectProperty._value.Shorthand) + if (string.Equals(objectProperty._key, "__proto__", StringComparison.Ordinal) && !objectProperty._value.Computed && !objectProperty._value.Shorthand) { if (propValue.IsObject() || propValue.IsNull()) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs index 415960764a..b4a2148d59 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs @@ -6,13 +6,13 @@ namespace Jint.Runtime.Interpreter.Expressions internal sealed class JintSequenceExpression : JintExpression { private JintExpression[] _expressions = Array.Empty(); + private bool _initialized; public JintSequenceExpression(SequenceExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (SequenceExpression) _expression; ref readonly var expressions = ref expression.Expressions; @@ -27,6 +27,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var result = JsValue.Undefined; foreach (var expression in _expressions) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs index 88ee87c6e4..95e34407b4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs @@ -12,13 +12,13 @@ internal sealed class JintTaggedTemplateExpression : JintExpression private JintExpression _tagIdentifier = null!; private JintTemplateLiteralExpression _quasi = null!; + private bool _initialized; public JintTaggedTemplateExpression(TaggedTemplateExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var taggedTemplateExpression = (TaggedTemplateExpression) _expression; _tagIdentifier = Build(taggedTemplateExpression.Tag); @@ -28,6 +28,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var identifier = _tagIdentifier.Evaluate(context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs index 105f9d81af..e3b7688ac4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs @@ -8,14 +8,14 @@ internal sealed class JintTemplateLiteralExpression : JintExpression { internal readonly TemplateLiteral _templateLiteralExpression; internal JintExpression[] _expressions = Array.Empty(); + private bool _initialized; public JintTemplateLiteralExpression(TemplateLiteral expression) : base(expression) { _templateLiteralExpression = expression; - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { DoInitialize(); } @@ -32,8 +32,14 @@ internal void DoInitialize() _initialized = true; } - private JsString BuildString(EvaluationContext context) + protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + using var sb = StringBuilderPool.Rent(); ref readonly var elements = ref _templateLiteralExpression.Quasis; for (var i = 0; i < elements.Count; i++) @@ -49,9 +55,4 @@ private JsString BuildString(EvaluationContext context) return JsString.Create(sb.ToString()); } - - protected override object EvaluateInternal(EvaluationContext context) - { - return BuildString(context); - } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs index aa02c80fe4..3d0b97a600 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs @@ -306,7 +306,7 @@ internal static bool TryOperatorOverloading(EvaluationContext context, JsValue v MethodInfo? foundMethod = null; foreach (var x in operandType.GetOperatorOverloadMethods()) { - if (x.Name == clrName && x.GetParameters().Length == 1) + if (string.Equals(x.Name, clrName, StringComparison.Ordinal) && x.GetParameters().Length == 1) { foundMethod = x; break; diff --git a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs index b71da841a1..ddcec5bcb3 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs @@ -13,13 +13,13 @@ internal sealed class JintUpdateExpression : JintExpression private JintIdentifierExpression? _leftIdentifier; private bool _evalOrArguments; + private bool _initialized; public JintUpdateExpression(UpdateExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (UpdateExpression) _expression; _prefix = expression.Prefix; @@ -43,6 +43,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var fastResult = _leftIdentifier != null ? UpdateIdentifier(context) : null; diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index f68388eae6..7b8f20237b 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -48,7 +48,8 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun AsyncFunctionStart(context, promiseCapability, context => { context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList); - return new Completion(CompletionType.Return, _bodyExpression.GetValue(context), _bodyExpression._expression); + var jsValue = _bodyExpression.GetValue(context).Clone(); + return new Completion(CompletionType.Return, jsValue, _bodyExpression._expression); }); result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body); } @@ -147,7 +148,7 @@ 8. Return unused. /// /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody /// - private Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) + private static Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) { ExceptionHelper.ThrowNotImplementedException("generators not implemented"); return default; @@ -340,7 +341,7 @@ private static void GetBoundNames( { _hasDuplicates |= checkDuplicates && target.Contains(identifier.Name); target.Add(identifier.Name); - hasArguments |= identifier.Name == "arguments"; + hasArguments |= string.Equals(identifier.Name, "arguments", StringComparison.Ordinal); return; } @@ -430,7 +431,7 @@ private static void ProcessParameters( { var id = (Identifier) parameter; state.HasDuplicates |= parameterNames.Contains(id.Name); - hasArguments = id.Name == "arguments"; + hasArguments = string.Equals(id.Name, "arguments", StringComparison.Ordinal); parameterNames.Add(id.Name); } else if (type != Nodes.Literal) @@ -484,7 +485,7 @@ private static bool HasArgumentsReference(Node node) var childType = childNode.Type; if (childType == Nodes.Identifier) { - if (((Identifier) childNode).Name == "arguments") + if (string.Equals(((Identifier) childNode).Name, "arguments", StringComparison.Ordinal)) { return true; } diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 74f84a3e90..52bd55b0a0 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -92,6 +92,10 @@ public Completion Execute(EvaluationContext context) if (c.Value is null) { c = s.Execute(context); + if (context.Engine._error is not null) + { + return HandleError(context.Engine, s); + } } if (c.Type != CompletionType.Normal) @@ -138,6 +142,19 @@ private static Completion HandleException(EvaluationContext context, Exception e throw exception; } + private static Completion HandleError(Engine engine, JintStatement? s) + { + var error = engine._error!; + engine._error = null; + return CreateThrowCompletion(error.ErrorConstructor, error.Message, engine._lastSyntaxElement ?? s!._statement); + } + + private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, string? message, SyntaxElement s) + { + var error = errorConstructor.Construct(message); + return new Completion(CompletionType.Throw, error, s); + } + private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, Exception e, SyntaxElement s) { var error = errorConstructor.Construct(e.Message); diff --git a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs index fca4e299ee..10ab83138c 100644 --- a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs @@ -37,9 +37,9 @@ protected override Completion ExecuteInternal(EvaluationContext context) v = completion.Value; } - if (completion.Type != CompletionType.Continue || context.Target != _labelSetName) + if (completion.Type != CompletionType.Continue || !string.Equals(context.Target, _labelSetName, StringComparison.Ordinal)) { - if (completion.Type == CompletionType.Break && (context.Target == null || context.Target == _labelSetName)) + if (completion.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _labelSetName, StringComparison.Ordinal))) { return new Completion(CompletionType.Normal, v, _statement); } diff --git a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs index f0e03c046d..4d280895eb 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs @@ -80,7 +80,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) /// /// https://tc39.es/ecma262/#sec-initializeboundname /// - private void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment) + private static void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment) { if (environment is not null) { diff --git a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs index 574045905a..e13a7a6329 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs @@ -7,6 +7,9 @@ internal sealed class JintExpressionStatement : JintStatement /// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled /// - private JsValue AsyncModuleExecutionFulfilled(JsValue thisObject, JsValue[] arguments) + private static JsValue AsyncModuleExecutionFulfilled(JsValue thisObject, JsValue[] arguments) { var module = (CyclicModuleRecord) arguments.At(0); if (module.Status == ModuleStatus.Evaluated) diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs index 67dd9f30c5..b019f8c54e 100644 --- a/Jint/Runtime/Modules/DefaultModuleLoader.cs +++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs @@ -151,6 +151,6 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) private static bool IsRelative(string specifier) { - return specifier.StartsWith(".") || specifier.StartsWith("/"); + return specifier.StartsWith('.') || specifier.StartsWith('/'); } } diff --git a/Jint/Runtime/Modules/FailFastModuleLoader.cs b/Jint/Runtime/Modules/FailFastModuleLoader.cs index a89163809c..a00e414d11 100644 --- a/Jint/Runtime/Modules/FailFastModuleLoader.cs +++ b/Jint/Runtime/Modules/FailFastModuleLoader.cs @@ -6,7 +6,9 @@ internal sealed class FailFastModuleLoader : IModuleLoader { public static readonly IModuleLoader Instance = new FailFastModuleLoader(); +#pragma warning disable CA1822 public Uri BasePath +#pragma warning restore CA1822 { get { diff --git a/Jint/Runtime/Modules/ModuleNamespace.cs b/Jint/Runtime/Modules/ModuleNamespace.cs index 6523b87a5d..3213797d67 100644 --- a/Jint/Runtime/Modules/ModuleNamespace.cs +++ b/Jint/Runtime/Modules/ModuleNamespace.cs @@ -20,7 +20,7 @@ internal sealed class ModuleNamespace : ObjectInstance public ModuleNamespace(Engine engine, ModuleRecord module, List exports) : base(engine) { _module = module; - _exports = new HashSet(exports); + _exports = new HashSet(exports, StringComparer.Ordinal); } protected override void Initialize() @@ -162,7 +162,7 @@ public override JsValue Get(JsValue property, JsValue receiver) var binding = m.ResolveExport(p); var targetModule = binding.Module; - if (binding.BindingName == "*namespace*") + if (string.Equals(binding.BindingName, "*namespace*", StringComparison.Ordinal)) { return ModuleRecord.GetModuleNamespace(targetModule); } @@ -204,7 +204,7 @@ public override bool Delete(JsValue property) public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var result = new List(); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { result.Capacity = _exports.Count; foreach (var export in _exports) diff --git a/Jint/Runtime/Modules/ModuleRecord.cs b/Jint/Runtime/Modules/ModuleRecord.cs index 21b27460c0..043efbb890 100644 --- a/Jint/Runtime/Modules/ModuleRecord.cs +++ b/Jint/Runtime/Modules/ModuleRecord.cs @@ -67,7 +67,7 @@ public static ObjectInstance GetModuleNamespace(ModuleRecord module) /// /// https://tc39.es/ecma262/#sec-modulenamespacecreate /// - private static ObjectInstance CreateModuleNamespace(ModuleRecord module, List unambiguousNames) + private static ModuleNamespace CreateModuleNamespace(ModuleRecord module, List unambiguousNames) { var m = new ModuleNamespace(module._engine, module, unambiguousNames); module._namespace = m; diff --git a/Jint/Runtime/Modules/SourceTextModuleRecord.cs b/Jint/Runtime/Modules/SourceTextModuleRecord.cs index f1c1881366..06d55650f8 100644 --- a/Jint/Runtime/Modules/SourceTextModuleRecord.cs +++ b/Jint/Runtime/Modules/SourceTextModuleRecord.cs @@ -107,7 +107,7 @@ public override List GetExportedNames(List exportSta for (var j = 0; j < starNames.Count; j++) { var n = starNames[j]; - if (!"default".Equals(n) && !exportedNames.Contains(n)) + if (!"default".Equals(n, StringComparison.Ordinal) && !exportedNames.Contains(n)) { exportedNames.Add(n); } @@ -127,7 +127,7 @@ internal override ResolvedBinding ResolveExport(string exportName, List(); + var declaredVarNames = new HashSet(StringComparer.Ordinal); if (varDeclarations != null) { var boundNames = new List(); @@ -320,7 +320,7 @@ protected override void InitializeEnvironment() env.CreateMutableBinding(fn, true); // TODO private scope var fo = realm.Intrinsics.Function.InstantiateFunctionObject(fd, env, privateEnv: null); - if (fn == "*default*") + if (string.Equals(fn, "*default*", StringComparison.Ordinal)) { fo.SetFunctionName("default"); } diff --git a/Jint/Runtime/OrderedDictionary.cs b/Jint/Runtime/OrderedDictionary.cs index 69182b9a78..8b2c43c78b 100644 --- a/Jint/Runtime/OrderedDictionary.cs +++ b/Jint/Runtime/OrderedDictionary.cs @@ -515,7 +515,7 @@ public void CopyTo(TValue[] array, int arrayIndex) { if (arrayIndex < 0) { - ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(arrayIndex), string.Format(TooSmall, 0)); + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(arrayIndex), string.Format(CultureInfo.InvariantCulture, TooSmall, 0)); } if (parent.dictionary.Count > array.Length - arrayIndex) { diff --git a/Jint/Runtime/References/Reference.cs b/Jint/Runtime/References/Reference.cs index eef5c5ba27..cdf88353e1 100644 --- a/Jint/Runtime/References/Reference.cs +++ b/Jint/Runtime/References/Reference.cs @@ -52,7 +52,7 @@ public bool Strict public bool HasPrimitiveBase { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_base._type & InternalTypes.Primitive) != 0; + get => (_base._type & InternalTypes.Primitive) != InternalTypes.None; } public bool IsUnresolvableReference @@ -68,7 +68,7 @@ public bool IsUnresolvableReference public bool IsPropertyReference { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_base._type & (InternalTypes.Primitive | InternalTypes.Object)) != 0; + get => (_base._type & (InternalTypes.Primitive | InternalTypes.Object)) != InternalTypes.None; } public JsValue ThisValue @@ -96,8 +96,8 @@ internal Reference Reassign(JsValue baseValue, JsValue name, bool strict, JsValu internal void AssertValid(Realm realm) { if (_strict - && (_base._type & InternalTypes.ObjectEnvironmentRecord) != 0 - && (_referencedName == CommonProperties.Eval || _referencedName == CommonProperties.Arguments)) + && (_base._type & InternalTypes.ObjectEnvironmentRecord) != InternalTypes.None + && (CommonProperties.Eval.Equals(_referencedName) || CommonProperties.Arguments.Equals(_referencedName))) { ExceptionHelper.ThrowSyntaxError(realm); } diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index ece06bf412..381f22bf4d 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -78,7 +78,7 @@ static TypeConverter() { for (var i = 0; i < intToString.Length; ++i) { - intToString[i] = i.ToString(); + intToString[i] = i.ToString(CultureInfo.InvariantCulture); } for (var i = 0; i < charToString.Length; ++i) @@ -800,7 +800,7 @@ internal static BigInteger BigIntegerModulo(BigInteger a, BigInteger n) if (value is JsString jsString) { - if (jsString.ToString() == "-0") + if (string.Equals(jsString.ToString(), "-0", StringComparison.Ordinal)) { return JsNumber.NegativeZero._value; } @@ -845,41 +845,46 @@ public static uint ToIndex(Realm realm, JsValue value) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(long i) { - return i >= 0 && i < intToString.Length - ? intToString[i] - : i.ToString(); + var temp = intToString; + return (ulong) i < (ulong) temp.Length + ? temp[i] + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(int i) { - return i >= 0 && i < intToString.Length - ? intToString[i] - : i.ToString(); + var temp = intToString; + return (uint) i < (uint) temp.Length + ? temp[i] + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(uint i) { - return i < (uint) intToString.Length - ? intToString[i] - : i.ToString(); + var temp = intToString; + return i < (uint) temp.Length + ? temp[i] + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(char c) { - return c >= 0 && c < charToString.Length - ? charToString[c] + var temp = charToString; + return (uint) c < (uint) temp.Length + ? temp[c] : c.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(ulong i) { - return i >= 0 && i < (ulong) intToString.Length - ? intToString[i] - : i.ToString(); + var temp = intToString; + return i < (ulong) temp.Length + ? temp[i] + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -920,7 +925,7 @@ internal static bool CanBeStringifiedAsLong(double d) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(BigInteger bigInteger) { - return bigInteger.ToString(); + return bigInteger.ToString(CultureInfo.InvariantCulture); } /// @@ -930,7 +935,7 @@ internal static string ToString(BigInteger bigInteger) public static JsValue ToPropertyKey(JsValue o) { const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName; - return (o._type & PropertyKeys) != 0 + return (o._type & PropertyKeys) != InternalTypes.None ? o : ToPropertyKeyNonString(o); } @@ -940,7 +945,7 @@ private static JsValue ToPropertyKeyNonString(JsValue o) { const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName; var primitive = ToPrimitive(o, Types.String); - return (primitive._type & PropertyKeys) != 0 + return (primitive._type & PropertyKeys) != InternalTypes.None ? primitive : ToStringNonString(primitive); } diff --git a/README.md b/README.md index edaaf5a8df..93113e6778 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Following features are supported in version 3.x. #### ECMAScript Stage 3 (no version yet) - ✔ Array Grouping - `Object.groupBy` and `Map.groupBy` +- ✔ Promise.withResolvers - ✔ ShadowRealm #### Other