Browse Source

Replace LoopingBlockTransform with StatementTransform.

This transform interleaves statement-combining transforms so that nested structures can be detected better.
pull/881/merge
Daniel Grunwald 8 years ago
parent
commit
7017c6f6e6
  1. 3
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 18
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 9
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  5. 32
      ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
  6. 2
      ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
  7. 11
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  8. 24
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  9. 12
      ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
  10. 23
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  11. 26
      ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs
  12. 146
      ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs
  13. 44
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
  14. 22
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  15. 4
      ILSpy/Languages/ILAstLanguage.cs

3
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -170,9 +170,6 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public void CheckedUnchecked([ValueSource("defaultOptions")] CompilerOptions cscOptions) public void CheckedUnchecked([ValueSource("defaultOptions")] CompilerOptions cscOptions)
{ {
if (cscOptions.HasFlag(CompilerOptions.UseRoslyn) && cscOptions.HasFlag(CompilerOptions.Optimize)) {
Assert.Ignore("Roslyn opt replaces locals with stack slots, resulting in S_* variable names.");
}
Run(cscOptions: cscOptions); Run(cscOptions: cscOptions);
} }

18
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -110,15 +110,18 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformAssignment(), new TransformAssignment(),
new NullableLiftingBlockTransform(), new NullableLiftingBlockTransform(),
new CopyPropagation(), new CopyPropagation(),
new LoopingBlockTransform( new StatementTransform(
// per-block transforms that depend on each other, and thus need to loop (fixpoint iteration). // per-block transforms that depend on each other, and thus need to
// run interleaved (statement by statement).
// Pretty much all transforms that open up new expression inlining // Pretty much all transforms that open up new expression inlining
// opportunities belong in this category. // opportunities belong in this category.
new ILInlining(),
// Inlining must be first, because it doesn't trigger re-runs.
// Any other transform that opens up new inlining opportunities should call RequestRerun().
new ExpressionTransforms(), new ExpressionTransforms(),
new NullCoalescingTransform(), new NullCoalescingTransform(),
new TransformArrayInitializers(), new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers(), new TransformCollectionAndObjectInitializers()
new ILInlining()
) )
} }
}, },
@ -646,14 +649,15 @@ namespace ICSharpCode.Decompiler.CSharp
int i = 0; int i = 0;
var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var parameter in entityDecl.GetChildrenByRole(Roles.Parameter)) { foreach (var parameter in entityDecl.GetChildrenByRole(Roles.Parameter)) {
ILVariable v; if (parameters.TryGetValue(i, out var v))
if (parameters.TryGetValue(i, out v))
parameter.AddAnnotation(new ILVariableResolveResult(v, method.Parameters[i].Type)); parameter.AddAnnotation(new ILVariableResolveResult(v, method.Parameters[i].Type));
i++; i++;
} }
} }
var context = new ILTransformContext { Settings = settings, TypeSystem = specializingTypeSystem, CancellationToken = CancellationToken }; var context = new ILTransformContext(function, specializingTypeSystem, settings) {
CancellationToken = CancellationToken
};
foreach (var transform in ilTransforms) { foreach (var transform in ilTransforms) {
CancellationToken.ThrowIfCancellationRequested(); CancellationToken.ThrowIfCancellationRequested();
transform.Run(function, context); transform.Run(function, context);

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -288,6 +288,7 @@
<Compile Include="IL\Transforms\LockTransform.cs" /> <Compile Include="IL\Transforms\LockTransform.cs" />
<Compile Include="IL\Transforms\NullableLiftingTransform.cs" /> <Compile Include="IL\Transforms\NullableLiftingTransform.cs" />
<Compile Include="IL\Transforms\NullCoalescingTransform.cs" /> <Compile Include="IL\Transforms\NullCoalescingTransform.cs" />
<Compile Include="IL\Transforms\StatementTransform.cs" />
<Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" /> <Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" />
<Compile Include="Output\TextTokenWriter.cs" /> <Compile Include="Output\TextTokenWriter.cs" />
<Compile Include="Util\KeyComparer.cs" /> <Compile Include="Util\KeyComparer.cs" />

9
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -377,11 +377,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
); );
} }
var il = new ILReader(typeSystem).ReadIL(method.Body, context.CancellationToken); var il = new ILReader(typeSystem).ReadIL(method.Body, context.CancellationToken);
il.RunTransforms(CSharpDecompiler.EarlyILTransforms(true), new ILTransformContext { il.RunTransforms(CSharpDecompiler.EarlyILTransforms(true),
Settings = context.Settings, new ILTransformContext(il, typeSystem, context.Settings) {
CancellationToken = context.CancellationToken, CancellationToken = context.CancellationToken
TypeSystem = context.TypeSystem });
});
return il; return il;
} }
#endregion #endregion

