From 9a8ccac7dba231bfe8dd6ea59762b4760c0d0b8f Mon Sep 17 00:00:00 2001 From: lights li Date: Wed, 6 May 2020 10:50:37 +0800 Subject: [PATCH] add trycatch support (#186) * parseTryCatch * fix try endpos problem * add parse code for no catch and no finally * update trycatch * add a unittest * fix compile error on compiler * fix compiler about trycatch * fix opcodes * fix optimizer for try catch * open optimize for trycatch unittest * Fix format * fix UT (#255) * add try-finally ut (#259) * add try-finally ut * format, translate * fix try-finally and add try-catch * refactor code * keep only one try-catch matched * add commont for exception paramcount. * Update src/Neo.Compiler.MSIL/MSIL/Converter.cs * Update src/Neo.Compiler.MSIL/MSIL/Converter.cs * add try-catch failure ut (#261) * fix some skip leaves problem. * Reduce changes Co-authored-by: Shargon Co-authored-by: ShawnYun <42930111+ShawnYun@users.noreply.github.com> Co-authored-by: Luchuan --- src/Neo.Compiler.MSIL/MSIL/Conv_Multi.cs | 21 +- src/Neo.Compiler.MSIL/MSIL/Converter.cs | 104 +++++++++- src/Neo.Compiler.MSIL/MSIL/ILModule.cs | 187 +++++++++++++++++- .../Optimizer/NefInstruction.cs | 26 ++- .../Optimizer/Parser_DeleteDeadCode.cs | 8 +- .../TestClasses/Contract_TryCatch.cs | 167 ++++++++++++++++ .../UnitTest_TryCatch.cs | 126 ++++++++++++ .../Utils/TestEngine.cs | 5 + 8 files changed, 627 insertions(+), 17 deletions(-) create mode 100644 tests/Neo.Compiler.MSIL.UnitTests/TestClasses/Contract_TryCatch.cs create mode 100644 tests/Neo.Compiler.MSIL.UnitTests/UnitTest_TryCatch.cs diff --git a/src/Neo.Compiler.MSIL/MSIL/Conv_Multi.cs b/src/Neo.Compiler.MSIL/MSIL/Conv_Multi.cs index 55210823f..9eb505553 100644 --- a/src/Neo.Compiler.MSIL/MSIL/Conv_Multi.cs +++ b/src/Neo.Compiler.MSIL/MSIL/Conv_Multi.cs @@ -1308,21 +1308,34 @@ private int ConvertNewObj(ILMethod from, OpCode src, NeoMethod to) var _type = (src.tokenUnknown as Mono.Cecil.MethodReference); if (_type.FullName == "System.Void System.Numerics.BigInteger::.ctor(System.Byte[])") { - return 0;//donothing; + return 0; // donothing; } else if (_type.DeclaringType.FullName.Contains("Exception")) { + // NeoVM `catch` instruction need one exception parameter Convert1by1(VM.OpCode.NOP, src, to); + var pcount = _type.Parameters.Count; - for (var i = 0; i < pcount; i++) + //pcount must be 1 + //if more then one, drop them. + //if pcount==0,add one. + if (pcount == 0) // If there is no parameter, insert one pararmeter + { + ConvertPushString("usererror", src, to); + } + else if (pcount > 1) { - Insert1(VM.OpCode.DROP, "", to); + // Keep the first exception parameter + for (var i = 0; i < pcount - 1; i++) + { + Insert1(VM.OpCode.DROP, "", to); + } } return 0; } var type = _type.Resolve(); - //Replace the New Array operation if there is an [OpCode] on the constructor + // Replace the New Array operation if there is an [OpCode] on the constructor foreach (var m in type.DeclaringType.Methods) { if (m.IsConstructor && m.HasCustomAttributes) diff --git a/src/Neo.Compiler.MSIL/MSIL/Converter.cs b/src/Neo.Compiler.MSIL/MSIL/Converter.cs index a9cb600a6..b02f927a5 100644 --- a/src/Neo.Compiler.MSIL/MSIL/Converter.cs +++ b/src/Neo.Compiler.MSIL/MSIL/Converter.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -346,16 +347,48 @@ private void ConvertAddrInMethod(NeoMethod to) { if (c.needfix) { - try + if (c.code == VM.OpCode.TRY_L) { - var _addr = addrconv[c.srcaddr]; - Int32 addroff = (Int32)(_addr - c.addr); - c.bytes = BitConverter.GetBytes(addroff); - c.needfix = false; + var srcCatch = BitConverter.ToInt32(c.bytes, 0); + var srcFinal = BitConverter.ToInt32(c.bytes, 4); + if (srcCatch == -1) + { + var bytesCatch = new byte[] { 0, 0, 0, 0 }; + Array.Copy(bytesCatch, 0, c.bytes, 0, 4); + } + else + { + var _addrCatch = addrconv[srcCatch]; + Int32 addroffCatch = (Int32)(_addrCatch - c.addr); + var bytesCatch = BitConverter.GetBytes(addroffCatch); + Array.Copy(bytesCatch, 0, c.bytes, 0, 4); + } + if (srcFinal == -1) + { + var bytesFinal = new byte[] { 0, 0, 0, 0 }; + Array.Copy(bytesFinal, 0, c.bytes, 4, 4); + } + else + { + var _addrFinal = addrconv[srcFinal]; + int addroffFinal = (int)(_addrFinal - c.addr); + var bytesFinal = BitConverter.GetBytes(addroffFinal); + Array.Copy(bytesFinal, 0, c.bytes, 4, 4); + } } - catch + else { - throw new Exception("cannot convert addr in: " + to.name + "\r\n"); + try + { + var _addr = addrconv[c.srcaddr]; + int addroff = (int)(_addr - c.addr); + c.bytes = BitConverter.GetBytes(addroff); + c.needfix = false; + } + catch + { + throw new Exception("cannot convert addr in: " + to.name + "\r\n"); + } } } } @@ -363,6 +396,34 @@ private void ConvertAddrInMethod(NeoMethod to) private int ConvertCode(ILMethod method, OpCode src, NeoMethod to) { + //add try code + if (method.tryPositions.Contains(src.addr)) + { + foreach (var info in method.tryInfos) + { + if (info.addr_Try_Begin == src.addr) + { + if (info.catch_Infos.Count > 1) + throw new Exception("only support one catch for now."); + + var buf = new byte[8]; + var catchAddr = -1; + if (info.catch_Infos.Count == 1) + { + var first = info.catch_Infos.First().Value; + catchAddr = first.addrBegin; + } + var bytesCatch = BitConverter.GetBytes(catchAddr); + var bytesFinally = BitConverter.GetBytes(info.addr_Finally_Begin); + + Array.Copy(bytesCatch, 0, buf, 0, 4); + Array.Copy(bytesFinally, 0, buf, 4, 4); + var trycode = Convert1by1(VM.OpCode.TRY_L, src, to, buf); + trycode.needfix = true; + break; + } + } + } int skipcount = 0; switch (src.code) { @@ -479,14 +540,39 @@ private int ConvertCode(ILMethod method, OpCode src, NeoMethod to) // Address convert required case CodeEx.Br: case CodeEx.Br_S: - case CodeEx.Leave: - case CodeEx.Leave_S: { var code = Convert1by1(VM.OpCode.JMP_L, src, to, new byte[] { 0, 0, 0, 0 }); code.needfix = true; code.srcaddr = src.tokenAddr_Index; } + break; + case CodeEx.Leave: + case CodeEx.Leave_S: + {//will support try catch + + var tryinfo = method.GetTryInfo(src.addr, out ILMethod.TryCodeType type); + //leaves in try + //leaves in catch + if (type == ILMethod.TryCodeType.Try || type == ILMethod.TryCodeType.Catch) + { + var code = Convert1by1(VM.OpCode.ENDTRY_L, src, to, new byte[] { 0, 0, 0, 0 }); + code.needfix = true; + code.srcaddr = src.tokenAddr_Index; + } + else //or else just jmp + { + var code = Convert1by1(VM.OpCode.JMP_L, src, to, new byte[] { 0, 0, 0, 0 }); + code.needfix = true; + code.srcaddr = src.tokenAddr_Index; + } + } + break; + case CodeEx.Endfinally: + { + //need vm add these opcodes + var code = Convert1by1(VM.OpCode.ENDFINALLY, src, to); + } break; case CodeEx.Switch: { diff --git a/src/Neo.Compiler.MSIL/MSIL/ILModule.cs b/src/Neo.Compiler.MSIL/MSIL/ILModule.cs index 9f73b799f..3acec93e5 100644 --- a/src/Neo.Compiler.MSIL/MSIL/ILModule.cs +++ b/src/Neo.Compiler.MSIL/MSIL/ILModule.cs @@ -198,6 +198,23 @@ public override string ToString() } } + public class ILCatchInfo + { + public int addrBegin; + public int addrEnd; + public string catchType; + } + + public class ILTryInfo + { + public int addr_Try_Begin = -1; + public int addr_Try_End = -1; + public int addr_Try_End_F = -1;//IL try catch,try final is 2 different Block,need to process that. + public Dictionary catch_Infos = new Dictionary(); + public int addr_Finally_Begin = -1; + public int addr_Finally_End = -1; + } + public class ILMethod { public ILType type = null; @@ -208,7 +225,8 @@ public class ILMethod public List body_Variables = new List(); public SortedDictionary body_Codes = new SortedDictionary(); public string fail = null; - + public List tryInfos = new List(); + public List tryPositions = new List(); public ILMethod(ILType type, Mono.Cecil.MethodDefinition method, ILogger logger = null) { this.type = type; @@ -269,8 +287,175 @@ public ILMethod(ILType type, Mono.Cecil.MethodDefinition method, ILogger logger c.InitToken(code.Operand); this.body_Codes.Add(c.addr, c); } + if (method.Body.HasExceptionHandlers) + { + var mapTryInfos = new Dictionary(); + foreach (var e in method.Body.ExceptionHandlers) + { + if (e.HandlerType == Mono.Cecil.Cil.ExceptionHandlerType.Catch) + { + var key = e.TryStart.Offset + "_" + e.TryEnd.Offset; + if (mapTryInfos.ContainsKey(key) == false) + mapTryInfos[key] = new ILTryInfo(); + + var tryinfo = mapTryInfos[key]; + tryinfo.addr_Try_Begin = e.TryStart.Offset; + tryinfo.addr_Try_End = e.TryEnd.Offset; + + var catchtypestr = e.CatchType.FullName; + + tryinfo.catch_Infos[catchtypestr] = new ILCatchInfo() + { + addrBegin = e.HandlerStart.Offset, + addrEnd = e.HandlerEnd.Offset, + catchType = catchtypestr + }; + } + else if (e.HandlerType == Mono.Cecil.Cil.ExceptionHandlerType.Finally) + { + int start = e.TryStart.Offset; + int end = e.TryEnd.Offset; + Mono.Cecil.Cil.ExceptionHandler handler = null; + foreach (var tryinfocatch in method.Body.ExceptionHandlers) + { + if (tryinfocatch.HandlerType == Mono.Cecil.Cil.ExceptionHandlerType.Catch && tryinfocatch.TryStart.Offset == e.TryStart.Offset && tryinfocatch.TryEnd.Offset <= e.TryEnd.Offset) + {//find include catch element. + if (handler == null) + handler = tryinfocatch; + else if (handler.TryEnd.Offset < tryinfocatch.TryEnd.Offset) + handler = tryinfocatch; + } + } + if (handler != null) + { + start = handler.TryStart.Offset; + end = handler.TryEnd.Offset; + } + + var key = start + "_" + end; + + if (mapTryInfos.ContainsKey(key) == false) + mapTryInfos[key] = new ILTryInfo(); + + var tryinfo = mapTryInfos[key]; + tryinfo.addr_Try_Begin = start; + tryinfo.addr_Try_End = end; + tryinfo.addr_Try_End_F = e.TryEnd.Offset; + + tryinfo.addr_Finally_Begin = e.HandlerStart.Offset; + tryinfo.addr_Finally_End = e.HandlerEnd.Offset; + } + else + { + throw new Exception("not support yet." + e.HandlerType); + } + } + this.tryInfos = new List(mapTryInfos.Values); + foreach (var info in this.tryInfos) + { + if (this.tryPositions.Contains(info.addr_Try_Begin) == false) + this.tryPositions.Add(info.addr_Try_Begin); + } + } + } + } + } + + public enum TryCodeType + { + None, + Try, + Try_Final, + Catch, + Final, + } + public ILTryInfo GetTryInfo(int addr, out TryCodeType type) + { + type = TryCodeType.None; + ILTryInfo last = null; + int begin = -1; + int end = -1; + foreach (var info in tryInfos) + { + //check try first + if (info.addr_Try_Begin <= addr && addr < info.addr_Try_End) + { + if (last == null) + { + type = TryCodeType.Try; + last = info; + begin = info.addr_Try_Begin; + end = last.addr_Try_End; + } + else if (begin <= info.addr_Try_Begin && last.addr_Try_End < end) + { + type = TryCodeType.Try; + last = info; + begin = info.addr_Try_Begin; + end = last.addr_Try_End; + } + } + + //then code in final but not in try + if (info.addr_Try_Begin <= addr && addr < info.addr_Try_End_F) + { + if (last == null) + { + type = TryCodeType.Try_Final; + last = info; + begin = info.addr_Try_Begin; + end = last.addr_Try_End_F; + } + else if (begin <= info.addr_Try_Begin && last.addr_Try_End_F < end) + { + type = TryCodeType.Try_Final; + last = info; + begin = info.addr_Try_Begin; + end = last.addr_Try_End_F; + } + } + + //find match finally + if (info.addr_Finally_Begin <= addr && addr < info.addr_Finally_End) + { + if (last == null) + { + type = TryCodeType.Final; + last = info; + begin = info.addr_Finally_Begin; + end = last.addr_Finally_End; + } + else if (begin <= info.addr_Finally_Begin && last.addr_Finally_End < end) + { + type = TryCodeType.Final; + last = info; + begin = info.addr_Finally_Begin; + end = last.addr_Finally_End; + } + } + //find match catch + foreach (var c in info.catch_Infos) + { + if (c.Value.addrBegin <= addr && addr < c.Value.addrEnd) + { + if (last == null) + { + type = TryCodeType.Catch; + last = info; + begin = c.Value.addrBegin; + end = c.Value.addrEnd; + } + else if (begin <= c.Value.addrBegin && c.Value.addrBegin < end) + { + type = TryCodeType.Catch; + last = info; + begin = c.Value.addrBegin; + end = c.Value.addrEnd; + } + } } } + return last; } public int GetLastCodeAddr(int srcaddr) diff --git a/src/Neo.Compiler.MSIL/Optimizer/NefInstruction.cs b/src/Neo.Compiler.MSIL/Optimizer/NefInstruction.cs index f5574d2f2..373bdbb4d 100644 --- a/src/Neo.Compiler.MSIL/Optimizer/NefInstruction.cs +++ b/src/Neo.Compiler.MSIL/Optimizer/NefInstruction.cs @@ -161,6 +161,7 @@ public void SetOpCode(OpCode opcode) case OpCode.JMPEQ_L: case OpCode.JMPGE_L: case OpCode.JMPGT_L: + case OpCode.ENDTRY_L: { Labels = new string[1]; // is an address if (oldlabels != null && oldlabels.Length >= 1) @@ -168,8 +169,17 @@ public void SetOpCode(OpCode opcode) AddressSize = 4; // 32 bit break; } - //TODO case OpCode.TRY_L: 32 + 32 bit - //TODO case OpCode.TRY: 8 + 8 bit + case OpCode.TRY_L: + { + Labels = new string[2]; // is an address + if (oldlabels != null && oldlabels.Length >= 2) + { + Labels[0] = oldlabels[0]; + Labels[1] = oldlabels[1]; + } + AddressSize = 4; // 32 bit + break; + } case OpCode.CALL: case OpCode.JMP: @@ -181,6 +191,7 @@ public void SetOpCode(OpCode opcode) case OpCode.JMPEQ: case OpCode.JMPGE: case OpCode.JMPGT: + case OpCode.ENDTRY: { Labels = new string[1]; // an address if (oldlabels != null && oldlabels.Length >= 1) @@ -188,6 +199,17 @@ public void SetOpCode(OpCode opcode) AddressSize = 1; //8 bit break; } + case OpCode.TRY: + { + Labels = new string[2]; // an address + if (oldlabels != null && oldlabels.Length >= 2) + { + Labels[0] = oldlabels[0]; + Labels[1] = oldlabels[1]; + } + AddressSize = 1; //8 bit + break; + } default: break; } } diff --git a/src/Neo.Compiler.MSIL/Optimizer/Parser_DeleteDeadCode.cs b/src/Neo.Compiler.MSIL/Optimizer/Parser_DeleteDeadCode.cs index b23c34f48..45698f75a 100644 --- a/src/Neo.Compiler.MSIL/Optimizer/Parser_DeleteDeadCode.cs +++ b/src/Neo.Compiler.MSIL/Optimizer/Parser_DeleteDeadCode.cs @@ -48,6 +48,9 @@ private static void Touch(List items, List reachableAddrs, int be reachableAddrs.Add(inst.Offset); } + // Try is not linear. If encounter a try, skip to the catch and finally segments to scan. + // If encounter endtry, will also skip to finally segment to scan + if (inst.AddressCountInData > 0) // The instruction may contain jmp addess { for (var j = 0; j < inst.AddressCountInData; j++) @@ -66,7 +69,10 @@ private static void Touch(List items, List reachableAddrs, int be if (inst.OpCode == OpCode.JMP || inst.OpCode == OpCode.JMP_L || inst.OpCode == OpCode.RET || - inst.OpCode == OpCode.THROW) + inst.OpCode == OpCode.THROW || + inst.OpCode == OpCode.ENDTRY || + inst.OpCode == OpCode.ENDTRY_L || + inst.OpCode == OpCode.ENDFINALLY) { return; } diff --git a/tests/Neo.Compiler.MSIL.UnitTests/TestClasses/Contract_TryCatch.cs b/tests/Neo.Compiler.MSIL.UnitTests/TestClasses/Contract_TryCatch.cs new file mode 100644 index 000000000..c4b9d0e81 --- /dev/null +++ b/tests/Neo.Compiler.MSIL.UnitTests/TestClasses/Contract_TryCatch.cs @@ -0,0 +1,167 @@ +namespace Neo.Compiler.MSIL.TestClasses +{ + class Contract_shift : SmartContract.Framework.SmartContract + { + public static object try01() + { + int v = 0; + try + { + v = 2; + } + catch + { + v = 3; + } + finally + { + v++; + } + return v; + } + + public static object try02() + { + int v = 0; + try + { + v = 2; + throw new System.Exception(); + } + catch + { + v = 3; + } + finally + { + v++; + } + return v; + } + + public static object try03() + { + int v = 0; + try + { + v = 2; + throwcall(); + } + catch + { + v = 3; + } + finally + { + v++; + } + return v; + } + + public static object tryNest() + { + int v = 0; + try + { + try + { + v = 2; + throwcall(); + } + catch + { + v = 3; + throwcall(); + } + finally + { + throwcall(); + v++; + } + } + catch + { + v++; + } + return v; + } + + public static object tryFinally() + { + int v = 0; + try + { + v = 2; + } + finally + { + v++; + } + return v; + } + + public static object tryFinallyAndRethrow() + { + int v = 0; + try + { + v = 2; + throwcall(); + } + finally + { + v++; + } + return v; + } + + public static object tryCatch() + { + int v = 0; + try + { + v = 2; + throwcall(); + } + catch + { + v++; + } + return v; + } + + public static object tryWithTwoFinally() + { + int v = 0; + try + { + try + { + v++; + } + catch + { + v += 2; + } + finally + { + v += 3; + } + } + catch + { + v += 4; + } + finally + { + v += 5; + } + return v; + } + + public static object throwcall() + { + throw new System.Exception(); + } + } +} diff --git a/tests/Neo.Compiler.MSIL.UnitTests/UnitTest_TryCatch.cs b/tests/Neo.Compiler.MSIL.UnitTests/UnitTest_TryCatch.cs new file mode 100644 index 000000000..30c4b06e1 --- /dev/null +++ b/tests/Neo.Compiler.MSIL.UnitTests/UnitTest_TryCatch.cs @@ -0,0 +1,126 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Compiler.MSIL.UnitTests.Utils; +using System; + +namespace Neo.Compiler.MSIL +{ + [TestClass] + public class UnitTest_TryCatch + { + [TestMethod] + public void Test_TryCatch_Succ() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("try01"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 3); + } + + [TestMethod] + public void Test_TryCatch_ThrowByCall() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("try03"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 4); + } + + [TestMethod] + public void Test_TryCatch_Throw() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("try02"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 4); + } + + [TestMethod] + public void Test_TryNest() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("tryNest"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 4); + } + + [TestMethod] + public void Test_TryFinally() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("tryFinally"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 3); + } + + [TestMethod] + public void Test_TryFinallyAndRethrow() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("tryFinallyAndRethrow"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + Assert.AreEqual(testengine.State, VM.VMState.FAULT); + } + + [TestMethod] + public void Test_TryCatch() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("tryCatch"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 3); + } + + [TestMethod] + public void Test_TryWithTwoFinally() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_TryCatch.cs"); + testengine.ScriptEntry.DumpNEF(); + var result = testengine.ExecuteTestCaseStandard("tryWithTwoFinally"); + Console.WriteLine("state=" + testengine.State + " result on stack= " + result.Count); + var value = result.Pop(); + Console.WriteLine("result:" + value.Type + " " + value.ToString()); + var num = value as Neo.VM.Types.Integer; + Console.WriteLine("result = " + num.ToBigInteger().ToString()); + Assert.AreEqual(num.ToBigInteger(), 9); + } + } +} diff --git a/tests/Neo.Compiler.MSIL.UnitTests/Utils/TestEngine.cs b/tests/Neo.Compiler.MSIL.UnitTests/Utils/TestEngine.cs index 5b8cd1f0f..a85f4fe7a 100644 --- a/tests/Neo.Compiler.MSIL.UnitTests/Utils/TestEngine.cs +++ b/tests/Neo.Compiler.MSIL.UnitTests/Utils/TestEngine.cs @@ -11,6 +11,11 @@ namespace Neo.Compiler.MSIL.UnitTests.Utils { public class TestEngine : ApplicationEngine { + protected override bool PreExecuteInstruction() + { + return true; + } + public static InteropDescriptor Native_Deploy; static TestEngine()