ControlFlowGraph
- graph builder - block, edge - graph visitor
This commit is contained in:
Родитель
6cb9b1b306
Коммит
4b137d80ad
|
@ -22,7 +22,7 @@ namespace Pchp.CodeAnalysis
|
|||
readonly PEModuleBuilder _moduleBuilder;
|
||||
readonly bool _emittingPdb;
|
||||
readonly DiagnosticBag _diagnostics;
|
||||
readonly Worklist _worklist;
|
||||
readonly Worklist<BoundBlock> _worklist;
|
||||
// readonly CallGraph _callgraph; //keeps graph of what methods call specific method // used to reanalyze caller methods when return type ot arg. type changes
|
||||
|
||||
private SourceCompiler(PhpCompilation compilation, PEModuleBuilder moduleBuilder, bool emittingPdb, DiagnosticBag diagnostics)
|
||||
|
@ -36,7 +36,7 @@ namespace Pchp.CodeAnalysis
|
|||
_emittingPdb = emittingPdb;
|
||||
_diagnostics = diagnostics;
|
||||
|
||||
_worklist = new Worklist(); // parallel worklist algorithm
|
||||
_worklist = new Worklist<BoundBlock>(AnalyzeMethod); // parallel worklist algorithm
|
||||
|
||||
// semantic model
|
||||
}
|
||||
|
@ -62,19 +62,14 @@ namespace Pchp.CodeAnalysis
|
|||
|
||||
internal void AnalyzeMethods()
|
||||
{
|
||||
//this.WalkMethods(m => worklist.Enlist(m, this.AnalyzeMethod))
|
||||
//worklist.Do
|
||||
|
||||
// DEBUG
|
||||
this.WalkMethods(this.AnalyzeMethod);
|
||||
this.WalkMethods(m => _worklist.Enqueue(BindMethod(m)));
|
||||
_worklist.DoAll();
|
||||
}
|
||||
|
||||
private void AnalyzeMethod(SourceBaseMethodSymbol method)
|
||||
private void AnalyzeMethod(BoundBlock method)
|
||||
{
|
||||
Contract.ThrowIfNull(method);
|
||||
|
||||
var bound = method.BoundBlock;
|
||||
|
||||
// Initial State // declared locals, initial types
|
||||
// TypeAnalysis + ResolveSymbols
|
||||
// if (LowerBody(bound)) Enlist(method)
|
||||
|
|
|
@ -12,11 +12,66 @@ namespace Pchp.CodeAnalysis.FlowAnalysis
|
|||
/// <summary>
|
||||
/// Queue of work items to do.
|
||||
/// </summary>
|
||||
internal class Worklist
|
||||
internal class Worklist<T>
|
||||
{
|
||||
readonly object _syncRoot = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Action performed on blocks.
|
||||
/// </summary>
|
||||
readonly Action<T> _analyzer;
|
||||
|
||||
//public event EventHandler MethodDone;
|
||||
|
||||
///// <summary>
|
||||
///// Set of blocks being analyzed.
|
||||
///// Used for recursion prevention.
|
||||
///// </summary>
|
||||
//readonly HashSet<T> _pending;
|
||||
|
||||
/// <summary>
|
||||
/// List of blocks to be processed.
|
||||
/// </summary>
|
||||
readonly DistinctQueue<BoundBlock> _queue = new DistinctQueue<BoundBlock>();
|
||||
readonly DistinctQueue<T> _queue = new DistinctQueue<T>();
|
||||
|
||||
public Worklist(Action<T> analyzer)
|
||||
{
|
||||
Contract.ThrowIfNull(analyzer);
|
||||
|
||||
_analyzer = analyzer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds block to the queue.
|
||||
/// </summary>
|
||||
public void Enqueue(T block)
|
||||
{
|
||||
_queue.Enqueue(block);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all tasks until the queue is not empty.
|
||||
/// </summary>
|
||||
public void DoAll()
|
||||
{
|
||||
for (; DoNext();) ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop next item from the queue and process it.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if there was an item, otherwise <c>false</c>.</returns>
|
||||
public bool DoNext()
|
||||
{
|
||||
T block;
|
||||
if (!_queue.TryDequeue(out block))
|
||||
return false;
|
||||
|
||||
//
|
||||
_analyzer(block);
|
||||
|
||||
//
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@
|
|||
<Compile Include="FlowAnalysis\Worklist.cs" />
|
||||
<Compile Include="Semantics\BoundMethodBody.cs" />
|
||||
<Compile Include="Semantics\Expressions.cs" />
|
||||
<Compile Include="Semantics\Graph\Block.cs" />
|
||||
<Compile Include="Semantics\Graph\BuilderVisitor.cs" />
|
||||
<Compile Include="Semantics\Graph\ControlFlowGraph.cs" />
|
||||
<Compile Include="Semantics\Graph\Edge.cs" />
|
||||
<Compile Include="Semantics\Graph\GraphVisitor.cs" />
|
||||
<Compile Include="Semantics\SemanticsBinder.cs" />
|
||||
<Compile Include="Semantics\Statements.cs" />
|
||||
<Compile Include="CodeGen\MethodGenerator.cs" />
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
using Microsoft.CodeAnalysis.Semantics;
|
||||
using Pchp.Syntax;
|
||||
using Pchp.Syntax.AST;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Pchp.CodeAnalysis.Semantics.Graph
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents control flow block.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Block")]
|
||||
public class Block : AstNode, IBlockStatement
|
||||
{
|
||||
readonly List<BoundStatement>/*!*/_statements;
|
||||
Edge _next;
|
||||
|
||||
/// <summary>
|
||||
/// Tag used for graph algorithms.
|
||||
/// </summary>
|
||||
public int Tag { get { return _tag; } set { _tag = value; } }
|
||||
private int _tag;
|
||||
|
||||
/// <summary>
|
||||
/// Gets statements contained in this block.
|
||||
/// </summary>
|
||||
public List<BoundStatement>/*!!*/Statements => _statements;
|
||||
|
||||
/// <summary>
|
||||
/// Gets edge pointing out of this block.
|
||||
/// </summary>
|
||||
public Edge NextEdge
|
||||
{
|
||||
get { return _next; }
|
||||
internal set { _next = value; }
|
||||
}
|
||||
|
||||
internal Block()
|
||||
{
|
||||
_statements = new List<BoundStatement>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds statement to the block.
|
||||
/// </summary>
|
||||
internal void AddStatement(BoundStatement stmt)
|
||||
{
|
||||
Contract.ThrowIfNull(stmt);
|
||||
_statements.Add(stmt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses empty blocks to their non-empty successor. Skips duplicities.
|
||||
/// </summary>
|
||||
internal static List<Block>/*!*/SkipEmpty(IEnumerable<Block>/*!*/blocks)
|
||||
{
|
||||
Contract.ThrowIfNull(blocks);
|
||||
|
||||
var result = new HashSet<Block>();
|
||||
|
||||
foreach (var x in blocks)
|
||||
{
|
||||
var block = x;
|
||||
while (block != null && block.GetType() == typeof(Block) && block.Statements.Count == 0)
|
||||
{
|
||||
var edge = block.NextEdge as SimpleEdge;
|
||||
if (edge != null || block.NextEdge == null)
|
||||
{
|
||||
block = (edge != null && edge.Target != block) ? edge.Target : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (block != null)
|
||||
{
|
||||
result.Add(block);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public virtual void Accept(GraphVisitor visitor) => visitor.VisitCFGBlock(this);
|
||||
|
||||
#region IBlockStatement
|
||||
|
||||
ImmutableArray<IStatement> IBlockStatement.Statements => _statements.AsImmutable<IStatement>();
|
||||
|
||||
ImmutableArray<ILocalSymbol> IBlockStatement.Locals => Locals;
|
||||
protected virtual ImmutableArray<ILocalSymbol> Locals => ImmutableArray<ILocalSymbol>.Empty;
|
||||
|
||||
OperationKind IOperation.Kind => OperationKind.BlockStatement;
|
||||
|
||||
bool IOperation.IsInvalid => false;
|
||||
|
||||
SyntaxNode IOperation.Syntax => null;
|
||||
|
||||
void IOperation.Accept(OperationVisitor visitor)
|
||||
=> visitor.VisitBlockStatement(this);
|
||||
|
||||
TResult IOperation.Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
|
||||
=> visitor.VisitBlockStatement(this, argument);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a start block.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Start")]
|
||||
public sealed class StartBlock : Block
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an exit block.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Exit")]
|
||||
public sealed class ExitBlock : Block
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents control flow block of catch item.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("CatchBlock({ClassName.QualifiedName})")]
|
||||
public class CatchBlock : Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Catch variable type.
|
||||
/// </summary>
|
||||
public DirectTypeRef TypeRef { get { return _typeRef; } }
|
||||
private readonly DirectTypeRef _typeRef;
|
||||
|
||||
/// <summary>
|
||||
/// A variable where an exception is assigned in.
|
||||
/// </summary>
|
||||
public VariableName VariableName { get { return _variableName; } }
|
||||
private readonly VariableName _variableName;
|
||||
|
||||
public CatchBlock(CatchItem item)
|
||||
: base()
|
||||
{
|
||||
_typeRef = item.TypeRef;
|
||||
_variableName = item.Variable.VarName;
|
||||
}
|
||||
|
||||
public override void Accept(GraphVisitor visitor) => visitor.VisitCFGCatchBlock(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents control flow block of case item.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("CaseBlock")]
|
||||
public class CaseBlock : Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets case value expression. In case of default item, returns <c>null</c>.
|
||||
/// </summary>
|
||||
public BoundExpression CaseValue { get { return _caseValue; } }
|
||||
private readonly BoundExpression _caseValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating whether the case represents a default.
|
||||
/// </summary>
|
||||
public bool IsDefault => _caseValue == null;
|
||||
|
||||
public CaseBlock(SwitchItem item)
|
||||
: base()
|
||||
{
|
||||
var caseItem = item as CaseItem;
|
||||
_caseValue = (caseItem != null)
|
||||
? SemanticsBinder.BindExpression(caseItem.CaseVal)
|
||||
: null; // DefaultItem has no value.
|
||||
}
|
||||
|
||||
public override void Accept(GraphVisitor visitor) => visitor.VisitCFGCaseBlock(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,635 @@
|
|||
using Pchp.Syntax;
|
||||
using Pchp.Syntax.AST;
|
||||
using Pchp.Syntax.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pchp.CodeAnalysis.Semantics.Graph
|
||||
{
|
||||
/// <summary>
|
||||
/// Visitor implementation that constructs the graph.
|
||||
/// </summary>
|
||||
internal sealed class BuilderVisitor : TreeVisitor
|
||||
{
|
||||
private Block/*!*/_current;
|
||||
private Dictionary<string, ControlFlowGraph.LabelBlockState> _labels;
|
||||
private List<BreakTargetScope> _breakTargets;
|
||||
private Stack<TryCatchEdge> _tryTargets;
|
||||
|
||||
public Block/*!*/Start { get; private set; }
|
||||
public Block/*!*/Exit { get; private set; }
|
||||
public Block Exception { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets labels defined within the routine.
|
||||
/// </summary>
|
||||
public ControlFlowGraph.LabelBlockState[] Labels
|
||||
{
|
||||
get { return (_labels != null) ? _labels.Values.ToArray() : EmptyArray<ControlFlowGraph.LabelBlockState>.Instance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocks we know nothing is pointing to (right after jump, throw, etc.).
|
||||
/// </summary>
|
||||
public List<Block>/*!*/DeadBlocks { get { return _deadBlocks; } }
|
||||
private readonly List<Block>/*!*/_deadBlocks = new List<Block>();
|
||||
|
||||
#region BreakTargetScope
|
||||
|
||||
/// <summary>
|
||||
/// Represents break scope.
|
||||
/// </summary>
|
||||
private struct BreakTargetScope
|
||||
{
|
||||
public readonly Block/*!*/BreakTarget;
|
||||
public readonly Block/*!*/ContinueTarget;
|
||||
|
||||
public BreakTargetScope(Block breakBlock, Block continueBlock)
|
||||
{
|
||||
BreakTarget = breakBlock;
|
||||
ContinueTarget = continueBlock;
|
||||
}
|
||||
}
|
||||
|
||||
private BreakTargetScope GetBreakTarget(int level)
|
||||
{
|
||||
if (level < 1) level = 1;
|
||||
if (_breakTargets == null || _breakTargets.Count < level)
|
||||
return default(BreakTargetScope);
|
||||
|
||||
return _breakTargets[_breakTargets.Count - level];
|
||||
}
|
||||
|
||||
private void EnterBreakTarget(Block breakBlock, Block continueBlock)
|
||||
{
|
||||
if (_breakTargets == null) _breakTargets = new List<BreakTargetScope>(1);
|
||||
_breakTargets.Add(new BreakTargetScope(breakBlock, continueBlock));
|
||||
}
|
||||
|
||||
private void ExitBreakTarget()
|
||||
{
|
||||
Debug.Assert(_breakTargets != null && _breakTargets.Count != 0);
|
||||
_breakTargets.RemoveAt(_breakTargets.Count - 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryTargetScope
|
||||
|
||||
private TryCatchEdge GetTryTarget()
|
||||
{
|
||||
if (_tryTargets == null || _tryTargets.Count == 0)
|
||||
return null;
|
||||
|
||||
return _tryTargets.Peek();
|
||||
}
|
||||
|
||||
private void EnterTryTarget(TryCatchEdge edge)
|
||||
{
|
||||
if (_tryTargets == null) _tryTargets = new Stack<TryCatchEdge>();
|
||||
_tryTargets.Push(edge);
|
||||
}
|
||||
|
||||
private void ExitTryTarget()
|
||||
{
|
||||
Debug.Assert(_tryTargets != null && _tryTargets.Count != 0);
|
||||
_tryTargets.Pop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
private BuilderVisitor(IList<Statement>/*!*/statements)
|
||||
{
|
||||
Contract.ThrowIfNull(statements);
|
||||
|
||||
this.Start = new StartBlock();
|
||||
this.Exit = new ExitBlock();
|
||||
|
||||
_current = this.Start;
|
||||
|
||||
statements.ForEach(this.VisitElement);
|
||||
_current = Connect(_current, this.Exit);
|
||||
}
|
||||
|
||||
public static BuilderVisitor/*!*/Build(IList<Statement>/*!*/statements)
|
||||
{
|
||||
return new BuilderVisitor(statements);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private Block/*!*/GetExceptionBlock()
|
||||
{
|
||||
if (this.Exception == null)
|
||||
this.Exception = new ExitBlock();
|
||||
return this.Exception;
|
||||
}
|
||||
|
||||
private void Add(Statement stmt)
|
||||
{
|
||||
_current.AddStatement(SemanticsBinder.BindStatement(stmt));
|
||||
}
|
||||
|
||||
private Block/*!*/NewBlock()
|
||||
{
|
||||
return new Block();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates block we know nothing is pointing to.
|
||||
/// Such block will be analysed later whether it is empty or whether it contains some statements (which will be reported as unreachable).
|
||||
/// </summary>
|
||||
private Block/*!*/NewDeadBlock()
|
||||
{
|
||||
var block = NewBlock();
|
||||
_deadBlocks.Add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
private CatchBlock/*!*/NewBlock(CatchItem item)
|
||||
{
|
||||
return new CatchBlock(item);
|
||||
}
|
||||
|
||||
private CaseBlock/*!*/NewBlock(SwitchItem item)
|
||||
{
|
||||
return new CaseBlock(item);
|
||||
}
|
||||
|
||||
private Block/*!*/Connect(Block/*!*/source, Block/*!*/ifTarget, Block/*!*/elseTarget, Expression/*!*/condition)
|
||||
{
|
||||
new ConditionalEdge(source, ifTarget, elseTarget, SemanticsBinder.BindExpression(condition));
|
||||
return ifTarget;
|
||||
}
|
||||
|
||||
private Block/*!*/Connect(Block/*!*/source, Block/*!*/target)
|
||||
{
|
||||
new SimpleEdge(source, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
private ControlFlowGraph.LabelBlockState/*!*/GetLabelBlock(string label)
|
||||
{
|
||||
if (_labels == null)
|
||||
_labels = new Dictionary<string, ControlFlowGraph.LabelBlockState>(StringComparer.Ordinal); // goto is case sensitive
|
||||
|
||||
ControlFlowGraph.LabelBlockState result;
|
||||
if (!_labels.TryGetValue(label, out result))
|
||||
{
|
||||
_labels[label] = result = new ControlFlowGraph.LabelBlockState()
|
||||
{
|
||||
Block = NewBlock(),
|
||||
GotoSpan = Span.Invalid,
|
||||
LabelSpan = Span.Invalid,
|
||||
Label = label,
|
||||
Flags = ControlFlowGraph.LabelBlockFlags.None,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Declaration Statements
|
||||
|
||||
public override void VisitTypeDecl(TypeDecl x)
|
||||
{
|
||||
if (x.IsConditional)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
// ignored
|
||||
}
|
||||
|
||||
public override void VisitMethodDecl(MethodDecl x)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
public override void VisitConstDeclList(ConstDeclList x)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
public override void VisitFunctionDecl(FunctionDecl x)
|
||||
{
|
||||
if (x.IsConditional)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
// ignored
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flow-Thru Statements
|
||||
|
||||
public override void VisitEmptyStmt(EmptyStmt x)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
public override void VisitEchoStmt(EchoStmt x)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitBlockStmt(BlockStmt x)
|
||||
{
|
||||
base.VisitBlockStmt(x); // visit nested statements
|
||||
}
|
||||
|
||||
public override void VisitDeclareStmt(DeclareStmt x)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitGlobalCode(GlobalCode x)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override void VisitGlobalStmt(GlobalStmt x)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitStaticStmt(StaticStmt x)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitExpressionStmt(ExpressionStmt x)
|
||||
{
|
||||
Add(x);
|
||||
|
||||
VisitElement(x.Expression as ExitEx);
|
||||
}
|
||||
|
||||
public override void VisitUnsetStmt(UnsetStmt x)
|
||||
{
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitPHPDocStmt(PHPDocStmt x)
|
||||
{
|
||||
if (x.GetProperty<LangElement>() == null)
|
||||
{
|
||||
// if PHPDoc is not associated with any declaration yet
|
||||
Add(x);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conditional Statements
|
||||
|
||||
public override void VisitConditionalStmt(ConditionalStmt x)
|
||||
{
|
||||
throw new InvalidOperationException(); // should be handled by IfStmt
|
||||
}
|
||||
|
||||
public override void VisitExitEx(ExitEx x)
|
||||
{
|
||||
// NOTE: Added by VisitExpressionStmt already
|
||||
// NOTE: similar to ThrowEx but unhandleable
|
||||
|
||||
// connect to Exception block
|
||||
Connect(_current, this.GetExceptionBlock()); // unreachable
|
||||
_current = NewDeadBlock();
|
||||
}
|
||||
|
||||
public override void VisitForeachStmt(ForeachStmt x)
|
||||
{
|
||||
var end = NewBlock();
|
||||
var move = NewBlock();
|
||||
var body = NewBlock();
|
||||
|
||||
// _current -> move -> body -> move -> ...
|
||||
|
||||
// ForeachEnumereeEdge : SimpleEdge
|
||||
// x.Enumeree.GetEnumerator();
|
||||
var enumereeEdge = new ForeachEnumereeEdge(_current, move, SemanticsBinder.BindExpression(x.Enumeree));
|
||||
|
||||
// ContinueTarget:
|
||||
EnterBreakTarget(end, move);
|
||||
|
||||
// ForeachMoveNextEdge : ConditionalEdge
|
||||
var moveEdge = new ForeachMoveNextEdge(move, body, end, enumereeEdge, x.KeyVariable, x.ValueVariable);
|
||||
// while (enumerator.MoveNext()) {
|
||||
// var key = enumerator.Current.Key
|
||||
// var value = enumerator.Current.Value
|
||||
|
||||
// Block
|
||||
// { x.Body }
|
||||
_current = body;
|
||||
VisitElement(x.Body);
|
||||
// goto ContinueTarget;
|
||||
Connect(_current, move);
|
||||
|
||||
// BreakTarget:
|
||||
ExitBreakTarget();
|
||||
|
||||
//
|
||||
_current = end;
|
||||
}
|
||||
|
||||
private void BuildForLoop(List<Expression> initExpr, List<Expression> condExpr, List<Expression> actionExpr, Statement/*!*/bodyStmt)
|
||||
{
|
||||
var end = NewBlock();
|
||||
|
||||
bool hasActions = actionExpr != null && actionExpr.Count != 0;
|
||||
bool hasConditions = condExpr != null && condExpr.Count != 0;
|
||||
|
||||
// { initializer }
|
||||
if (initExpr != null && initExpr.Count != 0)
|
||||
initExpr.ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr)));
|
||||
|
||||
var body = NewBlock();
|
||||
var cond = hasConditions ? NewBlock() : body;
|
||||
var action = hasActions ? NewBlock() : cond;
|
||||
EnterBreakTarget(end, action);
|
||||
|
||||
// while (x.Codition) {
|
||||
_current = Connect(_current, cond);
|
||||
if (hasConditions)
|
||||
{
|
||||
if (condExpr.Count > 1)
|
||||
condExpr.Take(condExpr.Count - 1).ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr)));
|
||||
_current = Connect(_current, body, end, condExpr.LastOrDefault());
|
||||
}
|
||||
else
|
||||
{
|
||||
_deadBlocks.Add(end);
|
||||
}
|
||||
|
||||
// { x.Body }
|
||||
VisitElement(bodyStmt);
|
||||
// { x.Action }
|
||||
if (hasActions)
|
||||
{
|
||||
_current = Connect(_current, action);
|
||||
actionExpr.ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr)));
|
||||
}
|
||||
// }
|
||||
Connect(_current, cond);
|
||||
|
||||
//
|
||||
ExitBreakTarget();
|
||||
|
||||
//
|
||||
_current = end;
|
||||
}
|
||||
|
||||
public override void VisitForStmt(ForStmt x)
|
||||
{
|
||||
BuildForLoop(x.InitExList, x.CondExList, x.ActionExList, x.Body);
|
||||
}
|
||||
|
||||
public override void VisitGotoStmt(GotoStmt x)
|
||||
{
|
||||
Add(x);
|
||||
|
||||
var/*!*/label = GetLabelBlock(x.LabelName.Value);
|
||||
label.Flags |= ControlFlowGraph.LabelBlockFlags.Used; // label is used
|
||||
label.GotoSpan = x.Span;
|
||||
|
||||
Connect(_current, label.Block);
|
||||
|
||||
_current = NewDeadBlock(); // any statement inside this block would be unreachable unless it is LabelStmt
|
||||
}
|
||||
|
||||
public override void VisitJumpStmt(JumpStmt x)
|
||||
{
|
||||
Add(x);
|
||||
|
||||
if (x.Type == JumpStmt.Types.Return)
|
||||
{
|
||||
Connect(_current, this.Exit);
|
||||
}
|
||||
else if (x.Type == JumpStmt.Types.Break || x.Type == JumpStmt.Types.Continue)
|
||||
{
|
||||
int level = (x.Expression is IntLiteral)
|
||||
? ((IntLiteral)x.Expression).Value
|
||||
: 1;
|
||||
|
||||
var brk = GetBreakTarget(level);
|
||||
var target = (x.Type == JumpStmt.Types.Break) ? brk.BreakTarget : brk.ContinueTarget;
|
||||
if (target != null)
|
||||
{
|
||||
Connect(_current, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
Connect(_current, this.GetExceptionBlock()); // unreachable // fatal error in PHP
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_current = NewDeadBlock(); // anything after these statements is unreachable
|
||||
}
|
||||
|
||||
public override void VisitIfStmt(IfStmt x)
|
||||
{
|
||||
var end = NewBlock();
|
||||
|
||||
var conditions = x.Conditions;
|
||||
Debug.Assert(conditions.Count != 0);
|
||||
Block elseBlock = null;
|
||||
for (int i = 0; i < conditions.Count; i++)
|
||||
{
|
||||
var cond = conditions[i];
|
||||
if (cond.Condition != null) // if (Condition) ...
|
||||
{
|
||||
elseBlock = (i == conditions.Count - 1) ? end : NewBlock();
|
||||
_current = Connect(_current, NewBlock(), elseBlock, cond.Condition);
|
||||
}
|
||||
else // else ...
|
||||
{
|
||||
Debug.Assert(i != 0 && elseBlock != null);
|
||||
var body = elseBlock;
|
||||
elseBlock = end; // last ConditionalStmt
|
||||
_current = Connect(_current, body);
|
||||
}
|
||||
|
||||
VisitElement(cond.Statement);
|
||||
Connect(_current, end);
|
||||
_current = elseBlock;
|
||||
}
|
||||
|
||||
Debug.Assert(_current == end);
|
||||
}
|
||||
|
||||
public override void VisitLabelStmt(LabelStmt x)
|
||||
{
|
||||
var/*!*/label = GetLabelBlock(x.Name.Value);
|
||||
if ((label.Flags & ControlFlowGraph.LabelBlockFlags.Defined) != 0)
|
||||
label.Flags |= ControlFlowGraph.LabelBlockFlags.Redefined; // label was defined already
|
||||
label.Flags |= ControlFlowGraph.LabelBlockFlags.Defined; // label is defined
|
||||
label.LabelSpan = x.Span;
|
||||
|
||||
_current = Connect(_current, label.Block);
|
||||
|
||||
Add(x);
|
||||
}
|
||||
|
||||
public override void VisitSwitchStmt(SwitchStmt x)
|
||||
{
|
||||
var items = x.SwitchItems;
|
||||
if (items == null || items.Length == 0)
|
||||
return;
|
||||
|
||||
var end = NewBlock();
|
||||
|
||||
bool hasDefault = false;
|
||||
var cases = new List<CaseBlock>(items.Length);
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
cases.Add(NewBlock(items[i]));
|
||||
hasDefault |= (items[i] is DefaultItem);
|
||||
}
|
||||
if (!hasDefault)
|
||||
{
|
||||
// create implicit default:
|
||||
cases.Add(NewBlock(new DefaultItem(x.Span, EmptyArray<Statement>.Instance)));
|
||||
}
|
||||
|
||||
// SwitchEdge // Connects _current to cases
|
||||
var edge = new SwitchEdge(_current, SemanticsBinder.BindExpression(x.SwitchValue), cases.ToArray());
|
||||
_current = cases[0];
|
||||
|
||||
EnterBreakTarget(end, end); // NOTE: inside switch, Continue ~ Break
|
||||
|
||||
for (int i = 0; i < cases.Count; i++)
|
||||
{
|
||||
if (i < items.Length)
|
||||
items[i].Statements.ForEach(VisitElement); // any break will connect block to end
|
||||
|
||||
_current = Connect(_current, (i == cases.Count - 1) ? end : cases[i + 1]);
|
||||
}
|
||||
|
||||
ExitBreakTarget();
|
||||
|
||||
Debug.Assert(_current == end);
|
||||
}
|
||||
|
||||
public override void VisitThrowStmt(ThrowStmt x)
|
||||
{
|
||||
Add(x);
|
||||
|
||||
//var tryedge = GetTryTarget();
|
||||
//if (tryedge != null)
|
||||
//{
|
||||
// // find handling catch block
|
||||
// QualifiedName qname;
|
||||
// var newex = x.Expression as NewEx;
|
||||
// if (newex != null && newex.ClassNameRef is DirectTypeRef)
|
||||
// {
|
||||
// qname = ((DirectTypeRef)newex.ClassNameRef).ClassName;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// qname = new QualifiedName(Name.EmptyBaseName);
|
||||
// }
|
||||
|
||||
// CatchBlock handlingCatch = tryedge.HandlingCatch(qname);
|
||||
// if (handlingCatch != null)
|
||||
// {
|
||||
// // throw jumps to a catch item in runtime
|
||||
// }
|
||||
//}
|
||||
|
||||
// connect to Exception block
|
||||
Connect(_current, this.GetExceptionBlock());
|
||||
_current = NewDeadBlock(); // unreachable
|
||||
}
|
||||
|
||||
public override void VisitTryStmt(TryStmt x)
|
||||
{
|
||||
// try {
|
||||
// x.Body
|
||||
// }
|
||||
// catch (E1) { body }
|
||||
// catch (E2) { body }
|
||||
// finally { body }
|
||||
// end
|
||||
|
||||
var end = NewBlock();
|
||||
var body = NewBlock();
|
||||
|
||||
// init catch blocks and finally block
|
||||
var catchBlocks = new CatchBlock[(x.Catches == null) ? 0 : x.Catches.Length];
|
||||
Block finallyBlock = null;
|
||||
|
||||
for (int i = 0; i < catchBlocks.Length; i++)
|
||||
catchBlocks[i] = NewBlock(x.Catches[i]);
|
||||
|
||||
if (x.FinallyItem != null)
|
||||
finallyBlock = NewBlock();
|
||||
|
||||
// TryCatchEdge // Connects _current to body, catch blocks and finally
|
||||
var edge = new TryCatchEdge(_current, body, catchBlocks, finallyBlock);
|
||||
|
||||
// build try body
|
||||
EnterTryTarget(edge);
|
||||
_current = body;
|
||||
x.Statements.ForEach(VisitElement);
|
||||
ExitTryTarget();
|
||||
_current = Connect(_current, finallyBlock ?? end);
|
||||
|
||||
// built catches
|
||||
for (int i = 0; i < catchBlocks.Length; i++)
|
||||
{
|
||||
_current = catchBlocks[i];
|
||||
x.Catches[i].Statements.ForEach(VisitElement);
|
||||
_current = Connect(_current, finallyBlock ?? end);
|
||||
}
|
||||
|
||||
// build finally
|
||||
if (finallyBlock != null)
|
||||
{
|
||||
_current = finallyBlock;
|
||||
x.FinallyItem.Statements.ForEach(VisitElement);
|
||||
_current = Connect(_current, end);
|
||||
}
|
||||
|
||||
// _current == end
|
||||
}
|
||||
|
||||
public override void VisitWhileStmt(WhileStmt x)
|
||||
{
|
||||
if (x.LoopType == WhileStmt.Type.Do)
|
||||
{
|
||||
var end = NewBlock();
|
||||
var body = NewBlock();
|
||||
EnterBreakTarget(end, body);
|
||||
_current = Connect(_current, body);
|
||||
// do {
|
||||
VisitElement(x.Body);
|
||||
Connect(_current, body, end, x.CondExpr);
|
||||
// } while (x.CondExpr)
|
||||
ExitBreakTarget();
|
||||
_current = end;
|
||||
}
|
||||
else if (x.LoopType == WhileStmt.Type.While)
|
||||
{
|
||||
Debug.Assert(x.CondExpr != null);
|
||||
BuildForLoop(null, new List<Expression>(1) { x.CondExpr }, null, x.Body);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
using Pchp.Syntax.AST;
|
||||
using Pchp.Syntax.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pchp.CodeAnalysis.Semantics.Graph
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents statements control flow graph.
|
||||
/// </summary>
|
||||
public sealed class ControlFlowGraph : AstNode
|
||||
{
|
||||
#region LabelBlockFlags, LabelBlockInfo
|
||||
|
||||
/// <summary>
|
||||
/// Found label reference (definition or target) information.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LabelBlockFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Not used nor defined.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Label is defined.
|
||||
/// </summary>
|
||||
Defined = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Label is used as a target.
|
||||
/// </summary>
|
||||
Used = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Label was defined twice or more.
|
||||
/// </summary>
|
||||
Redefined = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Label state.
|
||||
/// </summary>
|
||||
public sealed class LabelBlockState
|
||||
{
|
||||
/// <summary>
|
||||
/// Label identifier.
|
||||
/// </summary>
|
||||
public string Label;
|
||||
|
||||
/// <summary>
|
||||
/// Positions of label definition and last label use.
|
||||
/// </summary>
|
||||
public Span GotoSpan, LabelSpan;
|
||||
|
||||
/// <summary>
|
||||
/// Lable target block.
|
||||
/// </summary>
|
||||
public Block Block;
|
||||
|
||||
/// <summary>
|
||||
/// Label information.
|
||||
/// </summary>
|
||||
public LabelBlockFlags Flags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields & Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control flow start block. Cannot be <c>null</c>.
|
||||
/// </summary>
|
||||
public Block/*!*/Start { get { return _start; } }
|
||||
readonly Block/*!*/_start;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control flow exit block. Cannot be <c>null</c>.
|
||||
/// </summary>
|
||||
public Block/*!*/Exit { get { return _exit; } }
|
||||
readonly Block/*!*/_exit;
|
||||
|
||||
/// <summary>
|
||||
/// Exception block. Can be <c>null</c>.
|
||||
/// If set, code can throw an exception or be terminated by call to <c>exit</c>, before reaching exit block.
|
||||
/// This block is connected with blocks ending with <c>throw</c> statement.
|
||||
/// </summary>
|
||||
public Block Throws { get { return _exception; } }
|
||||
readonly Block _exception;
|
||||
|
||||
/// <summary>
|
||||
/// Array of labels within routine. Can be <c>null</c>.
|
||||
/// </summary>
|
||||
public LabelBlockState[] Labels { get { return _labels; } }
|
||||
readonly LabelBlockState[] _labels;
|
||||
|
||||
/// <summary>
|
||||
/// List of blocks that are unreachable syntactically (statements after JumpStmt etc.).
|
||||
/// </summary>
|
||||
public List<Block>/*!*/UnreachableBlocks { get { return _unrecachable; } }
|
||||
readonly List<Block>/*!*/_unrecachable;
|
||||
|
||||
/// <summary>
|
||||
/// Last "tag" color used. Used internally for graph algorithms.
|
||||
/// </summary>
|
||||
int _lastcolor = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public ControlFlowGraph(IList<Statement>/*!*/statements)
|
||||
: this(BuilderVisitor.Build(statements))
|
||||
{
|
||||
}
|
||||
|
||||
private ControlFlowGraph(BuilderVisitor/*!*/builder)
|
||||
: this(builder.Start, builder.Exit, builder.Exception, builder.Labels, builder.DeadBlocks)
|
||||
{
|
||||
}
|
||||
|
||||
private ControlFlowGraph(Block/*!*/start, Block/*!*/exit, Block exception, LabelBlockState[] labels, List<Block> unreachable)
|
||||
{
|
||||
Contract.ThrowIfNull(start);
|
||||
Contract.ThrowIfNull(exit);
|
||||
|
||||
_start = start;
|
||||
_exit = exit;
|
||||
_exception = exception;
|
||||
_labels = labels;
|
||||
_unrecachable = unreachable ?? new List<Block>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets new (unique) color for use by graph algorithms.
|
||||
/// </summary>
|
||||
/// <returns>New color index.</returns>
|
||||
public int NewColor()
|
||||
{
|
||||
return unchecked(++_lastcolor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits control flow blocks and contained statements, in deep.
|
||||
/// Unreachable blocks are not visited.
|
||||
/// </summary>
|
||||
/// <remarks>Visitor does not implement infinite recursion prevention.</remarks>
|
||||
public void Visit(GraphVisitor/*!*/visitor) => visitor.VisitCFG(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
using Microsoft.CodeAnalysis.Semantics;
|
||||
using Pchp.Syntax;
|
||||
using Pchp.Syntax.AST;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pchp.CodeAnalysis.Semantics.Graph
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents edge between to graph blocks.
|
||||
/// </summary>
|
||||
public abstract class Edge : AstNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties key as a recursion prevention when visiting edges recursively.
|
||||
/// </summary>
|
||||
protected static readonly object/*!*/RecursionLockKey = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Target blocks.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<Block>/*!!*/Targets { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating whether the edge represents a conditional edge.
|
||||
/// </summary>
|
||||
public virtual bool IsConditional => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating whether the edge represents try/catch.
|
||||
/// </summary>
|
||||
public virtual bool IsTryCatch => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating whether the edge represents switch.
|
||||
/// </summary>
|
||||
public virtual bool IsSwitch => false;
|
||||
|
||||
/// <summary>
|
||||
/// Condition expression of conditional edge.
|
||||
/// </summary>
|
||||
public virtual BoundExpression Condition => null;
|
||||
|
||||
/// <summary>
|
||||
/// Catch blocks if try/catch edge.
|
||||
/// </summary>
|
||||
public virtual CatchBlock[] CatchBlocks => EmptyArray<CatchBlock>.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Finally block of try/catch edge.
|
||||
/// </summary>
|
||||
public virtual Block FinallyBlock => null;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration with single case blocks.
|
||||
/// </summary>
|
||||
public virtual CaseBlock[] CaseBlocks => EmptyArray<CaseBlock>.Instance;
|
||||
|
||||
internal Edge(Block/*!*/source)
|
||||
{
|
||||
Contract.ThrowIfNull(source);
|
||||
}
|
||||
|
||||
protected void Connect(Block/*!*/source)
|
||||
{
|
||||
source.NextEdge = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public abstract void Visit(GraphVisitor visitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents simple unconditional jump.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("SimpleEdge")]
|
||||
public class SimpleEdge : Edge
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the target block if the simple edge.
|
||||
/// </summary>
|
||||
public Block Target { get { return _target; } }
|
||||
private readonly Block _target;
|
||||
|
||||
internal SimpleEdge(Block source, Block target)
|
||||
: base(source)
|
||||
{
|
||||
_target = target;
|
||||
Connect(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Target blocks.
|
||||
/// </summary>
|
||||
public override IEnumerable<Block> Targets => new Block[] { _target };
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor) => visitor.VisitCFGSimpleEdge(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conditional edge.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("ConditionalEdge")]
|
||||
public sealed class ConditionalEdge : Edge
|
||||
{
|
||||
private readonly Block _true, _false;
|
||||
private readonly BoundExpression _condition;
|
||||
|
||||
/// <summary>
|
||||
/// Target true block
|
||||
/// </summary>
|
||||
public Block/*!*/TrueTarget => _true;
|
||||
|
||||
/// <summary>
|
||||
/// Target false block.
|
||||
/// </summary>
|
||||
public Block/*!*/FalseTarget => _false;
|
||||
|
||||
internal ConditionalEdge(Block source, Block @true, Block @false, BoundExpression cond)
|
||||
: base(source)
|
||||
{
|
||||
Debug.Assert(@true != @false);
|
||||
|
||||
_true = @true;
|
||||
_false = @false;
|
||||
_condition = cond;
|
||||
|
||||
Connect(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All target blocks.
|
||||
/// </summary>
|
||||
public override IEnumerable<Block> Targets => new Block[] { _true, _false };
|
||||
|
||||
public override bool IsConditional
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override BoundExpression Condition => _condition;
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor) => visitor.VisitCFGConditionalEdge(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents try/catch edge.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("TryCatchEdge")]
|
||||
public sealed class TryCatchEdge : Edge
|
||||
{
|
||||
private readonly Block _body;
|
||||
private readonly CatchBlock[] _catchBlocks;
|
||||
private readonly Block _finallyBlock;
|
||||
|
||||
/// <summary>
|
||||
/// Try block.
|
||||
/// </summary>
|
||||
public Block BodyBlock => _body;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given class name is equal to <c>Exception</c>.
|
||||
/// </summary>
|
||||
private static bool IsExceptionClassName(DirectTypeRef tref)
|
||||
{
|
||||
return
|
||||
tref.GenericParams.Count == 0 &&
|
||||
tref.ClassName == NameUtils.SpecialNames.Exception;
|
||||
}
|
||||
|
||||
internal CatchBlock HandlingCatch(QualifiedName exceptionClassName)
|
||||
{
|
||||
foreach (var block in _catchBlocks)
|
||||
if (block.TypeRef.ClassName == exceptionClassName || IsExceptionClassName(block.TypeRef))
|
||||
return block;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal TryCatchEdge(Block source, Block body, CatchBlock[] catchBlocks, Block finallyBlock)
|
||||
: base(source)
|
||||
{
|
||||
_body = body;
|
||||
_catchBlocks = catchBlocks;
|
||||
_finallyBlock = finallyBlock;
|
||||
Connect(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All target blocks.
|
||||
/// </summary>
|
||||
public override IEnumerable<Block> Targets
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<Block>(_catchBlocks.Length + 2);
|
||||
|
||||
list.Add(_body);
|
||||
list.AddRange(_catchBlocks);
|
||||
if (_finallyBlock != null)
|
||||
list.Add(_finallyBlock);
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsTryCatch => true;
|
||||
|
||||
public override CatchBlock[] CatchBlocks => _catchBlocks;
|
||||
|
||||
public override Block FinallyBlock => _finallyBlock;
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor)
|
||||
{
|
||||
visitor.VisitCFGTryCatchEdge(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents foreach edge through the enumeree invocation.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("ForeachEnumeree")]
|
||||
public sealed class ForeachEnumereeEdge : SimpleEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Array to enumerate through.
|
||||
/// </summary>
|
||||
public BoundExpression Enumeree => _enumeree;
|
||||
private readonly BoundExpression _enumeree;
|
||||
|
||||
internal ForeachEnumereeEdge(Block/*!*/source, Block/*!*/target, BoundExpression/*!*/enumeree)
|
||||
: base(source, target)
|
||||
{
|
||||
Contract.ThrowIfNull(enumeree);
|
||||
_enumeree = enumeree;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor)
|
||||
{
|
||||
visitor.VisitCFGForeachEnumereeEdge(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents foreach edge from enumeree invocation through <c>MoveNext</c> to body block or end.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("ForeachMoveNextEdge")]
|
||||
public sealed class ForeachMoveNextEdge : Edge
|
||||
{
|
||||
/// <summary>
|
||||
/// Content of the foreach.
|
||||
/// </summary>
|
||||
public Block BodyBlock => _body;
|
||||
|
||||
/// <summary>
|
||||
/// Block after the foreach.
|
||||
/// </summary>
|
||||
public Block EndBlock => _end;
|
||||
readonly Block _body, _end;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the edge defining the enumeree.
|
||||
/// </summary>
|
||||
public ForeachEnumereeEdge EnumereeEdge => _enumereeEdge;
|
||||
readonly ForeachEnumereeEdge _enumereeEdge;
|
||||
|
||||
/// <summary>
|
||||
/// Variable to store key in (can be null).
|
||||
/// </summary>
|
||||
public ForeachVar KeyVariable { get { return _keyVariable; } }
|
||||
readonly ForeachVar _keyVariable;
|
||||
|
||||
/// <summary>
|
||||
/// Variable to store value in
|
||||
/// </summary>
|
||||
public ForeachVar ValueVariable { get { return _valueVariable; } }
|
||||
readonly ForeachVar _valueVariable;
|
||||
|
||||
internal ForeachMoveNextEdge(Block/*!*/source, Block/*!*/body, Block/*!*/end, ForeachEnumereeEdge/*!*/enumereeEdge, ForeachVar keyVar, ForeachVar/*!*/valueVar)
|
||||
: base(source)
|
||||
{
|
||||
Contract.ThrowIfNull(body);
|
||||
Contract.ThrowIfNull(end);
|
||||
Contract.ThrowIfNull(enumereeEdge);
|
||||
|
||||
_body = body;
|
||||
_end = end;
|
||||
_enumereeEdge = enumereeEdge;
|
||||
_keyVariable = keyVar;
|
||||
_valueVariable = valueVar;
|
||||
|
||||
Connect(source);
|
||||
}
|
||||
|
||||
public override IEnumerable<Block> Targets
|
||||
{
|
||||
get { return new Block[] { _body, _end }; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor)
|
||||
{
|
||||
visitor.VisitCFGForeachMoveNextEdge(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents switch edge.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("SwitchEdge")]
|
||||
public sealed class SwitchEdge : Edge
|
||||
{
|
||||
readonly BoundExpression _switchValue;
|
||||
readonly CaseBlock[] _caseBlocks;
|
||||
|
||||
/// <summary>
|
||||
/// The expression representing the switch value.
|
||||
/// </summary>
|
||||
public BoundExpression SwitchValue => _switchValue;
|
||||
|
||||
public override IEnumerable<Block> Targets => _caseBlocks;
|
||||
|
||||
public override bool IsSwitch => true;
|
||||
|
||||
public override CaseBlock[] CaseBlocks => _caseBlocks;
|
||||
|
||||
internal SwitchEdge(Block source, BoundExpression switchValue, CaseBlock[] caseBlocks)
|
||||
: base(source)
|
||||
{
|
||||
Contract.ThrowIfNull(caseBlocks);
|
||||
_switchValue = switchValue;
|
||||
_caseBlocks = caseBlocks;
|
||||
|
||||
Connect(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the object by given visitor.
|
||||
/// </summary>
|
||||
public override void Visit(GraphVisitor visitor)
|
||||
{
|
||||
visitor.VisitCFGSwitchEdge(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
using Microsoft.CodeAnalysis.Semantics;
|
||||
using Pchp.Syntax.AST;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pchp.CodeAnalysis.Semantics.Graph
|
||||
{
|
||||
/// <summary>
|
||||
/// Control flow graph visitor.
|
||||
/// </summary>
|
||||
/// <remarks>Visitor does not implement infinite recursion prevention.</remarks>
|
||||
public class GraphVisitor : TreeVisitor
|
||||
{
|
||||
#region Declarations (ignored)
|
||||
|
||||
public override void VisitTypeDecl(TypeDecl x)
|
||||
{
|
||||
}
|
||||
|
||||
public override void VisitMethodDecl(MethodDecl x)
|
||||
{
|
||||
}
|
||||
|
||||
public override void VisitConstDeclList(ConstDeclList x)
|
||||
{
|
||||
}
|
||||
|
||||
public override void VisitFunctionDecl(FunctionDecl x)
|
||||
{
|
||||
}
|
||||
|
||||
public override void VisitLambdaFunctionExpr(LambdaFunctionExpr x)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bound
|
||||
|
||||
/// <summary>
|
||||
/// Visitor for bound operations.
|
||||
/// </summary>
|
||||
readonly OperationVisitor _opvisitor;
|
||||
|
||||
/// <summary>
|
||||
/// Forwards the operation to the <see cref="OperationVisitor"/>.
|
||||
/// </summary>
|
||||
void Accept(IOperation op) => op.Accept(_opvisitor);
|
||||
|
||||
#endregion
|
||||
|
||||
#region ControlFlowGraph
|
||||
|
||||
public GraphVisitor(OperationVisitor opvisitor)
|
||||
{
|
||||
Contract.ThrowIfNull(opvisitor);
|
||||
_opvisitor = opvisitor;
|
||||
}
|
||||
|
||||
public virtual void VisitCFG(ControlFlowGraph x) => x.Start.Accept(this);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Graph.Block
|
||||
|
||||
void VisitCFGBlockStatements(Block x)
|
||||
{
|
||||
for (int i = 0; i < x.Statements.Count; i++)
|
||||
{
|
||||
Accept(x.Statements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits block statements and its edge to next block.
|
||||
/// </summary>
|
||||
protected virtual void VisitCFGBlockInternal(Block x)
|
||||
{
|
||||
VisitCFGBlockStatements(x);
|
||||
|
||||
if (x.NextEdge != null)
|
||||
x.NextEdge.Visit(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGBlock(Block x)
|
||||
{
|
||||
VisitCFGBlockInternal(x);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGCatchBlock(CatchBlock x)
|
||||
{
|
||||
VisitCFGBlockInternal(x);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGCaseBlock(CaseBlock x)
|
||||
{
|
||||
Accept(x.CaseValue);
|
||||
VisitCFGBlockInternal(x);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Graph.Edge
|
||||
|
||||
public virtual void VisitCFGSimpleEdge(SimpleEdge x)
|
||||
{
|
||||
Debug.Assert(x.Target != null);
|
||||
x.Target.Accept(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGConditionalEdge(ConditionalEdge x)
|
||||
{
|
||||
Accept(x.Condition);
|
||||
|
||||
x.TrueTarget.Accept(this);
|
||||
x.FalseTarget.Accept(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGTryCatchEdge(TryCatchEdge x)
|
||||
{
|
||||
x.BodyBlock.Accept(this);
|
||||
|
||||
foreach (var c in x.CatchBlocks)
|
||||
c.Accept(this);
|
||||
|
||||
if (x.FinallyBlock != null)
|
||||
x.FinallyBlock.Accept(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGForeachEnumereeEdge(ForeachEnumereeEdge x)
|
||||
{
|
||||
Accept(x.Enumeree);
|
||||
x.Target.Accept(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGForeachMoveNextEdge(ForeachMoveNextEdge x)
|
||||
{
|
||||
x.BodyBlock.Accept(this);
|
||||
x.EndBlock.Accept(this);
|
||||
}
|
||||
|
||||
public virtual void VisitCFGSwitchEdge(SwitchEdge x)
|
||||
{
|
||||
Accept(x.SwitchValue);
|
||||
|
||||
//
|
||||
var arr = x.CaseBlocks;
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
arr[i].Accept(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Pchp.CodeAnalysis.Semantics
|
|||
/// Represents PHP semantics.
|
||||
/// Used to query semantic questions about the compilation in specific context.
|
||||
/// </summary>
|
||||
/// <remarks>Use <see cref="Microsoft.CodeAnalysis.SemanticModel"/> once we implement <see cref="SyntaxTree"/>.</remarks>
|
||||
/// <remarks>Use <see cref="SemanticModel"/> once we implement <see cref="SyntaxTree"/>.</remarks>
|
||||
internal interface ISemanticModel
|
||||
{
|
||||
// TODO: source file, constant, local variable
|
||||
|
|
|
@ -15,12 +15,9 @@ namespace Pchp.CodeAnalysis.Semantics
|
|||
/// </summary>
|
||||
internal static class SemanticsBinder
|
||||
{
|
||||
public static IEnumerable<BoundStatement> BindStatements(IList<AST.Statement> statements)
|
||||
public static IEnumerable<BoundStatement> BindStatements(IEnumerable<AST.Statement> statements)
|
||||
{
|
||||
Debug.Assert(statements != null);
|
||||
|
||||
for (int i = 0; i < statements.Count; i++)
|
||||
yield return BindStatement(statements[i]);
|
||||
return statements.Select(BindStatement);
|
||||
}
|
||||
|
||||
public static BoundStatement BindStatement(AST.Statement stmt)
|
||||
|
|
|
@ -47,24 +47,22 @@ namespace Pchp.CodeAnalysis.Utilities
|
|||
/// <summary>
|
||||
/// Dequeues item from the queue.
|
||||
/// </summary>
|
||||
public T TryDequeue()
|
||||
public bool TryDequeue(out T value)
|
||||
{
|
||||
T value;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_queue.Count != 0)
|
||||
{
|
||||
value = _queue.Dequeue();
|
||||
_set.Remove(value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче