@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks>
/// </remarks>
public class ILReader
public class ILReader
{
{
/// <summary>
/// Represents a block of IL instructions.
/// </summary>
private sealed class ImportedBlock
{
// These members are immediately available after construction:
public readonly Block Block = new Block ( BlockKind . ControlFlow ) ;
public ImmutableStack < ILVariable > InputStack ;
public int StartILOffset = > Block . StartILOffset ;
/// True if the import is in progress or completed.
public bool ImportStarted = false ;
// When the block is imported, Block.Instructions is filled with the imported instructions
// and the following members are initialized:
public List < ( ImportedBlock , ImmutableStack < ILVariable > ) > OutgoingEdges = new ( ) ;
public ImportedBlock ( int offset , ImmutableStack < ILVariable > inputStack )
{
this . InputStack = inputStack ;
this . Block . AddILRange ( new Interval ( offset , offset ) ) ;
}
/// <summary>
/// Compares stack types and update InputStack if necessary.
/// Returns true if InputStack was updated, making a reimport necessary.
/// </summary>
public bool MergeStackTypes ( ImmutableStack < ILVariable > newEdge )
{
var a = this . InputStack ;
var b = newEdge ;
bool changed = false ;
while ( ! a . IsEmpty & & ! b . IsEmpty )
{
if ( a . Peek ( ) . StackType < b . Peek ( ) . StackType )
{
changed = true ;
}
a = a . Pop ( ) ;
b = b . Pop ( ) ;
}
if ( ! changed | | ! ( a . IsEmpty & & b . IsEmpty ) )
return false ;
a = this . InputStack ;
b = newEdge ;
var output = new List < ILVariable > ( ) ;
while ( ! a . IsEmpty & & ! b . IsEmpty )
{
if ( a . Peek ( ) . StackType < b . Peek ( ) . StackType )
{
output . Add ( b . Peek ( ) ) ;
}
else
{
output . Add ( a . Peek ( ) ) ;
}
a = a . Pop ( ) ;
b = b . Pop ( ) ;
}
this . InputStack = ImmutableStack . CreateRange ( output ) ;
this . ImportStarted = false ;
return true ;
}
internal void ResetForReimport ( )
{
this . Block . Instructions . Clear ( ) ;
this . OutgoingEdges . Clear ( ) ;
}
}
readonly ICompilation compilation ;
readonly ICompilation compilation ;
readonly MetadataModule module ;
readonly MetadataModule module ;
readonly MetadataReader metadata ;
readonly MetadataReader metadata ;
@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType ;
StackType methodReturnStackType ;
BlobReader reader ;
BlobReader reader ;
ImmutableStack < ILVariable > currentStack = ImmutableStack < ILVariable > . Empty ;
ImmutableStack < ILVariable > currentStack = ImmutableStack < ILVariable > . Empty ;
ImportedBlock ? currentBlock ;
List < ILInstruction > expressionStack = new List < ILInstruction > ( ) ;
List < ILInstruction > expressionStack = new List < ILInstruction > ( ) ;
ILVariable [ ] parameterVariables = null ! ;
ILVariable [ ] parameterVariables = null ! ;
ILVariable [ ] localVariables = null ! ;
ILVariable [ ] localVariables = null ! ;
BitSet isBranchTarget = null ! ;
BitSet isBranchTarget = null ! ;
BlockContainer mainContainer = null ! ;
BlockContainer mainContainer = null ! ;
List < ILInstruction > instructionBuilder = new List < ILInstruction > ( ) ;
int currentInstructionStart ;
int currentInstructionStart ;
// Dictionary that stores stacks for each IL instruction
Dictionary < int , ImportedBlock > blocksByOffset = new Dictionary < int , ImportedBlock > ( ) ;
Dictionary < int , ImmutableStack < ILVariable > > stackByOffset = new Dictionary < int , ImmutableStack < ILVariable > > ( ) ;
Queue < ImportedBlock > importQueue = new Queue < ImportedBlock > ( ) ;
Dictionary < ExceptionRegion , ILVariable > variableByExceptionHandler = new Dictionary < ExceptionRegion , ILVariable > ( ) ;
Dictionary < ExceptionRegion , ILVariable > variableByExceptionHandler = new Dictionary < ExceptionRegion , ILVariable > ( ) ;
UnionFind < ILVariable > unionFind = null ! ;
List < ( ILVariable , ILVariable ) > stackMismatchPairs = new List < ( ILVariable , ILVariable ) > ( ) ;
IEnumerable < ILVariable > ? stackVariables ;
IEnumerable < ILVariable > ? stackVariables ;
void Init ( MethodDefinitionHandle methodDefinitionHandle , MethodBodyBlock body , GenericContext genericContext )
void Init ( MethodDefinitionHandle methodDefinitionHandle , MethodBodyBlock body , GenericContext genericContext )
@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL
this . reader = body . GetILReader ( ) ;
this . reader = body . GetILReader ( ) ;
this . currentStack = ImmutableStack < ILVariable > . Empty ;
this . currentStack = ImmutableStack < ILVariable > . Empty ;
this . expressionStack . Clear ( ) ;
this . expressionStack . Clear ( ) ;
this . unionFind = new UnionFind < ILVariable > ( ) ;
this . stackMismatchPairs . Clear ( ) ;
this . methodReturnStackType = method . ReturnType . GetStackType ( ) ;
this . methodReturnStackType = method . ReturnType . GetStackType ( ) ;
InitParameterVariables ( ) ;
InitParameterVariables ( ) ;
localVariables = InitLocalVariables ( ) ;
localVariables = InitLocalVariables ( ) ;
@ -128,9 +195,9 @@ namespace ICSharpCode.Decompiler.IL
v . UsesInitialValue = true ;
v . UsesInitialValue = true ;
}
}
this . mainContainer = new BlockContainer ( expectedResultType : methodReturnStackType ) ;
this . mainContainer = new BlockContainer ( expectedResultType : methodReturnStackType ) ;
this . instructionBuilder . Clear ( ) ;
this . blocksByOffset . Clear ( ) ;
this . importQueue . Clear ( ) ;
this . isBranchTarget = new BitSet ( reader . Length ) ;
this . isBranchTarget = new BitSet ( reader . Length ) ;
this . stackByOffset . Clear ( ) ;
this . variableByExceptionHandler . Clear ( ) ;
this . variableByExceptionHandler . Clear ( ) ;
}
}
@ -284,82 +351,71 @@ namespace ICSharpCode.Decompiler.IL
Warnings . Add ( string . Format ( "IL_{0:x4}: {1}" , currentInstructionStart , message ) ) ;
Warnings . Add ( string . Format ( "IL_{0:x4}: {1}" , currentInstructionStart , message ) ) ;
}
}
ImmutableStack < ILVariable > MergeStacks ( ImmutableStack < ILVariable > a , ImmutableStack < ILVariable > b )
/// <summary>
/// Check control flow edges for compatible stacks.
/// Returns union find data structure for unifying the different variables for the same stack slot.
/// Also inserts stack adjustments where necessary.
/// </summary>
UnionFind < ILVariable > CheckOutgoingEdges ( )
{
{
if ( CheckStackCompatibleWithoutAdjustments ( a , b ) )
var unionFind = new UnionFind < ILVariable > ( ) ;
{
foreach ( var block in blocksByOffset . Values )
// We only need to union the input variables, but can
// otherwise re-use the existing stack.
ImmutableStack < ILVariable > output = a ;
while ( ! a . IsEmpty & & ! b . IsEmpty )
{
Debug . Assert ( a . Peek ( ) . StackType = = b . Peek ( ) . StackType ) ;
unionFind . Merge ( a . Peek ( ) , b . Peek ( ) ) ;
a = a . Pop ( ) ;
b = b . Pop ( ) ;
}
return output ;
}
else if ( a . Count ( ) ! = b . Count ( ) )
{
// Let's not try to merge mismatched stacks.
Warn ( "Incompatible stack heights: " + a . Count ( ) + " vs " + b . Count ( ) ) ;
return a ;
}
else
{
{
// The more complex case where the stacks don't match exactly.
foreach ( var ( outgoing , stack ) in block . OutgoingEdges )
var output = new List < ILVariable > ( ) ;
while ( ! a . IsEmpty & & ! b . IsEmpty )
{
{
var varA = a . Peek ( ) ;
var a = stack ;
var varB = b . Peek ( ) ;
var b = outgoing . InputStack ;
if ( varA . StackType = = varB . StackType )
if ( a . Count ( ) ! = b . Count ( ) )
{
{
unionFind . Merge ( varA , varB ) ;
// Let's not try to merge mismatched stacks.
output . Add ( varA ) ;
Warnings . Add ( $"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack heights: {a.Count()} vs {b.Count()}" ) ;
continue ;
}
}
else
while ( ! a . IsEmpty & & ! b . IsEmpty )
{
{
if ( ! IsValidTypeStackTypeMerge ( varA . StackType , varB . StackType ) )
var varA = a . Peek ( ) ;
{
var varB = b . Peek ( ) ;
Warn ( "Incompatible stack types: " + varA . StackType + " vs " + varB . StackType ) ;
if ( varA . StackType = = varB . StackType )
}
if ( varA . StackType > varB . StackType )
{
{
output . Add ( varA ) ;
// The stack types match, so we can merge the variables.
// every store to varB should also store to varA
unionFind . Merge ( varA , varB ) ;
stackMismatchPairs . Add ( ( varB , varA ) ) ;
}
}
else
else
{
{
output . Add ( varB ) ;
Debug . Assert ( varA . StackType < varB . StackType ) ;
// every store to varA should also store to varB
if ( ! IsValidTypeStackTypeMerge ( varA . StackType , varB . StackType ) )
stackMismatchPairs . Add ( ( varA , varB ) ) ;
{
Warnings . Add ( $"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack types: {varA.StackType} vs {varB.StackType}" ) ;
}
InsertStackAdjustment ( block . Block , varA , varB ) ;
}
}
a = a . Pop ( ) ;
b = b . Pop ( ) ;
}
}
a = a . Pop ( ) ;
b = b . Pop ( ) ;
}
}
// because we built up output by popping from the input stacks, we need to reverse it to get back the original order
output . Reverse ( ) ;
return ImmutableStack . CreateRange ( output ) ;
}
}
return unionFind ;
}
}
static bool CheckStackCompatibleWithoutAdjustments ( ImmutableStack < ILVariable > a , ImmutableStack < ILVariable > b )
/// <summary>
/// Inserts a copy from varA to varB (with conversion) at the end of <paramref name="block"/>.
/// If the block ends with a branching instruction, the copy is inserted before the branching instruction.
/// </summary>
private void InsertStackAdjustment ( Block block , ILVariable varA , ILVariable varB )
{
{
while ( ! a . IsEmpty & & ! b . IsEmpty )
int insertionPosition = block . Instructions . Count ;
while ( insertionPosition > 0 & & block . Instructions [ insertionPosition - 1 ] . HasFlag ( InstructionFlags . MayBranch ) )
{
{
if ( a . Peek ( ) . StackType ! = b . Peek ( ) . StackType )
// Branch instruction mustn't be initializing varA.
return false ;
Debug . Assert ( ! block . Instructions [ insertionPosition - 1 ] . HasFlag ( InstructionFlags . MayWriteLocals ) ) ;
a = a . Pop ( ) ;
insertionPosition - - ;
b = b . Pop ( ) ;
}
}
return a . IsEmpty & & b . IsEmpty ;
ILInstruction value = new LdLoc ( varA ) ;
value = new Conv ( value , varB . StackType . ToPrimitiveType ( ) , false , Sign . Signed ) ;
block . Instructions . Insert ( insertionPosition , new StLoc ( varB , value ) { IsStackAdjustment = true } ) ;
}
}
private bool IsValidTypeStackTypeMerge ( StackType stackType1 , StackType stackType2 )
private static bool IsValidTypeStackTypeMerge ( StackType stackType1 , StackType stackType2 )
{
{
if ( stackType1 = = StackType . I & & stackType2 = = StackType . I4 )
if ( stackType1 = = StackType . I & & stackType2 = = StackType . I4 )
return true ;
return true ;
@ -375,42 +431,66 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// <summary>
/// Stores the given stack for a branch to `offset`.
/// Stores the given stack for a branch to `offset`.
///
/// The stack may be modified if stack adjustments are necessary. (e.g. implicit I4->I conversion)
/// </summary>
/// </summary>
void StoreStackForOffset ( int offset , ref ImmutableStack < ILVariable > stack )
ImportedBlock StoreStackForOffset ( int offset , ImmutableStack < ILVariable > stack )
{
{
if ( stack ByOffset. TryGetValue ( offset , out var existing ) )
if ( blocks ByOffset. TryGetValue ( offset , out var existing ) )
{
{
stack = MergeStacks ( existing , stack ) ;
bool wasImported = existing . ImportStarted ;
if ( stack ! = existing )
if ( existing . MergeStackTypes ( stack ) & & wasImported )
stackByOffset [ offset ] = stack ;
{
// If the stack changed, we need to re-import the block.
importQueue . Enqueue ( existing ) ;
}
return existing ;
}
}
else
else
{
{
stackByOffset . Add ( offset , stack ) ;
ImportedBlock newBlock = new ImportedBlock ( offset , stack ) ;
blocksByOffset . Add ( offset , newBlock ) ;
importQueue . Enqueue ( newBlock ) ;
return newBlock ;
}
}
}
}
void ReadInstructions ( CancellationToken cancellationToken )
void ReadInstructions ( CancellationToken cancellationToken )
{
{
reader . Reset ( ) ;
reader . Reset ( ) ;
StoreStackForOffset ( 0 , ImmutableStack < ILVariable > . Empty ) ;
ILParser . SetBranchTargets ( ref reader , isBranchTarget ) ;
ILParser . SetBranchTargets ( ref reader , isBranchTarget ) ;
reader . Reset ( ) ;
PrepareBranchTargetsAndStacksForExceptionHandlers ( ) ;
PrepareBranchTargetsAndStacksForExceptionHandlers ( ) ;
bool nextInstructionBeginsNewBlock = false ;
// Import of IL byte codes:
while ( importQueue . Count > 0 )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
ImportedBlock block = importQueue . Dequeue ( ) ;
ReadBlock ( block , cancellationToken ) ;
}
reader . Reset ( ) ;
// Merge different variables for same stack slot:
var unionFind = CheckOutgoingEdges ( ) ;
var visitor = new CollectStackVariablesVisitor ( unionFind ) ;
foreach ( var block in blocksByOffset . Values )
{
block . Block . AcceptVisitor ( visitor ) ;
}
stackVariables = visitor . variables ;
}
void ReadBlock ( ImportedBlock block , CancellationToken cancellationToken )
{
Debug . Assert ( ! block . ImportStarted ) ;
block . ResetForReimport ( ) ;
block . ImportStarted = true ;
reader . Offset = block . StartILOffset ;
currentBlock = block ;
currentStack = block . InputStack ;
// Read instructions until we reach the end of the block.
while ( reader . RemainingBytes > 0 )
while ( reader . RemainingBytes > 0 )
{
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
int start = reader . Offset ;
int start = reader . Offset ;
if ( isBranchTarget [ start ] )
{
FlushExpressionStack ( ) ;
StoreStackForOffset ( start , ref currentStack ) ;
}
currentInstructionStart = start ;
currentInstructionStart = start ;
bool startedWithEmptyStack = CurrentStackIsEmpty ( ) ;
bool startedWithEmptyStack = CurrentStackIsEmpty ( ) ;
DecodedInstruction decodedInstruction ;
DecodedInstruction decodedInstruction ;
@ -432,9 +512,9 @@ namespace ICSharpCode.Decompiler.IL
{
{
// Flush to avoid re-ordering of side-effects
// Flush to avoid re-ordering of side-effects
FlushExpressionStack ( ) ;
FlushExpressionStack ( ) ;
instructionBuilder . Add ( inst ) ;
block . Block . Instructions . Add ( inst ) ;
}
}
else if ( isBranchTarget [ start ] | | nextInstructionBeginsNewBlock )
else if ( start = = block . StartILOffset )
{
{
// If this instruction is the first in a new block, avoid it being inlined
// If this instruction is the first in a new block, avoid it being inlined
// into the next instruction.
// into the next instruction.
@ -442,39 +522,39 @@ namespace ICSharpCode.Decompiler.IL
// detect block starts, and doesn't search nested instructions.
// detect block starts, and doesn't search nested instructions.
FlushExpressionStack ( ) ;
FlushExpressionStack ( ) ;
}
}
if ( inst . HasDirectFlag ( InstructionFlags . EndPointUnreachable ) )
if ( ( ! decodedInstruction . PushedOnExpressionStack & & IsSequencePointInstruction ( inst ) ) | | startedWithEmptyStack )
{
{
FlushExpressionStack ( ) ;
this . SequencePointCandidates . Add ( inst . StartILOffset ) ;
if ( stackByOffset . TryGetValue ( end , out var stackAfterEnd ) )
{
currentStack = stackAfterEnd ;
}
else
{
currentStack = ImmutableStack < ILVariable > . Empty ;
}
nextInstructionBeginsNewBlock = true ;
}
}
else
if ( inst . HasDirectFlag ( InstructionFlags . EndPointUnreachable ) )
{
{
nextInstructionBeginsNewBlock = inst . HasFlag ( InstructionFlags . MayBranch ) ;
break ; // end of block, don't parse following instructions if they are unreachable
}
}
else if ( isBranchTarget [ end ] | | inst . HasFlag ( InstructionFlags . MayBranch ) )
if ( ( ! decodedInstruction . PushedOnExpressionStack & & IsSequencePointInstruction ( inst ) ) | | startedWithEmptyStack )
{
{
this . SequencePointCandidates . Add ( inst . StartILOffset ) ;
break ; // end of block (we'll create fall through)
}
}
}
}
// Finalize block
FlushExpressionStack ( ) ;
FlushExpressionStack ( ) ;
block . Block . AddILRange ( new Interval ( block . StartILOffset , reader . Offset ) ) ;
var visitor = new CollectStackVariablesVisitor ( unionFind ) ;
if ( ! block . Block . HasFlag ( InstructionFlags . EndPointUnreachable ) )
for ( int i = 0 ; i < instructionBuilder . Count ; i + + )
{
{
instructionBuilder [ i ] = instructionBuilder [ i ] . AcceptVisitor ( visitor ) ;
// create fall through branch
MarkBranchTarget ( reader . Offset , isFallThrough : true ) ;
if ( block . Block . Instructions . LastOrDefault ( ) is SwitchInstruction switchInst & & switchInst . Sections . Last ( ) . Body . MatchNop ( ) )
{
// Instead of putting the default branch after the switch instruction
switchInst . Sections . Last ( ) . Body = new Branch ( reader . Offset ) ;
Debug . Assert ( switchInst . HasFlag ( InstructionFlags . EndPointUnreachable ) ) ;
}
else
{
block . Block . Instructions . Add ( new Branch ( reader . Offset ) ) ;
}
}
}
stackVariables = visitor . variables ;
InsertStackAdjustments ( ) ;
}
}
private bool CurrentStackIsEmpty ( )
private bool CurrentStackIsEmpty ( )
@ -488,9 +568,7 @@ namespace ICSharpCode.Decompiler.IL
foreach ( var eh in body . ExceptionRegions )
foreach ( var eh in body . ExceptionRegions )
{
{
// Always mark the start of the try block as a "branch target".
// Always mark the start of the try block as a "branch target".
// We don't actually need to store the stack state here,
// We need to ensure that we put a block boundary there.
// but we need to ensure that no ILInstructions are inlined
// into the try-block.
isBranchTarget [ eh . TryOffset ] = true ;
isBranchTarget [ eh . TryOffset ] = true ;
ImmutableStack < ILVariable > ehStack ;
ImmutableStack < ILVariable > ehStack ;
@ -520,25 +598,23 @@ namespace ICSharpCode.Decompiler.IL
if ( eh . FilterOffset ! = - 1 )
if ( eh . FilterOffset ! = - 1 )
{
{
isBranchTarget [ eh . FilterOffset ] = true ;
isBranchTarget [ eh . FilterOffset ] = true ;
StoreStackForOffset ( eh . FilterOffset , ref ehStack ) ;
StoreStackForOffset ( eh . FilterOffset , ehStack ) ;
}
}
if ( eh . HandlerOffset ! = - 1 )
if ( eh . HandlerOffset ! = - 1 )
{
{
isBranchTarget [ eh . HandlerOffset ] = true ;
isBranchTarget [ eh . HandlerOffset ] = true ;
StoreStackForOffset ( eh . HandlerOffset , ref ehStack ) ;
StoreStackForOffset ( eh . HandlerOffset , ehStack ) ;
}
}
}
}
}
}
private bool IsSequencePointInstruction ( ILInstruction instruction )
private static bool IsSequencePointInstruction ( ILInstruction instruction )
{
{
if ( instruction . OpCode = = OpCode . Nop | |
if ( instruction . OpCode is OpCode . Nop
( instructionBuilder . Count > 0
or OpCode . Call
& & instructionBuilder . Last ( ) . OpCode is OpCode . Call
or OpCode . CallIndirect
or OpCode . CallIndirect
or OpCode . CallVirt )
or OpCode . CallVirt ) )
{
{
return true ;
return true ;
}
}
else
else
@ -547,39 +623,6 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
}
void InsertStackAdjustments ( )
{
if ( stackMismatchPairs . Count = = 0 )
return ;
var dict = new MultiDictionary < ILVariable , ILVariable > ( ) ;
foreach ( var ( origA , origB ) in stackMismatchPairs )
{
var a = unionFind . Find ( origA ) ;
var b = unionFind . Find ( origB ) ;
Debug . Assert ( a . StackType < b . StackType ) ;
// For every store to a, insert a converting store to b.
if ( ! dict [ a ] . Contains ( b ) )
dict . Add ( a , b ) ;
}
var newInstructions = new List < ILInstruction > ( ) ;
foreach ( var inst in instructionBuilder )
{
newInstructions . Add ( inst ) ;
if ( inst is StLoc store )
{
foreach ( var additionalVar in dict [ store . Variable ] )
{
ILInstruction value = new LdLoc ( store . Variable ) ;
value = new Conv ( value , additionalVar . StackType . ToPrimitiveType ( ) , false , Sign . Signed ) ;
newInstructions . Add ( new StLoc ( additionalVar , value ) {
IsStackAdjustment = true ,
} . WithILRange ( inst ) ) ;
}
}
}
instructionBuilder = newInstructions ;
}
/// <summary>
/// <summary>
/// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout.
/// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout.
/// </summary>
/// </summary>
@ -588,39 +631,24 @@ namespace ICSharpCode.Decompiler.IL
{
{
Init ( method , body , genericContext ) ;
Init ( method , body , genericContext ) ;
ReadInstructions ( cancellationToken ) ;
ReadInstructions ( cancellationToken ) ;
foreach ( var inst in instructionBuilder )
foreach ( var importBlock in blocksByOffset . Values . OrderBy ( b = > b . StartILOffset ) )
{
{
if ( inst is StLoc stloc & & stloc . IsStackAdjustment )
output . Write ( " [" ) ;
{
bool isFirstElement = true ;
output . Write ( " " ) ;
foreach ( var element in importBlock . InputStack )
inst . WriteTo ( output , new ILAstWritingOptions ( ) ) ;
output . WriteLine ( ) ;
continue ;
}
if ( stackByOffset . TryGetValue ( inst . StartILOffset , out var stack ) )
{
{
output . Write ( " [" ) ;
if ( isFirstElement )
bool isFirstElement = true ;
isFirstElement = false ;
foreach ( var element in stack )
else
{
output . Write ( ", " ) ;
if ( isFirstElement )
output . WriteLocalReference ( element . Name , element ) ;
isFirstElement = false ;
output . Write ( ":" ) ;
else
output . Write ( element . StackType ) ;
output . Write ( ", " ) ;
output . WriteLocalReference ( element . Name , element ) ;
output . Write ( ":" ) ;
output . Write ( element . StackType ) ;
}
output . Write ( ']' ) ;
output . WriteLine ( ) ;
}
}
if ( isBranchTarget [ inst . StartILOffset ] )
output . Write ( ']' ) ;
output . Write ( '*' ) ;
output . WriteLine ( ) ;
else
output . Write ( ' ' ) ;
importBlock . Block . WriteTo ( output , new ILAstWritingOptions ( ) ) ;
output . WriteLocalReference ( "IL_" + inst . StartILOffset . ToString ( "x4" ) , inst . StartILOffset , isDefinition : true ) ;
output . Write ( ": " ) ;
inst . WriteTo ( output , new ILAstWritingOptions ( ) ) ;
output . WriteLine ( ) ;
output . WriteLine ( ) ;
}
}
new Disassembler . MethodBodyDisassembler ( output , cancellationToken ) { DetectControlStructure = false }
new Disassembler . MethodBodyDisassembler ( output , cancellationToken ) { DetectControlStructure = false }
@ -636,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL
Init ( method , body , genericContext ) ;
Init ( method , body , genericContext ) ;
ReadInstructions ( cancellationToken ) ;
ReadInstructions ( cancellationToken ) ;
var blockBuilder = new BlockBuilder ( body , variableByExceptionHandler , compilation ) ;
var blockBuilder = new BlockBuilder ( body , variableByExceptionHandler , compilation ) ;
blockBuilder . CreateBlocks ( mainContainer , instructionBuilder , isBranchTarget , cancellationToken ) ;
blockBuilder . CreateBlocks ( mainContainer , blocksByOffset . Values . Select ( ib = > ib . Block ) , cancellationToken ) ;
var function = new ILFunction ( this . method , body . GetCodeSize ( ) , this . genericContext , mainContainer , kind ) ;
var function = new ILFunction ( this . method , body . GetCodeSize ( ) , this . genericContext , mainContainer , kind ) ;
function . Variables . AddRange ( parameterVariables ) ;
function . Variables . AddRange ( parameterVariables ) ;
function . Variables . AddRange ( localVariables ) ;
function . Variables . AddRange ( localVariables ) ;
@ -1187,7 +1215,7 @@ namespace ICSharpCode.Decompiler.IL
return currentStack . Peek ( ) . StackType ;
return currentStack . Peek ( ) . StackType ;
}
}
class CollectStackVariablesVisitor : ILVisitor < ILInstruction >
sealed class CollectStackVariablesVisitor : ILVisitor < ILInstruction >
{
{
readonly UnionFind < ILVariable > unionFind ;
readonly UnionFind < ILVariable > unionFind ;
internal readonly HashSet < ILVariable > variables = new HashSet < ILVariable > ( ) ;
internal readonly HashSet < ILVariable > variables = new HashSet < ILVariable > ( ) ;
@ -1216,7 +1244,7 @@ namespace ICSharpCode.Decompiler.IL
{
{
var variable = unionFind . Find ( inst . Variable ) ;
var variable = unionFind . Find ( inst . Variable ) ;
if ( variables . Add ( variable ) )
if ( variables . Add ( variable ) )
variable . Name = "S_" + ( variables . Count - 1 ) ;
variable . Name = $"S_{variables.Count - 1}" ;
return new LdLoc ( variable ) . WithILRange ( inst ) ;
return new LdLoc ( variable ) . WithILRange ( inst ) ;
}
}
return inst ;
return inst ;
@ -1229,7 +1257,7 @@ namespace ICSharpCode.Decompiler.IL
{
{
var variable = unionFind . Find ( inst . Variable ) ;
var variable = unionFind . Find ( inst . Variable ) ;
if ( variables . Add ( variable ) )
if ( variables . Add ( variable ) )
variable . Name = "S_" + ( variables . Count - 1 ) ;
variable . Name = $"S_{variables.Count - 1}" ;
return new StLoc ( variable , inst . Value ) . WithILRange ( inst ) ;
return new StLoc ( variable , inst . Value ) . WithILRange ( inst ) ;
}
}
return inst ;
return inst ;
@ -1296,7 +1324,7 @@ namespace ICSharpCode.Decompiler.IL
return Cast ( inst , expectedType , Warnings , reader . Offset ) ;
return Cast ( inst , expectedType , Warnings , reader . Offset ) ;
}
}
internal static ILInstruction Cast ( ILInstruction inst , StackType expectedType , List < string > warnings , int ilOffset )
internal static ILInstruction Cast ( ILInstruction inst , StackType expectedType , List < string > ? warnings , int ilOffset )
{
{
if ( expectedType ! = inst . ResultType )
if ( expectedType ! = inst . ResultType )
{
{
@ -1913,11 +1941,13 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
}
void MarkBranchTarget ( int targetILOffset )
void MarkBranchTarget ( int targetILOffset , bool isFallThrough = false )
{
{
FlushExpressionStack ( ) ;
FlushExpressionStack ( ) ;
Debug . Assert ( isBranchTarget [ targetILOffset ] ) ;
Debug . Assert ( isFallThrough | | isBranchTarget [ targetILOffset ] ) ;
StoreStackForOffset ( targetILOffset , ref currentStack ) ;
var targetBlock = StoreStackForOffset ( targetILOffset , currentStack ) ;
Debug . Assert ( currentBlock ! = null ) ;
currentBlock . OutgoingEdges . Add ( ( targetBlock , currentStack ) ) ;
}
}
/// <summary>
/// <summary>
@ -1928,6 +1958,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
/// </summary>
private void FlushExpressionStack ( )
private void FlushExpressionStack ( )
{
{
Debug . Assert ( currentBlock ! = null ) ;
foreach ( var inst in expressionStack )
foreach ( var inst in expressionStack )
{
{
Debug . Assert ( inst . ResultType ! = StackType . Void ) ;
Debug . Assert ( inst . ResultType ! = StackType . Void ) ;
@ -1935,7 +1966,7 @@ namespace ICSharpCode.Decompiler.IL
var v = new ILVariable ( VariableKind . StackSlot , type , inst . ResultType ) ;
var v = new ILVariable ( VariableKind . StackSlot , type , inst . ResultType ) ;
v . HasGeneratedName = true ;
v . HasGeneratedName = true ;
currentStack = currentStack . Push ( v ) ;
currentStack = currentStack . Push ( v ) ;
instructionBuilder . Add ( new StLoc ( v , inst ) . WithILRange ( inst ) ) ;
currentBlock . Block . Instructions . Add ( new StLoc ( v , inst ) . WithILRange ( inst ) ) ;
}
}
expressionStack . Clear ( ) ;
expressionStack . Clear ( ) ;
}
}