Browse Source

Fix some issues with missing casts for overload resolution and for boxing in attributes.

pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
f0a0ba8ac0
  1. 21
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 33
      ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterOutputFormatter.cs
  3. 69
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  4. 52
      ICSharpCode.Decompiler/Tests/CallOverloadedMethod.cs
  5. 2
      ICSharpCode.Decompiler/Tests/CustomAttributes/CustomAttributeTests.cs
  6. 26
      ICSharpCode.Decompiler/Tests/CustomAttributes/S_CustomAttributes.cs
  7. 45
      ICSharpCode.Decompiler/Tests/TestCases/Correctness/OverloadResolution.cs
  8. 2
      clean.bat
  9. 2
      debugbuild.bat
  10. 2
      releasebuild.bat

21
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -87,16 +87,17 @@ namespace ICSharpCode.Decompiler.CSharp
public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr) public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr)
{ {
var expr = astBuilder.ConvertConstantValue(rr); var expr = astBuilder.ConvertConstantValue(rr);
var pe = expr as PrimitiveExpression; if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
if (pe != null) { expr = expr.CastTo(ConvertType(rr.Type));
if (pe.Value is sbyte) } else {
expr = expr.CastTo(new PrimitiveType("sbyte")); switch (rr.Type.GetDefinition()?.KnownTypeCode) {
else if (pe.Value is byte) case KnownTypeCode.SByte:
expr = expr.CastTo(new PrimitiveType("byte")); case KnownTypeCode.Byte:
else if (pe.Value is short) case KnownTypeCode.Int16:
expr = expr.CastTo(new PrimitiveType("short")); case KnownTypeCode.UInt16:
else if (pe.Value is ushort) expr = expr.CastTo(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)));
expr = expr.CastTo(new PrimitiveType("ushort")); break;
}
} }
var exprRR = expr.Annotation<ResolveResult>(); var exprRR = expr.Annotation<ResolveResult>();
if (exprRR == null) { if (exprRR == null) {

33
ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterOutputFormatter.cs

@ -368,17 +368,36 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return "\\t"; return "\\t";
case '\v': case '\v':
return "\\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: default:
if (char.IsControl(ch) || char.IsSurrogate(ch) || switch (char.GetUnicodeCategory(ch)) {
// print all uncommon white spaces as numbers case UnicodeCategory.ModifierLetter:
(char.IsWhiteSpace(ch) && ch != ' ')) { case UnicodeCategory.NonSpacingMark:
return "\\u" + ((int)ch).ToString("x4"); case UnicodeCategory.SpacingCombiningMark:
} else { case UnicodeCategory.EnclosingMark:
return ch.ToString(); 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> /// <summary>
/// Converts special characters to escape sequences within the given string. /// Converts special characters to escape sequences within the given string.
/// </summary> /// </summary>

69
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -428,16 +428,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return attr; return attr;
} }
#endregion #endregion
#region Convert Constant Value #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) public Expression ConvertConstantValue(ResolveResult rr)
{ {
if (rr == null) if (rr == null)
throw new ArgumentNullException("rr"); throw new ArgumentNullException("rr");
if (rr is ConversionResolveResult) { bool isBoxing = false;
if (rr is ConversionResolveResult crr) {
// unpack ConversionResolveResult if necessary // unpack ConversionResolveResult if necessary
// (e.g. a boxing conversion or string->object reference conversion) // (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) { if (rr is TypeOfResolveResult) {
@ -445,12 +454,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
if (AddResolveResultAnnotations) if (AddResolveResultAnnotations)
expr.AddAnnotation(rr); expr.AddAnnotation(rr);
return expr; return expr;
} else if (rr is ArrayCreateResolveResult) { } else if (rr is ArrayCreateResolveResult acrr) {
ArrayCreateResolveResult acrr = (ArrayCreateResolveResult)rr;
ArrayCreateExpression ace = new ArrayCreateExpression(); ArrayCreateExpression ace = new ArrayCreateExpression();
ace.Type = ConvertType(acrr.Type); ace.Type = ConvertType(acrr.Type);
ComposedType composedType = ace.Type as ComposedType; if (ace.Type is ComposedType composedType) {
if (composedType != null) {
composedType.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); composedType.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers);
if (!composedType.HasNullableSpecifier && composedType.PointerRank == 0) if (!composedType.HasNullableSpecifier && composedType.PointerRank == 0)
ace.Type = composedType.BaseType; ace.Type = composedType.BaseType;
@ -469,12 +476,30 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ace.AddAnnotation(rr); ace.AddAnnotation(rr);
return ace; return ace;
} else if (rr.IsCompileTimeConstant) { } 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 { } else {
return new ErrorExpression(); 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) public Expression ConvertConstantValue(IType type, object constantValue)
{ {
if (type == null) if (type == null)
@ -494,10 +519,32 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} else if (type.Kind == TypeKind.Enum) { } else if (type.Kind == TypeKind.Enum) {
return ConvertEnumValue(type, (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false)); return ConvertEnumValue(type, (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false));
} else { } 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) bool IsFlagsEnum(ITypeDefinition type)
{ {
IType flagsAttributeType = type.Compilation.FindType(typeof(System.FlagsAttribute)); IType flagsAttributeType = type.Compilation.FindType(typeof(System.FlagsAttribute));

52
ICSharpCode.Decompiler/Tests/CallOverloadedMethod.cs

@ -1,52 +0,0 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
public class CallOverloadedMethod
{
public void OverloadedMethod(object a)
{
}
public void OverloadedMethod(int? a)
{
}
public void OverloadedMethod(string a)
{
}
public void Call()
{
this.OverloadedMethod("(string)");
this.OverloadedMethod((object)"(object)");
this.OverloadedMethod(5);
this.OverloadedMethod((object)5);
this.OverloadedMethod(5L);
this.OverloadedMethod((object)null);
this.OverloadedMethod((string)null);
this.OverloadedMethod((int?)null);
}
public void CallMethodUsingInterface(List<int> list)
{
((ICollection<int>)list).Clear();
}
}

2
ICSharpCode.Decompiler/Tests/CustomAttributes/CustomAttributeTests.cs

@ -6,7 +6,7 @@ namespace ICSharpCode.Decompiler.Tests.CustomAttributes
public class CustomAttributeTests : DecompilerTestBase public class CustomAttributeTests : DecompilerTestBase
{ {
[Test] [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() public void CustomAttributeSamples()
{ {
ValidateFileRoundtrip(@"CustomAttributes/S_CustomAttributeSamples.cs"); ValidateFileRoundtrip(@"CustomAttributes/S_CustomAttributeSamples.cs");

26
ICSharpCode.Decompiler/Tests/CustomAttributes/S_CustomAttributes.cs

@ -63,7 +63,7 @@ namespace aa
StringComparison.Ordinal, StringComparison.Ordinal,
StringComparison.CurrentCulture StringComparison.CurrentCulture
})] })]
public static void ArrayAsAttribute1() public static void EnumArray()
{ {
} }
// Boxing of each array element // Boxing of each array element
@ -71,7 +71,29 @@ namespace aa
StringComparison.Ordinal, StringComparison.Ordinal,
StringComparison.CurrentCulture 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()
{ {
} }
} }

45
ICSharpCode.Decompiler/Tests/TestCases/Correctness/OverloadResolution.cs

@ -28,15 +28,47 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
static void Main() static void Main()
{ {
CallOverloadedMethod();
TestBoxing(); TestBoxing();
CallIssue180(); TestIssue180();
CallExtensionMethod(); 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() static void TestBoxing()
{ {
Print(1); Print(1);
Print((ushort)1); Print((ushort)1);
Print(null);
} }
static void Print(object obj) static void Print(object obj)
@ -46,8 +78,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
else else
Console.WriteLine("{0}: {1}", obj.GetType().Name, obj); Console.WriteLine("{0}: {1}", obj.GetType().Name, obj);
} }
#endregion
static void CallIssue180() #region #180
static void TestIssue180()
{ {
Issue180(null); Issue180(null);
Issue180(new object[1]); Issue180(new object[1]);
@ -63,8 +97,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
Console.WriteLine("#180: params object[]"); Console.WriteLine("#180: params object[]");
} }
#endregion
static void CallExtensionMethod() #region Extension Method
static void TestExtensionMethod()
{ {
new object().ExtensionMethod(); new object().ExtensionMethod();
ExtensionMethod(null); // issue #167 ExtensionMethod(null); // issue #167
@ -74,5 +110,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
Console.WriteLine("ExtensionMethod(obj)"); Console.WriteLine("ExtensionMethod(obj)");
} }
#endregion
} }
} }

2
clean.bat

@ -2,7 +2,7 @@
git submodule update --init || exit /b 1 git submodule update --init || exit /b 1
) )
@setlocal enabledelayedexpansion @setlocal enabledelayedexpansion
set MSBUILD= @set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do ( @for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" ( @if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe" @set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"

2
debugbuild.bat

@ -2,7 +2,7 @@
git submodule update --init || exit /b 1 git submodule update --init || exit /b 1
) )
@setlocal enabledelayedexpansion @setlocal enabledelayedexpansion
set MSBUILD= @set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do ( @for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" ( @if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe" @set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"

2
releasebuild.bat

@ -2,7 +2,7 @@
git submodule update --init || exit /b 1 git submodule update --init || exit /b 1
) )
@setlocal enabledelayedexpansion @setlocal enabledelayedexpansion
set MSBUILD= @set MSBUILD=
@for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do ( @for /D %%M in ("%ProgramFiles(x86)%\Microsoft Visual Studio\2017"\*) do (
@if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" ( @if exist "%%M\MSBuild\15.0\Bin\MSBuild.exe" (
@set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe" @set "MSBUILD=%%M\MSBuild\15.0\Bin\MSBuild.exe"

Loading…
Cancel
Save