Parse numeric strings as numbers again during conversions (#8681)

This commit is contained in:
Joel Sallow (/u/ta11ow) 2019-02-04 15:22:05 -05:00 committed by Andrew
parent d032cb2c87
commit 130298bfae
4 changed files with 113 additions and 20 deletions

View file

@ -2885,6 +2885,34 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Attempts to use Parser.ScanNumber to get the value of a numeric string.
/// </summary>
/// <param name="strToConvert">The string to convert to a number.</param>
/// <param name="resultType">The resulting value type to convert to.</param>
/// <param name="result">The resulting numeric value.</param>
/// <returns>
/// True if the parse succeeds, false if a parse exception arises.
/// In all other cases, an exception will be thrown.
/// </returns>
private static bool TryScanNumber(string strToConvert, Type resultType, out object result)
{
try
{
result = Convert.ChangeType(
Parser.ScanNumber(strToConvert, resultType, shouldTryCoercion: false),
resultType,
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
return true;
}
catch (Exception)
{
// Parse or convert failed
result = null;
return false;
}
}
private static object ConvertStringToInteger(object valueToConvert,
Type resultType,
bool recursion,
@ -2907,7 +2935,14 @@ namespace System.Management.Automation
TypeConverter integerConverter = LanguagePrimitives.GetIntegerSystemConverter(resultType);
try
{
return integerConverter.ConvertFrom(strToConvert);
if (TryScanNumber(strToConvert, resultType, out object result))
{
return result;
}
else
{
return integerConverter.ConvertFrom(strToConvert);
}
}
catch (Exception e)
{
@ -2946,8 +2981,9 @@ namespace System.Management.Automation
TypeTable backupTable)
{
Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string");
var strToConvert = valueToConvert as string;
if (((string)valueToConvert).Length == 0)
if (strToConvert.Length == 0)
{
typeConversion.WriteLine("Returning numeric zero.");
// This is not wrapped in a try/catch because it can't fail.
@ -2957,8 +2993,15 @@ namespace System.Management.Automation
typeConversion.WriteLine("Converting to decimal.");
try
{
return Convert.ChangeType(valueToConvert, resultType,
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes");
if (TryScanNumber(strToConvert, resultType, out object result))
{
return result;
}
else
{
return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat);
}
}
catch (Exception e)
{
@ -2967,7 +3010,7 @@ namespace System.Management.Automation
{
try
{
return ConvertNumericThroughDouble(valueToConvert, resultType);
return ConvertNumericThroughDouble(strToConvert, resultType);
}
catch (Exception ex)
{
@ -2977,7 +3020,7 @@ namespace System.Management.Automation
throw new PSInvalidCastException("InvalidCastFromStringToDecimal", e,
ExtendedTypeSystem.InvalidCastExceptionWithInnerException,
valueToConvert.ToString(), resultType.ToString(), e.Message);
strToConvert, resultType.ToString(), e.Message);
}
}
@ -2989,8 +3032,9 @@ namespace System.Management.Automation
TypeTable backupTable)
{
Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string");
var strToConvert = valueToConvert as string;
if (((string)valueToConvert).Length == 0)
if (strToConvert.Length == 0)
{
typeConversion.WriteLine("Returning numeric zero.");
// This is not wrapped in a try/catch because it can't fail.
@ -3000,15 +3044,23 @@ namespace System.Management.Automation
typeConversion.WriteLine("Converting to double or single.");
try
{
return Convert.ChangeType(valueToConvert, resultType,
System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes");
if (TryScanNumber(strToConvert, resultType, out object result))
{
return result;
}
else
{
return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat);
}
}
catch (Exception e)
{
typeConversion.WriteLine("Exception converting to double or single: \"{0}\".", e.Message);
throw new PSInvalidCastException("InvalidCastFromStringToDoubleOrSingle", e,
ExtendedTypeSystem.InvalidCastExceptionWithInnerException,
valueToConvert.ToString(), resultType.ToString(), e.Message);
strToConvert, resultType.ToString(), e.Message);
}
}

View file

@ -225,7 +225,7 @@ namespace System.Management.Automation.Language
}
// This helper routine is used from the runtime to convert a string to a number.
internal static object ScanNumber(string str, Type toType)
internal static object ScanNumber(string str, Type toType, bool shouldTryCoercion = true)
{
str = str.Trim();
if (str.Length == 0)
@ -249,11 +249,17 @@ namespace System.Management.Automation.Language
if (token == null || !tokenizer.IsAtEndOfScript(token.Extent))
{
// We call ConvertTo, primarily because we expect it will throw an exception,
// but it's possible it could succeed, e.g. if the string had commas, our lexer
// will fail, but Convert.ChangeType could succeed.
return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);
if (shouldTryCoercion)
{
// We call ConvertTo, primarily because we expect it will throw an exception,
// but it's possible it could succeed, e.g. if the string had commas, our lexer
// will fail, but Convert.ChangeType could succeed.
return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);
}
else
{
throw new ParseException();
}
}
return token.Value;

View file

@ -477,7 +477,7 @@ namespace System.Management.Automation.Language
return BindingRestrictions.GetExpressionRestriction(
Expression.Block(
new[] {tmp},
new[] { tmp },
Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS),
test));
}
@ -2587,7 +2587,11 @@ namespace System.Management.Automation.Language
toType = typeof(int);
}
return Expression.Call(CachedReflectionInfo.Parser_ScanNumber, expr.Cast(typeof(string)), Expression.Constant(toType, typeof(Type)));
return Expression.Call(
CachedReflectionInfo.Parser_ScanNumber,
expr.Cast(typeof(string)),
Expression.Constant(toType, typeof(Type)),
Expression.Constant(true));
}
private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject arg, Type targetType)
@ -7414,7 +7418,7 @@ namespace System.Management.Automation.Language
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
{
var enumerator = (new object[] {obj}).GetEnumerator();
var enumerator = (new object[] { obj }).GetEnumerator();
switch (args.Length)
{
case 1:
@ -7430,7 +7434,7 @@ namespace System.Management.Automation.Language
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
{
var enumerator = (new object[] {obj}).GetEnumerator();
var enumerator = (new object[] { obj }).GetEnumerator();
return EnumerableOps.ForEach(enumerator, args[0], Utils.EmptyArray<object>());
}

View file

@ -486,4 +486,35 @@ Describe 'method conversion' -Tags 'CI' {
$n = [N]::new()
{ [System.Management.Automation.LanguagePrimitives]::ConvertTo($n.GetC, [Func[[int], [object]]]) } | Should -Throw -ErrorId "PSInvalidCastException"
}
$TestCases = @(
@{ Number = "100y"; Value = "100"; Type = [int] }
@{ Number = "100uy"; Value = "100"; Type = [double] }
@{ Number = "1200u"; Value = "1200"; Type = [short] }
@{ Number = "1200L"; Value = "1200"; Type = [int] }
@{ Number = "127ul"; Value = "127"; Type = [ulong] }
@{ Number = "127d"; Value = "127"; Type = [byte] }
@{ Number = "127s"; Value = "127"; Type = [sbyte] }
@{ Number = "127y"; Value = "127"; Type = [uint] }
)
It "Correctly casts <Number> to value <Value> as type <Type>" -TestCases $TestCases {
param($Number, $Value, $Type)
$Result = $Number -as $Type
$Result | Should -Be $Value
$Result | Should -BeOfType $Type
}
$TestCases = @(
@{ Number = "200y" }
@{ Number = "300uy" }
@{ Number = "70000us" }
@{ Number = "40000s" }
)
It "Fails to cast invalid PowerShell-Style suffixed numeral <Number>" -TestCases $TestCases {
param($Number)
$Result = $Number -as [int]
$Result | Should -BeNullOrEmpty
}
}