Browse Source

Fix #2612 decompilation of newarr with int.MaxValue causes OOME in decompiler.

pull/2616/head
Siegfried Pammer 4 years ago
parent
commit
4aa7f5fc38
  1. 7
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
  2. 4
      ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs
  3. 86
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

7
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

@ -724,6 +724,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
{ null, "test", "hello", "world" } { null, "test", "hello", "world" }
}; };
} }
private static void OutOfMemory()
{
byte[] array = new byte[int.MaxValue];
array[0] = 1;
Console.WriteLine(array.Length);
}
#endregion #endregion
#region Object initializers #region Object initializers

4
ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs

@ -356,7 +356,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (value is NewArr typeArgsNewArr && typeArgsNewArr.Type.IsKnownType(KnownTypeCode.Type) && typeArgsNewArr.Indices.Count == 1 && typeArgsNewArr.Indices[0].MatchLdcI4(out numberOfTypeArguments)) if (value is NewArr typeArgsNewArr && typeArgsNewArr.Type.IsKnownType(KnownTypeCode.Type) && typeArgsNewArr.Indices.Count == 1 && typeArgsNewArr.Indices[0].MatchLdcI4(out numberOfTypeArguments))
{ {
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInitBlock, 3, variableOrTemporary, typeArgsNewArr.Type, new[] { numberOfTypeArguments }, out var typeArguments, out _)) if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInitBlock, 3, variableOrTemporary, new[] { numberOfTypeArguments }, out var typeArguments, out _))
return false; return false;
int i = 0; int i = 0;
callSiteInfo.TypeArguments = new IType[numberOfTypeArguments]; callSiteInfo.TypeArguments = new IType[numberOfTypeArguments];
@ -535,7 +535,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (!(value is NewArr newArr2 && newArr2.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr2.Indices.Count == 1 && newArr2.Indices[0].MatchLdcI4(out var numberOfArguments))) if (!(value is NewArr newArr2 && newArr2.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr2.Indices.Count == 1 && newArr2.Indices[0].MatchLdcI4(out var numberOfArguments)))
return false; return false;
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInfo.InitBlock, instructionOffset, variable, newArr2.Type, new[] { numberOfArguments }, out var arguments, out _)) if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInfo.InitBlock, instructionOffset, variable, new[] { numberOfArguments }, out var arguments, out _))
return false; return false;
int i = 0; int i = 0;
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments]; callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];

