|
|
@ -16,13 +16,14 @@ |
|
|
|
// 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.Linq; |
|
|
|
using System.Linq; |
|
|
|
using ICSharpCode.Decompiler.TypeSystem; |
|
|
|
using ICSharpCode.Decompiler.TypeSystem; |
|
|
|
|
|
|
|
|
|
|
|
namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
{ |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Description of TransformAssignment.
|
|
|
|
/// Constructs compound assignments and inline assignments.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public class TransformAssignment : IBlockTransform |
|
|
|
public class TransformAssignment : IBlockTransform |
|
|
|
{ |
|
|
|
{ |
|
|
@ -42,7 +43,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
} |
|
|
|
} |
|
|
|
if (TransformInlineAssignmentStObj(block, i)) |
|
|
|
if (TransformInlineAssignmentStObj(block, i)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
if (TransformInlineAssignmentCall(block, i)) |
|
|
|
if (TransformInlineCompoundAssignmentCall(block, i)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (TransformRoslynCompoundAssignmentCall(block, i)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -82,8 +85,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type); |
|
|
|
replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type); |
|
|
|
} else { // otherwise it must be local
|
|
|
|
} else { // otherwise it must be local
|
|
|
|
TransformInlineAssignmentLocal(block, i); |
|
|
|
return TransformInlineAssignmentLocal(block, i); |
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
context.Step("Inline assignment to instance field", fieldStore); |
|
|
|
context.Step("Inline assignment to instance field", fieldStore); |
|
|
|
local = localStore.Variable; |
|
|
|
local = localStore.Variable; |
|
|
@ -106,76 +108,151 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
/// <code>
|
|
|
|
/// <code>
|
|
|
|
/// stloc s(binary(callvirt(getter), value))
|
|
|
|
/// stloc s(binary(callvirt(getter), value))
|
|
|
|
/// callvirt (setter, ldloc s)
|
|
|
|
/// callvirt (setter, ldloc s)
|
|
|
|
/// ... usage of s ...
|
|
|
|
/// (followed by single usage of s in next instruction)
|
|
|
|
/// -->
|
|
|
|
|
|
|
|
/// ... compound.op.new(callvirt(getter), value) ...
|
|
|
|
|
|
|
|
/// </code>
|
|
|
|
|
|
|
|
/// -or-
|
|
|
|
|
|
|
|
/// <code>
|
|
|
|
|
|
|
|
/// stloc s(stloc v(binary(callvirt(getter), value)))
|
|
|
|
|
|
|
|
/// callvirt (setter, ldloc s)
|
|
|
|
|
|
|
|
/// ... usage of v ...
|
|
|
|
|
|
|
|
/// -->
|
|
|
|
/// -->
|
|
|
|
/// ... compound.op.new(callvirt(getter), value) ...
|
|
|
|
/// stloc s(compound.op.new(callvirt(getter), value))
|
|
|
|
/// </code>
|
|
|
|
/// </code>
|
|
|
|
bool TransformInlineAssignmentCall(Block block, int i) |
|
|
|
bool TransformInlineCompoundAssignmentCall(Block block, int i) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var inst = block.Instructions[i] as StLoc; |
|
|
|
var mainStLoc = block.Instructions[i] as StLoc; |
|
|
|
// in some cases it can be a compiler-generated local
|
|
|
|
// in some cases it can be a compiler-generated local
|
|
|
|
if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local)) |
|
|
|
if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
BinaryNumericInstruction binary = mainStLoc.Value as BinaryNumericInstruction; |
|
|
|
|
|
|
|
ILVariable localVariable = mainStLoc.Variable; |
|
|
|
|
|
|
|
if (!localVariable.IsSingleDefinition) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (localVariable.LoadCount != 2) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
BinaryNumericInstruction binary; |
|
|
|
|
|
|
|
ILVariable localVariable; |
|
|
|
|
|
|
|
if (inst.Value is StLoc) { |
|
|
|
|
|
|
|
var tmp = (StLoc)inst.Value; |
|
|
|
|
|
|
|
binary = tmp.Value as BinaryNumericInstruction; |
|
|
|
|
|
|
|
localVariable = tmp.Variable; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
binary = inst.Value as BinaryNumericInstruction; |
|
|
|
|
|
|
|
localVariable = inst.Variable; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var getterCall = binary?.Left as CallInstruction; |
|
|
|
var getterCall = binary?.Left as CallInstruction; |
|
|
|
var setterCall = block.Instructions.ElementAtOrDefault(i + 1) as CallInstruction; |
|
|
|
var setterCall = block.Instructions.ElementAtOrDefault(i + 1) as CallInstruction; |
|
|
|
if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) |
|
|
|
if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
var owner = getterCall.Method.AccessorOwner as IProperty; |
|
|
|
if (!setterCall.Arguments.Last().MatchLdLoc(localVariable)) |
|
|
|
if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter)) |
|
|
|
|
|
|
|
return false; |
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
var next = block.Instructions.ElementAtOrDefault(i + 2); |
|
|
|
var next = block.Instructions.ElementAtOrDefault(i + 2); |
|
|
|
if (next == null) |
|
|
|
if (next == null) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
var usages = next.Descendants.Where(d => d.MatchLdLoc(localVariable)).ToArray(); |
|
|
|
if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1) |
|
|
|
if (usages.Length != 1) |
|
|
|
|
|
|
|
return false; |
|
|
|
return false; |
|
|
|
context.Step($"Compound assignment to '{owner.Name}'", setterCall); |
|
|
|
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType)) |
|
|
|
block.Instructions.RemoveAt(i + 1); |
|
|
|
return false; |
|
|
|
block.Instructions.RemoveAt(i); |
|
|
|
context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); |
|
|
|
usages[0].ReplaceWith(new CompoundAssignmentInstruction( |
|
|
|
block.Instructions.RemoveAt(i + 1); // remove setter call
|
|
|
|
|
|
|
|
binary.ReplaceWith(new CompoundAssignmentInstruction( |
|
|
|
binary, getterCall, binary.Right, |
|
|
|
binary, getterCall, binary.Right, |
|
|
|
getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue)); |
|
|
|
getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue)); |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Roslyn compound assignment that's not inline within another instruction.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
bool TransformRoslynCompoundAssignmentCall(Block block, int i) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// stloc variable(callvirt get_Property(ldloc obj))
|
|
|
|
|
|
|
|
// callvirt set_Property(ldloc obj, binary.op(ldloc variable, ldc.i4 1))
|
|
|
|
|
|
|
|
// => compound.op.new(callvirt get_Property(ldloc obj), ldc.i4 1)
|
|
|
|
|
|
|
|
if (!(block.Instructions[i] is StLoc stloc)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!(stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 1)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
var getterCall = stloc.Value as CallInstruction; |
|
|
|
|
|
|
|
var setterCall = block.Instructions[i + 1] as CallInstruction; |
|
|
|
|
|
|
|
if (!(MatchingGetterAndSetterCalls(getterCall, setterCall))) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
var binary = setterCall.Arguments.Last() as BinaryNumericInstruction; |
|
|
|
|
|
|
|
if (binary == null || !binary.Left.MatchLdLoc(stloc.Variable)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); |
|
|
|
|
|
|
|
block.Instructions.RemoveAt(i + 1); // remove setter call
|
|
|
|
|
|
|
|
stloc.ReplaceWith(new CompoundAssignmentInstruction( |
|
|
|
|
|
|
|
binary, getterCall, binary.Right, |
|
|
|
|
|
|
|
getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue)); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
var owner = getterCall.Method.AccessorOwner as IProperty; |
|
|
|
|
|
|
|
if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
// Ensure that same arguments are passed to getterCall and setterCall:
|
|
|
|
|
|
|
|
for (int j = 0; j < getterCall.Arguments.Count; j++) { |
|
|
|
|
|
|
|
if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Transform compound assignments where the return value is not being used,
|
|
|
|
|
|
|
|
/// or where there's an inlined assignment within the setter call.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
|
|
/// Called by ExpressionTransforms.
|
|
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
|
|
internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
|
|
|
|
|
|
|
|
// ==> compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value)
|
|
|
|
|
|
|
|
var setterValue = setterCall.Arguments.LastOrDefault(); |
|
|
|
|
|
|
|
var storeInSetter = setterValue as StLoc; |
|
|
|
|
|
|
|
if (storeInSetter != null) { |
|
|
|
|
|
|
|
// callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
|
|
|
|
|
|
|
|
// ==> stloc v(compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value))
|
|
|
|
|
|
|
|
setterValue = storeInSetter.Value; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!(setterValue is BinaryNumericInstruction binary)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
var getterCall = binary.Left as CallInstruction; |
|
|
|
|
|
|
|
if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); |
|
|
|
|
|
|
|
ILInstruction newInst = new CompoundAssignmentInstruction( |
|
|
|
|
|
|
|
binary, getterCall, binary.Right, |
|
|
|
|
|
|
|
getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue); |
|
|
|
|
|
|
|
if (storeInSetter != null) { |
|
|
|
|
|
|
|
storeInSetter.Value = newInst; |
|
|
|
|
|
|
|
newInst = storeInSetter; |
|
|
|
|
|
|
|
context.RequestRerun(); // moving stloc to top-level might trigger inlining
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setterCall.ReplaceWith(newInst); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <code>
|
|
|
|
/// <code>
|
|
|
|
/// stloc s(value)
|
|
|
|
/// stloc s(value)
|
|
|
|
/// stloc l(ldloc s)
|
|
|
|
/// stloc l(ldloc s)
|
|
|
|
/// -->
|
|
|
|
/// -->
|
|
|
|
/// stloc s(stloc l(value))
|
|
|
|
/// stloc s(stloc l(value))
|
|
|
|
/// </code>
|
|
|
|
/// </code>
|
|
|
|
void TransformInlineAssignmentLocal(Block block, int i) |
|
|
|
bool TransformInlineAssignmentLocal(Block block, int i) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var inst = block.Instructions[i] as StLoc; |
|
|
|
var inst = block.Instructions[i] as StLoc; |
|
|
|
var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; |
|
|
|
var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; |
|
|
|
if (inst == null || nextInst == null) |
|
|
|
if (inst == null || nextInst == null) |
|
|
|
return; |
|
|
|
return false; |
|
|
|
if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdLoc(inst.Variable)) |
|
|
|
if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdLoc(inst.Variable)) |
|
|
|
return; |
|
|
|
return false; |
|
|
|
context.Step("Inline assignment to local variable", inst); |
|
|
|
context.Step("Inline assignment to local variable", inst); |
|
|
|
var value = inst.Value; |
|
|
|
var value = inst.Value; |
|
|
|
var var = nextInst.Variable; |
|
|
|
var var = nextInst.Variable; |
|
|
|
var stackVar = inst.Variable; |
|
|
|
var stackVar = inst.Variable; |
|
|
|
block.Instructions.RemoveAt(i); |
|
|
|
block.Instructions.RemoveAt(i); |
|
|
|
nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value))); |
|
|
|
nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value))); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <code>
|
|
|
|
/// <code>
|
|
|
|