32
ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.FlowAnalysis;
using ICSharpCode.Decompiler.IL.ControlFlow; using ICSharpCode.Decompiler.IL.ControlFlow;
@ -24,11 +26,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
public class BlockTransformContext : ILTransformContext public class BlockTransformContext : ILTransformContext
{ {
/// <summary>
/// The function containing the block currently being processed.
/// </summary>
public ILFunction Function { get; set; }
/// <summary> /// <summary>
/// The block to process. /// The block to process.
/// </summary> /// </summary>
@ -69,6 +66,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public IList<IBlockTransform> PreOrderTransforms { get; } = new List<IBlockTransform>(); public IList<IBlockTransform> PreOrderTransforms { get; } = new List<IBlockTransform>();
public IList<IBlockTransform> PostOrderTransforms { get; } = new List<IBlockTransform>(); public IList<IBlockTransform> PostOrderTransforms { get; } = new List<IBlockTransform>();
bool running;
public override string ToString() public override string ToString()
{ {
return $"{nameof(BlockILTransform)} ({string.Join(", ", PreOrderTransforms.Concat(PostOrderTransforms).Select(t => t.GetType().Name))})"; return $"{nameof(BlockILTransform)} ({string.Join(", ", PreOrderTransforms.Concat(PostOrderTransforms).Select(t => t.GetType().Name))})";
@ -76,13 +75,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
{ {
var blockContext = new BlockTransformContext(context); if (running)
blockContext.Function = function; throw new InvalidOperationException("Reentrancy detected. Transforms (and the CSharpDecompiler) are neither neither thread-safe nor re-entrant.");
foreach (var container in function.Descendants.OfType<BlockContainer>().ToList()) { try {
context.CancellationToken.ThrowIfCancellationRequested(); running = true;
blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken); var blockContext = new BlockTransformContext(context);
VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext); Debug.Assert(blockContext.Function == function);
// TODO: handle unreachable code? foreach (var container in function.Descendants.OfType<BlockContainer>().ToList()) {
context.CancellationToken.ThrowIfCancellationRequested();
blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken);
VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext);
// TODO: handle unreachable code?
}
} finally {
running = false;
} }
} }

2
ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs

@ -38,7 +38,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
continue; continue;
} }
if (CachedDelegateInitializationWithLocal(inst)) { if (CachedDelegateInitializationWithLocal(inst)) {
ILInlining.InlineIfPossible(block, ref i, context); ILInlining.InlineOneIfPossible(block, i, true, context);
continue; continue;
} }
if (CachedDelegateInitializationRoslynInStaticWithLocal(inst) || CachedDelegateInitializationRoslynWithLocal(inst)) { if (CachedDelegateInitializationRoslynInStaticWithLocal(inst) || CachedDelegateInitializationRoslynWithLocal(inst)) {

11
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction); return opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction);
} }
internal static bool IsPotentialClosure(BlockTransformContext context, NewObj inst) internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
{ {
var decompilationContext = new SimpleTypeResolveContext(context.TypeSystem.Resolve(context.Function.Method)); var decompilationContext = new SimpleTypeResolveContext(context.TypeSystem.Resolve(context.Function.Method));
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition); return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
@ -150,11 +150,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) {
v.Name = contextPrefix + v.Name; v.Name = contextPrefix + v.Name;
} }
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)), context); var nestedContext = new ILTransformContext(function, localTypeSystem, context.Settings) {
CancellationToken = context.CancellationToken
};
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)), nestedContext);
function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter))); function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter)));
// handle nested lambdas // handle nested lambdas
((IILTransform)new DelegateConstruction()).Run(function, new ILTransformContext(context) { TypeSystem = localTypeSystem }); ((IILTransform)new DelegateConstruction()).Run(function, nestedContext);
return function; return function;
} }
return null; return null;

