mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
Foreach decompilation
This commit is contained in:
@@ -6,42 +6,43 @@ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
|
||||
|
||||
namespace Microsoft.Iris.DecompXml;
|
||||
|
||||
public record CodeBlockInfo
|
||||
public record CodeBlock
|
||||
{
|
||||
public CodeBlockInfo(uint startOffset, uint endOffset, ICodeBlockAdditionalInfo additionalInfo = null)
|
||||
public CodeBlock(uint startOffset, uint endOffset, ICodeBlockInfo meta = null)
|
||||
{
|
||||
StartOffset = startOffset;
|
||||
EndOffset = endOffset;
|
||||
AdditionalInfo = additionalInfo;
|
||||
Info = meta;
|
||||
}
|
||||
|
||||
public uint StartOffset { get; init; }
|
||||
|
||||
public uint EndOffset { get; init; }
|
||||
|
||||
public ICodeBlockAdditionalInfo AdditionalInfo { get; init; }
|
||||
public ICodeBlockInfo Info { get; init; }
|
||||
|
||||
public List<StatementSyntax> Statements { get; } = [];
|
||||
|
||||
public void FinalizeBlock(CodeBlockInfo parentBlock)
|
||||
public void FinalizeBlock(CodeBlock parentBlock)
|
||||
{
|
||||
var blockBody = Block(Statements);
|
||||
|
||||
switch (AdditionalInfo)
|
||||
switch (Info)
|
||||
{
|
||||
case IfBlockInfo ifBlockInfo:
|
||||
var ifStatement = IfStatement(ifBlockInfo.Condition, blockBody);
|
||||
|
||||
if (ifBlockInfo.ElseClause is not null)
|
||||
ifStatement = ifStatement.WithElse(ifBlockInfo.ElseClause);
|
||||
|
||||
parentBlock.Statements.Add(ifStatement);
|
||||
break;
|
||||
|
||||
case ElseBlockInfo _:
|
||||
var elseClause = ElseClause(blockBody);
|
||||
if (parentBlock.Info is not IfBlockInfo ifElseBlockInfo)
|
||||
throw new InvalidOperationException($"Else block must be preceded by an if block, got '{parentBlock.Info}'");
|
||||
|
||||
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);
|
||||
ifElseBlockInfo.ElseClause = ElseClause(blockBody);
|
||||
break;
|
||||
|
||||
case ForEachBlockInfo forEachBlockInfo:
|
||||
@@ -51,21 +52,23 @@ public record CodeBlockInfo
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unrecognized code block kind '{AdditionalInfo.GetType().Name}'");
|
||||
throw new NotImplementedException($"Unrecognized code block kind '{Info.GetType().Name}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICodeBlockAdditionalInfo;
|
||||
public interface ICodeBlockInfo;
|
||||
|
||||
public class IfBlockInfo(ExpressionSyntax condition = null) : ICodeBlockAdditionalInfo
|
||||
public class IfBlockInfo(ExpressionSyntax condition = null) : ICodeBlockInfo
|
||||
{
|
||||
public ExpressionSyntax Condition { get; set; } = condition;
|
||||
|
||||
public ElseClauseSyntax ElseClause { get; set; }
|
||||
}
|
||||
|
||||
public class ElseBlockInfo : ICodeBlockAdditionalInfo;
|
||||
public class ElseBlockInfo : ICodeBlockInfo;
|
||||
|
||||
public class ForEachBlockInfo : ICodeBlockAdditionalInfo
|
||||
public class ForEachBlockInfo : ICodeBlockInfo
|
||||
{
|
||||
public ExpressionSyntax Source { get; set; }
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ControlFlowAnalyzer
|
||||
|
||||
public List<IProgramBlock> ControlBlocks { get; }
|
||||
|
||||
public Stack<CodeBlockInfo> BlockStack { get; }
|
||||
public Stack<CodeBlock> BlockStack { get; }
|
||||
|
||||
public IProgramBlock GetByOffset(uint offset)
|
||||
{
|
||||
@@ -46,7 +46,7 @@ public class ControlFlowAnalyzer
|
||||
{
|
||||
var currentBlock = BlockStack.Pop();
|
||||
|
||||
if (currentBlock.EndOffset != currentOffset)
|
||||
if (currentBlock.EndOffset > currentOffset)
|
||||
{
|
||||
BlockStack.Push(currentBlock);
|
||||
break;
|
||||
@@ -56,21 +56,43 @@ public class ControlFlowAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
public void PushBlock(CodeBlockInfo block) => BlockStack.Push(block);
|
||||
public void PushBlock(CodeBlock block)
|
||||
{
|
||||
BlockStack.Push(block);
|
||||
return;
|
||||
|
||||
public bool TryPeekBlock<T>(out T additionalInfo) where T : ICodeBlockAdditionalInfo
|
||||
while (BlockStack.Count > 1)
|
||||
{
|
||||
var currentBlock = BlockStack.Pop();
|
||||
var parentBlock = BlockStack.Peek();
|
||||
|
||||
if (currentBlock.EndOffset <= parentBlock.EndOffset)
|
||||
{
|
||||
BlockStack.Push(currentBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
currentBlock.FinalizeBlock(parentBlock);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeekBlockInfo<T>(out T info) where T : ICodeBlockInfo => TryPeekBlock(out _, out info);
|
||||
|
||||
public bool TryPeekBlock<T>(out CodeBlock block, out T info) where T : ICodeBlockInfo
|
||||
{
|
||||
if (BlockStack.Count > 1)
|
||||
{
|
||||
var currentBlock = BlockStack.Peek();
|
||||
if (currentBlock.AdditionalInfo is T a)
|
||||
if (currentBlock.Info is T a)
|
||||
{
|
||||
additionalInfo = a;
|
||||
block = currentBlock;
|
||||
info = a;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
additionalInfo = default;
|
||||
block = default;
|
||||
info = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,12 @@ partial class Decompiler
|
||||
var dotGraph = cfa.SerializeToGraphviz();
|
||||
Console.WriteLine(dotGraph);
|
||||
|
||||
var breakOrContinueBlocks = controlBlocks
|
||||
.Where(c => c.Body.TrueForAll(i => i.OpCode is OpCode.ClearSymbol or OpCode.Jump))
|
||||
.ToList();
|
||||
HashSet<uint> breakOrContinueStartOffsets = new(breakOrContinueBlocks.Select(c => c.StartOffset));
|
||||
HashSet<uint> breakOrContinueEndOffsets = new(breakOrContinueBlocks.Select(c => c.EndOffset));
|
||||
|
||||
HashSet<uint> foreachLoopHeadOffsets = [];
|
||||
|
||||
Dictionary<string, TypeSchema> scopedLocals = [];
|
||||
@@ -44,8 +50,6 @@ partial class Decompiler
|
||||
{
|
||||
var instruction = methodBody[i];
|
||||
|
||||
cfa.FinalizeCompletedBlocks(instruction.Offset);
|
||||
|
||||
var opCode = instruction.OpCode;
|
||||
|
||||
try
|
||||
@@ -82,14 +86,14 @@ partial class Decompiler
|
||||
Source = IrisExpression.ToSyntax(stack.Pop(), _context),
|
||||
};
|
||||
|
||||
var foreachBlock = new CodeBlockInfo(instruction.Offset, loopBodyEndOffset, forEachBlockInfo);
|
||||
var foreachBlock = new CodeBlock(instruction.Offset, loopBodyEndOffset, forEachBlockInfo);
|
||||
cfa.PushBlock(foreachBlock);
|
||||
|
||||
break;
|
||||
|
||||
case OpCode.MethodInvokePeek:
|
||||
// Ignore MoveNext calls when in a foreach loop, as long as we haven't already initialized this loop
|
||||
if (!cfa.TryPeekBlock<ForEachBlockInfo>(out var forEachBlockInfo1) || forEachBlockInfo1.Type is not null)
|
||||
if (!cfa.TryPeekBlockInfo<ForEachBlockInfo>(out var forEachBlockInfo1) || forEachBlockInfo1.Type is not null)
|
||||
goto default;
|
||||
|
||||
var methodSchemaPeek = _context.GetImportedMethod(instruction.Operands.First());
|
||||
@@ -206,7 +210,7 @@ partial class Decompiler
|
||||
case OpCode.PropertyGetPeek:
|
||||
// PGETP is only used in foreach loops
|
||||
|
||||
if (!cfa.TryPeekBlock<ForEachBlockInfo>(out var forEachBlockInfo2))
|
||||
if (!cfa.TryPeekBlockInfo<ForEachBlockInfo>(out var forEachBlockInfo2))
|
||||
throw new InvalidOperationException("Unexpected call to Current outside of a foreach loop");
|
||||
|
||||
var propToGet = _context.GetImportedProperty(instruction.Operands.First());
|
||||
@@ -254,7 +258,7 @@ partial class Decompiler
|
||||
case OpCode.JumpIfTruePeek:
|
||||
var jumpToOffset = (uint)instruction.Operands.First().Value;
|
||||
|
||||
if (opCode is OpCode.JumpIfFalse && cfa.TryPeekBlock<ForEachBlockInfo>(out var jmpfForEachBlockInfo)
|
||||
if (opCode is OpCode.JumpIfFalse && cfa.TryPeekBlockInfo<ForEachBlockInfo>(out var jmpfForEachBlockInfo)
|
||||
&& jmpfForEachBlockInfo.Type is null)
|
||||
break;
|
||||
|
||||
@@ -270,7 +274,7 @@ partial class Decompiler
|
||||
.First()
|
||||
.Offset;
|
||||
|
||||
var ifBlock = new CodeBlockInfo(instruction.Offset, ifBlockEndOffset, new IfBlockInfo(jumpCondition));
|
||||
var ifBlock = new CodeBlock(instruction.Offset, ifBlockEndOffset, new IfBlockInfo(jumpCondition));
|
||||
cfa.PushBlock(ifBlock);
|
||||
}
|
||||
else
|
||||
@@ -285,28 +289,51 @@ partial class Decompiler
|
||||
case OpCode.Jump:
|
||||
var jumpOffset = (uint)instruction.Operands.First().Value;
|
||||
|
||||
CodeBlock currentForEachBlock = null;
|
||||
ForEachBlockInfo jmpForEachBlockInfo = null;
|
||||
|
||||
foreach (var block in cfa.BlockStack)
|
||||
{
|
||||
currentForEachBlock = block;
|
||||
jmpForEachBlockInfo = block.Info as ForEachBlockInfo;
|
||||
|
||||
if (jmpForEachBlockInfo is not null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (jumpOffset < instruction.Offset)
|
||||
{
|
||||
// End of loop
|
||||
|
||||
if (!foreachLoopHeadOffsets.Contains(jumpOffset))
|
||||
{
|
||||
throw new NotImplementedException("For and while loops are not supported at this time.");
|
||||
}
|
||||
|
||||
if (currentForEachBlock is null)
|
||||
throw new NotImplementedException($"Unexpected backwards JMP at 0x{instruction.Offset:X}");
|
||||
|
||||
// Continue statements look like premature jumps back to the loop header
|
||||
if (currentForEachBlock.EndOffset > instruction.Offset)
|
||||
cfa.AppendToBlock(ContinueStatement());
|
||||
}
|
||||
else
|
||||
{
|
||||
// End of if block, skipping else block
|
||||
// Break statements look like premature jumps to the loop tail
|
||||
if (currentForEachBlock is not null && currentForEachBlock.EndOffset < jumpOffset)
|
||||
{
|
||||
cfa.AppendToBlock(BreakStatement());
|
||||
}
|
||||
else
|
||||
{
|
||||
// End of if block, skipping over 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;
|
||||
// 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 elseBlock = new CodeBlockInfo(instruction.Offset, elseBlockEndOffset, new ElseBlockInfo());
|
||||
cfa.PushBlock(elseBlock);
|
||||
var elseBlock = new CodeBlock(instruction.Offset, elseBlockEndOffset, new ElseBlockInfo());
|
||||
cfa.PushBlock(elseBlock);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -335,6 +362,8 @@ partial class Decompiler
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
cfa.FinalizeCompletedBlocks(instruction.Offset);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -24,14 +24,10 @@
|
||||
|
||||
foreach (Int32 num in ArraySet)
|
||||
{
|
||||
int y = num;
|
||||
|
||||
if (y % 4 == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sum = sum + y;
|
||||
sum = sum + num;
|
||||
continue;
|
||||
|
||||
num = sum - 1;
|
||||
}
|
||||
|
||||
int x = sum * 5;
|
||||
|
||||
Reference in New Issue
Block a user