Browse Source

Implement support for Visual Basic yield return state machines

pull/2874/head
ElektroKill 3 years ago
parent
commit
b110d5c2dc
No known key found for this signature in database
GPG Key ID: 7E3C5C084E40E3EC
  1. 4
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 4
      ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
  3. 263
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  4. 7
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  5. 10
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -1804,6 +1804,10 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden);
} }
if (function.StateMachineCompiledWithLegacyVisualBasic)
{
RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough);
}
} }
if (function.IsAsync) if (function.IsAsync)
{ {

4
ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs

@ -1,4 +1,4 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// //
// 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
@ -118,7 +118,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
public SymbolicValue Eval(ILInstruction inst) public SymbolicValue Eval(ILInstruction inst)
{ {
if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.CheckForOverflow) if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub)
{ {
var left = Eval(bni.Left); var left = Eval(bni.Left);
var right = Eval(bni.Right); var right = Eval(bni.Right);

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

@ -1,4 +1,4 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// //
// 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
@ -59,6 +59,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks> /// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
bool isCompiledWithMono; bool isCompiledWithMono;
/// <remarks>Set in MatchEnumeratorCreationPattern() or ConstructExceptionTable()</remarks>
bool isCompiledWithVisualBasic;
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
/// <remarks>If this is true, then isCompiledWithVisualBasic is also true.</remarks>
bool isCompiledWithLegacyVisualBasic;
/// <summary>The dispose method of the compiler-generated enumerator class.</summary> /// <summary>The dispose method of the compiler-generated enumerator class.</summary>
/// <remarks>Set in ConstructExceptionTable()</remarks> /// <remarks>Set in ConstructExceptionTable()</remarks>
MethodDefinitionHandle disposeMethod; MethodDefinitionHandle disposeMethod;
@ -100,6 +107,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary> /// </summary>
ILVariable skipFinallyBodies; ILVariable skipFinallyBodies;
/// <summary>
/// Local bool variable in MoveNext() that signifies whether to execute finally bodies.
/// </summary>
ILVariable doFinallyBodies;
/// <summary> /// <summary>
/// Set of variables might hold copies of the generated state field. /// Set of variables might hold copies of the generated state field.
/// </summary> /// </summary>
@ -115,6 +127,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
this.currentType = metadata.GetMethodDefinition((MethodDefinitionHandle)context.Function.Method.MetadataToken).GetDeclaringType(); this.currentType = metadata.GetMethodDefinition((MethodDefinitionHandle)context.Function.Method.MetadataToken).GetDeclaringType();
this.enumeratorType = default; this.enumeratorType = default;
this.enumeratorCtor = default; this.enumeratorCtor = default;
this.isCompiledWithMono = false;
this.isCompiledWithVisualBasic = false;
this.isCompiledWithLegacyVisualBasic = false;
this.stateField = null; this.stateField = null;
this.currentField = null; this.currentField = null;
this.disposingField = null; this.disposingField = null;
@ -123,6 +138,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
this.decompiledFinallyMethods.Clear(); this.decompiledFinallyMethods.Clear();
this.returnStores.Clear(); this.returnStores.Clear();
this.skipFinallyBodies = null; this.skipFinallyBodies = null;
this.doFinallyBodies = null;
this.cachedStateVars = null; this.cachedStateVars = null;
if (!MatchEnumeratorCreationPattern(function)) if (!MatchEnumeratorCreationPattern(function))
return; return;
@ -144,6 +160,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.Step("Replacing body with MoveNext() body", function); context.Step("Replacing body with MoveNext() body", function);
function.IsIterator = true; function.IsIterator = true;
function.StateMachineCompiledWithMono = isCompiledWithMono; function.StateMachineCompiledWithMono = isCompiledWithMono;
function.StateMachineCompiledWithLegacyVisualBasic = isCompiledWithLegacyVisualBasic;
var oldBody = function.Body; var oldBody = function.Body;
function.Body = newBody; function.Body = newBody;
// register any locals used in newBody // register any locals used in newBody
@ -159,7 +176,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.Step("Delete unreachable blocks", function); context.Step("Delete unreachable blocks", function);
if (isCompiledWithMono) if (isCompiledWithMono || isCompiledWithVisualBasic)
{ {
// mono has try-finally inline (like async on MS); we also need to sort nested blocks: // mono has try-finally inline (like async on MS); we also need to sort nested blocks:
foreach (var nestedContainer in newBody.Blocks.SelectMany(c => c.Descendants).OfType<BlockContainer>()) foreach (var nestedContainer in newBody.Blocks.SelectMany(c => c.Descendants).OfType<BlockContainer>())
@ -180,6 +197,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
CleanSkipFinallyBodies(function); CleanSkipFinallyBodies(function);
} }
else if (isCompiledWithLegacyVisualBasic)
{
CleanDoFinallyBodies(function);
}
else if (isCompiledWithVisualBasic)
{
CleanFinallyStateChecks(function);
}
else else
{ {
DecompileFinallyBlocks(); DecompileFinallyBlocks();
@ -192,6 +217,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.Step("Transform failed, roll it back", function); context.Step("Transform failed, roll it back", function);
function.IsIterator = false; function.IsIterator = false;
function.StateMachineCompiledWithMono = false; function.StateMachineCompiledWithMono = false;
function.StateMachineCompiledWithLegacyVisualBasic = false;
function.Body = oldBody; function.Body = oldBody;
function.Variables.RemoveDead(); function.Variables.RemoveDead();
function.Warnings.Add($"yield-return decompiler failed: {ex.Message}"); function.Warnings.Add($"yield-return decompiler failed: {ex.Message}");
@ -202,7 +228,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
TranslateFieldsToLocalAccess(function, function, fieldToParameterMap, isCompiledWithMono); TranslateFieldsToLocalAccess(function, function, fieldToParameterMap, isCompiledWithMono);
// On mono, we still need to remove traces of the state variable(s): // On mono, we still need to remove traces of the state variable(s):
if (isCompiledWithMono) if (isCompiledWithMono || isCompiledWithVisualBasic)
{ {
if (fieldToParameterMap.TryGetValue(stateField, out var stateVar)) if (fieldToParameterMap.TryGetValue(stateField, out var stateVar))
{ {
@ -275,18 +301,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (MatchEnumeratorCreationNewObj(newObj)) if (MatchEnumeratorCreationNewObj(newObj))
{ {
pos++; // OK pos++; // OK
isCompiledWithMono = false;
} }
else if (MatchMonoEnumeratorCreationNewObj(newObj)) else if (MatchMonoEnumeratorCreationNewObj(newObj))
{ {
pos++; pos++;
if (TransformDisplayClassUsage.ValidateConstructor(context, ((NewObj)newObj).Method))
{
isCompiledWithMono = true; isCompiledWithMono = true;
} }
else else
{
isCompiledWithVisualBasic = true;
isCompiledWithLegacyVisualBasic = true;
}
}
else
{ {
return false; return false;
} }
bool stateFieldInitialized = false;
for (; pos < body.Instructions.Count; pos++) for (; pos < body.Instructions.Count; pos++)
{ {
// stfld(..., ldloc(var_1), ldloc(parameter)) // stfld(..., ldloc(var_1), ldloc(parameter))
@ -306,6 +340,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// copy of 'this' in struct // copy of 'this' in struct
fieldToParameterMap[(IField)storedField.MemberDefinition] = ((LdLoc)ldobj.Target).Variable; fieldToParameterMap[(IField)storedField.MemberDefinition] = ((LdLoc)ldobj.Target).Variable;
} }
else if ((isCompiledWithMono || isCompiledWithLegacyVisualBasic) && (value.MatchLdcI4(-2) || value.MatchLdcI4(-1) || value.MatchLdcI4(0)))
{
stateField = (IField)storedField.MemberDefinition;
stateFieldInitialized = true;
}
else else
{ {
return false; return false;
@ -319,7 +358,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// stloc(var_2, ldloc(var_1)) // stloc(var_2, ldloc(var_1))
pos++; pos++;
} }
if (isCompiledWithMono) if (isCompiledWithMono && !stateFieldInitialized)
{ {
// Mono initializes the state field separately: // Mono initializes the state field separately:
// (but not if it's left at the default value 0) // (but not if it's left at the default value 0)
@ -328,7 +367,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& (value.MatchLdcI4(-2) || value.MatchLdcI4(0))) && (value.MatchLdcI4(-2) || value.MatchLdcI4(0)))
{ {
stateField = (IField)field.MemberDefinition; stateField = (IField)field.MemberDefinition;
isCompiledWithMono = true;
pos++; pos++;
} }
} }
@ -477,8 +515,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void AnalyzeCurrentProperty() void AnalyzeCurrentProperty()
{ {
MethodDefinitionHandle getCurrentMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault( MethodDefinitionHandle getCurrentMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(
m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) m => IsMethod(m, "get_Current"));
&& metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".get_Current", StringComparison.Ordinal));
Block body = SingleBlock(CreateILAst(getCurrentMethod, context).Body); Block body = SingleBlock(CreateILAst(getCurrentMethod, context).Body);
if (body == null) if (body == null)
throw new SymbolicAnalysisFailedException("get_Current has no body"); throw new SymbolicAnalysisFailedException("get_Current has no body");
@ -516,8 +553,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void ResolveIEnumerableIEnumeratorFieldMapping() void ResolveIEnumerableIEnumeratorFieldMapping()
{ {
MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault( MethodDefinitionHandle getEnumeratorMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(
m => metadata.GetString(metadata.GetMethodDefinition(m).Name).StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) m => IsMethod(m, "GetEnumerator"));
&& metadata.GetString(metadata.GetMethodDefinition(m).Name).EndsWith(".GetEnumerator", StringComparison.Ordinal));
ResolveIEnumerableIEnumeratorFieldMapping(getEnumeratorMethod, context, fieldToParameterMap); ResolveIEnumerableIEnumeratorFieldMapping(getEnumeratorMethod, context, fieldToParameterMap);
} }
@ -551,12 +587,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void ConstructExceptionTable() void ConstructExceptionTable()
{ {
if (isCompiledWithMono) disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => IsMethod(m, "Dispose"));
{
disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "Dispose");
var function = CreateILAst(disposeMethod, context); var function = CreateILAst(disposeMethod, context);
if (!isCompiledWithVisualBasic && !isCompiledWithMono)
{
BlockContainer body = (BlockContainer)function.Body; BlockContainer body = (BlockContainer)function.Body;
foreach (var instr in body.Blocks.SelectMany(block => block.Instructions))
{
if (instr is CallInstruction call && call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis() &&
IsMethod((MethodDefinitionHandle)call.Method.MetadataToken, "MoveNext"))
{
isCompiledWithVisualBasic = true;
break;
}
}
}
if (isCompiledWithMono || isCompiledWithVisualBasic)
{
BlockContainer body = (BlockContainer)function.Body;
for (var i = 0; (i < body.EntryPoint.Instructions.Count) && !(body.EntryPoint.Instructions[i] is Branch); i++) for (var i = 0; (i < body.EntryPoint.Instructions.Count) && !(body.EntryPoint.Instructions[i] is Branch); i++)
{ {
if (body.EntryPoint.Instructions[i] is StObj stobj if (body.EntryPoint.Instructions[i] is StObj stobj
@ -570,14 +620,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
// On mono, we don't need to analyse Dispose() to reconstruct the try-finally structure. // On mono and VB, we don't need to analyse Dispose() to reconstruct the try-finally structure.
finallyMethodToStateRange = default; finallyMethodToStateRange = default;
} }
else else
{ {
// Non-Mono: analyze try-finally structure in Dispose() // Non-Mono/Non-VB: analyze try-finally structure in Dispose()
disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "System.IDisposable.Dispose");
var function = CreateILAst(disposeMethod, context);
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField); var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField);
rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe); rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe);
finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange; finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange;
@ -616,6 +664,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
CopyPropagation.Propagate(stloc, context); CopyPropagation.Propagate(stloc, context);
} }
// Copy propagate stack slots holding a 32 bit integer.
foreach (var stloc in moveNextFunction.Descendants.OfType<StLoc>().Where(s => s.Variable.Kind == VariableKind.StackSlot && s.Variable.IsSingleDefinition && s.Value.OpCode == OpCode.LdcI4).ToList())
{
CopyPropagation.Propagate(stloc, context);
}
foreach (var block in moveNextFunction.Descendants.OfType<Block>())
{
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4);
}
var body = (BlockContainer)moveNextFunction.Body; var body = (BlockContainer)moveNextFunction.Body;
if (body.Blocks.Count == 1 && body.Blocks[0].Instructions.Count == 1 && body.Blocks[0].Instructions[0] is TryFault tryFault) if (body.Blocks.Count == 1 && body.Blocks[0].Instructions.Count == 1 && body.Blocks[0].Instructions[0] is TryFault tryFault)
{ {
@ -635,6 +694,35 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
if (isCompiledWithLegacyVisualBasic && (body.Blocks.Count == 2 || body.Blocks.Count == 1) &&
body.Blocks[0].Instructions.Count == 2 &&
body.Blocks[0].Instructions[0].MatchStLoc(out var firstVar, out var ldc) && ldc.MatchLdcI4(1))
{
doFinallyBodies = firstVar;
if (body.Blocks[0].Instructions[1] is TryCatch tryCatch && tryCatch.Handlers.Count == 1)
{
TryCatchHandler catchHandler = tryCatch.Handlers[0];
var catchBlockContainer = catchHandler.Body as BlockContainer;
if (catchBlockContainer?.Blocks.Count != 1)
throw new SymbolicAnalysisFailedException("Unexpected number of blocks in MoveNext() catch block");
var catchBlock = catchBlockContainer.Blocks.Single();
if (!(catchBlock.Instructions.Count == 4 && catchBlock.Instructions[0] is Call call &&
call.Method.Name == "SetProjectError" && call.Arguments.Count == 1 &&
call.Arguments[0].MatchLdLoc(catchHandler.Variable) &&
catchBlock.Instructions[1].MatchStLoc(out _, out var ldloc) &&
ldloc.MatchLdLoc(catchHandler.Variable) &&
catchBlock.Instructions[2].MatchStFld(out var ldThis, out var fld, out var value) &&
ldThis.MatchLdThis() && fld.MemberDefinition.Equals(stateField) && value is LdcI4 &&
catchBlock.Instructions[3] is Rethrow))
throw new SymbolicAnalysisFailedException("Unexpected catch block contents in MoveNext()");
BlockContainer tryCatchBody = (BlockContainer)tryCatch.TryBlock;
// Move return block
if (body.Blocks.Count == 2)
tryCatchBody.Blocks.Add(body.Blocks[1]);
body = tryCatchBody;
}
}
if (stateField == null) if (stateField == null)
{ {
// With mono-compiled state machines, it's possible that we haven't discovered the state field // With mono-compiled state machines, it's possible that we haven't discovered the state field
@ -690,6 +778,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField); var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField);
rangeAnalysis.skipFinallyBodies = skipFinallyBodies; rangeAnalysis.skipFinallyBodies = skipFinallyBodies;
rangeAnalysis.doFinallyBodies = doFinallyBodies;
rangeAnalysis.CancellationToken = context.CancellationToken; rangeAnalysis.CancellationToken = context.CancellationToken;
rangeAnalysis.AssignStateRanges(body, LongSet.Universe); rangeAnalysis.AssignStateRanges(body, LongSet.Universe);
cachedStateVars = rangeAnalysis.CachedStateVars.ToHashSet(); cachedStateVars = rangeAnalysis.CachedStateVars.ToHashSet();
@ -813,7 +902,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// (this allows us to consider each block individually for try-finally reconstruction) // (this allows us to consider each block individually for try-finally reconstruction)
newBlock = SplitBlock(newBlock, oldInst); newBlock = SplitBlock(newBlock, oldInst);
} }
else if (oldInst is TryFinally tryFinally && isCompiledWithMono) else if (oldInst is TryFinally tryFinally && (isCompiledWithMono || isCompiledWithVisualBasic))
{ {
// with mono, we have to recurse into try-finally blocks // with mono, we have to recurse into try-finally blocks
var oldTryBlock = (BlockContainer)tryFinally.TryBlock; var oldTryBlock = (BlockContainer)tryFinally.TryBlock;
@ -821,6 +910,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
sra.AssignStateRanges(oldTryBlock, LongSet.Universe); sra.AssignStateRanges(oldTryBlock, LongSet.Universe);
tryFinally.TryBlock = ConvertBody(oldTryBlock, sra); tryFinally.TryBlock = ConvertBody(oldTryBlock, sra);
} }
else if (isCompiledWithLegacyVisualBasic && oldInst is IfInstruction ifInstruction &&
ifInstruction.FalseInst.MatchNop() &&
ifInstruction.Condition.MatchCompEquals(out var left, out var right) &&
left.MatchLdFld(out var ldThis, out var fld) && ldThis.MatchLdThis() &&
fld.MemberDefinition.Equals(disposingField) &&
right.MatchLdcI4(0))
{
newBlock.Instructions.Add(ifInstruction.TrueInst);
newBlock.AddILRange(ifInstruction.TrueInst);
UpdateBranchTargets(ifInstruction.TrueInst);
break;
}
// copy over the instruction to the new block // copy over the instruction to the new block
newBlock.Instructions.Add(oldInst); newBlock.Instructions.Add(oldInst);
newBlock.AddILRange(oldInst); newBlock.AddILRange(oldInst);
@ -828,12 +930,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
// Insert new artificial block as entry point, and jump to state 0. // Insert new artificial block as entry point, and jump to the initial state.
// This causes the method to start directly at the first user code, // This causes the method to start directly at the first user code,
// and the whole compiler-generated state-dispatching logic becomes unreachable code // and the whole compiler-generated state-dispatching logic becomes unreachable code
// and gets deleted. // and gets deleted.
int initialState = isCompiledWithLegacyVisualBasic ? -1 : 0;
newBody.Blocks.Insert(0, new Block { newBody.Blocks.Insert(0, new Block {
Instructions = { MakeGoTo(0) } Instructions = { MakeGoTo(initialState) }
}); });
return newBody; return newBody;
@ -870,10 +973,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
if (oldBlock.Instructions[pos].MatchStFld(out var target, out var field, out var value) // Visual Basic Compiler emits additional stores to variables.
int? localNewState = null;
if (oldBlock.Instructions[pos].MatchStLoc(out _, out var value) && value is LdcI4 ldci4)
{
localNewState = ldci4.Value;
pos++;
}
if (oldBlock.Instructions[pos].MatchStFld(out var target, out var field, out value)
&& target.MatchLdThis() && target.MatchLdThis()
&& field.MemberDefinition == stateField && field.MemberDefinition == stateField
&& value.MatchLdcI4(out int newState)) && value.MatchLdcI4(out int newState)
&& (localNewState is null || localNewState == newState))
{ {
pos++; pos++;
} }
@ -899,6 +1011,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
pos++; pos++;
} }
// We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables.
// This occurs for the Legacy VBC compiler.
if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies.Index)
{
if (!value.MatchLdcI4(0))
{
newBlock.Instructions.Add(new InvalidExpression {
ExpectedResultType = StackType.Void,
Message = "Unexpected assignment to doFinallyBodies"
});
}
pos++;
}
if (oldBlock.Instructions[pos].MatchReturn(out var retVal) && retVal.MatchLdcI4(1)) if (oldBlock.Instructions[pos].MatchReturn(out var retVal) && retVal.MatchLdcI4(1))
{ {
@ -1251,6 +1376,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
#endregion #endregion
#region Cleanup finally blocks
/// <summary> /// <summary>
/// Eliminates usage of doFinallyBodies /// Eliminates usage of doFinallyBodies
@ -1264,7 +1390,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.StepStartGroup("CleanFinallyBlocks", function); context.StepStartGroup("CleanFinallyBlocks", function);
if (skipFinallyBodies.StoreInstructions.Count != 0 || skipFinallyBodies.AddressCount != 0) if (skipFinallyBodies.StoreInstructions.Count != 0 || skipFinallyBodies.AddressCount != 0)
{ {
// misdetected another variable as doFinallyBodies? // misdetected another variable as skipFinallyBodies?
// Fortunately removing the initial store of 0 is harmless, as we // Fortunately removing the initial store of 0 is harmless, as we
// default-initialize the variable on uninit uses // default-initialize the variable on uninit uses
return; return;
@ -1318,5 +1444,96 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return !call.Method.MetadataToken.IsNil; return !call.Method.MetadataToken.IsNil;
} }
} }
private void CleanDoFinallyBodies(ILFunction function)
{
if (doFinallyBodies == null)
{
return; // only VB code uses doFinallyBodies
}
context.StepStartGroup("CleanDoFinallyBodies", function);
if (doFinallyBodies.StoreInstructions.Count != 0 || doFinallyBodies.AddressCount != 0)
{
// misdetected another variable as skipFinallyBodies?
// Fortunately removing the initial store of 0 is harmless, as we
// default-initialize the variable on uninit uses
return;
}
foreach (var tryFinally in function.Descendants.OfType<TryFinally>())
{
if (!(tryFinally.FinallyBlock is BlockContainer container))
continue;
Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container);
if (entryPoint?.Instructions[0] is IfInstruction ifInst)
{
if (ifInst.Condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(doFinallyBodies) && right.MatchLdcI4(0))
{
context.Step("Remove if (doFinallyBodies) from try-finally", tryFinally);
// condition will always be false now that we're using 'yield' instructions
entryPoint.Instructions.RemoveAt(0);
}
}
}
foreach (LdLoc load in doFinallyBodies.LoadInstructions.ToArray())
{
load.ReplaceWith(new LdcI4(1).WithILRange(load));
}
context.StepEndGroup(keepIfEmpty: true);
}
private void CleanFinallyStateChecks(ILFunction function)
{
context.StepStartGroup("CleanFinallyStateChecks", function);
foreach (var tryFinally in function.Descendants.OfType<TryFinally>())
{
if (!(tryFinally.FinallyBlock is BlockContainer container))
continue;
Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container);
if (entryPoint?.Instructions[0] is IfInstruction ifInst)
{
if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.GreaterThanOrEqual &&
comp.InputType == StackType.I4 && comp.Sign == Sign.Signed && comp.Left.MatchLdLoc(out var variable) &&
cachedStateVars.Contains(variable) &&
comp.Right.MatchLdcI4(0))
{
context.Step("Remove if (stateVar >= 0) from try-finally", tryFinally);
// condition will always be false now that we're using 'yield' instructions
entryPoint.Instructions.RemoveAt(0);
}
}
}
context.StepEndGroup(keepIfEmpty: true);
}
#endregion
bool IsMethod(MethodDefinitionHandle method, string name)
{
var methodDefinition = metadata.GetMethodDefinition(method);
if (metadata.GetString(methodDefinition.Name) == name)
return true;
foreach (var implHandle in method.GetMethodImplementations(metadata))
{
var impl = metadata.GetMethodImplementation(implHandle);
switch (impl.MethodDeclaration.Kind)
{
case HandleKind.MethodDefinition:
var md = metadata.GetMethodDefinition((MethodDefinitionHandle)impl.MethodDeclaration);
if (metadata.GetString(md.Name) != name)
continue;
return true;
case HandleKind.MemberReference:
var mr = metadata.GetMemberReference((MemberReferenceHandle)impl.MethodDeclaration);
if (mr.GetKind() != MemberReferenceKind.Method)
continue;
if (metadata.GetString(mr.Name) != name)
continue;
return true;
default:
continue;
}
}
return false;
}
} }
} }