24
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -29,16 +29,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <remarks> /// <remarks>
/// Should run after inlining so that the expression patterns can be detected. /// Should run after inlining so that the expression patterns can be detected.
/// </remarks> /// </remarks>
public class ExpressionTransforms : ILVisitor, IBlockTransform public class ExpressionTransforms : ILVisitor, IBlockTransform, IStatementTransform
{ {
internal ILTransformContext context; internal ILTransformContext context;
public void Run(Block block, BlockTransformContext context) public void Run(Block block, BlockTransformContext context)
{ {
this.context = context; this.context = context;
Default(block); Default(block);
} }
public void Run(Block block, int pos, StatementTransformContext context)
{
this.context = context;
context.StepStartGroup($"ExpressionTransforms ({block.Label}:{pos})", block.Instructions[pos]);
block.Instructions[pos].AcceptVisitor(this);
context.StepEndGroup(keepIfEmpty: true);
}
protected override void Default(ILInstruction inst) protected override void Default(ILInstruction inst)
{ {
foreach (var child in inst.Children) { foreach (var child in inst.Children) {
@ -310,6 +318,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitIfInstruction(IfInstruction inst) protected internal override void VisitIfInstruction(IfInstruction inst)
{ {
inst.TrueInst.AcceptVisitor(this);
inst.FalseInst.AcceptVisitor(this);
inst = HandleConditionalOperator(inst);
// Bring LogicAnd/LogicOr into their canonical forms: // Bring LogicAnd/LogicOr into their canonical forms:
// if (cond) ldc.i4 0 else RHS --> if (!cond) RHS else ldc.i4 0 // if (cond) ldc.i4 0 else RHS --> if (!cond) RHS else ldc.i4 0
// if (cond) RHS else ldc.i4 1 --> if (!cond) ldc.i4 1 else RHS // if (cond) RHS else ldc.i4 1 --> if (!cond) ldc.i4 1 else RHS
@ -324,10 +336,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.FalseInst = t; inst.FalseInst = t;
inst.Condition = Comp.LogicNot(inst.Condition); inst.Condition = Comp.LogicNot(inst.Condition);
} }
// Process condition after our potential modifications.
inst.Condition.AcceptVisitor(this);
base.VisitIfInstruction(inst);
inst = HandleConditionalOperator(inst);
if (new NullableLiftingTransform(context).Run(inst)) if (new NullableLiftingTransform(context).Run(inst))
return; return;
} }
@ -348,6 +359,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1); var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1);
newIf.ILRange = inst.ILRange; newIf.ILRange = inst.ILRange;
inst.ReplaceWith(new StLoc(v, newIf)); inst.ReplaceWith(new StLoc(v, newIf));
(context as StatementTransformContext)?.RequestRerun(); // trigger potential inlining of the newly created StLoc
return newIf; return newIf;
} }
return inst; return inst;

