Skip to content

Commit

Permalink
Fix some issues with missing casts for overload resolution and for bo…
Browse files Browse the repository at this point in the history
…xing in attributes.
  • Loading branch information
dgrunwald committed Dec 4, 2016
1 parent 6816419 commit f0a0ba8
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 90 deletions.
21 changes: 11 additions & 10 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,17 @@ public AstType ConvertType(IType type)
public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr)
{
var expr = astBuilder.ConvertConstantValue(rr);
var pe = expr as PrimitiveExpression;
if (pe != null) {
if (pe.Value is sbyte)
expr = expr.CastTo(new PrimitiveType("sbyte"));
else if (pe.Value is byte)
expr = expr.CastTo(new PrimitiveType("byte"));
else if (pe.Value is short)
expr = expr.CastTo(new PrimitiveType("short"));
else if (pe.Value is ushort)
expr = expr.CastTo(new PrimitiveType("ushort"));
if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
expr = expr.CastTo(ConvertType(rr.Type));
} else {
switch (rr.Type.GetDefinition()?.KnownTypeCode) {
case KnownTypeCode.SByte:
case KnownTypeCode.Byte:
case KnownTypeCode.Int16:
case KnownTypeCode.UInt16:
expr = expr.CastTo(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)));
break;
}
}
var exprRR = expr.Annotation<ResolveResult>();
if (exprRR == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,17 +368,36 @@ static string ConvertChar(char ch)
return "\\t";
case '\v':
return "\\v";
case ' ':
case '_':
case '`':
case '^':
// ASCII characters we allow directly in the output even though we don't use
// other Unicode characters of the same category.
return ch.ToString();
default:
if (char.IsControl(ch) || char.IsSurrogate(ch) ||
// print all uncommon white spaces as numbers
(char.IsWhiteSpace(ch) && ch != ' ')) {
return "\\u" + ((int)ch).ToString("x4");
} else {
return ch.ToString();
switch (char.GetUnicodeCategory(ch)) {
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.EnclosingMark:
case UnicodeCategory.LineSeparator:
case UnicodeCategory.ParagraphSeparator:
case UnicodeCategory.Control:
case UnicodeCategory.Format:
case UnicodeCategory.Surrogate:
case UnicodeCategory.PrivateUse:
case UnicodeCategory.ConnectorPunctuation:
case UnicodeCategory.ModifierSymbol:
case UnicodeCategory.OtherNotAssigned:
case UnicodeCategory.SpaceSeparator:
return "\\u" + ((int)ch).ToString("x4");
default:
return ch.ToString();
}
}
}

/// <summary>
/// Converts special characters to escape sequences within the given string.
/// </summary>
Expand Down
69 changes: 58 additions & 11 deletions ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,29 +428,36 @@ public Attribute ConvertAttribute(IAttribute attribute)
return attr;
}
#endregion

