Browse Source

Fix #2189: Invalid C# generated when pinning an unmanaged pointer.

C# has some special rules for allowed expressions inside a fixed statement.
In the non-allowed cases, emit an `Unsafe.AsRef()` call to prevent compiler errors.
pull/2214/head
Daniel Grunwald 5 years ago
parent
commit
54231edb4b
  1. 15
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs
  2. 50
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il
  3. 36
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

15
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs

@ -83,6 +83,21 @@ internal sealed class ExtraUnsafeTests @@ -83,6 +83,21 @@ internal sealed class ExtraUnsafeTests
Console.WriteLine("Hello World!");
}
}
private unsafe static void Issue2189()
{
fixed (int* ptr = &Unsafe.AsRef<int>((int*)Unsafe.AsPointer(ref SomeStruct.instance.mtfhist)))
{
int num = *ptr;
}
}
private unsafe static void PinUnmanagedPtr(int* A_0)
{
fixed (int* ptr = &Unsafe.AsRef<int>(A_0))
{
int num = *ptr;
}
}
}
namespace System.Runtime.CompilerServices

50
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il

@ -609,7 +609,6 @@ lbl: @@ -609,7 +609,6 @@ lbl:
// Code Size: 24 (0x18) bytes
// LocalVarSig Token: 0x11000001 RID: 1
.maxstack 2
.entrypoint
.locals init (
[0] int32 pinned
)
@ -633,4 +632,53 @@ lbl: @@ -633,4 +632,53 @@ lbl:
/* 0x0000027B 2A */ IL_0017: ret
} // end of method Issue2148
.method private hidebysig static
void Issue2189 () cil managed
{
.maxstack 2
.locals init (
[0] int32,
[1] int32& pinned
)
IL_0000: ldsflda valuetype [CORE_ASSEMBLY]SomeStruct [CORE_ASSEMBLY]SomeStruct::'instance'
IL_0005: ldflda uint32 [CORE_ASSEMBLY]SomeStruct::mtfhist
IL_000a: conv.u
IL_000b: stloc.1
ldloc.1
ldind.i4
stloc.0
IL_0185: ldc.i4.0
IL_0186: conv.i
IL_0187: stloc.1
IL_0188: ret
} // end of method Issue2189
.method private hidebysig static
void PinUnmanagedPtr (int32*) cil managed
{
.maxstack 2
.locals init (
[0] int32,
[1] int32& pinned
)
ldarg.0
stloc.1
ldloc.1
ldind.i4
stloc.0
ldc.i4.0
conv.i
stloc.1
ret
} // end of method Issue2189
}

36
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -1118,12 +1118,48 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1118,12 +1118,48 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new ResolveResult(inst.Variable.Type));
}
}
if (initExpr.GetResolveResult()?.Type.Kind == TypeKind.Pointer
&& !IsAddressOfMoveableVar(initExpr)
&& !IsFixedSizeBuffer(initExpr)
&& refType is ByReferenceType brt)
{
// C# doesn't allow pinning an already-unmanaged pointer
// fixed (int* ptr = existing_ptr) {} -> invalid
// fixed (int* ptr = &existing_ptr->field) {} -> invalid
// fixed (int* ptr = &local_var) {} -> invalid
// We work around this by instead doing:
// fixed (int* ptr = &Unsafe.AsRef<int>(existing_ptr))
var asRefCall = exprBuilder.CallUnsafeIntrinsic(
name: "AsRef",
arguments: new Expression[] { initExpr },
returnType: brt.ElementType,
typeArguments: new IType[] { brt.ElementType }
);
initExpr = new UnaryOperatorExpression(UnaryOperatorType.AddressOf, asRefCall)
.WithRR(new ResolveResult(inst.Variable.Type));
}
}
fixedStmt.Variables.Add(new VariableInitializer(inst.Variable.Name, initExpr).WithILVariable(inst.Variable));
fixedStmt.EmbeddedStatement = Convert(inst.Body);
return fixedStmt.WithILInstruction(inst);
}
private static bool IsAddressOfMoveableVar(Expression initExpr)
{
if (initExpr is UnaryOperatorExpression { Operator: UnaryOperatorType.AddressOf } uoe)
{
var inst = uoe.Expression.Annotation<ILInstruction>();
return !(inst != null && PointerArithmeticOffset.IsFixedVariable(inst));
}
return false;
}
private static bool IsFixedSizeBuffer(Expression initExpr)
{
var mrr = initExpr.GetResolveResult() as MemberResolveResult;
return mrr?.Member is IField f && CSharpDecompiler.IsFixedField(f, out _, out _);
}
protected internal override TranslatedStatement VisitBlock(Block block)
{
if (block.Kind != BlockKind.ControlFlow)

Loading…
Cancel
Save