12
ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
@ -35,18 +36,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
public class ILTransformContext public class ILTransformContext
{ {
public IDecompilerTypeSystem TypeSystem { get; set; } public ILFunction Function { get; }
public DecompilerSettings Settings { get; set; } public IDecompilerTypeSystem TypeSystem { get; }
public DecompilerSettings Settings { get; }
public CancellationToken CancellationToken { get; set; } public CancellationToken CancellationToken { get; set; }
public Stepper Stepper { get; set; } public Stepper Stepper { get; set; }
public ILTransformContext() public ILTransformContext(ILFunction function, IDecompilerTypeSystem typeSystem, DecompilerSettings settings = null)
{ {
this.Function = function ?? throw new ArgumentNullException(nameof(function));
this.TypeSystem = typeSystem ?? throw new ArgumentNullException(nameof(typeSystem));
this.Settings = settings ?? new DecompilerSettings();
Stepper = new Stepper(); Stepper = new Stepper();
} }
public ILTransformContext(ILTransformContext context) public ILTransformContext(ILTransformContext context)
{ {
this.Function = context.Function;
this.TypeSystem = context.TypeSystem; this.TypeSystem = context.TypeSystem;
this.Settings = context.Settings; this.Settings = context.Settings;
this.CancellationToken = context.CancellationToken; this.CancellationToken = context.CancellationToken;

23
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -1,4 +1,4 @@
// Copyright (c) 2011-2015 Daniel Grunwald // Copyright (c) 2011-2017 Daniel Grunwald
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software // software and associated documentation files (the "Software"), to deal in the Software
@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Performs inlining transformations. /// Performs inlining transformations.
/// </summary> /// </summary>
public class ILInlining : IILTransform, IBlockTransform public class ILInlining : IILTransform, IBlockTransform, IStatementTransform
{ {
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
{ {
@ -41,6 +41,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
InlineAllInBlock(block, context); InlineAllInBlock(block, context);
} }
public void Run(Block block, int pos, StatementTransformContext context)
{
InlineOneIfPossible(block, pos, aggressive: IsCatchWhenBlock(block), context: context);
}
public static bool InlineAllInBlock(Block block, ILTransformContext context) public static bool InlineAllInBlock(Block block, ILTransformContext context)
{ {
bool modified = false; bool modified = false;
@ -83,20 +88,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. /// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
/// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction.
/// </summary> /// </summary>
/// <remarks> public static bool InlineIfPossible(Block block, int pos, ILTransformContext context)
/// After the operation, pos will point to the new combined instruction.
/// </remarks>
public static bool InlineIfPossible(Block block, ref int pos, ILTransformContext context)
{ {
if (InlineOneIfPossible(block, pos, true, context)) { return InlineOneIfPossible(block, pos, true, context);
pos -= InlineInto(block, pos, false, context);
return true;
}
return false;
} }
/// <summary> /// <summary>
/// Inlines the stloc instruction at block.Instructions[pos] into the next instruction, if possible. /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction, if possible.
/// </summary> /// </summary>

26
ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs

@ -30,19 +30,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// ///
/// The ?? operator for nullables is handled by NullableLiftingTransform. /// The ?? operator for nullables is handled by NullableLiftingTransform.
/// </summary> /// </summary>
class NullCoalescingTransform : IBlockTransform class NullCoalescingTransform : IStatementTransform
{ {
BlockTransformContext context; public void Run(Block block, int pos, StatementTransformContext context)
void IBlockTransform.Run(Block block, BlockTransformContext context)
{ {
this.context = context; TransformRefTypes(block, pos, context);
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (TransformRefTypes(block, i)) {
block.Instructions.RemoveAt(i);
continue;
}
}
} }
/// <summary> /// <summary>
@ -55,15 +47,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// => /// =>
/// stloc s(if.notnull(valueInst, fallbackInst)) /// stloc s(if.notnull(valueInst, fallbackInst))
/// </summary> /// </summary>
bool TransformRefTypes(Block block, int i) bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
{ {
if (i == 0) if (!(block.Instructions[pos] is StLoc stloc))
return false;
if (!(block.Instructions[i - 1] is StLoc stloc))
return false; return false;
if (stloc.Variable.Kind != VariableKind.StackSlot) if (stloc.Variable.Kind != VariableKind.StackSlot)
return false; return false;
if (!block.Instructions[i].MatchIfInstruction(out var condition, out var trueInst)) if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
return false; return false;
trueInst = Block.Unwrap(trueInst); trueInst = Block.Unwrap(trueInst);
if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull() if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull()
@ -71,7 +61,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
) { ) {
context.Step("NullCoalescingTransform (reference types)", stloc); context.Step("NullCoalescingTransform (reference types)", stloc);
stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
return true; // returning true removes the if instruction block.Instructions.RemoveAt(pos + 1); // remove if instruction
ILInlining.InlineOneIfPossible(block, pos, false, context);
return true;
} }
return false; return false;
} }

146
ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs

@ -0,0 +1,146 @@
// Copyright (c) 2017 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// IL transform that runs on a sequence of statements within a block.
/// </summary>
/// <remarks>
/// Interleaving different statement-combining transforms on a per-statement level
/// improves detection of nested constructs.
/// For example, array initializers can assume that each element assignment was already
/// reduced to a single statement, even if the element contains a high-level construct
/// detected by a different transform (e.g. object initializer).
/// </remarks>
public interface IStatementTransform
{
/// <summary>
/// Runs the transform on the statements within a block.
///
/// Note: the transform may only modify block.Instructions[pos..].
/// The transform will be called repeatedly for pos=block.Instructions.Count-1, pos=block.Instructions.Count-2, ..., pos=0.
/// </summary>
/// <param name="block">The current block.</param>
/// <param name="pos">The starting position where the transform is allowed to work.</param>
/// <param name="context">Additional parameters.</param>
/// <remarks>
/// Instructions prior to block.Instructions[pos] must not be modified.
/// It is valid to read such instructions, but not recommended as those have not been transformed yet.
/// </remarks>
void Run(Block block, int pos, StatementTransformContext context);
}
/// <summary>
/// Parameter class holding various arguments for <see cref="IStatementTransform.Run"/>.
/// </summary>
public class StatementTransformContext : ILTransformContext
{
public BlockTransformContext BlockContext { get; }
public StatementTransformContext(BlockTransformContext blockContext) : base(blockContext)
{
this.BlockContext = blockContext ?? throw new ArgumentNullException(nameof(blockContext));
}
/// <summary>
/// Gets the block on which the transform is running.
/// </summary>
public Block Block => BlockContext.Block;
internal bool rerunCurrentPosition;
internal int? rerunPosition;
/// <summary>
/// After the current statement transform has completed,
/// do not continue with the next statement transform at the same position.
/// Instead, re-run all statement transforms (including the current transform) starting at the specified position.
/// </summary>
public void RequestRerun(int pos)
{
if (rerunPosition == null || pos > rerunPosition) {
rerunPosition = pos;
}
}
/// <summary>
/// After the current statement transform has completed,
/// repeat all statement transforms on the current position.
/// </summary>
public void RequestRerun()
{
rerunCurrentPosition = true;
}
}
/// <summary>
/// Block transform that runs a list of statement transforms.
/// </summary>
public class StatementTransform : IBlockTransform
{
readonly IStatementTransform[] children;
public StatementTransform(params IStatementTransform[] children)
{
this.children = children;
}
public void Run(Block block, BlockTransformContext context)
{
var ctx = new StatementTransformContext(context);
int pos = 0;
ctx.rerunPosition = block.Instructions.Count - 1;
while (pos >= 0) {
if (ctx.rerunPosition != null) {
Debug.Assert(ctx.rerunPosition >= pos);
#if DEBUG
for (; pos < ctx.rerunPosition; ++pos) {
block.Instructions[pos].ResetDirty();
}
#else
pos = ctx.rerunPosition.Value;
#endif
Debug.Assert(pos == ctx.rerunPosition);
ctx.rerunPosition = null;
}
foreach (var transform in children) {
transform.Run(block, pos, ctx);
block.CheckInvariant(ILPhase.Normal);
#if DEBUG
for (int i = 0; i < pos; ++i) {
Debug.Assert(!block.Instructions[i].IsDirty, $"{transform.GetType().Name} modified an instruction before pos");
}
#endif
if (ctx.rerunCurrentPosition) {
ctx.rerunCurrentPosition = false;
ctx.RequestRerun(pos);
}
if (ctx.rerunPosition != null) {
break;
}
}
if (ctx.rerunPosition == null) {
pos--;
}
}
}
}
}

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

@ -29,16 +29,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray. /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray.
/// For collection and object initializers see <see cref="TransformCollectionAndObjectInitializers"/> /// For collection and object initializers see <see cref="TransformCollectionAndObjectInitializers"/>
/// </summary> /// </summary>
public class TransformArrayInitializers : IBlockTransform public class TransformArrayInitializers : IStatementTransform
{ {
BlockTransformContext context; StatementTransformContext context;
void IBlockTransform.Run(Block block, BlockTransformContext context) void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{ {
this.context = context; this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) { try {
if (!DoTransform(block, i)) if (!DoTransform(block, pos))
DoTransformMultiDim(block, i); DoTransformMultiDim(block, pos);
} finally {
this.context = null;
} }
} }
@ -58,9 +60,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step("ForwardScanInitializeArrayRuntimeHelper", inst); context.Step("ForwardScanInitializeArrayRuntimeHelper", inst);
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
var block = BlockFromInitializer(tempStore, elementType, arrayLength, values); var block = BlockFromInitializer(tempStore, elementType, arrayLength, values);
body.Instructions[pos].ReplaceWith(new StLoc(v, block)); body.Instructions[pos] = new StLoc(v, block);
body.Instructions.RemoveAt(initArrayPos); body.Instructions.RemoveAt(initArrayPos);
ILInlining.InlineIfPossible(body, ref pos, context); ILInlining.InlineIfPossible(body, pos, context);
return true; return true;
} }
if (arrayLength.Length == 1) { if (arrayLength.Length == 1) {
@ -78,9 +80,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
)); ));
block.FinalInstruction = new LdLoc(tempStore); block.FinalInstruction = new LdLoc(tempStore);
body.Instructions[pos].ReplaceWith(new StLoc(v, block)); body.Instructions[pos] = new StLoc(v, block);
RemoveInstructions(body, pos + 1, instructionsToRemove); body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
ILInlining.InlineIfPossible(body, ref pos, context); ILInlining.InlineIfPossible(body, pos, context);
return true; return true;
} }
if (HandleJaggedArrayInitializer(body, pos + 1, v, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove)) { if (HandleJaggedArrayInitializer(body, pos + 1, v, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove)) {
@ -90,9 +92,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType))); block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)));
block.FinalInstruction = new LdLoc(tempStore); block.FinalInstruction = new LdLoc(tempStore);
body.Instructions[pos].ReplaceWith(new StLoc(finalStore, block)); body.Instructions[pos] = new StLoc(finalStore, block);
RemoveInstructions(body, pos + 1, instructionsToRemove); body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
ILInlining.InlineIfPossible(body, ref pos, context); ILInlining.InlineIfPossible(body, pos, context);
return true; return true;
} }
} }
@ -129,7 +131,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
ITypeDefinition typeDef = elementType.GetEnumUnderlyingType().GetDefinition(); ITypeDefinition typeDef = elementType.GetEnumUnderlyingType().GetDefinition();
if (typeDef == null) if (typeDef == null)
return new LdNull(); return new DefaultValue(elementType);
switch (typeDef.KnownTypeCode) { switch (typeDef.KnownTypeCode) {
case KnownTypeCode.Boolean: case KnownTypeCode.Boolean:
case KnownTypeCode.Char: case KnownTypeCode.Char:
@ -153,18 +155,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case KnownTypeCode.IntPtr: case KnownTypeCode.IntPtr:
case KnownTypeCode.UIntPtr: case KnownTypeCode.UIntPtr:
default: default:
return new LdNull(); return new DefaultValue(elementType);
} }
} }
void RemoveInstructions(Block body, int start, int count)
{
for (int i = 0; i < count; i++) {
body.Instructions.RemoveAt(start);
}
}
/// <summary> /// <summary>
/// Handle simple case where RuntimeHelpers.InitializeArray is not used. /// Handle simple case where RuntimeHelpers.InitializeArray is not used.
/// </summary> /// </summary>
@ -259,7 +253,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var block = BlockFromInitializer(v, arrayType, length, values); var block = BlockFromInitializer(v, arrayType, length, values);
body.Instructions[pos].ReplaceWith(new StLoc(v, block)); body.Instructions[pos].ReplaceWith(new StLoc(v, block));
body.Instructions.RemoveAt(initArrayPos); body.Instructions.RemoveAt(initArrayPos);
ILInlining.InlineIfPossible(body, ref pos, context); ILInlining.InlineIfPossible(body, pos, context);
return true; return true;
} }
} }

