Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions src/System.Management.Automation/engine/LanguagePrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,34 @@ private static Uri ConvertStringToUri(object valueToConvert,
}
}

/// <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,
Expand All @@ -2907,7 +2935,14 @@ private static object ConvertStringToInteger(object valueToConvert,
TypeConverter integerConverter = LanguagePrimitives.GetIntegerSystemConverter(resultType);
try
{
return integerConverter.ConvertFrom(strToConvert);
if (TryScanNumber(strToConvert, resultType, out object result))
{
return result;
}
else
{
return integerConverter.ConvertFrom(strToConvert);
Comment thread
iSazonov marked this conversation as resolved.
}
}
catch (Exception e)
{
Expand Down Expand Up @@ -2946,8 +2981,9 @@ private static object ConvertStringToDecimal(object valueToConvert,
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.
Expand All @@ -2957,8 +2993,15 @@ private static object ConvertStringToDecimal(object valueToConvert,
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)
{
Expand All @@ -2967,7 +3010,7 @@ private static object ConvertStringToDecimal(object valueToConvert,
{
try
{
return ConvertNumericThroughDouble(valueToConvert, resultType);
return ConvertNumericThroughDouble(strToConvert, resultType);
}
catch (Exception ex)
{
Expand All @@ -2977,7 +3020,7 @@ private static object ConvertStringToDecimal(object valueToConvert,

throw new PSInvalidCastException("InvalidCastFromStringToDecimal", e,
ExtendedTypeSystem.InvalidCastExceptionWithInnerException,
valueToConvert.ToString(), resultType.ToString(), e.Message);
strToConvert, resultType.ToString(), e.Message);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please format code.

}
}

Expand All @@ -2989,8 +3032,9 @@ private static object ConvertStringToReal(object valueToConvert,
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.
Expand All @@ -3000,15 +3044,23 @@ private static object ConvertStringToReal(object valueToConvert,
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please format code.

}
}

Expand Down
18 changes: 12 additions & 6 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private ScriptBlockAst ParseTask(string fileName, string input, List<Token> toke
}

// 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)
Expand All @@ -249,11 +249,17 @@ internal static object ScanNumber(string str, Type toType)

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ internal static BindingRestrictions GetLanguageModeCheckIfHasEverUsedConstrained

return BindingRestrictions.GetExpressionRestriction(
Expression.Block(
new[] {tmp},
new[] { tmp },
Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS),
test));
}
Expand Down Expand Up @@ -2587,7 +2587,11 @@ internal static Expression ConvertStringToNumber(Expression expr, Type toType)
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)
Expand Down Expand Up @@ -7414,7 +7418,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
// ([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:
Expand All @@ -7430,7 +7434,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object

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>());
}

Expand Down
31 changes: 31 additions & 0 deletions test/powershell/Language/Parser/Conversions.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}