Skip to content

Commit

Permalink
refacotors try/catch/finally handling to be able to more easily handl…
Browse files Browse the repository at this point in the history
…e emission of code in try/finally blocks

the initial motication was to avoid code duplication in foreach handling (#235)
  • Loading branch information
adrianoc committed Jul 22, 2023
1 parent 11687ee commit 495aaa4
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 109 deletions.
121 changes: 121 additions & 0 deletions Cecilifier.Core/AST/StatementVisitor.TryCatchFinally.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Mono.Cecil.Cil;

namespace Cecilifier.Core.AST
{
internal partial class StatementVisitor
{
private void ProcessTryCatchFinallyBlock(string ilVar, CSharpSyntaxNode tryStatement, CatchClauseSyntax[] catches, Action<string> finallyBlockHandler)
{
ProcessWithInTryCatchFinallyBlock(ilVar, () => tryStatement.Accept(this), catches, finallyBlockHandler);
}

protected void ProcessWithInTryCatchFinallyBlock(string ilVar, Action toProcess, CatchClauseSyntax[] catches, Action<string> finallyBlockHandler)
{
var exceptionHandlerTable = new ExceptionHandlerEntry[catches.Length + (finallyBlockHandler != null ? 1 : 0)];

var tryStartVar = AddCilInstructionWithLocalVariable(ilVar, OpCodes.Nop);
exceptionHandlerTable[0].TryStart = tryStartVar;

toProcess();

var firstInstructionAfterTryCatchBlock = CreateCilInstruction(ilVar, OpCodes.Nop);
exceptionHandlerTable[^1].HandlerEnd = firstInstructionAfterTryCatchBlock; // sets up last handler end instruction

Context.EmitCilInstruction(ilVar, OpCodes.Leave, firstInstructionAfterTryCatchBlock);

for (var i = 0; i < catches.Length; i++)
{
HandleCatchClause(ilVar, catches[i], exceptionHandlerTable, i, firstInstructionAfterTryCatchBlock);
}

HandleFinallyClause(ilVar, finallyBlockHandler, exceptionHandlerTable);

AddCecilExpression($"{ilVar}.Append({firstInstructionAfterTryCatchBlock});");

WriteExceptionHandlers(exceptionHandlerTable);
}

private void WriteExceptionHandlers(IEnumerable<ExceptionHandlerEntry> exceptionHandlerTable)
{
string methodVar = Context.DefinitionVariables.GetLastOf(VariableMemberKind.Method);
foreach (var handlerEntry in exceptionHandlerTable)
{
AddCecilExpression($"{methodVar}.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.{handlerEntry.Kind})");
AddCecilExpression("{");
if (handlerEntry.Kind == ExceptionHandlerType.Catch)
{
AddCecilExpression($" CatchType = {handlerEntry.CatchType},");
}

AddCecilExpression($" TryStart = {handlerEntry.TryStart},");
AddCecilExpression($" TryEnd = {handlerEntry.TryEnd},");
AddCecilExpression($" HandlerStart = {handlerEntry.HandlerStart},");
AddCecilExpression($" HandlerEnd = {handlerEntry.HandlerEnd}");
AddCecilExpression("});");
}
}

private void HandleCatchClause(string ilVar, CatchClauseSyntax node, ExceptionHandlerEntry[] exceptionHandlerTable, int currentIndex, string firstInstructionAfterTryCatchBlock)
{
exceptionHandlerTable[currentIndex].Kind = ExceptionHandlerType.Catch;
exceptionHandlerTable[currentIndex].HandlerStart = AddCilInstructionWithLocalVariable(ilVar, OpCodes.Pop); // pops the exception object from stack...

if (currentIndex == 0)
{
// The last instruction of the try block is the first instruction of the first catch block
exceptionHandlerTable[0].TryEnd = exceptionHandlerTable[currentIndex].HandlerStart;
}
else
{
exceptionHandlerTable[currentIndex - 1].HandlerEnd = exceptionHandlerTable[currentIndex].HandlerStart;
}

exceptionHandlerTable[currentIndex].TryStart = exceptionHandlerTable[0].TryStart;
exceptionHandlerTable[currentIndex].TryEnd = exceptionHandlerTable[0].TryEnd;
exceptionHandlerTable[currentIndex].CatchType = ResolveType(node.Declaration.Type);

VisitCatchClause(node);
Context.EmitCilInstruction(ilVar, OpCodes.Leave, firstInstructionAfterTryCatchBlock);
}

private void HandleFinallyClause(string ilVar, Action<string> finallyBlockHandler, ExceptionHandlerEntry[] exceptionHandlerTable)
{
if (finallyBlockHandler == null)
return;

var finallyEntryIndex = exceptionHandlerTable.Length - 1;

exceptionHandlerTable[finallyEntryIndex].TryStart = exceptionHandlerTable[0].TryStart;
exceptionHandlerTable[finallyEntryIndex].TryEnd = exceptionHandlerTable[0].TryEnd;
exceptionHandlerTable[finallyEntryIndex].Kind = ExceptionHandlerType.Finally;

var finallyStartVar = AddCilInstructionWithLocalVariable(ilVar, OpCodes.Nop);
exceptionHandlerTable[finallyEntryIndex].HandlerStart = finallyStartVar;
exceptionHandlerTable[finallyEntryIndex].TryEnd = finallyStartVar;

if (finallyEntryIndex != 0)
{
// We have one or more catch blocks... set the end of the last catch block as the first instruction of the *finally*
exceptionHandlerTable[finallyEntryIndex - 1].HandlerEnd = finallyStartVar;
}

finallyBlockHandler(exceptionHandlerTable[finallyEntryIndex].HandlerEnd);
Context.EmitCilInstruction(ilVar, OpCodes.Endfinally);
}

private struct ExceptionHandlerEntry
{
public ExceptionHandlerType Kind;
public string CatchType;
public string TryStart;
public string TryEnd;
public string HandlerStart;
public string HandlerEnd;
}
}
}
112 changes: 3 additions & 109 deletions Cecilifier.Core/AST/StatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Cecilifier.Core.AST
{
internal class StatementVisitor : SyntaxWalkerBase
internal partial class StatementVisitor : SyntaxWalkerBase
{
private static string _ilVar;

Expand Down Expand Up @@ -212,7 +212,7 @@ public override void VisitTryStatement(TryStatementSyntax node)
(Action<string>) null :
(inst) => node.Finally.Accept(this);

ProcessTryCatchFinallyBlock(node.Block, node.Catches.ToArray(), finallyBlockHandler);
ProcessTryCatchFinallyBlock(_ilVar, node.Block, node.Catches.ToArray(), finallyBlockHandler);
}

public override void VisitThrowStatement(ThrowStatementSyntax node)
Expand Down Expand Up @@ -263,7 +263,7 @@ void FinallyBlockHandler(string finallyEndVar)
AddCecilExpression($"{_ilVar}.Append({lastFinallyInstructionLabel});");
}

ProcessTryCatchFinallyBlock(node.Statement, Array.Empty<CatchClauseSyntax>(), FinallyBlockHandler);
ProcessTryCatchFinallyBlock(_ilVar, node.Statement, Array.Empty<CatchClauseSyntax>(), FinallyBlockHandler);
}