#region Convert Constant Value
/// <summary>
/// Creates an Expression for the given constant value.
///
/// Note: the returned expression is not necessarily of the desired type.
/// However, the returned expression will always be implicitly convertible to <c>rr.Type</c>,
/// and will have the correct value when being converted in this way.
/// </summary>
public Expression ConvertConstantValue(ResolveResult rr)
{
if (rr == null)
throw new ArgumentNullException("rr");
if (rr is ConversionResolveResult) {
bool isBoxing = false;
if (rr is ConversionResolveResult crr) {
// unpack ConversionResolveResult if necessary
// (e.g. a boxing conversion or string->object reference conversion)
rr = ((ConversionResolveResult)rr).Input;
rr = crr.Input;
isBoxing = crr.Conversion.IsBoxingConversion;
}

if (rr is TypeOfResolveResult) {
var expr = new TypeOfExpression(ConvertType(((TypeOfResolveResult)rr).ReferencedType));
if (AddResolveResultAnnotations)
expr.AddAnnotation(rr);
return expr;
} else if (rr is ArrayCreateResolveResult) {
ArrayCreateResolveResult acrr = (ArrayCreateResolveResult)rr;
} else if (rr is ArrayCreateResolveResult acrr) {
ArrayCreateExpression ace = new ArrayCreateExpression();
ace.Type = ConvertType(acrr.Type);
ComposedType composedType = ace.Type as ComposedType;
if (composedType != null) {
if (ace.Type is ComposedType composedType) {
composedType.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers);
if (!composedType.HasNullableSpecifier && composedType.PointerRank == 0)
ace.Type = composedType.BaseType;
Expand All @@ -469,12 +476,30 @@ public Expression ConvertConstantValue(ResolveResult rr)
ace.AddAnnotation(rr);
return ace;
} else if (rr.IsCompileTimeConstant) {
return ConvertConstantValue(rr.Type, rr.ConstantValue);
var expr = ConvertConstantValue(rr.Type, rr.ConstantValue);
if (isBoxing && IsSmallInteger(rr.Type)) {
// C# does not have small integer literal types.
// We need to add a cast so that the integer literal gets boxed as the correct type.
expr = new CastExpression(ConvertType(rr.Type), expr);
if (AddResolveResultAnnotations)
expr.AddAnnotation(rr);
}
return expr;
} else {
return new ErrorExpression();
}
}


/// <summary>
/// Creates an Expression for the given constant value.
///
/// Note: the returned expression is not necessarily of the specified <paramref name="type"/>:
/// For example, <c>ConvertConstantValue(typeof(string), null)</c> results in a <c>null</c> literal,
/// without a cast to <c>string</c>.
/// Similarly, <c>ConvertConstantValue(typeof(short), 1)</c> results in the literal <c>1</c>,
/// which is of type <c>int</c>.
/// However, the returned expression will always be implicitly convertible to <paramref name="type"/>.
/// </summary>
public Expression ConvertConstantValue(IType type, object constantValue)
{
if (type == null)
Expand All @@ -494,10 +519,32 @@ public Expression ConvertConstantValue(IType type, object constantValue)
} else if (type.Kind == TypeKind.Enum) {
return ConvertEnumValue(type, (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false));
} else {
return new PrimitiveExpression(constantValue);
if (IsSmallInteger(type)) {
// C# does not have integer literals of small integer types,
// use `int` literal instead.
constantValue = CSharpPrimitiveCast.Cast(TypeCode.Int32, constantValue, false);
type = type.GetDefinition().Compilation.FindType(KnownTypeCode.Int32);
}
var expr = new PrimitiveExpression(constantValue);
if (AddResolveResultAnnotations)
expr.AddAnnotation(new ConstantResolveResult(type, constantValue));
return expr;
}
}


bool IsSmallInteger(IType type)
{
switch (type.GetDefinition()?.KnownTypeCode) {
case KnownTypeCode.Byte:
case KnownTypeCode.SByte:
case KnownTypeCode.Int16:
case KnownTypeCode.UInt16:
return true;
default:
return false;
}
}

bool IsFlagsEnum(ITypeDefinition type)
{
IType flagsAttributeType = type.Compilation.FindType(typeof(System.FlagsAttribute));
Expand Down
52 changes: 0 additions & 52 deletions ICSharpCode.Decompiler/Tests/CallOverloadedMethod.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ICSharpCode.Decompiler.Tests.CustomAttributes
public class CustomAttributeTests : DecompilerTestBase
{
[Test]
[Ignore("Needs event pattern detection, which waits for improved control flow detection (loop conditions)")]
[Ignore("Needs event pattern detection, which waits for improved control flow detection (do-while loops)")]
public void CustomAttributeSamples()
{
ValidateFileRoundtrip(@"CustomAttributes/S_CustomAttributeSamples.cs");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,37 @@ public static void ObsoletedMethod()
StringComparison.Ordinal,
StringComparison.CurrentCulture
})]
public static void ArrayAsAttribute1()
public static void EnumArray()
{
}
// Boxing of each array element
[My(new object[] {
StringComparison.Ordinal,
StringComparison.CurrentCulture
})]
public static void ArrayAsAttribute2()
public static void BoxedEnumArray()
{
}
[My(new object[] {
1,
2u,
3L,
4uL,
// Ensure the decompiler doesn't leave these casts out:
(short)5,
(ushort)6,
(byte)7,
(sbyte)8,
'a',
'\0',
'\ufeff',
'\uffff',
1f,
2.0,
"text",
null
})]
public static void BoxedLiteralsArray()
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,47 @@ static class OverloadResolution
{
static void Main()
{
CallOverloadedMethod();
TestBoxing();
CallIssue180();
CallExtensionMethod();
TestIssue180();
TestExtensionMethod();
}

#region Simple Overloaded Method
static void CallOverloadedMethod()
{
OverloadedMethod("(string)");
OverloadedMethod((object)"(object)");
OverloadedMethod(5);
OverloadedMethod((object)5);
OverloadedMethod(5L);
OverloadedMethod((object)null);
OverloadedMethod((string)null);
OverloadedMethod((int?)null);
}

static void OverloadedMethod(object a)
{
Console.WriteLine("OverloadedMethod(object={0}, object.GetType()={1})", a, a != null ? a.GetType().Name : "null");
}

static void OverloadedMethod(int? a)
{
Console.WriteLine("OverloadedMethod(int?={0})", a);
}

static void OverloadedMethod(string a)
{
Console.WriteLine("OverloadedMethod(string={0})", a);
}
#endregion

#region Boxing
static void TestBoxing()
{
Print(1);
Print((ushort)1);
Print(null);
}

static void Print(object obj)
Expand All @@ -46,8 +78,10 @@ static void Print(object obj)
else
Console.WriteLine("{0}: {1}", obj.GetType().Name, obj);
}
#endregion

static void CallIssue180()
#region #180
static void TestIssue180()
{
Issue180(null);
Issue180(new object[1]);
Expand All @@ -63,8 +97,10 @@ static void Issue180(params object[] objs)
{
Console.WriteLine("#180: params object[]");
}
#endregion

static void CallExtensionMethod()
#region Extension Method
static void TestExtensionMethod()
{
new object().ExtensionMethod();
ExtensionMethod(null); // issue #167
Expand All @@ -74,5 +110,6 @@ public static void ExtensionMethod(this object obj)
{
Console.WriteLine("ExtensionMethod(obj)");
}
#endregion
}
}
2 changes: 1 addition & 1 deletion clean.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
git submodule update --init || exit /b 1
)
@setlocal enabledelayedexpansion
set MSBUILD=
@set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"
Expand Down
2 changes: 1 addition & 1 deletion debugbuild.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
git submodule update --init || exit /b 1
)
@setlocal enabledelayedexpansion
set MSBUILD=
@set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"
Expand Down
2 changes: 1 addition & 1 deletion releasebuild.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
git submodule update --init || exit /b 1
)
@setlocal enabledelayedexpansion
set MSBUILD=
@set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"
Expand Down

0 comments on commit f0a0ba8

Please sign in to comment.