7
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -1,4 +1,4 @@
#nullable enable #nullable enable
// Copyright (c) 2014 Daniel Grunwald // Copyright (c) 2014 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
@ -98,6 +98,11 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
public bool StateMachineCompiledWithMono; public bool StateMachineCompiledWithMono;
/// <summary>
/// Gets whether the YieldReturnDecompiler determined that the Legacy VB compiler was used to compile this function.
/// </summary>
public bool StateMachineCompiledWithLegacyVisualBasic;
/// <summary> /// <summary>
/// Gets whether this function is async. /// Gets whether this function is async.
/// This flag gets set by the AsyncAwaitDecompiler. /// This flag gets set by the AsyncAwaitDecompiler.

10
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -1,4 +1,4 @@
// Copyright (c) 2019 Siegfried Pammer // Copyright (c) 2019 Siegfried Pammer
// //
// 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
@ -289,7 +289,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null; return null;
if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc)) if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc))
return null; return null;
if (stloc.Value is NewObj newObj && ValidateConstructor(newObj.Method)) if (stloc.Value is NewObj newObj && ValidateConstructor(context, newObj.Method))
{ {
result = new DisplayClass(v, definition) { result = new DisplayClass(v, definition) {
CaptureScope = v.CaptureScope, CaptureScope = v.CaptureScope,
@ -393,7 +393,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var definition = newObj.Method.DeclaringType.GetDefinition(); var definition = newObj.Method.DeclaringType.GetDefinition();
if (!ValidateDisplayClassDefinition(definition)) if (!ValidateDisplayClassDefinition(definition))
return null; return null;
if (!ValidateConstructor(newObj.Method)) if (!ValidateConstructor(context, newObj.Method))
return null; return null;
if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable)) if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable))
return null; return null;
@ -432,7 +432,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
private bool ValidateConstructor(IMethod method) internal static bool ValidateConstructor(ILTransformContext context, IMethod method)
{ {
try try
{ {
@ -491,7 +491,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader) static ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader)
{ {
ILOpCode code; ILOpCode code;
do do

Loading…
Cancel
Save