diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index c29cd893b..2c729e390 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -1236,8 +1236,9 @@ namespace ICSharpCode.Decompiler.CSharp
var pointerType = new PointerType(((ByReferenceType)inputType).ElementType);
return arg.ConvertTo(pointerType, this).WithILInstruction(inst);
} else {
- Debug.Fail("ConversionKind.StopGCTracking should only be used with managed references");
- goto default;
+ // ConversionKind.StopGCTracking should only be used with managed references,
+ // but it's possible that we're supposed to stop tracking something we just started to track.
+ return arg;
}
case ConversionKind.SignExtend:
// We just need to ensure the input type before the conversion is signed.
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 16d564bae..6ba846dc9 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -129,7 +129,7 @@ namespace ICSharpCode.Decompiler.IL
ILVariable CreateILVariable(Cil.VariableDefinition v)
{
- VariableKind kind = v.IsPinned ? VariableKind.PinnedLocal : VariableKind.Local;
+ VariableKind kind = IsPinned(v.VariableType) ? VariableKind.PinnedLocal : VariableKind.Local;
ILVariable ilVar = new ILVariable(kind, typeSystem.Resolve(v.VariableType), v.Index);
if (!UseDebugSymbols || debugInfo == null || !debugInfo.TryGetName(v, out string name)) {
ilVar.Name = "V_" + v.Index;
@@ -143,6 +143,14 @@ namespace ICSharpCode.Decompiler.IL
return ilVar;
}
+ bool IsPinned(TypeReference type)
+ {
+ while (type is OptionalModifierType || type is RequiredModifierType) {
+ type = ((TypeSpecification)type).ElementType;
+ }
+ return type is PinnedType;
+ }
+
ILVariable CreateILVariable(ParameterDefinition p)
{
IType parameterType;
@@ -1028,8 +1036,8 @@ namespace ICSharpCode.Decompiler.IL
// to a method expecting a native pointer.
inst = new Conv(inst, PrimitiveType.I, false, Sign.None);
} else if (expectedType == StackType.Ref) {
- // implicitly start GC tracking
- if (!inst.ResultType.IsIntegerType()) {
+ // implicitly start GC tracking / object to interior
+ if (!inst.ResultType.IsIntegerType() && inst.ResultType != StackType.O) {
// We also handle the invalid to-ref cases here because the else case
// below uses expectedType.ToKnownTypeCode(), which doesn't work for Ref.
Warn($"Expected {expectedType}, but got {inst.ResultType}");
@@ -1332,6 +1340,12 @@ namespace ICSharpCode.Decompiler.IL
negate ? ComparisonKind.Equality : ComparisonKind.Inequality,
Sign.None, condition, new LdcI8(0));
break;
+ case StackType.Ref:
+ // introduce explicit comparison with null ref
+ condition = new Comp(
+ negate ? ComparisonKind.Equality : ComparisonKind.Inequality,
+ Sign.None, new Conv(condition, PrimitiveType.I, false, Sign.None), new Conv(new LdcI4(0), PrimitiveType.I, false, Sign.None));
+ break;
default:
if (negate) {
condition = Comp.LogicNot(condition);
diff --git a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs
index 5f6c26add..9562466fa 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs
@@ -79,7 +79,15 @@ namespace ICSharpCode.Decompiler.IL
///
/// Used to convert unmanaged pointers to managed references.
///
- StartGCTracking
+ StartGCTracking,
+ ///
+ /// Converts from an object reference (O) to an interior pointer (Ref) pointing to the start of the object.
+ ///
+ ///
+ /// C++/CLI emits "ldarg.1; stloc.0" where arg1 is a string and loc0 is "ref byte" (e.g. as part of the PtrToStringChars codegen);
+ /// we represent this type conversion explicitly in the ILAst.
+ ///
+ ObjectInterior
}
partial class Conv : UnaryInstruction, ILiftableInstruction
@@ -250,11 +258,15 @@ namespace ICSharpCode.Decompiler.IL
return ConversionKind.Invalid;
}
case PrimitiveType.Ref:
+ // There's no "conv.ref" in IL, but IL allows these conversions implicitly,
+ // whereas we represent them explicitly in the ILAst.
switch (inputType) {
case StackType.I4:
case StackType.I:
case StackType.I8:
return ConversionKind.StartGCTracking;
+ case StackType.O:
+ return ConversionKind.ObjectInterior;
default:
return ConversionKind.Invalid;
}
diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
index 7cc996073..25c06d8a9 100644
--- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
@@ -132,6 +132,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
if (typeReference == null)
return SpecialType.UnknownType;
+ // We need to skip SentinelType and PinnedType.
+ // But PinnedType can be nested within modopt, so we'll also skip those.
+ while (typeReference is OptionalModifierType || typeReference is RequiredModifierType) {
+ typeReference = ((TypeSpecification)typeReference).ElementType;
+ }
if (typeReference is SentinelType || typeReference is PinnedType) {
typeReference = ((TypeSpecification)typeReference).ElementType;
}