Skip to content

Commit

Permalink
goto statement
Browse files Browse the repository at this point in the history
  • Loading branch information
Hecate2 committed Jan 20, 2025
1 parent 6e61956 commit 00956b4
Show file tree
Hide file tree
Showing 9 changed files with 468 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,13 @@ private void ReverseStackItems(int count)

#region LabelsAndTargets

private JumpTarget AddLabel(ILabelSymbol symbol, bool checkTryStack)
private JumpTarget AddLabel(ILabelSymbol symbol)
{
if (!_labels.TryGetValue(symbol, out JumpTarget? target))
{
target = new JumpTarget();
_labels.Add(symbol, target);
}
if (checkTryStack && _tryStack.TryPeek(out ExceptionHandling? result) && result.State != ExceptionHandlingState.Finally)
{
result.Labels.Add(symbol);
}
return target;
}

Expand Down
11 changes: 11 additions & 0 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/BlockStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ internal partial class MethodConvert
/// </example>
private void ConvertBlockStatement(SemanticModel model, BlockSyntax syntax)
{
StatementContext sc = new(syntax);
_generalStatementStack.Push(sc);
foreach (StatementSyntax label in syntax.Statements)
if (label is LabeledStatementSyntax l)
{
ILabelSymbol symbol = (ILabelSymbol)model.GetDeclaredSymbol(l)!;
JumpTarget target = AddLabel(symbol);
sc.AddLabel(symbol, target);
}
_blockSymbols.Push(new List<ILocalSymbol>());
using (InsertSequencePoint(syntax.OpenBraceToken))
AddInstruction(OpCode.NOP);
Expand All @@ -53,6 +62,8 @@ private void ConvertBlockStatement(SemanticModel model, BlockSyntax syntax)
AddInstruction(OpCode.NOP);
foreach (ILocalSymbol symbol in _blockSymbols.Pop())
RemoveLocalVariable(symbol);
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
121 changes: 87 additions & 34 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/GotoStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Neo.VM;
using System.Collections.Generic;
using System.Linq;

namespace Neo.Compiler
Expand Down Expand Up @@ -55,55 +56,107 @@ internal partial class MethodConvert
/// </code>
/// </example>
private void ConvertGotoStatement(SemanticModel model, GotoStatementSyntax syntax)
{
if (syntax.CaseOrDefaultKeyword.IsKind(SyntaxKind.None))
ConvertGotoNormalLabel(model, syntax);
else
ConvertGotoSwitchLabel(model, syntax);
}

private void ConvertGotoNormalLabel(SemanticModel model, GotoStatementSyntax syntax)
{
using (InsertSequencePoint(syntax))
if (syntax.CaseOrDefaultKeyword.IsKind(SyntaxKind.None))
{
ILabelSymbol symbol = (ILabelSymbol)model.GetSymbolInfo(syntax.Expression!).Symbol!;
JumpTarget target = AddLabel(symbol, false);
if (_tryStack.TryPeek(out ExceptionHandling? result) && result.State != ExceptionHandlingState.Finally && !result.Labels.Contains(symbol))
result.PendingGotoStatments.Add(Jump(OpCode.ENDTRY_L, target));
else
Jump(OpCode.JMP_L, target);
{
JumpTarget? gotoTarget = null;
List<StatementContext> visitedTry = []; // from shallow to deep
ILabelSymbol label = (ILabelSymbol)model.GetSymbolInfo(syntax.Expression!).Symbol!;
foreach (StatementContext sc in _generalStatementStack)
{// start from the deepest context
// find the final goto target
if (sc.TryGetLabel(label, out gotoTarget))
break;
// stage the try stacks on the way
if (sc.StatementSyntax is TryStatementSyntax)
visitedTry.Add(sc);
}
else
{
var labels = _switchStack.Peek();
JumpTarget target = default!;
if (syntax.CaseOrDefaultKeyword.IsKind(SyntaxKind.DefaultKeyword))
{
target = labels.First(p => p.Item1 is DefaultSwitchLabelSyntax).Item2;
}
else
if (gotoTarget == null)
// goto is not handled
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Cannot find what to continue. " +
$"If not syntax error, this is probably a compiler bug. " +
$"Check whether the compiler is leaving out a push into {nameof(_generalStatementStack)}.");

foreach (StatementContext sc in visitedTry)
// start from the most external try
// internal try should ENDTRY, targeting the correct external goto target
gotoTarget = sc.AddEndTry(gotoTarget);

Jump(OpCode.JMP_L, gotoTarget);
// We could use ENDTRY if current statement calling `goto` is a try statement,
// but this job can be done by the optimizer
// Note that, do not Jump(OpCode.ENDTRY_L, gotoTarget) here,
// because the gotoTarget here is already an ENDTRY_L for current try stack.
}
}

private void ConvertGotoSwitchLabel(SemanticModel model, GotoStatementSyntax syntax)
{
using (InsertSequencePoint(syntax))
{
JumpTarget? gotoTarget = null;
List<StatementContext> visitedTry = []; // from shallow to deep
foreach (StatementContext sc in _generalStatementStack)
{// start from the deepest context
// find the final goto target
if (sc.StatementSyntax is SwitchStatementSyntax switch_)
{
object? value = model.GetConstantValue(syntax.Expression!).Value;
foreach (var (l, t) in labels)
if (syntax.CaseOrDefaultKeyword.IsKind(SyntaxKind.DefaultKeyword))
gotoTarget = sc.SwitchLabels!.First(p => p.Key is DefaultSwitchLabelSyntax).Value;
else
{
if (l is not CaseSwitchLabelSyntax cl) continue;
object? clValue = model.GetConstantValue(cl.Value).Value;
if (value is null)
object? value = model.GetConstantValue(syntax.Expression!).Value;
foreach ((SwitchLabelSyntax label, JumpTarget target) in sc.SwitchLabels!)
{
if (clValue is null)
if (label is not CaseSwitchLabelSyntax cl)
continue;
object? clValue = model.GetConstantValue(cl.Value).Value;
if (value is null)
{
target = t;
break;
if (clValue is null)
{
gotoTarget = target;
break;
}
}
}
else
{
if (value.Equals(clValue))
else if (value.Equals(clValue))
{
target = t;
gotoTarget = target;
break;
}
}
}
break;
}
if (_tryStack.TryPeek(out ExceptionHandling? result) && result.SwitchCount == 0)
Jump(OpCode.ENDTRY_L, target);
else
Jump(OpCode.JMP_L, target);
// stage the try stacks on the way
if (sc.StatementSyntax is TryStatementSyntax)
visitedTry.Add(sc);
}
if (gotoTarget == null)
// goto is not handled
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Cannot find what to goto. " +
$"If not syntax error, this is probably a compiler bug. " +
$"Check whether the compiler is leaving out a push into {nameof(_generalStatementStack)}.");

foreach (StatementContext sc in visitedTry)
// start from the most external try
// internal try should ENDTRY, targeting the correct external goto target
gotoTarget = sc.AddEndTry(gotoTarget);

Jump(OpCode.JMP_L, gotoTarget);
// We could use ENDTRY if current statement calling `goto` is a try statement,
// but this job can be done by the optimizer
// Note that, do not Jump(OpCode.ENDTRY_L, gotoTarget) here,
// because the gotoTarget here is already an ENDTRY_L for current try stack.
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,7 @@ internal partial class MethodConvert
private void ConvertLabeledStatement(SemanticModel model, LabeledStatementSyntax syntax)
{
ILabelSymbol symbol = model.GetDeclaredSymbol(syntax)!;
JumpTarget target = AddLabel(symbol, true);
if (_tryStack.TryPeek(out ExceptionHandling? result))
foreach (Instruction instruction in result.PendingGotoStatments)
if (instruction.Target == target)
instruction.OpCode = OpCode.JMP_L;
JumpTarget target = AddLabel(symbol);
target.Instruction = AddInstruction(OpCode.NOP);
ConvertStatement(model, syntax.Statement);
}
Expand Down
12 changes: 7 additions & 5 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ public bool AddLabel(ILabelSymbol label, JumpTarget target)
GotoLabels ??= [];
return GotoLabels.TryAdd(label, target);
}
public bool AddLabel(SwitchLabelSyntax label, JumpTarget target)
public bool TryGetLabel(ILabelSymbol label, out JumpTarget? target)
{
SwitchLabels ??= [];
return SwitchLabels.TryAdd(label, target);
if (GotoLabels is null)
{
target = null;
return false;
}
return GotoLabels.TryGetValue(label, out target);
}
public bool ContainsLabel(ILabelSymbol label) => GotoLabels is not null && GotoLabels.ContainsKey(label);
public bool ContainsLabel(SwitchLabelSyntax label) => SwitchLabels is not null && SwitchLabels.ContainsKey(label);
}

private readonly Stack<StatementContext> _generalStatementStack = new();
Expand Down
11 changes: 11 additions & 0 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/SwitchStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ private void ConvertSwitchStatement(SemanticModel model, SwitchStatementSyntax s
PushBreakTarget(breakTarget);
StatementContext sc = new(syntax, breakTarget: breakTarget, switchLabels: labels.ToDictionary());
_generalStatementStack.Push(sc);

// handle possible normal labels in all sections of this switch
foreach (SwitchSectionSyntax section in syntax.Sections)
foreach (StatementSyntax label in section.Statements)
if (label is LabeledStatementSyntax l)
{
ILabelSymbol symbol = (ILabelSymbol)model.GetDeclaredSymbol(l)!;
JumpTarget target = AddLabel(symbol);
sc.AddLabel(symbol, target);
}

using (InsertSequencePoint(syntax.Expression))
{
ConvertExpression(model, syntax.Expression);
Expand Down
119 changes: 116 additions & 3 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_GoTo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
using System.Numerics;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services;

namespace Neo.Compiler.CSharp.TestContracts
{
Expand Down Expand Up @@ -28,5 +27,119 @@ public static int testTry()
catch { }
goto sum;
}

public static void testTryComplex(bool exception)
{
Storage.Put("\xff\x00", "\x00");
bool? goto_ = true;
START0:
foreach (object i in Storage.Find("\xff"))
try
{
switch (goto_)
{
case true:
goto case null;
case null:
goto default;
case false:
goto END0;
default:
try { goto START0; } // will execute continue
finally { ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x00"); }
}
}
finally
{
switch (goto_)
{
case true:
goto_ = null;
break;
case null:
goto_ = false;
break;
case false:
goto DEFAULT;
default:
DEFAULT:
Storage.Put("\xff\x00", "\x01");
break;
}
}
END0:
ExecutionEngine.Assert(goto_ == false);
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x01");
foreach (object i in Storage.Find("\xff"))
try
{
throw new System.Exception();
}
catch
{
do { goto END1; }
while (true);
END1:
try { goto END2; } // break foreach; should execute finally
finally { Storage.Put("\xff\x00", "\x00"); }
}
finally
{
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x00");
foreach (int j in new int[] { -1, 0, 1, 2 })
{
switch (j)
{
case 0:
continue;
case 1:
goto case 0;
case 2:
break;
default:
goto case 0;
}
Storage.Put("\xff\x00", "\x02");
}
}
END2:
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x02");
foreach (object i in Storage.Find("\xff"))
try
{
switch (exception)
{
case false:
goto DEFAULT;
case true:
throw new System.Exception();
default:
DEFAULT:
goto ENDSWITCH;
}
ENDSWITCH:;
}
catch
{
try
{
Storage.Put("\xff\x00", "\x03");
throw;
}
catch { goto END3; } // should execute finally
finally
{
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x03");
Storage.Put("\xff\x00", "\x02");
}
}
finally
{
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x02");
Storage.Put("\xff\x00", "\x03");
}
END3:
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x03");
}
}
}
Loading

0 comments on commit 00956b4

Please sign in to comment.