From 130298bfae79dc4213b11f8b20fc172653243ab0 Mon Sep 17 00:00:00 2001
From: "Joel Sallow (/u/ta11ow)" <32407840+vexx32@users.noreply.github.com>
Date: Mon, 4 Feb 2019 15:22:05 -0500
Subject: [PATCH] Parse numeric strings as numbers again during conversions
(#8681)
---
.../engine/LanguagePrimitives.cs | 72 ++++++++++++++++---
.../engine/parser/Parser.cs | 18 +++--
.../engine/runtime/Binding/Binders.cs | 12 ++--
.../Language/Parser/Conversions.Tests.ps1 | 31 ++++++++
4 files changed, 113 insertions(+), 20 deletions(-)
diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs
index 253cfd4a6..93ecdbeff 100644
--- a/src/System.Management.Automation/engine/LanguagePrimitives.cs
+++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs
@@ -2885,6 +2885,34 @@ namespace System.Management.Automation
}
}
+ ///
+ /// Attempts to use Parser.ScanNumber to get the value of a numeric string.
+ ///
+ /// The string to convert to a number.
+ /// The resulting value type to convert to.
+ /// The resulting numeric value.
+ ///
+ /// True if the parse succeeds, false if a parse exception arises.
+ /// In all other cases, an exception will be thrown.
+ ///
+ 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);
}
}
diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs
index 7af775f56..273c4d561 100644
--- a/src/System.Management.Automation/engine/parser/Parser.cs
+++ b/src/System.Management.Automation/engine/parser/Parser.cs
@@ -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;
diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
index d78de553e..81156098b 100644
--- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
+++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
@@ -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