Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix if statement sequence points and SequencePointInserter #938

Merged
merged 24 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Neo.Compiler.CSharp/CompilationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ public JObject CreateDebugInformation(string folder = "")
foreach (var m in methodsConverted.Where(p => p.SyntaxNode is not null))
{
List<JString> sequencePoints = new();
foreach (var ins in m.Instructions.Where(i => i.SourceLocation is not null))
foreach (var ins in m.Instructions.Where(i => i.SourceLocation?.SourceTree is not null))
{
var doc = ins.SourceLocation!.SourceTree!.FilePath;
if (!string.IsNullOrEmpty(folder))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ partial class MethodConvert
{
private void ConvertExpression(SemanticModel model, ExpressionSyntax syntax)
{
using var sequence = InsertSequencePoint(syntax);

Optional<object?> constant = model.GetConstantValue(syntax);
if (constant.HasValue)
{
Push(constant.Value);
return;
}

switch (syntax)
{
case AnonymousObjectCreationExpressionSyntax expression:
Expand Down
9 changes: 3 additions & 6 deletions src/Neo.Compiler.CSharp/MethodConvert/SourceConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,16 @@ private void ConvertSource(SemanticModel model)
if (syntax.Body is not null)
ConvertStatement(model, syntax.Body);
else if (syntax.ExpressionBody is not null)
using (InsertSequencePoint(syntax.ExpressionBody.Expression))
ConvertExpression(model, syntax.ExpressionBody.Expression);
ConvertExpression(model, syntax.ExpressionBody.Expression);
else
ConvertNoBody(syntax);
break;
case ArrowExpressionClauseSyntax syntax:
using (InsertSequencePoint(syntax))
shargon marked this conversation as resolved.
Show resolved Hide resolved
ConvertExpression(model, syntax.Expression);
ConvertExpression(model, syntax.Expression);
break;
case BaseMethodDeclarationSyntax syntax:
if (syntax.Body is null)
using (InsertSequencePoint(syntax.ExpressionBody!.Expression))
ConvertExpression(model, syntax.ExpressionBody.Expression);
ConvertExpression(model, syntax.ExpressionBody!.Expression);
else
ConvertStatement(model, syntax.Body);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ private void ConvertDoStatement(SemanticModel model, DoStatementSyntax syntax)
startTarget.Instruction = AddInstruction(OpCode.NOP);
ConvertStatement(model, syntax.Statement);
continueTarget.Instruction = AddInstruction(OpCode.NOP);
using (InsertSequencePoint(syntax.Condition))
ConvertExpression(model, syntax.Condition);
ConvertExpression(model, syntax.Condition);
Jump(OpCode.JMPIF_L, startTarget);
breakTarget.Instruction = AddInstruction(OpCode.NOP);
PopContinueTarget();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ partial class MethodConvert
private void ConvertExpressionStatement(SemanticModel model, ExpressionStatementSyntax syntax)
{
ITypeSymbol type = model.GetTypeInfo(syntax.Expression).Type!;
using (InsertSequencePoint(syntax))
using (InsertSequencePoint(syntax.Expression))
{
ConvertExpression(model, syntax.Expression);
if (type.SpecialType != SpecialType.System_Void)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ private void ConvertForStatement(SemanticModel model, ForStatementSyntax syntax)
}
else
{
using (InsertSequencePoint(syntax.Condition))
ConvertExpression(model, syntax.Condition);
ConvertExpression(model, syntax.Condition);
Jump(OpCode.JMPIF_L, startTarget);
}
breakTarget.Instruction = AddInstruction(OpCode.NOP);
Expand Down
38 changes: 24 additions & 14 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/IfStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,31 @@ partial class MethodConvert
private void ConvertIfStatement(SemanticModel model, IfStatementSyntax syntax)
{
JumpTarget elseTarget = new();
using (InsertSequencePoint(syntax.Condition))
ConvertExpression(model, syntax.Condition);
Jump(OpCode.JMPIFNOT_L, elseTarget);
ConvertStatement(model, syntax.Statement);
if (syntax.Else is null)
{
elseTarget.Instruction = AddInstruction(OpCode.NOP);
}
else

using (InsertSequencePoint(syntax))
{
JumpTarget endTarget = new();
Jump(OpCode.JMP_L, endTarget);
elseTarget.Instruction = AddInstruction(OpCode.NOP);
ConvertStatement(model, syntax.Else.Statement);
endTarget.Instruction = AddInstruction(OpCode.NOP);
ConvertExpression(model, syntax.Condition);

Jump(OpCode.JMPIFNOT_L, elseTarget);
ConvertStatement(model, syntax.Statement);

if (syntax.Else is null)
{
elseTarget.Instruction = AddInstruction(OpCode.NOP);
}
else
{
JumpTarget endTarget = new();
Jump(OpCode.JMP_L, endTarget);

using (InsertSequencePoint(syntax.Else.Statement))
{
elseTarget.Instruction = AddInstruction(OpCode.NOP);
ConvertStatement(model, syntax.Else.Statement);
}

endTarget.Instruction = AddInstruction(OpCode.NOP);
}
}
}
}
Expand Down
19 changes: 15 additions & 4 deletions src/Neo.Compiler.CSharp/SequencePointInserter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,30 @@ namespace Neo.Compiler
class SequencePointInserter : IDisposable
{
private readonly IReadOnlyList<Instruction> instructions;
private readonly SyntaxNodeOrToken syntax;
private readonly Location? location;
private readonly int position;

public SequencePointInserter(IReadOnlyList<Instruction> instructions, SyntaxNodeOrToken syntax)
{
this.instructions = instructions;
this.syntax = syntax;
this.location = syntax.GetLocation();
this.position = instructions.Count;

// No location must be removed

if (this.location?.SourceTree is null)
this.location = null;
}

void IDisposable.Dispose()
{
if (position < instructions.Count)
instructions[position].SourceLocation = syntax.GetLocation();
for (int x = position; x < instructions.Count; x++)
{
if (instructions[x].SourceLocation is null)
{
instructions[x].SourceLocation = location;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ public ContractCoverageWriter(CoveredContract contract, NeoDebugInfo debugInfo)

public void WritePackage(XmlWriter writer)
{
var (lineCount, hitCount) = GetLineRate(Contract, DebugInfo.Methods.SelectMany(m => m.SequencePoints));
var allMethods = DebugInfo.Methods.SelectMany(m => m.SequencePoints).ToArray();
var (lineCount, hitCount) = GetLineRate(Contract, allMethods);
var lineRate = CoverageBase.CalculateHitRate(lineCount, hitCount);
var (branchCount, branchHit) = GetBranchRate(Contract, DebugInfo.Methods);
var (branchCount, branchHit) = GetBranchRate(Contract, allMethods);
var branchRate = CoverageBase.CalculateHitRate(branchCount, branchHit);

writer.WriteStartElement("package");
Expand Down Expand Up @@ -65,9 +66,10 @@ public void WritePackage(XmlWriter writer)

internal void WriteClass(XmlWriter writer, string name, string filename, IEnumerable<NeoDebugInfo.Method> methods)
{
var (lineCount, hitCount) = GetLineRate(Contract, methods.SelectMany(m => m.SequencePoints));
var allMethods = methods.SelectMany(m => m.SequencePoints).ToArray();
var (lineCount, hitCount) = GetLineRate(Contract, allMethods);
var lineRate = CoverageBase.CalculateHitRate(lineCount, hitCount);
var (branchCount, branchHit) = GetBranchRate(Contract, methods);
var (branchCount, branchHit) = GetBranchRate(Contract, allMethods);
var branchRate = CoverageBase.CalculateHitRate(branchCount, branchHit);

writer.WriteStartElement("class");
Expand Down Expand Up @@ -103,7 +105,7 @@ internal void WriteMethod(XmlWriter writer, NeoDebugInfo.Method method)
var signature = string.Join(", ", method.Parameters.Select(p => p.Type));
var (lineCount, hitCount) = GetLineRate(Contract, method.SequencePoints);
var lineRate = CoverageBase.CalculateHitRate(lineCount, hitCount);
var (branchCount, branchHit) = GetBranchRate(Contract, method);
var (branchCount, branchHit) = GetBranchRate(Contract, method.SequencePoints);
var branchRate = CoverageBase.CalculateHitRate(branchCount, branchHit);

writer.WriteStartElement("method");
Expand Down Expand Up @@ -143,16 +145,15 @@ internal void WriteLine(XmlWriter writer, NeoDebugInfo.Method method, NeoDebugIn
writer.WriteAttributeString("condition-coverage", $"{branchRate * 100:N}% ({branchHit}/{branchCount})");
writer.WriteStartElement("conditions");

foreach (var (address, opCode) in GetBranchInstructions(Contract, method, sp))
foreach ((var address, var branchI) in GetBranchInstructions(Contract, method, sp))
{
var (condBranchCount, condContinueCount) = Contract.TryGetBranch(address, out var brach) ?
(brach.Count, brach.Hits) : (0, 0);
var (condBranchCount, condContinueCount) = (branchI.Count, branchI.Hits);
var coverage = condBranchCount == 0 ? 0m : 1m;
coverage += condContinueCount == 0 ? 0m : 1m;

writer.WriteStartElement("condition");
writer.WriteAttributeString("number", $"{address}");
writer.WriteAttributeString("type", $"{opCode}");
writer.WriteAttributeString("type", $"{branchI.Offset}"); // TODO: Opcode?
writer.WriteAttributeString("coverage", $"{coverage / 2m * 100m}%");
writer.WriteEndElement();
}
Expand Down
41 changes: 15 additions & 26 deletions src/Neo.SmartContract.Testing/Coverage/Formats/CoberturaFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ internal void WriteReport(XmlWriter writer, IReadOnlyList<(CoveredContract Contr

foreach (var entry in coverage)
{
var (lineCount, hitCount) = GetLineRate(entry.Contract, entry.DebugInfo.Methods.SelectMany(m => m.SequencePoints));
var allPoints = entry.DebugInfo.Methods.SelectMany(m => m.SequencePoints).ToArray();

var (lineCount, hitCount) = GetLineRate(entry.Contract, allPoints);
linesValid += lineCount;
linesCovered += hitCount;

var (branchCount, branchHit) = GetBranchRate(entry.Contract, entry.DebugInfo.Methods);
var (branchCount, branchHit) = GetBranchRate(entry.Contract, allPoints);
branchesValid += branchCount;
branchesCovered += branchHit;
}
Expand Down Expand Up @@ -84,24 +86,11 @@ internal void WriteReport(XmlWriter writer, IReadOnlyList<(CoveredContract Contr
writer.WriteEndElement();
}

private static (int branchCount, int branchHit) GetBranchRate(CoveredContract contract, IEnumerable<NeoDebugInfo.Method> methods)
private static (int branchCount, int branchHit) GetBranchRate(CoveredContract contract, IEnumerable<NeoDebugInfo.SequencePoint> sequencePoints)
{
int branchCount = 0, branchHit = 0;
foreach (var method in methods)
{
var rate = GetBranchRate(contract, method);

branchCount += rate.branchCount;
branchHit += rate.branchHit;
}
return (branchCount, branchHit);
}

private static (int branchCount, int branchHit) GetBranchRate(CoveredContract contract, NeoDebugInfo.Method method)
{
int branchCount = 0, branchHit = 0;

foreach (var sp in method.SequencePoints)
foreach (var sp in sequencePoints)
{
if (contract.TryGetBranch(sp.Address, out var branch))
{
Expand All @@ -113,14 +102,14 @@ private static (int branchCount, int branchHit) GetBranchRate(CoveredContract co
return (branchCount, branchHit);
}

private static (int lineCount, int hitCount) GetLineRate(CoveredContract contract, IEnumerable<NeoDebugInfo.SequencePoint> lines)
private static (int lineCount, int hitCount) GetLineRate(CoveredContract contract, IEnumerable<NeoDebugInfo.SequencePoint> sequencePoints)
{
int lineCount = 0, hitCount = 0;

foreach (var line in lines)
foreach (var sp in sequencePoints)
{
lineCount++;
if (contract.TryGetLine(line.Address, out var hit) && hit.Hits > 0)
if (contract.TryGetLine(sp.Address, out var hit) && hit.Hits > 0)
{
hitCount++;
}
Expand All @@ -129,7 +118,7 @@ private static (int lineCount, int hitCount) GetLineRate(CoveredContract contrac
return (lineCount, hitCount);
}

public static IEnumerable<(int address, OpCode opCode)> GetBranchInstructions(
public static IEnumerable<(int address, CoverageBranch branch)> GetBranchInstructions(
CoveredContract contract, NeoDebugInfo.Method method, NeoDebugInfo.SequencePoint sequencePoint
)
{
Expand All @@ -139,10 +128,11 @@ private static (int lineCount, int hitCount) GetLineRate(CoveredContract contrac

foreach (var line in lines)
{
if (line.Offset > last) break;

if (contract.TryGetBranch(address, out var branch)) // IsBranchInstruction
{
//yield return (address, ins.OpCode);
yield return (address, OpCode.NOP);
yield return (address, branch);
}
}
}
Expand All @@ -158,12 +148,11 @@ public static int GetLineLastAddress(CoverageHit[] lines, NeoDebugInfo.Method me
else
{
var nextSPAddress = method.SequencePoints[index + 1].Address;
var point = method.SequencePoints[index];
var address = point.Address;
var address = method.SequencePoints[index].Address;

foreach (var line in lines)
{
if (line.Offset >= nextSPAddress)
if (line.Offset > nextSPAddress)
{
return address;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ public virtual void TestTransfer()
Assert.AreEqual(initialSupply, Contract.TotalSupply);
AssertTransferEvent(Alice.Account, Bob.Account, 3);

// Check 0 tokens transfer

Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, 0));
AssertTransferEvent(Alice.Account, Bob.Account, 0);

// Invoke invalid transfers

Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, -1)));
Expand Down
3 changes: 2 additions & 1 deletion tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DebugInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public void Test_DebugInfo()
Assert.IsInstanceOfType(debugInfo["methods"], typeof(JArray));
Assert.AreEqual(1, (debugInfo["methods"] as JArray).Count);
Assert.AreEqual("Neo.Compiler.CSharp.UnitTests.TestClasses.Contract_Event,Main", (debugInfo["methods"] as JArray)[0]["name"].AsString());
Assert.AreEqual("3[0]17:13-17:30;7[0]18:13-18:33;13[0]19:9-19:10", string.Join(';', ((debugInfo["methods"] as JArray)[0]["sequence-points"] as JArray).Select(u => u.AsString())));
Assert.AreEqual("3[0]17:28-17:29;4[0]17:13-17:29;5[0]17:13-17:29;6[0]17:13-17:29;7[0]18:28-18:32;8[0]18:28-18:32;10[0]18:13-18:32;11[0]18:13-18:32;12[0]18:13-18:32;13[0]19:9-19:10",
string.Join(';', ((debugInfo["methods"] as JArray)[0]["sequence-points"] as JArray).Select(u => u.AsString())));
Assert.IsTrue(debugInfo.ContainsProperty("events"));
Assert.IsInstanceOfType(debugInfo["events"], typeof(JArray));
Assert.AreEqual(1, (debugInfo["events"] as JArray).Count);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Services;
using Neo.SmartContract.Framework.Native;

namespace Neo.SmartContract.Framework.UnitTests.TestClasses
{
public class Contract_SequencePointInserter : SmartContract
{
public static int test(int a)
{
if (a == 1) return 23;
return 45;
}
}
}
Loading
Loading