|
|
|
@ -301,7 +301,8 @@ namespace Google.Protobuf |
|
|
|
|
/// </summary> |
|
|
|
|
private string ReadString() |
|
|
|
|
{ |
|
|
|
|
var value = new StringBuilder(); |
|
|
|
|
//builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire |
|
|
|
|
var builder = StringBuilderCache.Acquire(); |
|
|
|
|
bool haveHighSurrogate = false; |
|
|
|
|
while (true) |
|
|
|
|
{ |
|
|
|
@ -316,7 +317,7 @@ namespace Google.Protobuf |
|
|
|
|
{ |
|
|
|
|
throw reader.CreateException("Invalid use of surrogate pair code units"); |
|
|
|
|
} |
|
|
|
|
return value.ToString(); |
|
|
|
|
return StringBuilderCache.GetStringAndRelease(builder); |
|
|
|
|
} |
|
|
|
|
if (c == '\\') |
|
|
|
|
{ |
|
|
|
@ -330,7 +331,7 @@ namespace Google.Protobuf |
|
|
|
|
throw reader.CreateException("Invalid use of surrogate pair code units"); |
|
|
|
|
} |
|
|
|
|
haveHighSurrogate = char.IsHighSurrogate(c); |
|
|
|
|
value.Append(c); |
|
|
|
|
builder.Append(c); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -408,7 +409,8 @@ namespace Google.Protobuf |
|
|
|
|
|
|
|
|
|
private double ReadNumber(char initialCharacter) |
|
|
|
|
{ |
|
|
|
|
StringBuilder builder = new StringBuilder(); |
|
|
|
|
//builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire |
|
|
|
|
var builder = StringBuilderCache.Acquire(); |
|
|
|
|
if (initialCharacter == '-') |
|
|
|
|
{ |
|
|
|
|
builder.Append("-"); |
|
|
|
@ -437,9 +439,10 @@ namespace Google.Protobuf |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: What exception should we throw if the value can't be represented as a double? |
|
|
|
|
var builderValue = StringBuilderCache.GetStringAndRelease(builder); |
|
|
|
|
try |
|
|
|
|
{ |
|
|
|
|
double result = double.Parse(builder.ToString(), |
|
|
|
|
double result = double.Parse(builderValue, |
|
|
|
|
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, |
|
|
|
|
CultureInfo.InvariantCulture); |
|
|
|
|
|
|
|
|
@ -447,14 +450,14 @@ namespace Google.Protobuf |
|
|
|
|
// For compatibility with other Protobuf implementations the tokenizer should still throw. |
|
|
|
|
if (double.IsInfinity(result)) |
|
|
|
|
{ |
|
|
|
|
throw reader.CreateException("Numeric value out of range: " + builder); |
|
|
|
|
throw reader.CreateException("Numeric value out of range: " + builderValue); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
catch (OverflowException) |
|
|
|
|
{ |
|
|
|
|
throw reader.CreateException("Numeric value out of range: " + builder); |
|
|
|
|
throw reader.CreateException("Numeric value out of range: " + builderValue); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -728,6 +731,59 @@ namespace Google.Protobuf |
|
|
|
|
return new InvalidJsonException(message); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Provide a cached reusable instance of stringbuilder per thread. |
|
|
|
|
/// Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/StringBuilderCache.cs |
|
|
|
|
/// </summary> |
|
|
|
|
private static class StringBuilderCache |
|
|
|
|
{ |
|
|
|
|
private const int MaxCachedStringBuilderSize = 360; |
|
|
|
|
private const int DefaultStringBuilderCapacity = 16; // == StringBuilder.DefaultCapacity |
|
|
|
|
|
|
|
|
|
[ThreadStatic] |
|
|
|
|
private static StringBuilder cachedInstance; |
|
|
|
|
|
|
|
|
|
/// <summary>Get a StringBuilder for the specified capacity.</summary> |
|
|
|
|
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks> |
|
|
|
|
public static StringBuilder Acquire(int capacity = DefaultStringBuilderCapacity) |
|
|
|
|
{ |
|
|
|
|
if (capacity <= MaxCachedStringBuilderSize) |
|
|
|
|
{ |
|
|
|
|
StringBuilder sb = cachedInstance; |
|
|
|
|
if (sb != null) |
|
|
|
|
{ |
|
|
|
|
// Avoid stringbuilder block fragmentation by getting a new StringBuilder |
|
|
|
|
// when the requested size is larger than the current capacity |
|
|
|
|
if (capacity <= sb.Capacity) |
|
|
|
|
{ |
|
|
|
|
cachedInstance = null; |
|
|
|
|
sb.Clear(); |
|
|
|
|
return sb; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new StringBuilder(capacity); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>Place the specified builder in the cache if it is not too big.</summary> |
|
|
|
|
private static void Release(StringBuilder sb) |
|
|
|
|
{ |
|
|
|
|
if (sb.Capacity <= MaxCachedStringBuilderSize) |
|
|
|
|
{ |
|
|
|
|
cachedInstance = cachedInstance?.Capacity >= sb.Capacity ? cachedInstance : sb; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary> |
|
|
|
|
public static string GetStringAndRelease(StringBuilder sb) |
|
|
|
|
{ |
|
|
|
|
string result = sb.ToString(); |
|
|
|
|
Release(sb); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|