Make if and foreach constructs compatible

This commit is contained in:
Yoshi Askharoun
2025-09-25 15:52:13 -05:00
parent 587dcfa3b5
commit 62216f0fad
2 changed files with 88 additions and 33 deletions
+6 -1
View File
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
@@ -35,7 +36,11 @@ internal record CodeBlockInfo
case ElseBlockInfo _: case ElseBlockInfo _:
var elseClause = ElseClause(blockBody); var elseClause = ElseClause(blockBody);
var ifElseBlock = (IfStatementSyntax)parentBlock.Statements[^1];
var parentLastStatement = parentBlock.Statements[^1];
if (parentLastStatement is not IfStatementSyntax ifElseBlock)
throw new InvalidOperationException($"Else block must be preceded by an if block, got '{parentLastStatement.Kind()}'");
parentBlock.Statements[^1] = ifElseBlock.WithElse(elseClause); parentBlock.Statements[^1] = ifElseBlock.WithElse(elseClause);
break; break;
+82 -32
View File
@@ -56,37 +56,58 @@ partial class Decompiler
{ {
var instruction = methodBody[i]; var instruction = methodBody[i];
if (jumpFalseToOffsets.Contains(instruction.Offset) && blockStack.Count >= 2) while (blockStack.Count > 1)
{ {
var currentBlock = blockStack.Pop();
var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; if (currentBlock.EndOffset != instruction.Offset)
{
blockStack.Push(currentBlock);
break;
}
System.Diagnostics.Debug.WriteLine($"END: Finalizing {currentBlock}");
currentBlock.FinalizeBlock(blockStack.Peek()); currentBlock.FinalizeBlock(blockStack.Peek());
// This address marks the end of the affirmative branch of an IF clause.
// If multiple blocks lead to this address, then we're outside of the IF clause entirely.
// Otherwise, it's probably the start of an ELSE clause.
var currentControlBlock = cfa.GetByInstruction(instruction);
if (controlBlocks.Count(b => b.HasEdgeTo(currentControlBlock, controlBlocks)) <= 1)
{
blockStack.Push(new(instruction.Offset, uint.MaxValue, new ElseBlockInfo()));
}
} }
if (!foreachLoopHeadOffsets.Contains(instruction.Offset) && cfa.IsAlwaysExecuted(instruction.Offset)) //if (jumpToOffsets.Contains(instruction.Offset) && TryPeekBlock<ElseBlockInfo>(out _))
{ //{
while (blockStack.Count > 1) // // This address marks the end of the affirmative branch of an IF clause.
{ // // If multiple blocks lead to this address, then we're outside of the IF clause entirely.
var currentBlock = blockStack.Pop(); // // Otherwise, it's probably the start of an ELSE clause.
if (currentBlock.EndOffset is uint.MaxValue)
currentBlock = currentBlock with { EndOffset = instruction.Offset };
if (currentBlock.EndOffset != instruction.Offset) // while (blockStack.Count > 1)
break; // {
// var currentBlock = blockStack.Pop();
currentBlock.FinalizeBlock(blockStack.Peek()); // if (currentBlock.AdditionalInfo is not (ElseBlockInfo))
} // break;
}
// if (currentBlock.EndOffset is uint.MaxValue)
// currentBlock = currentBlock with { EndOffset = instruction.Offset };
// System.Diagnostics.Debug.WriteLine($"JMP: Finalizing ELSE {currentBlock}");
// currentBlock.FinalizeBlock(blockStack.Peek());
// }
//}
//if (!foreachLoopHeadOffsets.Contains(instruction.Offset) && cfa.IsAlwaysExecuted(instruction.Offset))
//{
// while (blockStack.Count > 1)
// {
// var currentBlock = blockStack.Pop();
// if (currentBlock.EndOffset is uint.MaxValue)
// {
// }
// if (currentBlock.EndOffset != instruction.Offset || currentBlock.AdditionalInfo is not (IfBlockInfo or ElseBlockInfo))
// break;
// System.Diagnostics.Debug.WriteLine($"Always exec'ed: Automatically finalizing {currentBlock}");
// currentBlock.FinalizeBlock(blockStack.Peek());
// }
//}
var opCode = instruction.OpCode; var opCode = instruction.OpCode;
@@ -299,7 +320,8 @@ partial class Decompiler
case OpCode.JumpIfTruePeek: case OpCode.JumpIfTruePeek:
var jumpToOffset = (uint)instruction.Operands.First().Value; var jumpToOffset = (uint)instruction.Operands.First().Value;
if (opCode is OpCode.JumpIfFalse && TryPeekBlock<ForEachBlockInfo>(out _)) if (opCode is OpCode.JumpIfFalse && TryPeekBlock<ForEachBlockInfo>(out var jmpfForEachBlockInfo)
&& jmpfForEachBlockInfo.Type is null)
break; break;
var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek; var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek;
@@ -308,7 +330,13 @@ partial class Decompiler
if (opCode is OpCode.JumpIfFalse) if (opCode is OpCode.JumpIfFalse)
{ {
// JMPF is used to evaluate the branch condition // JMPF is used to evaluate the branch condition
var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, new IfBlockInfo(jumpCondition)); var ifBlockEndOffset = methodBody
.Reverse()
.SkipWhile(i => i.Offset >= jumpToOffset)
.First()
.Offset;
var ifBlock = new CodeBlockInfo(instruction.Offset, ifBlockEndOffset, new IfBlockInfo(jumpCondition));
blockStack.Push(ifBlock); blockStack.Push(ifBlock);
} }
else else
@@ -329,14 +357,33 @@ partial class Decompiler
if (foreachLoopHeadOffsets.Contains(jumpOffset)) if (foreachLoopHeadOffsets.Contains(jumpOffset))
{ {
var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset }; //var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset };
currentBlock.FinalizeBlock(blockStack.Peek()); //System.Diagnostics.Debug.WriteLine($"JMP: Finalizing loop {currentBlock}");
//currentBlock.FinalizeBlock(blockStack.Peek());
} }
else else
{ {
throw new NotImplementedException("For and while loops are not supported at this time."); throw new NotImplementedException("For and while loops are not supported at this time.");
} }
} }
else
{
// End of if block, skipping else block
// Figure out where the else block ends by searching for the last instruction we skip
var elseBlockEndOffset = methodBody
.Reverse()
.SkipWhile(i => i.Offset >= jumpOffset)
.First()
.Offset;
//var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset };
//System.Diagnostics.Debug.WriteLine($"JMP: Finalizing presumed IF {currentBlock}");
//currentBlock.FinalizeBlock(blockStack.Peek());
var elseBlock = new CodeBlockInfo(instruction.Offset, elseBlockEndOffset, new ElseBlockInfo());
blockStack.Push(elseBlock);
}
break; break;
@@ -381,11 +428,14 @@ partial class Decompiler
bool TryPeekBlock<T>([NotNullWhen(true)] out T additionalInfo) where T : ICodeBlockAdditionalInfo bool TryPeekBlock<T>([NotNullWhen(true)] out T additionalInfo) where T : ICodeBlockAdditionalInfo
{ {
var currentBlock = blockStack.Peek(); if (blockStack.Count > 1)
if (currentBlock.AdditionalInfo is T a)
{ {
additionalInfo = a; var currentBlock = blockStack.Peek();
return true; if (currentBlock.AdditionalInfo is T a)
{
additionalInfo = a;
return true;
}
} }
additionalInfo = default; additionalInfo = default;