public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node) => node.Accept(new MethodDeclarationVisitor(Context));
Expand All @@ -277,102 +277,6 @@ void FinallyBlockHandler(string finallyEndVar)
public override void VisitGotoStatement(GotoStatementSyntax node) => LogUnsupportedSyntax(node);
public override void VisitYieldStatement(YieldStatementSyntax node) { LogUnsupportedSyntax(node); }

private void ProcessTryCatchFinallyBlock(CSharpSyntaxNode tryStatement, CatchClauseSyntax[] catches, Action<string> finallyBlockHandler)
{
var exceptionHandlerTable = new ExceptionHandlerEntry[catches.Length + (finallyBlockHandler != null ? 1 : 0)];

var tryStartVar = AddCilInstructionWithLocalVariable(_ilVar, OpCodes.Nop);
exceptionHandlerTable[0].TryStart = tryStartVar;

tryStatement.Accept(this);

var firstInstructionAfterTryCatchBlock = CreateCilInstruction(_ilVar, OpCodes.Nop);
exceptionHandlerTable[^1].HandlerEnd = firstInstructionAfterTryCatchBlock; // sets up last handler end instruction

Context.EmitCilInstruction(_ilVar, OpCodes.Leave, firstInstructionAfterTryCatchBlock);

for (var i = 0; i < catches.Length; i++)
{
HandleCatchClause(catches[i], exceptionHandlerTable, i, firstInstructionAfterTryCatchBlock);
}

HandleFinallyClause(finallyBlockHandler, exceptionHandlerTable);

AddCecilExpression($"{_ilVar}.Append({firstInstructionAfterTryCatchBlock});");

WriteExceptionHandlers(exceptionHandlerTable);
}

