mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
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 Codepull/4000/head
4 changed files with 317 additions and 0 deletions
@ -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) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
@ -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…
Reference in new issue