22
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -26,20 +26,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Transforms collection and object initialization patterns. /// Transforms collection and object initialization patterns.
/// </summary> /// </summary>
public class TransformCollectionAndObjectInitializers : IBlockTransform public class TransformCollectionAndObjectInitializers : IStatementTransform
{ {
BlockTransformContext context; StatementTransformContext context;
void IBlockTransform.Run(Block block, BlockTransformContext context) void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{ {
this.context = context; this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) try {
{ DoTransform(block, pos);
DoTransform(block, i); } finally {
// This happens in some cases: this.context = null;
// Use correct index after transformation.
if (i >= block.Instructions.Count)
i = block.Instructions.Count;
} }
} }
@ -136,9 +133,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
initInst.ReplaceWith(initializerBlock); initInst.ReplaceWith(initializerBlock);
for (int i = 0; i < initializerItemsCount; i++) body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
body.Instructions.RemoveAt(pos + 1); ILInlining.InlineIfPossible(body, pos, context);
ILInlining.InlineIfPossible(body, ref pos, context);
} }
return true; return true;
} }

4
ILSpy/Languages/ILAstLanguage.cs

@ -161,9 +161,7 @@ namespace ICSharpCode.ILSpy
var reader = new ILReader(specializingTypeSystem); var reader = new ILReader(specializingTypeSystem);
reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols;
ILFunction il = reader.ReadIL(method.Body, options.CancellationToken); ILFunction il = reader.ReadIL(method.Body, options.CancellationToken);
ILTransformContext context = new ILTransformContext { ILTransformContext context = new ILTransformContext(il, typeSystem, options.DecompilerSettings) {
Settings = options.DecompilerSettings,
TypeSystem = typeSystem,
CancellationToken = options.CancellationToken CancellationToken = options.CancellationToken
}; };
context.Stepper.StepLimit = options.StepLimit; context.Stepper.StepLimit = options.StepLimit;

Loading…
Cancel
Save