Browse Source

Fix #1154: Decompilation of fixed statement when pointer variable is unused

pull/1030/head
Daniel Grunwald 7 years ago
parent
commit
d8c8a75c2e
  1. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs
  2. 25
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.il
  3. 15
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.opt.il
  4. 24
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.opt.roslyn.il
  5. 27
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.roslyn.il
  6. 160
      ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs
  7. 3
      ILSpy/Languages/Language.cs

6
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs

@ -180,6 +180,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -180,6 +180,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public unsafe void FixedStringNoPointerUse(string text)
{
fixed (char* ptr = text) {
}
}
public unsafe void PutDoubleIntoLongArray1(long[] array, int index, double val)
{
fixed (long* ptr = array) {

25
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.il

@ -561,6 +561,31 @@ @@ -561,6 +561,31 @@
IL_002c: ret
} // end of method UnsafeCode::FixedStringAccess
.method public hidebysig instance void
FixedStringNoPointerUse(string text) cil managed
{
// Code size 20 (0x14)
.maxstack 2
.locals init (char* V_0,
string pinned V_1)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: conv.i
IL_0005: dup
IL_0006: brfalse.s IL_000e
IL_0008: call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
IL_000d: add
IL_000e: stloc.0
IL_000f: nop
IL_0010: nop
IL_0011: ldnull
IL_0012: stloc.1
IL_0013: ret
} // end of method UnsafeCode::FixedStringNoPointerUse
.method public hidebysig instance void
PutDoubleIntoLongArray1(int64[] 'array',
int32 index,

15
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.opt.il

@ -471,6 +471,21 @@ @@ -471,6 +471,21 @@
IL_0023: ret
} // end of method UnsafeCode::FixedStringAccess
.method public hidebysig instance void
FixedStringNoPointerUse(string text) cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init (string pinned V_0)
IL_0000: ldarg.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: pop
IL_0004: ldnull
IL_0005: stloc.0
IL_0006: ret
} // end of method UnsafeCode::FixedStringNoPointerUse
.method public hidebysig instance void
PutDoubleIntoLongArray1(int64[] 'array',
int32 index,

24
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.opt.roslyn.il

@ -480,6 +480,30 @@ @@ -480,6 +480,30 @@
IL_0024: ret
} // end of method UnsafeCode::FixedStringAccess
.method public hidebysig instance void
FixedStringNoPointerUse(string text) cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init (char* V_0,
string pinned V_1)
IL_0000: ldarg.1
IL_0001: stloc.1
IL_0002: ldloc.1
IL_0003: conv.u
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: brfalse.s IL_0010
IL_0008: ldloc.0
IL_0009: call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
IL_000e: add
IL_000f: stloc.0
IL_0010: ldnull
IL_0011: stloc.1
IL_0012: ret
} // end of method UnsafeCode::FixedStringNoPointerUse
.method public hidebysig instance void
PutDoubleIntoLongArray1(int64[] 'array',
int32 index,

27
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.roslyn.il

@ -563,6 +563,33 @@ @@ -563,6 +563,33 @@
IL_002d: ret
} // end of method UnsafeCode::FixedStringAccess
.method public hidebysig instance void
FixedStringNoPointerUse(string text) cil managed
{
// Code size 22 (0x16)
.maxstack 2
.locals init (char* V_0,
string pinned V_1)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: conv.u
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: brfalse.s IL_0011
IL_0009: ldloc.0
IL_000a: call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
IL_000f: add
IL_0010: stloc.0
IL_0011: nop
IL_0012: nop
IL_0013: ldnull
IL_0014: stloc.1
IL_0015: ret
} // end of method UnsafeCode::FixedStringNoPointerUse
.method public hidebysig instance void
PutDoubleIntoLongArray1(int64[] 'array',
int32 index,

160
ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs

@ -361,7 +361,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -361,7 +361,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
void ProcessPinnedRegion(PinnedRegion pinnedRegion)
{
BlockContainer body = (BlockContainer)pinnedRegion.Body;
if (pinnedRegion.Variable.Type.Kind == TypeKind.ByReference) {
// C# doesn't support a "by reference" variable, so replace it with a native pointer
context.Step("Replace pinned ref-local with native pointer", pinnedRegion);
@ -390,59 +389,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -390,59 +389,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
UseExistingVariableForPinnedRegion(pinnedRegion);
} else if (pinnedRegion.Variable.Type.IsKnownType(KnownTypeCode.String)) {
// fixing a string
ILVariable nativeVar;
ILInstruction initInst;
// stloc nativeVar(conv o->i (ldloc pinnedVar))
// if (comp(ldloc nativeVar == conv i4->i <sign extend>(ldc.i4 0))) br targetBlock
// br adjustOffsetToStringData
Block targetBlock, adjustOffsetToStringData;
if (body.EntryPoint.IncomingEdgeCount == 1
&& body.EntryPoint.Instructions.Count == 3
&& body.EntryPoint.Instructions[0].MatchStLoc(out nativeVar, out initInst)
&& nativeVar.Type.GetStackType() == StackType.I
&& nativeVar.StoreCount == 2
&& initInst.UnwrapConv(ConversionKind.StopGCTracking).MatchLdLoc(pinnedRegion.Variable)
&& IsBranchOnNull(body.EntryPoint.Instructions[1], nativeVar, out targetBlock)
&& targetBlock.Parent == body
&& body.EntryPoint.Instructions[2].MatchBranch(out adjustOffsetToStringData)
&& adjustOffsetToStringData.Parent == body && adjustOffsetToStringData.IncomingEdgeCount == 1
&& IsOffsetToStringDataBlock(adjustOffsetToStringData, nativeVar, targetBlock))
{
context.Step("Handle pinned string (with adjustOffsetToStringData)", pinnedRegion);
// remove old entry point
body.Blocks.RemoveAt(0);
body.Blocks.RemoveAt(adjustOffsetToStringData.ChildIndex);
// make targetBlock the new entry point
body.Blocks.RemoveAt(targetBlock.ChildIndex);
body.Blocks.Insert(0, targetBlock);
pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init);
ILVariable otherVar;
ILInstruction otherVarInit;
// In optimized builds, the 'nativeVar' may end up being a stack slot,
// and only gets assigned to a real variable after the offset adjustment.
if (nativeVar.Kind == VariableKind.StackSlot && nativeVar.LoadCount == 1
&& body.EntryPoint.Instructions[0].MatchStLoc(out otherVar, out otherVarInit)
&& otherVarInit.MatchLdLoc(nativeVar)
&& otherVar.IsSingleDefinition)
{
body.EntryPoint.Instructions.RemoveAt(0);
nativeVar = otherVar;
}
ILVariable newVar;
if (nativeVar.Kind == VariableKind.Local) {
newVar = new ILVariable(VariableKind.PinnedLocal, nativeVar.Type, nativeVar.Index);
newVar.Name = nativeVar.Name;
newVar.HasGeneratedName = nativeVar.HasGeneratedName;
nativeVar.Function.Variables.Add(newVar);
ReplacePinnedVar(nativeVar, newVar, pinnedRegion);
} else {
newVar = nativeVar;
}
ReplacePinnedVar(pinnedRegion.Variable, newVar, pinnedRegion);
}
HandleStringToPointer(pinnedRegion);
}
// Detect nested pinned regions:
BlockContainer body = (BlockContainer)pinnedRegion.Body;
foreach (var block in body.Blocks)
CreatePinnedRegion(block);
body.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks
@ -519,17 +469,109 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -519,17 +469,109 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& trueInst.MatchBranch(out targetBlock);
}
void HandleStringToPointer(PinnedRegion pinnedRegion)
{
Debug.Assert(pinnedRegion.Variable.Type.IsKnownType(KnownTypeCode.String));
BlockContainer body = (BlockContainer)pinnedRegion.Body;
if (body.EntryPoint.IncomingEdgeCount != 1)
return;
// stloc nativeVar(conv o->i (ldloc pinnedVar))
// if (comp(ldloc nativeVar == conv i4->i <sign extend>(ldc.i4 0))) br targetBlock
// br adjustOffsetToStringData
if (!body.EntryPoint.Instructions[0].MatchStLoc(out ILVariable nativeVar, out ILInstruction initInst))
return;
ILVariable newVar;
if (body.EntryPoint.Instructions.Count != 3) {
// potentially a special case with legacy csc and an unused pinned variable:
if (nativeVar.IsSingleDefinition && nativeVar.LoadCount == 0 && initInst.MatchLdLoc(pinnedRegion.Variable)
&& pinnedRegion.Variable.LoadCount == 1)
{
// initInst is dead store
body.EntryPoint.Instructions.RemoveAt(0);
var charPtr = new PointerType(context.TypeSystem.FindType(KnownTypeCode.Char));
newVar = new ILVariable(VariableKind.PinnedLocal, charPtr, pinnedRegion.Variable.Index);
newVar.Name = pinnedRegion.Variable.Name;
newVar.HasGeneratedName = pinnedRegion.Variable.HasGeneratedName;
nativeVar.Function.Variables.Add(newVar);
pinnedRegion.Variable = newVar;
pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init);
}
return;
}
if (nativeVar.Type.GetStackType() != StackType.I)
return;
if (!initInst.UnwrapConv(ConversionKind.StopGCTracking).MatchLdLoc(pinnedRegion.Variable))
return;
if (!IsBranchOnNull(body.EntryPoint.Instructions[1], nativeVar, out Block targetBlock))
return;
if (targetBlock.Parent != body)
return;
if (!body.EntryPoint.Instructions[2].MatchBranch(out Block adjustOffsetToStringData))
return;
if (!(adjustOffsetToStringData.Parent == body && adjustOffsetToStringData.IncomingEdgeCount == 1
&& IsOffsetToStringDataBlock(adjustOffsetToStringData, nativeVar, targetBlock)))
return;
context.Step("Handle pinned string (with adjustOffsetToStringData)", pinnedRegion);
// remove old entry point
body.Blocks.RemoveAt(0);
body.Blocks.RemoveAt(adjustOffsetToStringData.ChildIndex);
// make targetBlock the new entry point
body.Blocks.RemoveAt(targetBlock.ChildIndex);
body.Blocks.Insert(0, targetBlock);
pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init);
ILVariable otherVar;
ILInstruction otherVarInit;
// In optimized builds, the 'nativeVar' may end up being a stack slot,
// and only gets assigned to a real variable after the offset adjustment.
if (nativeVar.Kind == VariableKind.StackSlot && nativeVar.LoadCount == 1
&& body.EntryPoint.Instructions[0].MatchStLoc(out otherVar, out otherVarInit)
&& otherVarInit.MatchLdLoc(nativeVar)
&& otherVar.IsSingleDefinition) {
body.EntryPoint.Instructions.RemoveAt(0);
nativeVar = otherVar;
}
if (nativeVar.Kind == VariableKind.Local) {
newVar = new ILVariable(VariableKind.PinnedLocal, nativeVar.Type, nativeVar.Index);
newVar.Name = nativeVar.Name;
newVar.HasGeneratedName = nativeVar.HasGeneratedName;
nativeVar.Function.Variables.Add(newVar);
ReplacePinnedVar(nativeVar, newVar, pinnedRegion);
} else {
newVar = nativeVar;
}
ReplacePinnedVar(pinnedRegion.Variable, newVar, pinnedRegion);
}
bool IsOffsetToStringDataBlock(Block block, ILVariable nativeVar, Block targetBlock)
{
// stloc nativeVar(add(ldloc nativeVar, conv i4->i <sign extend>(call [Accessor System.Runtime.CompilerServices.RuntimeHelpers.get_OffsetToStringData():System.Int32]())))
// br IL_0011
ILInstruction left, right, value;
return block.Instructions.Count == 2
&& block.Instructions[0].MatchStLoc(nativeVar, out value)
&& value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out left, out right)
&& left.MatchLdLoc(nativeVar)
&& IsOffsetToStringDataCall(right)
&& block.Instructions[1].MatchBranch(targetBlock);
if (block.Instructions.Count != 2)
return false;
ILInstruction value;
if (nativeVar.IsSingleDefinition && nativeVar.LoadCount == 2) {
// If there are no loads (except for the two in the string-to-pointer pattern),
// then we might have split nativeVar:
if (!block.Instructions[0].MatchStLoc(out var otherVar, out value))
return false;
if (!(otherVar.IsSingleDefinition && otherVar.LoadCount == 0))
return false;
} else if (nativeVar.StoreCount == 2) {
// normal case with non-split variable
if (!block.Instructions[0].MatchStLoc(nativeVar, out value))
return false;
} else {
return false;
}
if (!value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out ILInstruction left, out ILInstruction right))
return false;
if (!left.MatchLdLoc(nativeVar))
return false;
if (!IsOffsetToStringDataCall(right))
return false;
return block.Instructions[1].MatchBranch(targetBlock);
}
bool IsOffsetToStringDataCall(ILInstruction inst)

3
ILSpy/Languages/Language.cs

@ -61,6 +61,9 @@ namespace ICSharpCode.ILSpy @@ -61,6 +61,9 @@ namespace ICSharpCode.ILSpy
/// <summary>
/// Base class for language-specific decompiler implementations.
/// </summary>
/// <remarks>
/// Implementations of this class must be thread-safe.
/// </remarks>
public abstract class Language
{
/// <summary>

Loading…
Cancel
Save