From 95a6eb3ac50eaa599018a97265725e135197159c Mon Sep 17 00:00:00 2001 From: serpentfabric <41795094+serpentfabric@users.noreply.github.com> Date: Mon, 28 Dec 2020 14:36:59 -0500 Subject: [PATCH] jpath regex timeout support added for a single regex expression, global umbrella for all regex calls, and support for allowing regex calls to get compiled if necessary require users to set a global cancellation token themselves, to avoid a potential unmanaged memory leak, which cleans up the code a bunch and fixes the failing tests fix tests so cached regex's don't cause one test to fail using prior tests timeout... handle the global timeout of multitoken regex internally and test for it --- Src/Directory.Build.props | 2 + .../Linq/JsonPath/JPathExecuteTests.cs | 77 +++++++++++++++++- .../Newtonsoft.Json.Tests.csproj | 17 +++- Src/Newtonsoft.Json/Linq/JToken.cs | 81 +++++++++++++++++-- Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs | 9 ++- .../Linq/JsonPath/QueryExpression.cs | 20 ++++- Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 6 +- .../Utilities/MiscellaneousUtils.cs | 5 ++ 8 files changed, 199 insertions(+), 18 deletions(-) diff --git a/Src/Directory.Build.props b/Src/Directory.Build.props index 11ecc418..cfaf3745 100644 --- a/Src/Directory.Build.props +++ b/Src/Directory.Build.props @@ -22,5 +22,7 @@ 4.4.0 2.3.1 2.3.1 + 32.0.2 + 5.1.0 diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 69c1a815..6b3a512e 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -31,6 +31,13 @@ using System.Numerics; #endif using Newtonsoft.Json.Linq.JsonPath; using Newtonsoft.Json.Tests.Bson; +#if HAVE_REGEX_TIMEOUTS +using Bogus; +using Nito.AsyncEx; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Threading; +#endif #if DNXCORE50 using Xunit; using Test = Xunit.FactAttribute; @@ -44,7 +51,6 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json.Tests.Linq.JsonPath @@ -71,6 +77,75 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.AreEqual(jObj, dd); } +#if HAVE_REGEX_TIMEOUTS + [Test] + public void BacktrackingRegex_SingleMatch_TimeoutRespected() + { + var RegexBacktrackingPattern = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + + var faker = new Faker(); + var regexBacktrackingData = new JArray(); + + for (var i = 0; i < 1000; i++) + { + var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1"; + regexBacktrackingData.Add(new JObject(new JProperty("b", value))); + } + + Xunit.Assert.Throws(() => + { + var tokens = regexBacktrackingData.SelectTokens( + $"[?(@.b =~ /{RegexBacktrackingPattern}/)]", + errorWhenNoMatch: false, + singleRegexMatchTimeout: TimeSpan.FromSeconds(.01)).ToArray(); + }); + } + + [Test] + public void BacktrackingRegex_GlobalMatch_TimeoutRespected() + { + var faker = new Faker(); + var regexBacktrackingData = new JArray(); + for (var i = 0; i < 1000; i++) + { + var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1"; + regexBacktrackingData.Add(new JObject(new JProperty("b", value))); + } + var RegexBacktrackingPattern = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + var jpathExpression = $"[?(@.b =~ /{RegexBacktrackingPattern}/c)]"; + + var exceptionThrow = Xunit.Assert.ThrowsAny(() => + { + var tokens = regexBacktrackingData.SelectTokens( + jpathExpression, + errorWhenNoMatch: false, + singleRegexMatchTimeout: TimeSpan.FromSeconds(2), + globalRegexMatchTimeout: TimeSpan.FromSeconds(.5)).ToArray(); + }); + Assert.IsTrue(exceptionThrow is OperationCanceledException or RegexMatchTimeoutException); + } + + [Test] + public async Task BacktrackingRegexCanBeVerySlowWithoutTimeouts() + { + var RegexBacktrackingPattern = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + var faker = new Faker(); + var regexBacktrackingData = new JArray(); + + for (var i = 0; i < 10; i++) + { + var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1"; + regexBacktrackingData.Add(new JObject(new JProperty("b", value))); + } + + var selectTokenTask = Task.Run(() => regexBacktrackingData.SelectTokens($"[?(@.b =~ /{RegexBacktrackingPattern}/)]").ToArray()); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(.3)); + using var ctts = new CancellationTokenTaskSource(cts.Token); + var finishedTask = await Task.WhenAny(selectTokenTask, ctts.Task); + Assert.AreEqual(finishedTask, ctts.Task); + } +#endif + [Test] public void GreaterThanWithIntegerParameterAndStringValue() { diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 2d619e32..7989fe9f 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -2,7 +2,7 @@ net46;net40;net35;net20;net5.0;netcoreapp3.1;netcoreapp2.1 $(TestFrameworks) - 8.0 + 9.0 1.0 James Newton-King Newtonsoft @@ -76,6 +76,7 @@ + Json.NET Tests .NET 4.0 @@ -121,11 +122,14 @@ + + + Json.NET Tests .NET Standard 2.0 .NETStandard,Version=v2.0 - NETSTANDARD2_0;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;$(AdditionalConstants) + NETSTANDARD2_0;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -142,11 +146,13 @@ + + Json.NET Tests .NET Standard 1.3 .NETStandard,Version=v1.3 - NETSTANDARD1_3;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;$(AdditionalConstants) + NETSTANDARD1_3;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -162,10 +168,13 @@ + + + Json.NET Tests .NET Standard 1.0 .NETStandard,Version=v1.0 - NETSTANDARD1_0;DNXCORE50;PORTABLE;$(AdditionalConstants) + NETSTANDARD1_0;DNXCORE50;PORTABLE;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 79aa9124..252f846c 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -44,6 +44,7 @@ using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; +using System.Threading; #endif namespace Newtonsoft.Json.Linq @@ -485,7 +486,7 @@ namespace Newtonsoft.Json.Linq return (Array.IndexOf(validTypes, o.Type) != -1) || (nullable && (o.Type == JTokenType.Null || o.Type == JTokenType.Undefined)); } -#region Cast from operators + #region Cast from operators /// /// Performs an explicit conversion from to . /// @@ -1497,9 +1498,9 @@ namespace Newtonsoft.Json.Linq return ConvertUtils.ToBigInteger(v.Value); } #endif -#endregion + #endregion -#region Cast to operators + #region Cast to operators /// /// Performs an implicit conversion from to . /// @@ -1863,7 +1864,7 @@ namespace Newtonsoft.Json.Linq { return new JValue(value); } -#endregion + #endregion IEnumerator IEnumerable.GetEnumerator() { @@ -2336,6 +2337,35 @@ namespace Newtonsoft.Json.Linq return token; } +#if HAVE_REGEX_TIMEOUTS + /// + /// Selects a using a JSONPath expression. Selects the token that matches the object path. + /// + /// + /// A that contains a JSONPath expression. + /// + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// the time after which a single call to regex.ismatch must complete, default is forever + /// A . + public JToken? SelectToken(string path, bool errorWhenNoMatch, TimeSpan? singleMatchTimeout = default) + { + JPath p = new JPath(path, singleMatchTimeout); + + JToken? token = null; + foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch)) + { + if (token != null) + { + throw new JsonException("Path returned multiple tokens."); + } + + token = t; + } + + return token; + } +#endif + /// /// Selects a collection of elements using a JSONPath expression. /// @@ -2358,10 +2388,51 @@ namespace Newtonsoft.Json.Linq /// An of that contains the selected elements. public IEnumerable SelectTokens(string path, bool errorWhenNoMatch) { - JPath p = new JPath(path); + var p = new JPath(path); return p.Evaluate(this, this, errorWhenNoMatch); } +#if HAVE_REGEX_TIMEOUTS + /// + /// Selects a collection of elements using a JSONPath expression. + /// + /// + /// A that contains a JSONPath expression. + /// + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// + /// for every token that matches a jpath, the time a regex is permitted to run + /// against that token before timeout + /// + /// + /// the time this method should wait for the given jpath regex to match all tokens. + /// worst case expected execution time roughly globalRegexMatchTimeout + Min(singleRegexMatchTimeout, globalRegexMatchTimeout) + /// + /// if single call timeout exceeded" + /// An of that contains the selected elements. + public IEnumerable SelectTokens(string path, + bool errorWhenNoMatch, + TimeSpan? singleRegexMatchTimeout = default, + TimeSpan? globalRegexMatchTimeout = default) + { + var singleTimeout = singleRegexMatchTimeout ?? Timeout.InfiniteTimeSpan; + var globalTimeout = globalRegexMatchTimeout ?? Timeout.InfiniteTimeSpan; + if (globalTimeout != Timeout.InfiniteTimeSpan && globalTimeout < singleTimeout) + { + singleTimeout = globalTimeout; + } + + using var cts = new CancellationTokenSource(globalTimeout); + var p = new JPath(path, singleTimeout); + var results = p.Evaluate(this, this, errorWhenNoMatch); + foreach (var result in results) + { + cts.Token.ThrowIfCancellationRequested(); + yield return result; + } + } +#endif + #if HAVE_DYNAMIC /// /// Returns the responsible for binding operations performed on this object. diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index f18fb6c9..d5bc4e89 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -27,6 +27,8 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Text; +using System.Text.RegularExpressions; +using System.Threading; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Linq.JsonPath @@ -37,14 +39,16 @@ namespace Newtonsoft.Json.Linq.JsonPath private readonly string _expression; public List Filters { get; } + public readonly TimeSpan? RegexSingleMatchTimeout = default; private int _currentIndex; - public JPath(string expression) + public JPath(string expression, TimeSpan? regexSingleMatchTimeout = default) { ValidationUtils.ArgumentNotNull(expression, nameof(expression)); _expression = expression; Filters = new List(); + RegexSingleMatchTimeout = regexSingleMatchTimeout; ParseMain(); } @@ -509,7 +513,8 @@ namespace Newtonsoft.Json.Linq.JsonPath right = ParseSide(); } - BooleanQueryExpression booleanExpression = new BooleanQueryExpression(op, left, right); + + var booleanExpression = new BooleanQueryExpression(op, left, right, RegexSingleMatchTimeout); if (_expression[_currentIndex] == ')') { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs index f7bf1d5f..f9aa84d1 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Text.RegularExpressions; using System.Diagnostics; +using System.Threading; #if !HAVE_LINQ using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -83,11 +84,17 @@ namespace Newtonsoft.Json.Linq.JsonPath { public readonly object Left; public readonly object? Right; + public readonly TimeSpan? SingleMatchTimeout; - public BooleanQueryExpression(QueryOperator @operator, object left, object? right) : base(@operator) + public BooleanQueryExpression(QueryOperator @operator, + object left, + object? right, + TimeSpan? regexSingleMatchTimeout = default) + : base(@operator) { Left = left; Right = right; + SingleMatchTimeout = regexSingleMatchTimeout; } private IEnumerable GetResult(JToken root, JToken t, object? o) @@ -143,7 +150,7 @@ namespace Newtonsoft.Json.Linq.JsonPath switch (Operator) { case QueryOperator.RegexEquals: - if (RegexEquals(leftValue, rightValue)) + if (RegexEquals(leftValue, rightValue, SingleMatchTimeout)) { return true; } @@ -215,7 +222,9 @@ namespace Newtonsoft.Json.Linq.JsonPath return false; } - private static bool RegexEquals(JValue input, JValue pattern) + private static bool RegexEquals(JValue input, + JValue pattern, + TimeSpan? regexMatchTimeout = default) { if (input.Type != JTokenType.String || pattern.Type != JTokenType.String) { @@ -228,7 +237,12 @@ namespace Newtonsoft.Json.Linq.JsonPath string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1); string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1); +#if HAVE_REGEX_TIMEOUTS + var timeout = regexMatchTimeout ?? Regex.InfiniteMatchTimeout; + return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText), timeout); +#else return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText)); +#endif } internal static bool EqualsWithStringCoercion(JValue value, JValue queryValue) diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index ea973f48..f6cc6cc7 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -66,7 +66,7 @@ Json.NET .NET Standard 1.0 - NETSTANDARD1_0;PORTABLE;HAVE_ASYNC;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;$(AdditionalConstants) + NETSTANDARD1_0;PORTABLE;HAVE_ASYNC;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -74,10 +74,10 @@ Json.NET .NET Standard 1.3 - NETSTANDARD1_3;PORTABLE;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_SERIALIZATION;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_XML_DOCUMENT;HAVE_CONCURRENT_DICTIONARY;HAVE_ICONVERTIBLE;$(AdditionalConstants) + NETSTANDARD1_3;PORTABLE;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_SERIALIZATION;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_XML_DOCUMENT;HAVE_CONCURRENT_DICTIONARY;HAVE_ICONVERTIBLE;HAVE_REGEX_TIMEOUTS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) Json.NET .NET Standard 2.0 - NETSTANDARD2_0;HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;$(AdditionalConstants) + NETSTANDARD2_0;HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs index 137ec445..d61ea5d7 100644 --- a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs @@ -165,6 +165,11 @@ namespace Newtonsoft.Json.Utilities case 'x': options |= RegexOptions.ExplicitCapture; break; +#if HAVE_REGEX_TIMEOUTS && (NETSTANDARD1_3 || NETSTANDARD2_0) + case 'c': + options |= RegexOptions.Compiled; + break; +#endif } }