86
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -75,7 +75,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
if (arrayLength.Length == 1) if (arrayLength.Length == 1)
{ {
if (HandleSimpleArrayInitializer(function, body, pos + 1, v, elementType, arrayLength, out var arrayValues, out var instructionsToRemove)) if (HandleSimpleArrayInitializer(function, body, pos + 1, v, arrayLength, out var arrayValues, out var instructionsToRemove))
{ {
context.Step("HandleSimpleArrayInitializer: single-dim", inst); context.Step("HandleSimpleArrayInitializer: single-dim", inst);
var block = new Block(BlockKind.ArrayInitializer); var block = new Block(BlockKind.ArrayInitializer);
@ -172,7 +172,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInlining.InlineIfPossible(body, pos, context); ILInlining.InlineIfPossible(body, pos, context);
return true; return true;
} }
if (HandleSimpleArrayInitializer(function, body, pos + 1, v, elementType, length, out var arrayValues, out var instructionsToRemove)) if (HandleSimpleArrayInitializer(function, body, pos + 1, v, length, out var arrayValues, out var instructionsToRemove))
{ {
context.Step("HandleSimpleArrayInitializer: multi-dim", inst); context.Step("HandleSimpleArrayInitializer: multi-dim", inst);
var block = new Block(BlockKind.ArrayInitializer); var block = new Block(BlockKind.ArrayInitializer);
@ -387,12 +387,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Handle simple case where RuntimeHelpers.InitializeArray is not used. /// Handle simple case where RuntimeHelpers.InitializeArray is not used.
/// </summary> /// </summary>
internal static bool HandleSimpleArrayInitializer(ILFunction function, Block block, int pos, ILVariable store, IType elementType, int[] arrayLength, out (ILInstruction[] Indices, ILInstruction Value)[] values, out int instructionsToRemove) internal static bool HandleSimpleArrayInitializer(ILFunction function, Block block, int pos, ILVariable store, int[] arrayLength, out (ILInstruction[] Indices, ILInstruction Value)[] values, out int instructionsToRemove)
{ {
instructionsToRemove = 0; instructionsToRemove = 0;
int elementCount = 0; int elementCount = 0;
var length = arrayLength.Aggregate(1, (t, l) => t * l); int length = arrayLength.Aggregate(1, (t, l) => t * l);
values = new (ILInstruction[] Indices, ILInstruction Value)[length]; // Cannot pre-allocate the result array, because we do not know yet,
// whether there is in fact an array initializer.
// To prevent excessive allocations, use min(|block|, arraySize) als initial capacity.
// This should prevent list-resizing as well as out-of-memory errors.
values = null;
var valuesList = new List<(ILInstruction[] Indices, ILInstruction Value)>(Math.Min(block.Instructions.Count, length));
int[] nextMinimumIndex = new int[arrayLength.Length]; int[] nextMinimumIndex = new int[arrayLength.Length];
@ -443,7 +448,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return nextIndices; return nextIndices;
} }
int j = 0;
int i = pos; int i = pos;
int step; int step;
while (i < block.Instructions.Count) while (i < block.Instructions.Count)
@ -483,7 +487,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (indices.Count != arrayLength.Length) if (indices.Count != arrayLength.Length)
break; break;
bool exact; bool exact;
if (j >= values.Length) if (length <= 0)
break; break;
do do
{ {
@ -492,16 +496,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (exact) if (exact)
{ {
values[j] = (nextIndices, value); valuesList.Add((nextIndices, value));
elementCount++; elementCount++;
instructionsToRemove += step; instructionsToRemove += step;
} }
else else
{ {
values[j] = (nextIndices, null); valuesList.Add((nextIndices, null));
} }
j++; } while (valuesList.Count < length && !exact);
} while (j < values.Length && !exact);
i += step; i += step;
} }
if (i < block.Instructions.Count) if (i < block.Instructions.Count)
@ -514,53 +517,59 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
} }
while (j < values.Length) if (pos + instructionsToRemove >= block.Instructions.Count)
return false;
bool mustTransform = ILInlining.IsCatchWhenBlock(block) || ILInlining.IsInConstructorInitializer(function, block.Instructions[pos]);
// If there are not enough user-defined elements after scanning all instructions, we can abort the transform.
// This avoids unnecessary allocations and OOM crashes (in case of int.MaxValue).
if (elementCount < length / 3 - 5)
{
if (!mustTransform)
return false;
// .NET does not allow allocation of arrays >= 2 GB.
if (length >= int.MaxValue)
return false;
}
for (int j = valuesList.Count; j < length; j++)
{ {
var nextIndices = CalculateNextIndices(null, out _); var nextIndices = CalculateNextIndices(null, out _);
if (nextIndices == null) if (nextIndices == null)
return false; return false;
values[j] = (nextIndices, null); valuesList.Add((nextIndices, null));
j++;
} }
if (pos + instructionsToRemove >= block.Instructions.Count) values = valuesList.ToArray();
return false; return true;
return ShouldTransformToInitializer(function, block, pos, elementCount, length);
}
static bool ShouldTransformToInitializer(ILFunction function, Block block, int startPos, int elementCount, int length)
{
if (elementCount == 0)
return false;
if (elementCount >= length / 3 - 5)
return true;
if (ILInlining.IsCatchWhenBlock(block) || ILInlining.IsInConstructorInitializer(function, block.Instructions[startPos]))
return true;
return false;
} }
bool HandleJaggedArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILVariable finalStore, out ILInstruction[] values, out int instructionsToRemove) bool HandleJaggedArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILVariable finalStore, out ILInstruction[] values, out int instructionsToRemove)
{ {
instructionsToRemove = 0; instructionsToRemove = 0;
finalStore = null; finalStore = null;
values = new ILInstruction[length]; // Cannot pre-allocate the result array, because we do not know yet,
// whether there is in fact an array initializer.
// To prevent excessive allocations, use min(|block|, arraySize) als initial capacity.
// This should prevent list-resizing as well as out-of-memory errors.
values = null;
var valuesList = new List<ILInstruction>(Math.Min(block.Instructions.Count, length));
ILInstruction initializer;
IType type;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
// 1. Instruction: (optional) temporary copy of store // 1. Instruction: (optional) temporary copy of store
bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out var temp, out var storeLoad) && storeLoad.MatchLdLoc(store); bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out var temp, out var storeLoad) && storeLoad.MatchLdLoc(store);
ILInstruction initializer;
if (hasTemporaryCopy) if (hasTemporaryCopy)
{ {
if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out type)) if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out _))
return false; return false;
} }
else else
{ {
if (!MatchJaggedArrayStore(block, pos, store, i, out initializer, out type)) if (!MatchJaggedArrayStore(block, pos, store, i, out initializer, out _))
return false; return false;
} }
values[i] = initializer; valuesList.Add(initializer);
int inc = hasTemporaryCopy ? 3 : 2; int inc = hasTemporaryCopy ? 3 : 2;
pos += inc; pos += inc;
instructionsToRemove += inc; instructionsToRemove += inc;
@ -569,10 +578,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Remove it and use its value instead. // Remove it and use its value instead.
if (block.Instructions[pos].MatchStLoc(out finalStore, out var array)) if (block.Instructions[pos].MatchStLoc(out finalStore, out var array))
{ {
if (!array.MatchLdLoc(store))
return false;
instructionsToRemove++; instructionsToRemove++;
return array.MatchLdLoc(store);
} }
finalStore = store; else
{
finalStore = store;
}
values = valuesList.ToArray();
return true; return true;
} }

Loading…
Cancel
Save