Browse Source

Add an ILPretty test for #3729

Structs whose assembly is missing decompile through the unresolved-type
path, which is not covered anywhere: the fixture pins the constructor
shapes that path has to recognize, and the reference-type cases that must
keep falling through to a plain call.

Assisted-by: Claude:claude-opus-5:Claude Code
pull/4000/head
Siegfried Pammer 1 month ago
parent
commit
ac8a8ff64d
  1. 3
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  3. 80
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs
  4. 228
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.il

3
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -202,6 +202,9 @@ @@ -202,6 +202,9 @@
<None Include="TestCases\ILPretty\TruncatedAccessorBody.cs" />
<Compile Remove="TestCases\ILPretty\SealedRecordProtectedCopyCtor.cs" />
<None Include="TestCases\ILPretty\SealedRecordProtectedCopyCtor.cs" />
<Compile Remove="TestCases\ILPretty\Issue3729.cs" />
<None Include="TestCases\ILPretty\Issue3729.cs" />
<None Include="TestCases\ILPretty\Issue3729.il" />
<Compile Remove="TestCases\ILPretty\FSharpLoops_Debug.cs" />
<None Include="TestCases\ILPretty\FSharpLoops_Debug.cs" />
<Compile Remove="TestCases\ILPretty\FSharpLoops_Release.cs" />

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -405,6 +405,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -405,6 +405,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run(settings: new DecompilerSettings { SortSwitchSections = true, FileScopedNamespaces = false });
}
[Test]
public async Task Issue3729()
{
await Run();
}
async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null,
AssemblerOptions assemblerOptions = AssemblerOptions.Library)
{

80
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
using System;
using System.Runtime.CompilerServices;
using Library1;
namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
{
public class Issue3729
{
private MyStruct structField;
public void TestSingleArgCtor()
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
Console.WriteLine("Test new struct: " + ((object)new MyStruct(4)/*cast due to constrained. prefix*/).ToString());
}
public void TestParameterlessCtor()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
MyEmptyStruct val = new MyEmptyStruct();
Console.WriteLine(val);
}
public void TestMultiArgCtor()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
MyBigStruct val = new MyBigStruct(1, "hello", 3.14);
Console.WriteLine(val);
}
public void TestFieldCtor()
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
ref MyStruct reference = ref structField;
reference = new MyStruct(5);
}
public unsafe static void TestPointerCtor(void* ptr)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
System.Runtime.CompilerServices.Unsafe.Write(ptr, new MyStruct(6));
}
public void TestArrayElemCtor()
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
MyStruct[] array = (MyStruct[])(object)new MyStruct[1] {
new MyStruct(7)
};
}
public void TestGenericStructCtor()
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
MyGenericStruct<int> val = new MyGenericStruct<int>(4);
Console.WriteLine(val);
}
public void TestRefTypeNewobj()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
MyClass value = new MyClass();
Console.WriteLine(value);
}
}
public class Issue3729_DerivedFromUnknown : MissingBase
{
}
public class Issue3729_DerivedFromUnknownWithArgs : MissingBase
{
public Issue3729_DerivedFromUnknownWithArgs()
: base(42)
{
}
}
}

228
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.il