private void WriteExceptionHandlers(ExceptionHandlerEntry[] exceptionHandlerTable)
{
string methodVar = Context.DefinitionVariables.GetLastOf(VariableMemberKind.Method);
foreach (var handlerEntry in exceptionHandlerTable)
{
AddCecilExpression($"{methodVar}.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.{handlerEntry.Kind})");
AddCecilExpression("{");
if (handlerEntry.Kind == ExceptionHandlerType.Catch)
{
AddCecilExpression($" CatchType = {handlerEntry.CatchType},");
}

AddCecilExpression($" TryStart = {handlerEntry.TryStart},");
AddCecilExpression($" TryEnd = {handlerEntry.TryEnd},");
AddCecilExpression($" HandlerStart = {handlerEntry.HandlerStart},");
AddCecilExpression($" HandlerEnd = {handlerEntry.HandlerEnd}");
AddCecilExpression("});");
}
}

private void HandleCatchClause(CatchClauseSyntax node, ExceptionHandlerEntry[] exceptionHandlerTable, int currentIndex, string firstInstructionAfterTryCatchBlock)
{
exceptionHandlerTable[currentIndex].Kind = ExceptionHandlerType.Catch;
exceptionHandlerTable[currentIndex].HandlerStart = AddCilInstructionWithLocalVariable(_ilVar, OpCodes.Pop); // pops the exception object from stack...

if (currentIndex == 0)
{
// The last instruction of the try block is the first instruction of the first catch block
exceptionHandlerTable[0].TryEnd = exceptionHandlerTable[currentIndex].HandlerStart;
}
else
{
exceptionHandlerTable[currentIndex - 1].HandlerEnd = exceptionHandlerTable[currentIndex].HandlerStart;
}

exceptionHandlerTable[currentIndex].TryStart = exceptionHandlerTable[0].TryStart;
exceptionHandlerTable[currentIndex].TryEnd = exceptionHandlerTable[0].TryEnd;
exceptionHandlerTable[currentIndex].CatchType = ResolveType(node.Declaration.Type);

VisitCatchClause(node);
Context.EmitCilInstruction(_ilVar, OpCodes.Leave, firstInstructionAfterTryCatchBlock);
}

private void HandleFinallyClause(Action<string> finallyBlockHandler, ExceptionHandlerEntry[] exceptionHandlerTable)
{
if (finallyBlockHandler == null)
{
return;
}

var finallyEntryIndex = exceptionHandlerTable.Length - 1;

exceptionHandlerTable[finallyEntryIndex].TryStart = exceptionHandlerTable[0].TryStart;
exceptionHandlerTable[finallyEntryIndex].TryEnd = exceptionHandlerTable[0].TryEnd;
exceptionHandlerTable[finallyEntryIndex].Kind = ExceptionHandlerType.Finally;

var finallyStartVar = AddCilInstructionWithLocalVariable(_ilVar, OpCodes.Nop);
exceptionHandlerTable[finallyEntryIndex].HandlerStart = finallyStartVar;
exceptionHandlerTable[finallyEntryIndex].TryEnd = finallyStartVar;

if (finallyEntryIndex != 0)
{
// We have one or more catch blocks... set the end of the last catch block as the first instruction of the *finally*
exceptionHandlerTable[finallyEntryIndex - 1].HandlerEnd = finallyStartVar;
}

finallyBlockHandler(exceptionHandlerTable[finallyEntryIndex].HandlerEnd);
Context.EmitCilInstruction(_ilVar, OpCodes.Endfinally);
}

private void AddLocalVariable(TypeSyntax type, VariableDeclaratorSyntax localVar, DefinitionVariable methodVar)
{
var resolvedVarType = type.IsVar
Expand Down Expand Up @@ -427,16 +331,6 @@ private void HandleVariableDeclaration(VariableDeclarationSyntax declaration)
}
}

private struct ExceptionHandlerEntry
{
public ExceptionHandlerType Kind;
public string CatchType;
public string TryStart;
public string TryEnd;
public string HandlerStart;
public string HandlerEnd;
}

// Stack with name of variables that holds instructions that a *break statement*
// will jump to. Each statement that supports *breaking* must push the instruction
// target of the break and pop it back when it gets out of scope.
Expand Down

0 comments on commit 495aaa4

Please sign in to comment.