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
9 changes: 5 additions & 4 deletions .github/workflows/build-ilspy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
run: msbuild ILSpy.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform /m

- name: Execute unit tests
run: dotnet test --logger "junit;LogFileName=${{ matrix.configuration }}.xml" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
run: dotnet test --logger "trx;LogFileName=${{ matrix.configuration }}.trx" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
env:
Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net8.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll
Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net8.0-windows\ILSpy.Tests.dll
Expand All @@ -71,13 +71,14 @@ jobs:
if: success() || failure()
with:
name: test-results-${{ matrix.configuration }}
path: 'test-results/${{ matrix.configuration }}.xml'
path: 'test-results/${{ matrix.configuration }}.trx'

- name: Create Test Report
uses: test-summary/action@v2
uses: icsharpcode/test-summary-action@dist
if: always()
with:
paths: "test-results/${{ matrix.configuration }}.xml"
paths: "test-results/${{ matrix.configuration }}.trx"
folded: true

- name: Format check
run: dotnet-format whitespace --verify-no-changes --verbosity detailed ILSpy.sln
Expand Down
8 changes: 6 additions & 2 deletions ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -313,6 +311,12 @@ public async Task Jmp()
await RunIL("Jmp.il");
}

[Test]
public async Task NonGenericConstrainedCallVirt()
{
await RunIL("NonGenericConstrainedCallVirt.il", CompilerOptions.UseRoslynLatest);
}

[Test]
public async Task StackTests()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<DefineConstants>ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>

<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>
Expand Down Expand Up @@ -95,6 +95,7 @@
<None Include="TestCases\ILPretty\Issue2260SwitchString.il" />
<None Include="TestCases\ILPretty\Issue3442.il" />
<None Include="TestCases\ILPretty\MonoFixed.il" />
<None Include="TestCases\Correctness\NonGenericConstrainedCallVirt.il" />
<None Include="TestCases\ILPretty\UnknownTypes.cs" />
<None Include="TestCases\ILPretty\UnknownTypes.il" />
<None Include="TestCases\ILPretty\EvalOrder.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}

.assembly _
{
.hash algorithm 0x00008004 // SHA1
.ver 0:0:0:0
}

.class public auto ansi beforefieldinit NonGenericConstrainedCallVirt
extends [mscorlib]System.Object
{
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 16 (0x10)
.entrypoint
.maxstack 8

IL_0000: ldstr "A"
IL_0005: newobj instance void C::.ctor(string)
IL_000a: call void NonGenericConstrainedCallVirt::Foo(class C)
IL_000f: ret
} // end of method NonGenericConstrainedCallVirt::Main

.method private hidebysig static
void Foo (
class C arg
) cil managed
{
// Method begins at RVA 0x2064
// Code size 25 (0x19)
.maxstack 8

IL_0000: ldarga arg
IL_0004: ldarga arg
IL_0008: call int32 NonGenericConstrainedCallVirt::Bar(class C&)
IL_000d: constrained. C
IL_0013: callvirt instance void C::Baz(int32)
IL_0018: ret
} // end of method NonGenericConstrainedCallVirt::Foo

.method private hidebysig static
int32 Bar (
class C& o
) cil managed
{
// Method begins at RVA 0x2080
// Code size 14 (0xe)
.maxstack 8

IL_0000: ldarg.0
IL_0001: ldstr "B"
IL_0006: newobj instance void C::.ctor(string)
IL_000b: stind.ref
IL_000c: ldc.i4.0
IL_000d: ret
} // end of method NonGenericConstrainedCallVirt::Bar

.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2090
// Code size 7 (0x7)
.maxstack 8

IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method NonGenericConstrainedCallVirt::.ctor

} // end of class NonGenericConstrainedCallVirt

.class public auto ansi beforefieldinit C
extends [mscorlib]System.Object
{
// Fields
.field private initonly string '<Name>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)

// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
string n
) cil managed
{
// Method begins at RVA 0x2098
// Code size 14 (0xe)
.maxstack 8

IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld string C::'<Name>k__BackingField'
IL_000d: ret
} // end of method C::.ctor

.method public hidebysig
instance void Baz (
int32 arg
) cil managed
{
// Method begins at RVA 0x20a8
// Code size 12 (0xc)
.maxstack 8

IL_0000: ldarg.0
IL_0001: call instance string C::get_Name()
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: ret
} // end of method C::Baz

.method public hidebysig specialname
instance string get_Name () cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x20b8
// Code size 7 (0x7)
.maxstack 8

IL_0000: ldarg.0
IL_0001: ldfld string C::'<Name>k__BackingField'
IL_0006: ret
} // end of method C::get_Name

// Properties
.property instance string Name()
{
.get instance string C::get_Name()
}

} // end of class C

54 changes: 54 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ public interface IInterface
{
void Method1<T>() where T : class;
void Method2<T>() where T : class;

void Method3(int a, string b, Type c);
#if CS72
void Method4(in int a);
#endif
}

public abstract class Base : IInterface
Expand All @@ -95,6 +100,16 @@ public abstract class Base : IInterface
void IInterface.Method2<T>()
{
}