@ -0,0 +1,228 @@ @@ -0,0 +1,228 @@
// Reproducer for issue #3729:
// Decompilation of a struct constructor call where the struct's defining
// assembly (Library1) cannot be resolved at decompile time.
// Mirrors what csc emits for `new Library1.MyStruct(4)` followed by
// string concat with the struct value.
.assembly extern Library1
{
}
.assembly extern System.Runtime
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
.ver 4:0:0:0
}
.assembly extern System.Console
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
.ver 4:0:0:0
}
.assembly Issue3729
{
.ver 1:0:0:0
}
.module Issue3729.dll
.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729
extends [System.Runtime]System.Object
{
.field private valuetype [Library1]Library1.MyStruct structField
// single-int-arg struct ctor with subsequent constrained.callvirt ToString()
.method public hidebysig
instance void TestSingleArgCtor () cil managed
{
.maxstack 2
.locals init (
[0] valuetype [Library1]Library1.MyStruct
)
IL_0000: ldloca.s 0
IL_0002: ldc.i4.4
IL_0003: call instance void [Library1]Library1.MyStruct::.ctor(int32)
IL_0008: ldstr "Test new struct: "
IL_000d: ldloca.s 0
IL_000f: constrained. [Library1]Library1.MyStruct
IL_0015: callvirt instance string [System.Runtime]System.Object::ToString()
IL_001a: call string [System.Runtime]System.String::Concat(string, string)
IL_001f: call void [System.Console]System.Console::WriteLine(string)
IL_0024: ret
}
// explicit parameterless struct ctor (C# 10+ feature in source)
// Single-argument ctor: the receiver sits directly below the argument.
.method public hidebysig
instance void TestParameterlessCtor () cil managed
{
.maxstack 1
.locals init (
[0] valuetype [Library1]Library1.MyEmptyStruct
)
IL_0000: ldloca.s 0
IL_0002: call instance void [Library1]Library1.MyEmptyStruct::.ctor()
IL_0007: ldloc.0
IL_0008: box [Library1]Library1.MyEmptyStruct
IL_000d: call void [System.Console]System.Console::WriteLine(object)
IL_0012: ret
}
// Multi-argument ctor with mixed parameter types: the receiver sits below three arguments
.method public hidebysig
instance void TestMultiArgCtor () cil managed
{
.maxstack 5
.locals init (
[0] valuetype [Library1]Library1.MyBigStruct
)
IL_0000: ldloca.s 0
IL_0002: ldc.i4.1
IL_0003: ldstr "hello"
IL_0008: ldc.r8 3.14
IL_0011: call instance void [Library1]Library1.MyBigStruct::.ctor(int32, string, float64)
IL_0016: ldloc.0
IL_0017: box [Library1]Library1.MyBigStruct
IL_001c: call void [System.Console]System.Console::WriteLine(object)
IL_0021: ret
}
// struct field init via `ldflda + call .ctor`. Receiver is the field
// address (StackType.Ref) - same shape as `ldloca`, just sourced from
// an instance field instead of a local.
.method public hidebysig
instance void TestFieldCtor () cil managed
{
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldflda valuetype [Library1]Library1.MyStruct ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729::structField
IL_0006: ldc.i4.5
IL_0007: call instance void [Library1]Library1.MyStruct::.ctor(int32)
IL_000c: ret
}
// Unsafe code passes the target as a pointer, so the receiver is a native
// pointer (StackType.I) rather than a managed reference.
.method public hidebysig static
void TestPointerCtor (
void* ptr
) cil managed
{
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.6
IL_0002: call instance void [Library1]Library1.MyStruct::.ctor(int32)
IL_0007: ret
}
// struct array element init via `ldelema + call .ctor`. Receiver is the
// element address (StackType.Ref) - again the same shape.
.method public hidebysig
instance void TestArrayElemCtor () cil managed
{
.maxstack 4
.locals init (
[0] valuetype [Library1]Library1.MyStruct[]
)
IL_0000: ldc.i4.1
IL_0001: newarr [Library1]Library1.MyStruct
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.0
IL_0009: ldelema [Library1]Library1.MyStruct
IL_000e: ldc.i4.7
IL_000f: call instance void [Library1]Library1.MyStruct::.ctor(int32)
IL_0014: ret
}
// generic struct instantiation `new MyGenericStruct<int>(4)`. Declaring
// type at the call site is a ParameterizedType wrapping the unresolved
// generic def - its Kind delegates to the underlying UnknownType.Kind,
// so a constructed generic type is unresolved in the same way.
.method public hidebysig
instance void TestGenericStructCtor () cil managed
{
.maxstack 2
.locals init (
[0] valuetype [Library1]Library1.MyGenericStruct`1<int32>
)
IL_0000: ldloca.s 0
IL_0002: ldc.i4.4
IL_0003: call instance void valuetype [Library1]Library1.MyGenericStruct`1<int32>::.ctor(!0)
IL_0008: ldloc.0
IL_0009: box valuetype [Library1]Library1.MyGenericStruct`1<int32>
IL_000e: call void [System.Console]System.Console::WriteLine(object)
IL_0013: ret
}
// newobj on an unresolved reference type - uses OpCode.NewObj, a different
// IL path that already produces correct C# (HandleConstructorCall in CallBuilder).
// Included as a control case for the value-type ctor handling above.
.method public hidebysig
instance void TestRefTypeNewobj () cil managed
{
.maxstack 1
.locals init (
[0] class [Library1]Library1.MyClass
)
IL_0000: newobj instance void [Library1]Library1.MyClass::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call void [System.Console]System.Console::WriteLine(object)
IL_000c: ret
}
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
}
}
// Classes deriving from an unresolved base class. The synthesized .ctor
// calls `[Library1]Library1.MissingBase::.ctor(...)` with `this`
// (StackType.O) as the receiver - exercises the StackType.Ref guard on
// a reference-typed receiver, which has to keep falling through to the
// default call path so the C# decompiler can render it as a normal
// `: base(...)` constructor initializer.
.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729_DerivedFromUnknown
extends [Library1]Library1.MissingBase
{
// parameterless base ctor - the implicit ctor would normally be elided.
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [Library1]Library1.MissingBase::.ctor()
IL_0006: ret
}
}
// Same as above but the base ctor takes an argument - receiver is at
// depth 1 (one parameter on the stack above it). Output should render
// the explicit `: base(<arg>)` initializer.
.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729_DerivedFromUnknownWithArgs
extends [Library1]Library1.MissingBase
{
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.s 42
IL_0003: call instance void [Library1]Library1.MissingBase::.ctor(int32)
IL_0008: ret
}
}
Loading…
Cancel
Save