From 001a60159a8fcd12fed124b25e8d26c7a3250f9c Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Jul 2024 18:13:31 +0200 Subject: [PATCH 1/9] Initial revamp of offset independent cfg nodes --- .../Echo.Ast/Analysis/AstPurityClassifier.cs | 1 + .../Echo.Ast/Analysis/AstPurityVisitor.cs | 1 + .../Analysis/FlowControlDeterminer.cs | 1 + .../Echo.Ast/Analysis/ReadVariableFinder.cs | 1 + .../Analysis/WrittenVariableFinder.cs | 1 + src/Core/Echo.Ast/AssignmentStatement.cs | 1 + src/Core/Echo.Ast/AstArchitecture.cs | 2 + src/Core/Echo.Ast/AstFormatter.cs | 2 + src/Core/Echo.Ast/AstNode.cs | 5 +- src/Core/Echo.Ast/AstNodeListener.cs | 1 + src/Core/Echo.Ast/AstNodeWalker.cs | 1 + src/Core/Echo.Ast/AstVariableExtensions.cs | 2 + src/Core/Echo.Ast/BlockStatement.cs | 1 + src/Core/Echo.Ast/CompilationUnit.cs | 1 + src/Core/Echo.Ast/Construction/AstBuilder.cs | 4 + .../Construction/CompilationUnitBuilder.cs | 1 + .../Construction/ControlFlowGraphLifter.cs | 7 +- src/Core/Echo.Ast/Construction/LiftedNode.cs | 1 + src/Core/Echo.Ast/Construction/StackState.cs | 1 + .../Echo.Ast/ExceptionHandlerStatement.cs | 1 + src/Core/Echo.Ast/Expression.cs | 16 +- src/Core/Echo.Ast/ExpressionStatement.cs | 1 + src/Core/Echo.Ast/HandlerClause.cs | 1 + src/Core/Echo.Ast/IAstNodeListener.cs | 1 + src/Core/Echo.Ast/IAstNodeVisitor.cs | 3 + src/Core/Echo.Ast/InstructionExpression.cs | 1 + .../Patterns/AssignmentStatementPattern.cs | 1 + .../Echo.Ast/Patterns/ExpressionPattern.cs | 13 +- .../Patterns/ExpressionStatementPattern.cs | 1 + .../Patterns/InstructionExpressionPattern.cs | 1 + .../Echo.Ast/Patterns/PhiStatementPattern.cs | 1 + .../Echo.Ast/Patterns/StatementPattern.cs | 22 +- .../Patterns/VariableExpressionPattern.cs | 1 + src/Core/Echo.Ast/PhiStatement.cs | 1 + src/Core/Echo.Ast/Statement.cs | 12 +- src/Core/Echo.Ast/VariableCollection.cs | 1 + src/Core/Echo.Ast/VariableExpression.cs | 1 + .../Analysis/Domination/DominatorTree.cs | 152 +++---- .../Analysis/Domination/DominatorTreeNode.cs | 29 +- .../Echo.ControlFlow/Blocks/BasicBlock.cs | 22 +- .../Echo.ControlFlow/Blocks/BlockFormatter.cs | 1 + ...{BlockListenerBase.cs => BlockListener.cs} | 3 +- .../Echo.ControlFlow/Blocks/BlockWalker.cs | 1 + .../Blocks/ExceptionHandlerBlock.cs | 10 +- .../Echo.ControlFlow/Blocks/HandlerBlock.cs | 11 +- src/Core/Echo.ControlFlow/Blocks/IBlock.cs | 5 +- .../Echo.ControlFlow/Blocks/IBlockListener.cs | 1 + .../Echo.ControlFlow/Blocks/IBlockVisitor.cs | 2 + .../Echo.ControlFlow/Blocks/ScopeBlock.cs | 5 +- .../Collections/AdjacencyCollection.cs | 129 ++---- .../Collections/NodeCollection.cs | 173 ++------ .../Collections/RegionCollection.cs | 1 + .../Collections/RegionNodeCollection.cs | 1 + .../Construction/FlowGraphBuilderBase.cs | 11 +- .../Construction/IFlowGraphBuilder.cs | 4 +- .../IInstructionTraversalResult.cs | 1 + .../InstructionTraversalResult.cs | 1 + .../Static/IStaticSuccessorResolver.cs | 1 + .../Static/StaticFlowGraphBuilder.cs | 2 +- .../Symbolic/IStateTransitioner.cs | 1 + .../Symbolic/ISymbolicInstructionProvider.cs | 1 + .../Construction/Symbolic/StateTransition.cs | 4 +- .../Symbolic/StateTransitionerBase.cs | 1 + .../Symbolic/StaticToSymbolicAdapter.cs | 1 + .../Symbolic/SymbolicFlowGraphBuilder.cs | 6 +- src/Core/Echo.ControlFlow/ControlFlowEdge.cs | 19 +- src/Core/Echo.ControlFlow/ControlFlowGraph.cs | 28 +- src/Core/Echo.ControlFlow/ControlFlowNode.cs | 101 ++--- .../Echo.ControlFlow/Echo.ControlFlow.csproj | 3 + .../Echo.ControlFlow/Editing/AddEdgeAction.cs | 64 --- .../Editing/ControlFlowGraphEditContext.cs | 197 --------- .../ControlFlowGraphEditTransaction.cs | 80 ---- .../Editing/IControlFlowGraphEditAction.cs | 36 -- .../Editing/RemoveEdgeAction.cs | 63 --- .../Editing/SplitNodeAction.cs | 58 --- .../FlowControlSynchronization.cs | 77 ---- .../FlowControlSynchronizationFlags.cs | 21 - .../FlowControlSynchronizer.cs | 374 ------------------ .../Editing/UpdateAdjacencyAction.cs | 91 ----- .../Editing/UpdateFallThroughAction.cs | 92 ----- .../Regions/ControlFlowRegion.cs | 13 +- .../Detection/ExceptionHandlerRange.cs | 10 +- .../Detection/RangedEHRegionDetector.cs | 21 +- .../Regions/ExceptionHandlerRegion.cs | 3 +- .../Echo.ControlFlow/Regions/HandlerRegion.cs | 18 +- .../Regions/IControlFlowRegion.cs | 37 +- .../Echo.ControlFlow/Regions/ScopeRegion.cs | 12 +- .../Serialization/Blocks/BlockBuilder.cs | 15 +- .../Serialization/Blocks/BlockSorter.cs | 29 +- .../Blocks/UnbreakablePathsView.cs | 24 +- .../Dot/ControlFlowEdgeAdorner.cs | 7 +- .../Dot/ControlFlowNodeAdorner.cs | 3 +- .../Dot/DefaultInstructionFormatter.cs | 6 +- .../Dot/ExceptionHandlerAdorner.cs | 23 +- .../Dot/IInstructionFormatter.cs | 1 + .../Serialization/Dot/OffsetNodeIdentifier.cs | 1 + .../Analysis/Sorting/TopologicalSorter.cs | 7 +- .../Graphing/Serialization/Dot/DotWriter.cs | 4 +- .../Serialization/Dot/HexLabelNodeAdorner.cs | 2 +- .../Serialization/Dot/IDotEdgeAdorner.cs | 2 +- .../Serialization/Dot/IDotNodeAdorner.cs | 2 +- .../Serialization/Dot/IDotSubGraphAdorner.cs | 2 +- .../Dispatch/ControlFlow/RetHandler.cs | 15 +- .../Emulation/Invocation/UnsafeInvoker.cs | 10 +- .../ControlFlowGraphLifterTest.cs | 54 ++- .../Analysis/Domination/DominatorTreeTest.cs | 70 ++-- .../Static/StaticGraphBuilderTest.cs | 17 +- .../Symbolic/SymbolicGraphBuilderTest.cs | 7 +- .../ControlFlowGraphTest.cs | 96 +---- .../ControlFlowNodeTest.cs | 177 +++++---- .../ControlFlowTestGraphs.cs | 119 +++--- .../Echo.ControlFlow.Tests.csproj | 1 + .../Editing/FlowControlSynchronizerTest.cs | 263 ------------ .../ExceptionHandlerRegionDetectionTest.cs | 87 ++-- .../Serialization/Blocks/BlockBuilderTest.cs | 3 +- .../Serialization/Blocks/BlockSorterTest.cs | 192 ++++----- .../StateTransitionResolverTest.cs | 6 +- .../StaticSuccessorResolverTest.cs | 8 +- .../StateTransitionResolverTest.cs | 6 +- .../StaticSuccessorResolverTest.cs | 6 +- .../X86StaticFlowGraphBuilderTest.cs | 25 +- 121 files changed, 966 insertions(+), 2436 deletions(-) rename src/Core/Echo.ControlFlow/Blocks/{BlockListenerBase.cs => BlockListener.cs} (94%) delete mode 100644 src/Core/Echo.ControlFlow/Editing/AddEdgeAction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditContext.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditTransaction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/IControlFlowGraphEditAction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/RemoveEdgeAction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/SplitNodeAction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronization.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizationFlags.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizer.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/UpdateAdjacencyAction.cs delete mode 100644 src/Core/Echo.ControlFlow/Editing/UpdateFallThroughAction.cs delete mode 100644 test/Core/Echo.ControlFlow.Tests/Editing/FlowControlSynchronizerTest.cs diff --git a/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs b/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs index 50f67252..ca84b14e 100644 --- a/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs +++ b/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs @@ -8,6 +8,7 @@ namespace Echo.Ast.Analysis; /// /// The type of instructions the statements store. public class AstPurityClassifier : IPurityClassifier> + where TInstruction : notnull { /// /// Creates a new instance of the class. diff --git a/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs index 8b289440..ae93a5ea 100644 --- a/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs +++ b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs @@ -7,6 +7,7 @@ namespace Echo.Ast.Analysis; /// /// The type of instructions to store in each expression. public class AstPurityVisitor : IAstNodeVisitor, Trilean> + where TInstruction : notnull { /// /// Gets the singleton instance of the class. diff --git a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs index c94f4ee6..c997d0b1 100644 --- a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs +++ b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs @@ -5,6 +5,7 @@ namespace Echo.Ast.Analysis { internal sealed class FlowControlDeterminer : IAstNodeVisitor + where TInstruction : notnull { private readonly IArchitecture _isa; diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs index 2195e0d1..601c1969 100644 --- a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs @@ -5,6 +5,7 @@ namespace Echo.Ast.Analysis { internal sealed class ReadVariableFinder : AstNodeListener + where TInstruction : notnull { private readonly IArchitecture _architecture; diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs index dc3b8bb4..43ee1b23 100644 --- a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs @@ -5,6 +5,7 @@ namespace Echo.Ast.Analysis { internal sealed class WrittenVariableFinder : AstNodeListener + where TInstruction : notnull { private readonly IArchitecture _architecture; diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index e712e15d..77b5799f 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -8,6 +8,7 @@ namespace Echo.Ast /// Represents a statement that assigns a value to a (set of) variable(s). /// public sealed class AssignmentStatement : Statement + where TInstruction : notnull { private Expression _expression = null!; diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index c091a70e..135f98fe 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -10,6 +10,7 @@ namespace Echo.Ast /// The instructions defined by the satck-based platform. public class AstArchitecture : IArchitecture> + where TInstruction : notnull { private readonly IArchitecture _baseArchitecture; private readonly FlowControlDeterminer _flowControlDeterminer; @@ -97,6 +98,7 @@ public static class AstArchitectureExtensions /// The type of instructions defined by the architecture. /// The lifted architecture. public static AstArchitecture ToAst(this IArchitecture self) + where TInstruction : notnull => new(self); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstFormatter.cs b/src/Core/Echo.Ast/AstFormatter.cs index e739a3ef..761014d8 100644 --- a/src/Core/Echo.Ast/AstFormatter.cs +++ b/src/Core/Echo.Ast/AstFormatter.cs @@ -16,6 +16,7 @@ public static class AstFormatter /// The type of instructions stored in the AST. /// The constructed formatter. public static AstFormatter ToAstFormatter(this IInstructionFormatter self) + where TInstruction : notnull { return new AstFormatter(self); } @@ -28,6 +29,7 @@ public static AstFormatter ToAstFormatter(this IInst public class AstFormatter : IAstNodeVisitor, IInstructionFormatter> + where TInstruction : notnull { /// /// Creates a new AST formatter using the default instruction formatter. diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 9b025e54..6e42924f 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,9 +1,5 @@ -using System.Collections.Generic; -using System.IO; using Echo.Code; -using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; -using Echo.Graphing.Serialization.Dot; namespace Echo.Ast { @@ -11,6 +7,7 @@ namespace Echo.Ast /// Represents a single node in an Abstract Syntax Tree (AST). /// public abstract class AstNode : TreeNodeBase + where TInstruction : notnull { /// /// Gets the direct parent of the AST node. diff --git a/src/Core/Echo.Ast/AstNodeListener.cs b/src/Core/Echo.Ast/AstNodeListener.cs index 0359726d..8fb7185c 100644 --- a/src/Core/Echo.Ast/AstNodeListener.cs +++ b/src/Core/Echo.Ast/AstNodeListener.cs @@ -5,6 +5,7 @@ namespace Echo.Ast /// /// The type of instructions stored in the AST. public abstract class AstNodeListener : IAstNodeListener + where TInstruction : notnull { /// public void EnterCompilationUnit(CompilationUnit unit) {} diff --git a/src/Core/Echo.Ast/AstNodeWalker.cs b/src/Core/Echo.Ast/AstNodeWalker.cs index f2204531..c17dfd09 100644 --- a/src/Core/Echo.Ast/AstNodeWalker.cs +++ b/src/Core/Echo.Ast/AstNodeWalker.cs @@ -5,6 +5,7 @@ namespace Echo.Ast /// /// The instruction stored in the AST. public class AstNodeWalker : IAstNodeVisitor + where TInstruction : notnull { private readonly IAstNodeListener _listener; diff --git a/src/Core/Echo.Ast/AstVariableExtensions.cs b/src/Core/Echo.Ast/AstVariableExtensions.cs index 5b957088..bb69e1b2 100644 --- a/src/Core/Echo.Ast/AstVariableExtensions.cs +++ b/src/Core/Echo.Ast/AstVariableExtensions.cs @@ -17,6 +17,7 @@ public static class AstVariableExtensions public static IEnumerable> GetIsUsedBy( this IVariable self, CompilationUnit unit) + where TInstruction : notnull { return unit.GetVariableUses(self); } @@ -30,6 +31,7 @@ public static IEnumerable> GetIsUsedBy> GetIsWrittenBy( this IVariable self, CompilationUnit unit) + where TInstruction : notnull { return unit.GetVariableWrites(self); } diff --git a/src/Core/Echo.Ast/BlockStatement.cs b/src/Core/Echo.Ast/BlockStatement.cs index 90484fe3..5d082811 100644 --- a/src/Core/Echo.Ast/BlockStatement.cs +++ b/src/Core/Echo.Ast/BlockStatement.cs @@ -8,6 +8,7 @@ namespace Echo.Ast; /// /// The type of instruction stored in the AST. public class BlockStatement : Statement + where TInstruction : notnull { /// /// Creates a new empty block. diff --git a/src/Core/Echo.Ast/CompilationUnit.cs b/src/Core/Echo.Ast/CompilationUnit.cs index e2a5458d..5ab9ffe6 100644 --- a/src/Core/Echo.Ast/CompilationUnit.cs +++ b/src/Core/Echo.Ast/CompilationUnit.cs @@ -11,6 +11,7 @@ namespace Echo.Ast; /// /// The type of instructions to store in the AST. public class CompilationUnit : AstNode + where TInstruction : notnull { private readonly Dictionary>> _variableUses = new(); private readonly Dictionary>> _variableWrites = new(); diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 8be55e4c..1c03a191 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -19,6 +19,7 @@ public static class AstBuilder public static ControlFlowGraph> Lift( this ControlFlowGraph cfg, IPurityClassifier classifier) + where TInstruction : notnull { return ControlFlowGraphLifter.Lift(cfg, classifier); } @@ -34,6 +35,7 @@ public static ControlFlowGraph> Lift( public static CompilationUnit ToCompilationUnit( this ControlFlowGraph self, IPurityClassifier classifier) + where TInstruction : notnull { return new CompilationUnitBuilder().Construct(self.Lift(classifier)); } @@ -46,6 +48,7 @@ public static CompilationUnit ToCompilationUnit( /// The constructed compilation unit. public static CompilationUnit ToCompilationUnit( this ControlFlowGraph> self) + where TInstruction : notnull { return new CompilationUnitBuilder().Construct(self); } @@ -58,6 +61,7 @@ public static CompilationUnit ToCompilationUnit( /// The constructed compilation unit. public static CompilationUnit ToCompilationUnit( this ScopeBlock> self) + where TInstruction : notnull { return new CompilationUnitBuilder().Construct(self); } diff --git a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs index 486aac84..59d462f4 100644 --- a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs +++ b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs @@ -9,6 +9,7 @@ namespace Echo.Ast.Construction; /// /// The type of instructions stored in the AST. public class CompilationUnitBuilder : IBlockVisitor, object?, AstNode> + where TInstruction : notnull { /// /// Constructs a compilation unit based on a lifted control flow graph. diff --git a/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs b/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs index fd0e585c..17069b31 100644 --- a/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs +++ b/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs @@ -11,6 +11,7 @@ namespace Echo.Ast.Construction; /// Lifts every node in a control flow graph to its AST representation. /// public sealed class ControlFlowGraphLifter + where TInstruction : notnull { private readonly ControlFlowGraph _original; private readonly ControlFlowGraph> _lifted; @@ -269,7 +270,7 @@ private void PopulatePhiStatements() var recordedStates = new Dictionary, StackState>(); var agenda = new Queue>(); - agenda.Enqueue(new StackState(_original.EntryPoint)); + agenda.Enqueue(new StackState(_original.EntryPoint!)); while (agenda.Count > 0) { @@ -366,7 +367,7 @@ private void AddEdges() private void TransformRegions() { - _lifted.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; + _lifted.EntryPoint = _liftedNodes[_original.EntryPoint!].Transformed; foreach (var region in _original.Regions) TransformRegion(x => _lifted.Regions.Add(x), region); } @@ -407,7 +408,7 @@ private void TransformScope(ScopeRegion scopeRegion, ScopeRegion /// internal sealed class LiftedNode + where TInstruction : notnull { private List>? _stackInputs; private Dictionary>? _stackInputRefs; diff --git a/src/Core/Echo.Ast/Construction/StackState.cs b/src/Core/Echo.Ast/Construction/StackState.cs index b3bc0ab6..2bb5c55f 100644 --- a/src/Core/Echo.Ast/Construction/StackState.cs +++ b/src/Core/Echo.Ast/Construction/StackState.cs @@ -7,6 +7,7 @@ namespace Echo.Ast.Construction; [DebuggerDisplay("{Node} (Stack: {Stack})")] internal readonly struct StackState + where TInstruction : notnull { public StackState(ControlFlowNode node) { diff --git a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs index 1059ac9f..bd04b12b 100644 --- a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs +++ b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs @@ -8,6 +8,7 @@ namespace Echo.Ast; /// /// The type of instruction stored in the AST. public class ExceptionHandlerStatement : Statement + where TInstruction : notnull { private BlockStatement _protectedBlock = null!; diff --git a/src/Core/Echo.Ast/Expression.cs b/src/Core/Echo.Ast/Expression.cs index d7bd3768..3afad5f4 100644 --- a/src/Core/Echo.Ast/Expression.cs +++ b/src/Core/Echo.Ast/Expression.cs @@ -14,7 +14,9 @@ public static class Expression /// The variable. /// The type of instruction. /// The resulting expression. - public static VariableExpression Variable(IVariable variable) => new(variable); + public static VariableExpression Variable(IVariable variable) + where TInstruction : notnull + => new(variable); /// /// Wraps an instruction into an expression with no arguments. @@ -22,7 +24,9 @@ public static class Expression /// The instruction. /// The type of instruction. /// The resulting expression. - public static InstructionExpression Instruction(TInstruction instruction) => new(instruction); + public static InstructionExpression Instruction(TInstruction instruction) + where TInstruction : notnull + => new(instruction); /// /// Wraps an instruction into an expression with the provided arguments. @@ -34,6 +38,7 @@ public static class Expression public static InstructionExpression Instruction( TInstruction instruction, params Expression[] arguments) + where TInstruction : notnull { return new InstructionExpression(instruction, arguments); } @@ -48,6 +53,7 @@ public static InstructionExpression Instruction( public static InstructionExpression Instruction( TInstruction instruction, IEnumerable> arguments) + where TInstruction : notnull { return new InstructionExpression(instruction, arguments); } @@ -59,13 +65,17 @@ public static InstructionExpression Instruction( /// The type of instruction. /// The resulting expression. public static VariableExpression ToExpression(this IVariable variable) - => new(variable); + where TInstruction : notnull + { + return new VariableExpression(variable); + } } /// /// Provides a base contract for expressions in the AST /// public abstract class Expression : AstNode + where TInstruction : notnull { /// /// Wraps the expression into an expression statement. diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index 48bca8db..49702b1d 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -7,6 +7,7 @@ namespace Echo.Ast /// Represents and expression statement in the AST /// public sealed class ExpressionStatement : Statement + where TInstruction : notnull { private Expression _expression = null!; diff --git a/src/Core/Echo.Ast/HandlerClause.cs b/src/Core/Echo.Ast/HandlerClause.cs index 20fbd102..0a8f447f 100644 --- a/src/Core/Echo.Ast/HandlerClause.cs +++ b/src/Core/Echo.Ast/HandlerClause.cs @@ -8,6 +8,7 @@ namespace Echo.Ast; /// /// The type of instructions stored in the AST. public class HandlerClause : AstNode + where TInstruction : notnull { private BlockStatement? _prologue; private BlockStatement _contents = null!; diff --git a/src/Core/Echo.Ast/IAstNodeListener.cs b/src/Core/Echo.Ast/IAstNodeListener.cs index 836aaee3..8051426d 100644 --- a/src/Core/Echo.Ast/IAstNodeListener.cs +++ b/src/Core/Echo.Ast/IAstNodeListener.cs @@ -6,6 +6,7 @@ namespace Echo.Ast /// /// The type of instructions stored in the AST. public interface IAstNodeListener + where TInstruction : notnull { /// /// Enters a compilation unit. diff --git a/src/Core/Echo.Ast/IAstNodeVisitor.cs b/src/Core/Echo.Ast/IAstNodeVisitor.cs index 4c0619f4..16a22cac 100644 --- a/src/Core/Echo.Ast/IAstNodeVisitor.cs +++ b/src/Core/Echo.Ast/IAstNodeVisitor.cs @@ -5,6 +5,7 @@ namespace Echo.Ast /// /// The type of the instruction the AST models public interface IAstNodeVisitor + where TInstruction : notnull { /// /// Visits a given @@ -59,6 +60,7 @@ public interface IAstNodeVisitor /// The type of the instruction the AST models /// The state to pass between visitors public interface IAstNodeVisitor + where TInstruction : notnull { /// /// Visits a given @@ -113,6 +115,7 @@ public interface IAstNodeVisitor /// The state to pass between visitors /// The return type of the Visit methods public interface IAstNodeVisitor + where TInstruction : notnull { /// /// Visits a given diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index 462f61d4..5a922541 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -7,6 +7,7 @@ namespace Echo.Ast /// Represents an instruction in the AST /// public sealed class InstructionExpression : Expression + where TInstruction : notnull { /// /// Creates a new instruction expression node with no arguments diff --git a/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs b/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs index f784a25d..21a5ef49 100644 --- a/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs @@ -9,6 +9,7 @@ namespace Echo.Ast.Patterns /// /// The type of instruction that is stored in the expression. public class AssignmentStatementPattern : StatementPattern + where TInstruction : notnull { /// /// Creates a new assignment statement pattern that matches on any variable and any value expression. diff --git a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs index 33a8857e..eb009919 100644 --- a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs @@ -11,7 +11,9 @@ public static class ExpressionPattern /// Creates a new pattern that matches any type of expressions. /// /// The pattern. - public static AnyPattern> Any() => new(); + public static AnyPattern> Any() + where TInstruction : notnull + => new(); /// /// Creates a new pattern that matches on instances of . @@ -19,6 +21,7 @@ public static class ExpressionPattern /// The instruction to match on. /// The pattern. public static InstructionExpressionPattern InstructionLiteral(TInstruction instruction) + where TInstruction : notnull => new(Pattern.Literal(instruction)); /// @@ -26,6 +29,7 @@ public static InstructionExpressionPattern InstructionLiteral /// The pattern. public static InstructionExpressionPattern Instruction() + where TInstruction : notnull => new(Pattern.Any()); /// @@ -34,13 +38,16 @@ public static InstructionExpressionPattern InstructionThe instruction pattern to match on. /// The pattern. public static InstructionExpressionPattern Instruction(Pattern instruction) + where TInstruction : notnull => new(instruction); /// /// Creates a new pattern that matches any type of variable expression. /// /// The pattern. - public static VariableExpressionPattern Variable() => new(); + public static VariableExpressionPattern Variable() + where TInstruction : notnull + => new(); /// /// Creates a new pattern that matches any type of variable expression. @@ -48,6 +55,7 @@ public static InstructionExpressionPattern InstructionThe pattern describing the referenced variable. /// The pattern. public static VariableExpressionPattern Variable(Pattern variable) + where TInstruction : notnull => new(variable); } @@ -56,6 +64,7 @@ public static VariableExpressionPattern Variable(Pat /// /// The type of instructions stored in the abstract syntax tree. public abstract class ExpressionPattern : Pattern> + where TInstruction : notnull { /// /// Wraps the expression pattern in an expression statement pattern. diff --git a/src/Core/Echo.Ast/Patterns/ExpressionStatementPattern.cs b/src/Core/Echo.Ast/Patterns/ExpressionStatementPattern.cs index cc0f6594..f20542fc 100644 --- a/src/Core/Echo.Ast/Patterns/ExpressionStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/ExpressionStatementPattern.cs @@ -7,6 +7,7 @@ namespace Echo.Ast.Patterns /// /// The type of instruction that is stored in the expression. public class ExpressionStatementPattern : StatementPattern + where TInstruction : notnull { /// /// Creates a new expression statement pattern matching on any embedded expression. diff --git a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs index 7f105d5b..233656e3 100644 --- a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs @@ -9,6 +9,7 @@ namespace Echo.Ast.Patterns /// /// The type of instruction that is stored in the expression. public class InstructionExpressionPattern : ExpressionPattern + where TInstruction : notnull { /// /// Creates a new instruction expression pattern describing an instruction expression with zero parameters. diff --git a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs index 5fece5b0..237281e5 100644 --- a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs @@ -9,6 +9,7 @@ namespace Echo.Ast.Patterns /// /// The type of instructions stored in the abstract syntax tree. public class PhiStatementPattern : StatementPattern + where TInstruction : notnull { /// /// Creates a new phi statement that matches on any target and source variables. diff --git a/src/Core/Echo.Ast/Patterns/StatementPattern.cs b/src/Core/Echo.Ast/Patterns/StatementPattern.cs index fa57f6fb..665807d1 100644 --- a/src/Core/Echo.Ast/Patterns/StatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/StatementPattern.cs @@ -12,13 +12,16 @@ public static class StatementPattern /// Creates a new pattern that matches any type of statement. /// /// The pattern. - public static AnyPattern> Any() => new(); + public static AnyPattern> Any() + where TInstruction : notnull + => new(); /// /// Creates a new pattern that matches on instances of with /// a single variable on the left hand side of the equals sign. /// public static AssignmentStatementPattern Assignment() + where TInstruction : notnull => new(Pattern.Any(), Pattern.Any>()); /// @@ -29,6 +32,7 @@ public static AssignmentStatementPattern Assignment( public static AssignmentStatementPattern Assignment( Pattern variable, Pattern> expression) + where TInstruction : notnull { return new AssignmentStatementPattern(variable, expression); } @@ -41,6 +45,7 @@ public static AssignmentStatementPattern Assignment( public static AssignmentStatementPattern Assignment( IEnumerable> variables, Pattern> expression) + where TInstruction : notnull { return new AssignmentStatementPattern(variables, expression); } @@ -49,7 +54,9 @@ public static AssignmentStatementPattern Assignment( /// Creates a new pattern that matches on instances of with /// any kind of embedded expression. /// - public static ExpressionStatementPattern Expression() => new(); + public static ExpressionStatementPattern Expression() + where TInstruction : notnull + => new(); /// /// Creates a new pattern that matches on instances of . @@ -57,6 +64,7 @@ public static AssignmentStatementPattern Assignment( /// The pattern for the embedded expression. public static ExpressionStatementPattern Expression( Pattern> expression) + where TInstruction : notnull { return new ExpressionStatementPattern(expression); } @@ -69,6 +77,7 @@ public static ExpressionStatementPattern Expression( /// The pattern. public static ExpressionStatementPattern Instruction( Pattern instruction) + where TInstruction : notnull { return new ExpressionStatementPattern(ExpressionPattern.Instruction(instruction)); } @@ -78,7 +87,9 @@ public static ExpressionStatementPattern Instruction /// any target and any number of source variables. /// /// The pattern. - public static PhiStatementPattern Phi() => new(); + public static PhiStatementPattern Phi() + where TInstruction : notnull + => new(); /// /// Creates a new pattern that matches on instances of with @@ -86,7 +97,9 @@ public static ExpressionStatementPattern Instruction /// /// The target pattern to match on. /// The pattern. - public static PhiStatementPattern Phi(Pattern target) => new(target); + public static PhiStatementPattern Phi(Pattern target) + where TInstruction : notnull + => new(target); } /// @@ -94,6 +107,7 @@ public static ExpressionStatementPattern Instruction /// /// The type of instructions stored in the abstract syntax tree. public abstract class StatementPattern : Pattern> + where TInstruction : notnull { } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs index 5fafcc1d..93f1c03c 100644 --- a/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs @@ -8,6 +8,7 @@ namespace Echo.Ast.Patterns /// /// The type of instruction that is stored in the expression. public class VariableExpressionPattern : ExpressionPattern + where TInstruction : notnull { /// /// Creates a new variable expression pattern that matches any variable. diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index 490161d7..5020f5ef 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -8,6 +8,7 @@ namespace Echo.Ast /// Represents a Phi statement in the Ast /// public sealed class PhiStatement : Statement + where TInstruction : notnull { /// /// Creates a new Phi statement diff --git a/src/Core/Echo.Ast/Statement.cs b/src/Core/Echo.Ast/Statement.cs index 87cbe03f..d53e637c 100644 --- a/src/Core/Echo.Ast/Statement.cs +++ b/src/Core/Echo.Ast/Statement.cs @@ -16,6 +16,7 @@ public static class Statement /// The type of instructions stored in the expression. /// The resulting statement. public static ExpressionStatement Expression(Expression expression) + where TInstruction : notnull => new(expression); /// @@ -28,6 +29,7 @@ public static ExpressionStatement Expression(Express public static AssignmentStatement Assignment( IVariable variable, Expression value) + where TInstruction : notnull { return new AssignmentStatement(variable, value); } @@ -42,6 +44,7 @@ public static AssignmentStatement Assignment( public static AssignmentStatement Assignment( IEnumerable variable, Expression value) + where TInstruction : notnull { return new AssignmentStatement(variable, value); } @@ -54,6 +57,7 @@ public static AssignmentStatement Assignment( /// The type of instructions stored in the expression. /// The resulting statement. public static PhiStatement Phi(IVariable representative, params IVariable[] sources) + where TInstruction : notnull { return new PhiStatement(representative, sources.Select(x => x.ToExpression())); } @@ -68,13 +72,17 @@ public static PhiStatement Phi(IVariable representat public static PhiStatement Phi( IVariable representative, params VariableExpression[] sources) + where TInstruction : notnull { return new PhiStatement(representative, sources); } } - + /// /// Provides a base contract for statements in the AST /// - public abstract class Statement : AstNode { } + public abstract class Statement : AstNode + where TInstruction : notnull + { + } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/VariableCollection.cs b/src/Core/Echo.Ast/VariableCollection.cs index 1dc44d31..32699d6b 100644 --- a/src/Core/Echo.Ast/VariableCollection.cs +++ b/src/Core/Echo.Ast/VariableCollection.cs @@ -4,6 +4,7 @@ namespace Echo.Ast; internal sealed class VariableCollection : Collection + where TInstruction : notnull { private readonly Statement _owner; diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index 3068443a..16e2f91f 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -9,6 +9,7 @@ namespace Echo.Ast /// Represents a variable expression in the AST /// public sealed class VariableExpression : Expression + where TInstruction : notnull { private IVariable _variable = null!; diff --git a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs index 7fc9a999..b5fb1242 100644 --- a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs +++ b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs @@ -1,10 +1,8 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Xml.Schema; -using Echo.ControlFlow.Regions; -using Echo.Graphing.Analysis.Traversal; using Echo.Graphing; namespace Echo.ControlFlow.Analysis.Domination @@ -13,25 +11,47 @@ namespace Echo.ControlFlow.Analysis.Domination /// Represents a dominator tree, where each tree node corresponds to one node in a graph, and each /// is immediately dominated by its parent. /// - public class DominatorTree : IGraph + public class DominatorTree : IGraph + where TInstruction : notnull { - private readonly IDictionary, DominatorTreeNode> _nodes; - private Dictionary, ISet>> _frontier; - private readonly object _frontierSyncLock = new object(); + private readonly IDictionary, DominatorTreeNode> _nodes; + private Dictionary, ISet>>? _frontier; + private readonly object _frontierSyncLock = new(); + + private DominatorTree(IDictionary, DominatorTreeNode> nodes, ControlFlowNode root) + { + _nodes = nodes; + Root = nodes[root]; + } + /// + /// Gets the root of the dominator tree. That is, the tree node that corresponds to the entrypoint of the + /// control flow graph. + /// + public DominatorTreeNode Root + { + get; + } + + /// + /// Gets the dominator tree node associated to the given control flow graph node. + /// + /// The control flow graph node to get the tree node from. + public DominatorTreeNode this[ControlFlowNode node] => _nodes[node]; + /// /// Constructs a dominator tree from a control flow graph. /// /// The control flow graph to turn into a dominator tree. /// The constructed dominator tree. - public static DominatorTree FromGraph(ControlFlowGraph graph) + public static DominatorTree FromGraph(ControlFlowGraph graph) { if (graph.EntryPoint == null) throw new ArgumentException("Control flow graph does not have an entrypoint."); var idoms = GetImmediateDominators(graph.EntryPoint); var nodes = ConstructTreeNodes(idoms, graph.EntryPoint); - return new DominatorTree(nodes, graph.EntryPoint); + return new DominatorTree(nodes, graph.EntryPoint); } /// @@ -44,30 +64,30 @@ public static DominatorTree FromGraph(ControlFlowGraph graph) /// https://www.cs.princeton.edu/courses/archive/fall03/cs528/handouts/a%20fast%20algorithm%20for%20finding.pdf /// https://www.cl.cam.ac.uk/~mr10/lengtarj.pdf /// - private static IDictionary, ControlFlowNode> GetImmediateDominators(ControlFlowNode entrypoint) + private static IDictionary, ControlFlowNode> GetImmediateDominators(ControlFlowNode entrypoint) { - var immediateDominators = new Dictionary, ControlFlowNode>(); + var immediateDominators = new Dictionary, ControlFlowNode?>(); - var pool = ArrayPool>.Shared; + var pool = ArrayPool>.Shared; var predecessorBuffer = pool.Rent(1); try { - var semi = new Dictionary, ControlFlowNode>(); - var ancestor = new Dictionary, ControlFlowNode>(); - var bucket = new Dictionary, ISet>>(); + var semi = new Dictionary, ControlFlowNode>(); + var ancestor = new Dictionary, ControlFlowNode?>(); + var bucket = new Dictionary, ISet>>(); // Traverse graph in depth first manner, and record node indices and parents. var traversalResult = TraverseGraph(entrypoint); // Initialize the intermediate mappings. var orderedNodes = traversalResult.TraversalOrder; - foreach (var node in orderedNodes.Cast>()) + foreach (var node in orderedNodes.Cast>()) { immediateDominators[node] = null; semi[node] = node; ancestor[node] = null; - bucket[node] = new HashSet>(); + bucket[node] = new HashSet>(); } for (int i = orderedNodes.Count - 1; i >= 1; i--) @@ -107,7 +127,7 @@ private static IDictionary, ControlFlowNode> GetImmediateD { var w = orderedNodes[i]; if (immediateDominators[w] != semi[w]) - immediateDominators[w] = immediateDominators[immediateDominators[w]]; + immediateDominators[w] = immediateDominators[immediateDominators[w]!]; } immediateDominators[entrypoint] = entrypoint; @@ -117,7 +137,7 @@ private static IDictionary, ControlFlowNode> GetImmediateD pool.Return(predecessorBuffer); } - int GetPredecessors(ControlFlowNode node) + int GetPredecessors(ControlFlowNode node) { // If the current node is the entrypoint of a handler block, then we implicitly have // all the nodes in the protected region as predecessor. However, for this algorithm, @@ -142,22 +162,22 @@ int GetPredecessors(ControlFlowNode node) // Copy over protected entrypoint if we were a handler entrypoint. if (isHandlerEntrypoint) - predecessorBuffer[actualInDegree1 - 1] = node.GetParentExceptionHandler().ProtectedRegion.EntryPoint; + predecessorBuffer[actualInDegree1 - 1] = node.GetParentExceptionHandler()!.ProtectedRegion.EntryPoint!; return actualInDegree1; } - return immediateDominators; + return immediateDominators!; } - private static TraversalResult TraverseGraph(ControlFlowNode entrypoint) + private static TraversalResult TraverseGraph(ControlFlowNode entrypoint) { var result = new TraversalResult(); - result.TraversalOrder = new List>(); - result.NodeIndices = new Dictionary, int>(); - result.NodeParents = new Dictionary, ControlFlowNode>(); + result.TraversalOrder = new List>(); + result.NodeIndices = new Dictionary, int>(); + result.NodeParents = new Dictionary, ControlFlowNode>(); - var visited = new HashSet>(); - var agenda = new Stack>(); + var visited = new HashSet>(); + var agenda = new Stack>(); agenda.Push(entrypoint); while (agenda.Count > 0) @@ -181,11 +201,11 @@ private static TraversalResult TraverseGraph(ControlFlowNode entrypoint) && currentNode.IsInRegion(parentEh.ProtectedRegion)) { for (int i = 0; i < parentEh.Handlers.Count; i++) - Schedule(currentNode, parentEh.Handlers[i].GetEntryPoint()); + Schedule(currentNode, parentEh.Handlers[i].GetEntryPoint()!); } } - void Schedule(ControlFlowNode origin, ControlFlowNode successor) + void Schedule(ControlFlowNode origin, ControlFlowNode successor) { agenda.Push(successor); @@ -201,13 +221,13 @@ void Schedule(ControlFlowNode origin, ControlFlowNode successor) /// /// The constructed tree. Each node added to the tree is linked to a node in the original graph by /// its name. - private static IDictionary, DominatorTreeNode> ConstructTreeNodes( - IDictionary, ControlFlowNode> idoms, - ControlFlowNode entrypoint) + private static IDictionary, DominatorTreeNode> ConstructTreeNodes( + IDictionary, ControlFlowNode> idoms, + ControlFlowNode entrypoint) { - var result = new Dictionary, DominatorTreeNode> + var result = new Dictionary, DominatorTreeNode> { - [entrypoint] = new DominatorTreeNode(entrypoint) + [entrypoint] = new DominatorTreeNode(entrypoint) }; foreach (var entry in idoms) @@ -218,9 +238,9 @@ private static IDictionary, DominatorTreeNode> ConstructTr if (dominator != dominated) { if (!result.TryGetValue(dominated, out var child)) - result[dominated] = child = new DominatorTreeNode(dominated); + result[dominated] = child = new DominatorTreeNode(dominated); if (!result.TryGetValue(dominator, out var parent)) - result[dominator] = parent = new DominatorTreeNode(dominator); + result[dominator] = parent = new DominatorTreeNode(dominator); parent.Children.Add(child); } @@ -230,17 +250,17 @@ private static IDictionary, DominatorTreeNode> ConstructTr } private static void Link( - ControlFlowNode parent, - ControlFlowNode node, - IDictionary, ControlFlowNode> ancestors) + ControlFlowNode parent, + ControlFlowNode node, + IDictionary, ControlFlowNode?> ancestors) { ancestors[node] = parent; } - private static ControlFlowNode Eval( - ControlFlowNode node, - IDictionary, ControlFlowNode> ancestors, - IDictionary, ControlFlowNode> semi, + private static ControlFlowNode Eval( + ControlFlowNode node, + IDictionary, ControlFlowNode?> ancestors, + IDictionary, ControlFlowNode> semi, in TraversalResult order) { var a = ancestors[node]; @@ -253,28 +273,7 @@ private static ControlFlowNode Eval( return node; } - - private DominatorTree(IDictionary, DominatorTreeNode> nodes, ControlFlowNode root) - { - _nodes = nodes; - Root = nodes[root]; - } - /// - /// Gets the root of the dominator tree. That is, the tree node that corresponds to the entrypoint of the - /// control flow graph. - /// - public DominatorTreeNode Root - { - get; - } - - /// - /// Gets the dominator tree node associated to the given control flow graph node. - /// - /// The control flow graph node to get the tree node from. - public DominatorTreeNode this[ControlFlowNode node] => _nodes[node]; - /// /// Determines whether one control flow graph node dominates another node. That is, whether execution of the /// dominated node means the dominator node has to be executed. @@ -285,15 +284,15 @@ public DominatorTreeNode Root /// True if the node in indeed dominates the provided control flow /// node in , false otherwise. /// - public bool Dominates(ControlFlowNode dominator, ControlFlowNode dominated) + public bool Dominates(ControlFlowNode dominator, ControlFlowNode dominated) { var current = this[dominated]; - while (current != null) + while (current is not null) { if (current.OriginalNode == dominator) return true; - current = (DominatorTreeNode) current.Parent; + current = current.Parent; } return false; @@ -305,9 +304,9 @@ public bool Dominates(ControlFlowNode dominator, ControlFlowNode dominated /// /// The node to obtain the dominance frontier from. /// A collection of nodes representing the dominance frontier. - public IEnumerable> GetDominanceFrontier(ControlFlowNode node) + public IEnumerable> GetDominanceFrontier(ControlFlowNode node) { - if (_frontier == null) + if (_frontier is null) { lock (_frontierSyncLock) { @@ -319,9 +318,10 @@ public IEnumerable> GetDominanceFrontier(ControlFlowNode n return _frontier[node]; } + [MemberNotNull(nameof(_frontier))] private void InitializeDominanceFrontiers() { - var frontier = _nodes.Keys.ToDictionary(x => x, _ => (ISet>) new HashSet>()); + var frontier = _nodes.Keys.ToDictionary(x => x, _ => (ISet>) new HashSet>()); foreach (var node in _nodes.Keys) { @@ -334,10 +334,10 @@ private void InitializeDominanceFrontiers() foreach (var p in predecessors) { var runner = p; - while (runner != ((DominatorTreeNode) _nodes[node].Parent).OriginalNode) + while (runner is not null && runner != _nodes[node].Parent?.OriginalNode) { frontier[runner].Add(node); - runner = (ControlFlowNode) ((DominatorTreeNode)_nodes[runner].Parent).OriginalNode; + runner = _nodes[runner].Parent?.OriginalNode; } } } @@ -350,7 +350,7 @@ private void InitializeDominanceFrontiers() IEnumerable ISubGraph.GetNodes() => _nodes.Values; /// - public IEnumerable GetSubGraphs() => Enumerable.Empty(); + public IEnumerable GetSubGraphs() => []; /// IEnumerable IGraph.GetEdges() => @@ -358,19 +358,19 @@ IEnumerable IGraph.GetEdges() => private struct TraversalResult { - public Dictionary, int> NodeIndices + public Dictionary, int> NodeIndices { get; set; } - public List> TraversalOrder + public List> TraversalOrder { get; set; } - public Dictionary, ControlFlowNode> NodeParents + public Dictionary, ControlFlowNode> NodeParents { get; set; diff --git a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTreeNode.cs b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTreeNode.cs index e9a73ce5..48a4998d 100644 --- a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTreeNode.cs +++ b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTreeNode.cs @@ -11,21 +11,24 @@ namespace Echo.ControlFlow.Analysis.Domination /// It stores the original control flow graph node from which this tree node was inferred, as well a reference to its /// immediate dominator, and the node it immediate dominates. /// - public class DominatorTreeNode : TreeNodeBase, IIdentifiedNode + public class DominatorTreeNode : TreeNodeBase + where TInstruction : notnull { - internal DominatorTreeNode(ControlFlowNode node) + internal DominatorTreeNode(ControlFlowNode node) { OriginalNode = node; - Children = new TreeNodeCollection, DominatorTreeNode>(this); + Children = new TreeNodeCollection, DominatorTreeNode>(this); } - /// - public long Id => OriginalNode.Id; + /// + /// The parent of this + /// + public new DominatorTreeNode? Parent => (DominatorTreeNode?) base.Parent; /// /// Gets the node that this tree node was derived from. /// - public IIdentifiedNode OriginalNode + public ControlFlowNode OriginalNode { get; } @@ -33,7 +36,7 @@ public IIdentifiedNode OriginalNode /// /// Gets the children of the current node. /// - public IList> Children + public IList> Children { get; } @@ -46,16 +49,16 @@ public IList> Children /// immediate successor of the original node. /// /// The children, represented by the dominator tree nodes. - public IEnumerable> GetDirectChildren() => - Children.Where(c => c.Parent.HasPredecessor(OriginalNode)); + public IEnumerable> GetDirectChildren() => + Children.Where(c => c.Parent!.HasPredecessor(OriginalNode)); /// /// Gets a collection of children representing all nodes that were dominated by the original node, but were not /// an immediate successor of the original node. /// /// The children, represented by the dominator tree nodes. - public IEnumerable> GetIndirectChildren() => - Children.Where(c => !c.Parent.HasPredecessor(OriginalNode)); + public IEnumerable> GetIndirectChildren() => + Children.Where(c => !c.Parent!.HasPredecessor(OriginalNode)); /// /// Gets all the nodes that are dominated by this control flow graph node. @@ -64,9 +67,9 @@ public IEnumerable> GetIndirectChildren() => /// /// Because of the nature of dominator analysis, this also includes the current node. /// - public IEnumerable> GetDominatedNodes() + public IEnumerable> GetDominatedNodes() { - var stack = new Stack>(); + var stack = new Stack>(); stack.Push(this); while (stack.Count > 0) diff --git a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs index 89d7a9f1..1cbfbf91 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Echo.Code; namespace Echo.ControlFlow.Blocks { @@ -9,12 +9,13 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions that the basic block contains. public class BasicBlock : IBlock + where TInstruction : notnull { /// /// Creates a new, empty basic block. /// public BasicBlock() - : this(Enumerable.Empty()) + : this([]) { } @@ -23,7 +24,7 @@ public BasicBlock() /// /// The offset to assign to the basic block. public BasicBlock(long offset) - : this(offset, Enumerable.Empty()) + : this(offset, []) { } @@ -33,7 +34,7 @@ public BasicBlock(long offset) /// The instructions to add to the basic block. /// Occurs when is null. public BasicBlock(IEnumerable instructions) - : this(-1, instructions) + : this(0, instructions) { } @@ -45,7 +46,7 @@ public BasicBlock(IEnumerable instructions) /// Occurs when is null. public BasicBlock(long offset, IEnumerable instructions) { - if (instructions == null) + if (instructions is null) throw new ArgumentNullException(nameof(instructions)); Offset = offset; Instructions = new List(instructions); @@ -76,12 +77,12 @@ public IList Instructions /// /// Gets the first instruction that is evaluated when this basic block is executed. /// - public TInstruction Header => !IsEmpty ? Instructions[0] : default; + public TInstruction? Header => !IsEmpty ? Instructions[0] : default; /// /// Gets the last instruction that is evaluated when this basic block is executed. /// - public TInstruction Footer => !IsEmpty ? Instructions[Instructions.Count - 1] : default; + public TInstruction? Footer => !IsEmpty ? Instructions[Instructions.Count - 1] : default; /// public override string ToString() => BlockFormatter.Format(this); @@ -94,6 +95,13 @@ public IList Instructions /// public BasicBlock GetLastBlock() => this; + public void UpdateOffset(IArchitecture architecture) + { + Offset = Header is not null + ? architecture.GetOffset(Header) + : 0; + } + /// public void AcceptVisitor(IBlockVisitor visitor) => visitor.VisitBasicBlock(this); diff --git a/src/Core/Echo.ControlFlow/Blocks/BlockFormatter.cs b/src/Core/Echo.ControlFlow/Blocks/BlockFormatter.cs index bb15c1b6..c2e17676 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BlockFormatter.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BlockFormatter.cs @@ -8,6 +8,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions stored in the block. public class BlockFormatter : IBlockListener + where TInstruction : notnull { private const string DefaultIndentationString = " "; diff --git a/src/Core/Echo.ControlFlow/Blocks/BlockListenerBase.cs b/src/Core/Echo.ControlFlow/Blocks/BlockListener.cs similarity index 94% rename from src/Core/Echo.ControlFlow/Blocks/BlockListenerBase.cs rename to src/Core/Echo.ControlFlow/Blocks/BlockListener.cs index a8f119f2..5bbb9845 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BlockListenerBase.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BlockListener.cs @@ -4,7 +4,8 @@ namespace Echo.ControlFlow.Blocks /// Provides an empty base implementation for a block listener. /// /// The type of instructions in the blocks. - public abstract class BlockListenerBase : IBlockListener + public abstract class BlockListener : IBlockListener + where TInstruction : notnull { /// public virtual void VisitBasicBlock(BasicBlock block) diff --git a/src/Core/Echo.ControlFlow/Blocks/BlockWalker.cs b/src/Core/Echo.ControlFlow/Blocks/BlockWalker.cs index 97572372..417f48fd 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BlockWalker.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BlockWalker.cs @@ -7,6 +7,7 @@ namespace Echo.ControlFlow.Blocks /// /// public class BlockWalker : IBlockVisitor + where TInstruction : notnull { private readonly IBlockListener _listener; diff --git a/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs b/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs index 890445f0..a9f236ba 100644 --- a/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace Echo.ControlFlow.Blocks { @@ -8,6 +7,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions stored in the blocks. public class ExceptionHandlerBlock : IBlock + where TInstruction : notnull { /// /// Gets the protected block. @@ -28,7 +28,7 @@ public IList> Handlers /// /// Gets or sets a user-defined tag that is assigned to this block. /// - public object Tag + public object? Tag { get; set; @@ -48,7 +48,7 @@ public IEnumerable> GetAllBlocks() } /// - public BasicBlock GetFirstBlock() + public BasicBlock? GetFirstBlock() { var result = ProtectedBlock.GetFirstBlock(); for (int i = 0; i < Handlers.Count && result is null; i++) @@ -57,9 +57,9 @@ public BasicBlock GetFirstBlock() } /// - public BasicBlock GetLastBlock() + public BasicBlock? GetLastBlock() { - BasicBlock result = null; + var result = default(BasicBlock); for (int i = Handlers.Count - 1; i > 0 && result is null; i--) result = Handlers[i].GetFirstBlock(); return result ?? ProtectedBlock.GetLastBlock(); diff --git a/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs b/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs index 7b8a5d50..ee33d910 100644 --- a/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs @@ -7,11 +7,12 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions that this block contains. public class HandlerBlock : IBlock + where TInstruction : notnull { /// /// Gets or sets the prologue block that gets executed before the main handler block (if available). /// - public ScopeBlock Prologue + public ScopeBlock? Prologue { get; set; @@ -28,7 +29,7 @@ public ScopeBlock Contents /// /// Gets or sets the epilogue block that gets executed after the main handler block (if available). /// - public ScopeBlock Epilogue + public ScopeBlock? Epilogue { get; set; @@ -37,7 +38,7 @@ public ScopeBlock Epilogue /// /// Gets or sets a user-defined tag that is assigned to this block. /// - public object Tag + public object? Tag { get; set; @@ -63,13 +64,13 @@ public IEnumerable> GetAllBlocks() } /// - public BasicBlock GetFirstBlock() => + public BasicBlock? GetFirstBlock() => Prologue?.GetFirstBlock() ?? Contents.GetFirstBlock() ?? Epilogue?.GetFirstBlock(); /// - public BasicBlock GetLastBlock() => + public BasicBlock? GetLastBlock() => Epilogue?.GetLastBlock() ?? Contents.GetLastBlock() ?? Prologue?.GetLastBlock(); diff --git a/src/Core/Echo.ControlFlow/Blocks/IBlock.cs b/src/Core/Echo.ControlFlow/Blocks/IBlock.cs index d9ea6bf7..a59245c2 100644 --- a/src/Core/Echo.ControlFlow/Blocks/IBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/IBlock.cs @@ -7,6 +7,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions that this block contains. public interface IBlock + where TInstruction : notnull { /// /// Gets an ordered collection of all basic blocks that can be found in this block. @@ -18,13 +19,13 @@ public interface IBlock /// Gets the first basic block that appears in the ordered list of blocks. /// /// The first basic block, or null if the block contains no basic blocks.. - BasicBlock GetFirstBlock(); + BasicBlock? GetFirstBlock(); /// /// Gets the last basic block that appears in the ordered list of blocks. /// /// The last basic block, or null if the block contains no basic blocks.. - BasicBlock GetLastBlock(); + BasicBlock? GetLastBlock(); /// /// Visit the current block using the provided visitor. diff --git a/src/Core/Echo.ControlFlow/Blocks/IBlockListener.cs b/src/Core/Echo.ControlFlow/Blocks/IBlockListener.cs index d198da63..7c12b82b 100644 --- a/src/Core/Echo.ControlFlow/Blocks/IBlockListener.cs +++ b/src/Core/Echo.ControlFlow/Blocks/IBlockListener.cs @@ -5,6 +5,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions in the blocks. public interface IBlockListener + where TInstruction : notnull { /// /// Visits a basic block. diff --git a/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs b/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs index a67dee96..21823d16 100644 --- a/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs +++ b/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs @@ -5,6 +5,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions in the blocks. public interface IBlockVisitor + where TInstruction : notnull { /// /// Visits a basic block. @@ -38,6 +39,7 @@ public interface IBlockVisitor /// The type of state to pass onto the visitor. /// The type of the result for every visited block. public interface IBlockVisitor + where TInstruction : notnull { /// /// Visits a basic block. diff --git a/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs b/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs index 63145f71..4f439f81 100644 --- a/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs @@ -8,6 +8,7 @@ namespace Echo.ControlFlow.Blocks /// /// The type of instructions that this block contains. public class ScopeBlock : IBlock + where TInstruction : notnull { /// /// Gets an ordered, mutable collection of blocks that are present in this scope. @@ -22,12 +23,12 @@ public IEnumerable> GetAllBlocks() => Blocks.SelectMany(b => b.GetAllBlocks()); /// - public BasicBlock GetFirstBlock() => Blocks.Count > 0 + public BasicBlock? GetFirstBlock() => Blocks.Count > 0 ? Blocks[0].GetFirstBlock() : null; /// - public BasicBlock GetLastBlock() => Blocks.Count > 0 + public BasicBlock? GetLastBlock() => Blocks.Count > 0 ? Blocks[Blocks.Count - 1].GetLastBlock() : null; diff --git a/src/Core/Echo.ControlFlow/Collections/AdjacencyCollection.cs b/src/Core/Echo.ControlFlow/Collections/AdjacencyCollection.cs index 671ff47b..993e7cf2 100644 --- a/src/Core/Echo.ControlFlow/Collections/AdjacencyCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/AdjacencyCollection.cs @@ -3,24 +3,22 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Echo.Code; -using Echo.Graphing; namespace Echo.ControlFlow.Collections { /// /// Represents a collection of edges originating from a single node. /// - /// The type of data that each node stores. + /// The type of data that each node stores. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class AdjacencyCollection : ICollection> + public class AdjacencyCollection : ICollection> + where TInstruction : notnull { - private readonly Dictionary>> _neighbours - = new Dictionary>>(); + private readonly Dictionary, HashSet>> _neighbours = new(); private int _count; - internal AdjacencyCollection(ControlFlowNode owner, ControlFlowEdgeType edgeType) + internal AdjacencyCollection(ControlFlowNode owner, ControlFlowEdgeType edgeType) { EdgeType = edgeType; Owner = owner ?? throw new ArgumentNullException(nameof(owner)); @@ -43,29 +41,19 @@ public ControlFlowEdgeType EdgeType /// /// Gets the node that all edges are originating from. /// - public ControlFlowNode Owner + public ControlFlowNode Owner { get; } - /// - /// Creates and adds a edge to the node with the provided address. - /// - /// The address of the new neighbouring node. - /// The created edge. - public ControlFlowEdge Add(long neighbourAddress) - { - return Add(Owner.ParentGraph.Nodes[neighbourAddress]); - } - /// /// Creates and adds a edge to the provided node. /// /// The new neighbouring node. /// The created edge. - public ControlFlowEdge Add(ControlFlowNode neighbour) + public ControlFlowEdge Add(ControlFlowNode neighbour) { - var edge = new ControlFlowEdge(Owner, neighbour, EdgeType); + var edge = new ControlFlowEdge(Owner, neighbour, EdgeType); Add(edge); return edge; } @@ -78,7 +66,7 @@ public ControlFlowEdge Add(ControlFlowNode neighbour) /// /// Occurs when the provided edge cannot be added to this collection because of an invalid source node or edge type. /// - public ControlFlowEdge Add(ControlFlowEdge edge) + public ControlFlowEdge Add(ControlFlowEdge edge) { AssertEdgeValidity(Owner, edge, EdgeType); GetEdges(edge.Target).Add(edge); @@ -88,7 +76,7 @@ public ControlFlowEdge Add(ControlFlowEdge edge) } /// - void ICollection>.Add(ControlFlowEdge edge) + void ICollection>.Add(ControlFlowEdge edge) { Add(edge); } @@ -107,19 +95,19 @@ public void Clear() /// /// The node to check. /// True if the provided node is a neighbour, false otherwise. - public bool Contains(ControlFlowNode neighbour) + public bool Contains(ControlFlowNode neighbour) { return GetEdges(neighbour).Count > 0; } /// - public bool Contains(ControlFlowEdge item) + public bool Contains(ControlFlowEdge item) { return GetEdges(item.Target).Contains(item); } /// - public void CopyTo(ControlFlowEdge[] array, int arrayIndex) + public void CopyTo(ControlFlowEdge[] array, int arrayIndex) { foreach (var edges in _neighbours.Values) { @@ -128,23 +116,12 @@ public void CopyTo(ControlFlowEdge[] array, int arrayIndex) } } - /// - /// Removes all edges originating from the current node to the neighbour with the provided address. - /// - /// The address of the neighbour to cut ties with. - /// True if at least one edge was removed, false otherwise. - public bool Remove(long neighbourAddress) - { - var nodes = Owner.ParentGraph.Nodes; - return nodes.Contains(neighbourAddress) && Remove(nodes[neighbourAddress]); - } - /// /// Removes all edges originating from the current node to the provided neighbour. /// /// The neighbour to cut ties with. /// True if at least one edge was removed, false otherwise. - public bool Remove(ControlFlowNode neighbour) + public bool Remove(ControlFlowNode neighbour) { var edges = GetEdges(neighbour); if (edges.Count > 0) @@ -158,7 +135,7 @@ public bool Remove(ControlFlowNode neighbour) } /// - public bool Remove(ControlFlowEdge edge) + public bool Remove(ControlFlowEdge edge) { bool result = GetEdges(edge.Target).Remove(edge); if (result) @@ -171,7 +148,7 @@ public bool Remove(ControlFlowEdge edge) } internal static void AssertEdgeValidity( - ControlFlowNode owner, ControlFlowEdge item, ControlFlowEdgeType type) + ControlFlowNode owner, ControlFlowEdge item, ControlFlowEdgeType type) { if (item.Type != type) { @@ -188,87 +165,25 @@ internal static void AssertEdgeValidity( /// /// The neighbouring node. /// The edges. - public IEnumerable> GetEdgesToNeighbour(ControlFlowNode target) => + public IEnumerable> GetEdgesToNeighbour(ControlFlowNode target) => GetEdges(target); - private ICollection> GetEdges(INode target) + private ICollection> GetEdges(ControlFlowNode target) { if (!_neighbours.TryGetValue(target, out var edges)) { - edges = new HashSet>(); + edges = new HashSet>(); _neighbours[target] = edges; } return edges; } - /// - /// Obtains an enumerator that enumerates all nodes in the collection. - /// - /// - public Enumerator GetEnumerator() => new Enumerator(this); - - IEnumerator> IEnumerable>.GetEnumerator() => - GetEnumerator(); + /// + public IEnumerator> GetEnumerator() => + _neighbours.SelectMany(x => x.Value).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// - /// Represents an enumerator that enumerates all nodes in a control flow graph. - /// - public struct Enumerator : IEnumerator> - { - private Dictionary>>.Enumerator _groupEnumerator; - private HashSet>.Enumerator _itemIterator; - private bool _hasItemIterator; - private ControlFlowEdge _current; - - /// - /// Creates a new instance of the structure. - /// - /// The collection to enumerate. - public Enumerator(AdjacencyCollection collection) - { - _groupEnumerator = collection._neighbours.GetEnumerator(); - _itemIterator = default; - _hasItemIterator = false; - _current = null; - } - - /// - public ControlFlowEdge Current => _current; - - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - while (true) - { - if (!_hasItemIterator) - { - if (!_groupEnumerator.MoveNext()) - return false; - - _itemIterator = _groupEnumerator.Current.Value.GetEnumerator(); - _hasItemIterator = true; - } - - if (_itemIterator.MoveNext()) - { - _current = _itemIterator.Current; - return true; - } - - _hasItemIterator = false; - } - } - - /// - public void Reset() => ((IEnumerator) _groupEnumerator).Reset(); - - /// - public void Dispose() => _groupEnumerator.Dispose(); - } } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs index 562f0944..c8abe147 100644 --- a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs @@ -3,21 +3,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Echo.ControlFlow.Blocks; namespace Echo.ControlFlow.Collections { /// /// Represents a mutable collection of nodes present in a graph. /// - /// The type of data that is stored in each node. + /// The type of data that is stored in each node. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class NodeCollection : ICollection> + public class NodeCollection : ICollection> + where TInstruction : notnull { - private readonly Dictionary> _nodes = new Dictionary>(); - private readonly ControlFlowGraph _owner; + private readonly List> _nodes = new(); + private readonly ControlFlowGraph _owner; - internal NodeCollection(ControlFlowGraph owner) + internal NodeCollection(ControlFlowGraph owner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } @@ -28,14 +28,8 @@ internal NodeCollection(ControlFlowGraph owner) /// public bool IsReadOnly => false; - /// - /// Gets a node by its offset. - /// - /// The node offset. - public ControlFlowNode this[long offset] => _nodes[offset]; - /// - public void Add(ControlFlowNode item) + public void Add(ControlFlowNode item) { if (item is null) throw new ArgumentNullException(); @@ -43,10 +37,8 @@ public void Add(ControlFlowNode item) return; if (item.ParentGraph != null) throw new ArgumentException("Cannot add a node from another graph."); - if (_nodes.ContainsKey(item.Offset)) - throw new ArgumentException($"A node with offset 0x{item.Offset:X8} was already added to the graph."); - _nodes.Add(item.Offset, item); + _nodes.Add(item); item.ParentRegion = _owner; } @@ -57,23 +49,23 @@ public void Add(ControlFlowNode item) /// /// Occurs when at least one node in the provided collection is already added to another graph. /// - public void AddRange(IEnumerable> items) + public void AddRange(IEnumerable> items) { var nodes = items.ToArray(); + // Validate before adding. for (int i = 0; i < nodes.Length; i++) { var node = nodes[i]; - if (node.ParentGraph != _owner && node.ParentGraph != null) + if (node.ParentGraph is not null && node.ParentGraph != _owner) throw new ArgumentException("Sequence contains nodes from another graph."); - if (_nodes.ContainsKey(node.Offset)) - throw new ArgumentException($"Sequence contains nodes with offsets that were already added to the graph."); } + // Add the nodes. for (int i = 0; i < nodes.Length; i++) { var node = nodes[i]; - _nodes.Add(node.Offset, node); + _nodes.Add(node); node.ParentRegion = _owner; } } @@ -81,43 +73,20 @@ public void AddRange(IEnumerable> items) /// public void Clear() { - foreach (var node in _nodes.Keys.ToArray()) + foreach (var node in _nodes.ToArray()) Remove(node); } - /// - /// Determines whether a node with a specific offset was added to the collection. - /// - /// The offset to the node. - /// true if there exists a node with the provided offset, false otherwise. - public bool Contains(long offset) - { - return _nodes.ContainsKey(offset); - } - /// - public bool Contains(ControlFlowNode item) - { - if (item == null) - return false; - return _nodes.TryGetValue(item.Offset, out var node) && node == item; - } + public bool Contains(ControlFlowNode item) => _nodes.Contains(item); /// - public void CopyTo(ControlFlowNode[] array, int arrayIndex) - { - _nodes.Values.CopyTo(array, arrayIndex); - } + public void CopyTo(ControlFlowNode[] array, int arrayIndex) => _nodes.CopyTo(array, arrayIndex); - /// - /// Removes a node by its offset. - /// - /// The offset. of the node to remove. - /// true if the collection contained a node with the provided offset., and the node was removed - /// successfully, false otherwise. - public bool Remove(long offset) + /// + public bool Remove(ControlFlowNode item) { - if (_nodes.TryGetValue(offset, out var item)) + if (_nodes.Remove(item)) { // Remove outgoing edges. item.UnconditionalEdge = null; @@ -144,111 +113,39 @@ public bool Remove(long offset) } } - //Remove node. - _nodes.Remove(offset); - item.ParentRegion.RemoveNode(item); - item.ParentRegion = null; - + // Remove node from graph. + if (item.ParentRegion is not null) + { + item.ParentRegion.RemoveNode(item); + item.ParentRegion = null; + } + return true; } return false; } - /// - public bool Remove(ControlFlowNode item) => - item != null && Remove(item.Offset); - /// /// Synchronizes all offsets of each node and basic blocks with the underlying instructions. /// - /// Occurs when one or more basic blocks referenced by the nodes - /// are in a state that new offsets cannot be determined. This includes empty basic blocks and duplicated header - /// offsets. - /// - /// - /// Because updating offsets is a relatively expensive task, calls to this method should be delayed as much as - /// possible. - /// - /// - /// This method will invalidate any enumerators that are enumerating this collection of nodes. - /// - /// public void UpdateOffsets() { - var nodes = new Dictionary>(Count); - - // Verify whether all basic blocks are valid, i.e. are not empty and contain no duplicate offsets. - // If any problem arises we do not want to commit any changes to the node collection. - foreach (var entry in _nodes) - { - var node = entry.Value; - - if (node.Contents.IsEmpty) - throw new InvalidOperationException("Collection contains one or more empty basic blocks."); - - long newOffset = _owner.Architecture.GetOffset(node.Contents.Header); - if (nodes.ContainsKey(newOffset)) - throw new InvalidOperationException($"Collection contains multiple basic blocks with header offset {newOffset:X8}."); - - nodes.Add(newOffset, node); - } - - // Update the collection by editing the dictionary directly instead of using the public Clear and Add - // methods. The public methods remove any incident edges to each node, which means we'd have to add them - // again. - - _nodes.Clear(); - - foreach (var entry in nodes) - { - entry.Value.Offset = entry.Key; - entry.Value.Contents.Offset = entry.Key; - _nodes.Add(entry.Key, entry.Value); - } + foreach (var node in _nodes) + node.UpdateOffset(); } - /// - /// Obtains an enumerator that enumerates all nodes in the collection. - /// - /// - public Enumerator GetEnumerator() => new Enumerator(this); - - IEnumerator> IEnumerable>.GetEnumerator() => - GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - /// Represents an enumerator that enumerates all nodes in a control flow graph. - /// - public struct Enumerator : IEnumerator> + public IDictionary> CreateOffsetMap() { - private Dictionary>.Enumerator _enumerator; - - /// - /// Creates a new instance of the structure. - /// - /// The collection to enumerate. - public Enumerator(NodeCollection collection) - { - _enumerator = collection._nodes.GetEnumerator(); - } - - /// - public ControlFlowNode Current => _enumerator.Current.Value; - - object IEnumerator.Current => Current; + return _nodes.ToDictionary(x => x.Offset, x => x); + } - /// - public bool MoveNext() => _enumerator.MoveNext(); + public ControlFlowNode? GetByOffset(long offset) => _nodes.FirstOrDefault(x => x.Contents.Offset == offset); - /// - public void Reset() => ((IEnumerator) _enumerator).Reset(); + /// + public IEnumerator> GetEnumerator() => _nodes.GetEnumerator(); - /// - public void Dispose() => _enumerator.Dispose(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Collections/RegionCollection.cs b/src/Core/Echo.ControlFlow/Collections/RegionCollection.cs index 0c2d91e4..647b23e7 100644 --- a/src/Core/Echo.ControlFlow/Collections/RegionCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/RegionCollection.cs @@ -12,6 +12,7 @@ namespace Echo.ControlFlow.Collections /// The type of the region to store. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public class RegionCollection : Collection + where TInstruction : notnull where TRegion : ControlFlowRegion { private readonly IControlFlowRegion _owner; diff --git a/src/Core/Echo.ControlFlow/Collections/RegionNodeCollection.cs b/src/Core/Echo.ControlFlow/Collections/RegionNodeCollection.cs index 06526594..4cb90aa3 100644 --- a/src/Core/Echo.ControlFlow/Collections/RegionNodeCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/RegionNodeCollection.cs @@ -13,6 +13,7 @@ namespace Echo.ControlFlow.Collections /// The type of data that each node in the graph stores. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public class RegionNodeCollection : Collection> + where TInstruction : notnull { private readonly IControlFlowRegion _owner; diff --git a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs index 924c9f97..81f6438f 100644 --- a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs +++ b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Collections.Generic; using Echo.Code; +using Echo.ControlFlow.Blocks; namespace Echo.ControlFlow.Construction { @@ -10,6 +11,7 @@ namespace Echo.ControlFlow.Construction /// /// The type of instructions to store in the control flow graph. public abstract class FlowGraphBuilderBase : IFlowGraphBuilder + where TInstruction : notnull { /// public abstract IArchitecture Architecture @@ -25,7 +27,7 @@ public ControlFlowGraph ConstructFlowGraph(long entrypoint, IEnume var graph = new ControlFlowGraph(Architecture); CreateNodes(graph, traversalResult); ConnectNodes(graph, traversalResult); - graph.EntryPoint = graph.GetNodeByOffset(entrypoint); + graph.EntryPoint = graph.Nodes.GetByOffset(entrypoint); return graph; } @@ -42,7 +44,8 @@ protected abstract IInstructionTraversalResult CollectInstructions private void CreateNodes(ControlFlowGraph graph, IInstructionTraversalResult traversalResult) { - ControlFlowNode currentNode = null; + var currentNode = default(ControlFlowNode); + foreach (var instruction in traversalResult.GetAllInstructions()) { // Check if we reached a new block header. @@ -50,7 +53,7 @@ private void CreateNodes(ControlFlowGraph graph, IInstructionTrave if (currentNode == null || traversalResult.IsBlockHeader(offset)) { // We arrived at a new basic block header. Create a new node for it. - currentNode = new ControlFlowNode(offset); + currentNode = new ControlFlowNode(new BasicBlock(offset)); graph.Nodes.Add(currentNode); } @@ -98,7 +101,7 @@ private void ConnectNodes(ControlFlowGraph graph, IInstructionTrav { var successor = successorsBuffer[i]; - var successorNode = graph.GetNodeByOffset(successor.DestinationAddress); + var successorNode = graph.Nodes.GetByOffset(successor.DestinationAddress); if (successorNode == null) throw new ArgumentException($"Instruction at address {footerOffset:X8} refers to a non-existing node."); diff --git a/src/Core/Echo.ControlFlow/Construction/IFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/IFlowGraphBuilder.cs index 23889097..847482f7 100644 --- a/src/Core/Echo.ControlFlow/Construction/IFlowGraphBuilder.cs +++ b/src/Core/Echo.ControlFlow/Construction/IFlowGraphBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Echo.ControlFlow.Regions.Detection; using Echo.Code; @@ -11,6 +10,7 @@ namespace Echo.ControlFlow.Construction /// /// The type of instructions that the control flow graph will contain. public interface IFlowGraphBuilder + where TInstruction : notnull { /// /// Gets the architecture of the instructions to graph. @@ -49,6 +49,7 @@ public static class FlowGraphBuilderExtensions public static ControlFlowGraph ConstructFlowGraph( this IFlowGraphBuilder self, long entrypoint) + where TInstruction : notnull { return self.ConstructFlowGraph(entrypoint, Array.Empty()); } @@ -67,6 +68,7 @@ public static ControlFlowGraph ConstructFlowGraph( this IFlowGraphBuilder self, long entrypoint, IEnumerable exceptionHandlers) + where TInstruction : notnull { var knownBlockHeaders = new HashSet(); foreach (var range in exceptionHandlers) diff --git a/src/Core/Echo.ControlFlow/Construction/IInstructionTraversalResult.cs b/src/Core/Echo.ControlFlow/Construction/IInstructionTraversalResult.cs index e6a7d365..8a174552 100644 --- a/src/Core/Echo.ControlFlow/Construction/IInstructionTraversalResult.cs +++ b/src/Core/Echo.ControlFlow/Construction/IInstructionTraversalResult.cs @@ -9,6 +9,7 @@ namespace Echo.ControlFlow.Construction /// /// The type of instructions that were traversed. public interface IInstructionTraversalResult : IStaticInstructionProvider + where TInstruction : notnull { /// /// Determines whether an offset was marked as a block header during the traversal. diff --git a/src/Core/Echo.ControlFlow/Construction/InstructionTraversalResult.cs b/src/Core/Echo.ControlFlow/Construction/InstructionTraversalResult.cs index 753e3ecb..7ceeb78c 100644 --- a/src/Core/Echo.ControlFlow/Construction/InstructionTraversalResult.cs +++ b/src/Core/Echo.ControlFlow/Construction/InstructionTraversalResult.cs @@ -11,6 +11,7 @@ namespace Echo.ControlFlow.Construction /// /// The type of instructions that were traversed. public class InstructionTraversalResult : IInstructionTraversalResult + where TInstruction : notnull { private readonly Dictionary _instructions = new(); private readonly HashSet _fallThroughOffsets = new(); diff --git a/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs b/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs index 955fff04..832c6506 100644 --- a/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs +++ b/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs @@ -38,6 +38,7 @@ namespace Echo.ControlFlow.Construction.Static /// /// public interface IStaticSuccessorResolver + where TInstruction : notnull { /// /// Gets the number of successors of the provided instruction. diff --git a/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs index fb6cebb6..01765514 100644 --- a/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs +++ b/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs @@ -1,7 +1,6 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.Linq; using Echo.Code; namespace Echo.ControlFlow.Construction.Static @@ -18,6 +17,7 @@ namespace Echo.ControlFlow.Construction.Static /// this graph builder, but jmp eax is not. /// public class StaticFlowGraphBuilder : FlowGraphBuilderBase + where TInstruction : notnull { /// /// Creates a new static graph builder using the provided instruction successor resolver. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs index b4b5bc53..4646a7a4 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs @@ -15,6 +15,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// public interface IStateTransitioner + where TInstruction : notnull { /// /// Gets the initial state of the program at a provided entry point address. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs index 99e1d67d..45e715ce 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs @@ -8,6 +8,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// The type of instructions that this collection provides. public interface ISymbolicInstructionProvider + where TInstruction : notnull { /// /// Gets the architecture describing the instructions exposed by this instruction provider. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs index 8e7595e0..ce476b97 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs @@ -1,5 +1,4 @@ -using System; -using Echo.DataFlow.Emulation; +using Echo.DataFlow.Emulation; namespace Echo.ControlFlow.Construction.Symbolic { @@ -8,6 +7,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// The type of instruction that was executed. public readonly struct StateTransition + where TInstruction : notnull { /// /// Creates a new program state transition. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs index 7230f231..7a13446e 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs @@ -12,6 +12,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// The type of instructions to evaluate. public abstract class StateTransitionerBase : IStateTransitioner + where TInstruction : notnull { private IVariable[] _variablesBuffer = new IVariable[1]; diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs index 45a4d374..23ae2032 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs @@ -12,6 +12,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// The type of instructions that this collection provides. public class StaticToSymbolicAdapter : ISymbolicInstructionProvider + where TInstruction : notnull { /// /// Creates a new instance of the adapter. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs index 972db1af..f3104757 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs +++ b/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs @@ -13,6 +13,7 @@ namespace Echo.ControlFlow.Construction.Symbolic /// /// The type of instructions to store in the control flow graph. public class SymbolicFlowGraphBuilder : FlowGraphBuilderBase + where TInstruction : notnull { /// /// Creates a new symbolic control flow graph builder using the provided program state transition resolver. @@ -281,11 +282,8 @@ internal StateTransition[] GetTransitionsBuffer(int minimalSize) public void Dispose() { - if (_transitionsBuffer is null) - return; - _transitionsBufferPool.Return(_transitionsBuffer); - _transitionsBuffer = null; + _transitionsBuffer = null!; } } } diff --git a/src/Core/Echo.ControlFlow/ControlFlowEdge.cs b/src/Core/Echo.ControlFlow/ControlFlowEdge.cs index a954284b..ce37d3cf 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowEdge.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowEdge.cs @@ -1,4 +1,4 @@ -using Echo.Code; +using System; using Echo.Graphing; namespace Echo.ControlFlow @@ -11,15 +11,16 @@ namespace Echo.ControlFlow /// If an edge is in between two nodes, it means that control might be transferred from the one node to the other /// during the execution of the program that is encoded by the control flow graph. /// - /// The type of contents that the connected nodes store. - public class ControlFlowEdge : IEdge + /// The type of instructions that the connected nodes store. + public class ControlFlowEdge : IEdge + where TInstruction : notnull { /// /// Creates a new fallthrough edge between two nodes. /// /// The node to start the edge at. /// The node to use as destination for the edge. - public ControlFlowEdge(ControlFlowNode origin, ControlFlowNode target) + public ControlFlowEdge(ControlFlowNode origin, ControlFlowNode target) : this(origin, target, ControlFlowEdgeType.FallThrough) { } @@ -30,7 +31,7 @@ public ControlFlowEdge(ControlFlowNode origin, ControlFlowNodeThe node to start the edge at. /// The node to use as destination for the edge. /// The type of the edge to create. - public ControlFlowEdge(ControlFlowNode origin, ControlFlowNode target, ControlFlowEdgeType edgeType) + public ControlFlowEdge(ControlFlowNode origin, ControlFlowNode target, ControlFlowEdgeType edgeType) { Origin = origin; Target = target; @@ -40,12 +41,12 @@ public ControlFlowEdge(ControlFlowNode origin, ControlFlowNode /// Gets the graph that contains this edge. /// - public ControlFlowGraph ParentGraph => Origin?.ParentGraph ?? Target?.ParentGraph; + public ControlFlowGraph? ParentGraph => Origin.ParentGraph ?? Target.ParentGraph; /// /// Gets the node that this edge originates from. /// - public ControlFlowNode Origin + public ControlFlowNode Origin { get; } @@ -53,7 +54,7 @@ public ControlFlowNode Origin /// /// Gets the node that this edge targets. /// - public ControlFlowNode Target + public ControlFlowNode Target { get; } @@ -71,6 +72,6 @@ public ControlFlowEdgeType Type } /// - public override string ToString() => $"{Origin.Offset:X8} -> {Target.Offset:X8} ({Type})"; + public override string ToString() => $"{Origin} -> {Target} ({Type})"; } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs index 32bfd517..8199ec3b 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs @@ -15,10 +15,11 @@ namespace Echo.ControlFlow /// Provides a generic base implementation of a control flow graph that contains for each node a user predefined /// object in a type safe manner. /// - /// The type of data that each node in the graph stores. + /// The type of instructions that each node in the graph stores. public class ControlFlowGraph : IGraph, IScopeControlFlowRegion + where TInstruction : notnull { - private ControlFlowNode _entryPoint; + private ControlFlowNode? _entryPoint; /// /// Creates a new empty graph. @@ -34,14 +35,14 @@ public ControlFlowGraph(IArchitecture architecture) /// /// Gets or sets the node that is executed first in the control flow graph. /// - public ControlFlowNode EntryPoint + public ControlFlowNode? EntryPoint { get => _entryPoint; set { if (_entryPoint != value) { - if (!Nodes.Contains(value)) + if (value is not null && !Nodes.Contains(value)) throw new ArgumentException("Node is not present in the graph.", nameof(value)); _entryPoint = value; } @@ -73,10 +74,10 @@ public RegionCollection> Regions } /// - ControlFlowGraph IControlFlowRegion.ParentGraph => null; + ControlFlowGraph? IControlFlowRegion.ParentGraph => null; /// - IControlFlowRegion IControlFlowRegion.ParentRegion => null; + IControlFlowRegion? IControlFlowRegion.ParentRegion => null; /// /// Gets a collection of all edges that transfer control from one block to the other in the graph. @@ -88,10 +89,13 @@ public IEnumerable> GetEdges() => IEnumerable IGraph.GetEdges() => GetEdges(); /// - public ControlFlowNode GetNodeByOffset(long offset) => Nodes[offset]; + IEnumerable> IControlFlowRegion.GetNodes() => Nodes; /// - IEnumerable> IControlFlowRegion.GetNodes() => Nodes; + ControlFlowNode? IControlFlowRegion.GetNodeByOffset(long offset) + { + return Nodes.GetByOffset(offset); + } /// IEnumerable ISubGraph.GetNodes() => Nodes; @@ -99,18 +103,16 @@ public IEnumerable> GetEdges() => /// IEnumerable ISubGraph.GetSubGraphs() => Regions; - ControlFlowNode IControlFlowRegion.GetEntryPoint() => EntryPoint; + ControlFlowNode? IControlFlowRegion.GetEntryPoint() => EntryPoint; /// IEnumerable> IControlFlowRegion.GetSubRegions() => Regions; /// - bool IControlFlowRegion.RemoveNode(ControlFlowNode node) => - Nodes.Remove(node); + bool IControlFlowRegion.RemoveNode(ControlFlowNode node) => Nodes.Remove(node); /// - IEnumerable> IControlFlowRegion.GetSuccessors() => - Enumerable.Empty>(); + IEnumerable> IControlFlowRegion.GetSuccessors() => []; /// /// Serializes the control flow graph to the provided output stream, in graphviz dot format. diff --git a/src/Core/Echo.ControlFlow/ControlFlowNode.cs b/src/Core/Echo.ControlFlow/ControlFlowNode.cs index cc68885e..6c441b0c 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowNode.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowNode.cs @@ -4,7 +4,6 @@ using Echo.ControlFlow.Blocks; using Echo.ControlFlow.Collections; using Echo.ControlFlow.Regions; -using Echo.Code; using Echo.Graphing; namespace Echo.ControlFlow @@ -13,48 +12,53 @@ namespace Echo.ControlFlow /// Represents a node in a control flow graph, containing a basic block of instructions that are to be executed /// in a sequence. /// - /// The type of data to store in the node. - public class ControlFlowNode : IIdentifiedNode + /// The type of instructions to store in the node. + public class ControlFlowNode : INode + where TInstruction : notnull { - private ControlFlowEdge _unconditionalEdge; + private ControlFlowEdge? _unconditionalEdge; /// - /// Creates a new control flow graph node with an empty basic block, to be added to the graph. + /// Creates a new empty control flow node. /// - /// The offset of the node. - public ControlFlowNode(long offset) - : this(offset, new BasicBlock(offset)) + public ControlFlowNode() + : this(new BasicBlock()) { } + /// + /// Creates a new empty control flow node for the provided offset. + /// + /// The offset. + public ControlFlowNode(long offset) + : this(new BasicBlock(offset)) + { + } + /// /// Creates a new control flow node containing the provided basic block of instructions, to be added to the graph. /// - /// The offset of the node. /// The basic block to store in the node. - public ControlFlowNode(long offset, params TInstruction[] instructions) - : this(offset, instructions.AsEnumerable()) + public ControlFlowNode(params TInstruction[] instructions) + : this(new BasicBlock(instructions)) { } /// /// Creates a new control flow node containing the provided basic block of instructions, to be added to the graph. /// - /// The offset of the node. /// The basic block to store in the node. - public ControlFlowNode(long offset, IEnumerable instructions) - : this(offset, new BasicBlock(offset, instructions)) + public ControlFlowNode(IEnumerable instructions) + : this(new BasicBlock(instructions)) { } /// /// Creates a new control flow node containing the provided basic block of instructions, to be added to the graph. /// - /// The offset of the node. /// The basic block to store in the node. - public ControlFlowNode(long offset, BasicBlock basicBlock) + public ControlFlowNode(BasicBlock basicBlock) { - Offset = offset; Contents = basicBlock ?? throw new ArgumentNullException(nameof(basicBlock)); ConditionalEdges = new AdjacencyCollection(this, ControlFlowEdgeType.Conditional); AbnormalEdges = new AdjacencyCollection(this, ControlFlowEdgeType.Abnormal); @@ -63,7 +67,7 @@ public ControlFlowNode(long offset, BasicBlock basicBlock) /// /// Gets the graph that contains this node, or null if the node is not added to any graph yet. /// - public ControlFlowGraph ParentGraph + public ControlFlowGraph? ParentGraph { get { @@ -87,23 +91,13 @@ public ControlFlowGraph ParentGraph /// /// Gets the graph region that contains this node, or null if the node is not added to any graph yet. /// - public IControlFlowRegion ParentRegion + public IControlFlowRegion? ParentRegion { get; internal set; } - /// - /// Gets the offset of the node. - /// - public long Offset - { - get; - internal set; - } - - /// - public long Id => Offset; + public long Offset => Contents.Offset; /// public int InDegree => IncomingEdges.Count; @@ -132,22 +126,22 @@ public BasicBlock Contents /// Gets or sets the neighbour to which the control is transferred to after execution of this block and no /// other condition is met. /// - public ControlFlowNode UnconditionalNeighbour + public ControlFlowNode? UnconditionalNeighbour { get => UnconditionalEdge?.Target; - set => UnconditionalEdge = value == null ? null : new ControlFlowEdge(this, value); + set => UnconditionalEdge = value is null ? null : new ControlFlowEdge(this, value); } /// /// Gets or sets the edge to the neighbour to which the control is transferred to after execution of this block /// and no other condition is met. /// - public ControlFlowEdge UnconditionalEdge + public ControlFlowEdge? UnconditionalEdge { get => _unconditionalEdge; set { - if (value is {}) + if (value is not null) { if (value.Type != ControlFlowEdgeType.FallThrough && value.Type != ControlFlowEdgeType.Unconditional) throw new ArgumentException("New edge must be either a fallthrough edge or an unconditional edge."); @@ -156,8 +150,7 @@ public ControlFlowEdge UnconditionalEdge _unconditionalEdge?.Target.IncomingEdges.Remove(_unconditionalEdge); _unconditionalEdge = value; - _unconditionalEdge?.Target.IncomingEdges.Add(value); - + _unconditionalEdge?.Target.IncomingEdges.Add(_unconditionalEdge); } } @@ -205,7 +198,7 @@ internal IList> IncomingEdges /// Occurs when the node already contains a fallthrough edge to another node. public ControlFlowEdge ConnectWith(ControlFlowNode neighbour) { - if (neighbour == null) + if (neighbour is null) throw new ArgumentNullException(nameof(neighbour)); return ConnectWith(neighbour, ControlFlowEdgeType.FallThrough); } @@ -251,7 +244,15 @@ public ControlFlowEdge ConnectWith(ControlFlowNode n return edge; } - + + public void UpdateOffset() + { + if (ParentGraph is not null) + Contents.UpdateOffset(ParentGraph.Architecture); + else + Contents.Offset = 0; + } + /// /// Splits the node and its embedded basic block in two nodes at the provided index, and connects the two /// resulting nodes with a fallthrough edge. @@ -264,6 +265,8 @@ public ControlFlowEdge ConnectWith(ControlFlowNode n /// public (ControlFlowNode First, ControlFlowNode Second) SplitAtIndex(int index) { + if (ParentGraph is null) + throw new InvalidOperationException("Cannot split a node that is not added to a graph yet."); if (Contents.Instructions.Count < 2) throw new InvalidOperationException("Cannot split up a node with less than two instructions."); if (index <= 0 || index >= Contents.Instructions.Count) @@ -279,7 +282,7 @@ public ControlFlowEdge ConnectWith(ControlFlowNode n // Create and add new node. var newBlock = new BasicBlock(ParentGraph.Architecture.GetOffset(instructions[0]), instructions); - var newNode = new ControlFlowNode(newBlock.Offset, newBlock); + var newNode = new ControlFlowNode(newBlock); ParentGraph.Nodes.Add(newNode); if (ParentRegion is ScopeRegion scope) newNode.MoveToRegion(scope); @@ -331,6 +334,9 @@ public void MergeWithPredecessor() /// public void MergeWithSuccessor() { + if (ParentGraph is null) + throw new InvalidOperationException("Node is not added to a graph."); + var successor = UnconditionalNeighbour; if (successor is null) throw new InvalidOperationException("Node has no fallthrough neighbour to merge into."); @@ -353,11 +359,8 @@ public void MergeWithSuccessor() /// Gets a collection of all edges that target this node. /// /// The incoming edges. - public IEnumerable> GetIncomingEdges() - { - return IncomingEdges; - } - + public IEnumerable> GetIncomingEdges() => IncomingEdges; + /// /// Gets a collection of all outgoing edges originating from this node. /// @@ -461,7 +464,7 @@ public void Disconnect() /// public void RemoveFromAnyRegion() { - if (ParentRegion != ParentGraph) + if (ParentRegion is not null && ParentRegion != ParentGraph) ParentRegion.RemoveNode(this); } @@ -481,8 +484,7 @@ public void MoveToRegion(ScopeRegion region) /// /// The parent exception handler region, or null if the node is not part of any exception handler. /// - public ExceptionHandlerRegion GetParentExceptionHandler() => - ParentRegion?.GetParentExceptionHandler(); + public ExceptionHandlerRegion? GetParentExceptionHandler() => ParentRegion?.GetParentExceptionHandler(); /// /// Obtains the parent handler region that this node resides in (if any). @@ -490,8 +492,7 @@ public ExceptionHandlerRegion GetParentExceptionHandler() => /// /// The parent handler region, or null if the node is not part of any handler. /// - public HandlerRegion GetParentHandler() => - ParentRegion?.GetParentHandler(); + public HandlerRegion? GetParentHandler() => ParentRegion?.GetParentHandler(); /// /// Traverses the region tree upwards and collects all regions this node is situated in. @@ -515,7 +516,7 @@ public IEnumerable> GetSituatedRegions() public bool IsInRegion(IControlFlowRegion region) => GetSituatedRegions().Contains(region); /// - public override string ToString() => Offset.ToString("X8"); + public override string ToString() => Contents.Offset.ToString("X8"); IEnumerable INode.GetIncomingEdges() => GetIncomingEdges(); diff --git a/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj b/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj index 50d3a23c..f30f85bf 100644 --- a/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj +++ b/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj @@ -7,6 +7,8 @@ program code analysis control-flow-graph blocks serialization dominator-analysis true true + enable + 12 @@ -24,6 +26,7 @@ + diff --git a/src/Core/Echo.ControlFlow/Editing/AddEdgeAction.cs b/src/Core/Echo.ControlFlow/Editing/AddEdgeAction.cs deleted file mode 100644 index 68124d06..00000000 --- a/src/Core/Echo.ControlFlow/Editing/AddEdgeAction.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Linq; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents an action that edits a control flow graph by adding an edge from one node to another. - /// - /// The type of instructions stored in the control flow graph. - public class AddEdgeAction : UpdateAdjacencyAction - { - private bool _hasSplit; - - /// - /// Creates a new instance of the class. - /// - /// The offset to the branching instruction that is the origin of the edge. - /// The offset to the neighbour that the new edge targets. - /// The type of edge. - /// - /// Occurs when equals - /// - public AddEdgeAction(long originOffset, long targetOffset, ControlFlowEdgeType edgeType) - : base(originOffset, targetOffset, edgeType) - { - if (edgeType == ControlFlowEdgeType.FallThrough) - throw new NotSupportedException("Fall through edges are not supported."); - } - - /// - protected override void OnApply(ControlFlowGraphEditContext context) - { - var origin = context.FindNode(OriginOffset); - var target = context.FindNodeOrSplit(TargetOffset, out _hasSplit); - origin.ConnectWith(target, EdgeType); - } - - /// - protected override void OnRevert(ControlFlowGraphEditContext context) - { - var origin = context.FindNode(OriginOffset); - var target = context.Graph.Nodes[TargetOffset]; - - var collection = EdgeType switch - { - ControlFlowEdgeType.Conditional => origin.ConditionalEdges, - ControlFlowEdgeType.Abnormal => origin.AbnormalEdges, - _ => throw new ArgumentOutOfRangeException() - }; - - collection.Remove(collection.GetEdgesToNeighbour(target).First()); - - if (_hasSplit) - { - context.RemoveNodeFromIndex(target.Offset); - target.MergeWithPredecessor(); - } - } - - /// - public override string ToString() => - $"Add {EdgeType} edge from {OriginOffset:X8} to {TargetOffset:X8}."; - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditContext.cs b/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditContext.cs deleted file mode 100644 index 31517ba4..00000000 --- a/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditContext.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Provides a workspace for editing a control flow graph. - /// - /// The type of instructions stored in the control flow graph. - public class ControlFlowGraphEditContext - { - private readonly List _nodeOffsets = new List(); - - /// - /// Creates a new instance of the class. - /// - /// The graph to edit. - public ControlFlowGraphEditContext(ControlFlowGraph graph) - { - Graph = graph ?? throw new ArgumentNullException(nameof(graph)); - FlushNodeOffsetIndex(); - } - - /// - /// Gets the graph to be edited. - /// - public ControlFlowGraph Graph - { - get; - } - - /// - /// Rebuilds the index of nodes and their offsets. - /// - /// - /// This method is supposed to be called every time a node is manually added or removed from the - /// control flow graph. - /// - public void FlushNodeOffsetIndex() - { - _nodeOffsets.Clear(); - _nodeOffsets.Capacity = Graph.Nodes.Count; - foreach (var node in Graph.Nodes) - _nodeOffsets.Add(node.Offset); - _nodeOffsets.Sort(); - } - - /// - /// Removes a node from the index. - /// - /// The node offset. - /// - /// Occurs when the provided offset does not exist in the current node index. - /// - public void RemoveNodeFromIndex(long offset) - { - int nodeIndex = FindClosestNodeIndex(offset); - if (nodeIndex == -1 || _nodeOffsets[nodeIndex] != offset) - throw new ArgumentException($"Node {offset:X8} was not indexed."); - _nodeOffsets.RemoveAt(nodeIndex); - } - - /// - /// Finds the node that contains the provided instruction offset. - /// - /// The offset of the instruction. - /// The node. - /// - /// Occurs when there is no node in the graph containing an instruction with the provided offset. - /// - /// - /// - /// This method can only work properly if the node index is up-to-date. Consider calling - /// before using this method. - /// - /// - public ControlFlowNode FindNode(long offset) - { - // Shortcut: check if a node with the provided offset exists in the graph first. - if (Graph.Nodes.Contains(offset)) - return Graph.Nodes[offset]; - - // Find the node that contains the offset. - return FindNodeSlow(offset, false, out _); - } - - /// - /// Finds the node that contains the provided instruction offset, and splits the node into two halves if the - /// instruction is not a header of the found node. - /// - /// The offset of the instruction. - /// Indicates whether the node was split up or not. - /// The node. - /// - /// Occurs when there is no node in the graph containing an instruction with the provided offset. - /// - /// - /// - /// This method can only work properly if the node index is up-to-date. Make sure that - /// was called before using this method. - /// - /// - /// When this method splits a node, the node index is updated automatically, and it is not needed to call - /// again. - /// - /// - public ControlFlowNode FindNodeOrSplit(long offset, out bool hasSplit) - { - // Shortcut: check if a node with the provided offset exists in the graph first. - if (Graph.Nodes.Contains(offset)) - { - hasSplit = false; - return Graph.Nodes[offset]; - } - - // Find the node that contains the offset. - return FindNodeSlow(offset, true, out hasSplit); - } - - private ControlFlowNode FindNodeSlow(long offset, bool splitIfNotHeader, out bool hasSplit) - { - hasSplit = false; - - int nodeIndex = FindClosestNodeIndex(offset); - if (nodeIndex != -1) - { - var node = Graph.Nodes[_nodeOffsets[nodeIndex]]; - int instructionIndex = FindInstructionIndex(node.Contents.Instructions, offset); - if (instructionIndex != -1) - { - if (instructionIndex > 0 && splitIfNotHeader) - { - (_, node) = node.SplitAtIndex(instructionIndex); - _nodeOffsets.Insert(nodeIndex + 1, node.Offset); - hasSplit = true; - } - - return node; - } - } - - throw new ArgumentException($"Node containing offset {offset:X8} was not found."); - } - - private int FindClosestNodeIndex(long offset) - { - int min = 0; - int max = _nodeOffsets.Count - 1; - int mid = 0; - - while (min <= max) - { - mid = (min + max) / 2; - if (offset < _nodeOffsets[mid]) - max = mid - 1; - else if (offset > _nodeOffsets[mid]) - min = mid + 1; - else - break; - } - - if (min > max) - return max; - - while (mid < _nodeOffsets.Count - 1) - { - if (_nodeOffsets[mid + 1] > offset) - return mid; - mid++; - } - - return -1; - } - - private int FindInstructionIndex(IList instructions, long offset) - { - var architecture = Graph.Architecture; - - int min = 0; - int max = instructions.Count - 1; - - while (min <= max) - { - int mid = (min + max) / 2; - long currentOffset = architecture.GetOffset(instructions[mid]); - if (offset < currentOffset) - max = mid - 1; - else if (offset > currentOffset) - min = mid + 1; - else - return mid; - } - - return -1; - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditTransaction.cs b/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditTransaction.cs deleted file mode 100644 index f815bdfa..00000000 --- a/src/Core/Echo.ControlFlow/Editing/ControlFlowGraphEditTransaction.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents a sequence of edits to be applied to a control flow graph. - /// - /// The type of instructions stored in the control flow graph. - public class ControlFlowGraphEditTransaction : IEnumerable> - { - private readonly List> _actions = new List>(); - - /// - /// Gets the number of actions that will be performed. - /// - public int Count => _actions.Count; - - /// - /// Gets a value indicating whether all the edits were applied successfully. - /// - public bool IsCompleted - { - get; - private set; - } - - /// - /// Adds an edit action to the end of the sequence. - /// - /// The action to add. - public void EnqueueAction(IControlFlowGraphEditAction action) - { - if (action == null) - throw new ArgumentNullException(nameof(action)); - _actions.Add(action); - } - - /// - /// Applies all edits to the control flow graph. - /// - /// The graph to apply the transaction on. - /// Occurs when the transaction was already applied. - /// - /// When an exception occurs within one of the edits, all edits previously applied will be reverted. - /// - public void Apply(ControlFlowGraph graph) - { - if (IsCompleted) - throw new InvalidOperationException("The transaction is already applied."); - - var context = new ControlFlowGraphEditContext(graph); - int index = 0; - - try - { - for (; index < _actions.Count; index++) - { - var edit = _actions[index]; - edit.Apply(context); - } - - IsCompleted = true; - } - catch - { - index--; - for (; index >= 0; index--) - _actions[index].Revert(context); - throw; - } - } - - /// - public IEnumerator> GetEnumerator() => _actions.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/IControlFlowGraphEditAction.cs b/src/Core/Echo.ControlFlow/Editing/IControlFlowGraphEditAction.cs deleted file mode 100644 index db18a27e..00000000 --- a/src/Core/Echo.ControlFlow/Editing/IControlFlowGraphEditAction.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents a reversible action that modifies a control flow graph. - /// - /// The type of instructions stored in the control flow graph. - public interface IControlFlowGraphEditAction - { - /// - /// Applies the edit. - /// - /// The workspace, including the graph to edit, to use. - /// - /// Occurs when the edit was already applied. - /// - /// - /// This method should only be called once. Calling this method a second time should happen after a call to - /// was made. - /// - void Apply(ControlFlowGraphEditContext context); - - /// - /// Reverts the edit. - /// - /// The workspace, including the graph to edit, to use. - /// - /// Occurs when the edit was not applied yet. - /// - /// - /// This method should only be called after the method was called. - /// - void Revert(ControlFlowGraphEditContext context); - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/RemoveEdgeAction.cs b/src/Core/Echo.ControlFlow/Editing/RemoveEdgeAction.cs deleted file mode 100644 index 4811aff9..00000000 --- a/src/Core/Echo.ControlFlow/Editing/RemoveEdgeAction.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Linq; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents an action that edits a control flow graph by removing an edge from one node to another. - /// - /// The type of instructions stored in the control flow graph. - public class RemoveEdgeAction : UpdateAdjacencyAction - { - /// - /// Creates a new instance of the class. - /// - /// The offset to the branching instruction that is the origin of the edge to remove. - /// The offset to the neighbour that the edge to remove targets. - /// The type of edge. - /// - /// Occurs when equals - /// - public RemoveEdgeAction(long originOffset, long targetOffset, ControlFlowEdgeType edgeType) - : base(originOffset, targetOffset, edgeType) - { - if (edgeType == ControlFlowEdgeType.FallThrough) - throw new NotSupportedException("Fallthrough edges are not supported."); - } - - /// - protected override void OnApply(ControlFlowGraphEditContext context) - { - var origin = context.FindNode(OriginOffset); - var target = context.Graph.Nodes[TargetOffset]; - - var collection = EdgeType switch - { - ControlFlowEdgeType.Conditional => origin.ConditionalEdges, - ControlFlowEdgeType.Abnormal => origin.AbnormalEdges, - _ => throw new ArgumentOutOfRangeException() - }; - - collection.Remove(collection.GetEdgesToNeighbour(target).First()); - - var incomingEdges = target.GetIncomingEdges().ToArray(); - if (incomingEdges.Length == 1 && incomingEdges[0].Type == ControlFlowEdgeType.FallThrough) - { - context.RemoveNodeFromIndex(target.Offset); - target.MergeWithPredecessor(); - } - } - - /// - protected override void OnRevert(ControlFlowGraphEditContext context) - { - var origin = context.FindNode(OriginOffset); - var target = context.FindNodeOrSplit(TargetOffset, out _); - origin.ConnectWith(target, EdgeType); - } - - /// - public override string ToString() => - $"Remove {EdgeType} edge from {OriginOffset:X8} to {TargetOffset:X8}."; - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/SplitNodeAction.cs b/src/Core/Echo.ControlFlow/Editing/SplitNodeAction.cs deleted file mode 100644 index 6791645e..00000000 --- a/src/Core/Echo.ControlFlow/Editing/SplitNodeAction.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents an action that edits a control flow graph by splitting a node into two halves. - /// - /// The type of instructions stored in the control flow graph. - public class SplitNodeAction : IControlFlowGraphEditAction - { - private bool _isApplied; - private bool _hasSplit; - - /// - /// Creates a new instance of the class. - /// - /// The offset to split at. - public SplitNodeAction(long splitOffset) - { - SplitOffset = splitOffset; - } - - /// - /// Gets the offset to split the node at. - /// - public long SplitOffset - { - get; - } - - /// - public void Apply(ControlFlowGraphEditContext context) - { - if (_isApplied) - throw new InvalidOperationException("Operation is already applied."); - context.FindNodeOrSplit(SplitOffset, out _hasSplit); - _isApplied = true; - } - - /// - public void Revert(ControlFlowGraphEditContext context) - { - if (!_isApplied) - throw new InvalidOperationException("Operation is not applied yet."); - - if (_hasSplit) - { - var node = context.Graph.Nodes[SplitOffset]; - context.RemoveNodeFromIndex(SplitOffset); - node.MergeWithPredecessor(); - } - } - - /// - public override string ToString() => - $"Split node at {SplitOffset:X8}."; - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronization.cs b/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronization.cs deleted file mode 100644 index 8a93ca32..00000000 --- a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronization.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Echo.ControlFlow.Construction.Static; - -namespace Echo.ControlFlow.Editing.Synchronization -{ - /// - /// Provides extensions for pulling updates from basic blocks into a control flow graph. This includes splitting - /// and merging nodes where necessary, as well as adding or removing any edges. - /// - public static class FlowControlSynchronization - { - /// - /// Pulls any updates from the basic block embedded in the node, and updates the parent control flow graph - /// accordingly. - /// - /// The node to pull updates from. - /// The object to use for resolving successors of a single instruction. - /// The type of instructions stored in the control flow graph. - /// true if any changes were made, false otherwise. - public static bool UpdateFlowControl( - this ControlFlowNode node, - IStaticSuccessorResolver successorResolver) - { - return UpdateFlowControl(node, successorResolver, FlowControlSynchronizationFlags.TraverseEntireBasicBlock); - } - - /// - /// Pulls any updates from the basic block embedded in the node, and updates the parent control flow graph - /// accordingly. - /// - /// The node to pull updates from. - /// The object to use for resolving successors of a single instruction. - /// Flags indicating several options for updating the control flow graph. - /// The type of instructions stored in the control flow graph. - /// true if any changes were made, false otherwise. - public static bool UpdateFlowControl( - this ControlFlowNode node, - IStaticSuccessorResolver successorResolver, - FlowControlSynchronizationFlags flags) - { - var synchronizer = new FlowControlSynchronizer(node.ParentGraph, successorResolver, flags); - return synchronizer.UpdateFlowControl(node); - } - - /// - /// Traverses all nodes in the control flow graph, and synchronizes the structure of the graph with the contents - /// of each basic block within the traversed nodes. - /// - /// The graph to synchronize. - /// The object to use for resolving successors of a single instruction. - /// The type of instructions stored in the control flow graph. - /// true if any changes were made, false otherwise. - public static bool UpdateFlowControl( - this ControlFlowGraph graph, - IStaticSuccessorResolver successorResolver) - { - return UpdateFlowControl(graph, successorResolver, FlowControlSynchronizationFlags.TraverseEntireBasicBlock); - } - - /// - /// Traverses all nodes in the control flow graph, and synchronizes the structure of the graph with the contents - /// of each basic block within the traversed nodes. - /// - /// The graph to synchronize. - /// The object to use for resolving successors of a single instruction. - /// Flags indicating several options for updating the control flow graph. - /// The type of instructions stored in the control flow graph. - /// true if any changes were made, false otherwise. - public static bool UpdateFlowControl( - this ControlFlowGraph graph, - IStaticSuccessorResolver successorResolver, - FlowControlSynchronizationFlags flags) - { - var synchronizer = new FlowControlSynchronizer(graph, successorResolver, flags); - return synchronizer.UpdateFlowControl(); - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizationFlags.cs b/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizationFlags.cs deleted file mode 100644 index 481d8c35..00000000 --- a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizationFlags.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Echo.ControlFlow.Editing.Synchronization -{ - /// - /// Provides flags that dictate the strategy used for pulling updates of a basic block into a control flow graph. - /// - [Flags] - public enum FlowControlSynchronizationFlags - { - /// - /// Indicates the synchronizer should only look at changes in the footer of a node in a control flow graph. - /// - TraverseFootersOnly = 0, - - /// - /// Indicates the synchronizer should traverse the entire basic block of a node in a control flow graph. - /// - TraverseEntireBasicBlock = 1 - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizer.cs b/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizer.cs deleted file mode 100644 index d4ec0ed3..00000000 --- a/src/Core/Echo.ControlFlow/Editing/Synchronization/FlowControlSynchronizer.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using Echo.ControlFlow.Collections; -using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; -using Echo.Code; - -namespace Echo.ControlFlow.Editing.Synchronization -{ - /// - /// Provides a mechanism for pulling updates from basic blocks into a control flow graph. This includes splitting - /// and merging nodes where necessary, as well as adding or removing any edges. - /// - /// The type of instructions the graph contains. - public class FlowControlSynchronizer - { - /// - /// Creates a new instance of the class. - /// - /// The control flow graph to update. - /// The object responsible for resolving successors of a single instruction. - /// The flags that dictate the strategy used for pulling basic block updates into a control flow graph. - public FlowControlSynchronizer( - ControlFlowGraph cfg, - IStaticSuccessorResolver successorResolver, - FlowControlSynchronizationFlags flags) - { - ControlFlowGraph = cfg ?? throw new ArgumentNullException(nameof(cfg)); - SuccessorResolver = successorResolver ?? throw new ArgumentNullException(nameof(successorResolver)); - Flags = flags; - } - - /// - /// Gets the control flow graph that needs to be updated. - /// - public ControlFlowGraph ControlFlowGraph - { - get; - } - - /// - /// Gets the object responsible for resolving successors of a single instruction. - /// - public IStaticSuccessorResolver SuccessorResolver - { - get; - } - - /// - /// Gets the flags that dictate the strategy used for pulling basic block updates into a control flow graph. - /// - public FlowControlSynchronizationFlags Flags - { - get; - } - - /// - /// Traverses all nodes in the control flow graph, and synchronizes the structure of the graph with the contents - /// of each basic block within the traversed nodes. - /// - public bool UpdateFlowControl() - { - var transaction = new ControlFlowGraphEditTransaction(); - foreach (var node in ControlFlowGraph.Nodes) - CheckForChangesInNode(transaction, node); - - if (transaction.Count > 0) - { - transaction.Apply(ControlFlowGraph); - return true; - } - - return false; - } - - /// - /// Pulls any updates from the provided node into the structure of the control flow graph. - /// - /// The node. - public bool UpdateFlowControl(ControlFlowNode node) - { - var transaction = new ControlFlowGraphEditTransaction(); - CheckForChangesInNode(transaction, node); - - if (transaction.Count > 0) - { - transaction.Apply(ControlFlowGraph); - return true; - } - - return false; - } - - private bool CheckForChangesInNode( - ControlFlowGraphEditTransaction transaction, - ControlFlowNode node) - { - bool hasChanges = false; - - if ((Flags & FlowControlSynchronizationFlags.TraverseEntireBasicBlock) != 0) - hasChanges |= CheckForChangesInBasicBlock(transaction, node); - - hasChanges |= CheckForChangesInFooter(transaction, node); - return hasChanges; - } - - private bool CheckForChangesInBasicBlock(ControlFlowGraphEditTransaction transaction, ControlFlowNode node) - { - bool hasChanges = false; - - // Most instructions will have <= 2 successors. - // - Immediate fallthrough successor or unconditional branch target. - // - A single conditional branch target. - - // The only exception will be switch-like instructions. - // Therefore we start off by renting a buffer of at least two elements. - var arrayPool = ArrayPool.Shared; - var successorsBuffer = arrayPool.Rent(2); - - try - { - var architecture = ControlFlowGraph.Architecture; - - // Skip last instruction (footer), since it is processed somewhere else. - for (int i = 0; i < node.Contents.Instructions.Count - 1; i++) - { - // Optimization: we assume the architecture has a faster implementation for categorizing instructions - // as instructions that influence the basic block (i.e. branches) than the successor resolver itself. - var instruction = node.Contents.Instructions[i]; - var flowControl = architecture.GetFlowControl(instruction); - if (SplitsBasicBlock(flowControl)) - { - long offset = architecture.GetOffset(instruction); - - // Split the node after the instruction. - var splitAction = new SplitNodeAction(offset + architecture.GetSize(instruction)); - transaction.EnqueueAction(splitAction); - - // Remove the fallthrough edge generated by ControlFlowNode.SplitAtIndex. - transaction.EnqueueAction(new UpdateFallThroughAction(offset, null)); - - // Verify that our buffer has enough elements. - int successorCount = SuccessorResolver.GetSuccessorsCount(instruction); - if (successorsBuffer.Length < successorCount) - { - arrayPool.Return(successorsBuffer); - successorsBuffer = arrayPool.Rent(successorCount); - } - - // Get new successors. - var successorBufferSlice = new Span(successorsBuffer, 0, successorCount); - int actualSuccessorCount = SuccessorResolver.GetSuccessors(instruction, successorBufferSlice); - if (actualSuccessorCount > successorCount) - throw new InvalidOperationException(); - - for (int j = 0; j < actualSuccessorCount; j++) - { - var successor = successorsBuffer[j]; - - IControlFlowGraphEditAction action; - if (successor.EdgeType == ControlFlowEdgeType.FallThrough) - { - action = new UpdateFallThroughAction( - offset, - successor.DestinationAddress); - } - else - { - action = new AddEdgeAction( - offset, - successor.DestinationAddress, - successor.EdgeType); - } - - transaction.EnqueueAction(action); - } - - hasChanges = true; - } - } - } - finally - { - arrayPool.Return(successorsBuffer); - } - - return hasChanges; - } - - private bool CheckForChangesInFooter( - ControlFlowGraphEditTransaction transaction, - ControlFlowNode node) - { - bool hasChanges = false; - - // Most instructions will have <= 2 successors. - // - Immediate fallthrough successor or unconditional branch target. - // - A single conditional branch target. - - // The only exception will be switch-like instructions. - // Therefore we start off by renting a buffer of at least two elements. - var arrayPool = ArrayPool.Shared; - - // Verify that our buffer has enough elements. - int successorCount = SuccessorResolver.GetSuccessorsCount(node.Contents.Footer); - var successorsBuffer = arrayPool.Rent(successorCount); - - try - { - // Get new successors. - var successorBufferSlice = new Span(successorsBuffer, 0, successorCount); - int actualSuccessorCount = SuccessorResolver.GetSuccessors(node.Contents.Footer, successorBufferSlice); - if (actualSuccessorCount > successorCount) - throw new InvalidOperationException(); - - // Group successors by type: - long? unconditionalSuccessor = null; - var conditionalSuccessors = new List(); - var abnormalSuccessors = new List(); - - for (int i = 0; i < actualSuccessorCount; i++) - { - var successor = successorsBuffer[i]; - if (!successor.IsRealEdge) - continue; - - switch (successor.EdgeType) - { - case ControlFlowEdgeType.FallThrough: - case ControlFlowEdgeType.Unconditional: - if (unconditionalSuccessor.HasValue) - throw new ArgumentException("Instruction has multiple fallthrough successors."); - else - unconditionalSuccessor = successor.DestinationAddress; - break; - - case ControlFlowEdgeType.Conditional: - conditionalSuccessors.Add(successor.DestinationAddress); - break; - - case ControlFlowEdgeType.Abnormal: - abnormalSuccessors.Add(successor.DestinationAddress); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - - // Check if there are any changes to the outgoing edges. - hasChanges |= CheckIfFallThroughChanged(transaction, node, unconditionalSuccessor); - hasChanges |= CheckIfAdjacencyListChanged(transaction, node.ConditionalEdges, conditionalSuccessors); - hasChanges |= CheckIfAdjacencyListChanged(transaction, node.AbnormalEdges, abnormalSuccessors); - } - finally - { - arrayPool.Return(successorsBuffer); - } - - return hasChanges; - } - - private bool CheckIfFallThroughChanged( - ControlFlowGraphEditTransaction transaction, - ControlFlowNode node, - long? successorOffset) - { - bool fallThroughChanged = false; - - if (!successorOffset.HasValue) - { - if (node.UnconditionalNeighbour is {}) - fallThroughChanged = true; // Fallthrough was removed. - } - else if (node.UnconditionalNeighbour is null || node.UnconditionalNeighbour.Offset != successorOffset.Value) - { - // Fallthrough was added or changed. - fallThroughChanged = true; - } - - if (fallThroughChanged) - { - var architecture = ControlFlowGraph.Architecture; - - var update = new UpdateFallThroughAction( - architecture.GetOffset(node.Contents.Footer), - successorOffset); - - transaction.EnqueueAction(update); - } - - return fallThroughChanged; - } - - private bool CheckIfAdjacencyListChanged( - ControlFlowGraphEditTransaction transaction, - AdjacencyCollection edges, - IList successorOffsets) - { - var architecture = ControlFlowGraph.Architecture; - long branchOffset = architecture.GetOffset(edges.Owner.Contents.Footer); - - bool hasChanges = successorOffsets.Count != edges.Count; - - // Count the number of existing edges per neighbour. - var oldNeighbours = edges - .GroupBy(x => x.Target.Offset) - .ToDictionary(x => x.Key, x => x.Count()); - - // Count the number of new edges per neighbour. - var newNeighbours = successorOffsets - .GroupBy(x => x) - .ToDictionary(x => x.Key, x => x.Count()); - - // Check if any neighbours were completely removed. - foreach (var entry in oldNeighbours) - { - if (!newNeighbours.ContainsKey(entry.Key)) - { - int oldCount = entry.Value; - for (int i = 0; i < oldCount; i++) - { - transaction.EnqueueAction(new RemoveEdgeAction( - branchOffset, - entry.Key, - edges.EdgeType)); - } - - hasChanges = true; - break; - } - } - - // Check if there are any new neighbours or changes in the count of edges to existing neighbours. - foreach (var entry in newNeighbours) - { - long successorOffset = entry.Key; - int newCount = entry.Value; - - // Get the original number of edges to the neighbour. - oldNeighbours.TryGetValue(successorOffset, out int oldCount); - - // Add new edges. - for (int i = oldCount; i < newCount; i++) - { - transaction.EnqueueAction(new AddEdgeAction( - branchOffset, - entry.Key, - edges.EdgeType)); - } - - // Remove deleted edges. - for (int i = newCount; i < oldCount; i++) - { - transaction.EnqueueAction(new RemoveEdgeAction( - branchOffset, - entry.Key, - edges.EdgeType)); - } - - } - - return hasChanges; - } - - private static bool SplitsBasicBlock(InstructionFlowControl flowControl) - { - return (flowControl & InstructionFlowControl.CanBranch) != 0 - || (flowControl & InstructionFlowControl.IsTerminator) != 0; - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/UpdateAdjacencyAction.cs b/src/Core/Echo.ControlFlow/Editing/UpdateAdjacencyAction.cs deleted file mode 100644 index a2abfdc5..00000000 --- a/src/Core/Echo.ControlFlow/Editing/UpdateAdjacencyAction.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents an action that edits a control flow graph by updating one of the adjacency collections of a single - /// node. - /// - /// The type of instructions stored in the control flow graph. - public abstract class UpdateAdjacencyAction : IControlFlowGraphEditAction - { - private bool _isApplied; - - /// - /// Initializes the base class. - /// - /// The offset of the branch instruction representing the origin of the edge. - /// The offset of the neighbour that the edge targets. - /// The type of edge. - protected UpdateAdjacencyAction(long originOffset, long targetOffset, ControlFlowEdgeType edgeType) - { - OriginOffset = originOffset; - TargetOffset = targetOffset; - EdgeType = edgeType; - } - - /// - /// Gets the offset of the branching instruction representing the origin of the edge. - /// - public long OriginOffset - { - get; - } - - /// - /// Gets the offset of the neighbour that the edge targets. - /// - public long TargetOffset - { - get; - } - - /// - /// Gets the type of edge. - /// - public ControlFlowEdgeType EdgeType - { - get; - } - - /// - public void Apply(ControlFlowGraphEditContext context) - { - if (_isApplied) - throw new InvalidOperationException("Operation is already applied."); - - OnApply(context); - - _isApplied = true; - } - - /// - /// Applies the update to the adjacency list of the node. - /// - /// The editing context. - /// - /// This method is guaranteed to be called before . - /// - protected abstract void OnApply(ControlFlowGraphEditContext context); - - /// - public void Revert(ControlFlowGraphEditContext context) - { - if (!_isApplied) - throw new InvalidOperationException("Operation is not applied yet."); - - OnRevert(context); - - _isApplied = false; - } - - /// - /// Reverts the update to the adjacency list of the node. - /// - /// The editing context. - /// - /// This method is guaranteed to be called after . - /// - protected abstract void OnRevert(ControlFlowGraphEditContext context); - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Editing/UpdateFallThroughAction.cs b/src/Core/Echo.ControlFlow/Editing/UpdateFallThroughAction.cs deleted file mode 100644 index 711ad2dd..00000000 --- a/src/Core/Echo.ControlFlow/Editing/UpdateFallThroughAction.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; - -namespace Echo.ControlFlow.Editing -{ - /// - /// Represents an action that edits a control flow graph by updating the fallthrough edge of a single node. - /// - /// The type of instructions stored in the control flow graph. - public class UpdateFallThroughAction : IControlFlowGraphEditAction - { - private bool _isApplied; - private bool _hasSplit; - private long? _oldFallThroughOffset; - - /// - /// Creates a new instance of the class. - /// - /// The offset of the branching instruction. - /// The offset to the new fallthrough neighbour, or null to remove the fallthrough edge. - public UpdateFallThroughAction(long branchOffset, long? newFallThroughOffset) - { - BranchOffset = branchOffset; - NewFallThroughOffset = newFallThroughOffset; - } - - /// - /// Gets the offset to the branching instruction responsible for the fallthrough edge. - /// - public long BranchOffset - { - get; - } - - /// - /// Gets the offset to the new fallthrough neighbour. When this value is null, the removal of the - /// fallthrough edge is indicated. - /// - public long? NewFallThroughOffset - { - get; - } - - /// - public void Apply(ControlFlowGraphEditContext context) - { - if (_isApplied) - throw new InvalidOperationException("Operation is already applied."); - - var node = context.FindNode(BranchOffset); - - // Save original fallthrough node offset. - _oldFallThroughOffset = node.UnconditionalNeighbour?.Offset; - - // Set new fallthrough neighbour. - node.UnconditionalNeighbour = NewFallThroughOffset.HasValue - ? context.FindNodeOrSplit(NewFallThroughOffset.Value, out _hasSplit) - : null; - - _isApplied = true; - } - - /// - public void Revert(ControlFlowGraphEditContext context) - { - if (!_isApplied) - throw new InvalidOperationException("Operation is not applied yet."); - - var node = context.FindNode(BranchOffset); - - // Re-merge node if it was split. - if (_hasSplit) - { - var newNeighbour = node.UnconditionalNeighbour; - node.UnconditionalNeighbour = null; - context.RemoveNodeFromIndex(newNeighbour.Offset); - newNeighbour.MergeWithPredecessor(); - } - - // Restore original fallthrough neighbour. - node.UnconditionalNeighbour = _oldFallThroughOffset.HasValue - ? context.FindNode(_oldFallThroughOffset.Value) - : null; - - _isApplied = false; - } - - /// - public override string ToString() => NewFallThroughOffset.HasValue - ? $"Set fallthrough neighbour of {BranchOffset:X8} to {NewFallThroughOffset:X8}." - : $"Remove fallthrough neighbour of {BranchOffset:X8}."; - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs b/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs index 37906d6e..629ab026 100644 --- a/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs @@ -8,9 +8,10 @@ namespace Echo.ControlFlow.Regions /// /// The type of data that each node in the graph stores. public abstract class ControlFlowRegion : IControlFlowRegion + where TInstruction : notnull { /// - public ControlFlowGraph ParentGraph + public ControlFlowGraph? ParentGraph { get { @@ -32,7 +33,7 @@ public ControlFlowGraph ParentGraph } /// - public IControlFlowRegion ParentRegion + public IControlFlowRegion? ParentRegion { get; internal set; @@ -41,22 +42,22 @@ public IControlFlowRegion ParentRegion /// /// Gets or sets a user-defined tag that is assigned to this region. /// - public object Tag + public object? Tag { get; set; } /// - public abstract ControlFlowNode GetEntryPoint(); + public abstract ControlFlowNode? GetEntryPoint(); /// - public virtual ControlFlowNode GetNodeByOffset(long offset) + public virtual ControlFlowNode? GetNodeByOffset(long offset) { foreach (var region in GetSubRegions()) { var node = region.GetNodeByOffset(offset); - if (node != null) + if (node is not null) return node; } diff --git a/src/Core/Echo.ControlFlow/Regions/Detection/ExceptionHandlerRange.cs b/src/Core/Echo.ControlFlow/Regions/Detection/ExceptionHandlerRange.cs index 56e3327e..129e125f 100644 --- a/src/Core/Echo.ControlFlow/Regions/Detection/ExceptionHandlerRange.cs +++ b/src/Core/Echo.ControlFlow/Regions/Detection/ExceptionHandlerRange.cs @@ -40,7 +40,7 @@ public ExceptionHandlerRange( AddressRange protectedRange, AddressRange prologueRange, AddressRange handlerRange, - object userData) + object? userData) : this(protectedRange, prologueRange, handlerRange, AddressRange.NilRange, userData) { } @@ -74,7 +74,7 @@ public ExceptionHandlerRange( AddressRange prologueRange, AddressRange handlerRange, AddressRange epilogueRange, - object userData) + object? userData) : this(protectedRange, handlerRange, userData) { PrologueRange = prologueRange; @@ -87,7 +87,7 @@ public ExceptionHandlerRange( /// The range indicating the code that is protected by the handler. /// The range indicating the handler code. /// A user defined tag that is added to the exception handler. - public ExceptionHandlerRange(AddressRange protectedRange, AddressRange handlerRange, object userData) + public ExceptionHandlerRange(AddressRange protectedRange, AddressRange handlerRange, object? userData) { ProtectedRange = protectedRange; PrologueRange = AddressRange.NilRange; @@ -134,7 +134,7 @@ public AddressRange EpilogueRange /// /// Gets a user defined tag that is added to the exception handler. /// - public object UserData + public object? UserData { get; } @@ -148,7 +148,7 @@ public bool Equals(in ExceptionHandlerRange other) => ProtectedRange.Equals(other.ProtectedRange) && HandlerRange.Equals(other.HandlerRange); /// - public override bool Equals(object obj) => + public override bool Equals(object? obj) => obj is ExceptionHandlerRange other && Equals(other); /// diff --git a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs index 7a51b8fb..3662a727 100644 --- a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs +++ b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Echo.Code; namespace Echo.ControlFlow.Regions.Detection { @@ -20,6 +19,7 @@ public static class RangedEHRegionDetector public static void DetectExceptionHandlerRegions( this ControlFlowGraph cfg, IEnumerable ranges) + where TInstruction : notnull { // Sort all ranges by their start and end offsets. var sortedRanges = ranges.ToList(); @@ -34,6 +34,7 @@ public static void DetectExceptionHandlerRegions( private static Dictionary> CreateEHRegions( ControlFlowGraph cfg, IReadOnlyList sortedRanges) + where TInstruction : notnull { var result = new Dictionary>(); @@ -88,10 +89,11 @@ private static Dictionary> CreateEHRegio return result; } - private static ScopeRegion FindParentRegion( + private static ScopeRegion? FindParentRegion( Dictionary> regions, IReadOnlyList sortedRanges, int currentRangeIndex) + where TInstruction : notnull { var ehRange = sortedRanges[currentRangeIndex]; @@ -118,6 +120,7 @@ private static void InsertNodesInEHRegions( ControlFlowGraph cfg, IReadOnlyList sortedRanges, Dictionary> rangeToRegionMapping) + where TInstruction : notnull { foreach (var node in cfg.Nodes) { @@ -130,6 +133,7 @@ private static void InsertNodesInEHRegions( private static AddressRange? GetParentRange( IReadOnlyList sortedRanges, ControlFlowNode node) + where TInstruction : notnull { // Since the ranges are sorted in such a way that outer ranges are coming first, we can simply reverse the // linear lookup to get the smallest EH region that this node contains. @@ -151,22 +155,25 @@ private static void InsertNodesInEHRegions( return null; } - private static void DetermineRegionEntrypoints(ControlFlowGraph cfg, List sortedRanges, + private static void DetermineRegionEntrypoints( + ControlFlowGraph cfg, + List sortedRanges, Dictionary> rangeToRegionMapping) + where TInstruction : notnull { foreach (var range in sortedRanges) { var protectedRegion = rangeToRegionMapping[range.ProtectedRange]; - protectedRegion.EntryPoint ??= cfg.GetNodeByOffset(range.ProtectedRange.Start); + protectedRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.ProtectedRange.Start); var handlerRegion = rangeToRegionMapping[range.HandlerRange]; - handlerRegion.EntryPoint ??= cfg.GetNodeByOffset(range.HandlerRange.Start); + handlerRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.HandlerRange.Start); if (rangeToRegionMapping.TryGetValue(range.PrologueRange, out var prologueRegion)) - prologueRegion.EntryPoint ??= cfg.GetNodeByOffset(range.PrologueRange.Start); + prologueRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.PrologueRange.Start); if (rangeToRegionMapping.TryGetValue(range.EpilogueRange, out var epilogueRegion)) - epilogueRegion.EntryPoint ??= cfg.GetNodeByOffset(range.EpilogueRange.Start); + epilogueRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.EpilogueRange.Start); } } diff --git a/src/Core/Echo.ControlFlow/Regions/ExceptionHandlerRegion.cs b/src/Core/Echo.ControlFlow/Regions/ExceptionHandlerRegion.cs index 7cd42e52..00ff5e7e 100644 --- a/src/Core/Echo.ControlFlow/Regions/ExceptionHandlerRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/ExceptionHandlerRegion.cs @@ -9,6 +9,7 @@ namespace Echo.ControlFlow.Regions /// /// The type of data that each node in the graph stores. public class ExceptionHandlerRegion : ControlFlowRegion + where TInstruction : notnull { /// /// Creates a new instance of the class. @@ -41,7 +42,7 @@ public RegionCollection> Handlers } /// - public override ControlFlowNode GetEntryPoint() => ProtectedRegion.EntryPoint; + public override ControlFlowNode? GetEntryPoint() => ProtectedRegion.EntryPoint; /// public override IEnumerable> GetNodes() => diff --git a/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs b/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs index 81707196..92a034b8 100644 --- a/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs @@ -9,9 +9,10 @@ namespace Echo.ControlFlow.Regions /// /// The type of data that each node in the graph stores. public class HandlerRegion : ControlFlowRegion + where TInstruction : notnull { - private ScopeRegion _prologue; - private ScopeRegion _epilogue; + private ScopeRegion? _prologue; + private ScopeRegion? _epilogue; /// /// Creates a new instance of the class without @@ -32,7 +33,7 @@ public HandlerRegion() /// /// This region is often used for filter clauses of the exception handler. /// - public ScopeRegion Prologue + public ScopeRegion? Prologue { get => _prologue; set => UpdateChildRegion(ref _prologue, value); @@ -49,26 +50,27 @@ public ScopeRegion Contents /// /// Gets the region of nodes that form the code that proceeds the handler. /// - public ScopeRegion Epilogue + public ScopeRegion? Epilogue { get => _epilogue; set => UpdateChildRegion(ref _epilogue, value); } - private void UpdateChildRegion(ref ScopeRegion field, ScopeRegion value) + private void UpdateChildRegion(ref ScopeRegion? field, ScopeRegion? value) { - if (value?.ParentRegion != null) + if (value?.ParentRegion is not null) throw new ArgumentException("Region is already added to another region."); if (field?.ParentRegion == this) field.ParentRegion = null; field = value; - if (value != null) + + if (field is not null) field.ParentRegion = this; } /// - public override ControlFlowNode GetEntryPoint() + public override ControlFlowNode? GetEntryPoint() { var entrypoint = _prologue?.GetEntryPoint(); entrypoint ??= Contents.GetEntryPoint(); diff --git a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs index 0ae0d33b..9fd659c2 100644 --- a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs @@ -9,11 +9,12 @@ namespace Echo.ControlFlow.Regions /// /// The type of data that each node in the graph stores. public interface IControlFlowRegion : ISubGraph + where TInstruction : notnull { /// /// Gets the parent graph this region is part of. /// - ControlFlowGraph ParentGraph + ControlFlowGraph? ParentGraph { get; } @@ -24,7 +25,7 @@ ControlFlowGraph ParentGraph /// /// When this property is set to null this region is the root. /// - IControlFlowRegion ParentRegion + IControlFlowRegion? ParentRegion { get; } @@ -32,8 +33,8 @@ IControlFlowRegion ParentRegion /// /// Obtains the first node that is executed in the region (if available). /// - /// The node, or null if no entrypoint was specified.. - ControlFlowNode GetEntryPoint(); + /// The node, or null if no entrypoint was specified. + ControlFlowNode? GetEntryPoint(); /// /// Gets a collection of all nested regions defined in this region. @@ -52,8 +53,8 @@ IControlFlowRegion ParentRegion /// Searches for a node in the control flow graph with the provided offset or identifier. /// /// The offset of the node to find. - /// The node. - ControlFlowNode GetNodeByOffset(long offset); + /// The node, or null if no node was found with the provided offset. + ControlFlowNode? GetNodeByOffset(long offset); /// /// Removes the node from the region. @@ -74,6 +75,7 @@ IControlFlowRegion ParentRegion /// /// The type of data that each node in the graph stores. public interface IScopeControlFlowRegion : IControlFlowRegion + where TInstruction : notnull { /// /// Gets a collection of nested sub regions that this region defines. @@ -95,17 +97,23 @@ public static class ControlFlowRegionExtensions /// /// The parent exception handler region, or null if the region is not part of any exception handler. /// - public static ExceptionHandlerRegion GetParentExceptionHandler(this IControlFlowRegion self)=> - GetParentRegion>(self); - + public static ExceptionHandlerRegion? GetParentExceptionHandler(this IControlFlowRegion self) + where TInstruction : notnull + { + return GetParentRegion>(self); + } + /// /// Obtains the parent handler region that this region resides in (if any). /// /// /// The parent exception handler region, or null if the region is not part of any exception handler. /// - public static HandlerRegion GetParentHandler(this IControlFlowRegion self) => - GetParentRegion>(self); + public static HandlerRegion? GetParentHandler(this IControlFlowRegion self) + where TInstruction : notnull + { + return GetParentRegion>(self); + } /// /// Obtains the parent region of a specific type that this region resides in (if any). @@ -113,14 +121,17 @@ public static HandlerRegion GetParentHandler(this IC /// /// The parent region, or null if the region is not part of any region of type . /// - private static TRegion GetParentRegion(IControlFlowRegion self) + private static TRegion? GetParentRegion(IControlFlowRegion self) + where TInstruction : notnull where TRegion : class, IControlFlowRegion { var region = self.ParentRegion; - while (region is {}) + + while (region is not null) { if (region is TRegion ehRegion) return ehRegion; + region = region.ParentRegion; } diff --git a/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs b/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs index f7d16a72..fd34e11e 100644 --- a/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Echo.ControlFlow.Collections; @@ -9,9 +8,8 @@ namespace Echo.ControlFlow.Regions /// /// The type of data that each node in the graph stores. public class ScopeRegion : ControlFlowRegion + where TInstruction : notnull { - private ControlFlowNode _entryPoint; - /// /// Creates a new instance of the class. /// @@ -24,10 +22,10 @@ public ScopeRegion() /// /// Gets or sets the first node that is executed in the region. /// - public ControlFlowNode EntryPoint + public ControlFlowNode? EntryPoint { - get => _entryPoint; - set => _entryPoint = value; + get; + set; } /// @@ -63,7 +61,7 @@ public override IEnumerable> GetNodes() } /// - public override ControlFlowNode GetEntryPoint() => EntryPoint; + public override ControlFlowNode? GetEntryPoint() => EntryPoint; /// public override IEnumerable> GetSubRegions() => Regions; diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs index ec99abdb..4995a4d2 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs @@ -18,6 +18,7 @@ public static class BlockBuilder /// The type of instructions stored in the graph. /// The root scope. public static ScopeBlock ConstructBlocks(this ControlFlowGraph cfg) + where TInstruction : notnull { return BuildBlocksFromSortedNodes(cfg, cfg.SortNodes()); } @@ -25,6 +26,7 @@ public static ScopeBlock ConstructBlocks(this Contro private static ScopeBlock BuildBlocksFromSortedNodes( ControlFlowGraph cfg, IEnumerable> sorting) + where TInstruction : notnull { // We maintain a stack of scope information. Every time we enter a new region, we enter a new scope, // and similarly, we leave a scope when we leave a region. @@ -51,6 +53,7 @@ private static ScopeBlock BuildBlocksFromSortedNodes private static void UpdateScopeStack( IndexableStack> scopeStack, ControlFlowNode node) + where TInstruction : notnull { // Figure out regions the node is in. var activeRegions = node.GetSituatedRegions() @@ -70,6 +73,7 @@ private static void UpdateScopeStack( private static int GetCommonRegionDepth( IndexableStack> scopeStack, IControlFlowRegion[] activeRegions) + where TInstruction : notnull { int largestPossibleCommonDepth = Math.Min(scopeStack.Count, activeRegions.Length); @@ -87,6 +91,7 @@ private static int GetCommonRegionDepth( private static void EnterNextRegion( IndexableStack> scopeStack, IControlFlowRegion[] activeRegions) + where TInstruction : notnull { var enteredRegion = activeRegions[scopeStack.Count]; @@ -117,6 +122,7 @@ private static void EnterNextRegion( private static void EnterExceptionHandlerRegion( IndexableStack> scopeStack, ExceptionHandlerRegion ehRegion) + where TInstruction : notnull { var ehBlock = new ExceptionHandlerBlock { @@ -131,6 +137,7 @@ private static void EnterExceptionHandlerSubRegion( IndexableStack> scopeStack, ExceptionHandlerRegion parentRegion, IControlFlowRegion enteredRegion) + where TInstruction : notnull { IBlock enteredBlock; IControlFlowRegion enteredSubRegion; @@ -176,6 +183,7 @@ private static void EnterHandlerSubRegion( IndexableStack> scopeStack, HandlerRegion parentRegion, IControlFlowRegion enteredRegion) + where TInstruction : notnull { IBlock enteredBlock; IControlFlowRegion enteredSubRegion; @@ -214,8 +222,10 @@ private static void EnterHandlerSubRegion( scopeStack.Push(new ScopeInfo(enteredSubRegion, enteredBlock)); } - private static void EnterGenericRegion(IndexableStack> scopeStack, + private static void EnterGenericRegion( + IndexableStack> scopeStack, IControlFlowRegion enteredRegion) + where TInstruction : notnull { var scopeBlock = new ScopeBlock(); scopeStack.Peek().AddBlock(scopeBlock); @@ -223,6 +233,7 @@ private static void EnterGenericRegion(IndexableStack + where TInstruction : notnull { public ScopeInfo(IControlFlowRegion region, IBlock block) { @@ -250,7 +261,7 @@ public void AddBlock(IBlock basicBlock) public override string ToString() { - return $"{Region.GetType().Name}, Offset: {Region.GetEntryPoint().Offset:X8}"; + return $"{Region.GetType().Name}, Offset: {Region.GetEntryPoint()?.Offset ?? 0:X8}"; } } diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs index 87328a83..4a937a15 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Echo.Graphing.Analysis.Sorting; @@ -45,18 +46,23 @@ public static class BlockSorter /// The ordering. public static IEnumerable> SortNodes( this ControlFlowGraph cfg) + where TInstruction : notnull { - var pathsView = DetermineUnbreakablePaths(cfg); - var sorter = new TopologicalSorter>(pathsView.GetImpliedNeighbours, true); + if (cfg.EntryPoint is null) + throw new ArgumentException("Control flow graph does not have an entry point."); + + var paths = GetUnbreakablePaths(cfg); + var sorter = new TopologicalSorter>(paths.GetImpliedNeighbours, true); return sorter .GetTopologicalSorting(cfg.EntryPoint) .Reverse() - .SelectMany(n => pathsView.GetUnbreakablePath(n)); + .SelectMany(n => paths.GetUnbreakablePath(n)); } - private static UnbreakablePathsView DetermineUnbreakablePaths( + private static UnbreakablePathsView GetUnbreakablePaths( ControlFlowGraph cfg) + where TInstruction : notnull { var visited = new HashSet>(); var result = new UnbreakablePathsView(); @@ -73,6 +79,7 @@ private static UnbreakablePathsView DetermineUnbreakablePaths> GetFallThroughPath( ControlFlowNode start, ISet> visited) + where TInstruction : notnull { // Navigate back to root of fallthrough path. var predecessor = start; @@ -95,11 +102,8 @@ private static List> GetFallThroughPath> GetFallThroughPath GetFallThroughPredecessor( + private static ControlFlowNode? GetFallThroughPredecessor( ControlFlowNode node) + where TInstruction : notnull { // There can only be one incoming fallthrough edge for every node. If more than one exists, // the input control flow graph is constructed incorrectly. @@ -120,13 +125,13 @@ private static ControlFlowNode GetFallThroughPredecessor predecessor = null; + ControlFlowNode? predecessor = null; foreach (var incomingEdge in node.GetIncomingEdges()) { if (incomingEdge.Type == ControlFlowEdgeType.FallThrough) { - if (predecessor != null) + if (predecessor is not null) { throw new BlockOrderingException( $"Node {node.Offset:X8} has multiple fallthrough predecessors."); diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs index 3f8e4be9..a3294e21 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs @@ -7,12 +7,10 @@ namespace Echo.ControlFlow.Serialization.Blocks { internal sealed class UnbreakablePathsView + where TInstruction : notnull { - private readonly Dictionary, IList>> _nodeToPath = - new Dictionary, IList>>(); - - private readonly Dictionary, IList>> _regionSuccessors = - new Dictionary, IList>>(); + private readonly Dictionary, IList>> _nodeToPath = new(); + private readonly Dictionary, IList>> _regionSuccessors = new(); public void AddUnbreakablePath(IList> path) { @@ -36,8 +34,8 @@ public IReadOnlyList> GetImpliedNeighbours(Control var n = path[i]; // Add unconditional edge. - if (n.UnconditionalEdge is {Type: ControlFlowEdgeType.Unconditional}) - AddSuccessorToResult(result, n.UnconditionalNeighbour); + if (n.UnconditionalEdge is {Type: ControlFlowEdgeType.Unconditional, Target: { } neighbour}) + AddSuccessorToResult(result, neighbour); // Add explicit conditional / abnormal edges. AddAdjacencyListToResult(result, n.ConditionalEdges); @@ -71,7 +69,7 @@ private void AddPotentialHandlerSuccessors( ICollection> result, ControlFlowNode node) { - // If the node is in an exception handler, here are a couple of "implicit" successors. + // If the node is in an exception handler, there are a couple "implicit" successors. // // - Any node in the protected region has an implicit successor to the start of every handler region. // @@ -88,13 +86,13 @@ private void AddPotentialHandlerSuccessors( var ehRegion = node.GetParentExceptionHandler(); - while (ehRegion is { }) + while (ehRegion is not null) { // If we entered this loop, it means the node is either in the protected region or a handler region // of an exception handler. if (node.IsInRegion(ehRegion.ProtectedRegion)) { - AddHandlerEntrypoints(result, ehRegion); + AddHandlerEntryPoints(result, ehRegion); } else { @@ -107,7 +105,7 @@ private void AddPotentialHandlerSuccessors( } } - private void AddHandlerEntrypoints( + private void AddHandlerEntryPoints( ICollection> result, ExceptionHandlerRegion ehRegion) { @@ -129,9 +127,9 @@ private void AddHandlerEntrypoints( private void AddNextHandlerRegion(ICollection> result, ControlFlowNode node) { - var handlerRegion = node.GetParentHandler(); + var handlerRegion = node.GetParentHandler()!; - ControlFlowNode nextEntry = null; + ControlFlowNode? nextEntry = null; if (node.ParentRegion == handlerRegion.Prologue) nextEntry = handlerRegion.Contents.EntryPoint; if (nextEntry is null && node.ParentRegion == handlerRegion.Contents) diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowEdgeAdorner.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowEdgeAdorner.cs index c58770d2..286d242c 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowEdgeAdorner.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowEdgeAdorner.cs @@ -10,6 +10,7 @@ namespace Echo.ControlFlow.Serialization.Dot /// /// The type of instructions the nodes contain. public class ControlFlowEdgeAdorner : IDotEdgeAdorner + where TInstruction : notnull { /// /// Gets or sets the edge style to use for normal fallthrough edges. @@ -57,7 +58,7 @@ public DotEntityStyle AbnormalStyle } = new DotEntityStyle("gray", "dashed"); /// - public IDictionary GetEdgeAttributes(IEdge edge, long sourceId, long targetId) + public IDictionary? GetEdgeAttributes(IEdge edge, long sourceId, long targetId) { if (edge is ControlFlowEdge cfgEdge) { @@ -75,9 +76,9 @@ public IDictionary GetEdgeAttributes(IEdge edge, long sourceId, }; if (!string.IsNullOrEmpty(style.Color)) - result["color"] = style.Color; + result["color"] = style.Color!; if (!string.IsNullOrEmpty(style.Style)) - result["style"] = style.Style; + result["style"] = style.Style!; return result; } diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowNodeAdorner.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowNodeAdorner.cs index 1594e5c7..c83af30f 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowNodeAdorner.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/ControlFlowNodeAdorner.cs @@ -10,6 +10,7 @@ namespace Echo.ControlFlow.Serialization.Dot /// /// The type of instructions the nodes contain. public class ControlFlowNodeAdorner : IDotNodeAdorner + where TInstruction : notnull { /// /// Creates a new with the default formatter. @@ -72,7 +73,7 @@ public IInstructionFormatter InstructionFormatter } /// - public IDictionary GetNodeAttributes(INode node, long id) + public IDictionary? GetNodeAttributes(INode node, long id) { if (node is ControlFlowNode cfgNode) { diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs index 6585f760..4c2a536c 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs @@ -5,11 +5,15 @@ /// /// The type of the instruction to create a formatter of. public sealed class DefaultInstructionFormatter : IInstructionFormatter + where TInstruction : notnull { /// /// Gets a singleton instance of the class. /// - public static DefaultInstructionFormatter Instance { get; } = new(); + public static DefaultInstructionFormatter Instance + { + get; + } = new(); /// public string Format(in TInstruction instruction) => instruction.ToString(); diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/ExceptionHandlerAdorner.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/ExceptionHandlerAdorner.cs index b8c80cb9..16cfd817 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/ExceptionHandlerAdorner.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/ExceptionHandlerAdorner.cs @@ -11,6 +11,7 @@ namespace Echo.ControlFlow.Serialization.Dot /// /// The type of instructions the nodes contain. public class ExceptionHandlerAdorner : IDotSubGraphAdorner + where TInstruction : notnull { /// /// Gets or sets the style of an enclosing exception handler region. @@ -133,7 +134,7 @@ public DotEntityStyle DefaultStyle public string GetSubGraphName(ISubGraph subGraph) { if (!(subGraph is IControlFlowRegion region)) - return null; + return string.Empty; string prefix = DetermineRegionPrefix(region); @@ -189,23 +190,25 @@ private static string GetScopeRegionPrefix(ScopeRegion basicRegion } } - return "cluster_block";; + return "cluster_block"; } /// - public IDictionary GetSubGraphAttributes(ISubGraph subGraph) + public IDictionary? GetSubGraphAttributes(ISubGraph subGraph) { if (!(subGraph is IControlFlowRegion region)) return null; - var (style, label) = GetSubGraphStyle(region); + (var style, string label) = GetSubGraphStyle(region); - return new Dictionary - { - ["color"] = style.Color, - ["style"] = style.Style, - ["label"] = label - }; + var result = new Dictionary(); + if (!string.IsNullOrEmpty(style.Color)) + result["color"] = style.Color!; + if (!string.IsNullOrEmpty(style.Style)) + result["style"] = style.Style!; + result["label"] = label; + + return result; } private (DotEntityStyle Style, string Label) GetSubGraphStyle(IControlFlowRegion region) diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/IInstructionFormatter.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/IInstructionFormatter.cs index 89e9ba7e..90afcffd 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/IInstructionFormatter.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/IInstructionFormatter.cs @@ -5,6 +5,7 @@ /// /// The type of instructions the nodes contain. public interface IInstructionFormatter + where TInstruction : notnull { /// /// Formats a given . diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/OffsetNodeIdentifier.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/OffsetNodeIdentifier.cs index e928614b..6f72272b 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/OffsetNodeIdentifier.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/OffsetNodeIdentifier.cs @@ -10,6 +10,7 @@ namespace Echo.ControlFlow.Serialization.Dot /// /// The type of instructions stored in the basic block. public class OffsetNodeIdentifier : INodeIdentifier + where TInstruction : notnull { /// /// Provides a default instance of the class. diff --git a/src/Core/Echo/Graphing/Analysis/Sorting/TopologicalSorter.cs b/src/Core/Echo/Graphing/Analysis/Sorting/TopologicalSorter.cs index 4697d62c..70d93224 100644 --- a/src/Core/Echo/Graphing/Analysis/Sorting/TopologicalSorter.cs +++ b/src/Core/Echo/Graphing/Analysis/Sorting/TopologicalSorter.cs @@ -83,15 +83,14 @@ public IEnumerable GetTopologicalSorting(TNode root) { if (permanent.Contains(current.Node)) continue; - if (temporary.Contains(current.Node)) + + if (!temporary.Add(current.Node)) { if (IgnoreCycles) continue; throw new CycleDetectedException(); } - - temporary.Add(current.Node); - + // Schedule remaining steps. We push this before pushing dependencies so it gets executed after // the dependencies are traversed. agenda.Push(new State(current.Node, true)); diff --git a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs index 55d8608f..eaf14858 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs @@ -232,7 +232,7 @@ protected virtual void WriteEdge(IEdge edge) Writer.WriteLine(); } - private void WriteEntityAttributes(IEnumerable> attributes) + private void WriteEntityAttributes(IEnumerable>? attributes) { var array = attributes as KeyValuePair[] ?? attributes.ToArray(); if (array.Length > 0) @@ -243,7 +243,7 @@ private void WriteEntityAttributes(IEnumerable> att } } - private void WriteAttributes(IEnumerable> attributes, string delimiter, bool newLines) + private void WriteAttributes(IEnumerable>? attributes, string delimiter, bool newLines) { var array = attributes as KeyValuePair[] ?? attributes.ToArray(); for (int i = 0; i < array.Length; i++) diff --git a/src/Core/Echo/Graphing/Serialization/Dot/HexLabelNodeAdorner.cs b/src/Core/Echo/Graphing/Serialization/Dot/HexLabelNodeAdorner.cs index 108da120..c467b924 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/HexLabelNodeAdorner.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/HexLabelNodeAdorner.cs @@ -36,7 +36,7 @@ public int PaddingZeroes } = 0; /// - public IDictionary GetNodeAttributes(INode node, long id) => new Dictionary + public IDictionary? GetNodeAttributes(INode node, long id) => new Dictionary { ["label"] = $"{Prefix}{id.ToString($"X{PaddingZeroes.ToString()}")}{Suffix}" }; diff --git a/src/Core/Echo/Graphing/Serialization/Dot/IDotEdgeAdorner.cs b/src/Core/Echo/Graphing/Serialization/Dot/IDotEdgeAdorner.cs index 3f34f50c..54abc3ac 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/IDotEdgeAdorner.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/IDotEdgeAdorner.cs @@ -14,6 +14,6 @@ public interface IDotEdgeAdorner /// The identifier assigned to the source node. /// The identifier assigned to the target node. /// The adornments. - IDictionary GetEdgeAttributes(IEdge edge, long sourceId, long targetId); + IDictionary? GetEdgeAttributes(IEdge edge, long sourceId, long targetId); } } \ No newline at end of file diff --git a/src/Core/Echo/Graphing/Serialization/Dot/IDotNodeAdorner.cs b/src/Core/Echo/Graphing/Serialization/Dot/IDotNodeAdorner.cs index d22299ad..10e2d626 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/IDotNodeAdorner.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/IDotNodeAdorner.cs @@ -13,6 +13,6 @@ public interface IDotNodeAdorner /// The node to adorn. /// The identifier assigned to the node. /// The adornments. - IDictionary GetNodeAttributes(INode node, long id); + IDictionary? GetNodeAttributes(INode node, long id); } } \ No newline at end of file diff --git a/src/Core/Echo/Graphing/Serialization/Dot/IDotSubGraphAdorner.cs b/src/Core/Echo/Graphing/Serialization/Dot/IDotSubGraphAdorner.cs index d885123d..0053420d 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/IDotSubGraphAdorner.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/IDotSubGraphAdorner.cs @@ -19,6 +19,6 @@ public interface IDotSubGraphAdorner /// /// The sub graph to adorn. /// The adornments. - IDictionary GetSubGraphAttributes(ISubGraph subGraph); + IDictionary? GetSubGraphAttributes(ISubGraph subGraph); } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs index 900fd91a..4b4fc847 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs @@ -16,7 +16,7 @@ public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction in { var calleeFrame = context.Thread.CallStack.Pop(); var callerFrame = context.CurrentFrame; - + var genericContext = GenericContext.FromMethod(calleeFrame.Method); if (calleeFrame.Method.Signature!.ReturnsValue) { @@ -25,31 +25,32 @@ public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction in var value = calleeFrame.EvaluationStack.Pop(returnType); callerFrame.EvaluationStack.Push(value, returnType, true); } - else if (callerFrame.Body is { } body) + else if (calleeFrame.Body is {Owner: {IsConstructor: true, IsStatic: false}} + && callerFrame.Body is { } body) { // The method may still be a constructor called via newobj. // In that case we need to push the created value, stored in the `this` pointer. - + int index = body.Instructions.GetIndexByOffset(callerFrame.ProgramCounter) - 1; if (index != -1 && body.Instructions[index].OpCode.Code == CilCode.Newobj) { var resultingType = calleeFrame.Method.DeclaringType! .ToTypeSignature() .InstantiateGenericTypes(genericContext); - + var slot = CreateResultingStackSlot( context, resultingType, calleeFrame.ReadArgument(0) ); - + callerFrame.EvaluationStack.Push(slot); } } - + return CilDispatchResult.Success(); } - + internal static StackSlot CreateResultingStackSlot( CilExecutionContext context, TypeSignature type, diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs index 548d96c3..ea9716ec 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs @@ -46,6 +46,7 @@ public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor me "AreSame" => InvokeAreSame(context, arguments), "Add" => InvokeAdd(context, method, arguments), "AddByteOffset" => InvokeAddByteOffset(context, arguments), + "ByteOffset" => InvokeByteOffset(context, arguments), _ => InvocationResult.Inconclusive() }; @@ -53,7 +54,7 @@ public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor me return InvocationResult.Inconclusive(); } } - + private static InvocationResult InvokeAsOrAsRef(CilExecutionContext context, IList arguments) { // We don't do any GC tracking, thus returning the same input reference suffices. @@ -73,6 +74,13 @@ private static InvocationResult InvokeAddByteOffset(CilExecutionContext context, result.AsSpan().IntegerAdd(arguments[1]); return InvocationResult.StepOver(result); } + + private static InvocationResult InvokeByteOffset(CilExecutionContext context, IList arguments) + { + var result = arguments[1].Clone(context.Machine.ValueFactory.BitVectorPool); + result.AsSpan().IntegerSubtract(arguments[0]); + return InvocationResult.StepOver(result); + } private static InvocationResult InvokeAdd(CilExecutionContext context, IMethodDescriptor method, IList arguments) { diff --git a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs index 3d3e9112..5e42fdf7 100644 --- a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs @@ -166,12 +166,13 @@ public void ExpressionWithMultipleReturnValues() // ret() DummyInstruction.Ret(3) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.True(StatementPattern .Assignment( new[] {Pattern.Any(), Pattern.Any()}, ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) - ).Match(cfg.Nodes[0].Contents.Instructions[0]).IsSuccess + ).Match(offsetMap[0].Contents.Instructions[0]).IsSuccess ); } @@ -336,9 +337,10 @@ public void TwoNodes() DummyInstruction.Op(10, 0,0), DummyInstruction.Ret(11) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.Equal(2, cfg.Nodes.Count); - var (n1, n2) = (cfg.Nodes[0], cfg.Nodes[10]); + var (n1, n2) = (offsetMap[0], offsetMap[10]); Assert.True(StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .Match(n1.Contents.Instructions[0]) @@ -369,9 +371,10 @@ public void TwoNodesWithStackDeltas() DummyInstruction.Pop(10, 1), DummyInstruction.Ret(11) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.Equal(2, cfg.Nodes.Count); - var (n1, n2) = (cfg.Nodes[0], cfg.Nodes[10]); + var (n1, n2) = (offsetMap[0], offsetMap[10]); var variable = new CaptureGroup("variable"); @@ -414,6 +417,7 @@ public void TwoNodesWithIndirectStackDeltaShouldInline() DummyInstruction.Pop(10, 1), DummyInstruction.Ret(11) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); var variable = new CaptureGroup(); @@ -424,7 +428,7 @@ public void TwoNodesWithIndirectStackDeltaShouldInline() ) .FollowedBy(ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)).ToStatement()) .FollowedBy(ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Jmp)).ToStatement()) - .Match(cfg.Nodes[0].Contents.Instructions); + .Match(offsetMap[0].Contents.Instructions); var match2 = StatementPattern .Expression(ExpressionPattern @@ -433,7 +437,7 @@ public void TwoNodesWithIndirectStackDeltaShouldInline() ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) ) ) - .Match(cfg.Nodes[10].Contents.Instructions[0]); + .Match(offsetMap[10].Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match2.IsSuccess); @@ -452,8 +456,9 @@ public void TwoNodesPushBeforeImpure() DummyInstruction.Pop(10, 1), DummyInstruction.Ret(11) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var block = cfg.Nodes[0].Contents; + var block = offsetMap[0].Contents; Assert.IsAssignableFrom>(block.Instructions[0]); Assert.IsAssignableFrom>(block.Instructions[1]); } @@ -472,6 +477,7 @@ public void TwoNodesWithNestedStackDelta() DummyInstruction.Pop(11, 1), DummyInstruction.Ret(12) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify var variable = new CaptureGroup("variable"); @@ -482,8 +488,8 @@ public void TwoNodesWithNestedStackDelta() ); // Ensure expressions are pushed as variables. - var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[0]); - var match2 = pattern.Match(cfg.Nodes[0].Contents.Instructions[1]); + var match1 = pattern.Match(offsetMap[0].Contents.Instructions[0]); + var match2 = pattern.Match(offsetMap[0].Contents.Instructions[1]); Assert.True(match1.IsSuccess); Assert.True(match2.IsSuccess); @@ -515,9 +521,10 @@ public void StackDeltaConvergingControlFlowPaths() DummyInstruction.Pop(11, 1), DummyInstruction.Ret(12) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify graph structure. - var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[2], cfg.Nodes[10], cfg.Nodes[11]); + var (n1, n2, n3, n4) = (offsetMap[0], offsetMap[2], offsetMap[10], offsetMap[11]); Assert.Same(n2, n1.UnconditionalNeighbour); Assert.Same(n3, Assert.Single(n1.ConditionalEdges).Target); Assert.Same(n4, n2.UnconditionalNeighbour); @@ -581,10 +588,11 @@ public void StackDeltaTwoLayers() DummyInstruction.Pop(21, 1), DummyInstruction.Ret(22) }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify graph structure. var (n1, n2, n3, n4, n5, n6) = - (cfg.Nodes[0], cfg.Nodes[2], cfg.Nodes[4], cfg.Nodes[10], cfg.Nodes[20], cfg.Nodes[21]); + (offsetMap[0], offsetMap[2], offsetMap[4], offsetMap[10], offsetMap[20], offsetMap[21]); Assert.Same(n2, n1.UnconditionalNeighbour); Assert.Same(n5, Assert.Single(n1.ConditionalEdges).Target); @@ -646,6 +654,7 @@ public void ConditionalReplaceStackSlot() DummyInstruction.Pop(5, 1), DummyInstruction.Ret(6), }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); var variablesCapture = new CaptureGroup("variables"); var sourcesCapture = new CaptureGroup>("sources"); @@ -656,18 +665,18 @@ public void ConditionalReplaceStackSlot() Pattern.Any().CaptureAs(variablesCapture), Pattern.Any() ) - .Match(cfg.Nodes[0].Contents.Instructions[0]); + .Match(offsetMap[0].Contents.Instructions[0]); var match2 = StatementPattern .Assignment() .WithVariables(1) .CaptureVariables(variablesCapture) - .Match(cfg.Nodes[3].Contents.Instructions[^1]); + .Match(offsetMap[3].Contents.Instructions[^1]); var match3 = StatementPattern.Phi() .WithSources(2) .CaptureSources(sourcesCapture) - .Match(cfg.Nodes[5].Contents.Instructions[0]); + .Match(offsetMap[5].Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match2.IsSuccess); @@ -698,6 +707,7 @@ public void ConditionalReplaceStackSlotNested() DummyInstruction.Pop(6, 2), DummyInstruction.Ret(7), }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); var variablesCapture = new CaptureGroup("variables"); var sourcesCapture = new CaptureGroup>("sources"); @@ -708,18 +718,18 @@ public void ConditionalReplaceStackSlotNested() Pattern.Any().CaptureAs(variablesCapture), Pattern.Any() ) - .Match(cfg.Nodes[0].Contents.Instructions[^2]); + .Match(offsetMap[0].Contents.Instructions[^2]); var match2 = StatementPattern .Assignment() .WithVariables(1) .CaptureVariables(variablesCapture) - .Match(cfg.Nodes[4].Contents.Instructions[^1]); + .Match(offsetMap[4].Contents.Instructions[^1]); var match3 = StatementPattern.Phi() .WithSources(2) .CaptureSources(sourcesCapture) - .Match(cfg.Nodes[6].Contents.Instructions[0]); + .Match(offsetMap[6].Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match3.IsSuccess); @@ -747,9 +757,10 @@ public void Loop() DummyInstruction.Ret(4), }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify - var (n1, n2, n3) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[4]); + var (n1, n2, n3) = (offsetMap[0], offsetMap[1], offsetMap[4]); Assert.Same(n2, n1.UnconditionalNeighbour); Assert.Same(n2, Assert.Single(n2.ConditionalEdges).Target); @@ -774,9 +785,10 @@ public void LoopWithStackDelta() DummyInstruction.Ret(5), }); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify - var (n1, n2, n3) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[5]); + var (n1, n2, n3) = (offsetMap[0], offsetMap[1], offsetMap[5]); Assert.Same(n2, n1.UnconditionalNeighbour); Assert.Same(n2, Assert.Single(n2.ConditionalEdges).Target); Assert.Same(n3, n2.UnconditionalNeighbour); @@ -832,9 +844,10 @@ public void Handler() new AddressRange(1, 3), new AddressRange(3, 5) )); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify - var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[3], cfg.Nodes[5]); + var (n1, n2, n3, n4) = (offsetMap[0], offsetMap[1], offsetMap[3], offsetMap[5]); var eh = Assert.IsAssignableFrom>>(Assert.Single(cfg.Regions)); Assert.Same(n2, eh.ProtectedRegion.EntryPoint); @@ -863,9 +876,10 @@ public void HandlerPopException() new AddressRange(1, 3), new AddressRange(3, 6) )); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // Verify - var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[3], cfg.Nodes[6]); + var (n1, n2, n3, n4) = (offsetMap[0], offsetMap[1], offsetMap[3], offsetMap[6]); var eh = Assert.IsAssignableFrom>>(Assert.Single(cfg.Regions)); Assert.Same(n2, eh.ProtectedRegion.EntryPoint); diff --git a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs index 05892790..06d0e8ea 100644 --- a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.Linq; using Echo.ControlFlow.Analysis.Domination; using Echo.ControlFlow.Regions; using Echo.Platforms.DummyPlatform; @@ -16,7 +16,7 @@ public void SingleNode() var dominatorTree = DominatorTree.FromGraph(graph); Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); - Assert.True(dominatorTree.Dominates(graph.EntryPoint, graph.EntryPoint)); + Assert.True(dominatorTree.Dominates(graph.EntryPoint!, graph.EntryPoint!)); } [Fact] @@ -24,10 +24,10 @@ public void Path() { // Artificially construct a path of four nodes in sequential order. var graph = TestGraphs.CreatePath(); - var n1 = graph.GetNodeByOffset(0); - var n2 = graph.GetNodeByOffset(1); - var n3 = graph.GetNodeByOffset(2); - var n4 = graph.GetNodeByOffset(3); + var n1 = graph.Nodes.GetByOffset(0)!; + var n2 = graph.Nodes.GetByOffset(1)!; + var n3 = graph.Nodes.GetByOffset(2)!; + var n4 = graph.Nodes.GetByOffset(3)!; var dominatorTree = DominatorTree.FromGraph(graph); Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); @@ -58,10 +58,10 @@ public void If() { // Artificially construct an if construct. var graph = TestGraphs.CreateIfElse(); - var n1 = graph.GetNodeByOffset(0); - var n2 = graph.GetNodeByOffset(2); - var n3 = graph.GetNodeByOffset(3); - var n4 = graph.GetNodeByOffset(4); + var n1 = graph.Nodes.GetByOffset(0)!; + var n2 = graph.Nodes.GetByOffset(2)!; + var n3 = graph.Nodes.GetByOffset(3)!; + var n4 = graph.Nodes.GetByOffset(4)!; var dominatorTree = DominatorTree.FromGraph(graph); Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); @@ -92,10 +92,10 @@ public void Loop() { // Artificially construct a looping construct. var graph = TestGraphs.CreateLoop(); - var n1 = graph.GetNodeByOffset(0); - var n2 = graph.GetNodeByOffset(1); - var n3 = graph.GetNodeByOffset(2); - var n4 = graph.GetNodeByOffset(4); + var n1 = graph.Nodes.GetByOffset(0); + var n2 = graph.Nodes.GetByOffset(1); + var n3 = graph.Nodes.GetByOffset(2); + var n4 = graph.Nodes.GetByOffset(4); var dominatorTree = DominatorTree.FromGraph(graph); Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); @@ -125,41 +125,41 @@ public void Loop() public void ExceptionHandler() { var cfg = new ControlFlowGraph(IntArchitecture.Instance); - - for (int i = 0; i < 7; i++) - cfg.Nodes.Add(new ControlFlowNode(i)); - cfg.EntryPoint = cfg.Nodes[0]; + var nodes = Enumerable.Range(0, 7).Select(x => new ControlFlowNode(x)).ToArray(); + cfg.Nodes.AddRange(nodes); + + cfg.EntryPoint = cfg.Nodes.GetByOffset(0); - cfg.Nodes[0].ConnectWith(cfg.Nodes[1]); - cfg.Nodes[1].ConnectWith(cfg.Nodes[2], ControlFlowEdgeType.Conditional); - cfg.Nodes[1].ConnectWith(cfg.Nodes[3], ControlFlowEdgeType.FallThrough); - cfg.Nodes[2].ConnectWith(cfg.Nodes[4], ControlFlowEdgeType.Unconditional); - cfg.Nodes[3].ConnectWith(cfg.Nodes[4], ControlFlowEdgeType.FallThrough); - cfg.Nodes[4].ConnectWith(cfg.Nodes[6], ControlFlowEdgeType.Unconditional); - cfg.Nodes[5].ConnectWith(cfg.Nodes[6], ControlFlowEdgeType.Unconditional); + nodes[0].ConnectWith(nodes[1]); + nodes[1].ConnectWith(nodes[2], ControlFlowEdgeType.Conditional); + nodes[1].ConnectWith(nodes[3], ControlFlowEdgeType.FallThrough); + nodes[2].ConnectWith(nodes[4], ControlFlowEdgeType.Unconditional); + nodes[3].ConnectWith(nodes[4], ControlFlowEdgeType.FallThrough); + nodes[4].ConnectWith(nodes[6], ControlFlowEdgeType.Unconditional); + nodes[5].ConnectWith(nodes[6], ControlFlowEdgeType.Unconditional); var ehRegion = new ExceptionHandlerRegion(); cfg.Regions.Add(ehRegion); - ehRegion.ProtectedRegion.EntryPoint = cfg.Nodes[1]; + ehRegion.ProtectedRegion.EntryPoint = nodes[1]; ehRegion.ProtectedRegion.Nodes.AddRange(new[] { - cfg.Nodes[1], - cfg.Nodes[2], - cfg.Nodes[3], - cfg.Nodes[4], + nodes[1], + nodes[2], + nodes[3], + nodes[4], }); var handler = new HandlerRegion(); ehRegion.Handlers.Add(handler); - handler.Contents.Nodes.Add(cfg.Nodes[5]); - handler.Contents.EntryPoint = cfg.Nodes[5]; + handler.Contents.Nodes.Add(nodes[5]); + handler.Contents.EntryPoint = nodes[5]; var tree = DominatorTree.FromGraph(cfg); - Assert.True(tree.Dominates(cfg.Nodes[1], cfg.Nodes[6])); - Assert.False(tree.Dominates(cfg.Nodes[4], cfg.Nodes[6])); - Assert.False(tree.Dominates(cfg.Nodes[5], cfg.Nodes[6])); + Assert.True(tree.Dominates(nodes[1], nodes[6])); + Assert.False(tree.Dominates(nodes[4], nodes[6])); + Assert.False(tree.Dominates(nodes[5], nodes[6])); } } } \ No newline at end of file diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs index d3f9b166..0d06f78a 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using Echo.ControlFlow.Construction.Static; -using Echo.ControlFlow.Serialization.Dot; -using Echo.Graphing.Serialization.Dot; using Echo.Platforms.DummyPlatform.Code; using Xunit; @@ -106,11 +103,12 @@ public void If() var graph = BuildControlFlowGraph(instructions); Assert.Equal(4, graph.Nodes.Count); - Assert.Single(graph.EntryPoint.ConditionalEdges); + Assert.Single(graph.EntryPoint!.ConditionalEdges); Assert.NotNull(graph.EntryPoint.UnconditionalEdge); Assert.Equal( - graph.EntryPoint.UnconditionalNeighbour.UnconditionalNeighbour, - graph.EntryPoint.ConditionalEdges.First().Target.UnconditionalNeighbour); + graph.EntryPoint.UnconditionalNeighbour?.UnconditionalNeighbour, + graph.EntryPoint.ConditionalEdges.First().Target.UnconditionalNeighbour + ); } [Fact] @@ -145,7 +143,7 @@ public void Loop() Assert.Equal(4, graph.Nodes.Count); // Entrypoint. - Assert.NotNull(graph.EntryPoint.UnconditionalNeighbour); + Assert.NotNull(graph.EntryPoint!.UnconditionalNeighbour); Assert.Empty(graph.EntryPoint.ConditionalEdges); // Loop header @@ -160,6 +158,7 @@ public void Loop() // Exit var exit = loopHeader.UnconditionalNeighbour; + Assert.NotNull(exit); Assert.Empty(exit.GetOutgoingEdges()); } @@ -224,8 +223,8 @@ public void BlockHeadersImpliedByInstructionsShouldAlwaysBeAdded() Assert.Contains(graph.Nodes, n => n.Offset == 0); Assert.Contains(graph.Nodes, n => n.Offset == 10); Assert.DoesNotContain(graph.Nodes, n => n.Offset == 1); - Assert.Empty(graph.Nodes[0].GetOutgoingEdges()); - Assert.Empty(graph.Nodes[10].GetIncomingEdges()); + Assert.Empty(graph.Nodes.GetByOffset(0)!.GetOutgoingEdges()); + Assert.Empty(graph.Nodes.GetByOffset(10)!.GetIncomingEdges()); } } } \ No newline at end of file diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs index c03956c7..39b934b2 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs @@ -1,11 +1,8 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using Echo.ControlFlow.Construction; using Echo.ControlFlow.Construction.Symbolic; -using Echo.ControlFlow.Serialization.Dot; -using Echo.Graphing.Serialization.Dot; using Echo.DataFlow; using Echo.DataFlow.Emulation; using Echo.Platforms.DummyPlatform.Code; @@ -307,8 +304,8 @@ public void BlockHeadersImpliedByInstructionsShouldAlwaysBeAdded() Assert.Contains(cfg.Nodes, n => n.Offset == 0); Assert.Contains(cfg.Nodes, n => n.Offset == 10); Assert.DoesNotContain(cfg.Nodes, n => n.Offset == 1); - Assert.Empty(cfg.Nodes[0].GetOutgoingEdges()); - Assert.Empty(cfg.Nodes[10].GetIncomingEdges()); + Assert.Empty(cfg.Nodes.GetByOffset(0)!.GetOutgoingEdges()); + Assert.Empty(cfg.Nodes.GetByOffset(10)!.GetIncomingEdges()); } } } \ No newline at end of file diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs index ae568648..cc989e39 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs @@ -203,58 +203,12 @@ public void MultipleConditionalEdgesToSameNodeIsAllowed() n3, n3, n3, n3 }, n1.ConditionalEdges.Select(e => e.Target)); } - - [Fact] - public void UpdateOffsetsWithNoChangeShouldReuseNodeInstances() - { - var graph = new ControlFlowGraph(IntArchitecture.Instance); - - var n1 = new ControlFlowNode(1, 1); - var n2 = new ControlFlowNode(2, 2); - var n3 = new ControlFlowNode(3, 3); - - graph.Nodes.AddRange(new[] - { - n1, n2, n3 - }); - - graph.Nodes.UpdateOffsets(); - - Assert.Same(n1, graph.Nodes[1]); - Assert.Same(n2, graph.Nodes[2]); - Assert.Same(n3, graph.Nodes[3]); - } - - [Fact] - public void UpdateOffsetsWithChangeShouldReuseNodeInstances() - { - var graph = new ControlFlowGraph(IntArchitecture.Instance); - - var n1 = new ControlFlowNode(1, 1); - var n2 = new ControlFlowNode(2, 2); - var n3 = new ControlFlowNode(3, 3); - - graph.Nodes.AddRange(new[] - { - n1, n2, n3 - }); - - n1.Contents.Instructions[0] = 4; - n2.Contents.Instructions[0] = 5; - n3.Contents.Instructions[0] = 6; - - graph.Nodes.UpdateOffsets(); - - Assert.Same(n1, graph.Nodes[4]); - Assert.Same(n2, graph.Nodes[5]); - Assert.Same(n3, graph.Nodes[6]); - } - + [Fact] public void UpdateOffsetsShouldUpdateNodeOffset() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - var n1 = new ControlFlowNode(1, 1); + var n1 = new ControlFlowNode(1); graph.Nodes.Add(n1); n1.Contents.Instructions[0] = 5; @@ -263,51 +217,5 @@ public void UpdateOffsetsShouldUpdateNodeOffset() Assert.Equal(5, n1.Offset); Assert.Equal(5, n1.Contents.Offset); } - - [Fact] - public void UpdateOffsetsOnEmptyBasicBlocksShouldThrowAndDiscard() - { - var graph = new ControlFlowGraph(IntArchitecture.Instance); - var n1 = new ControlFlowNode(1, 1); - var n2 = new ControlFlowNode(2); - graph.Nodes.AddRange(new[] - { - n1, - n2 - }); - - Assert.Throws(() => graph.Nodes.UpdateOffsets()); - - Assert.Equal(1, n1.Offset); - Assert.Equal(1, n1.Contents.Offset); - Assert.Equal(2, n2.Offset); - } - - [Fact] - public void UpdateOffsetsWithDuplicatedOffsetsShouldThrowAndDiscard() - { - var graph = new ControlFlowGraph(IntArchitecture.Instance); - var n1 = new ControlFlowNode(1, 1); - var n2 = new ControlFlowNode(2, 2); - var n3 = new ControlFlowNode(3, 3); - graph.Nodes.AddRange(new[] - { - n1, - n2, - n3 - }); - - n2.Contents.Instructions[0] = 4; - n3.Contents.Instructions[0] = 4; - - Assert.Throws(() => graph.Nodes.UpdateOffsets()); - - Assert.Equal(1, n1.Offset); - Assert.Equal(1, n1.Contents.Offset); - Assert.Equal(2, n2.Offset); - Assert.Equal(2, n2.Contents.Offset); - Assert.Equal(3, n3.Offset); - Assert.Equal(3, n3.Contents.Offset); - } } } \ No newline at end of file diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs index 26432deb..660c7464 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs @@ -216,9 +216,10 @@ public void GetPredecessors() public void SplitNodeShouldConnectUsingFallThrough() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0, 1, 2, 3, 4)); + var node = new ControlFlowNode(0, 1, 2, 3, 4); + graph.Nodes.Add(node); - var (first, second) = graph.Nodes[0].SplitAtIndex(2); + var (first, second) = node.SplitAtIndex(2); Assert.Equal(new[] { 0, 1 }, first.Contents.Instructions); Assert.Equal(new[] { 2, 3, 4 }, second.Contents.Instructions); @@ -229,80 +230,95 @@ public void SplitNodeShouldConnectUsingFallThrough() public void SplitEmptyNodeShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0)); - Assert.Throws(() => graph.Nodes[0].SplitAtIndex(1)); + var node = new ControlFlowNode([]); + graph.Nodes.Add(node); + Assert.Throws(() => node.SplitAtIndex(1)); } [Fact] public void SplitNodeWithSingleInstructionShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - Assert.Throws(() => graph.Nodes[0].SplitAtIndex(0)); + var node = new ControlFlowNode(0); + graph.Nodes.Add(node); + Assert.Throws(() => node.SplitAtIndex(0)); } [Fact] public void SplitNodeAtHeaderShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0, 1, 2)); - Assert.Throws(() => graph.Nodes[0].SplitAtIndex(0)); + var node = new ControlFlowNode(0, 1, 2); + graph.Nodes.Add(node); + Assert.Throws(() => node.SplitAtIndex(0)); } [Fact] public void SplitNodeAtFooterShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0, 1, 2)); - Assert.Throws(() => graph.Nodes[0].SplitAtIndex(3)); + var node = new ControlFlowNode(0, 1, 2); + graph.Nodes.Add(node); + Assert.Throws(() => node.SplitAtIndex(3)); } [Fact] public void SplitNodeShouldTransferFallThroughEdge() { var graph = new ControlFlowGraph(IntArchitecture.Instance); + + var nodes = new ControlFlowNode[] + { + new(0), + new(1, 2), + new(3), + }; - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1, 2)); - graph.Nodes.Add(new ControlFlowNode(3, 3)); + graph.Nodes.AddRange(nodes); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); - graph.Nodes[1].ConnectWith(graph.Nodes[3]); + nodes[0].ConnectWith(nodes[1]); + nodes[1].ConnectWith(nodes[2]); - var (first, second) = graph.Nodes[1].SplitAtIndex(1); + var (first, second) = nodes[1].SplitAtIndex(1); - Assert.Same(graph.Nodes[3], second.UnconditionalNeighbour); + Assert.Same(nodes[2], second.UnconditionalNeighbour); } [Fact] public void SplitNodeShouldTransferConditionalEdges() { var graph = new ControlFlowGraph(IntArchitecture.Instance); + + var nodes = new ControlFlowNode[] + { + new(0), + new(1, 2), + new(3), + new(4), + }; - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1, 2)); - graph.Nodes.Add(new ControlFlowNode(3, 3)); - graph.Nodes.Add(new ControlFlowNode(4, 4)); - - graph.Nodes[0].ConnectWith(graph.Nodes[1]); - graph.Nodes[1].ConnectWith(graph.Nodes[3], ControlFlowEdgeType.Conditional); - graph.Nodes[1].ConnectWith(graph.Nodes[4]); + graph.Nodes.AddRange(nodes); + + nodes[0].ConnectWith(nodes[1]); + nodes[1].ConnectWith(nodes[2], ControlFlowEdgeType.Conditional); + nodes[1].ConnectWith(nodes[3]); - var (first, second) = graph.Nodes[1].SplitAtIndex(1); + var (first, second) = nodes[1].SplitAtIndex(1); - Assert.Same(graph.Nodes[4], second.UnconditionalNeighbour); - Assert.Contains(graph.Nodes[3], second.ConditionalEdges.Select(e => e.Target)); + Assert.Same(nodes[3], second.UnconditionalNeighbour); + Assert.Contains(nodes[2], second.ConditionalEdges.Select(e => e.Target)); } [Fact] public void SplitSelfLoopNode() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - - graph.Nodes.Add(new ControlFlowNode(0, 0, 1)); - graph.Nodes[0].UnconditionalNeighbour = graph.Nodes[0]; - var (first, second) = graph.Nodes[0].SplitAtIndex(1); + var node = new ControlFlowNode(0, 1); + graph.Nodes.Add(node); + node.UnconditionalNeighbour = node; + + var (first, second) = node.SplitAtIndex(1); Assert.Same(second, first.UnconditionalNeighbour); Assert.Same(first, second.UnconditionalNeighbour); @@ -312,88 +328,99 @@ public void SplitSelfLoopNode() public void MergeWithSuccessorShouldRemoveSuccessor() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1)); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); + var nodes = new ControlFlowNode[] + { + new(0), + new(1), + }; + graph.Nodes.AddRange(nodes); + nodes[0].ConnectWith(nodes[1]); - graph.Nodes[0].MergeWithSuccessor(); + nodes[0].MergeWithSuccessor(); - Assert.Equal(new[] - { - graph.Nodes[0] - }, graph.Nodes); + Assert.Equal([nodes[0]], graph.Nodes.ToArray()); } [Fact] public void MergeWithSuccessorShouldCombineInstructions() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1)); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); + var nodes = new ControlFlowNode[] + { + new(0), + new(1), + }; + graph.Nodes.AddRange(nodes); + nodes[0].ConnectWith(nodes[1]); - graph.Nodes[0].MergeWithSuccessor(); + nodes[0].MergeWithSuccessor(); - Assert.Equal(new[] - { - 0, - 1 - }, graph.Nodes[0].Contents.Instructions); + Assert.Equal([0, 1], nodes[0].Contents.Instructions); } [Fact] public void MergeWithSuccessorShouldInheritFallThroughEdge() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1)); - graph.Nodes.Add(new ControlFlowNode(2, 2)); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); - graph.Nodes[1].ConnectWith(graph.Nodes[2]); + var nodes = new ControlFlowNode[] + { + new(0), + new(1), + new(2), + }; + graph.Nodes.AddRange(nodes); + nodes[0].ConnectWith(nodes[1]); + nodes[1].ConnectWith(nodes[2]); - graph.Nodes[0].MergeWithSuccessor(); - - Assert.Same(graph.Nodes[2], graph.Nodes[0].UnconditionalNeighbour); + nodes[0].MergeWithSuccessor(); + + Assert.Same(nodes[2], nodes[0].UnconditionalNeighbour); } [Fact] public void MergeWithSuccessorShouldInheritConditionalEdge() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1)); - graph.Nodes.Add(new ControlFlowNode(2, 2)); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); - graph.Nodes[1].ConnectWith(graph.Nodes[2], ControlFlowEdgeType.Conditional); + var nodes = new ControlFlowNode[] + { + new(0), + new(1), + new(2), + }; + graph.Nodes.AddRange(nodes); + nodes[0].ConnectWith(nodes[1]); + nodes[1].ConnectWith(nodes[2], ControlFlowEdgeType.Conditional); - graph.Nodes[0].MergeWithSuccessor(); + nodes[0].MergeWithSuccessor(); - Assert.Equal(new[] - { - graph.Nodes[2] - }, graph.Nodes[0].ConditionalEdges.Select(e => e.Target)); + Assert.Equal([nodes[2]], nodes[0].ConditionalEdges.Select(e => e.Target)); } [Fact] public void MergeWithSuccessorWithNoFallThroughNeighbourShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); + var node = new ControlFlowNode(0); + graph.Nodes.Add(node); - Assert.Throws(() => graph.Nodes[0].MergeWithSuccessor()); + Assert.Throws(() => node.MergeWithSuccessor()); } [Fact] public void MergeWithSuccessorWithConditionalEdgesShouldThrow() { var graph = new ControlFlowGraph(IntArchitecture.Instance); - graph.Nodes.Add(new ControlFlowNode(0, 0)); - graph.Nodes.Add(new ControlFlowNode(1, 1)); - graph.Nodes.Add(new ControlFlowNode(2, 2)); - graph.Nodes[0].ConnectWith(graph.Nodes[1]); - graph.Nodes[0].ConnectWith(graph.Nodes[2], ControlFlowEdgeType.Conditional); + var nodes = new ControlFlowNode[] + { + new(0), + new(1), + new(2), + }; + graph.Nodes.AddRange(nodes); + nodes[0].ConnectWith(nodes[1]); + nodes[0].ConnectWith(nodes[2], ControlFlowEdgeType.Conditional); - Assert.Throws(() => graph.Nodes[0].MergeWithSuccessor()); + Assert.Throws(() => nodes[0].MergeWithSuccessor()); } [Theory] diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs index 942877e6..65866d45 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs @@ -8,7 +8,7 @@ public static ControlFlowGraph CreateSingularGraph() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, DummyInstruction.Ret(0)); + var n1 = new ControlFlowNode(DummyInstruction.Ret(0)); graph.Nodes.Add(n1); graph.EntryPoint = n1; @@ -20,19 +20,24 @@ public static ControlFlowGraph CreatePath() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, - DummyInstruction.Op(0, 0, 0)); + var n1 = new ControlFlowNode( + DummyInstruction.Op(0, 0, 0) + ); - var n2 = new ControlFlowNode(1, - DummyInstruction.Op(1, 0, 0)); + var n2 = new ControlFlowNode( + DummyInstruction.Op(1, 0, 0) + ); - var n3 = new ControlFlowNode(2, - DummyInstruction.Op(2, 0, 0)); + var n3 = new ControlFlowNode( + DummyInstruction.Op(2, 0, 0) + ); - var n4 = new ControlFlowNode(3, - DummyInstruction.Ret(3)); + var n4 = new ControlFlowNode( + DummyInstruction.Ret(3) + ); - graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); + graph.Nodes.AddRange([n1, n2, n3, n4]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n2); @@ -46,17 +51,17 @@ public static ControlFlowGraph CreateIf() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, + var n1 = new ControlFlowNode( DummyInstruction.Op(0, 0, 1), - DummyInstruction.JmpCond(1, 3)); + DummyInstruction.JmpCond(1, 3) + ); - var n2 = new ControlFlowNode(2, - DummyInstruction.Op(2, 0, 0)); + var n2 = new ControlFlowNode(DummyInstruction.Op(2, 0, 0)); - var n3 = new ControlFlowNode(3, - DummyInstruction.Ret(3)); + var n3 = new ControlFlowNode(DummyInstruction.Ret(3)); - graph.Nodes.AddRange(new[] {n1, n2, n3}); + graph.Nodes.AddRange([n1, n2, n3]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n2); @@ -70,20 +75,19 @@ public static ControlFlowGraph CreateIfElse() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, + var n1 = new ControlFlowNode( DummyInstruction.Op(0, 0, 1), - DummyInstruction.JmpCond(1, 3)); + DummyInstruction.JmpCond(1, 3) + ); - var n2 = new ControlFlowNode(2, - DummyInstruction.Jmp(2, 4)); + var n2 = new ControlFlowNode(DummyInstruction.Jmp(2, 4)); - var n3 = new ControlFlowNode(3, - DummyInstruction.Op(3, 0, 0)); + var n3 = new ControlFlowNode(DummyInstruction.Op(3, 0, 0)); - var n4 = new ControlFlowNode(4, - DummyInstruction.Ret(4)); + var n4 = new ControlFlowNode(DummyInstruction.Ret(4)); - graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); + graph.Nodes.AddRange([n1, n2, n3, n4]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n2); @@ -98,27 +102,26 @@ public static ControlFlowGraph CreateIfElseNested() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, + var n1 = new ControlFlowNode( DummyInstruction.Op(0, 0, 1), - DummyInstruction.JmpCond(1, 7)); + DummyInstruction.JmpCond(1, 7) + ); - var n2 = new ControlFlowNode(2, + var n2 = new ControlFlowNode( DummyInstruction.Op(2, 0, 1), - DummyInstruction.JmpCond(3, 4)); + DummyInstruction.JmpCond(3, 4) + ); - var n3 = new ControlFlowNode(4, - DummyInstruction.Jmp(4, 6)); + var n3 = new ControlFlowNode(DummyInstruction.Jmp(4, 6)); - var n4 = new ControlFlowNode(5, - DummyInstruction.Jmp(5, 6)); + var n4 = new ControlFlowNode(DummyInstruction.Jmp(5, 6)); - var n5 = new ControlFlowNode(6, - DummyInstruction.Jmp(6, 7)); + var n5 = new ControlFlowNode(DummyInstruction.Jmp(6, 7)); - var n6 = new ControlFlowNode(7, - DummyInstruction.Ret(7)); + var n6 = new ControlFlowNode(DummyInstruction.Ret(7)); - graph.Nodes.AddRange(new[] {n1, n2, n3, n4, n5, n6}); + graph.Nodes.AddRange([n1, n2, n3, n4, n5, n6]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n2); @@ -136,20 +139,19 @@ public static ControlFlowGraph CreateLoop() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, - DummyInstruction.Jmp(0, 2)); + var n1 = new ControlFlowNode(DummyInstruction.Jmp(0, 2)); - var n2 = new ControlFlowNode(1, - DummyInstruction.Jmp(1, 4)); + var n2 = new ControlFlowNode(DummyInstruction.Jmp(1, 4)); - var n3 = new ControlFlowNode(2, + var n3 = new ControlFlowNode( DummyInstruction.Op(2, 0, 1), - DummyInstruction.JmpCond(3, 1)); + DummyInstruction.JmpCond(3, 1) + ); - var n4 = new ControlFlowNode(4, - DummyInstruction.Ret(4)); + var n4 = new ControlFlowNode(DummyInstruction.Ret(4)); - graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); + graph.Nodes.AddRange([n1, n2, n3, n4]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n3); @@ -164,20 +166,15 @@ public static ControlFlowGraph CreateSwitch() { var graph = new ControlFlowGraph(DummyArchitecture.Instance); - var n1 = new ControlFlowNode(0, - DummyInstruction.Switch(0, 2, 3, 4, 5)); - var n2 = new ControlFlowNode(1, - DummyInstruction.Jmp(1, 5)); - var n3 = new ControlFlowNode(2, - DummyInstruction.Jmp(2, 5)); - var n4 = new ControlFlowNode(3, - DummyInstruction.Jmp(3, 5)); - var n5 = new ControlFlowNode(4, - DummyInstruction.Jmp(4, 5)); - var n6 = new ControlFlowNode(5, - DummyInstruction.Ret(5)); + var n1 = new ControlFlowNode(DummyInstruction.Switch(0, 2, 3, 4, 5)); + var n2 = new ControlFlowNode(DummyInstruction.Jmp(1, 5)); + var n3 = new ControlFlowNode(DummyInstruction.Jmp(2, 5)); + var n4 = new ControlFlowNode(DummyInstruction.Jmp(3, 5)); + var n5 = new ControlFlowNode(DummyInstruction.Jmp(4, 5)); + var n6 = new ControlFlowNode( DummyInstruction.Ret(5)); - graph.Nodes.AddRange(new[] {n1, n2, n3, n4, n5, n6}); + graph.Nodes.AddRange([n1, n2, n3, n4, n5, n6]); + graph.Nodes.UpdateOffsets(); graph.EntryPoint = n1; n1.ConnectWith(n2); diff --git a/test/Core/Echo.ControlFlow.Tests/Echo.ControlFlow.Tests.csproj b/test/Core/Echo.ControlFlow.Tests/Echo.ControlFlow.Tests.csproj index b42a359f..d095e2ce 100644 --- a/test/Core/Echo.ControlFlow.Tests/Echo.ControlFlow.Tests.csproj +++ b/test/Core/Echo.ControlFlow.Tests/Echo.ControlFlow.Tests.csproj @@ -3,6 +3,7 @@ net6.0 false + 12 diff --git a/test/Core/Echo.ControlFlow.Tests/Editing/FlowControlSynchronizerTest.cs b/test/Core/Echo.ControlFlow.Tests/Editing/FlowControlSynchronizerTest.cs deleted file mode 100644 index f884f240..00000000 --- a/test/Core/Echo.ControlFlow.Tests/Editing/FlowControlSynchronizerTest.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; -using Echo.ControlFlow.Editing.Synchronization; -using Echo.Platforms.DummyPlatform.Code; -using Xunit; - -namespace Echo.ControlFlow.Tests.Editing -{ - public class FlowControlSynchronizerTest - { - private static ControlFlowGraph BuildControlFlowGraph(DummyInstruction[] instructions, - long entrypoint = 0, IEnumerable knownBlockHeaders = null) - { - var builder = new StaticFlowGraphBuilder( - DummyArchitecture.Instance, - instructions, - DummyArchitecture.Instance.SuccessorResolver); - - return builder.ConstructFlowGraph(entrypoint, knownBlockHeaders ?? ImmutableArray.Empty); - } - - [Fact] - public void NoChangeShouldResultInNoChangeInTheGraph() - { - var cfg = BuildControlFlowGraph(new[] - { - DummyInstruction.Ret(0) - }); - - Assert.False(cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - } - - [Fact] - public void BranchTargetChangeToAnotherNodeHeaderShouldUpdateFallThroughEdge() - { - var cfg = BuildControlFlowGraph(new[] - { - DummyInstruction.Op(0,0, 0), - DummyInstruction.Jmp(1, 10), - - DummyInstruction.Jmp(10, 20), - - DummyInstruction.Ret(20) - }); - - // Change branch target of the first jmp to the ret at offset 20. - cfg.Nodes[0].Contents.Footer.Operands[0] = 20L; - - Assert.True(cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - Assert.Same(cfg.Nodes[20], cfg.Nodes[0].UnconditionalNeighbour); - } - - [Fact] - public void ConditionalBranchTargetChangeToAnotherNodeHeaderShouldUpdateConditionalEdge() - { - var cfg = BuildControlFlowGraph(new[] - { - DummyInstruction.Push(0,1), - DummyInstruction.JmpCond(1, 20), - - DummyInstruction.Jmp(2, 20), - - DummyInstruction.Ret(20) - },0); - - // Add a new node to use as a branch target. - var newTarget = new ControlFlowNode(100, DummyInstruction.Jmp(100, 20)); - cfg.Nodes.Add(newTarget); - newTarget.ConnectWith(cfg.Nodes[20]); - - // Update branch target. - cfg.Nodes[0].Contents.Footer.Operands[0] = 100L; - - Assert.True(cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - Assert.Single(cfg.Nodes[0].ConditionalEdges); - Assert.True(cfg.Nodes[0].ConditionalEdges.Contains(cfg.Nodes[100])); - } - - [Fact] - public void SwapUnconditionalWithConditionalBranchShouldUpdateFallThroughAndConditionalEdge() - { - var cfg = BuildControlFlowGraph(new[] - { - DummyInstruction.Push(0,1), - DummyInstruction.Jmp(1, 10), - - DummyInstruction.Jmp(2, 20), - - DummyInstruction.Jmp(10, 2), - - DummyInstruction.Ret(20) - },0); - - // Update unconditional jmp to a conditional one. - var blockInstructions = cfg.Nodes[0].Contents.Instructions; - blockInstructions[blockInstructions.Count - 1] = DummyInstruction.JmpCond(1, 20); - - Assert.True(cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - Assert.Same(cfg.Nodes[2], cfg.Nodes[0].UnconditionalNeighbour); - Assert.Single(cfg.Nodes[0].ConditionalEdges); - Assert.True(cfg.Nodes[0].ConditionalEdges.Contains(cfg.Nodes[20])); - } - - [Fact] - public void ChangeBranchTargetToMiddleOfNodeShouldSplitNode() - { - var instructions = new[] - { - DummyInstruction.Push(0,1), - DummyInstruction.Jmp(1, 10), - - DummyInstruction.Op(10, 0,0), - DummyInstruction.Op(11, 0,0), - DummyInstruction.Op(12, 0,0), - DummyInstruction.Op(13, 0,0), - DummyInstruction.Op(14, 0,0), - DummyInstruction.JmpCond(15, 10), - - DummyInstruction.Ret(16) - }; - var cfg = BuildControlFlowGraph(instructions,0); - - // Change jmp target to an instruction in the middle of node[10]. - cfg.Nodes[0].Contents.Footer.Operands[0] = 13L; - - Assert.True(cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - - Assert.True(cfg.Nodes.Contains(10), "Original target does not exist anymore."); - Assert.True(cfg.Nodes.Contains(13), "Original target was not split up correctly."); - - Assert.Same(cfg.Nodes[13], cfg.Nodes[10].UnconditionalNeighbour); - Assert.Same(cfg.Nodes[13], cfg.Nodes[0].UnconditionalNeighbour); - } - - [Fact] - public void UpdateToInvalidBranchTargetShouldThrowAndDiscard() - { - var cfg = BuildControlFlowGraph(new[] - { - DummyInstruction.Jmp(0, 10), - DummyInstruction.Ret(10), - },0); - - cfg.Nodes[0].Contents.Header.Operands[0] = 100L; - - Assert.Throws(() => cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - - Assert.Same(cfg.Nodes[10], cfg.Nodes[0].UnconditionalNeighbour); - } - - [Fact] - public void SplittedNodeShouldBeRemergedAfterDetectingInvalidFallThroughNeighbour() - { - var instructions = new[] - { - DummyInstruction.Op(0, 0, 0), - DummyInstruction.Jmp(1, 10), - DummyInstruction.Op(10, 0, 0), - DummyInstruction.Op(11, 0, 0), - DummyInstruction.Op(12, 0, 0), - DummyInstruction.Jmp(13, 20), - DummyInstruction.Ret(20), - }; - var cfg = BuildControlFlowGraph(instructions,0); - - instructions[1].Operands[0] = 12L; - instructions[5].Operands[0] = 100L; - - Assert.Throws(() => cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - - Assert.Same(cfg.Nodes[10], cfg.Nodes[0].UnconditionalNeighbour); - Assert.False(cfg.Nodes.Contains(12)); - } - - [Fact] - public void SplittedNodeShouldBeRemergedAfterDetectingInvalidConditionalNeighbour() - { - var instructions = new[] - { - DummyInstruction.Op(0, 0, 0), - DummyInstruction.Switch(1, 2, 3, 4, 6), - - DummyInstruction.Jmp(2, 20), - - DummyInstruction.Jmp(3, 20), - - DummyInstruction.Op(4, 0, 0), - DummyInstruction.Jmp(5, 20), - - DummyInstruction.Jmp(6, 20), - - DummyInstruction.Ret(20), - }; - var cfg = BuildControlFlowGraph(instructions,0); - - var targets = (long[]) instructions[1].Operands[0]; - targets[2] = 5L; - targets[3] = 100L; - - Assert.Throws(() => cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver)); - - Assert.False(cfg.Nodes.Contains(5)); - Assert.Equal(new[] - { - instructions[4], - instructions[5] - }, cfg.Nodes[4].Contents.Instructions); - } - - [Fact] - public void AddBranchInMiddleOfBlockShouldSplit() - { - var instructions = new[] - { - DummyInstruction.Op(0, 0, 0), - DummyInstruction.Op(1, 0, 0), - DummyInstruction.Op(2, 0, 0), - DummyInstruction.Op(3, 0, 0), - DummyInstruction.Ret(4), - }; - var cfg = BuildControlFlowGraph(instructions, 0); - - cfg.Nodes[0].Contents.Instructions[1] = DummyInstruction.Jmp(1, 4); - cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver); - - Assert.True(cfg.Nodes.Contains(0)); - Assert.True(cfg.Nodes.Contains(4)); - Assert.Same(cfg.Nodes[4], cfg.Nodes[0].UnconditionalNeighbour); - } - - [Fact] - public void AddBranchInMiddleOfBlockShouldSplit2() - { - var instructions = new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Pop(1, 1), - DummyInstruction.Push(2, 1), - DummyInstruction.JmpCond(3, 5), - - DummyInstruction.Ret(4), - - DummyInstruction.Op(5, 0, 0), - DummyInstruction.Ret(6), - }; - var cfg = BuildControlFlowGraph(instructions, 0); - - cfg.Nodes[0].Contents.Instructions[1] = DummyInstruction.JmpCond(1, 5); - cfg.Nodes[0].Contents.Instructions[3] = DummyInstruction.Pop(3, 1); - cfg.UpdateFlowControl(DummyArchitecture.Instance.SuccessorResolver); - - Assert.True(cfg.Nodes.Contains(2)); - Assert.Same(cfg.Nodes[2], cfg.Nodes[0].UnconditionalNeighbour); - Assert.Same(cfg.Nodes[5], cfg.Nodes[0].ConditionalEdges.First().Target); - Assert.Same(cfg.Nodes[4], cfg.Nodes[2].UnconditionalNeighbour); - Assert.Empty(cfg.Nodes[2].ConditionalEdges); - } - } -} \ No newline at end of file diff --git a/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs b/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs index e08a59da..568308c7 100644 --- a/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs @@ -4,7 +4,6 @@ using Echo.ControlFlow.Construction; using Echo.ControlFlow.Construction.Static; using Echo.ControlFlow.Regions.Detection; -using Echo.Code; using Echo.Platforms.DummyPlatform.Code; using Xunit; @@ -51,15 +50,16 @@ public void DetectSingleEHByRange() }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - Assert.Same(cfg, cfg.Nodes[0].ParentRegion); - Assert.Same(cfg, cfg.Nodes[5].ParentRegion); + Assert.Same(cfg, offsetMap[0]!.ParentRegion); + Assert.Same(cfg, offsetMap[5]!.ParentRegion); - var ehRegion = cfg.Nodes[1].GetParentExceptionHandler(); + var ehRegion = offsetMap[1]!.GetParentExceptionHandler(); Assert.NotNull(ehRegion); - Assert.Same(ehRegion.ProtectedRegion, cfg.Nodes[1].ParentRegion); - Assert.Contains(cfg.Nodes[3].GetParentHandler(), ehRegion.Handlers); + Assert.Same(ehRegion.ProtectedRegion, offsetMap[1].ParentRegion); + Assert.Contains(offsetMap[3].GetParentHandler(), ehRegion.Handlers); } [Theory] @@ -102,15 +102,16 @@ public void DetectSequentialEHsByRanges(bool reverse) }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var ehRegion1 = cfg.Nodes[1].GetParentExceptionHandler(); - var ehRegion2 = cfg.Nodes[6].GetParentExceptionHandler(); + var ehRegion1 = offsetMap[1].GetParentExceptionHandler(); + var ehRegion2 = offsetMap[6].GetParentExceptionHandler(); Assert.NotSame(ehRegion1, ehRegion2); - Assert.Same(ehRegion1.ProtectedRegion, cfg.Nodes[1].ParentRegion); - Assert.Contains(cfg.Nodes[3].GetParentHandler(), ehRegion1.Handlers); - Assert.Same(ehRegion1.ProtectedRegion, cfg.Nodes[1].ParentRegion); - Assert.Contains(cfg.Nodes[3].GetParentHandler(), ehRegion1.Handlers); + Assert.Same(ehRegion1.ProtectedRegion, offsetMap[1].ParentRegion); + Assert.Contains(offsetMap[3].GetParentHandler(), ehRegion1.Handlers); + Assert.Same(ehRegion1.ProtectedRegion, offsetMap[1].ParentRegion); + Assert.Contains(offsetMap[3].GetParentHandler(), ehRegion1.Handlers); } [Theory] @@ -147,12 +148,13 @@ public void EHWithMultipleHandlersByRangesShouldGroupTogether(bool reverse) }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var ehRegion = cfg.Nodes[1].GetParentExceptionHandler(); + var ehRegion = offsetMap[1].GetParentExceptionHandler(); - Assert.Same(ehRegion, cfg.Nodes[3].GetParentExceptionHandler()); - Assert.Same(ehRegion, cfg.Nodes[5].GetParentExceptionHandler()); - Assert.NotSame(cfg.Nodes[3].ParentRegion, cfg.Nodes[5].ParentRegion); + Assert.Same(ehRegion, offsetMap[3].GetParentExceptionHandler()); + Assert.Same(ehRegion, offsetMap[5].GetParentExceptionHandler()); + Assert.NotSame(offsetMap[3].ParentRegion, offsetMap[5].ParentRegion); } [Theory] @@ -194,18 +196,19 @@ public void DetectNestedEHInProtectedRegionByRanges(bool reverse) }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var ehRegion1 = cfg.Nodes[1].GetParentExceptionHandler(); - var ehRegion2 = cfg.Nodes[2].GetParentExceptionHandler(); + var ehRegion1 = offsetMap[1].GetParentExceptionHandler(); + var ehRegion2 = offsetMap[2].GetParentExceptionHandler(); Assert.NotSame(ehRegion1, ehRegion2); - Assert.Null(cfg.Nodes[0].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[1].GetParentExceptionHandler()); - Assert.Same(ehRegion2, cfg.Nodes[2].GetParentExceptionHandler()); - Assert.Same(ehRegion2, cfg.Nodes[4].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[6].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[7].GetParentExceptionHandler()); - Assert.Null(cfg.Nodes[9].GetParentExceptionHandler()); + Assert.Null(offsetMap[0].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[1].GetParentExceptionHandler()); + Assert.Same(ehRegion2, offsetMap[2].GetParentExceptionHandler()); + Assert.Same(ehRegion2, offsetMap[4].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[6].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[7].GetParentExceptionHandler()); + Assert.Null(offsetMap[9].GetParentExceptionHandler()); } [Theory] @@ -247,18 +250,19 @@ public void DetectNestedEHInHandlerRegionByRanges(bool reverse) }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var ehRegion1 = cfg.Nodes[1].GetParentExceptionHandler(); - var ehRegion2 = cfg.Nodes[4].GetParentExceptionHandler(); + var ehRegion1 = offsetMap[1].GetParentExceptionHandler(); + var ehRegion2 = offsetMap[4].GetParentExceptionHandler(); Assert.NotSame(ehRegion1, ehRegion2); - Assert.Null(cfg.Nodes[0].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[1].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[3].GetParentExceptionHandler()); - Assert.Same(ehRegion2, cfg.Nodes[4].GetParentExceptionHandler()); - Assert.Same(ehRegion2, cfg.Nodes[6].GetParentExceptionHandler()); - Assert.Same(ehRegion1, cfg.Nodes[8].GetParentExceptionHandler()); - Assert.Null(cfg.Nodes[9].GetParentExceptionHandler()); + Assert.Null(offsetMap[0].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[1].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[3].GetParentExceptionHandler()); + Assert.Same(ehRegion2, offsetMap[4].GetParentExceptionHandler()); + Assert.Same(ehRegion2, offsetMap[6].GetParentExceptionHandler()); + Assert.Same(ehRegion1, offsetMap[8].GetParentExceptionHandler()); + Assert.Null(offsetMap[9].GetParentExceptionHandler()); } [Fact] @@ -297,19 +301,20 @@ public void ExceptionHandlerWithPrologueAndEpilogue() }; var cfg = ConstructGraphWithEHRegions(instructions, ranges); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - var ehRegion = cfg.Nodes[1].GetParentExceptionHandler(); + var ehRegion = offsetMap[1].GetParentExceptionHandler(); var handlerRegion = Assert.Single(ehRegion.Handlers); Assert.NotNull(handlerRegion); Assert.NotNull(handlerRegion.Prologue); Assert.NotNull(handlerRegion.Epilogue); - Assert.Same(cfg, cfg.Nodes[0].ParentRegion); - Assert.Same(ehRegion.ProtectedRegion, cfg.Nodes[1].ParentRegion); - Assert.Same(handlerRegion.Prologue, cfg.Nodes[3].GetParentHandler().Prologue); - Assert.Same(handlerRegion.Contents, cfg.Nodes[5].GetParentHandler().Contents); - Assert.Same(handlerRegion.Epilogue, cfg.Nodes[7].GetParentHandler().Epilogue); - Assert.Same(cfg, cfg.Nodes[9].ParentRegion); + Assert.Same(cfg, offsetMap[0].ParentRegion); + Assert.Same(ehRegion.ProtectedRegion, offsetMap[1].ParentRegion); + Assert.Same(handlerRegion.Prologue, offsetMap[3].GetParentHandler().Prologue); + Assert.Same(handlerRegion.Contents, offsetMap[5].GetParentHandler().Contents); + Assert.Same(handlerRegion.Epilogue, offsetMap[7].GetParentHandler().Epilogue); + Assert.Same(cfg, offsetMap[9].ParentRegion); } } diff --git a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs index 73e794f3..3af37381 100644 --- a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs @@ -6,7 +6,6 @@ using Echo.ControlFlow.Regions; using Echo.ControlFlow.Regions.Detection; using Echo.ControlFlow.Serialization.Blocks; -using Echo.Code; using Echo.Platforms.DummyPlatform.Code; using Xunit; @@ -63,7 +62,7 @@ public void BasicRegionShouldTranslateToSingleScopeBlock() var region = new ScopeRegion(); cfg.Regions.Add(region); - region.Nodes.Add(cfg.Nodes[2]); + region.Nodes.Add(cfg.Nodes.GetByOffset(2)); var rootScope = cfg.ConstructBlocks(); diff --git a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs index 3a948e07..9dc50b85 100644 --- a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using Echo.ControlFlow.Regions; using Echo.ControlFlow.Serialization.Blocks; -using Echo.ControlFlow.Serialization.Dot; -using Echo.Graphing.Serialization.Dot; using Echo.Platforms.DummyPlatform; using Xunit; @@ -18,31 +15,33 @@ private static ControlFlowGraph GenerateGraph(int nodeCount) var cfg = new ControlFlowGraph(IntArchitecture.Instance); for (int i = 0; i < nodeCount; i++) cfg.Nodes.Add(new ControlFlowNode(i, i)); - cfg.EntryPoint = cfg.Nodes[0]; + cfg.EntryPoint = cfg.Nodes.GetByOffset(0); + cfg.Nodes.UpdateOffsets(); return cfg; } private static void AssertHasSubSequence(ControlFlowNode[] ordering, params int[] subSequence) { - var cfg = ordering[0].ParentGraph; - int index = Array.IndexOf(ordering, cfg.Nodes[subSequence[0]]); + var cfg = ordering[0].ParentGraph!; + int index = Array.IndexOf(ordering, cfg.Nodes.GetByOffset(subSequence[0])); Assert.NotEqual(-1, index); for (int i = 0; i < subSequence.Length; i++) - Assert.Equal(cfg.Nodes[subSequence[i]], ordering[i + index]); + Assert.Equal(cfg.Nodes.GetByOffset(subSequence[i]), ordering[i + index]); } private static void AssertHasCluster(ControlFlowNode[] ordering, params int[] cluster) { var expected = new HashSet>(); - var cfg = ordering[0].ParentGraph; + var cfg = ordering[0].ParentGraph!; + var offsetMap = cfg.Nodes.CreateOffsetMap(); int minIndex = int.MaxValue; int maxIndex = int.MinValue; foreach (int id in cluster) { - var node = cfg.Nodes[id]; + var node = offsetMap[id]; expected.Add(node); int index = Array.IndexOf(ordering, node); @@ -63,9 +62,11 @@ private static void AssertHasCluster(ControlFlowNode[] ordering, params int public void SequenceShouldStartWithEntrypointNode(long entrypoint) { var cfg = GenerateGraph(2); - cfg.EntryPoint = cfg.Nodes[entrypoint]; - cfg.Nodes[0].ConnectWith(cfg.Nodes[1], ControlFlowEdgeType.Unconditional); - cfg.Nodes[1].ConnectWith(cfg.Nodes[0], ControlFlowEdgeType.Unconditional); + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + cfg.EntryPoint = offsetMap[entrypoint]; + offsetMap[0].ConnectWith(offsetMap[1], ControlFlowEdgeType.Unconditional); + offsetMap[1].ConnectWith(offsetMap[0], ControlFlowEdgeType.Unconditional); var sorting = cfg .SortNodes() @@ -78,19 +79,21 @@ public void SequenceShouldStartWithEntrypointNode(long entrypoint) public void FallThroughEdgesShouldStickTogether() { var cfg = GenerateGraph(8); - cfg.Nodes[0].ConnectWith(cfg.Nodes[1], ControlFlowEdgeType.FallThrough); - cfg.Nodes[1].ConnectWith(cfg.Nodes[6], ControlFlowEdgeType.Unconditional); + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + offsetMap[0].ConnectWith(offsetMap[1], ControlFlowEdgeType.FallThrough); + offsetMap[1].ConnectWith(offsetMap[6], ControlFlowEdgeType.Unconditional); - cfg.Nodes[6].ConnectWith(cfg.Nodes[2], ControlFlowEdgeType.Conditional); - cfg.Nodes[6].ConnectWith(cfg.Nodes[7], ControlFlowEdgeType.FallThrough); + offsetMap[6].ConnectWith(offsetMap[2], ControlFlowEdgeType.Conditional); + offsetMap[6].ConnectWith(offsetMap[7], ControlFlowEdgeType.FallThrough); - cfg.Nodes[2].ConnectWith(cfg.Nodes[3], ControlFlowEdgeType.FallThrough); - cfg.Nodes[2].ConnectWith(cfg.Nodes[4], ControlFlowEdgeType.Conditional); + offsetMap[2].ConnectWith(offsetMap[3], ControlFlowEdgeType.FallThrough); + offsetMap[2].ConnectWith(offsetMap[4], ControlFlowEdgeType.Conditional); - cfg.Nodes[3].ConnectWith(cfg.Nodes[5], ControlFlowEdgeType.Unconditional); - cfg.Nodes[4].ConnectWith(cfg.Nodes[5], ControlFlowEdgeType.FallThrough); + offsetMap[3].ConnectWith(offsetMap[5], ControlFlowEdgeType.Unconditional); + offsetMap[4].ConnectWith(offsetMap[5], ControlFlowEdgeType.FallThrough); - cfg.Nodes[5].ConnectWith(cfg.Nodes[6], ControlFlowEdgeType.FallThrough); + offsetMap[5].ConnectWith(offsetMap[6], ControlFlowEdgeType.FallThrough); var sorting = cfg .SortNodes() @@ -105,8 +108,10 @@ public void FallThroughEdgesShouldStickTogether() public void MultipleIncomingFallThroughEdgesShouldThrow() { var cfg = GenerateGraph(3); - cfg.Nodes[0].ConnectWith(cfg.Nodes[2], ControlFlowEdgeType.FallThrough); - cfg.Nodes[1].ConnectWith(cfg.Nodes[2], ControlFlowEdgeType.FallThrough); + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + offsetMap[0].ConnectWith(offsetMap[2], ControlFlowEdgeType.FallThrough); + offsetMap[1].ConnectWith(offsetMap[2], ControlFlowEdgeType.FallThrough); Assert.Throws(() => cfg.SortNodes()); } @@ -118,12 +123,13 @@ public void MultipleIncomingFallThroughEdgesShouldThrow() public void PreferExitPointsLastInDoLoop(long[] indices) { var cfg = GenerateGraph(4); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.Conditional); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + offsetMap[indices[0]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[2]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[3]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.Conditional); + cfg.EntryPoint = offsetMap[indices[0]]; var sorting = cfg .SortNodes() @@ -142,13 +148,14 @@ public void PreferExitPointsLastInDoLoop(long[] indices) public void PreferExitPointsLastInWhileLoop(long[] indices) { var cfg = GenerateGraph(4); + var offsetMap = cfg.Nodes.CreateOffsetMap(); - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.Unconditional); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.Conditional); + offsetMap[indices[0]].ConnectWith(offsetMap[indices[2]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[2]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[3]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.Conditional); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = offsetMap[indices[0]]; var sorting = cfg .SortNodes() @@ -167,35 +174,36 @@ public void PreferExitPointsLastInWhileLoop(long[] indices) public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) { var cfg = GenerateGraph(7); + var offsetMap = cfg.Nodes.CreateOffsetMap(); // pre - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[0]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.FallThrough); // protected region. - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.Conditional); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[4]], ControlFlowEdgeType.Unconditional); - cfg.Nodes[indices[3]].ConnectWith(cfg.Nodes[indices[4]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[2]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[3]], ControlFlowEdgeType.Conditional); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[4]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[3]].ConnectWith(offsetMap[indices[4]], ControlFlowEdgeType.FallThrough); // handler region. - cfg.Nodes[indices[5]].ConnectWith(cfg.Nodes[indices[6]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[5]].ConnectWith(offsetMap[indices[6]], ControlFlowEdgeType.Unconditional); // post - cfg.Nodes[indices[4]].ConnectWith(cfg.Nodes[indices[6]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[4]].ConnectWith(offsetMap[indices[6]], ControlFlowEdgeType.Unconditional); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = offsetMap[indices[0]]; // Set up regions. var ehRegion = new ExceptionHandlerRegion(); cfg.Regions.Add(ehRegion); ehRegion.ProtectedRegion.Nodes.AddRange(new[] { - cfg.Nodes[indices[1]], cfg.Nodes[indices[2]], cfg.Nodes[indices[3]], cfg.Nodes[indices[4]] + offsetMap[indices[1]], offsetMap[indices[2]], offsetMap[indices[3]], offsetMap[indices[4]] }); var handlerRegion = new HandlerRegion(); ehRegion.Handlers.Add(handlerRegion); - handlerRegion.Contents.Nodes.Add(cfg.Nodes[indices[5]]); - handlerRegion.Contents.EntryPoint = cfg.Nodes[indices[5]]; + handlerRegion.Contents.Nodes.Add(offsetMap[indices[5]]); + handlerRegion.Contents.EntryPoint = offsetMap[indices[5]]; // Sort var sorting = cfg @@ -206,7 +214,7 @@ public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) AssertHasSubSequence(sorting, indices[0], indices[1], indices[2]); AssertHasSubSequence(sorting, indices[3], indices[4]); AssertHasCluster(sorting, indices[1], indices[2], indices[3], indices[4]); - Assert.True(Array.IndexOf(sorting, cfg.Nodes[indices[1]]) < Array.IndexOf(sorting, cfg.Nodes[indices[5]]), + Assert.True(Array.IndexOf(sorting, offsetMap[indices[1]]) < Array.IndexOf(sorting, offsetMap[indices[5]]), "Handler region was ordered before protected region."); } @@ -217,27 +225,29 @@ public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) public void HandlerRegionWithNoExitShouldBeOrderedBeforeNormalExit(int[] indices) { var cfg = GenerateGraph(4); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + cfg.EntryPoint = offsetMap[indices[0]]; - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.FallThrough); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.Unconditional); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[0]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.FallThrough); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[3]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[2]], ControlFlowEdgeType.Unconditional); // Set up regions. var ehRegion = new ExceptionHandlerRegion(); cfg.Regions.Add(ehRegion); - ehRegion.ProtectedRegion.Nodes.Add(cfg.Nodes[indices[1]]); + ehRegion.ProtectedRegion.Nodes.Add(offsetMap[indices[1]]); var handlerRegion = new HandlerRegion(); ehRegion.Handlers.Add(handlerRegion); - handlerRegion.Contents.Nodes.Add(cfg.Nodes[indices[2]]); - handlerRegion.Contents.EntryPoint = cfg.Nodes[indices[2]]; + handlerRegion.Contents.Nodes.Add(offsetMap[indices[2]]); + handlerRegion.Contents.EntryPoint = offsetMap[indices[2]]; // Sort var sorting = cfg .SortNodes() .ToArray(); - Assert.True(Array.IndexOf(sorting, cfg.Nodes[indices[2]]) < Array.IndexOf(sorting, cfg.Nodes[indices[3]]), + Assert.True(Array.IndexOf(sorting, offsetMap[indices[2]]) < Array.IndexOf(sorting, offsetMap[indices[3]]), "Handler region was ordered after normal exit."); } @@ -248,40 +258,42 @@ public void HandlerRegionWithNoExitShouldBeOrderedBeforeNormalExit(int[] indices public void PrologueAndEpilogueRegionsShouldHaveCorrectPrecedence(int[] indices) { var cfg = GenerateGraph(6); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + cfg.EntryPoint = offsetMap[indices[0]]; var eh = new ExceptionHandlerRegion(); cfg.Regions.Add(eh); - eh.ProtectedRegion.Nodes.Add(cfg.Nodes[indices[1]]); - eh.ProtectedRegion.EntryPoint = cfg.Nodes[indices[1]]; + eh.ProtectedRegion.Nodes.Add(offsetMap[indices[1]]); + eh.ProtectedRegion.EntryPoint = offsetMap[indices[1]]; var handler = new HandlerRegion(); eh.Handlers.Add(handler); handler.Prologue = new ScopeRegion(); - handler.Prologue.Nodes.Add(cfg.Nodes[indices[2]]); - handler.Prologue.EntryPoint = cfg.Nodes[indices[2]]; + handler.Prologue.Nodes.Add(offsetMap[indices[2]]); + handler.Prologue.EntryPoint = offsetMap[indices[2]]; - handler.Contents.Nodes.Add(cfg.Nodes[indices[3]]); - handler.Contents.EntryPoint = cfg.Nodes[indices[3]]; + handler.Contents.Nodes.Add(offsetMap[indices[3]]); + handler.Contents.EntryPoint = offsetMap[indices[3]]; handler.Epilogue = new ScopeRegion(); - handler.Epilogue.Nodes.Add(cfg.Nodes[indices[4]]); - handler.Epilogue.EntryPoint = cfg.Nodes[indices[4]]; + handler.Epilogue.Nodes.Add(offsetMap[indices[4]]); + handler.Epilogue.EntryPoint = offsetMap[indices[4]]; - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]]); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[5]], ControlFlowEdgeType.Unconditional); + offsetMap[indices[0]].ConnectWith(offsetMap[indices[1]]); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[5]], ControlFlowEdgeType.Unconditional); var sorted = cfg.SortNodes(); Assert.Equal(new[] { - cfg.Nodes[indices[0]], // start - cfg.Nodes[indices[1]], // protected - cfg.Nodes[indices[2]], // prologue - cfg.Nodes[indices[3]], // handler - cfg.Nodes[indices[4]], // epilogue - cfg.Nodes[indices[5]], // exit + offsetMap[indices[0]], // start + offsetMap[indices[1]], // protected + offsetMap[indices[2]], // prologue + offsetMap[indices[3]], // handler + offsetMap[indices[4]], // epilogue + offsetMap[indices[5]], // exit }, sorted); } @@ -292,39 +304,41 @@ public void PrologueAndEpilogueRegionsShouldHaveCorrectPrecedence(int[] indices) public void HandlerWithNoLeaveBranch(int[] indices) { var cfg = GenerateGraph(6); - cfg.EntryPoint = cfg.Nodes[indices[0]]; + var offsetMap = cfg.Nodes.CreateOffsetMap(); + + cfg.EntryPoint = offsetMap[indices[0]]; var eh = new ExceptionHandlerRegion(); cfg.Regions.Add(eh); eh.ProtectedRegion.Nodes.AddRange(new[] { - cfg.Nodes[indices[1]], - cfg.Nodes[indices[2]], - cfg.Nodes[indices[3]] + offsetMap[indices[1]], + offsetMap[indices[2]], + offsetMap[indices[3]] }); - eh.ProtectedRegion.EntryPoint = cfg.Nodes[indices[1]]; + eh.ProtectedRegion.EntryPoint = offsetMap[indices[1]]; var handler = new HandlerRegion(); eh.Handlers.Add(handler); - handler.Contents.Nodes.Add(cfg.Nodes[indices[4]]); - handler.Contents.EntryPoint = cfg.Nodes[indices[4]]; - - cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]]); - cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]]); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[3]]); - cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.Conditional); - cfg.Nodes[indices[3]].ConnectWith(cfg.Nodes[indices[5]], ControlFlowEdgeType.Unconditional); + handler.Contents.Nodes.Add(offsetMap[indices[4]]); + handler.Contents.EntryPoint = offsetMap[indices[4]]; + + offsetMap[indices[0]].ConnectWith(offsetMap[indices[1]]); + offsetMap[indices[1]].ConnectWith(offsetMap[indices[2]]); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[3]]); + offsetMap[indices[2]].ConnectWith(offsetMap[indices[1]], ControlFlowEdgeType.Conditional); + offsetMap[indices[3]].ConnectWith(offsetMap[indices[5]], ControlFlowEdgeType.Unconditional); var sorted = cfg.SortNodes(); Assert.Equal(new[] { - cfg.Nodes[indices[0]], - cfg.Nodes[indices[1]], // protected - cfg.Nodes[indices[2]], // protected - cfg.Nodes[indices[3]], // protected - cfg.Nodes[indices[4]], // handler - cfg.Nodes[indices[5]], + offsetMap[indices[0]], + offsetMap[indices[1]], // protected + offsetMap[indices[2]], // protected + offsetMap[indices[3]], // protected + offsetMap[indices[4]], // handler + offsetMap[indices[5]], }, sorted); } } diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs index e311ad89..5a703bb0 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs @@ -29,7 +29,7 @@ public void SingleBlock() var cfg = body.ConstructSymbolicFlowGraph(out _); Assert.Single(cfg.Nodes); - Assert.Equal(body.Instructions, cfg.Nodes[0].Contents.Instructions); + Assert.Equal(body.Instructions, cfg.Nodes.GetByOffset(0)!.Contents.Instructions); } [Fact] @@ -40,7 +40,7 @@ public void If() var body = method.CilMethodBody!; var cfg = body.ConstructSymbolicFlowGraph(out var dfg); - Assert.Single(cfg.EntryPoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint!.ConditionalEdges); var ldstrAdult = FindLdstr("Adult"); var ldstrChild = FindLdstr("Child"); @@ -99,7 +99,7 @@ public void JmpShouldTerminate() var cfg = method.CilMethodBody.ConstructSymbolicFlowGraph(out _); var node = Assert.Single(cfg.Nodes); - Assert.Equal(CilOpCodes.Jmp, node.Contents.Footer.OpCode); + Assert.Equal(CilOpCodes.Jmp, node.Contents.Footer!.OpCode); } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs index 74434695..7b6fe1fb 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs @@ -28,7 +28,7 @@ public void SingleBlock() var cfg = body.ConstructStaticFlowGraph(); Assert.Single(cfg.Nodes); - Assert.Equal(body.Instructions, cfg.Nodes[0].Contents.Instructions); + Assert.Equal(body.Instructions, cfg.Nodes.GetByOffset(0)!.Contents.Instructions); } [Fact] @@ -39,7 +39,7 @@ public void If() var body = method.CilMethodBody!; var cfg = body.ConstructStaticFlowGraph(); - Assert.Single(cfg.EntryPoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint!.ConditionalEdges); } [Fact] @@ -50,7 +50,7 @@ public void Switch() var body = method.CilMethodBody!; var cfg = body.ConstructStaticFlowGraph(); - Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint!.ConditionalEdges.Count); } [Fact] @@ -74,7 +74,7 @@ public void JmpShouldTerminate() var cfg = method.CilMethodBody.ConstructStaticFlowGraph(); var node = Assert.Single(cfg.Nodes); - Assert.Equal(CilOpCodes.Jmp, node.Contents.Footer.OpCode); + Assert.Equal(CilOpCodes.Jmp, node.Contents.Footer!.OpCode); } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs index bc60453e..e5484d89 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs @@ -25,7 +25,7 @@ public void SingleBlock() var cfg = method.ConstructSymbolicFlowGraph(out _); Assert.Single(cfg.Nodes); - Assert.Equal(method.Body.Instructions, cfg.Nodes[0].Contents.Instructions); + Assert.Equal(method.Body.Instructions, cfg.Nodes.GetByOffset(0)!.Contents.Instructions); } [Fact] @@ -35,7 +35,7 @@ public void If() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var cfg = method.ConstructSymbolicFlowGraph(out var dfg); - Assert.Single(cfg.EntryPoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint!.ConditionalEdges); var ldstrAdult = FindLdstr("Adult"); var ldstrChild = FindLdstr("Child"); @@ -80,7 +80,7 @@ public void Switch() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.SwitchColor)); var cfg = method.ConstructSymbolicFlowGraph(out _); - Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint!.ConditionalEdges.Count); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs index 168b99fb..fdb67a16 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs @@ -23,7 +23,7 @@ public void SingleBlock() var cfg = method.ConstructStaticFlowGraph(); Assert.Single(cfg.Nodes); - Assert.Equal(method.Body.Instructions, cfg.Nodes[0].Contents.Instructions); + Assert.Equal(method.Body.Instructions, cfg.Nodes.GetByOffset(0)!.Contents.Instructions); } [Fact] @@ -33,7 +33,7 @@ public void If() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var cfg = method.ConstructStaticFlowGraph(); - Assert.Single(cfg.EntryPoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint!.ConditionalEdges); } [Fact] @@ -43,7 +43,7 @@ public void Switch() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.SwitchColor)); var cfg = method.ConstructStaticFlowGraph(); - Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint!.ConditionalEdges.Count); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs b/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs index 36c4cf62..bbd5d3dd 100644 --- a/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs +++ b/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs @@ -58,16 +58,17 @@ public void ConditionalBranchesShouldResultInBlocksWithConditionalEdges() 0x5d, // pop ebp 0xc3 // ret }, 0); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.Equal(new long[] { 0x0, 0x7, 0xe, 0x11, 0x17, }.ToHashSet(), cfg.Nodes.Select(n => n.Offset).ToHashSet()); - Assert.Equal(cfg.Nodes[0x7], cfg.Nodes[0x0].UnconditionalNeighbour); - Assert.Equal(cfg.Nodes[0xE], cfg.Nodes[0x7].UnconditionalNeighbour); - Assert.Contains(cfg.Nodes[0x11], cfg.Nodes[0x7].ConditionalEdges.Select(e=>e.Target)); - Assert.Equal(cfg.Nodes[0x11], cfg.Nodes[0xE].UnconditionalNeighbour); - Assert.Contains(cfg.Nodes[0x7], cfg.Nodes[0x11].ConditionalEdges.Select(e=>e.Target)); + Assert.Equal(offsetMap[0x7], offsetMap[0x0].UnconditionalNeighbour); + Assert.Equal(offsetMap[0xE], offsetMap[0x7].UnconditionalNeighbour); + Assert.Contains(offsetMap[0x11], offsetMap[0x7].ConditionalEdges.Select(e=>e.Target)); + Assert.Equal(offsetMap[0x11], offsetMap[0xE].UnconditionalNeighbour); + Assert.Contains(offsetMap[0x7], offsetMap[0x11].ConditionalEdges.Select(e=>e.Target)); } [Fact] @@ -79,13 +80,14 @@ public void DisconnectedBlocksShouldSkipOverNotExecutableData() /* 2: */ 0xFF, // db 0xFF /* 3: */ 0xC3 // ret }, 0); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.Contains(cfg.Nodes, node => node.Offset == 0); Assert.DoesNotContain(cfg.Nodes, node => node.Offset == 2); Assert.Contains(cfg.Nodes, node => node.Offset == 3); - Assert.Single(cfg.Nodes[0].Contents.Instructions); - Assert.Single(cfg.Nodes[3].Contents.Instructions); + Assert.Single(offsetMap[0].Contents.Instructions); + Assert.Single(offsetMap[3].Contents.Instructions); } [Fact] @@ -118,17 +120,18 @@ public void RecursiveTraversalOfConditionalEdgeShouldTraverseBothPaths() /* 0x2D */ 0x5B, // pop ebx /* 0x2E */ 0xC3, // ret }, 0); + var offsetMap = cfg.Nodes.CreateOffsetMap(); Assert.Equal(new long[] { 0x0, 0xD, 0x14, 0x15, }.ToHashSet(), cfg.Nodes.Select(n => n.Offset).ToHashSet()); - Assert.Equal(cfg.Nodes[0xD], cfg.Nodes[0].UnconditionalNeighbour); - Assert.Equal(cfg.Nodes[0x15], cfg.Nodes[0xD].UnconditionalNeighbour); - Assert.Equal(cfg.Nodes[0x15], cfg.Nodes[0x14].UnconditionalNeighbour); + Assert.Equal(offsetMap[0xD], offsetMap[0].UnconditionalNeighbour); + Assert.Equal(offsetMap[0x15], offsetMap[0xD].UnconditionalNeighbour); + Assert.Equal(offsetMap[0x15], offsetMap[0x14].UnconditionalNeighbour); Assert.Contains( - cfg.Nodes[0x0].ConditionalEdges + offsetMap[0x0].ConditionalEdges .Select(e => e.Target), node => node.Offset == 0x14); } From 2c34b638a4b7ab3ee756e0c1d9ba36f811f3bb7c Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Jul 2024 21:27:25 +0200 Subject: [PATCH 2/9] Simplify flow graph building. Decouple ControlFlow from DataFlow. --- .../Echo.Ast/Analysis/ReadVariableFinder.cs | 13 +- .../Analysis/WrittenVariableFinder.cs | 13 +- src/Core/Echo.Ast/AstArchitecture.cs | 31 +-- ...raphBuilderBase.cs => FlowGraphBuilder.cs} | 2 +- .../{Static => }/IStaticSuccessorResolver.cs | 15 +- .../Static/StaticFlowGraphBuilder.cs | 191 ------------------ .../Construction/StaticFlowGraphBuilder.cs | 158 +++++++++++++++ .../Echo.ControlFlow/Echo.ControlFlow.csproj | 3 +- ...lowNodeCollection.cs => NodeCollection.cs} | 4 +- .../Construction}/IStateTransitioner.cs | 19 +- .../ISymbolicInstructionProvider.cs | 2 +- .../Construction}/StateTransition.cs | 5 +- .../Construction/StateTransitioner.cs} | 56 ++--- .../Construction}/StaticToSymbolicAdapter.cs | 2 +- .../Construction}/SymbolicFlowGraphBuilder.cs | 63 ++---- src/Core/Echo.DataFlow/DataFlowGraph.cs | 4 +- src/Core/Echo.DataFlow/Echo.DataFlow.csproj | 1 + src/Core/Echo/Code/IArchitecture.cs | 25 +-- .../AsmResolverExtensions.cs | 6 +- .../CilArchitecture.cs | 48 +---- .../CilStateTransitioner.cs | 99 +++------ .../CilStaticSuccessorResolver.cs | 105 +++------- .../Echo.Platforms.AsmResolver.csproj | 1 + .../Echo.Platforms.Dnlib/CilArchitecture.cs | 46 +---- .../CilStateTransitioner.cs | 172 ++++++++-------- .../CilStaticSuccessorResolver.cs | 114 +++++------ .../Echo.Platforms.Dnlib/DnlibExtensions.cs | 4 +- .../Echo.Platforms.Dnlib.csproj | 1 + .../Echo.Platforms.Iced.csproj | 1 + .../Echo.Platforms.Iced/X86Architecture.cs | 101 +-------- .../X86StateTransitioner.cs | 74 +++---- .../X86StaticSuccessorResolver.cs | 66 +++--- .../ControlFlowGraphLifterTest.cs | 22 +- .../Static/StaticGraphBuilderTest.cs | 2 +- .../Symbolic/SymbolicGraphBuilderTest.cs | 2 +- .../ExceptionHandlerRegionDetectionTest.cs | 1 - .../Serialization/Blocks/BlockBuilderTest.cs | 1 - .../CilArchitectureTest.cs | 6 +- .../CilArchitectureTest.cs | 5 +- .../StaticFlowGraphTest.cs | 1 - .../SymbolicFlowGraphTest.cs | 6 +- .../Code/DummyArchitecture.cs | 37 +--- .../DummyStaticSuccessorResolver.cs | 70 ++----- .../ControlFlow/DummyTransitioner.cs | 104 ++++------ .../IntArchitecture.cs | 15 +- .../X86StateTransitionResolverTest.cs | 2 +- .../X86StaticFlowGraphBuilderTest.cs | 1 - 47 files changed, 603 insertions(+), 1117 deletions(-) rename src/Core/Echo.ControlFlow/Construction/{FlowGraphBuilderBase.cs => FlowGraphBuilder.cs} (98%) rename src/Core/Echo.ControlFlow/Construction/{Static => }/IStaticSuccessorResolver.cs (76%) delete mode 100644 src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs create mode 100644 src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs rename src/Core/Echo.DataFlow/Collections/{DataFlowNodeCollection.cs => NodeCollection.cs} (97%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic => Echo.DataFlow/Construction}/IStateTransitioner.cs (63%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic => Echo.DataFlow/Construction}/ISymbolicInstructionProvider.cs (95%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic => Echo.DataFlow/Construction}/StateTransition.cs (94%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs => Echo.DataFlow/Construction/StateTransitioner.cs} (73%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic => Echo.DataFlow/Construction}/StaticToSymbolicAdapter.cs (97%) rename src/Core/{Echo.ControlFlow/Construction/Symbolic => Echo.DataFlow/Construction}/SymbolicFlowGraphBuilder.cs (82%) diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs index 601c1969..3955e9a0 100644 --- a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs @@ -24,18 +24,7 @@ public override void ExitVariableExpression(VariableExpression exp public override void ExitInstructionExpression(InstructionExpression expression) { - int count = _architecture.GetReadVariablesCount(expression.Instruction); - if (count == 0) - return; - - var variables = ArrayPool.Shared.Rent(count); - - int actualCount = _architecture.GetReadVariables(expression.Instruction, variables); - for (int i = 0; i < actualCount; i++) - Variables.Add(variables[i]); - - ArrayPool.Shared.Return(variables); - + _architecture.GetReadVariables(expression.Instruction, Variables); base.ExitInstructionExpression(expression); } } diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs index 43ee1b23..06b6b229 100644 --- a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs @@ -31,18 +31,7 @@ public override void ExitPhiStatement(PhiStatement phiStatement) public override void ExitInstructionExpression(InstructionExpression expression) { - int count = _architecture.GetWrittenVariablesCount(expression.Instruction); - if (count == 0) - return; - - var variables = ArrayPool.Shared.Rent(count); - - int actualCount = _architecture.GetWrittenVariables(expression.Instruction, variables); - for (int i = 0; i < actualCount; i++) - Variables.Add(variables[i]); - - ArrayPool.Shared.Return(variables); - + _architecture.GetWrittenVariables(expression.Instruction, Variables); base.ExitInstructionExpression(expression); } } diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index 135f98fe..5898fcce 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Echo.Ast.Analysis; using Echo.Code; @@ -44,45 +45,23 @@ public InstructionFlowControl GetFlowControl(in Statement instruct public int GetStackPopCount(in Statement instruction) => 0; /// - public int GetReadVariablesCount(in Statement instruction) - { - var finder = new ReadVariableFinder(_baseArchitecture); - AstNodeWalker.Walk(finder, instruction); - return finder.Variables.Count; - } - - /// - public int GetReadVariables(in Statement instruction, Span variablesBuffer) + public void GetReadVariables(in Statement instruction, ICollection variablesBuffer) { var finder = new ReadVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); - int i = 0; foreach (var variable in finder.Variables) - variablesBuffer[i++] = variable; - - return finder.Variables.Count; + variablesBuffer.Add(variable); } /// - public int GetWrittenVariablesCount(in Statement instruction) - { - var finder = new WrittenVariableFinder(_baseArchitecture); - AstNodeWalker.Walk(finder, instruction); - return finder.Variables.Count; - } - - /// - public int GetWrittenVariables(in Statement instruction, Span variablesBuffer) + public void GetWrittenVariables(in Statement instruction, ICollection variablesBuffer) { var finder = new WrittenVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); - int i = 0; foreach (var variable in finder.Variables) - variablesBuffer[i++] = variable; - - return finder.Variables.Count; + variablesBuffer.Add(variable); } } diff --git a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilder.cs similarity index 98% rename from src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs rename to src/Core/Echo.ControlFlow/Construction/FlowGraphBuilder.cs index 81f6438f..1eacf4d9 100644 --- a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs +++ b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilder.cs @@ -10,7 +10,7 @@ namespace Echo.ControlFlow.Construction /// Provides a base for control flow graph builders that depend on a single traversal of the instructions. /// /// The type of instructions to store in the control flow graph. - public abstract class FlowGraphBuilderBase : IFlowGraphBuilder + public abstract class FlowGraphBuilder : IFlowGraphBuilder where TInstruction : notnull { /// diff --git a/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs b/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs similarity index 76% rename from src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs rename to src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs index 832c6506..3d76d662 100644 --- a/src/Core/Echo.ControlFlow/Construction/Static/IStaticSuccessorResolver.cs +++ b/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs @@ -1,6 +1,7 @@ using System; +using System.Collections.Generic; -namespace Echo.ControlFlow.Construction.Static +namespace Echo.ControlFlow.Construction { /// /// Provides members for resolving the static successors of a single instruction. That is, resolve any successor @@ -40,19 +41,11 @@ namespace Echo.ControlFlow.Construction.Static public interface IStaticSuccessorResolver where TInstruction : notnull { - /// - /// Gets the number of successors of the provided instruction. - /// - /// The instruction to resolve the successors from. - /// The number of successors. - int GetSuccessorsCount(in TInstruction instruction); - /// /// Gets a collection of references that represent the successors of the provided instruction. /// /// The instruction to resolve the successors from. - /// The buffer to write the successors into. - /// The extracted successor references. - int GetSuccessors(in TInstruction instruction, Span successorsBuffer); + /// The buffer to add the successors into. + void GetSuccessors(in TInstruction instruction, IList successorsBuffer); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs deleted file mode 100644 index 01765514..00000000 --- a/src/Core/Echo.ControlFlow/Construction/Static/StaticFlowGraphBuilder.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using Echo.Code; - -namespace Echo.ControlFlow.Construction.Static -{ - /// - /// Provides an implementation of a control flow graph builder that traverses the instructions in a recursive manner, - /// and determines for each instruction the successors by looking at the general flow control of each instruction. - /// - /// The type of instructions to store in the control flow graph. - /// - /// This flow graph builder does not do any emulation or data flow analysis. Therefore, this flow - /// graph builder can only be reliably used when the instructions to graph do not contain any indirect branching - /// instructions. For example, if we target x86, the instruction jmp 12345678h is possible to process using - /// this graph builder, but jmp eax is not. - /// - public class StaticFlowGraphBuilder : FlowGraphBuilderBase - where TInstruction : notnull - { - /// - /// Creates a new static graph builder using the provided instruction successor resolver. - /// - /// The architecture of the instructions. - /// The instructions to traverse. - /// The object used to determine the successors of a single instruction. - /// Occurs when any of the arguments is null. - public StaticFlowGraphBuilder( - IArchitecture architecture, - IEnumerable instructions, - IStaticSuccessorResolver successorResolver) - { - Instructions = new ListInstructionProvider(architecture, instructions); - SuccessorResolver = successorResolver ?? throw new ArgumentNullException(nameof(successorResolver)); - } - - /// - /// Creates a new static graph builder using the provided instruction successor resolver. - /// - /// The instructions to traverse. - /// The object used to determine the successors of a single instruction. - /// Occurs when any of the arguments is null. - public StaticFlowGraphBuilder( - IStaticInstructionProvider instructions, - IStaticSuccessorResolver successorResolver) - { - Instructions = instructions ?? throw new ArgumentNullException(nameof(instructions)); - SuccessorResolver = successorResolver ?? throw new ArgumentNullException(nameof(successorResolver)); - } - - /// - /// Gets the instructions to traverse. - /// - public IStaticInstructionProvider Instructions - { - get; - } - - /// - public override IArchitecture Architecture => Instructions.Architecture; - - /// - /// Gets the object used to determine the successors of a single instruction. - /// - public IStaticSuccessorResolver SuccessorResolver - { - get; - } - - /// - protected override IInstructionTraversalResult CollectInstructions(long entrypoint, IEnumerable knownBlockHeaders) - { - var result = new InstructionTraversalResult(Architecture); - result.BlockHeaders.Add(entrypoint); - result.BlockHeaders.UnionWith(knownBlockHeaders); - - // Most instructions will have <= 2 successors. - // - Immediate fallthrough successor or unconditional branch target. - // - A single conditional branch target. - - // The only exception will be switch-like instructions. - // Therefore we start off by renting a buffer of at least two elements. - var successorsBufferPool = ArrayPool.Shared; - var successorsBuffer = successorsBufferPool.Rent(2); - - try - { - var visited = new HashSet(); - - // Start at the entrypoint and block headers. - var agenda = new Stack(); - foreach (var header in result.BlockHeaders) - agenda.Push(header); - - while (agenda.Count > 0) - { - // Get the current offset to process. - long currentOffset = agenda.Pop(); - - if (visited.Add(currentOffset)) - { - // Get the instruction at the provided offset, and figure out how many successors it has. - var instruction = Instructions.GetInstructionAtOffset(currentOffset); - int successorCount = GetSuccessors(successorsBufferPool, ref successorsBuffer, instruction); - - // Store collected data. - result.AddInstruction(instruction); - - // Figure out next offsets to process. - bool nextInstructionIsSuccessor = false; - int realSuccessorCount = 0; - for (int i = 0; i < successorCount; i++) - { - var successor = successorsBuffer[i]; - long destinationAddress = successor.DestinationAddress; - - if (!successor.IsRealEdge) - { - // Successor is implied by the instruction but does not necessarily - // transfer control to it directly. Only register the block header. - result.BlockHeaders.Add(destinationAddress); - agenda.Push(destinationAddress); - continue; - } - - realSuccessorCount++; - if (destinationAddress == currentOffset + Architecture.GetSize(instruction)) - { - // Successor is just the next instruction. - nextInstructionIsSuccessor = true; - } - else - { - // Successor is a jump to another address. This is a new basic block header! - result.BlockHeaders.Add(destinationAddress); - } - - result.RegisterSuccessor(instruction, successor); - agenda.Push(destinationAddress); - } - - // If we have multiple successors (e.g. as with an if-else construct), or the next instruction is - // not a successor (e.g. with a return address), the next instruction is another block header. - if (!nextInstructionIsSuccessor - || realSuccessorCount > 1 - || (Architecture.GetFlowControl(instruction) & InstructionFlowControl.CanBranch) != 0) - { - result.BlockHeaders.Add(currentOffset + Architecture.GetSize(instruction)); - } - - } - } - } - finally - { - successorsBufferPool.Return(successorsBuffer); - } - - return result; - } - - private int GetSuccessors( - ArrayPool arrayPool, - ref SuccessorInfo[] successorsBuffer, - in TInstruction instruction) - { - int successorCount = SuccessorResolver.GetSuccessorsCount(instruction); - - // Verify that our buffer has enough elements. - if (successorsBuffer.Length < successorCount) - { - arrayPool.Return(successorsBuffer); - successorsBuffer = arrayPool.Rent(successorCount); - } - - // Get successor information. - var successorsBufferSlice = new Span(successorsBuffer, 0, successorCount); - int actualSuccessorCount = SuccessorResolver.GetSuccessors(instruction, successorsBufferSlice); - if (actualSuccessorCount > successorCount) - { - // Sanity check: This should only happen if the successor resolver contains a bug. - throw new ArgumentException( - "The number of successors that was returned by the successor resolver is inconsistent " - + "with the number of actual written successors."); - } - - return actualSuccessorCount; - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs new file mode 100644 index 00000000..00335338 --- /dev/null +++ b/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs @@ -0,0 +1,158 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using Echo.Code; + +namespace Echo.ControlFlow.Construction +{ + /// + /// Provides an implementation of a control flow graph builder that traverses the instructions in a recursive manner, + /// and determines for each instruction the successors by looking at the general flow control of each instruction. + /// + /// The type of instructions to store in the control flow graph. + /// + /// This flow graph builder does not do any emulation or data flow analysis. Therefore, this flow + /// graph builder can only be reliably used when the instructions to graph do not contain any indirect branching + /// instructions. For example, if we target x86, the instruction jmp 12345678h is possible to process using + /// this graph builder, but jmp eax is not. + /// + public class StaticFlowGraphBuilder : FlowGraphBuilder + where TInstruction : notnull + { + /// + /// Creates a new static graph builder using the provided instruction successor resolver. + /// + /// The architecture of the instructions. + /// The instructions to traverse. + /// The object used to determine the successors of a single instruction. + /// Occurs when any of the arguments is null. + public StaticFlowGraphBuilder( + IArchitecture architecture, + IEnumerable instructions, + IStaticSuccessorResolver successorResolver) + { + Instructions = new ListInstructionProvider(architecture, instructions); + SuccessorResolver = successorResolver ?? throw new ArgumentNullException(nameof(successorResolver)); + } + + /// + /// Creates a new static graph builder using the provided instruction successor resolver. + /// + /// The instructions to traverse. + /// The object used to determine the successors of a single instruction. + /// Occurs when any of the arguments is null. + public StaticFlowGraphBuilder( + IStaticInstructionProvider instructions, + IStaticSuccessorResolver successorResolver) + { + Instructions = instructions ?? throw new ArgumentNullException(nameof(instructions)); + SuccessorResolver = successorResolver ?? throw new ArgumentNullException(nameof(successorResolver)); + } + + /// + /// Gets the instructions to traverse. + /// + public IStaticInstructionProvider Instructions + { + get; + } + + /// + public override IArchitecture Architecture => Instructions.Architecture; + + /// + /// Gets the object used to determine the successors of a single instruction. + /// + public IStaticSuccessorResolver SuccessorResolver + { + get; + } + + /// + protected override IInstructionTraversalResult CollectInstructions(long entrypoint, IEnumerable knownBlockHeaders) + { + var result = new InstructionTraversalResult(Architecture); + result.BlockHeaders.Add(entrypoint); + result.BlockHeaders.UnionWith(knownBlockHeaders); + + // Most instructions will have <= 2 successors. + // - Immediate fallthrough successor or unconditional branch target. + // - A single conditional branch target. + + // The only exception will be switch-like instructions. + // Therefore we start off by renting a buffer of at least two elements. + var successorsBuffer = new List(2); + + var visited = new HashSet(); + + // Start at the entrypoint and block headers. + var agenda = new Stack(); + foreach (var header in result.BlockHeaders) + agenda.Push(header); + + while (agenda.Count > 0) + { + // Get the current offset to process. + long currentOffset = agenda.Pop(); + + if (visited.Add(currentOffset)) + { + // Get the instruction at the provided offset. + var instruction = Instructions.GetInstructionAtOffset(currentOffset); + + // Determine successors. + successorsBuffer.Clear(); + SuccessorResolver.GetSuccessors(instruction, successorsBuffer); + + // Store collected data. + result.AddInstruction(instruction); + + // Figure out next offsets to process. + bool nextInstructionIsSuccessor = false; + int realSuccessorCount = 0; + for (int i = 0; i < successorsBuffer.Count; i++) + { + var successor = successorsBuffer[i]; + long destinationAddress = successor.DestinationAddress; + + if (!successor.IsRealEdge) + { + // Successor is implied by the instruction but does not necessarily + // transfer control to it directly. Only register the block header. + result.BlockHeaders.Add(destinationAddress); + agenda.Push(destinationAddress); + continue; + } + + realSuccessorCount++; + if (destinationAddress == currentOffset + Architecture.GetSize(instruction)) + { + // Successor is just the next instruction. + nextInstructionIsSuccessor = true; + } + else + { + // Successor is a jump to another address. This is a new basic block header! + result.BlockHeaders.Add(destinationAddress); + } + + result.RegisterSuccessor(instruction, successor); + agenda.Push(destinationAddress); + } + + // If we have multiple successors (e.g. as with an if-else construct), or the next instruction is + // not a successor (e.g. with a return address), the next instruction is another block header. + if (!nextInstructionIsSuccessor + || realSuccessorCount > 1 + || (Architecture.GetFlowControl(instruction) & InstructionFlowControl.CanBranch) != 0) + { + result.BlockHeaders.Add(currentOffset + Architecture.GetSize(instruction)); + } + + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj b/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj index f30f85bf..6c7fc1a4 100644 --- a/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj +++ b/src/Core/Echo.ControlFlow/Echo.ControlFlow.csproj @@ -21,7 +21,6 @@ - @@ -29,4 +28,4 @@ - + \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/Collections/DataFlowNodeCollection.cs b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs similarity index 97% rename from src/Core/Echo.DataFlow/Collections/DataFlowNodeCollection.cs rename to src/Core/Echo.DataFlow/Collections/NodeCollection.cs index 5936e41d..a3b7731f 100644 --- a/src/Core/Echo.DataFlow/Collections/DataFlowNodeCollection.cs +++ b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs @@ -11,12 +11,12 @@ namespace Echo.DataFlow.Collections /// /// The type of data that is stored in each node. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class DataFlowNodeCollection : ICollection> + public class NodeCollection : ICollection> { private readonly Dictionary> _nodes = new(); private readonly DataFlowGraph _owner; - internal DataFlowNodeCollection(DataFlowGraph owner) + internal NodeCollection(DataFlowGraph owner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs b/src/Core/Echo.DataFlow/Construction/IStateTransitioner.cs similarity index 63% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs rename to src/Core/Echo.DataFlow/Construction/IStateTransitioner.cs index 4646a7a4..8542c05f 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/IStateTransitioner.cs +++ b/src/Core/Echo.DataFlow/Construction/IStateTransitioner.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Provides members for resolving the next possible states of a program after the execution of an instruction. @@ -24,24 +25,16 @@ public interface IStateTransitioner /// The object representing the initial state of the program. SymbolicProgramState GetInitialState(long entrypointAddress); - /// - /// Gets the number of transitions the current program state might transition into. - /// - /// The current state of the program. - /// The instruction to evaluate. - /// The number of transitions that the provided instruction might apply. - int GetTransitionCount(in SymbolicProgramState currentState, in TInstruction instruction); - /// /// Resolves all possible program state transitions that the provided instruction can apply. /// /// The current state of the program. /// The instruction to evaluate. - /// The output buffer to write the transitions that the instruction might apply. - /// The number of transitions that were written into . - int GetTransitions( + /// The output buffer to add the transitions that the instruction might apply. + void GetTransitions( in SymbolicProgramState currentState, in TInstruction instruction, - Span> transitionBuffer); + IList> transitionsBuffer + ); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs b/src/Core/Echo.DataFlow/Construction/ISymbolicInstructionProvider.cs similarity index 95% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs rename to src/Core/Echo.DataFlow/Construction/ISymbolicInstructionProvider.cs index 45e715ce..0329a125 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/ISymbolicInstructionProvider.cs +++ b/src/Core/Echo.DataFlow/Construction/ISymbolicInstructionProvider.cs @@ -1,7 +1,7 @@ using Echo.Code; using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Provides members for obtaining instructions based on the current state of a program. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs b/src/Core/Echo.DataFlow/Construction/StateTransition.cs similarity index 94% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs rename to src/Core/Echo.DataFlow/Construction/StateTransition.cs index ce476b97..6030c909 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransition.cs +++ b/src/Core/Echo.DataFlow/Construction/StateTransition.cs @@ -1,6 +1,7 @@ -using Echo.DataFlow.Emulation; +using Echo.ControlFlow; +using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Represents an object that encodes the transition from one program state to another after an instruction was executed. diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs b/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs similarity index 73% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs rename to src/Core/Echo.DataFlow/Construction/StateTransitioner.cs index 7a13446e..6d523af4 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StateTransitionerBase.cs +++ b/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs @@ -1,26 +1,26 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using Echo.Code; -using Echo.DataFlow; using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Provides a base implementation for a state transition resolver, that maintains a data flow graph (DFG) for /// resolving each program state transition an instruction might apply. /// /// The type of instructions to evaluate. - public abstract class StateTransitionerBase : IStateTransitioner + public abstract class StateTransitioner : IStateTransitioner where TInstruction : notnull { - private IVariable[] _variablesBuffer = new IVariable[1]; + private readonly List _variablesBuffer = new(); /// /// Initializes the base implementation of the state state transition resolver. /// /// The architecture that describes the instruction set. - public StateTransitionerBase(IArchitecture architecture) + public StateTransitioner(IArchitecture architecture) { Architecture = architecture ?? throw new ArgumentNullException(nameof(architecture)); DataFlowGraph = new DataFlowGraph(architecture); @@ -46,14 +46,11 @@ public DataFlowGraph DataFlowGraph public virtual SymbolicProgramState GetInitialState(long entrypointAddress) => new(entrypointAddress); /// - public abstract int GetTransitionCount( + public abstract void GetTransitions( in SymbolicProgramState currentState, - in TInstruction instruction); - - /// - public abstract int GetTransitions(in SymbolicProgramState currentState, - in TInstruction instruction, - Span> transitionBuffer); + in TInstruction instruction, + IList> transitionsBuffer + ); /// /// Applies the default fallthrough transition on a symbolic program state. @@ -114,22 +111,12 @@ private ImmutableDictionary> ApplyVariabl { var instruction = node.Contents; - // Ensure buffer is large enough. - int readCount = Architecture.GetReadVariablesCount(instruction); - int writtenCount = Architecture.GetWrittenVariablesCount(instruction); - - int bufferSize = Math.Max(readCount, writtenCount); - if (_variablesBuffer.Length < bufferSize) - _variablesBuffer = new IVariable[bufferSize]; - // Get read variables. - var variablesBufferSlice = _variablesBuffer.AsSpan(0, readCount); - int actualCount = Architecture.GetReadVariables(instruction, variablesBufferSlice); - if (actualCount > variablesBufferSlice.Length) - throw new ArgumentException("GetReadVariables returned a number of variables that is inconsistent."); + _variablesBuffer.Clear(); + Architecture.GetReadVariables(instruction, _variablesBuffer); // Register variable dependencies. - for (int i = 0; i < actualCount; i++) + for (int i = 0; i < _variablesBuffer.Count; i++) { var variable = _variablesBuffer[i]; if (variables.TryGetValue(variable, out var dataSources)) @@ -137,13 +124,11 @@ private ImmutableDictionary> ApplyVariabl } // Get written variables. - variablesBufferSlice = _variablesBuffer.AsSpan(0, writtenCount); - actualCount = Architecture.GetWrittenVariables(instruction, variablesBufferSlice); - if (actualCount > bufferSize) - throw new ArgumentException("GetWrittenVariables returned a number of variables that is inconsistent."); + _variablesBuffer.Clear(); + Architecture.GetWrittenVariables(instruction, _variablesBuffer); // Apply variable changes in program state. - for (int i = 0; i < actualCount; i++) + for (int i = 0; i < _variablesBuffer.Count; i++) { var variable = _variablesBuffer[i]; variables = variables.SetItem(variable, SymbolicValue.CreateVariableValue(node, variable)); @@ -177,16 +162,11 @@ protected DataFlowNode GetOrCreateDataFlowNode(TInstruction instru node.StackDependencies.Add(new StackDependency()); // Get read variables. - int variableReadCount = Architecture.GetReadVariablesCount(instruction); - if (_variablesBuffer.Length < variableReadCount) - _variablesBuffer = new IVariable[variableReadCount]; + _variablesBuffer.Clear(); + Architecture.GetReadVariables(instruction, _variablesBuffer); - int actualCount = Architecture.GetReadVariables(instruction, _variablesBuffer); - if (actualCount > variableReadCount) - throw new ArgumentException("GetReadVariables returned a number of variables that is inconsistent with GetReadVariablesCount."); - // Register (unknown) variable dependencies. - for (int i = 0; i < actualCount; i++) + for (int i = 0; i < _variablesBuffer.Count; i++) { var variable = _variablesBuffer[i]; if (!node.VariableDependencies.ContainsVariable(variable)) diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs b/src/Core/Echo.DataFlow/Construction/StaticToSymbolicAdapter.cs similarity index 97% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs rename to src/Core/Echo.DataFlow/Construction/StaticToSymbolicAdapter.cs index 23ae2032..e098a1bd 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/StaticToSymbolicAdapter.cs +++ b/src/Core/Echo.DataFlow/Construction/StaticToSymbolicAdapter.cs @@ -3,7 +3,7 @@ using Echo.Code; using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Provides an implementation of an adapter that maps a diff --git a/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs b/src/Core/Echo.DataFlow/Construction/SymbolicFlowGraphBuilder.cs similarity index 82% rename from src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs rename to src/Core/Echo.DataFlow/Construction/SymbolicFlowGraphBuilder.cs index f3104757..fac70956 100644 --- a/src/Core/Echo.ControlFlow/Construction/Symbolic/SymbolicFlowGraphBuilder.cs +++ b/src/Core/Echo.DataFlow/Construction/SymbolicFlowGraphBuilder.cs @@ -3,16 +3,17 @@ using System.Collections.Generic; using System.Linq; using Echo.Code; +using Echo.ControlFlow.Construction; using Echo.DataFlow.Emulation; -namespace Echo.ControlFlow.Construction.Symbolic +namespace Echo.DataFlow.Construction { /// /// Provides an implementation of a control flow graph builder that traverses the instructions in a recursive manner, /// and maintains an symbolic program state to determine all possible branch targets of any indirect branching instruction. /// /// The type of instructions to store in the control flow graph. - public class SymbolicFlowGraphBuilder : FlowGraphBuilderBase + public class SymbolicFlowGraphBuilder : FlowGraphBuilder where TInstruction : notnull { /// @@ -80,7 +81,7 @@ public IStateTransitioner StateTransitioner protected override IInstructionTraversalResult CollectInstructions( long entrypoint, IEnumerable knownBlockHeaders) { - using var context = new GraphBuilderContext(Architecture); + var context = new GraphBuilderContext(Architecture); long[] blockHeaders = knownBlockHeaders as long[] ?? knownBlockHeaders.ToArray(); // Perform traversal. @@ -156,24 +157,13 @@ private void ResolveAndScheduleSuccessors( { var result = context.Result; - // Get a buffer to write to. - int transitionCount = StateTransitioner.GetTransitionCount(currentState, instruction); - var transitionsBuffer = context.GetTransitionsBuffer(transitionCount); - // Read transitions. - var transitionsBufferSlice = transitionsBuffer.AsSpan(0, transitionCount); - int actualTransitionCount = StateTransitioner.GetTransitions(currentState, instruction, transitionsBufferSlice); - if (actualTransitionCount > transitionCount) - { - // Sanity check: This should only happen if the transition resolver contains a bug. - throw new ArgumentException( - "The number of transitions that was returned by the transition resolver is inconsistent " - + "with the number of actual written transitions."); - } + context.TransitionsBuffer.Clear(); + StateTransitioner.GetTransitions(currentState, instruction, context.TransitionsBuffer); - for (int i = 0; i < actualTransitionCount; i++) + for (int i = 0; i < context.TransitionsBuffer.Count; i++) { - var transition = transitionsBufferSlice[i]; + var transition = context.TransitionsBuffer[i]; if (transition.IsRealEdge) { @@ -243,48 +233,19 @@ private void DetermineBlockHeaders(in GraphBuilderContext context) } } - private sealed class GraphBuilderContext : IDisposable + private sealed class GraphBuilderContext { - private readonly ArrayPool> _transitionsBufferPool; - private StateTransition[] _transitionsBuffer; - internal GraphBuilderContext(IArchitecture architecture) { Result = new InstructionTraversalResult(architecture); RecordedStates = new Dictionary>(); - - _transitionsBufferPool = ArrayPool>.Shared; - - // Most common case is at most 2 transitions per instruction. - _transitionsBuffer = _transitionsBufferPool.Rent(2); - } - - internal InstructionTraversalResult Result - { - get; } - internal IDictionary> RecordedStates - { - get; - } + internal InstructionTraversalResult Result { get; } - internal StateTransition[] GetTransitionsBuffer(int minimalSize) - { - if (_transitionsBuffer.Length < minimalSize) - { - _transitionsBufferPool.Return(_transitionsBuffer); - _transitionsBuffer = _transitionsBufferPool.Rent(minimalSize); - } + internal IDictionary> RecordedStates { get; } - return _transitionsBuffer; - } - - public void Dispose() - { - _transitionsBufferPool.Return(_transitionsBuffer); - _transitionsBuffer = null!; - } + public List> TransitionsBuffer { get; } = new(2); } } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/DataFlowGraph.cs b/src/Core/Echo.DataFlow/DataFlowGraph.cs index e53f0f90..972f7a5e 100644 --- a/src/Core/Echo.DataFlow/DataFlowGraph.cs +++ b/src/Core/Echo.DataFlow/DataFlowGraph.cs @@ -23,7 +23,7 @@ public class DataFlowGraph : IGraph public DataFlowGraph(IArchitecture architecture) { Architecture = architecture ?? throw new ArgumentNullException(nameof(architecture)); - Nodes = new DataFlowNodeCollection(this); + Nodes = new NodeCollection(this); } /// @@ -37,7 +37,7 @@ public IArchitecture Architecture /// /// Gets a collection of nodes that are present in the graph. /// - public DataFlowNodeCollection Nodes + public NodeCollection Nodes { get; } diff --git a/src/Core/Echo.DataFlow/Echo.DataFlow.csproj b/src/Core/Echo.DataFlow/Echo.DataFlow.csproj index 8eb50921..c59fb477 100644 --- a/src/Core/Echo.DataFlow/Echo.DataFlow.csproj +++ b/src/Core/Echo.DataFlow/Echo.DataFlow.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Core/Echo/Code/IArchitecture.cs b/src/Core/Echo/Code/IArchitecture.cs index 7cd054d1..093c384a 100644 --- a/src/Core/Echo/Code/IArchitecture.cs +++ b/src/Core/Echo/Code/IArchitecture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Echo.Code { @@ -42,35 +43,21 @@ public interface IArchitecture /// The instruction to get the stack pop count from. /// The number of stack slots the instruction pops. int GetStackPopCount(in TInstruction instruction); - - /// - /// Gets the number of variables that the provided instruction reads from. - /// - /// The instruction to get the number of read variables from. - /// The number of variables. - int GetReadVariablesCount(in TInstruction instruction); - + /// /// Gets a collection of variables that an instruction reads from. /// /// The instruction to get the variables from. - /// The output buffer to write the read variables into. + /// The output buffer to add the read variables into. /// The number of variables that were written into . - int GetReadVariables(in TInstruction instruction, Span variablesBuffer); - - /// - /// Gets the number of variables that the provided instruction writes to. - /// - /// The instruction to get the number of written variables from. - /// The number of variables. - int GetWrittenVariablesCount(in TInstruction instruction); + void GetReadVariables(in TInstruction instruction, ICollection variablesBuffer); /// /// Gets a collection of variables that an instruction writes to. /// /// The instruction to get the variables from. - /// The output buffer to write the written variables into. + /// The output buffer to add the written variables into. /// The number of variables that were written into . - int GetWrittenVariables(in TInstruction instruction, Span variablesBuffer); + void GetWrittenVariables(in TInstruction instruction, ICollection variablesBuffer); } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/AsmResolverExtensions.cs b/src/Platforms/Echo.Platforms.AsmResolver/AsmResolverExtensions.cs index 42593826..36674c21 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/AsmResolverExtensions.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/AsmResolverExtensions.cs @@ -4,10 +4,9 @@ using AsmResolver.PE.DotNet.Cil; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; -using Echo.ControlFlow.Construction.Symbolic; using Echo.ControlFlow.Regions.Detection; using Echo.DataFlow; +using Echo.DataFlow.Construction; namespace Echo.Platforms.AsmResolver { @@ -84,7 +83,8 @@ public static ControlFlowGraph ConstructSymbolicFlowGraph( var cfgBuilder = new SymbolicFlowGraphBuilder( architecture, self.Instructions, - dfgBuilder); + dfgBuilder + ); var ehRanges = self.ExceptionHandlers .ToEchoRanges() diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilArchitecture.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilArchitecture.cs index f52b1deb..8a73627b 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilArchitecture.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilArchitecture.cs @@ -104,51 +104,21 @@ public InstructionFlowControl GetFlowControl(in CilInstruction instruction) public int GetStackPopCount(in CilInstruction instruction) => instruction.GetStackPopCount(MethodBody); /// - public int GetReadVariablesCount(in CilInstruction instruction) => - instruction.IsLdloc() || instruction.IsLdarg() - ? 1 - : 0; - - /// - public int GetReadVariables(in CilInstruction instruction, Span variablesBuffer) + public void GetReadVariables(in CilInstruction instruction, ICollection variablesBuffer) { if (instruction.IsLdloc()) - { - variablesBuffer[0] = GetLocal(instruction.GetLocalVariable(MethodBody.LocalVariables)); - return 1; - } - - if (instruction.IsLdarg()) - { - variablesBuffer[0] = GetParameter(instruction.GetParameter(MethodBody.Owner.Parameters)); - return 1; - } - - return 0; + variablesBuffer.Add(GetLocal(instruction.GetLocalVariable(MethodBody.LocalVariables))); + else if (instruction.IsLdarg()) + variablesBuffer.Add(GetParameter(instruction.GetParameter(MethodBody.Owner.Parameters))); } /// - public int GetWrittenVariablesCount(in CilInstruction instruction) => - instruction.IsStloc() || instruction.IsStarg() - ? 1 - : 0; - - /// - public int GetWrittenVariables(in CilInstruction instruction, Span variablesBuffer) - { + public void GetWrittenVariables(in CilInstruction instruction, ICollection variablesBuffer) + { if (instruction.IsStloc()) - { - variablesBuffer[0] = GetLocal(instruction.GetLocalVariable(MethodBody.LocalVariables)); - return 1; - } - - if (instruction.IsStarg()) - { - variablesBuffer[0] = GetParameter(instruction.GetParameter(MethodBody.Owner.Parameters)); - return 1; - } - - return 0; + variablesBuffer.Add(GetLocal(instruction.GetLocalVariable(MethodBody.LocalVariables))); + else if (instruction.IsStarg()) + variablesBuffer.Add(GetParameter(instruction.GetParameter(MethodBody.Owner.Parameters))); } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs index 7b8c018c..fca7dcd1 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs @@ -3,8 +3,8 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.PE.DotNet.Cil; using Echo.ControlFlow; -using Echo.ControlFlow.Construction.Symbolic; using Echo.DataFlow; +using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; namespace Echo.Platforms.AsmResolver @@ -12,7 +12,7 @@ namespace Echo.Platforms.AsmResolver /// /// Provides an implementation of a state transition resolver for the CIL instruction set. /// - public class CilStateTransitioner : StateTransitionerBase + public class CilStateTransitioner : StateTransitioner { private readonly CilArchitecture _architecture; @@ -68,45 +68,10 @@ public override SymbolicProgramState GetInitialState(long entryp } /// - public override int GetTransitionCount( - in SymbolicProgramState currentState, - in CilInstruction instruction) - { - switch (instruction.OpCode.FlowControl) - { - case CilFlowControl.Call when instruction.OpCode.Code == CilCode.Jmp: - return 0; - - case CilFlowControl.Call: - case CilFlowControl.Meta: - case CilFlowControl.Next: - case CilFlowControl.Break: - case CilFlowControl.Branch: - return 1; - - case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch: - return ((ICollection) instruction.Operand!).Count + 1; - - case CilFlowControl.ConditionalBranch: - return 2; - - case CilFlowControl.Return: - case CilFlowControl.Throw: - return 0; - - case CilFlowControl.Phi: - throw new NotSupportedException(); - - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - public override int GetTransitions( - in SymbolicProgramState currentState, - in CilInstruction instruction, - Span> transitionBuffer) + public override void GetTransitions( + in SymbolicProgramState currentState, + in CilInstruction instruction, + IList> transitionsBuffer) { // Multiplex based on flow control. @@ -115,19 +80,23 @@ public override int GetTransitions( case CilFlowControl.Call when instruction.OpCode.Code == CilCode.Jmp: case CilFlowControl.Return: case CilFlowControl.Throw: - return ProcessTerminatingTransition(currentState, instruction); + Terminate(currentState, instruction); + break; case CilFlowControl.Branch: - return GetUnconditionalBranchTransitions(currentState, instruction, transitionBuffer); + UnconditionalBranch(currentState, instruction, transitionsBuffer); + break; case CilFlowControl.ConditionalBranch: - return GetConditionalBranchTransitions(currentState, instruction, transitionBuffer); + ConditionalBranch(currentState, instruction, transitionsBuffer); + break; case CilFlowControl.Call: case CilFlowControl.Meta: case CilFlowControl.Next: case CilFlowControl.Break: - return GetFallthroughTransitions(currentState, instruction, transitionBuffer); + FallThrough(currentState, instruction, transitionsBuffer); + break; case CilFlowControl.Phi: throw new NotSupportedException(); @@ -137,47 +106,44 @@ public override int GetTransitions( } } - private int ProcessTerminatingTransition(in SymbolicProgramState currentState, in CilInstruction instruction) + private void Terminate(in SymbolicProgramState currentState, in CilInstruction instruction) { // Note: we still perform the transition, to record the final dependencies that a throw or a ret might have. ApplyDefaultBehaviour(currentState, instruction); - return 0; } - private int GetFallthroughTransitions( + private void FallThrough( in SymbolicProgramState currentState, CilInstruction instruction, - Span> successorBuffer) + IList> transitionsBuffer) { // Fallthrough instructions just transform the state normally. var nextState = ApplyDefaultBehaviour(currentState, instruction); - successorBuffer[0] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return 1; + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - private int GetUnconditionalBranchTransitions( + private void UnconditionalBranch( in SymbolicProgramState currentState, CilInstruction instruction, - Span> successorBuffer) - { + IList> transitionsBuffer) + { // Unconditional branches are similar to normal fallthrough, except they change the program counter. var nextState = ApplyDefaultBehaviour(currentState, instruction) .WithProgramCounter(((ICilLabel) instruction.Operand!).Offset); - successorBuffer[0] = new StateTransition(nextState, ControlFlowEdgeType.Unconditional); - return 1; + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.Unconditional)); } - private int GetConditionalBranchTransitions( + private void ConditionalBranch( in SymbolicProgramState currentState, CilInstruction instruction, - Span> successorBuffer) + IList> transitionsBuffer) { // Conditional branches result in multiple possible transitions that could happen. var baseNextState = ApplyDefaultBehaviour(currentState, instruction); // Define the transition if the branch was not taken. (this is a normal fall through transition). - successorBuffer[0] = new StateTransition(baseNextState, ControlFlowEdgeType.FallThrough); + transitionsBuffer.Add(new StateTransition(baseNextState, ControlFlowEdgeType.FallThrough)); // CIL conditional branches can have a single target or multiple targets. Fork the next state, and change // the program counters to their branch targets. @@ -186,19 +152,20 @@ private int GetConditionalBranchTransitions( { case ICilLabel singleTarget: var branchState = baseNextState.WithProgramCounter(singleTarget.Offset); - successorBuffer[1] = new StateTransition(branchState, ControlFlowEdgeType.Conditional); - return 2; + transitionsBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Conditional)); + break; case IList multipleTargets: for (int i = 0; i < multipleTargets.Count; i++) { var nextBranchState = baseNextState.WithProgramCounter(multipleTargets[i].Offset); - successorBuffer[i + 1] = new StateTransition( - nextBranchState, - ControlFlowEdgeType.Conditional); + transitionsBuffer.Add(new StateTransition( + nextBranchState, + ControlFlowEdgeType.Conditional + )); } - - return multipleTargets.Count + 1; + + break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilStaticSuccessorResolver.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilStaticSuccessorResolver.cs index 23ee571b..0a044a2e 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilStaticSuccessorResolver.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilStaticSuccessorResolver.cs @@ -3,7 +3,6 @@ using AsmResolver.PE.DotNet.Cil; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; namespace Echo.Platforms.AsmResolver { @@ -15,123 +14,79 @@ public class CilStaticSuccessorResolver : IStaticSuccessorResolver /// Gets a reusable singleton instance of the static successor resolver for the CIL architecture. /// - public static CilStaticSuccessorResolver Instance - { - get; - } = new(); - + public static CilStaticSuccessorResolver Instance { get; } = new(); + /// - public int GetSuccessorsCount(in CilInstruction instruction) + public void GetSuccessors(in CilInstruction instruction, IList successorsBuffer) { + // Multiplex based on flow control. switch (instruction.OpCode.FlowControl) { - case CilFlowControl.Call when instruction.OpCode.Code == CilCode.Jmp: - return 0; + case CilFlowControl.Break or CilFlowControl.Meta or CilFlowControl.Next or CilFlowControl.Call: + if (instruction.OpCode.Code != CilCode.Jmp) + AddFallThrough(instruction, successorsBuffer); + break; - case CilFlowControl.Break: - case CilFlowControl.Meta: - case CilFlowControl.Next: case CilFlowControl.Branch: - case CilFlowControl.Call: - return 1; - - case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch: - return ((ICollection) instruction.Operand!).Count + 1; + AddUnconditionalBranch(instruction, successorsBuffer); + break; case CilFlowControl.ConditionalBranch: - return 2; + AddConditionalBranches(instruction, successorsBuffer); + break; - case CilFlowControl.Phi: - throw new NotSupportedException(); - - case CilFlowControl.Return: - case CilFlowControl.Throw: - return 0; - - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - public int GetSuccessors(in CilInstruction instruction, Span successorsBuffer) - { - // Multiplex based on flow control. - - switch (instruction.OpCode.FlowControl) - { - case CilFlowControl.Break: - case CilFlowControl.Meta: - case CilFlowControl.Next: - return GetFallThroughTransitions(instruction, successorsBuffer); + case CilFlowControl.Return or CilFlowControl.Throw: + return; - case CilFlowControl.Call: - return instruction.OpCode.Code != CilCode.Jmp - ? GetFallThroughTransitions(instruction, successorsBuffer) - : 0; - - case CilFlowControl.Branch: - return GetUnconditionalBranchTransitions(instruction, successorsBuffer); - - case CilFlowControl.ConditionalBranch: - return GetConditionalBranchTransitions(instruction, successorsBuffer); - case CilFlowControl.Phi: throw new NotSupportedException(); - - case CilFlowControl.Return: - case CilFlowControl.Throw: - return 0; - + default: throw new ArgumentOutOfRangeException(); } } - private static int GetFallThroughTransitions(CilInstruction instruction, Span successorsBuffer) + private static void AddFallThrough(CilInstruction instruction, IList successorsBuffer) { - // Fallthrough instructions always move to the next instruction. - successorsBuffer[0] = FallThrough(instruction); - return 1; + successorsBuffer.Add(FallThrough(instruction)); } - private static int GetUnconditionalBranchTransitions(CilInstruction instruction, Span successorsBuffer) + private static void AddUnconditionalBranch(CilInstruction instruction, IList successorsBuffer) { // Unconditional branches always move to the instruction referenced in the operand. var label = (ICilLabel) instruction.Operand!; - successorsBuffer[0] = new SuccessorInfo(label.Offset, ControlFlowEdgeType.Unconditional); - return 1; + successorsBuffer.Add(new SuccessorInfo(label.Offset, ControlFlowEdgeType.Unconditional)); } - private static int GetConditionalBranchTransitions(CilInstruction instruction, Span successorsBuffer) + private static void AddConditionalBranches(CilInstruction instruction, IList successorsBuffer) { // Conditional branches can reference one or more instructions in the operand. switch (instruction.Operand) { case ICilLabel singleTarget: - successorsBuffer[0] = Conditional(singleTarget); - successorsBuffer[1] = FallThrough(instruction); - return 2; + successorsBuffer.Add(Conditional(singleTarget)); + successorsBuffer.Add(FallThrough(instruction)); + break; case IList multipleTargets: for (int i = 0; i < multipleTargets.Count; i++) - successorsBuffer[i] = Conditional(multipleTargets[i]); - successorsBuffer[multipleTargets.Count] = FallThrough(instruction); - return multipleTargets.Count + 1; + successorsBuffer.Add(Conditional(multipleTargets[i])); + successorsBuffer.Add(FallThrough(instruction)); + break; default: throw new ArgumentOutOfRangeException(); } } - private static SuccessorInfo FallThrough(CilInstruction instruction) + private static SuccessorInfo Conditional(ICilLabel singleTarget) { - return new SuccessorInfo(instruction.Offset + instruction.Size, ControlFlowEdgeType.FallThrough); + return new SuccessorInfo(singleTarget.Offset, ControlFlowEdgeType.Conditional); } - private static SuccessorInfo Conditional(ICilLabel singleTarget) + private static SuccessorInfo FallThrough(CilInstruction instruction) { - return new SuccessorInfo(singleTarget.Offset, ControlFlowEdgeType.Conditional); + return new SuccessorInfo(instruction.Offset + instruction.Size, ControlFlowEdgeType.FallThrough); } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Echo.Platforms.AsmResolver.csproj b/src/Platforms/Echo.Platforms.AsmResolver/Echo.Platforms.AsmResolver.csproj index 10d48f75..3aa2cd43 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Echo.Platforms.AsmResolver.csproj +++ b/src/Platforms/Echo.Platforms.AsmResolver/Echo.Platforms.AsmResolver.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Platforms/Echo.Platforms.Dnlib/CilArchitecture.cs b/src/Platforms/Echo.Platforms.Dnlib/CilArchitecture.cs index 032abf77..44f7eef5 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/CilArchitecture.cs +++ b/src/Platforms/Echo.Platforms.Dnlib/CilArchitecture.cs @@ -98,51 +98,21 @@ public int GetStackPopCount(in Instruction instruction) } /// - public int GetReadVariablesCount(in Instruction instruction) => - instruction.IsLdloc() || instruction.IsLdarg() - ? 1 - : 0; - - /// - public int GetReadVariables(in Instruction instruction, Span variablesBuffer) + public void GetReadVariables(in Instruction instruction, ICollection variablesBuffer) { if (instruction.IsLdloc()) - { - variablesBuffer[0] = _variables[instruction.GetLocal(MethodBody.Variables).Index]; - return 1; - } - - if (instruction.IsLdarg()) - { - variablesBuffer[0] = _parameters[instruction.GetParameter(Method.Parameters).Index]; - return 1; - } - - return 0; + variablesBuffer.Add(_variables[instruction.GetLocal(MethodBody.Variables).Index]); + else if (instruction.IsLdarg()) + variablesBuffer.Add(_parameters[instruction.GetParameter(Method.Parameters).Index]); } - - /// - public int GetWrittenVariablesCount(in Instruction instruction) => - instruction.IsStloc() || instruction.IsStarg() - ? 1 - : 0; /// - public int GetWrittenVariables(in Instruction instruction, Span variablesBuffer) + public void GetWrittenVariables(in Instruction instruction, ICollection variablesBuffer) { if (instruction.IsStloc()) - { - variablesBuffer[0] = _variables[instruction.GetLocal(MethodBody.Variables).Index]; - return 1; - } - - if (instruction.IsStarg()) - { - variablesBuffer[0] = _parameters[instruction.GetParameter(Method.Parameters).Index]; - return 1; - } - - return 0; + variablesBuffer.Add(_variables[instruction.GetLocal(MethodBody.Variables).Index]); + else if (instruction.IsStarg()) + variablesBuffer.Add(_parameters[instruction.GetParameter(Method.Parameters).Index]); } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs b/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs index 568b307d..4fe1c483 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs +++ b/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; using dnlib.DotNet.Emit; using Echo.ControlFlow; -using Echo.ControlFlow.Construction.Symbolic; using Echo.DataFlow; +using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; using DnlibCode = dnlib.DotNet.Emit.Code; @@ -11,7 +12,7 @@ namespace Echo.Platforms.Dnlib /// /// Provides an implementation of a state transition resolver for the CIL instruction set. /// - public class CilStateTransitioner : StateTransitionerBase + public class CilStateTransitioner : StateTransitioner { private readonly CilArchitecture _architecture; @@ -19,7 +20,8 @@ public class CilStateTransitioner : StateTransitionerBase /// Creates a new instance of the class. /// /// The CIL architecture variant to compute state transitions for. - public CilStateTransitioner(CilArchitecture architecture) : base(architecture) + public CilStateTransitioner(CilArchitecture architecture) + : base(architecture) { _architecture = architecture; } @@ -39,23 +41,25 @@ public override SymbolicProgramState GetInitialState(long entrypoin } var exceptionSource = default(ExternalDataSourceNode); - if (handler.HandlerStart.Offset == entrypointAddress) + + if (handler.HandlerStart!.Offset == entrypointAddress) { exceptionSource = new ExternalDataSourceNode( - -handler.HandlerStart.Offset, + -(long) handler.HandlerStart.Offset, $"HandlerException_{handler.HandlerStart.Offset:X4}"); } else if (handler.FilterStart != null && handler.FilterStart.Offset == entrypointAddress) { exceptionSource = new ExternalDataSourceNode( - -handler.FilterStart.Offset, + -(long) handler.FilterStart.Offset, $"FilterException_{handler.FilterStart.Offset:X4}"); } if (exceptionSource is { }) { DataFlowGraph.Nodes.Add(exceptionSource); - result = result.Push(new SymbolicValue(new StackDataSource(exceptionSource))); + result = result.Push(new SymbolicValue( + new StackDataSource(exceptionSource))); break; } } @@ -64,111 +68,109 @@ public override SymbolicProgramState GetInitialState(long entrypoin } /// - public override int GetTransitionCount( - in SymbolicProgramState currentState, - in Instruction instruction) + public override void GetTransitions( + in SymbolicProgramState currentState, + in Instruction instruction, + IList> transitionsBuffer) { + // Multiplex based on flow control. + switch (instruction.OpCode.FlowControl) { case FlowControl.Call when instruction.OpCode.Code == DnlibCode.Jmp: - return 0; + case FlowControl.Return: + case FlowControl.Throw: + Terminate(currentState, instruction); + break; + + case FlowControl.Branch: + UnconditionalBranch(currentState, instruction, transitionsBuffer); + break; + + case FlowControl.Cond_Branch: + ConditionalBranch(currentState, instruction, transitionsBuffer); + break; case FlowControl.Call: case FlowControl.Meta: case FlowControl.Next: case FlowControl.Break: - case FlowControl.Branch: - return 1; - - case FlowControl.Cond_Branch when instruction.OpCode.Code == DnlibCode.Switch: - var targets = (Instruction[]) instruction.Operand; - return targets.Length + 1; - - case FlowControl.Cond_Branch: - return 2; + FallThrough(currentState, instruction, transitionsBuffer); + break; - case FlowControl.Return: - case FlowControl.Throw: - return 0; - case FlowControl.Phi: - throw new NotSupportedException("There are no known instructions with Phi control flow"); - + throw new NotSupportedException(); + default: throw new ArgumentOutOfRangeException(); } } - /// - public override int GetTransitions( - in SymbolicProgramState currentState, - in Instruction instruction, - Span> transitionBuffer) + private void Terminate(in SymbolicProgramState currentState, in Instruction instruction) { - // Multiplex based on flow control. + // Note: we still perform the transition, to record the final dependencies that a throw or a ret might have. + ApplyDefaultBehaviour(currentState, instruction); + } - switch (instruction.OpCode.FlowControl) - { - case FlowControl.Call when instruction.OpCode.Code == DnlibCode.Jmp: - case FlowControl.Return: - case FlowControl.Throw: - ApplyDefaultBehaviour(currentState, instruction); - return 0; - - case FlowControl.Call: - case FlowControl.Meta: - case FlowControl.Next: - case FlowControl.Break: - transitionBuffer[0] = FallThrough(currentState, instruction); - return 1; + private void FallThrough( + in SymbolicProgramState currentState, + Instruction instruction, + IList> transitionsBuffer) + { + // Fallthrough instructions just transform the state normally. + var nextState = ApplyDefaultBehaviour(currentState, instruction); + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); + } - case FlowControl.Branch: - transitionBuffer[0] = Branch(false, currentState, instruction); - return 1; + private void UnconditionalBranch( + in SymbolicProgramState currentState, + Instruction instruction, + IList> transitionsBuffer) + { + // Unconditional branches are similar to normal fallthrough, except they change the program counter. + var nextState = ApplyDefaultBehaviour(currentState, instruction) + .WithProgramCounter(((Instruction) instruction.Operand!).Offset); - case FlowControl.Cond_Branch when instruction.OpCode.Code == DnlibCode.Switch: - var targets = (Instruction[]) instruction.Operand; - for (int i = 0; i < targets.Length; i++) - { - var nextState = ApplyDefaultBehaviour(currentState, instruction) - .WithProgramCounter(targets[i].Offset); - transitionBuffer[i] = new StateTransition(nextState, ControlFlowEdgeType.Conditional); - } + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.Unconditional)); + } - transitionBuffer[targets.Length] = FallThrough(currentState, instruction); - return targets.Length + 1; + private void ConditionalBranch( + in SymbolicProgramState currentState, + Instruction instruction, + IList> transitionsBuffer) + { + // Conditional branches result in multiple possible transitions that could happen. + var baseNextState = ApplyDefaultBehaviour(currentState, instruction); - case FlowControl.Cond_Branch: - transitionBuffer[0] = Branch(true, currentState, instruction); - transitionBuffer[1] = FallThrough(currentState, instruction); - return 2; + // Define the transition if the branch was not taken. (this is a normal fall through transition). + transitionsBuffer.Add(new StateTransition(baseNextState, ControlFlowEdgeType.FallThrough)); - case FlowControl.Phi: - throw new NotSupportedException("There are no known instructions with Phi control flow"); + // CIL conditional branches can have a single target or multiple targets. Fork the next state, and change + // the program counters to their branch targets. + + switch (instruction.Operand) + { + case Instruction singleTarget: + var branchState = baseNextState.WithProgramCounter(singleTarget.Offset); + transitionsBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Conditional)); + break; + + case IList multipleTargets: + for (int i = 0; i < multipleTargets.Count; i++) + { + var nextBranchState = baseNextState.WithProgramCounter(multipleTargets[i].Offset); + transitionsBuffer.Add(new StateTransition( + nextBranchState, + ControlFlowEdgeType.Conditional + )); + } + break; + default: throw new ArgumentOutOfRangeException(); } - } - - private StateTransition FallThrough( - in SymbolicProgramState currentState, - Instruction instruction) - { - var nextState = ApplyDefaultBehaviour(currentState, instruction); - return new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - } - - private StateTransition Branch( - bool conditional, - in SymbolicProgramState currentState, - Instruction instruction) - { - var nextState = ApplyDefaultBehaviour(currentState, instruction) - .WithProgramCounter(((Instruction) instruction.Operand).Offset); - return new StateTransition(nextState, conditional - ? ControlFlowEdgeType.Conditional - : ControlFlowEdgeType.Unconditional); + } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.Dnlib/CilStaticSuccessorResolver.cs b/src/Platforms/Echo.Platforms.Dnlib/CilStaticSuccessorResolver.cs index ccb3665a..ecaf621a 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/CilStaticSuccessorResolver.cs +++ b/src/Platforms/Echo.Platforms.Dnlib/CilStaticSuccessorResolver.cs @@ -3,111 +3,91 @@ using dnlib.DotNet.Emit; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using DnlibCode = dnlib.DotNet.Emit.Code; namespace Echo.Platforms.Dnlib { /// - /// Provides an implementation of + /// Provides an implementation of /// public class CilStaticSuccessorResolver : IStaticSuccessorResolver { /// /// Gets a reusable singleton instance of the static successor resolver for the CIL architecture. /// - public static CilStaticSuccessorResolver Instance - { - get; - } = new(); - + public static CilStaticSuccessorResolver Instance { get; } = new(); + /// - public int GetSuccessorsCount(in Instruction instruction) + public void GetSuccessors(in Instruction instruction, IList successorsBuffer) { + // Multiplex based on flow control. switch (instruction.OpCode.FlowControl) { - case FlowControl.Call when instruction.OpCode.Code == DnlibCode.Jmp: - return 0; - - case FlowControl.Break: - case FlowControl.Meta: - case FlowControl.Next: + case FlowControl.Break or FlowControl.Meta or FlowControl.Next or FlowControl.Call: + if (instruction.OpCode.Code != DnlibCode.Jmp) + AddFallThrough(instruction, successorsBuffer); + break; + case FlowControl.Branch: - case FlowControl.Call: - return 1; + AddUnconditionalBranch(instruction, successorsBuffer); + break; - case FlowControl.Cond_Branch when instruction.OpCode.Code == DnlibCode.Switch: - return ((ICollection) instruction.Operand).Count + 1; - case FlowControl.Cond_Branch: - return 2; + AddConditionalBranches(instruction, successorsBuffer); + break; - case FlowControl.Return: - case FlowControl.Throw: - return 0; + case FlowControl.Return or FlowControl.Throw: + return; case FlowControl.Phi: - throw new NotSupportedException("There are no known instructions with Phi control flow"); + throw new NotSupportedException(); default: throw new ArgumentOutOfRangeException(); } } - /// - public int GetSuccessors(in Instruction instruction, Span successorsBuffer) + private static void AddFallThrough(Instruction instruction, IList successorsBuffer) { - switch (instruction.OpCode.FlowControl) - { - case FlowControl.Break: - case FlowControl.Meta: - case FlowControl.Next: - successorsBuffer[0] = FallThrough(instruction); - return 1; - - case FlowControl.Call: - if (instruction.OpCode.Code == DnlibCode.Jmp) - return 0; - - successorsBuffer[0] = FallThrough(instruction); - return 1; - - case FlowControl.Branch: - successorsBuffer[0] = Branch(false, instruction); - return 1; - - case FlowControl.Cond_Branch when instruction.OpCode.Code == DnlibCode.Switch: - var multipleTargets = (Instruction[]) instruction.Operand; - for (int i = 0; i < multipleTargets.Length; i++) - successorsBuffer[i] = new SuccessorInfo(multipleTargets[i].Offset, ControlFlowEdgeType.Conditional); - successorsBuffer[multipleTargets.Length] = FallThrough(instruction); - return multipleTargets.Length + 1; - - case FlowControl.Cond_Branch: - successorsBuffer[0] = Branch(true, instruction); - successorsBuffer[1] = FallThrough(instruction); - return 2; - - case FlowControl.Return: - case FlowControl.Throw: - return 0; + successorsBuffer.Add(FallThrough(instruction)); + } - case FlowControl.Phi: - throw new NotSupportedException("There are no known instructions with Phi control flow"); + private static void AddUnconditionalBranch(Instruction instruction, IList successorsBuffer) + { + // Unconditional branches always move to the instruction referenced in the operand. + var label = (Instruction) instruction.Operand!; + successorsBuffer.Add(new SuccessorInfo(label.Offset, ControlFlowEdgeType.Unconditional)); + } + private static void AddConditionalBranches(Instruction instruction, IList successorsBuffer) + { + // Conditional branches can reference one or more instructions in the operand. + switch (instruction.Operand) + { + case Instruction singleTarget: + successorsBuffer.Add(Conditional(singleTarget)); + successorsBuffer.Add(FallThrough(instruction)); + break; + + case IList multipleTargets: + for (int i = 0; i < multipleTargets.Count; i++) + successorsBuffer.Add(Conditional(multipleTargets[i])); + successorsBuffer.Add(FallThrough(instruction)); + break; + default: throw new ArgumentOutOfRangeException(); } } + private static SuccessorInfo Conditional(Instruction singleTarget) + { + return new SuccessorInfo(singleTarget.Offset, ControlFlowEdgeType.Conditional); + } + private static SuccessorInfo FallThrough(Instruction instruction) { return new SuccessorInfo(instruction.Offset + instruction.GetSize(), ControlFlowEdgeType.FallThrough); } - - private static SuccessorInfo Branch(bool conditional, Instruction instruction) => - new SuccessorInfo(((Instruction) instruction.Operand).Offset, conditional - ? ControlFlowEdgeType.Conditional - : ControlFlowEdgeType.Unconditional); } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.Dnlib/DnlibExtensions.cs b/src/Platforms/Echo.Platforms.Dnlib/DnlibExtensions.cs index 0bdb2eec..895fc5c0 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/DnlibExtensions.cs +++ b/src/Platforms/Echo.Platforms.Dnlib/DnlibExtensions.cs @@ -4,11 +4,9 @@ using dnlib.DotNet.Emit; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; -using Echo.ControlFlow.Construction.Symbolic; using Echo.ControlFlow.Regions.Detection; -using Echo.Code; using Echo.DataFlow; +using Echo.DataFlow.Construction; namespace Echo.Platforms.Dnlib { diff --git a/src/Platforms/Echo.Platforms.Dnlib/Echo.Platforms.Dnlib.csproj b/src/Platforms/Echo.Platforms.Dnlib/Echo.Platforms.Dnlib.csproj index 3864c39f..8e6148c3 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/Echo.Platforms.Dnlib.csproj +++ b/src/Platforms/Echo.Platforms.Dnlib/Echo.Platforms.Dnlib.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Platforms/Echo.Platforms.Iced/Echo.Platforms.Iced.csproj b/src/Platforms/Echo.Platforms.Iced/Echo.Platforms.Iced.csproj index ac8df7a4..d2c7daee 100644 --- a/src/Platforms/Echo.Platforms.Iced/Echo.Platforms.Iced.csproj +++ b/src/Platforms/Echo.Platforms.Iced/Echo.Platforms.Iced.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Platforms/Echo.Platforms.Iced/X86Architecture.cs b/src/Platforms/Echo.Platforms.Iced/X86Architecture.cs index bfd339b6..4294909d 100644 --- a/src/Platforms/Echo.Platforms.Iced/X86Architecture.cs +++ b/src/Platforms/Echo.Platforms.Iced/X86Architecture.cs @@ -80,46 +80,8 @@ public int GetStackPopCount(in Instruction instruction) } /// - public int GetReadVariablesCount(in Instruction instruction) + public void GetReadVariables(in Instruction instruction, ICollection variablesBuffer) { - int count = 0; - - ref readonly var info = ref _infoFactory.GetInfo(instruction); - - // Check for any general purpose register reads. - foreach (var use in info.GetUsedRegisters()) - { - switch (use.Access) - { - case OpAccess.Read: - case OpAccess.CondRead: - case OpAccess.ReadWrite: - case OpAccess.ReadCondWrite: - count++; - break; - } - } - - // Check for any flag register reads. - var readFlags = instruction.RflagsRead; - if (readFlags != RflagsBits.None) - { - for (int i = 1; i <= (int) RflagsBits.AC; i <<= 1) - { - var flag = (RflagsBits) i; - if ((readFlags & flag) != 0) - count++; - } - } - - return count; - } - - /// - public int GetReadVariables(in Instruction instruction, Span variablesBuffer) - { - int count = 0; - ref readonly var info = ref _infoFactory.GetInfo(instruction); // Check for any general purpose register reads. @@ -131,8 +93,7 @@ public int GetReadVariables(in Instruction instruction, Span variable case OpAccess.CondRead: case OpAccess.ReadWrite: case OpAccess.ReadCondWrite: - variablesBuffer[count] = _gpr[use.Register]; - count++; + variablesBuffer.Add(_gpr[use.Register]); break; } } @@ -145,21 +106,14 @@ public int GetReadVariables(in Instruction instruction, Span variable { var flag = (RflagsBits) i; if ((readFlags & flag) != 0) - { - variablesBuffer[count] = _flags[flag]; - count++; - } + variablesBuffer.Add(_flags[flag]); } } - - return count; } /// - public int GetWrittenVariablesCount(in Instruction instruction) + public void GetWrittenVariables(in Instruction instruction, ICollection variablesBuffer) { - int count = 0; - ref readonly var info = ref _infoFactory.GetInfo(instruction); // Check for any general purpose register writes. @@ -171,7 +125,7 @@ public int GetWrittenVariablesCount(in Instruction instruction) case OpAccess.CondWrite: case OpAccess.ReadWrite: case OpAccess.ReadCondWrite: - count++; + variablesBuffer.Add(_gpr[use.Register]); break; } } @@ -184,52 +138,9 @@ public int GetWrittenVariablesCount(in Instruction instruction) { var flag = (RflagsBits) i; if ((modifiedFlags & flag) != 0) - count++; + variablesBuffer.Add(_flags[flag]); } } - - return count; } - - /// - public int GetWrittenVariables(in Instruction instruction, Span variablesBuffer) - { - int count = 0; - - ref readonly var info = ref _infoFactory.GetInfo(instruction); - - // Check for any general purpose register writes. - foreach (var use in info.GetUsedRegisters()) - { - switch (use.Access) - { - case OpAccess.Write: - case OpAccess.CondWrite: - case OpAccess.ReadWrite: - case OpAccess.ReadCondWrite: - variablesBuffer[count] = _gpr[use.Register]; - count++; - break; - } - } - - // Check for any flag register writes. - var modifiedFlags = instruction.RflagsModified; - if (modifiedFlags != RflagsBits.None) - { - for (int i = 1; i <= (int) RflagsBits.AC; i <<= 1) - { - var flag = (RflagsBits) i; - if ((modifiedFlags & flag) != 0) - { - variablesBuffer[count] = _flags[flag]; - count++; - } - } - } - - return count; - } - } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.Iced/X86StateTransitioner.cs b/src/Platforms/Echo.Platforms.Iced/X86StateTransitioner.cs index 901ba938..5c870ebc 100644 --- a/src/Platforms/Echo.Platforms.Iced/X86StateTransitioner.cs +++ b/src/Platforms/Echo.Platforms.Iced/X86StateTransitioner.cs @@ -1,7 +1,7 @@ -using System; +using System.Collections.Generic; using Echo.ControlFlow; -using Echo.ControlFlow.Construction.Symbolic; using Echo.Code; +using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; using Iced.Intel; @@ -11,7 +11,7 @@ namespace Echo.Platforms.Iced /// Provides an implementation of the interface, that /// implements the state transitioning for the x86 instruction set. /// - public class X86StateTransitioner : StateTransitionerBase + public class X86StateTransitioner : StateTransitioner { /// /// Creates a new instance of . @@ -23,81 +23,59 @@ public X86StateTransitioner(IArchitecture architecture) } /// - public override int GetTransitionCount( - in SymbolicProgramState currentState, - in Instruction instruction) - { - switch (instruction.FlowControl) - { - case FlowControl.ConditionalBranch: - return 2; - - case FlowControl.IndirectBranch: - //TODO: Try inferring indirect branch from data flow graph. - - case FlowControl.Return: - return 0; - - default: - return 1; - } - } - - /// - public override int GetTransitions( - in SymbolicProgramState currentState, - in Instruction instruction, - Span> transitionBuffer) + public override void GetTransitions( + in SymbolicProgramState currentState, + in Instruction instruction, + IList> transitionsBuffer) { var nextState = ApplyDefaultBehaviour(currentState, instruction); switch (instruction.FlowControl) { case FlowControl.UnconditionalBranch: - return GetUnconditionalBranchTransitions(instruction, nextState, transitionBuffer); - + UnconditionalBranch(instruction, nextState, transitionsBuffer); + break; + case FlowControl.ConditionalBranch: - return GetConditionalBranchTransitions(instruction, nextState, transitionBuffer); + ConditionalBranch(instruction, nextState, transitionsBuffer); + break; case FlowControl.IndirectBranch: //TODO: Try inferring indirect branch from data flow graph. case FlowControl.Return: - return 0; + break; default: - return GetFallthroughTransitions(nextState, transitionBuffer); + FallThrough(nextState, transitionsBuffer); + break; } } - - private static int GetUnconditionalBranchTransitions( + + private static void UnconditionalBranch( in Instruction instruction, in SymbolicProgramState nextState, - Span> successorBuffer) + IList> successorBuffer) { var branchState = nextState.WithProgramCounter((long) instruction.NearBranchTarget); - successorBuffer[0] = new StateTransition(branchState, ControlFlowEdgeType.Unconditional); - return 1; + successorBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Unconditional)); } - private static int GetConditionalBranchTransitions( + private static void ConditionalBranch( in Instruction instruction, in SymbolicProgramState nextState, - Span> successorBuffer) + IList> successorBuffer) { var branchState = nextState.WithProgramCounter((long) instruction.NearBranchTarget); - successorBuffer[0] = new StateTransition(branchState, ControlFlowEdgeType.Conditional); - successorBuffer[1] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return 2; + successorBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Conditional)); + successorBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - private static int GetFallthroughTransitions( + private static void FallThrough( in SymbolicProgramState nextState, - Span> successorBuffer) + IList> successorBuffer) { - successorBuffer[0] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return 1; + successorBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.Iced/X86StaticSuccessorResolver.cs b/src/Platforms/Echo.Platforms.Iced/X86StaticSuccessorResolver.cs index 6686ae9e..59c858b6 100644 --- a/src/Platforms/Echo.Platforms.Iced/X86StaticSuccessorResolver.cs +++ b/src/Platforms/Echo.Platforms.Iced/X86StaticSuccessorResolver.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Iced.Intel; namespace Echo.Platforms.Iced @@ -14,72 +13,55 @@ namespace Echo.Platforms.Iced public class X86StaticSuccessorResolver : IStaticSuccessorResolver { /// - public int GetSuccessorsCount(in Instruction instruction) - { - switch (instruction.FlowControl) - { - case FlowControl.ConditionalBranch: - return 2; - - case FlowControl.IndirectBranch: - case FlowControl.Return: - return 0; - - default: - return 1; - } - } - - /// - public int GetSuccessors(in Instruction instruction, Span successorsBuffer) + public void GetSuccessors(in Instruction instruction, IList successorsBuffer) { switch (instruction.FlowControl) { case FlowControl.UnconditionalBranch: - return GetUnconditionalBranchSuccessors(instruction, successorsBuffer); - + UnconditionalBranch(instruction, successorsBuffer); + break; + case FlowControl.ConditionalBranch: - return GetConditionalBranchSuccessors(instruction, successorsBuffer); + ConditionalBranch(instruction, successorsBuffer); + break; case FlowControl.IndirectBranch: case FlowControl.Return: - return 0; + break; default: - return GetFallthroughSuccessors(instruction, successorsBuffer); + FallThrough(instruction, successorsBuffer); + break; } } - private static int GetUnconditionalBranchSuccessors(Instruction instruction, Span successorsBuffer) + private static void UnconditionalBranch(Instruction instruction, IList successorsBuffer) { - successorsBuffer[0] = new SuccessorInfo( + successorsBuffer.Add(new SuccessorInfo( (long) instruction.NearBranchTarget, - ControlFlowEdgeType.Unconditional); - - return 1; + ControlFlowEdgeType.Unconditional + )); } - private static int GetConditionalBranchSuccessors(Instruction instruction, Span successorsBuffer) + private static void ConditionalBranch(Instruction instruction, IList successorsBuffer) { - successorsBuffer[0] = new SuccessorInfo( + successorsBuffer.Add(new SuccessorInfo( (long) instruction.NearBranchTarget, - ControlFlowEdgeType.Conditional); + ControlFlowEdgeType.Conditional + )); - successorsBuffer[1] =new SuccessorInfo( + successorsBuffer.Add(new SuccessorInfo( (long) instruction.IP + instruction.Length, - ControlFlowEdgeType.FallThrough); - - return 2; + ControlFlowEdgeType.FallThrough + )); } - private static int GetFallthroughSuccessors(Instruction instruction, Span successorsBuffer) + private static void FallThrough(Instruction instruction, IList successorsBuffer) { - successorsBuffer[0] = new SuccessorInfo( + successorsBuffer.Add(new SuccessorInfo( (long) instruction.IP + instruction.Length, - ControlFlowEdgeType.FallThrough); - - return 1; + ControlFlowEdgeType.FallThrough + )); } - } } \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs index 5e42fdf7..5dfe0fed 100644 --- a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs @@ -7,7 +7,6 @@ using Echo.Code; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Echo.ControlFlow.Regions; using Echo.ControlFlow.Regions.Detection; using Echo.Platforms.DummyPlatform.Code; @@ -325,6 +324,27 @@ public void PushArgumentBeforeImpureStatement() Assert.Same(match2.GetCaptures(variable)[0], match3.GetCaptures(variable)[0]); } + [Fact] + public void Test() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Op(1, 0, 1), + DummyInstruction.Op(2, 0, 1), + DummyInstruction.Op(3, 1, 1), + DummyInstruction.Op(4, 0, 1), + DummyInstruction.Op(5, 4, 0), + + // ret() + DummyInstruction.Ret(6) + }); + + using var fs = File.CreateText("/tmp/output.dot"); + cfg.ToDotGraph(fs, new DummyFormatter { IncludeOffset = false }.ToAstFormatter()); + } + [Fact] public void TwoNodes() { diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs index 0d06f78a..f8dcde29 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Echo.ControlFlow.Construction.Static; +using Echo.ControlFlow.Construction; using Echo.Platforms.DummyPlatform.Code; using Xunit; diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs index 39b934b2..e29d87bd 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs @@ -2,8 +2,8 @@ using System.Collections.Immutable; using System.Linq; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Symbolic; using Echo.DataFlow; +using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; using Echo.Platforms.DummyPlatform.Code; using Echo.Platforms.DummyPlatform.ControlFlow; diff --git a/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs b/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs index 568308c7..23e45702 100644 --- a/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Regions/Detection/ExceptionHandlerRegionDetectionTest.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Echo.ControlFlow.Regions.Detection; using Echo.Platforms.DummyPlatform.Code; using Xunit; diff --git a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs index 3af37381..04a73850 100644 --- a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockBuilderTest.cs @@ -2,7 +2,6 @@ using System.Linq; using Echo.ControlFlow.Blocks; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Echo.ControlFlow.Regions; using Echo.ControlFlow.Regions.Detection; using Echo.ControlFlow.Serialization.Blocks; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/CilArchitectureTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/CilArchitectureTest.cs index 4ec18175..39a215ea 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/CilArchitectureTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/CilArchitectureTest.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using AsmResolver.DotNet; using AsmResolver.PE.DotNet.Cil; @@ -18,7 +19,7 @@ public void GetReadParametersInStaticContextShouldStartAtZeroIndex() var architecture = new CilArchitecture(method.CilMethodBody!); - var readVariables = new IVariable[1]; + var readVariables = new List(); architecture.GetReadVariables(new CilInstruction(CilOpCodes.Ldarg_0), readVariables); Assert.Equal(new[] {method.Parameters[0]}, readVariables @@ -36,13 +37,14 @@ public void GetReadParametersInInstanceContextShouldStartAtThisParameter() var architecture = new CilArchitecture(method.CilMethodBody!); - var readVariables = new IVariable[1]; + var readVariables = new List(); architecture.GetReadVariables(new CilInstruction(CilOpCodes.Ldarg_0), readVariables); Assert.Equal(new[] { method.Parameters.ThisParameter }, readVariables .Cast() .Select(p => p.Parameter)); + readVariables.Clear(); architecture.GetReadVariables(new CilInstruction(CilOpCodes.Ldarg_1), readVariables); Assert.Equal(new[] { method.Parameters[0] }, readVariables .Cast() diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/CilArchitectureTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/CilArchitectureTest.cs index 28d24c55..e1bea074 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/CilArchitectureTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/CilArchitectureTest.cs @@ -1,4 +1,5 @@ -using dnlib.DotNet.Emit; +using System.Collections.Generic; +using dnlib.DotNet.Emit; using Mocks; using Xunit; using IVariable = Echo.Code.IVariable; @@ -19,7 +20,7 @@ public void LdArgInstructionShouldMatchReadParameter(string name, string paramet var param = method.Parameters[index]; var testInstruction = Instruction.Create(OpCodes.Ldarg, param); - var readVariables = new IVariable[1]; + var readVariables = new List(); arch.GetReadVariables(testInstruction, readVariables); Assert.Equal(parameterName, readVariables[0].Name); diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs index 4d2f046e..42f6354c 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs @@ -1,7 +1,6 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Echo.ControlFlow.Regions; using Mocks; using Xunit; diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs index ce9bfd21..e7f4a12f 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs @@ -1,8 +1,6 @@ -using System; -using System.Linq; -using dnlib.DotNet.Emit; +using dnlib.DotNet.Emit; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Symbolic; +using Echo.DataFlow.Construction; using Mocks; using Xunit; diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs index eccf4838..813238e3 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs @@ -1,6 +1,7 @@ using System; -using Echo.ControlFlow.Construction.Static; +using System.Collections.Generic; using Echo.Code; +using Echo.ControlFlow.Construction; using Echo.Platforms.DummyPlatform.ControlFlow; namespace Echo.Platforms.DummyPlatform.Code @@ -34,41 +35,17 @@ public InstructionFlowControl GetFlowControl(in DummyInstruction instruction) => public int GetStackPushCount(in DummyInstruction instruction) => instruction.PushCount; public int GetStackPopCount(in DummyInstruction instruction) => instruction.PopCount; - - public int GetReadVariablesCount(in DummyInstruction instruction) - { - return instruction.OpCode == DummyOpCode.Get - ? 1 - : 0; - } - - public int GetReadVariables(in DummyInstruction instruction, Span variablesBuffer) + + public void GetReadVariables(in DummyInstruction instruction, ICollection variablesBuffer) { if (instruction.OpCode == DummyOpCode.Get) - { - variablesBuffer[0] = (IVariable) instruction.Operands[0]; - return 1; - } - - return 0; - } - - public int GetWrittenVariablesCount(in DummyInstruction instruction) - { - return instruction.OpCode == DummyOpCode.Set - ? 1 - : 0; + variablesBuffer.Add((IVariable) instruction.Operands[0]); } - public int GetWrittenVariables(in DummyInstruction instruction, Span variablesBuffer) + public void GetWrittenVariables(in DummyInstruction instruction, ICollection variablesBuffer) { if (instruction.OpCode == DummyOpCode.Set) - { - variablesBuffer[0] = (IVariable) instruction.Operands[0]; - return 1; - } - - return 0; + variablesBuffer.Add((IVariable) instruction.Operands[0]); } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs index 2bbd81ec..81c19d3e 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs @@ -1,49 +1,16 @@ using System; +using System.Collections.Generic; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Echo.Platforms.DummyPlatform.Code; namespace Echo.Platforms.DummyPlatform.ControlFlow { public class DummyStaticSuccessorResolver : IStaticSuccessorResolver { - public static DummyStaticSuccessorResolver Instance - { - get; - } = new(); + public static DummyStaticSuccessorResolver Instance { get; } = new(); - /// - public int GetSuccessorsCount(in DummyInstruction instruction) - { - switch (instruction.OpCode) - { - case DummyOpCode.Op: - case DummyOpCode.Push: - case DummyOpCode.Pop: - case DummyOpCode.Get: - case DummyOpCode.Set: - case DummyOpCode.Jmp: - return 1; - - case DummyOpCode.JmpCond: - case DummyOpCode.PushOffset: - return 2; - - case DummyOpCode.Switch: - var targets = ((long[]) instruction.Operands[0]); - return targets.Length + 1; - - case DummyOpCode.Ret: - return 0; - - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - public int GetSuccessors(in DummyInstruction instruction, Span successorBuffer) + public void GetSuccessors(in DummyInstruction instruction, IList successorsBuffer) { switch (instruction.OpCode) { @@ -52,37 +19,36 @@ public int GetSuccessors(in DummyInstruction instruction, Span su case DummyOpCode.Pop: case DummyOpCode.Get: case DummyOpCode.Set: - successorBuffer[0] = new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough); - return 1; + successorsBuffer.Add(new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough)); + break; case DummyOpCode.Jmp: - successorBuffer[0] = new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.Unconditional); - return 1; + successorsBuffer.Add(new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.Unconditional)); + break; case DummyOpCode.JmpCond: - successorBuffer[0] = new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough); - successorBuffer[1] = new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.Conditional); - return 2; + successorsBuffer.Add(new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough)); + successorsBuffer.Add(new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.Conditional)); + break; case DummyOpCode.PushOffset: - successorBuffer[0] = new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough); - successorBuffer[1] = new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.None); - return 2; + successorsBuffer.Add(new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough)); + successorsBuffer.Add(new SuccessorInfo((long) instruction.Operands[0], ControlFlowEdgeType.None)); + break; case DummyOpCode.Switch: - var targets = (long[]) instruction.Operands[0]; + long[] targets = (long[]) instruction.Operands[0]; for (int i = 0; i < targets.Length; i++) - successorBuffer[i] = new SuccessorInfo(targets[i], ControlFlowEdgeType.Conditional); - successorBuffer[targets.Length] = new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough); - return targets.Length + 1; + successorsBuffer.Add(new SuccessorInfo(targets[i], ControlFlowEdgeType.Conditional)); + successorsBuffer.Add(new SuccessorInfo(instruction.Offset + 1, ControlFlowEdgeType.FallThrough)); + break; case DummyOpCode.Ret: - return 0; + break; default: throw new ArgumentOutOfRangeException(); } } - } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyTransitioner.cs b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyTransitioner.cs index 7d681cdf..88f86dd1 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyTransitioner.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyTransitioner.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; using Echo.ControlFlow; -using Echo.ControlFlow.Construction.Symbolic; -using Echo.Code; +using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; using Echo.Platforms.DummyPlatform.Code; namespace Echo.Platforms.DummyPlatform.ControlFlow { - public class DummyTransitioner : StateTransitionerBase + public class DummyTransitioner : StateTransitioner { public DummyTransitioner() : base(DummyArchitecture.Instance) @@ -27,39 +25,10 @@ public override SymbolicProgramState GetInitialState(long entr return InitialState.WithProgramCounter(entrypointAddress); } - public override int GetTransitionCount( - in SymbolicProgramState currentState, - in DummyInstruction instruction) - { - switch (instruction.OpCode) - { - case DummyOpCode.Op: - case DummyOpCode.Push: - case DummyOpCode.Pop: - case DummyOpCode.Get: - case DummyOpCode.Set: - case DummyOpCode.Jmp: - return 1; - - case DummyOpCode.JmpCond: - case DummyOpCode.PushOffset: - return 2; - - case DummyOpCode.Ret: - return 0; - - case DummyOpCode.Switch: - return ((ICollection) instruction.Operands[0]).Count + 1; - - default: - throw new ArgumentOutOfRangeException(); - } - } - - public override int GetTransitions( - in SymbolicProgramState currentState, - in DummyInstruction instruction, - Span> transitionBuffer) + public override void GetTransitions( + in SymbolicProgramState currentState, + in DummyInstruction instruction, + IList> transitionsBuffer) { var nextState = ApplyDefaultBehaviour(currentState, instruction); @@ -70,83 +39,84 @@ public override int GetTransitions( case DummyOpCode.Pop: case DummyOpCode.Get: case DummyOpCode.Set: - return GetFallthroughTransitions(nextState, transitionBuffer); + GetFallthroughTransitions(nextState, transitionsBuffer); + break; case DummyOpCode.Jmp: - return GetJumpTransitions(nextState, instruction, transitionBuffer); + GetJumpTransitions(nextState, instruction, transitionsBuffer); + break; case DummyOpCode.JmpCond: - return GetJumpCondTransitions(nextState, instruction, transitionBuffer); + GetJumpCondTransitions(nextState, instruction, transitionsBuffer); + break; case DummyOpCode.PushOffset: - return GetPushOffsetTransitions(nextState, instruction, transitionBuffer); + GetPushOffsetTransitions(nextState, instruction, transitionsBuffer); + break; case DummyOpCode.Ret: - return 0; + break; case DummyOpCode.Switch: - return GetSwitchTransitions(nextState, instruction, transitionBuffer); + GetSwitchTransitions(nextState, instruction, transitionsBuffer); + break; default: throw new ArgumentOutOfRangeException(); } } - private static int GetFallthroughTransitions( + private static void GetFallthroughTransitions( in SymbolicProgramState nextState, - Span> successorBuffer) + IList> transitionsBuffer) { - successorBuffer[0] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return 1; + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - private static int GetJumpTransitions( + private static void GetJumpTransitions( in SymbolicProgramState nextState, DummyInstruction instruction, - Span> successorBuffer) + IList> transitionsBuffer) { - successorBuffer[0] = new StateTransition( + transitionsBuffer.Add(new StateTransition( nextState.WithProgramCounter((long) instruction.Operands[0]), - ControlFlowEdgeType.Unconditional); - return 1; + ControlFlowEdgeType.Unconditional + )); } - private static int GetJumpCondTransitions( + private static void GetJumpCondTransitions( in SymbolicProgramState nextState, DummyInstruction instruction, - Span> successorBuffer) + IList> transitionsBuffer) { var branchState = nextState.WithProgramCounter((long) instruction.Operands[0]); - successorBuffer[0] = new StateTransition(branchState, ControlFlowEdgeType.Conditional); - successorBuffer[1] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return 2; + transitionsBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Conditional)); + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - private static int GetSwitchTransitions( + private static void GetSwitchTransitions( in SymbolicProgramState nextState, DummyInstruction instruction, - Span> successorBuffer) + IList> transitionsBuffer) { var targets = (IList) instruction.Operands[0]; for (int i = 0; i < targets.Count; i++) { var branchState = nextState.WithProgramCounter(targets[i]); - successorBuffer[i] = new StateTransition(branchState, ControlFlowEdgeType.Conditional); + transitionsBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.Conditional)); } - successorBuffer[targets.Count] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - return targets.Count + 1; + transitionsBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); } - private static int GetPushOffsetTransitions( + private static void GetPushOffsetTransitions( in SymbolicProgramState nextState, DummyInstruction instruction, - Span> successorBuffer) + IList> successorBuffer) { var branchState = nextState.WithProgramCounter((long) instruction.Operands[0]); - successorBuffer[1] = new StateTransition(nextState, ControlFlowEdgeType.FallThrough); - successorBuffer[0] = new StateTransition(branchState, ControlFlowEdgeType.None); - return 2; + successorBuffer.Add(new StateTransition(nextState, ControlFlowEdgeType.FallThrough)); + successorBuffer.Add(new StateTransition(branchState, ControlFlowEdgeType.None)); } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/IntArchitecture.cs b/test/Platforms/Echo.Platforms.DummyPlatform/IntArchitecture.cs index 94b00fa3..35ddfde4 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/IntArchitecture.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/IntArchitecture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Echo.Code; namespace Echo.Platforms.DummyPlatform @@ -38,13 +39,13 @@ private IntArchitecture() public int GetStackPushCount(in int instruction) => 0; public int GetStackPopCount(in int instruction) => 0; - public int GetReadVariablesCount(in int instruction) => 0; - - public int GetReadVariables(in int instruction, Span variablesBuffer) => 0; - - public int GetWrittenVariablesCount(in int instruction) => 0; - - public int GetWrittenVariables(in int instruction, Span variablesBuffer) => 0; + + public void GetReadVariables(in int instruction, ICollection variablesBuffer) + { + } + public void GetWrittenVariables(in int instruction, ICollection variablesBuffer) + { + } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs index 989da193..e5e85673 100644 --- a/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs @@ -1,8 +1,8 @@ using System.Linq; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Symbolic; using Echo.DataFlow; +using Echo.DataFlow.Construction; using Iced.Intel; using Xunit; diff --git a/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs b/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs index bbd5d3dd..980a0cb9 100644 --- a/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs +++ b/test/Platforms/Echo.Platforms.Iced.Tests/X86StaticFlowGraphBuilderTest.cs @@ -1,7 +1,6 @@ using System.Linq; using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Static; using Iced.Intel; using Xunit; From 25eb02b9c6c363d6e236c408518833496e1ed1fb Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Jul 2024 21:37:30 +0200 Subject: [PATCH 3/9] Update docs. --- docs/guides/core/cfg-basics.md | 36 ++++++++++++++++++++++++---- docs/guides/core/cfg-construction.md | 13 ++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/docs/guides/core/cfg-basics.md b/docs/guides/core/cfg-basics.md index b0bf5bbf..0b846d01 100644 --- a/docs/guides/core/cfg-basics.md +++ b/docs/guides/core/cfg-basics.md @@ -27,7 +27,7 @@ Refer to the platform-specific documentation for more details. ## Nodes Nodes in a control flow graph represent the individual basic blocks in the code, and are implemented by the `ControlFlowNode` class. -They can be accessed from the `Nodes` property: +They can be accessed from the `Nodes` property, which can be iterated: ```csharp ControlFlowGraph cfg = ...; @@ -37,14 +37,27 @@ foreach (var node in cfg.Nodes) Console.WriteLine($"{node.Offset:X8}"); ``` -Nodes are indexed by offset. -They can be obtained via the `GetNodeByOffset` method: +Individual nodes can also be obtained by looking them up by offset: ```csharp -var node = cfg.GetNodeByOffset(offset: 0x1234); +var node = cfg.Nodes.GetByOffset(offset: 0x1234); ``` -Every node exposes a basic blockc containing the instructions it executes: +This performs a linear search through all the nodes, and finds the first basic block that matches. +To ensure all nodes have updated offsets according to their contents, use the `UpdateOffsets` method: + +```csharp +cfg.Nodes.UpdateOffsets(); +``` + +If many nodes are supposed to be queried by offset, consider first creating an offset map; a dictionary that maps all basic block header offsets to their corresponding nodes: + +```csharp +var offsetMap = cfg.Nodes.CreateOffsetMap(); +var node = cfg.Nodes[0x1234]; +``` + +Every node exposes a basic block containing the instructions it executes: ```csharp ControlFlowNode node = ...; @@ -117,6 +130,19 @@ foreach (var predecessor in node.GetPredecessors()) ``` +New edges can be drawn by either mutating the outgoing edges properties, or by using the `ConnectWith` helper method: + +```csharp +ControlFlowNode node1 = ...; +ControlFlowNode node2 = ...; +ControlFlowNode node3 = ...; +ControlFlowNode node4 = ...; + +node1.ConnectWith(node2); +node2.ConnectWith(node3, ControlFlowEdgeType.Conditional); +node2.ConnectWith(node4, ControlFlowEdgeType.FallThrough); +``` + ## Regions Control flow graphs can be subdivided into regions. diff --git a/docs/guides/core/cfg-construction.md b/docs/guides/core/cfg-construction.md index 32c24784..ed1c8226 100644 --- a/docs/guides/core/cfg-construction.md +++ b/docs/guides/core/cfg-construction.md @@ -67,7 +67,7 @@ This interface takes a symbolic input state, and transforms it into a set of all ```csharp IArchitecture architecture = ...; -IStateTransitioner transitioner = ...; +StateTransitioner transitioner = ...; IList instructions = ...; var builder = new SymbolicFlowGraphBuilder( @@ -79,12 +79,15 @@ var builder = new SymbolicFlowGraphBuilder( var cfg = builder.ConstructFlowGraph(); ``` -A by-product of symbolic graph building is that it also produces a **data flow graph**: +Most state transitioners produce a data flow graph as a by-product. ```csharp -var dfg = builder.DataFlowGraph; -``` +// First create the CFG. +var cfg = builder.ConstructFlowGraph(); +// After building the CFG, a DFG is populated in the transitioner. +var dfg = transitioner.DataFlowGraph; +``` > [!WARNING] -> While symbolic graph construction usually is more accurate, it is significantly slower than static graph construction and can take a lot of memory. +> While symbolic graph construction usually is more accurate, it is significantly slower than static graph construction and can take a lot of memory. \ No newline at end of file From 06bdf2bb0717203707e3d2995cd9f629f65b487a Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Jul 2024 23:04:59 +0200 Subject: [PATCH 4/9] Update xmldocs, add convenience Add method --- .../Echo.ControlFlow/Blocks/BasicBlock.cs | 4 ++++ .../Collections/NodeCollection.cs | 23 +++++++++++++++++++ src/Core/Echo.ControlFlow/ControlFlowEdge.cs | 9 ++++++++ src/Core/Echo.ControlFlow/ControlFlowNode.cs | 15 ++++++++++++ 4 files changed, 51 insertions(+) diff --git a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs index 1cbfbf91..380917f3 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs @@ -95,6 +95,10 @@ public IList Instructions /// public BasicBlock GetLastBlock() => this; + /// + /// Synchronizes the basic block's offset with the offset of the first instruction. + /// + /// The architecture description of the instructions. public void UpdateOffset(IArchitecture architecture) { Offset = Header is not null diff --git a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs index c8abe147..e9d710a4 100644 --- a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Echo.ControlFlow.Blocks; namespace Echo.ControlFlow.Collections { @@ -28,6 +29,18 @@ internal NodeCollection(ControlFlowGraph owner) /// public bool IsReadOnly => false; + /// + /// Wraps a basic block into a node and adds it to the graph. + /// + /// The block. + /// The created node. + public ControlFlowNode Add(BasicBlock item) + { + var node = new ControlFlowNode(item); + Add(node); + return node; + } + /// public void Add(ControlFlowNode item) { @@ -135,11 +148,21 @@ public void UpdateOffsets() node.UpdateOffset(); } + /// + /// Constructs a mapping from basic block header offsets to their respective nodes. + /// + /// The mapping + /// The control flow graph contains nodes with duplicated offsets. public IDictionary> CreateOffsetMap() { return _nodes.ToDictionary(x => x.Offset, x => x); } + /// + /// Finds a node by its basic block header offset. + /// + /// The offset. + /// The node, or null if no node was found with the provided offset. public ControlFlowNode? GetByOffset(long offset) => _nodes.FirstOrDefault(x => x.Contents.Offset == offset); /// diff --git a/src/Core/Echo.ControlFlow/ControlFlowEdge.cs b/src/Core/Echo.ControlFlow/ControlFlowEdge.cs index ce37d3cf..dc8170d3 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowEdge.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowEdge.cs @@ -59,6 +59,15 @@ public ControlFlowNode Target get; } + /// + /// Gets or sets user data that is added to the edge. + /// + public object? UserData + { + get; + set; + } + INode IEdge.Origin => Origin; INode IEdge.Target => Target; diff --git a/src/Core/Echo.ControlFlow/ControlFlowNode.cs b/src/Core/Echo.ControlFlow/ControlFlowNode.cs index 6c441b0c..1d88c3e3 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowNode.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowNode.cs @@ -97,6 +97,9 @@ public IControlFlowRegion? ParentRegion internal set; } + /// + /// Gets the offset of the basic block the node is representing. + /// public long Offset => Contents.Offset; /// @@ -122,6 +125,15 @@ public BasicBlock Contents get; } + /// + /// Gets or sets user data that is added to the node. + /// + public object? UserData + { + get; + set; + } + /// /// Gets or sets the neighbour to which the control is transferred to after execution of this block and no /// other condition is met. @@ -245,6 +257,9 @@ public ControlFlowEdge ConnectWith(ControlFlowNode n return edge; } + /// + /// Synchronizes the basic block's offset with the offset of the first instruction. + /// public void UpdateOffset() { if (ParentGraph is not null) From f5157d6e2e02a6cd30fd70b912495cad8875dc2a Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 30 Jul 2024 16:20:20 +0200 Subject: [PATCH 5/9] Remove hard-coupling of data flow nodes and offsets. --- src/Core/Echo.ControlFlow/ControlFlowNode.cs | 4 +- .../Analysis/DependencyCollection.cs | 20 ++- .../Collections/NodeCollection.cs | 153 +++++------------ .../Collections/StackDependencyCollection.cs | 31 ++-- .../VariableDependencyCollection.cs | 35 ++-- .../Construction/StateTransitioner.cs | 28 ++-- src/Core/Echo.DataFlow/DataDependency.cs | 27 +-- src/Core/Echo.DataFlow/DataFlowEdge.cs | 11 +- src/Core/Echo.DataFlow/DataFlowGraph.cs | 21 +-- src/Core/Echo.DataFlow/DataFlowNode.cs | 73 ++++---- src/Core/Echo.DataFlow/DataSource.cs | 19 ++- src/Core/Echo.DataFlow/Echo.DataFlow.csproj | 1 + .../Emulation/SymbolicProgramState.cs | 51 +++--- .../Echo.DataFlow/Emulation/SymbolicValue.cs | 156 +++++++++--------- .../Echo.DataFlow/ExternalDataSourceNode.cs | 33 +--- .../Serialization/Dot/DataFlowEdgeAdorner.cs | 24 +-- .../Serialization/Dot/DataFlowNodeAdorner.cs | 15 +- src/Core/Echo.DataFlow/StackDataSource.cs | 15 +- src/Core/Echo.DataFlow/StackDependency.cs | 13 +- src/Core/Echo.DataFlow/VariableDataSource.cs | 9 +- src/Core/Echo.DataFlow/VariableDependency.cs | 9 +- src/Core/Echo/Code/IArchitecture.cs | 1 + src/Core/Echo/Code/IPurityClassifier.cs | 1 + .../Echo/Code/IStaticInstructionProvider.cs | 1 + src/Core/Echo/Code/ListInstructionProvider.cs | 1 + .../CilStateTransitioner.cs | 20 +-- .../CilStateTransitioner.cs | 13 +- .../{Static => }/StaticGraphBuilderTest.cs | 2 +- .../Analysis/DependencyCollectionTest.cs | 92 +++++------ .../Construction}/SymbolicGraphBuilderTest.cs | 61 ++++--- .../Echo.DataFlow.Tests/DataFlowGraphTest.cs | 78 +-------- .../Echo.DataFlow.Tests/DataFlowNodeTest.cs | 72 ++++---- .../Emulation/SymbolicStackStateTest.cs | 2 +- .../Emulation/SymbolicVariableStateTest.cs | 2 +- .../StateTransitionResolverTest.cs | 5 +- .../StateTransitionResolverTest.cs | 5 +- .../SymbolicFlowGraphTest.cs | 4 +- .../X86StateTransitionResolverTest.cs | 17 +- 38 files changed, 509 insertions(+), 616 deletions(-) rename test/Core/Echo.ControlFlow.Tests/Construction/{Static => }/StaticGraphBuilderTest.cs (99%) rename test/Core/{Echo.ControlFlow.Tests/Construction/Symbolic => Echo.DataFlow.Tests/Construction}/SymbolicGraphBuilderTest.cs (81%) diff --git a/src/Core/Echo.ControlFlow/ControlFlowNode.cs b/src/Core/Echo.ControlFlow/ControlFlowNode.cs index 1d88c3e3..27f913c4 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowNode.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowNode.cs @@ -13,7 +13,7 @@ namespace Echo.ControlFlow /// in a sequence. /// /// The type of instructions to store in the node. - public class ControlFlowNode : INode + public class ControlFlowNode : IIdentifiedNode where TInstruction : notnull { private ControlFlowEdge? _unconditionalEdge; @@ -102,6 +102,8 @@ public IControlFlowRegion? ParentRegion /// public long Offset => Contents.Offset; + long IIdentifiedNode.Id => Offset; + /// public int InDegree => IncomingEdges.Count; diff --git a/src/Core/Echo.DataFlow/Analysis/DependencyCollection.cs b/src/Core/Echo.DataFlow/Analysis/DependencyCollection.cs index 4923bd82..6a12f5c3 100644 --- a/src/Core/Echo.DataFlow/Analysis/DependencyCollection.cs +++ b/src/Core/Echo.DataFlow/Analysis/DependencyCollection.cs @@ -15,11 +15,12 @@ public static class DependencyCollection /// of nodes can be executed sequentially. /// /// The node to find all dependencies for. - /// The type of contents that each node contains. + /// The type of instructions that each node contains. /// The topological ordering of all dependencies of the node. /// Occurs when there is a cyclic dependency in the graph. - public static IEnumerable> GetOrderedDependencies(this DataFlowNode node) => - GetOrderedDependencies(node, DependencyCollectionFlags.IncludeAllDependencies); + public static IEnumerable> GetOrderedDependencies(this DataFlowNode node) + where TInstruction : notnull + => GetOrderedDependencies(node, DependencyCollectionFlags.IncludeAllDependencies); /// /// Collects all dependency nodes recursively, and sorts them in a topological order such that the final collection @@ -27,14 +28,17 @@ public static IEnumerable> GetOrderedDependencies(this DataFl /// /// The node to find all dependencies for. /// Flags that influence the behaviour of the algorithm. - /// The type of contents that each node contains. + /// The type of instructions that each node contains. /// The topological ordering of all dependencies of the node. /// Occurs when there is a cyclic dependency in the graph. - public static IEnumerable> GetOrderedDependencies(this DataFlowNode node, DependencyCollectionFlags flags) + public static IEnumerable> GetOrderedDependencies( + this DataFlowNode node, + DependencyCollectionFlags flags) + where TInstruction : notnull { try { - var topologicalSorting = new TopologicalSorter>(GetSortedOutgoingEdges); + var topologicalSorting = new TopologicalSorter>(GetSortedOutgoingEdges); return topologicalSorting.GetTopologicalSorting(node); } catch (CycleDetectedException ex) @@ -42,9 +46,9 @@ public static IEnumerable> GetOrderedDependencies(this DataFl throw new CyclicDependencyException("Cyclic dependency was detected.", ex); } - IReadOnlyList> GetSortedOutgoingEdges(DataFlowNode n) + IReadOnlyList> GetSortedOutgoingEdges(DataFlowNode n) { - var result = new List>(); + var result = new List>(); // Prioritize stack dependencies over variable dependencies. if ((flags & DependencyCollectionFlags.IncludeStackDependencies) != 0) diff --git a/src/Core/Echo.DataFlow/Collections/NodeCollection.cs b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs index a3b7731f..41c3f7c1 100644 --- a/src/Core/Echo.DataFlow/Collections/NodeCollection.cs +++ b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs @@ -9,14 +9,15 @@ namespace Echo.DataFlow.Collections /// /// Represents a mutable collection of nodes present in a data flow graph. /// - /// The type of data that is stored in each node. + /// The type of instruction that is stored in each node. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class NodeCollection : ICollection> + public class NodeCollection : ICollection> + where TInstruction : notnull { - private readonly Dictionary> _nodes = new(); - private readonly DataFlowGraph _owner; + private readonly List> _nodes = new(); + private readonly DataFlowGraph _owner; - internal NodeCollection(DataFlowGraph owner) + internal NodeCollection(DataFlowGraph owner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } @@ -27,36 +28,27 @@ internal NodeCollection(DataFlowGraph owner) /// public bool IsReadOnly => false; - /// - /// Gets a node by its identifier. - /// - /// The node identifier. - public DataFlowNode this[long id] => _nodes[id]; - /// /// Creates and adds a new node to the collection of data flow nodes. /// - /// The unique identifier of the node. /// The contents of the node. /// The created node. - public DataFlowNode Add(long id, TContents contents) + public DataFlowNode Add(TInstruction contents) { - var node = new DataFlowNode(id, contents); + var node = new DataFlowNode(contents); Add(node); return node; } /// - public void Add(DataFlowNode item) + public void Add(DataFlowNode item) { if (item.ParentGraph == _owner) return; - if (item.ParentGraph != null) + if (item.ParentGraph is not null) throw new ArgumentException("Cannot add a node from another graph."); - if (_nodes.ContainsKey(item.Id)) - throw new ArgumentException($"A node with identifier 0x{item.Id:X8} was already added to the graph."); - _nodes.Add(item.Id, item); + _nodes.Add(item); item.ParentGraph = _owner; } @@ -67,23 +59,23 @@ public void Add(DataFlowNode item) /// /// Occurs when at least one node in the provided collection is already added to another graph. /// - public void AddRange(IEnumerable> items) + public void AddRange(IEnumerable> items) { var nodes = items.ToArray(); + // Validate before adding. for (int i = 0; i < nodes.Length; i++) { var node = nodes[i]; - if (node.ParentGraph != _owner && node.ParentGraph != null) + if (node.ParentGraph is not null && node.ParentGraph != _owner) throw new ArgumentException("Sequence contains nodes from another graph."); - if (_nodes.ContainsKey(node.Id)) - throw new ArgumentException($"Sequence contains nodes with identifiers that were already added to the graph."); } + // Add the nodes. for (int i = 0; i < nodes.Length; i++) { var node = nodes[i]; - _nodes.Add(node.Id, node); + _nodes.Add(node); node.ParentGraph = _owner; } } @@ -91,114 +83,53 @@ public void AddRange(IEnumerable> items) /// public void Clear() { - foreach (long node in _nodes.Keys.ToArray()) + foreach (var node in _nodes.ToArray()) Remove(node); } - - /// - /// Determines whether a node with a specific offset was added to the collection. - /// - /// The offset to the node. - /// true if there exists a node with the provided offset, false otherwise. - public bool Contains(long offset) - { - return _nodes.ContainsKey(offset); - } - + /// - public bool Contains(DataFlowNode item) - { - if (item == null) - return false; - return _nodes.TryGetValue(item.Id, out var node) && node == item; - } + public bool Contains(DataFlowNode item) => _nodes.Contains(item); /// - public void CopyTo(DataFlowNode[] array, int arrayIndex) - { - _nodes.Values.CopyTo(array, arrayIndex); - } + public void CopyTo(DataFlowNode[] array, int arrayIndex) => _nodes.CopyTo(array, arrayIndex); - /// - /// Removes a node by its offset. - /// - /// The offset. of the node to remove. - /// true if the collection contained a node with the provided offset., and the node was removed - /// successfully, false otherwise. - public bool Remove(long offset) + /// + public bool Remove(DataFlowNode item) { - if (_nodes.TryGetValue(offset, out var node)) + if (_nodes.Remove(item)) { - node.Disconnect(); - node.ParentGraph = null; - _nodes.Remove(offset); + item.Disconnect(); + item.ParentGraph = null; return true; } - + return false; } /// - /// Synchronizes all offsets of each node with the underlying instructions. + /// Synchronizes all offsets of each node and basic blocks with the underlying instructions. /// - /// Occurs when one or more nodes are in a state that new offsets - /// cannot be determined. This includes duplicated offsets. - /// - /// - /// Because updating offsets is a relatively expensive task, calls to this method should be delayed as much as - /// possible. - /// - /// - /// This method will invalidate any enumerators that are enumerating this collection of nodes. - /// - /// - public void UpdateIndices() + public void UpdateOffsets() { - var nodes = new Dictionary>(Count); - - // Verify whether all basic blocks are valid, i.e. all offsets can be obtained successfully and contain - // no duplicate offsets. If any problem arises we do not want to commit any changes to the node collection. - foreach (var entry in _nodes) - { - var node = entry.Value; - if (node.IsExternal) - { - nodes.Add(entry.Key, entry.Value); - } - else - { - long newOffset = _owner.Architecture.GetOffset(node.Contents); - if (nodes.ContainsKey(newOffset)) - { - throw new InvalidOperationException( - $"Collection contains multiple instances of the offset {newOffset:X8}."); - } - - nodes.Add(newOffset, node); - } - } - - // Update the collection by editing the dictionary directly instead of using the public Clear and Add - // methods. The public methods remove any incident edges to each node, which means we'd have to add them - // again. - - _nodes.Clear(); - - foreach (var entry in nodes) - { - entry.Value.Id = entry.Key; - _nodes.Add(entry.Key, entry.Value); - } + foreach (var node in _nodes) + node.UpdateOffset(); } - /// - public bool Remove(DataFlowNode item) => - item != null && Remove(item.Id); + /// + /// Constructs a mapping from basic block header offsets to their respective nodes. + /// + /// The mapping + /// The control flow graph contains nodes with duplicated offsets. + public IDictionary> CreateOffsetMap() + { + return _nodes + .Where(x => !x.IsExternal) + .ToDictionary(x => x.Offset, x => x); + } /// - public IEnumerator> GetEnumerator() => _nodes.Values.GetEnumerator(); + public IEnumerator> GetEnumerator() => _nodes.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/Collections/StackDependencyCollection.cs b/src/Core/Echo.DataFlow/Collections/StackDependencyCollection.cs index 6fd4cf27..e5d7e6b5 100644 --- a/src/Core/Echo.DataFlow/Collections/StackDependencyCollection.cs +++ b/src/Core/Echo.DataFlow/Collections/StackDependencyCollection.cs @@ -10,17 +10,18 @@ namespace Echo.DataFlow.Collections /// /// Represents a collection of dependencies allocated on a stack for a node in a data flow graph. /// - /// The type of contents to put in each node. + /// The type of instructions to put in each node. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class StackDependencyCollection : Collection> + public class StackDependencyCollection : Collection> + where TInstruction : notnull { - private readonly DataFlowNode _owner; + private readonly DataFlowNode _owner; /// /// Creates a new dependency collection for a node. /// /// The owner node. - internal StackDependencyCollection(DataFlowNode owner) + internal StackDependencyCollection(DataFlowNode owner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } @@ -30,7 +31,7 @@ internal StackDependencyCollection(DataFlowNode owner) /// public int EdgeCount => this.Sum(d => d.Count); - private void AssertDependencyValidity(StackDependency item) + private void AssertDependencyValidity(StackDependency item) { if (item is null) throw new ArgumentNullException(nameof(item)); @@ -56,12 +57,12 @@ public void SetCount(int count) else if (count > Count) { while(Count != count) - Add(new StackDependency()); + Add(new StackDependency()); } } /// - protected override void InsertItem(int index, StackDependency item) + protected override void InsertItem(int index, StackDependency item) { AssertDependencyValidity(item); base.InsertItem(index, item); @@ -69,7 +70,7 @@ protected override void InsertItem(int index, StackDependency item) } /// - protected override void SetItem(int index, StackDependency item) + protected override void SetItem(int index, StackDependency item) { AssertDependencyValidity(item); @@ -101,26 +102,26 @@ protected override void ClearItems() /// /// Represents an enumerator for a stack dependency collection. /// - public struct Enumerator : IEnumerator> + public struct Enumerator : IEnumerator> { - private readonly StackDependencyCollection _collection; - private StackDependency _current; + private readonly StackDependencyCollection _collection; + private StackDependency _current; private int _index; /// /// Creates a new instance of the structure. /// /// The collection to enumerate. - public Enumerator(StackDependencyCollection collection) + public Enumerator(StackDependencyCollection collection) : this() { _collection = collection; _index = -1; - _current = null; + _current = null!; } /// - public StackDependency Current => _current; + public StackDependency Current => _current; object IEnumerator.Current => Current; @@ -134,7 +135,6 @@ public bool MoveNext() return true; } - _current = null; return false; } @@ -142,7 +142,6 @@ public bool MoveNext() public void Reset() { _index = -1; - _current = null; } /// diff --git a/src/Core/Echo.DataFlow/Collections/VariableDependencyCollection.cs b/src/Core/Echo.DataFlow/Collections/VariableDependencyCollection.cs index 1deff1db..de0b753c 100644 --- a/src/Core/Echo.DataFlow/Collections/VariableDependencyCollection.cs +++ b/src/Core/Echo.DataFlow/Collections/VariableDependencyCollection.cs @@ -10,14 +10,15 @@ namespace Echo.DataFlow.Collections /// /// Represents a collection of variables and their symbolic values that a node in a data flow graph depends on. /// - /// The type of contents to put in each node. + /// The type of instruction to put in each node. [DebuggerDisplay("Count = {" + nameof(Count) + "}")] - public class VariableDependencyCollection : ICollection> + public class VariableDependencyCollection : ICollection> + where TInstruction : notnull { - private readonly Dictionary> _entries = new(); - private readonly DataFlowNode _owner; + private readonly Dictionary> _entries = new(); + private readonly DataFlowNode _owner; - internal VariableDependencyCollection(DataFlowNode owner) + internal VariableDependencyCollection(DataFlowNode owner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } @@ -26,7 +27,7 @@ internal VariableDependencyCollection(DataFlowNode owner) /// Gets or sets the variable dependency assigned to the variable. /// /// The variable - public VariableDependency this[IVariable variable] + public VariableDependency this[IVariable variable] { get => _entries[variable]; set @@ -38,7 +39,7 @@ public VariableDependency this[IVariable variable] } /// - public bool Remove(VariableDependency item) + public bool Remove(VariableDependency item) { throw new NotImplementedException(); } @@ -54,7 +55,7 @@ public bool Remove(VariableDependency item) /// public bool IsReadOnly => false; - private void AssertDependencyValidity(VariableDependency item) + private void AssertDependencyValidity(VariableDependency item) { if (item is null) throw new ArgumentNullException(nameof(item)); @@ -72,14 +73,14 @@ private void AssertDependencyValidity(VariableDependency item) /// The variable. /// When this function returns true, contains the dependency. /// true if the variable was registered as a dependency, false otherwise. - public bool TryGetDependency(IVariable variable, out VariableDependency dependency) => + public bool TryGetDependency(IVariable variable, out VariableDependency dependency) => _entries.TryGetValue(variable, out dependency); /// /// Adds a variable dependency to the node. /// /// The dependency to add. - public void Add(VariableDependency dependency) + public void Add(VariableDependency dependency) { AssertDependencyValidity(dependency); _entries.Add(dependency.Variable, dependency); @@ -94,7 +95,7 @@ public void Clear() } /// - public bool Contains(VariableDependency item) + public bool Contains(VariableDependency item) { return item is not null && _entries.TryGetValue(item.Variable, out var dependency) @@ -110,7 +111,7 @@ public bool ContainsVariable(IVariable variable) => _entries.ContainsKey(variable); /// - public void CopyTo(VariableDependency[] array, int arrayIndex) => + public void CopyTo(VariableDependency[] array, int arrayIndex) => _entries.Values.CopyTo(array, arrayIndex); /// @@ -142,7 +143,7 @@ public bool Remove(IVariable variable) /// The enumerator. public Enumerator GetEnumerator() => new(this); - IEnumerator> IEnumerable>.GetEnumerator() => + IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => @@ -151,21 +152,21 @@ IEnumerator IEnumerable.GetEnumerator() => /// /// Represents an enumerator that enumerates all entries in a variable dependencies collection. /// - public struct Enumerator : IEnumerator> + public struct Enumerator : IEnumerator> { - private Dictionary>.Enumerator _enumerator; + private Dictionary>.Enumerator _enumerator; /// /// Creates a new instance of the class. /// /// The collection to enumerate. - public Enumerator(VariableDependencyCollection collection) + public Enumerator(VariableDependencyCollection collection) { _enumerator = collection._entries.GetEnumerator(); } /// - public VariableDependency Current => _enumerator.Current.Value; + public VariableDependency Current => _enumerator.Current.Value; /// object IEnumerator.Current => Current; diff --git a/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs b/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs index 6d523af4..209af243 100644 --- a/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs +++ b/src/Core/Echo.DataFlow/Construction/StateTransitioner.cs @@ -42,6 +42,11 @@ public DataFlowGraph DataFlowGraph get; } + public IDictionary> OffsetMap + { + get; + } = new Dictionary>(); + /// public virtual SymbolicProgramState GetInitialState(long entrypointAddress) => new(entrypointAddress); @@ -74,7 +79,9 @@ private ImmutableStack> ApplyStackTransition( DataFlowNode node, ImmutableStack> stack) { - var instruction = node.Contents; + var instruction = node.Instruction; + if (instruction is null) + throw new ArgumentException("Cannot apply stack transition on an empty data flow node."); int argumentsCount = Architecture.GetStackPopCount(instruction); if (argumentsCount == -1) @@ -109,7 +116,9 @@ private ImmutableDictionary> ApplyVariabl DataFlowNode node, ImmutableDictionary> variables) { - var instruction = node.Contents; + var instruction = node.Instruction; + if (instruction is null) + throw new ArgumentException("Cannot apply variable transition on an empty data flow node."); // Get read variables. _variablesBuffer.Clear(); @@ -146,16 +155,14 @@ private ImmutableDictionary> ApplyVariabl protected DataFlowNode GetOrCreateDataFlowNode(TInstruction instruction) { long offset = Architecture.GetOffset(instruction); - DataFlowNode node; - if (DataFlowGraph.Nodes.Contains(offset)) + if (!OffsetMap.TryGetValue(offset, out var node)) { - node = DataFlowGraph.Nodes[offset]; - } - else - { - node = new DataFlowNode(offset, instruction); - + node = new DataFlowNode(instruction) + { + Offset = offset + }; + // Register (unknown) stack dependencies. int stackArgumentCount = Architecture.GetStackPopCount(instruction); for (int i = 0; i < stackArgumentCount; i++) @@ -174,6 +181,7 @@ protected DataFlowNode GetOrCreateDataFlowNode(TInstruction instru } DataFlowGraph.Nodes.Add(node); + OffsetMap.Add(offset, node); } return node; diff --git a/src/Core/Echo.DataFlow/DataDependency.cs b/src/Core/Echo.DataFlow/DataDependency.cs index 66dd527c..e211ad11 100644 --- a/src/Core/Echo.DataFlow/DataDependency.cs +++ b/src/Core/Echo.DataFlow/DataDependency.cs @@ -10,12 +10,13 @@ namespace Echo.DataFlow /// nodes where the owner node might pull data from. /// /// The type of data source that this dependency uses. - /// The type of contents to put in a data flow node. - public abstract class DataDependency : ISet - where TSource : DataSource + /// The type of contents to put in a data flow node. + public abstract class DataDependency : ISet + where TSource : DataSource + where TInstruction : notnull { - private readonly List> _edges = new(); - private DataFlowNode _dependent; + private readonly List> _edges = new(); + private DataFlowNode? _dependent; /// public int Count => _edges.Count; @@ -31,7 +32,7 @@ public abstract class DataDependency : ISet /// /// Gets the node that owns the dependency. /// - public DataFlowNode Dependent + public DataFlowNode? Dependent { get => _dependent; internal set @@ -65,12 +66,12 @@ public bool Add(TSource item) if (item is null) throw new ArgumentNullException(nameof(item)); - if (item.Node.ParentGraph != _dependent.ParentGraph) + if (_dependent is null || item.Node.ParentGraph != _dependent.ParentGraph) throw new ArgumentException("Data source is not added to the same graph."); if (!Contains(item)) { - AddEdge(new(Dependent, item)); + AddEdge(new DataFlowEdge(_dependent, item)); return true; } @@ -206,7 +207,7 @@ public void UnionWith(IEnumerable other) Add(item); } - private void AddEdge(DataFlowEdge edge) + private void AddEdge(DataFlowEdge edge) { _edges.Add(edge); edge.DataSource.Node.IncomingEdges.Add(edge); @@ -268,7 +269,7 @@ public bool Remove(TSource item) /// /// The node. /// true if at least one edge was removed, false otherwise. - public bool Remove(DataFlowNode node) + public bool Remove(DataFlowNode node) { AssertDependentIsNotNull(); @@ -287,7 +288,7 @@ public bool Remove(DataFlowNode node) return changed; } - private void RemoveEdge(DataFlowEdge edge) + private void RemoveEdge(DataFlowEdge edge) { if (_edges.Remove(edge)) { @@ -307,12 +308,12 @@ IEnumerator IEnumerable.GetEnumerator() => /// Gets a collection of data flow edges that encode the stored data sources. /// /// The edges. - public IEnumerable> GetEdges() => _edges; + public IEnumerable> GetEdges() => _edges; /// /// Gets a collection of nodes that are possible data sources for the dependency. /// /// - public IEnumerable> GetNodes() => _edges.Select(e => e.DataSource.Node); + public IEnumerable> GetNodes() => _edges.Select(e => e.DataSource.Node); } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/DataFlowEdge.cs b/src/Core/Echo.DataFlow/DataFlowEdge.cs index da20669e..f52b058a 100644 --- a/src/Core/Echo.DataFlow/DataFlowEdge.cs +++ b/src/Core/Echo.DataFlow/DataFlowEdge.cs @@ -6,15 +6,16 @@ namespace Echo.DataFlow /// Represents an edge between two nodes in a data flow graph (DFG). The origin of the node represents the dependant, /// and the target of the node represents the dependency. /// - /// The type of information to store in each data flow node. - public class DataFlowEdge : IEdge + /// The type of instructions to store in each data flow node. + public class DataFlowEdge : IEdge + where TInstruction : notnull { /// /// Creates a new dependency edge between two nodes. /// /// The dependent node. /// The dependency node. - public DataFlowEdge(DataFlowNode dependent, DataSource target) + public DataFlowEdge(DataFlowNode dependent, DataSource target) { Dependent = dependent; DataSource = target; @@ -23,7 +24,7 @@ public DataFlowEdge(DataFlowNode dependent, DataSource tar /// /// Gets node that depends on the data source. /// - public DataFlowNode Dependent + public DataFlowNode Dependent { get; } @@ -33,7 +34,7 @@ public DataFlowNode Dependent /// /// Gets the data source this data flow edge points to. /// - public DataSource DataSource + public DataSource DataSource { get; } diff --git a/src/Core/Echo.DataFlow/DataFlowGraph.cs b/src/Core/Echo.DataFlow/DataFlowGraph.cs index 972f7a5e..09a9fedd 100644 --- a/src/Core/Echo.DataFlow/DataFlowGraph.cs +++ b/src/Core/Echo.DataFlow/DataFlowGraph.cs @@ -11,25 +11,26 @@ namespace Echo.DataFlow { /// - /// Represents a graph that encodes data dependencies between objects. An edge (A, B) indicates node A depends on - /// the evaluation of node B. + /// Represents a graph that encodes data dependencies between instructions. + /// An edge (A, B) indicates node A depends on the evaluation of node B. /// - /// The type of contents to store for each node. - public class DataFlowGraph : IGraph + /// The type of instruction to store in each node. + public class DataFlowGraph : IGraph + where TInstruction : notnull { /// /// Creates a new data flow graph. /// - public DataFlowGraph(IArchitecture architecture) + public DataFlowGraph(IArchitecture architecture) { Architecture = architecture ?? throw new ArgumentNullException(nameof(architecture)); - Nodes = new NodeCollection(this); + Nodes = new NodeCollection(this); } /// /// Gets the architecture of the instructions that are stored in the data flow graph. /// - public IArchitecture Architecture + public IArchitecture Architecture { get; } @@ -37,7 +38,7 @@ public IArchitecture Architecture /// /// Gets a collection of nodes that are present in the graph. /// - public NodeCollection Nodes + public NodeCollection Nodes { get; } @@ -69,8 +70,8 @@ public void ToDotGraph(TextWriter writer) var dotWriter = new DotWriter(writer) { NodeIdentifier = new IdentifiedNodeIdentifier(), - NodeAdorner = new DataFlowNodeAdorner(), - EdgeAdorner = new DataFlowEdgeAdorner() + NodeAdorner = new DataFlowNodeAdorner(), + EdgeAdorner = new DataFlowEdgeAdorner() }; dotWriter.Write(this); } diff --git a/src/Core/Echo.DataFlow/DataFlowNode.cs b/src/Core/Echo.DataFlow/DataFlowNode.cs index a872dca7..2baecb68 100644 --- a/src/Core/Echo.DataFlow/DataFlowNode.cs +++ b/src/Core/Echo.DataFlow/DataFlowNode.cs @@ -9,34 +9,26 @@ namespace Echo.DataFlow /// /// Represents a single node in a data flow graph. /// - /// The type of contents to store in the node. - public class DataFlowNode : IIdentifiedNode + /// The type of contents to store in the node. + public class DataFlowNode : IIdentifiedNode + where TInstruction : notnull { /// /// Creates a new data flow graph node. /// - /// A unique identifier for the node that can be used for indexing the node. - /// The contents of the node. - public DataFlowNode(long id, TContents contents) + /// The contents of the node. + public DataFlowNode(TInstruction? instruction) { - Id = id; - Contents = contents; - StackDependencies = new StackDependencyCollection(this); - VariableDependencies = new VariableDependencyCollection(this); - IncomingEdges = new List>(); + Instruction = instruction; + StackDependencies = new StackDependencyCollection(this); + VariableDependencies = new VariableDependencyCollection(this); + IncomingEdges = new List>(); } /// /// Gets the data flow graph this node is a part of. /// - public DataFlowGraph ParentGraph - { - get; - internal set; - } - - /// - public long Id + public DataFlowGraph? ParentGraph { get; internal set; @@ -52,11 +44,22 @@ public long Id /// Gets a value indicating whether the data flow node represents an external data source. /// public virtual bool IsExternal => false; + + /// + /// Gets or sets the offset associated to the data flow node. + /// + public long Offset + { + get; + set; + } + long IIdentifiedNode.Id => Offset; + /// /// Gets the contents of the node. /// - public TContents Contents + public TInstruction? Instruction { get; set; @@ -65,7 +68,7 @@ public TContents Contents /// /// Gets a collection of values allocated on a stack that this node depends on. /// - public StackDependencyCollection StackDependencies + public StackDependencyCollection StackDependencies { get; } @@ -73,12 +76,12 @@ public StackDependencyCollection StackDependencies /// /// Gets a collection of values that are assigned to variables that this node depends on. /// - public VariableDependencyCollection VariableDependencies + public VariableDependencyCollection VariableDependencies { get; } - internal List> IncomingEdges + internal List> IncomingEdges { get; } @@ -87,7 +90,7 @@ internal List> IncomingEdges /// Obtains a collection of edges that refer to dependent nodes. /// /// The edges. - public IEnumerable> GetIncomingEdges() => IncomingEdges; + public IEnumerable> GetIncomingEdges() => IncomingEdges; IEnumerable INode.GetIncomingEdges() => IncomingEdges; @@ -95,7 +98,7 @@ internal List> IncomingEdges /// Obtains a collection of edges encoding all the dependencies that this node has. /// /// The edges. - public IEnumerable> GetOutgoingEdges() + public IEnumerable> GetOutgoingEdges() { return StackDependencies .SelectMany(d => d.GetEdges()) @@ -108,10 +111,10 @@ public IEnumerable> GetOutgoingEdges() /// Obtains a collection of nodes that depend on this node. /// /// The dependant nodes. - public IEnumerable> GetDependants() => IncomingEdges + public IEnumerable> GetDependants() => IncomingEdges .Select(e => e.Dependent) .Distinct(); - + IEnumerable INode.GetPredecessors() => GetDependants(); IEnumerable INode.GetSuccessors() => GetOutgoingEdges() @@ -122,6 +125,16 @@ IEnumerable INode.GetSuccessors() => GetOutgoingEdges() bool INode.HasSuccessor(INode node) => GetOutgoingEdges().Any(e => e.DataSource.Node == node); + /// + /// Synchronizes the node's offset with the offset of the embedded instruction. + /// + public void UpdateOffset() + { + Offset = ParentGraph is not null && Instruction is not null + ? ParentGraph.Architecture.GetOffset(Instruction) + : 0; + } + /// /// Removes all incident edges (both incoming and outgoing edges) from the node, effectively isolating the node /// in the graph. @@ -139,14 +152,14 @@ public void Disconnect() dependency.Clear(); } - private static void RemoveIncomingEdge(DataFlowEdge edge) + private static void RemoveIncomingEdge(DataFlowEdge edge) { switch (edge.DataSource.Type) { case DataDependencyType.Stack: foreach (var dependency in edge.Dependent.StackDependencies) { - if (dependency.Remove((StackDataSource) edge.DataSource)) + if (dependency.Remove((StackDataSource) edge.DataSource)) break; } @@ -155,7 +168,7 @@ private static void RemoveIncomingEdge(DataFlowEdge edge) case DataDependencyType.Variable: foreach (var dependency in edge.Dependent.VariableDependencies) { - if (dependency.Remove((VariableDataSource) edge.DataSource)) + if (dependency.Remove((VariableDataSource) edge.DataSource)) break; } @@ -167,6 +180,6 @@ private static void RemoveIncomingEdge(DataFlowEdge edge) } /// - public override string ToString() => $"{Id:X8} ({Contents})"; + public override string ToString() => Offset.ToString("X8"); } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/DataSource.cs b/src/Core/Echo.DataFlow/DataSource.cs index e429edf0..f016c2ac 100644 --- a/src/Core/Echo.DataFlow/DataSource.cs +++ b/src/Core/Echo.DataFlow/DataSource.cs @@ -5,14 +5,15 @@ namespace Echo.DataFlow /// /// Represents a data source in a data flow graph. /// - /// The type of data stored in each data flow node. - public abstract class DataSource + /// The type of instruction stored in each data flow node. + public abstract class DataSource + where TInstruction : notnull { /// /// Creates a new data source. /// /// The node producing the data. - protected DataSource(DataFlowNode node) + protected DataSource(DataFlowNode node) { Node = node ?? throw new ArgumentNullException(nameof(node)); } @@ -20,7 +21,7 @@ protected DataSource(DataFlowNode node) /// /// Gets the data flow node that produced the data. /// - public DataFlowNode Node + public DataFlowNode Node { get; } @@ -37,17 +38,17 @@ public abstract DataDependencyType Type /// Determines whether the data sources are considered equal. /// /// The other data source. - protected virtual bool Equals(DataSource other) + protected virtual bool Equals(DataSource other) { return Equals(Node, other.Node); } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; - return obj.GetType() == GetType() && Equals((DataSource) obj); + return obj.GetType() == GetType() && Equals((DataSource) obj); } /// @@ -59,7 +60,7 @@ public override bool Equals(object obj) /// The first data source. /// The second data source. /// true if they are considered equal, false otherwise. - public static bool operator ==(DataSource a, DataSource b) + public static bool operator ==(DataSource? a, DataSource? b) { if (a is null && b is null) return true; @@ -74,7 +75,7 @@ public override bool Equals(object obj) /// The first data source. /// The second data source. /// true if they are not considered equal, false otherwise. - public static bool operator !=(DataSource a, DataSource b) + public static bool operator !=(DataSource a, DataSource b) { return !(a == b); } diff --git a/src/Core/Echo.DataFlow/Echo.DataFlow.csproj b/src/Core/Echo.DataFlow/Echo.DataFlow.csproj index c59fb477..0c2178db 100644 --- a/src/Core/Echo.DataFlow/Echo.DataFlow.csproj +++ b/src/Core/Echo.DataFlow/Echo.DataFlow.csproj @@ -7,6 +7,7 @@ program code analysis data-flow-graph slicing dependency-analysis true true + enable diff --git a/src/Core/Echo.DataFlow/Emulation/SymbolicProgramState.cs b/src/Core/Echo.DataFlow/Emulation/SymbolicProgramState.cs index dee936d9..74548d09 100644 --- a/src/Core/Echo.DataFlow/Emulation/SymbolicProgramState.cs +++ b/src/Core/Echo.DataFlow/Emulation/SymbolicProgramState.cs @@ -8,13 +8,14 @@ namespace Echo.DataFlow.Emulation /// /// Represents an immutable snapshot of a program state that is fully symbolic. /// - /// The type of instructions. - public readonly struct SymbolicProgramState + /// The type of instructions. + public readonly struct SymbolicProgramState + where TInstruction : notnull { /// /// Gets an empty program state. /// - public static SymbolicProgramState Empty + public static SymbolicProgramState Empty { get; } = new(0); @@ -26,8 +27,8 @@ public static SymbolicProgramState Empty public SymbolicProgramState(long programCounter) { ProgramCounter = programCounter; - Stack = ImmutableStack>.Empty; - Variables = ImmutableDictionary>.Empty; + Stack = ImmutableStack>.Empty; + Variables = ImmutableDictionary>.Empty; } /// @@ -37,11 +38,11 @@ public SymbolicProgramState(long programCounter) /// The initial stack state. public SymbolicProgramState( long programCounter, - ImmutableStack> stack) + ImmutableStack> stack) { ProgramCounter = programCounter; Stack = stack ?? throw new ArgumentNullException(nameof(stack)); - Variables = ImmutableDictionary>.Empty; + Variables = ImmutableDictionary>.Empty; } /// @@ -51,10 +52,10 @@ public SymbolicProgramState( /// The initial state of the variables. public SymbolicProgramState( long programCounter, - ImmutableDictionary> variables) + ImmutableDictionary> variables) { ProgramCounter = programCounter; - Stack = ImmutableStack>.Empty; + Stack = ImmutableStack>.Empty; Variables = variables ?? throw new ArgumentNullException(nameof(variables)); } @@ -66,8 +67,8 @@ public SymbolicProgramState( /// The initial state of the variables. public SymbolicProgramState( long programCounter, - ImmutableStack> stack, - ImmutableDictionary> variables) + ImmutableStack> stack, + ImmutableDictionary> variables) { ProgramCounter = programCounter; Stack = stack ?? throw new ArgumentNullException(nameof(stack)); @@ -85,7 +86,7 @@ public long ProgramCounter /// /// Gets the current stack state of the program. /// - public ImmutableStack> Stack + public ImmutableStack> Stack { get; } @@ -93,7 +94,7 @@ public ImmutableStack> Stack /// /// Gets the current variable state of the program. /// - public ImmutableDictionary> Variables + public ImmutableDictionary> Variables { get; } @@ -103,7 +104,7 @@ public ImmutableDictionary> Variables /// /// The new program counter. /// The new program state. - public SymbolicProgramState WithProgramCounter(long programCounter) => + public SymbolicProgramState WithProgramCounter(long programCounter) => new(programCounter, Stack, Variables); /// @@ -111,7 +112,7 @@ public SymbolicProgramState WithProgramCounter(long programCounter) => /// /// The new stack state. /// The new program state. - public SymbolicProgramState WithStack(ImmutableStack> stack) => + public SymbolicProgramState WithStack(ImmutableStack> stack) => new(ProgramCounter, stack, Variables); /// @@ -119,7 +120,7 @@ public SymbolicProgramState WithStack(ImmutableStack> stack) /// /// The new variables state. /// The new program state. - public SymbolicProgramState WithVariables(ImmutableDictionary> variables) => + public SymbolicProgramState WithVariables(ImmutableDictionary> variables) => new(ProgramCounter, Stack, variables); /// @@ -127,7 +128,7 @@ public SymbolicProgramState WithVariables(ImmutableDictionary /// The new value. /// The new program state. - public SymbolicProgramState Push(SymbolicValue value) => + public SymbolicProgramState Push(SymbolicValue value) => new(ProgramCounter, Stack.Push(value), Variables); /// @@ -136,7 +137,7 @@ public SymbolicProgramState Push(SymbolicValue value) => /// The popped value. /// Occurs when the stack is empty. /// The new program state. - public SymbolicProgramState Pop(out SymbolicValue value) + public SymbolicProgramState Pop(out SymbolicValue value) { if (Stack.IsEmpty) throw new StackImbalanceException(ProgramCounter); @@ -151,7 +152,7 @@ public SymbolicProgramState Pop(out SymbolicValue value) /// true if the state has changed, false otherwise. /// Occurs when the program counters do not match. /// Occurs when the stack heights do not match. - public bool MergeStates(in SymbolicProgramState otherState, out SymbolicProgramState newState) + public bool MergeStates(in SymbolicProgramState otherState, out SymbolicProgramState newState) { if (ProgramCounter != otherState.ProgramCounter) throw new ArgumentException("Input program state has a different program counter."); @@ -163,11 +164,11 @@ public bool MergeStates(in SymbolicProgramState otherState, out SymbolicProgr var newVariables = otherState.Variables; changed |= MergeVariables(ref newVariables); - newState = new SymbolicProgramState(ProgramCounter, newStack, newVariables); + newState = new SymbolicProgramState(ProgramCounter, newStack, newVariables); return changed; } - private bool MergeStacks(ref ImmutableStack> other) + private bool MergeStacks(ref ImmutableStack> other) { var stack1 = Stack; var stack2 = other; @@ -180,7 +181,7 @@ private bool MergeStacks(ref ImmutableStack> other) bool changed = false; - var result = new SymbolicValue[count]; + var result = new SymbolicValue[count]; count--; while(count >= 0) @@ -194,7 +195,7 @@ private bool MergeStacks(ref ImmutableStack> other) var newValue = value1; if (!value1.SetEquals(value2)) { - newValue = new SymbolicValue(value1, value2); + newValue = new SymbolicValue(value1, value2); changed = true; } @@ -208,7 +209,7 @@ private bool MergeStacks(ref ImmutableStack> other) return changed; } - private bool MergeVariables(ref ImmutableDictionary> newVariables) + private bool MergeVariables(ref ImmutableDictionary> newVariables) { var result = Variables; bool changed = false; @@ -227,7 +228,7 @@ private bool MergeVariables(ref ImmutableDictionary> else if (!value.SetEquals(otherValue)) { // Variable does exist but has different data sources. Create new merged symbolic value. - var newValue = new SymbolicValue(value, otherValue); + var newValue = new SymbolicValue(value, otherValue); result = result.SetItem(variable, newValue); changed = true; } diff --git a/src/Core/Echo.DataFlow/Emulation/SymbolicValue.cs b/src/Core/Echo.DataFlow/Emulation/SymbolicValue.cs index d69b8575..6be5a401 100644 --- a/src/Core/Echo.DataFlow/Emulation/SymbolicValue.cs +++ b/src/Core/Echo.DataFlow/Emulation/SymbolicValue.cs @@ -9,7 +9,8 @@ namespace Echo.DataFlow.Emulation /// /// Represents a symbolic value that resides in memory. /// - public sealed class SymbolicValue : ISet> + public sealed class SymbolicValue : ISet> + where TInstruction : notnull { // ------------------------- // Implementation rationale: @@ -26,7 +27,7 @@ public sealed class SymbolicValue : ISet> // - DataSource: The dependency has a single data source. // - HashSet>: The dependency has multiple data sources. - private object _listObject; + private object? _listObject; /// /// Creates a new symbolic value with no data sources. @@ -40,7 +41,7 @@ public SymbolicValue() /// Creates a new symbolic value with a single data source. /// /// The data source of the symbolic value. - public SymbolicValue(DataSource dataSource) + public SymbolicValue(DataSource dataSource) { _listObject = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); } @@ -49,15 +50,15 @@ public SymbolicValue(DataSource dataSource) /// Creates a new symbolic value with the provided data sources. /// /// The data sources of the symbolic value. - public SymbolicValue(IEnumerable> dataSources) + public SymbolicValue(IEnumerable> dataSources) { - _listObject = new HashSet>(dataSources); + _listObject = new HashSet>(dataSources); } /// /// Merges two data dependencies into one symbolic value. /// - public SymbolicValue(SymbolicValue left, SymbolicValue right) + public SymbolicValue(SymbolicValue left, SymbolicValue right) { int totalCount = left.Count + right.Count; switch (totalCount) @@ -71,7 +72,7 @@ public SymbolicValue(SymbolicValue left, SymbolicValue right) break; default: - var set = new HashSet>(left); + var set = new HashSet>(left); set.UnionWith(right); _listObject = set; break; @@ -82,8 +83,8 @@ public SymbolicValue(SymbolicValue left, SymbolicValue right) public int Count => _listObject switch { null => 0, - DataSource _ => 1, - ICollection> collection => collection.Count, + DataSource _ => 1, + ICollection> collection => collection.Count, _ => throw new InvalidOperationException("Data dependency is in an invalid state.") }; @@ -100,8 +101,8 @@ public SymbolicValue(SymbolicValue left, SymbolicValue right) /// /// The node producing the value. /// The symbolic value. - public static SymbolicValue CreateStackValue(DataFlowNode node) => - new(new StackDataSource(node, 0)); + public static SymbolicValue CreateStackValue(DataFlowNode node) => + new(new StackDataSource(node, 0)); /// /// Creates a new symbolic value referencing a stack value produced by the provided node. @@ -109,8 +110,8 @@ public static SymbolicValue CreateStackValue(DataFlowNode node) => /// The node producing the value. /// The index of the stack value that was produced by the node. /// The symbolic value. - public static SymbolicValue CreateStackValue(DataFlowNode node, int slotIndex) => - new(new StackDataSource(node, slotIndex)); + public static SymbolicValue CreateStackValue(DataFlowNode node, int slotIndex) => + new(new StackDataSource(node, slotIndex)); /// /// Creates a new symbolic value referencing a variable value assigned by the provided node. @@ -118,20 +119,20 @@ public static SymbolicValue CreateStackValue(DataFlowNode node, int slotIn /// The node assigning the value. /// The variable that was assigned a value. /// The symbolic value. - public static SymbolicValue CreateVariableValue(DataFlowNode node, IVariable variable) => - new(new VariableDataSource(node, variable)); + public static SymbolicValue CreateVariableValue(DataFlowNode node, IVariable variable) => + new(new VariableDataSource(node, variable)); /// /// Interprets the symbolic value as a collection of stack data sources. /// /// The stack data sources. - public IEnumerable> AsStackValue() => this.Cast>(); + public IEnumerable> AsStackValue() => this.Cast>(); /// /// Interprets the symbolic value as a collection of variable data sources. /// /// The variable data sources. - public IEnumerable> AsVariableValue() => this.Cast>(); + public IEnumerable> AsVariableValue() => this.Cast>(); private static bool ThrowInvalidStateException() => throw new InvalidOperationException("Data dependency is in an invalid state."); @@ -143,7 +144,7 @@ private void AssertIsWritable() } /// - public bool Add(DataSource item) + public bool Add(DataSource item) { AssertIsWritable(); @@ -153,18 +154,18 @@ public bool Add(DataSource item) _listObject = item; return true; - case DataSource node: + case DataSource node: if (node == item) return false; - _listObject = new HashSet> + _listObject = new HashSet> { node, item }; return true; - case ISet> nodes: + case ISet> nodes: return nodes.Add(item); default: @@ -173,7 +174,7 @@ public bool Add(DataSource item) } /// - public void ExceptWith(IEnumerable> other) + public void ExceptWith(IEnumerable> other) { AssertIsWritable(); @@ -182,11 +183,11 @@ public void ExceptWith(IEnumerable> other) } /// - public void IntersectWith(IEnumerable> other) + public void IntersectWith(IEnumerable> other) { AssertIsWritable(); - var set = new HashSet>(other); + var set = new HashSet>(other); foreach (var item in this) { if (!set.Contains(item)) @@ -195,14 +196,14 @@ public void IntersectWith(IEnumerable> other) } /// - public bool IsProperSubsetOf(IEnumerable> other) + public bool IsProperSubsetOf(IEnumerable> other) { switch (_listObject) { case null: return other.Any(); - case DataSource node: + case DataSource node: bool containsElement = false; foreach (var item in other) { @@ -214,7 +215,7 @@ public bool IsProperSubsetOf(IEnumerable> other) return false; - case ISet> nodes: + case ISet> nodes: return nodes.IsProperSubsetOf(other); default: @@ -223,38 +224,38 @@ public bool IsProperSubsetOf(IEnumerable> other) } /// - public bool IsProperSupersetOf(IEnumerable> other) => _listObject switch + public bool IsProperSupersetOf(IEnumerable> other) => _listObject switch { null => false, - DataSource _ => !other.Any(), - ISet> nodes => nodes.IsProperSupersetOf(other), + DataSource _ => !other.Any(), + ISet> nodes => nodes.IsProperSupersetOf(other), _ => ThrowInvalidStateException(), }; /// - public bool IsSubsetOf(IEnumerable> other) => _listObject switch + public bool IsSubsetOf(IEnumerable> other) => _listObject switch { null => true, - DataSource node => other.Contains(node), - ISet> nodes => nodes.IsSubsetOf(other), + DataSource node => other.Contains(node), + ISet> nodes => nodes.IsSubsetOf(other), _ => ThrowInvalidStateException(), }; /// - public bool IsSupersetOf(IEnumerable> other) + public bool IsSupersetOf(IEnumerable> other) { switch (_listObject) { case null: return !other.Any(); - case DataSource node: + case DataSource node: { using var enumerator = other.GetEnumerator(); return !enumerator.MoveNext() || enumerator.Current == node && !enumerator.MoveNext(); } - case ISet> nodes: + case ISet> nodes: return nodes.IsSupersetOf(other); default: @@ -263,18 +264,18 @@ public bool IsSupersetOf(IEnumerable> other) } /// - public bool Overlaps(IEnumerable> other) => _listObject switch + public bool Overlaps(IEnumerable> other) => _listObject switch { null => false, - DataSource node => other.Contains(node), - ISet> nodes => nodes.Overlaps(other), + DataSource node => other.Contains(node), + ISet> nodes => nodes.Overlaps(other), _ => ThrowInvalidStateException(), }; /// - public bool SetEquals(IEnumerable> other) + public bool SetEquals(IEnumerable> other) { - if (other is SymbolicValue otherSymbolicValue) + if (other is SymbolicValue otherSymbolicValue) return SetEqualsFast(otherSymbolicValue); switch (_listObject) @@ -282,13 +283,13 @@ public bool SetEquals(IEnumerable> other) case null: return !other.Any(); - case DataSource node: + case DataSource node: { using var enumerator = other.GetEnumerator(); return enumerator.MoveNext() && node == enumerator.Current && !enumerator.MoveNext(); } - case ISet> nodes: + case ISet> nodes: return nodes.SetEquals(other); default: @@ -296,18 +297,18 @@ public bool SetEquals(IEnumerable> other) } } - private bool SetEqualsFast(SymbolicValue other) + private bool SetEqualsFast(SymbolicValue other) { switch (_listObject) { case null: return other._listObject is null; - case DataSource node: - return other._listObject is DataSource otherSource && node == otherSource; + case DataSource node: + return other._listObject is DataSource otherSource && node == otherSource; - case ISet> nodes: - return other._listObject is ISet> otherSet && nodes.SetEquals(otherSet); + case ISet> nodes: + return other._listObject is ISet> otherSet && nodes.SetEquals(otherSet); default: return ThrowInvalidStateException(); @@ -315,7 +316,7 @@ private bool SetEqualsFast(SymbolicValue other) } /// - public void SymmetricExceptWith(IEnumerable> other) + public void SymmetricExceptWith(IEnumerable> other) { foreach (var item in other) { @@ -327,14 +328,14 @@ public void SymmetricExceptWith(IEnumerable> other) } /// - public void UnionWith(IEnumerable> other) + public void UnionWith(IEnumerable> other) { AssertIsWritable(); foreach (var node in other) Add(node); } - void ICollection>.Add(DataSource item) + void ICollection>.Add(DataSource item) { if (item is null) throw new ArgumentNullException(nameof(item)); @@ -350,11 +351,11 @@ public void Clear() case null: break; - case DataSource node: + case DataSource node: Remove(node); break; - case ICollection> nodes: + case ICollection> nodes: foreach (var node in nodes.ToArray()) Remove(node); break; @@ -368,27 +369,27 @@ public void Clear() } /// - public bool Contains(DataSource item) => _listObject switch + public bool Contains(DataSource item) => _listObject switch { null => false, - DataSource node => item == node, - ICollection> nodes => nodes.Contains(item), + DataSource node => item == node, + ICollection> nodes => nodes.Contains(item), _ => ThrowInvalidStateException() }; /// - public void CopyTo(DataSource[] array, int arrayIndex) + public void CopyTo(DataSource[] array, int arrayIndex) { switch (_listObject) { case null: break; - case DataSource node: + case DataSource node: array[arrayIndex] = node; break; - case ICollection> nodes: + case ICollection> nodes: nodes.CopyTo(array, arrayIndex); break; @@ -403,11 +404,11 @@ public void CopyTo(DataSource[] array, int arrayIndex) /// /// The node to remove all data sources from. /// true if any data source was removed, false otherwise. - public bool Remove(DataFlowNode node) + public bool Remove(DataFlowNode node) { AssertIsWritable(); - var sourcesToRemove = new List>(); + var sourcesToRemove = new List>(); foreach (var source in this) { @@ -422,7 +423,7 @@ public bool Remove(DataFlowNode node) } /// - public bool Remove(DataSource item) + public bool Remove(DataSource item) { AssertIsWritable(); @@ -431,7 +432,7 @@ public bool Remove(DataSource item) case null: return false; - case DataSource node: + case DataSource node: if (node == item) { _listObject = null; @@ -440,7 +441,7 @@ public bool Remove(DataSource item) return false; - case ICollection> nodes: + case ICollection> nodes: return nodes.Remove(item); default: @@ -451,7 +452,7 @@ public bool Remove(DataSource item) /// /// Gets a collection of nodes that were referenced by all data sources in this data dependency. /// - public IEnumerable> GetNodes() => this + public IEnumerable> GetNodes() => this .Select(source => source.Node) .Distinct(); @@ -459,8 +460,8 @@ public IEnumerable> GetNodes() => this public override string ToString() => _listObject switch { null => "?", - DataSource node => node.ToString(), - IEnumerable> collection => $"({string.Join(" | ", collection)})", + DataSource node => node.ToString(), + IEnumerable> collection => $"({string.Join(" | ", collection)})", _ => ThrowInvalidStateException().ToString() }; @@ -470,32 +471,32 @@ public IEnumerable> GetNodes() => this /// The enumerator. public Enumerator GetEnumerator() => new (this); - IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); + IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Provides a mechanism for enumerating all data sources within a single symbolic value. /// - public struct Enumerator : IEnumerator> + public struct Enumerator : IEnumerator> { - private readonly SymbolicValue _collection; - private HashSet>.Enumerator _setEnumerator; + private readonly SymbolicValue _collection; + private HashSet>.Enumerator _setEnumerator; /// /// Creates a new instance of the structure. /// /// The data dependency to enumerate the data sources for. - public Enumerator(SymbolicValue collection) + public Enumerator(SymbolicValue collection) { _collection = collection ?? throw new ArgumentNullException(nameof(collection)); - if (collection._listObject is HashSet> nodes) + if (collection._listObject is HashSet> nodes) _setEnumerator = nodes.GetEnumerator(); - Current = null; + Current = null!; } /// - public DataSource Current + public DataSource Current { get; private set; @@ -514,7 +515,7 @@ public bool MoveNext() case null: return false; - case DataSource source: + case DataSource source: if (Current is null) { Current = source; @@ -526,13 +527,12 @@ public bool MoveNext() default: if (_setEnumerator.MoveNext()) { - Current = _setEnumerator.Current; + Current = _setEnumerator.Current!; return true; } break; } - Current = null; return false; } diff --git a/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs b/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs index 62c42609..b9b2e0d9 100644 --- a/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs +++ b/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs @@ -1,41 +1,26 @@ -using System; - namespace Echo.DataFlow { /// /// Represents an external data source in a data flow graph. /// - /// The type of contents to store in the node. - public class ExternalDataSourceNode : DataFlowNode + /// The type of instructions to store in the node. + public class ExternalDataSourceNode : DataFlowNode + where TInstruction : notnull { /// /// Creates a new external data source. /// - /// The unique identifier of the data source. This should be a negative number. - /// The display name of the external data source. - public ExternalDataSourceNode(long id, string name) - : this(id, name, default) - { - } - - /// - /// Creates a new external data source. - /// - /// The unique identifier of the data source. This should be a negative number. - /// The display name of the external data source. - /// The contents of the data flow node. - public ExternalDataSourceNode(long id, string name, TContents contents) - : base(id, contents) + /// The external data source. + public ExternalDataSourceNode(object source) + : base(default) { - if (id >= 0) - throw new ArgumentException("Identifiers of external data sources should be negative."); - Name = name; + Source = source; } /// /// Gets the name of the auxiliary data flow node. /// - public string Name + public object Source { get; } @@ -44,6 +29,6 @@ public string Name public override bool IsExternal => true; /// - public override string ToString() => Name; + public override string ToString() => Source.ToString(); } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowEdgeAdorner.cs b/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowEdgeAdorner.cs index 25802092..fb0b94dd 100644 --- a/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowEdgeAdorner.cs +++ b/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowEdgeAdorner.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Echo.Graphing; using Echo.Graphing.Serialization.Dot; @@ -8,8 +7,9 @@ namespace Echo.DataFlow.Serialization.Dot /// /// Represents an adorner that styles edges in a data flow graph. /// - /// The type of contents the nodes contain. - public class DataFlowEdgeAdorner : IDotEdgeAdorner + /// The type of instructions the nodes contain. + public class DataFlowEdgeAdorner : IDotEdgeAdorner + where TInstruction : notnull { /// /// Gets or sets the edge style to use for edges representing stack dependencies. @@ -50,25 +50,25 @@ public bool IncludeVariableEdgeLabels } = true; /// - public IDictionary GetEdgeAttributes(IEdge edge, long sourceId, long targetId) + public IDictionary? GetEdgeAttributes(IEdge edge, long sourceId, long targetId) { - if (edge is DataFlowEdge e) + if (edge is DataFlowEdge e) { var result = new Dictionary(); - (var style, string label) = e.DataSource switch + (var style, string? label) = e.DataSource switch { - StackDataSource source => (StackDependencyStyle, source.SlotIndex.ToString()), - VariableDataSource source => (VariableDependencyStyle, source.Variable.Name), + StackDataSource source => (StackDependencyStyle, source.SlotIndex.ToString()), + VariableDataSource source => (VariableDependencyStyle, source.Variable.Name), _ => default }; if (!string.IsNullOrEmpty(style.Color)) - result["color"] = style.Color; + result["color"] = style.Color!; if (!string.IsNullOrEmpty(style.Style)) - result["style"] = style.Style; + result["style"] = style.Style!; if (!string.IsNullOrEmpty(label)) - result["label"] = label; + result["label"] = label!; return result; } diff --git a/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowNodeAdorner.cs b/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowNodeAdorner.cs index a0ff22f0..9df3d473 100644 --- a/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowNodeAdorner.cs +++ b/src/Core/Echo.DataFlow/Serialization/Dot/DataFlowNodeAdorner.cs @@ -7,8 +7,9 @@ namespace Echo.DataFlow.Serialization.Dot /// /// Represents an adorner that adds the string representation of the embedded instructions to a node in a graph. /// - /// The type of instructions the nodes contain. - public class DataFlowNodeAdorner : IDotNodeAdorner + /// The type of instructions the nodes contain. + public class DataFlowNodeAdorner : IDotNodeAdorner + where TInstruction : notnull { /// /// Gets or sets the shape of the node. @@ -20,22 +21,22 @@ public string NodeShape } = "box3d"; /// - public IDictionary GetNodeAttributes(INode node, long id) + public IDictionary? GetNodeAttributes(INode node, long id) { switch (node) { - case ExternalDataSourceNode externalDataSource: + case ExternalDataSourceNode externalDataSource: return new Dictionary { ["shape"] = NodeShape, - ["label"] = externalDataSource.Name + ["label"] = externalDataSource.Source.ToString() }; - case DataFlowNode dataFlowNode: + case DataFlowNode dataFlowNode: return new Dictionary { ["shape"] = NodeShape, - ["label"] = dataFlowNode.Contents.ToString() + ["label"] = dataFlowNode.Instruction?.ToString() ?? dataFlowNode.Offset.ToString("X8") }; default: diff --git a/src/Core/Echo.DataFlow/StackDataSource.cs b/src/Core/Echo.DataFlow/StackDataSource.cs index 6fd7f021..5b22e84b 100644 --- a/src/Core/Echo.DataFlow/StackDataSource.cs +++ b/src/Core/Echo.DataFlow/StackDataSource.cs @@ -3,14 +3,15 @@ namespace Echo.DataFlow /// /// Represents a data source that refers to a stack value produced by a node in a data flow graph. /// - /// The type of data stored in each data flow node. - public class StackDataSource : DataSource + /// The type of data stored in each data flow node. + public class StackDataSource : DataSource + where TInstruction : notnull { /// /// Creates a new stack data source, referencing the first stack value produced by the provided node. /// /// The node producing the value. - public StackDataSource(DataFlowNode node) + public StackDataSource(DataFlowNode node) : base(node) { } @@ -20,7 +21,7 @@ public StackDataSource(DataFlowNode node) /// /// The node producing the value. /// The index of the stack value that was produced by the node. - public StackDataSource(DataFlowNode node, int slotIndex) + public StackDataSource(DataFlowNode node, int slotIndex) : base(node) { SlotIndex = slotIndex; @@ -38,12 +39,12 @@ public int SlotIndex public override DataDependencyType Type => DataDependencyType.Stack; /// - public override string ToString() => $"{Node.Id:X8}#{SlotIndex}"; + public override string ToString() => $"{Node.Offset:X8}#{SlotIndex}"; /// - protected override bool Equals(DataSource other) + protected override bool Equals(DataSource other) { - return base.Equals(other) && other is StackDataSource source && source.SlotIndex == SlotIndex; + return base.Equals(other) && other is StackDataSource source && source.SlotIndex == SlotIndex; } /// diff --git a/src/Core/Echo.DataFlow/StackDependency.cs b/src/Core/Echo.DataFlow/StackDependency.cs index 7c03b0f7..209730bc 100644 --- a/src/Core/Echo.DataFlow/StackDependency.cs +++ b/src/Core/Echo.DataFlow/StackDependency.cs @@ -5,17 +5,18 @@ namespace Echo.DataFlow /// /// Represents a collection of data sources for a single stack slot dependency of a node. /// - /// The type of contents to put in a data flow node. - public class StackDependency : DataDependency, TContents> + /// The type of instructions to put in a data flow node. + public class StackDependency : DataDependency, TInstruction> + where TInstruction : notnull { /// /// Adds a data source to the dependency, referencing the first stack value produced by the provided node. /// /// The node producing the value. /// The stack data source. - public StackDataSource Add(DataFlowNode node) + public StackDataSource Add(DataFlowNode node) { - var source = new StackDataSource(node); + var source = new StackDataSource(node); return Add(source) ? source : this.First(x => x.Equals(source)); @@ -27,9 +28,9 @@ public StackDataSource Add(DataFlowNode node) /// The node producing the value. /// The index of the stack value that was produced by the node. /// The stack data source. - public StackDataSource Add(DataFlowNode node, int slotIndex) + public StackDataSource Add(DataFlowNode node, int slotIndex) { - var source = new StackDataSource(node, slotIndex); + var source = new StackDataSource(node, slotIndex); return Add(source) ? source : this.First(x => x.Equals(source)); diff --git a/src/Core/Echo.DataFlow/VariableDataSource.cs b/src/Core/Echo.DataFlow/VariableDataSource.cs index c7ceef82..b3b7f157 100644 --- a/src/Core/Echo.DataFlow/VariableDataSource.cs +++ b/src/Core/Echo.DataFlow/VariableDataSource.cs @@ -6,15 +6,16 @@ namespace Echo.DataFlow /// /// Represents a data source that refers to a variable value assigned by a node in a data flow graph. /// - /// The type of data stored in each data flow node. - public class VariableDataSource : DataSource + /// The type of data stored in each data flow node. + public class VariableDataSource : DataSource + where TInstruction : notnull { /// /// Creates a new variable data source referencing a variable value assigned by the provided node. /// /// The node assigning the value. /// The variable that was assigned a value. - public VariableDataSource(DataFlowNode node, IVariable variable) + public VariableDataSource(DataFlowNode node, IVariable variable) : base(node) { Variable = variable ?? throw new ArgumentNullException(nameof(variable)); @@ -32,6 +33,6 @@ public IVariable Variable public override DataDependencyType Type => DataDependencyType.Variable; /// - public override string ToString() => $"{Node.Id:X8}:{Variable.Name}"; + public override string ToString() => $"{Node.Offset:X8}:{Variable.Name}"; } } \ No newline at end of file diff --git a/src/Core/Echo.DataFlow/VariableDependency.cs b/src/Core/Echo.DataFlow/VariableDependency.cs index 428c3fc6..7eeb91bf 100644 --- a/src/Core/Echo.DataFlow/VariableDependency.cs +++ b/src/Core/Echo.DataFlow/VariableDependency.cs @@ -7,8 +7,9 @@ namespace Echo.DataFlow /// /// Represents a collection of data sources for a single variable dependency of a node. /// - /// The type of contents to put in a data flow node. - public class VariableDependency : DataDependency, TContents> + /// The type of instruction to put in a data flow node. + public class VariableDependency : DataDependency, TInstruction> + where TInstruction : notnull { /// /// Creates a new variable dependency. @@ -32,9 +33,9 @@ public IVariable Variable /// /// The node assigning the value. /// The variable data source. - public VariableDataSource Add(DataFlowNode node) + public VariableDataSource Add(DataFlowNode node) { - var source = new VariableDataSource(node, Variable); + var source = new VariableDataSource(node, Variable); return Add(source) ? source : this.First(x => x.Equals(source)); diff --git a/src/Core/Echo/Code/IArchitecture.cs b/src/Core/Echo/Code/IArchitecture.cs index 093c384a..6ba8fdf1 100644 --- a/src/Core/Echo/Code/IArchitecture.cs +++ b/src/Core/Echo/Code/IArchitecture.cs @@ -8,6 +8,7 @@ namespace Echo.Code /// /// The type of the instruction model this architecture describes. public interface IArchitecture + where TInstruction : notnull { /// /// Gets the offset of an instruction. diff --git a/src/Core/Echo/Code/IPurityClassifier.cs b/src/Core/Echo/Code/IPurityClassifier.cs index 460cf702..feb13dc3 100644 --- a/src/Core/Echo/Code/IPurityClassifier.cs +++ b/src/Core/Echo/Code/IPurityClassifier.cs @@ -5,6 +5,7 @@ namespace Echo.Code /// /// The type of instructions. public interface IPurityClassifier + where TInstruction : notnull { /// /// Gets a value indicating whether a particular instruction is considered pure, that is, has no side effects. diff --git a/src/Core/Echo/Code/IStaticInstructionProvider.cs b/src/Core/Echo/Code/IStaticInstructionProvider.cs index edbf6a64..20a1d683 100644 --- a/src/Core/Echo/Code/IStaticInstructionProvider.cs +++ b/src/Core/Echo/Code/IStaticInstructionProvider.cs @@ -5,6 +5,7 @@ namespace Echo.Code /// /// The type of instructions that this collection provides. public interface IStaticInstructionProvider + where TInstruction : notnull { /// /// Gets the architecture describing the instructions exposed by this instruction provider. diff --git a/src/Core/Echo/Code/ListInstructionProvider.cs b/src/Core/Echo/Code/ListInstructionProvider.cs index 0715d456..fffd3ec8 100644 --- a/src/Core/Echo/Code/ListInstructionProvider.cs +++ b/src/Core/Echo/Code/ListInstructionProvider.cs @@ -8,6 +8,7 @@ namespace Echo.Code /// /// The type of instructions to store. public class ListInstructionProvider : IStaticInstructionProvider + where TInstruction : notnull { private readonly IDictionary _instructions = new Dictionary(); diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs index fca7dcd1..e585886a 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilStateTransitioner.cs @@ -34,28 +34,18 @@ public override SymbolicProgramState GetInitialState(long entryp for (int i = 0; i < _architecture.MethodBody.ExceptionHandlers.Count; i++) { var handler = _architecture.MethodBody.ExceptionHandlers[i]; - if (handler.HandlerType == CilExceptionHandlerType.Fault - || handler.HandlerType == CilExceptionHandlerType.Finally) - { + if (handler.HandlerType is CilExceptionHandlerType.Fault or CilExceptionHandlerType.Finally) continue; - } var exceptionSource = default(ExternalDataSourceNode); - if (handler.HandlerStart!.Offset == entrypointAddress) - { - exceptionSource = new ExternalDataSourceNode( - -(long) handler.HandlerStart.Offset, - $"HandlerException_{handler.HandlerStart.Offset:X4}"); - } - else if (handler.FilterStart != null && handler.FilterStart.Offset == entrypointAddress) + if (handler.HandlerStart is not null && handler.HandlerStart.Offset == entrypointAddress + || handler.FilterStart is not null && handler.FilterStart.Offset == entrypointAddress) { - exceptionSource = new ExternalDataSourceNode( - -(long) handler.FilterStart.Offset, - $"FilterException_{handler.FilterStart.Offset:X4}"); + exceptionSource = new ExternalDataSourceNode(handler); } - if (exceptionSource is { }) + if (exceptionSource is not null) { DataFlowGraph.Nodes.Add(exceptionSource); result = result.Push(new SymbolicValue( diff --git a/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs b/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs index 4fe1c483..a802aced 100644 --- a/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs +++ b/src/Platforms/Echo.Platforms.Dnlib/CilStateTransitioner.cs @@ -42,17 +42,10 @@ public override SymbolicProgramState GetInitialState(long entrypoin var exceptionSource = default(ExternalDataSourceNode); - if (handler.HandlerStart!.Offset == entrypointAddress) + if (handler.HandlerStart is not null && handler.HandlerStart.Offset == entrypointAddress + || handler.FilterStart is not null && handler.FilterStart.Offset == entrypointAddress) { - exceptionSource = new ExternalDataSourceNode( - -(long) handler.HandlerStart.Offset, - $"HandlerException_{handler.HandlerStart.Offset:X4}"); - } - else if (handler.FilterStart != null && handler.FilterStart.Offset == entrypointAddress) - { - exceptionSource = new ExternalDataSourceNode( - -(long) handler.FilterStart.Offset, - $"FilterException_{handler.FilterStart.Offset:X4}"); + exceptionSource = new ExternalDataSourceNode(handler); } if (exceptionSource is { }) diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/StaticGraphBuilderTest.cs similarity index 99% rename from test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs rename to test/Core/Echo.ControlFlow.Tests/Construction/StaticGraphBuilderTest.cs index f8dcde29..2926a71d 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/StaticGraphBuilderTest.cs @@ -5,7 +5,7 @@ using Echo.Platforms.DummyPlatform.Code; using Xunit; -namespace Echo.ControlFlow.Tests.Construction.Static +namespace Echo.ControlFlow.Tests.Construction { public class StaticGraphBuilderTest { diff --git a/test/Core/Echo.DataFlow.Tests/Analysis/DependencyCollectionTest.cs b/test/Core/Echo.DataFlow.Tests/Analysis/DependencyCollectionTest.cs index 609ae890..50caf855 100644 --- a/test/Core/Echo.DataFlow.Tests/Analysis/DependencyCollectionTest.cs +++ b/test/Core/Echo.DataFlow.Tests/Analysis/DependencyCollectionTest.cs @@ -12,7 +12,7 @@ public class DependencyCollectionTest public void NoDependencies() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); + var n1 = dfg.Nodes.Add(1); Assert.Equal(new[] { @@ -24,8 +24,8 @@ public void NoDependencies() public void SingleStackDependency() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); n2.StackDependencies.SetCount(1); n2.StackDependencies[0].Add(n1); @@ -40,9 +40,9 @@ public void SingleStackDependency() public void MultipleStackDependenciesShouldResultInOrder() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3 , 3); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); n3.StackDependencies.SetCount(2); n3.StackDependencies[0].Add(n1); @@ -58,9 +58,9 @@ public void MultipleStackDependenciesShouldResultInOrder() public void PathStackDependencyGraphShouldResultInOrder() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); n3.StackDependencies.Add(new StackDependency()); n3.StackDependencies[0].Add(n2); @@ -77,11 +77,11 @@ public void PathStackDependencyGraphShouldResultInOrder() public void TreeStackDependencyGraphShouldResultInOrder() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); - var n5 = dfg.Nodes.Add(5, 5); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); + var n5 = dfg.Nodes.Add(5); n5.StackDependencies.SetCount(2); n5.StackDependencies[0].Add(n3); @@ -101,11 +101,11 @@ public void TreeStackDependencyGraphShouldResultInOrder() public void MirroredTreeStackDependencyGraphShouldResultInOrder() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); - var n5 = dfg.Nodes.Add(5, 5); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); + var n5 = dfg.Nodes.Add(5); n5.StackDependencies.SetCount(2); n5.StackDependencies[0].Add(n3); @@ -125,10 +125,10 @@ public void MirroredTreeStackDependencyGraphShouldResultInOrder() public void ConvergingEvenDependencyPathsShouldFinishSubPathsBeforeGoingDeeper() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); n4.StackDependencies.SetCount(2); n4.StackDependencies[0].Add(n2); @@ -150,12 +150,12 @@ public void ConvergingEvenDependencyPathsShouldFinishSubPathsBeforeGoingDeeper() public void ConvergingShortLongDependencyPathsShouldFinishSubPathsBeforeGoingDeeper() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); - var n5 = dfg.Nodes.Add(5, 5); - var n6 = dfg.Nodes.Add(6, 6); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); + var n5 = dfg.Nodes.Add(5); + var n6 = dfg.Nodes.Add(6); n6.StackDependencies.SetCount(2); n6.StackDependencies[0].Add(n4); @@ -183,7 +183,7 @@ public void ConvergingShortLongDependencyPathsShouldFinishSubPathsBeforeGoingDee public void SelfLoopShouldThrowCyclicDependencyException() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); + var n1 = dfg.Nodes.Add(1); n1.StackDependencies.SetCount(1); n1.StackDependencies[0].Add(n1); @@ -195,8 +195,8 @@ public void SelfLoopShouldThrowCyclicDependencyException() public void ShortLoopShouldThrowCyclicDependencyException() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); n1.StackDependencies.SetCount(1); n1.StackDependencies[0].Add(n2); @@ -211,10 +211,10 @@ public void ShortLoopShouldThrowCyclicDependencyException() public void LongLoopShouldThrowCyclicDependencyException() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); n1.StackDependencies.SetCount(1); n1.StackDependencies[0].Add(n2); @@ -237,11 +237,11 @@ public void IgnoreVariableDependenciesWhenNotIncluded() var variable = new DummyVariable("v1"); var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); - var n5 = dfg.Nodes.Add(5, 5); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); + var n5 = dfg.Nodes.Add(5); n1.StackDependencies.SetCount(1); n1.StackDependencies[0].Add(n2); @@ -269,11 +269,11 @@ public void IgnoreStackDependenciesWhenNotIncluded() var variable = new DummyVariable("v1"); var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - var n3 = dfg.Nodes.Add(3, 3); - var n4 = dfg.Nodes.Add(4, 4); - var n5 = dfg.Nodes.Add(5, 5); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); + var n3 = dfg.Nodes.Add(3); + var n4 = dfg.Nodes.Add(4); + var n5 = dfg.Nodes.Add(5); n1.StackDependencies.SetCount(1); n1.StackDependencies[0].Add(n2); diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs b/test/Core/Echo.DataFlow.Tests/Construction/SymbolicGraphBuilderTest.cs similarity index 81% rename from test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs rename to test/Core/Echo.DataFlow.Tests/Construction/SymbolicGraphBuilderTest.cs index e29d87bd..bb8ffb2c 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Symbolic/SymbolicGraphBuilderTest.cs +++ b/test/Core/Echo.DataFlow.Tests/Construction/SymbolicGraphBuilderTest.cs @@ -1,15 +1,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Echo.ControlFlow; using Echo.ControlFlow.Construction; -using Echo.DataFlow; using Echo.DataFlow.Construction; using Echo.DataFlow.Emulation; using Echo.Platforms.DummyPlatform.Code; using Echo.Platforms.DummyPlatform.ControlFlow; using Xunit; -namespace Echo.ControlFlow.Tests.Construction.Symbolic +namespace Echo.DataFlow.Tests.Construction { public class SymbolicGraphBuilderTest { @@ -37,8 +37,9 @@ public void NonPoppingInstructionShouldHaveNoStackDependencies() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Empty(dfg.Nodes[0].StackDependencies); + Assert.Empty(offsetMap[0].StackDependencies); } [Fact] @@ -52,10 +53,11 @@ public void SinglePopShouldHaveSingleStackDependency() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Single(dfg.Nodes[1].StackDependencies); - Assert.Equal(dfg.Nodes[0], dfg.Nodes[1].StackDependencies[0].First().Node); - Assert.Equal(new[]{dfg.Nodes[1]}, dfg.Nodes[0].GetDependants()); + Assert.Single(offsetMap[1].StackDependencies); + Assert.Equal(offsetMap[0], offsetMap[1].StackDependencies[0].First().Node); + Assert.Equal(new[]{offsetMap[1]}, offsetMap[0].GetDependants()); } [Fact] @@ -71,12 +73,13 @@ public void MultiplePopsShouldHaveMultipleStackDependencies() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Equal(3, dfg.Nodes[3].StackDependencies.Count); + Assert.Equal(3, offsetMap[3].StackDependencies.Count); Assert.Equal(new[] { - dfg.Nodes[0], dfg.Nodes[1], dfg.Nodes[2], - }, dfg.Nodes[3].StackDependencies.Select(dep => dep.First().Node)); + offsetMap[0], offsetMap[1], offsetMap[2], + }, offsetMap[3].StackDependencies.Select(dep => dep.First().Node)); } [Fact] @@ -98,11 +101,12 @@ public void BranchingPathsShouldCopyDependency() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Single(dfg.Nodes[3].StackDependencies); - Assert.Equal(dfg.Nodes[0], dfg.Nodes[3].StackDependencies[0].First().Node); - Assert.Single(dfg.Nodes[5].StackDependencies); - Assert.Equal(dfg.Nodes[0], dfg.Nodes[5].StackDependencies[0].First().Node); + Assert.Single(offsetMap[3].StackDependencies); + Assert.Equal(offsetMap[0], offsetMap[3].StackDependencies[0].First().Node); + Assert.Single(offsetMap[5].StackDependencies); + Assert.Equal(offsetMap[0], offsetMap[5].StackDependencies[0].First().Node); } [Fact] @@ -123,11 +127,12 @@ public void JoiningPathsShouldMergeDependencies() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Single(dfg.Nodes[5].StackDependencies); + Assert.Single(offsetMap[5].StackDependencies); Assert.Equal( - new HashSet> {dfg.Nodes[2], dfg.Nodes[4]}, - new HashSet>(dfg.Nodes[5].StackDependencies[0].Select(s => s.Node))); + new HashSet> {offsetMap[2], offsetMap[4]}, + new HashSet>(offsetMap[5].StackDependencies[0].Select(s => s.Node))); } [Fact] @@ -149,18 +154,19 @@ public void JoiningPathsShouldMergeDependenciesAndPropagate() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); Assert.Equal( - new HashSet> {dfg.Nodes[2], dfg.Nodes[4]}, - new HashSet>(dfg.Nodes[6].StackDependencies[0].Select(s => s.Node))); + new HashSet> {offsetMap[2], offsetMap[4]}, + new HashSet>(offsetMap[6].StackDependencies[0].Select(s => s.Node))); Assert.Equal(new[] { - dfg.Nodes[6] - }, dfg.Nodes[2].GetDependants()); + offsetMap[6] + }, offsetMap[2].GetDependants()); Assert.Equal(new[] { - dfg.Nodes[6] - }, dfg.Nodes[4].GetDependants()); + offsetMap[6] + }, offsetMap[4].GetDependants()); } [Fact] @@ -256,7 +262,7 @@ public void EntryPointPopWithSingleItemOnStackShouldAddDependencyToExternalSourc }; var dfgBuilder = new DummyTransitioner(); - var argument = new ExternalDataSourceNode(-1, "Argument 1"); + var argument = new ExternalDataSourceNode("Argument 1"); dfgBuilder.DataFlowGraph.Nodes.Add(argument); dfgBuilder.InitialState = new SymbolicProgramState(0, ImmutableStack.Create(SymbolicValue.CreateStackValue(argument))); @@ -266,9 +272,11 @@ public void EntryPointPopWithSingleItemOnStackShouldAddDependencyToExternalSourc instructions, dfgBuilder); cfgBuilder.ConstructFlowGraph(0); + var dfg = dfgBuilder.DataFlowGraph; + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Equal(new[] {argument}, dfg.Nodes[0].StackDependencies[0].GetNodes()); + Assert.Equal(new[] {argument}, offsetMap[0].StackDependencies[0].GetNodes()); } [Fact] @@ -283,9 +291,10 @@ public void PushingMultipleStackSlots() }; var (cfg, dfg) = BuildFlowGraphs(instructions); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - Assert.Equal(1, dfg.Nodes[1].StackDependencies[0].First().SlotIndex); - Assert.Equal(0, dfg.Nodes[2].StackDependencies[0].First().SlotIndex); + Assert.Equal(1, offsetMap[1].StackDependencies[0].First().SlotIndex); + Assert.Equal(0, offsetMap[2].StackDependencies[0].First().SlotIndex); } [Fact] diff --git a/test/Core/Echo.DataFlow.Tests/DataFlowGraphTest.cs b/test/Core/Echo.DataFlow.Tests/DataFlowGraphTest.cs index a15c6d3e..834d7c9b 100644 --- a/test/Core/Echo.DataFlow.Tests/DataFlowGraphTest.cs +++ b/test/Core/Echo.DataFlow.Tests/DataFlowGraphTest.cs @@ -18,35 +18,18 @@ public void EmptyGraph() public void AddSingleNode() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - dfg.Nodes.Add(0, 0); + dfg.Nodes.Add(0); Assert.Single(dfg.Nodes); } - - [Fact] - public void AddDuplicateNodeShouldNotAppearInNodes() - { - var dfg = new DataFlowGraph(IntArchitecture.Instance); - var node = dfg.Nodes.Add(0, 0); - dfg.Nodes.Add(node); - Assert.Single(dfg.Nodes); - } - - [Fact] - public void AddNodeWithSameIdentifierShouldThrow() - { - var dfg = new DataFlowGraph(IntArchitecture.Instance); - dfg.Nodes.Add(0, 0); - Assert.Throws(() => dfg.Nodes.Add(0, 0)); - } - + [Fact] public void AddFromAnotherGraphShouldThrow() { var dfg1 = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg1.Nodes.Add(0, 0); + var n1 = dfg1.Nodes.Add(0); var dfg2 = new DataFlowGraph(IntArchitecture.Instance); - var n2 = dfg2.Nodes.Add(1, 1); + var n2 = dfg2.Nodes.Add(1); Assert.Throws(() => dfg1.Nodes.Add(n2)); } @@ -55,8 +38,8 @@ public void AddFromAnotherGraphShouldThrow() public void EmptyDependencyShouldNotResultInAnEdge() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); n1.StackDependencies.Add(new StackDependency()); Assert.Empty(dfg.GetEdges()); @@ -66,57 +49,12 @@ public void EmptyDependencyShouldNotResultInAnEdge() public void DependencyShouldResultInAnEdge() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); n1.StackDependencies.Add(new StackDependency()); n1.StackDependencies[0].Add(new StackDataSource(n0)); Assert.Contains(dfg.GetEdges(), e => e.Origin == n1 && e.Target == n0); } - - [Fact] - public void UpdateOffsetsWithNoChangeShouldReuseInstances() - { - var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); - - dfg.Nodes.UpdateIndices(); - - Assert.Same(n0, dfg.Nodes[0]); - Assert.Same(n1, dfg.Nodes[1]); - } - - [Fact] - public void UpdateOffsetsWithChangeShouldReuseInstances() - { - var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); - - n0.Contents = 2; - n1.Contents = 3; - dfg.Nodes.UpdateIndices(); - - Assert.Same(n0, dfg.Nodes[2]); - Assert.Same(n1, dfg.Nodes[3]); - } - - [Fact] - public void UpdateOffsetsWithDuplicatedOffsetsShouldThrowAndDiscard() - { - var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); - - n1.Contents = 4; - n2.Contents = 4; - Assert.Throws(() => dfg.Nodes.UpdateIndices()); - - Assert.Equal(0, n0.Id); - Assert.Equal(1, n1.Id); - Assert.Equal(2, n2.Id); - } } } \ No newline at end of file diff --git a/test/Core/Echo.DataFlow.Tests/DataFlowNodeTest.cs b/test/Core/Echo.DataFlow.Tests/DataFlowNodeTest.cs index 3479ac4e..9702693a 100644 --- a/test/Core/Echo.DataFlow.Tests/DataFlowNodeTest.cs +++ b/test/Core/Echo.DataFlow.Tests/DataFlowNodeTest.cs @@ -11,15 +11,15 @@ public class DataFlowNodeTest [Fact] public void ConstructorShouldSetContents() { - var node = new DataFlowNode(1, 2); - Assert.Equal(2, node.Contents); + var node = new DataFlowNode(2); + Assert.Equal(2, node.Instruction); } [Fact] public void AddNodeToGraphShouldSetParentGraph() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var node = new DataFlowNode(1, 2); + var node = new DataFlowNode(2); dfg.Nodes.Add(node); Assert.Same(dfg, node.ParentGraph); } @@ -28,7 +28,7 @@ public void AddNodeToGraphShouldSetParentGraph() public void RemoveNodeFromGraphShouldUnsetParentGraph() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var node = new DataFlowNode(1, 2); + var node = new DataFlowNode(2); dfg.Nodes.Add(node); dfg.Nodes.Remove(node); Assert.Null(node.ParentGraph); @@ -38,8 +38,8 @@ public void RemoveNodeFromGraphShouldUnsetParentGraph() public void AddStackDependencyShouldSetDependant() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); var dependency = new StackDependency(); n1.StackDependencies.Add(dependency); @@ -52,8 +52,8 @@ public void AddStackDependencyShouldSetDependant() public void RemoveStackDependencyShouldUnsetDependant() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); var symbolicValue = new StackDependency(); n1.StackDependencies.Add(symbolicValue); @@ -67,9 +67,9 @@ public void RemoveStackDependencyShouldUnsetDependant() public void AddStackDependencyShouldAddToDependants() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); var dependency1 = new StackDependency(); n1.StackDependencies.Add(dependency1); @@ -89,9 +89,9 @@ public void AddStackDependencyShouldAddToDependants() public void RemoveStackDependencyShouldRemoveFromDependants() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n0 = dfg.Nodes.Add(0, 0); - var n1 = dfg.Nodes.Add(1, 1); - var n2 = dfg.Nodes.Add(2, 2); + var n0 = dfg.Nodes.Add(0); + var n1 = dfg.Nodes.Add(1); + var n2 = dfg.Nodes.Add(2); var dependency1 = new StackDependency(); n1.StackDependencies.Add(dependency1); @@ -113,10 +113,10 @@ public void RemoveStackDependencyShouldRemoveFromDependants() public void AddDependencyToAnotherGraphShouldThrow() { var dfg1 = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg1.Nodes.Add(1, 0); + var n1 = dfg1.Nodes.Add(0); var dfg2 = new DataFlowGraph(IntArchitecture.Instance); - var n2 = dfg2.Nodes.Add(2, 0); + var n2 = dfg2.Nodes.Add(0); n1.StackDependencies.Add(new StackDependency()); @@ -127,10 +127,10 @@ public void AddDependencyToAnotherGraphShouldThrow() public void AddDataSourceToAnotherGraphShouldThrow() { var dfg1 = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg1.Nodes.Add(1, 0); + var n1 = dfg1.Nodes.Add(0); var dfg2 = new DataFlowGraph(IntArchitecture.Instance); - var n2 = dfg2.Nodes.Add(2, 0); + var n2 = dfg2.Nodes.Add(0); n1.StackDependencies.Add(new StackDependency()); Assert.Throws(() => n1.StackDependencies[0].Add(new StackDataSource(n2))); @@ -140,8 +140,8 @@ public void AddDataSourceToAnotherGraphShouldThrow() public void RemoveNodeShouldRemoveStackDeps() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 0); - var n2 = dfg.Nodes.Add(2, 0); + var n1 = dfg.Nodes.Add(0); + var n2 = dfg.Nodes.Add(0); n1.StackDependencies.Add(new StackDependency()); n1.StackDependencies[0].Add(n2); @@ -159,8 +159,8 @@ public void RemoveNodeShouldRemoveStackDeps() public void RemoveNodeShouldRemoveStackDependants() { var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 0); - var n2 = dfg.Nodes.Add(2, 0); + var n1 = dfg.Nodes.Add(0); + var n2 = dfg.Nodes.Add(0); n1.StackDependencies.Add(new StackDependency()); n1.StackDependencies[0].Add(n2); @@ -180,8 +180,8 @@ public void RemoveNodeShouldRemoveVarDeps() var variable = new DummyVariable("V_1"); var dfg = new DataFlowGraph(IntArchitecture.Instance); - var n1 = dfg.Nodes.Add(1, 0); - var n2 = dfg.Nodes.Add(2, 0); + var n1 = dfg.Nodes.Add(0); + var n2 = dfg.Nodes.Add(0); var dependency = new VariableDependency(variable); n1.VariableDependencies[variable] = dependency; @@ -207,11 +207,11 @@ public void DisconnectNodeShouldRemoveEdge(DataDependencyType edgeType, bool rem var graph = new DataFlowGraph(IntArchitecture.Instance); - var n1 = new DataFlowNode(0, 0); + var n1 = new DataFlowNode(0); n1.StackDependencies.Add(new StackDependency()); n1.VariableDependencies.Add(new VariableDependency(variable)); - var n2 = new DataFlowNode(1, 1); + var n2 = new DataFlowNode(1); graph.Nodes.AddRange(new[] { @@ -246,8 +246,8 @@ public void AddStackDependencyWithSingeSourceShouldSetDegree() { var graph = new DataFlowGraph(IntArchitecture.Instance); - var n1 = graph.Nodes.Add(1, 1); - var n2 = graph.Nodes.Add(2, 2); + var n1 = graph.Nodes.Add(1); + var n2 = graph.Nodes.Add(2); Assert.Equal(0, n1.OutDegree); Assert.Equal(0, n2.InDegree); @@ -265,9 +265,9 @@ public void AddStackDependencyWithMultipleSourcesShouldSetDegree() { var graph = new DataFlowGraph(IntArchitecture.Instance); - var n1 = graph.Nodes.Add(1, 1); - var n2 = graph.Nodes.Add(2, 2); - var n3 = graph.Nodes.Add(3, 3); + var n1 = graph.Nodes.Add(1); + var n2 = graph.Nodes.Add(2); + var n3 = graph.Nodes.Add(3); Assert.Equal(0, n1.OutDegree); @@ -285,8 +285,8 @@ public void AddVariableDependencyWithSingeSourceShouldSetDegree() var variable = new DummyVariable("var1"); var graph = new DataFlowGraph(IntArchitecture.Instance); - var n1 = graph.Nodes.Add(1, 1); - var n2 = graph.Nodes.Add(2, 2); + var n1 = graph.Nodes.Add(1); + var n2 = graph.Nodes.Add(2); Assert.Equal(0, n1.OutDegree); @@ -304,9 +304,9 @@ public void AddVariableDependencyWithMultipleSourcesShouldSetDegree() var variable = new DummyVariable("var1"); var graph = new DataFlowGraph(IntArchitecture.Instance); - var n1 = graph.Nodes.Add(1, 1); - var n2 = graph.Nodes.Add(2, 2); - var n3 = graph.Nodes.Add(3, 3); + var n1 = graph.Nodes.Add(1); + var n2 = graph.Nodes.Add(2); + var n3 = graph.Nodes.Add(3); Assert.Equal(0, n1.OutDegree); diff --git a/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicStackStateTest.cs b/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicStackStateTest.cs index f5f5315d..92f46b81 100644 --- a/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicStackStateTest.cs +++ b/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicStackStateTest.cs @@ -11,7 +11,7 @@ public class SymbolicStackStateTest { private static DataFlowNode CreateDummyNode(long id) { - return new DataFlowNode(id, DummyInstruction.Op(id, 0, 1)); + return new DataFlowNode(DummyInstruction.Op(id, 0, 1)); } [Fact] diff --git a/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicVariableStateTest.cs b/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicVariableStateTest.cs index a655fe8a..b1b38570 100644 --- a/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicVariableStateTest.cs +++ b/test/Core/Echo.DataFlow.Tests/Emulation/SymbolicVariableStateTest.cs @@ -11,7 +11,7 @@ public class SymbolicVariableStateTest { private static DataFlowNode CreateDummyNode(long id) { - return new DataFlowNode(id, DummyInstruction.Op(id, 0, 1)); + return new DataFlowNode(DummyInstruction.Op(id, 0, 1)); } [Fact] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs index 5a703bb0..2cc73bb1 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs @@ -39,6 +39,7 @@ public void If() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var body = method.CilMethodBody!; var cfg = body.ConstructSymbolicFlowGraph(out var dfg); + var offsetMap = dfg.Nodes.CreateOffsetMap(); Assert.Single(cfg.EntryPoint!.ConditionalEdges); @@ -69,9 +70,9 @@ bool ValueReachesOffset(int sourceOffset, int targetOffset) if (!visited.Add(currentOffset)) continue; - var current = dfg.Nodes[currentOffset]; + var current = offsetMap[currentOffset]; foreach (var dependant in current.GetDependants()) - agenda.Push(dependant.Id); + agenda.Push(dependant.Offset); } return false; diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs index e5484d89..b82ac4b3 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs @@ -34,6 +34,7 @@ public void If() var type = (TypeDef) _moduleFixture.MockModule.ResolveToken(typeof(SimpleClass).MetadataToken); var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var cfg = method.ConstructSymbolicFlowGraph(out var dfg); + var offsetMap = dfg.Nodes.CreateOffsetMap(); Assert.Single(cfg.EntryPoint!.ConditionalEdges); @@ -64,9 +65,9 @@ bool ValueReachesOffset(uint sourceOffset, uint targetOffset) if (!visited.Add(currentOffset)) continue; - var current = dfg.Nodes[currentOffset]; + var current = offsetMap[currentOffset]; foreach (var dependant in current.GetDependants()) - agenda.Push(dependant.Id); + agenda.Push(dependant.Offset); } return false; diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs index e7f4a12f..317ef0bb 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs @@ -50,8 +50,8 @@ public void DataFlowGraphShouldBeCorrectOnConditional() // check that `arg0 % 2` is correctly turned into dfg var remNode = Assert.Single(graph.Nodes, n => n.StackDependencies.Count == 2); - Assert.Single(remNode!.StackDependencies, n => Assert.Single(n)!.Node.Contents.IsLdarg()); - Assert.Single(remNode!.StackDependencies, n => Assert.Single(n)!.Node.Contents.IsLdcI4()); + Assert.Single(remNode!.StackDependencies, n => Assert.Single(n)!.Node.Instruction.IsLdarg()); + Assert.Single(remNode!.StackDependencies, n => Assert.Single(n)!.Node.Instruction.IsLdcI4()); Assert.Single(remNode.GetDependants()); } } diff --git a/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs index e5e85673..3a1ccf16 100644 --- a/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Iced.Tests/X86StateTransitionResolverTest.cs @@ -39,10 +39,11 @@ public void TestGeneralPurposeRegisterDependency() 0x83, 0xC0, 0x02, // add eax, 2 0xC3 // ret }, 0); - + var offsetMap = dfg.Nodes.CreateOffsetMap(); + var eax = _architecture.GetRegister(Register.EAX); - Assert.True(dfg.Nodes[0x5].VariableDependencies.ContainsVariable(eax)); - Assert.Contains(dfg.Nodes[0], dfg.Nodes[0x5].VariableDependencies[eax].GetNodes()); + Assert.True(offsetMap[0x5].VariableDependencies.ContainsVariable(eax)); + Assert.Contains(offsetMap[0], offsetMap[0x5].VariableDependencies[eax].GetNodes()); } [Fact] @@ -54,10 +55,11 @@ public void TestFlagRegisterDependency() 0x7C, 0x00, // jl +0 0xC3 // ret }, 0); + var offsetMap = dfg.Nodes.CreateOffsetMap(); var of = _architecture.GetFlag(RflagsBits.OF); - Assert.True(dfg.Nodes[0x3].VariableDependencies.ContainsVariable(of)); - Assert.Contains(dfg.Nodes[0], dfg.Nodes[0x3].VariableDependencies[of].GetNodes()); + Assert.True(offsetMap[0x3].VariableDependencies.ContainsVariable(of)); + Assert.Contains(offsetMap[0], offsetMap[0x3].VariableDependencies[of].GetNodes()); } [Fact] @@ -69,11 +71,12 @@ public void PushEspShouldOnlyReturnEspOnceForReadAndWrittenVariables() /* 1: */ 0x5C, // pop esp /* 2: */ 0xC3 // ret }, 0); + var offsetMap = dfg.Nodes.CreateOffsetMap(); - var dependency = dfg.Nodes[1].VariableDependencies + var dependency = offsetMap[1].VariableDependencies .FirstOrDefault(dependency => dependency.Variable.Name == "ESP"); Assert.NotNull(dependency); - Assert.Contains(dfg.Nodes[0], dependency.GetNodes()); + Assert.Contains(offsetMap[0], dependency.GetNodes()); } } } \ No newline at end of file From 4e619853e41f7a1eb9b6f8fd4ed22fcda0356fca Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 30 Jul 2024 16:31:07 +0200 Subject: [PATCH 6/9] Update DFG docs on new API changes. --- docs/guides/core/cfg-basics.md | 10 ++++---- docs/guides/core/cfg-construction.md | 16 ++++++++++++- docs/guides/core/dfg-basics.md | 23 ++++++++++++------- .../Collections/NodeCollection.cs | 4 ++++ .../Collections/NodeCollection.cs | 13 ++++++++++- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/docs/guides/core/cfg-basics.md b/docs/guides/core/cfg-basics.md index 0b846d01..149c4fb6 100644 --- a/docs/guides/core/cfg-basics.md +++ b/docs/guides/core/cfg-basics.md @@ -37,7 +37,7 @@ foreach (var node in cfg.Nodes) Console.WriteLine($"{node.Offset:X8}"); ``` -Individual nodes can also be obtained by looking them up by offset: +Individual nodes can be obtained by looking them up by offset: ```csharp var node = cfg.Nodes.GetByOffset(offset: 0x1234); @@ -50,11 +50,13 @@ To ensure all nodes have updated offsets according to their contents, use the `U cfg.Nodes.UpdateOffsets(); ``` -If many nodes are supposed to be queried by offset, consider first creating an offset map; a dictionary that maps all basic block header offsets to their corresponding nodes: +When doing many lookups by offset, consider first creating an offset map for faster lookups. ```csharp var offsetMap = cfg.Nodes.CreateOffsetMap(); -var node = cfg.Nodes[0x1234]; +var n1 = offsetMap[0x0001]; +var n2 = offsetMap[0x0004]; +var n3 = offsetMap[0x0010]; ``` Every node exposes a basic block containing the instructions it executes: @@ -118,7 +120,7 @@ If only interested in the target nodes, `GetSuccessors()` can be used instead: ```csharp foreach (var successor in node.GetSuccessors()) Console.WriteLine(successor); -``` +```[dfg-basics.md](dfg-basics.md) Similarly, incoming edges can also be obtained using `GetIncomingEdges()` and `GetPredecessors()`: diff --git a/docs/guides/core/cfg-construction.md b/docs/guides/core/cfg-construction.md index ed1c8226..161c5b63 100644 --- a/docs/guides/core/cfg-construction.md +++ b/docs/guides/core/cfg-construction.md @@ -10,6 +10,8 @@ Each architecture that supports static control flow graph building implements th A graph can then be constructed using the `StaticFlowGraphBuilder` class: ```csharp +using Echo.ControlFlow.Construction; + IArchitecture architecture = ...; IStaticSuccessorResolver resolver = ...; @@ -66,6 +68,8 @@ This interface takes a symbolic input state, and transforms it into a set of all ```csharp +using Echo.DataFlow.Construction; + IArchitecture architecture = ...; StateTransitioner transitioner = ...; @@ -90,4 +94,14 @@ var dfg = transitioner.DataFlowGraph; ``` > [!WARNING] -> While symbolic graph construction usually is more accurate, it is significantly slower than static graph construction and can take a lot of memory. \ No newline at end of file +> While symbolic graph construction usually is more accurate, it is significantly slower than static graph construction and can take a lot of memory. + + +> [!NOTE] +> Often, a backend platform has this boilerplate already implemented by extension methods. +> For instance, `Echo.Platforms.AsmResolver` defines an extension method on `CilMethodBody` called `ConstructStaticFlowGraph`. +> ```csharp +> CilMethodBody methodBody = ...; +> var cfg = methodBody.ConstructSymbolicFlowGraph(out var dfg); +> ``` +> Refer to the platform-specific documentation to see how these graphs can be constructed easily. diff --git a/docs/guides/core/dfg-basics.md b/docs/guides/core/dfg-basics.md index d89edb8b..b5a927bc 100644 --- a/docs/guides/core/dfg-basics.md +++ b/docs/guides/core/dfg-basics.md @@ -37,23 +37,30 @@ DataFlowGraph dfg = ...; // Iterate over all nodes in a data flow graph: foreach (var node in dfg.Nodes) - Console.WriteLine(node.Contents); + Console.WriteLine(node.Instruction); ``` -Nodes are indexed by offset. -They can be obtained via the `GetNodeById` method: +Individual nodes can be obtained by looking them up by offset: ```csharp -var node = dfg.GetNodeById(id: 0x1234); +var node = dfg.Nodes.GetByOffset(offset: 0x1234); ``` -Every node exposes a basic blockc containing the instructions that introduces or requires dependencies: +This performs a linear search through all the nodes, and finds the first node that matches in offset. +To ensure all nodes have updated offsets according to their contents, use the `UpdateOffsets` method: ```csharp -DataFlowNode node = ...; -var instruction = node.Contents; +dfg.Nodes.UpdateOffsets(); ``` +When doing many lookups by offset, consider first creating an offset map for faster lookups. + +```csharp +var offsetMap = dfg.Nodes.CreateOffsetMap(); +var n1 = offsetMap[0x0001]; +var n2 = offsetMap[0x0004]; +var n3 = offsetMap[0x0010]; +``` ## Edges @@ -105,7 +112,7 @@ var dependencies = node.GetOrderedDependencies(); By default, `GetOrderedDependencies` traverses all edges in the data flow graph. This includes variable dependencies that were registered in the graph. -If only the stack dependnecies are meant to be traversed (e.g. to get the instructions that make up a single expression), additional flags can be specified to alter the behaviour of the traversal. +If only the stack dependencies are meant to be traversed (e.g. to get the instructions that make up a single expression), additional flags can be specified to alter the behaviour of the traversal. ```csharp DataFlowGraph node = ... diff --git a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs index e9d710a4..41ac62ac 100644 --- a/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs +++ b/src/Core/Echo.ControlFlow/Collections/NodeCollection.cs @@ -163,6 +163,10 @@ public IDictionary> CreateOffsetMap() /// /// The offset. /// The node, or null if no node was found with the provided offset. + /// + /// This is a linear lookup. For many lookups by offset, consider first creating an offset map using + /// . + /// public ControlFlowNode? GetByOffset(long offset) => _nodes.FirstOrDefault(x => x.Contents.Offset == offset); /// diff --git a/src/Core/Echo.DataFlow/Collections/NodeCollection.cs b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs index 41c3f7c1..2244101d 100644 --- a/src/Core/Echo.DataFlow/Collections/NodeCollection.cs +++ b/src/Core/Echo.DataFlow/Collections/NodeCollection.cs @@ -116,7 +116,7 @@ public void UpdateOffsets() } /// - /// Constructs a mapping from basic block header offsets to their respective nodes. + /// Constructs a mapping from instruction offsets to their respective nodes. /// /// The mapping /// The control flow graph contains nodes with duplicated offsets. @@ -127,6 +127,17 @@ public IDictionary> CreateOffsetMap() .ToDictionary(x => x.Offset, x => x); } + /// + /// Finds a node by its instruction offset. + /// + /// The offset. + /// The node, or null if no node was found with the provided offset. + /// + /// This is a linear lookup. For many lookups by offset, consider first creating an offset map using + /// . + /// + public DataFlowNode? GetByOffset(long offset) => _nodes.FirstOrDefault(x => x.Offset == offset); + /// public IEnumerator> GetEnumerator() => _nodes.GetEnumerator(); From 702a2a96524822ca741d517aeb01ab5943595711 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 30 Jul 2024 17:03:16 +0200 Subject: [PATCH 7/9] Use offset map in ranged EH detector --- .../Regions/Detection/RangedEHRegionDetector.cs | 14 ++++++++------ src/Core/Echo.DataFlow/ExternalDataSourceNode.cs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs index 3662a727..0fb89210 100644 --- a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs +++ b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs @@ -28,7 +28,7 @@ public static void DetectExceptionHandlerRegions( // Build up region tree. var rangeToRegionMapping = CreateEHRegions(cfg, sortedRanges); InsertNodesInEHRegions(cfg, sortedRanges, rangeToRegionMapping); - DetermineRegionEntrypoints(cfg, sortedRanges, rangeToRegionMapping); + DetermineRegionEntryPoints(cfg, sortedRanges, rangeToRegionMapping); } private static Dictionary> CreateEHRegions( @@ -155,25 +155,27 @@ private static void InsertNodesInEHRegions( return null; } - private static void DetermineRegionEntrypoints( + private static void DetermineRegionEntryPoints( ControlFlowGraph cfg, List sortedRanges, Dictionary> rangeToRegionMapping) where TInstruction : notnull { + var offsetMap = cfg.Nodes.CreateOffsetMap(); + foreach (var range in sortedRanges) { var protectedRegion = rangeToRegionMapping[range.ProtectedRange]; - protectedRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.ProtectedRange.Start); + protectedRegion.EntryPoint ??= offsetMap[range.ProtectedRange.Start]; var handlerRegion = rangeToRegionMapping[range.HandlerRange]; - handlerRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.HandlerRange.Start); + handlerRegion.EntryPoint ??= offsetMap[range.HandlerRange.Start]; if (rangeToRegionMapping.TryGetValue(range.PrologueRange, out var prologueRegion)) - prologueRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.PrologueRange.Start); + prologueRegion.EntryPoint ??= offsetMap[range.PrologueRange.Start]; if (rangeToRegionMapping.TryGetValue(range.EpilogueRange, out var epilogueRegion)) - epilogueRegion.EntryPoint ??= cfg.Nodes.GetByOffset(range.EpilogueRange.Start); + epilogueRegion.EntryPoint ??= offsetMap[range.EpilogueRange.Start]; } } diff --git a/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs b/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs index b9b2e0d9..e07eb59a 100644 --- a/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs +++ b/src/Core/Echo.DataFlow/ExternalDataSourceNode.cs @@ -18,7 +18,7 @@ public ExternalDataSourceNode(object source) } /// - /// Gets the name of the auxiliary data flow node. + /// Gets the object representing the external source of the auxiliary data flow node. /// public object Source { From 1a9cfc58e7c1b7b79da2c03f12b68dbd08449060 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 23 Aug 2024 17:01:33 +0200 Subject: [PATCH 8/9] Add architecture convenience extension methods. --- .../Construction/IStaticSuccessorResolver.cs | 1 - .../Construction/StaticFlowGraphBuilder.cs | 1 - src/Core/Echo/Code/ArchitectureExtensions.cs | 41 +++++++++++++++++++ src/Core/Echo/Code/IArchitecture.cs | 3 +- 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/Core/Echo/Code/ArchitectureExtensions.cs diff --git a/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs b/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs index 3d76d662..150eca32 100644 --- a/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs +++ b/src/Core/Echo.ControlFlow/Construction/IStaticSuccessorResolver.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; namespace Echo.ControlFlow.Construction diff --git a/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs b/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs index 00335338..45628ec2 100644 --- a/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs +++ b/src/Core/Echo.ControlFlow/Construction/StaticFlowGraphBuilder.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using System.Collections.Generic; using Echo.Code; diff --git a/src/Core/Echo/Code/ArchitectureExtensions.cs b/src/Core/Echo/Code/ArchitectureExtensions.cs new file mode 100644 index 00000000..fc6425f7 --- /dev/null +++ b/src/Core/Echo/Code/ArchitectureExtensions.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Echo.Code; + +/// +/// Provides convenience extensions for the interface. +/// +public static class ArchitectureExtensions +{ + /// + /// Gets a collection of variables that an instruction reads from. + /// + /// The architecture. + /// The instruction to get the variables from. + /// The list of variables. + public static IList GetReadVariables( + this IArchitecture self, + in TInstruction instruction) + where TInstruction : notnull + { + var result = new List(); + self.GetReadVariables(in instruction, result); + return result; + } + + /// + /// Gets a collection of variables that an instruction writes to. + /// + /// The architecture. + /// The instruction to get the variables from. + /// The list of variables. + public static IList GetWrittenVariables( + this IArchitecture self, + in TInstruction instruction) + where TInstruction : notnull + { + var result = new List(); + self.GetWrittenVariables(in instruction, result); + return result; + } +} \ No newline at end of file diff --git a/src/Core/Echo/Code/IArchitecture.cs b/src/Core/Echo/Code/IArchitecture.cs index 6ba8fdf1..9e2e8ddb 100644 --- a/src/Core/Echo/Code/IArchitecture.cs +++ b/src/Core/Echo/Code/IArchitecture.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Echo.Code { From 4ccf49943e6d81ba444567594f0da131036f1cc1 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 23 Aug 2024 17:01:50 +0200 Subject: [PATCH 9/9] Allow DotWriter to render undirected graphs. --- .../Graphing/Serialization/Dot/DotWriter.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs index eaf14858..81fda008 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs @@ -1,5 +1,4 @@ using System; -using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; @@ -100,13 +99,23 @@ public IDotSubGraphAdorner? SubGraphAdorner set; } + /// + /// Gets or sets a value indicating the resulting graph should be rendered as a directed graph or an + /// undirected graph. + /// + public bool DirectedGraph + { + get; + set; + } = true; + /// /// Writes a graph to the character stream. /// /// The graph to write. public void Write(IGraph graph) { - WriteHeader("digraph", null); + WriteHeader(DirectedGraph ? "digraph" : "graph", null); Writer.Indent++; var freeNodes = new HashSet(graph.GetNodes()); @@ -146,12 +155,12 @@ private void WriteSubGraph(ISubGraph subGraph, HashSet scope) Writer.Indent++; var attributes = SubGraphAdorner.GetSubGraphAttributes(subGraph); - if (attributes.Count > 0) + if (attributes is {Count: > 0}) { string delimiter = IncludeSemicolons ? ";" : string.Empty; - + WriteAttributes(attributes, delimiter, true); Writer.WriteLine(delimiter); Writer.WriteLine(); @@ -193,7 +202,7 @@ protected virtual void WriteHeader(string graphType, string? graphName) /// private void WriteFooter() { - Writer.WriteLine("}"); + Writer.WriteLine('}'); } /// @@ -205,7 +214,7 @@ protected virtual void WriteNode(INode node) long id = NodeIdentifier.GetIdentifier(node); WriteIdentifier(id.ToString()); - if (NodeAdorner != null) + if (NodeAdorner is not null) WriteEntityAttributes(NodeAdorner.GetNodeAttributes(node, id)); WriteSemicolon(); @@ -222,42 +231,45 @@ protected virtual void WriteEdge(IEdge edge) long targetId = NodeIdentifier.GetIdentifier(edge.Target); WriteIdentifier(sourceId.ToString()); - Writer.Write(" -> "); + Writer.Write(DirectedGraph ? " -> " : " -- "); WriteIdentifier(targetId.ToString()); - if (EdgeAdorner != null) + if (EdgeAdorner is not null) WriteEntityAttributes(EdgeAdorner.GetEdgeAttributes(edge, sourceId, targetId)); WriteSemicolon(); Writer.WriteLine(); } - private void WriteEntityAttributes(IEnumerable>? attributes) + private void WriteEntityAttributes(IDictionary? attributes) { - var array = attributes as KeyValuePair[] ?? attributes.ToArray(); - if (array.Length > 0) + if (attributes is {Count: > 0}) { Writer.Write(" ["); - WriteAttributes(array, ", ", false); + WriteAttributes(attributes, ", ", false); Writer.Write(']'); } } - private void WriteAttributes(IEnumerable>? attributes, string delimiter, bool newLines) + private void WriteAttributes(IDictionary? attributes, string delimiter, bool newLines) { - var array = attributes as KeyValuePair[] ?? attributes.ToArray(); - for (int i = 0; i < array.Length; i++) - { - WriteIdentifier(array[i].Key); - Writer.Write('='); - WriteIdentifier(array[i].Value); + if (attributes is null) + return; - if (i < array.Length - 1) + bool writtenOne = false; + foreach (var item in attributes) + { + if (writtenOne) { Writer.Write(delimiter); if (newLines) Writer.WriteLine(); } + + WriteIdentifier(item.Key); + Writer.Write('='); + WriteIdentifier(item.Value); + writtenOne = true; } } @@ -297,9 +309,13 @@ protected void WriteSemicolon() protected static bool NeedsEscaping(string text) { bool startsWithDigit = char.IsDigit(text[0]); - return text.Any(c => EscapedCharacters.ContainsKey(c) - || !char.IsLetterOrDigit(c) - || startsWithDigit && !char.IsDigit(c)); + foreach (char c in text) + { + if (EscapedCharacters.ContainsKey(c) || !char.IsLetterOrDigit(c) || startsWithDigit && !char.IsDigit(c)) + return true; + } + + return false; } ///