void IInterface.Method3(int a, string b, Type c)
{
}

#if CS72
void IInterface.Method4(in int a)
{
}
#endif
}

public class Derived : Base
Expand Down Expand Up @@ -296,5 +311,44 @@ public static string Issue2231b<T>()
{
return default(T).ToString();
}

public static void ConstrainedCall<T>(T x, ref T y) where T : IDisposable
{
x.Dispose();
y.Dispose();
}

// prior to C# 7.0 UseRefLocalsForAccurateOrderOfEvaluation is disabled, so we will inline.
// Roslyn 4 generates the explicit ldobj.if.ref pattern, so we can also inline.
// The versions in between, we don't inline, so the code doesn't look pretty.
#if ROSLYN4 || !CS70
public static int[] Issue3438<T>(T[] array)
{
List<int> list = new List<int>();
for (int i = 0; i < array.Length; i++)
{
if (!array[i].Equals(default(T)))
{
list.Add(i);
}
}
return list.ToArray();
}
public void Issue3438b<T>(T[] item1, T item2, int item3) where T : IInterface
{
item1[CastToInt(item2)].Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
public void Issue3438c<T>(T item, T item2, int item3) where T : IInterface
{
CastFromInt<T>(item3).Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
//#if CS72
// Disabled because ILInlining does not support inlining ldloca currently.
// public void Issue3438d<T>(T[] item1, T item2, int item3) where T : IInterface
// {
// item1[CastToInt(item2)].Method4(CastToInt(item2));
// }
//#endif
#endif
}
}
2 changes: 2 additions & 0 deletions ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public static List<IILTransform> GetILTransforms()
new IndexRangeTransform(),
new DeconstructionTransform(),
new NamedArgumentTransform(),
new RemoveUnconstrainedGenericReferenceTypeCheck(),
new UserDefinedLogicTransform(),
new InterpolatedStringTransform()
),
Expand Down Expand Up @@ -1717,6 +1718,7 @@ void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun de
{
var ilReader = new ILReader(typeSystem.MainModule) {
UseDebugSymbols = settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider
};
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
Expand Down
11 changes: 9 additions & 2 deletions ICSharpCode.Decompiler/CSharp/CallBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,18 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
}
else
{
var thisArg = callArguments.FirstOrDefault();
if (thisArg is LdObjIfRef ldObjIfRef)
{
Debug.Assert(constrainedTo != null);
thisArg = ldObjIfRef.Target;
}
target = expressionBuilder.TranslateTarget(
callArguments.FirstOrDefault(),
thisArg,
nonVirtualInvocation: callOpCode == OpCode.Call || method.IsConstructor,
memberStatic: method.IsStatic,
memberDeclaringType: constrainedTo ?? method.DeclaringType);
memberDeclaringType: method.DeclaringType,
constrainedTo: constrainedTo);
if (constrainedTo == null
&& target.Expression is CastExpression cast
&& target.ResolveResult is ConversionResolveResult conversion
Expand Down
18 changes: 9 additions & 9 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2614,7 +2614,7 @@ protected internal override TranslatedExpression VisitBlockContainer(BlockContai
}

internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirtualInvocation,
bool memberStatic, IType memberDeclaringType)
bool memberStatic, IType memberDeclaringType, IType constrainedTo = null)
{
// If references are missing member.IsStatic might not be set correctly.
// Additionally check target for null, in order to avoid a crash.
Expand All @@ -2630,8 +2630,8 @@ internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirt
}
else
{
IType targetTypeHint = memberDeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
IType targetTypeHint = constrainedTo ?? memberDeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
if (target.ResultType == StackType.Ref)
{
Expand All @@ -2643,13 +2643,13 @@ internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirt
}
}
var translatedTarget = Translate(target, targetTypeHint);
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
// When accessing members on value types, ensure we use a reference of the correct type,
// and not a pointer or a reference to a different type (issue #1333)
if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, memberDeclaringType)))
if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, constrainedTo ?? memberDeclaringType)))
{
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(memberDeclaringType), this);
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? memberDeclaringType), this);
}
}
if (translatedTarget.Expression is DirectionExpression)
Expand All @@ -2675,9 +2675,9 @@ internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirt
}
else
{
return new TypeReferenceExpression(ConvertType(memberDeclaringType))
return new TypeReferenceExpression(ConvertType(constrainedTo ?? memberDeclaringType))
.WithoutILInstruction()
.WithRR(new TypeResolveResult(memberDeclaringType));
.WithRR(new TypeResolveResult(constrainedTo ?? memberDeclaringType));
}

bool ShouldUseBaseReference()
Expand All @@ -2686,7 +2686,7 @@ bool ShouldUseBaseReference()
return false;
if (!MatchLdThis(target))
return false;
if (memberDeclaringType.GetDefinition() == resolver.CurrentTypeDefinition)
if ((constrainedTo ?? memberDeclaringType).GetDefinition() == resolver.CurrentTypeDefinition)
return false;
return true;
}
Expand Down
Loading
Loading