diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs index f67cc9caec..043b095ef0 100644 --- a/src/Neo.VM/ExecutionEngine.cs +++ b/src/Neo.VM/ExecutionEngine.cs @@ -12,11 +12,7 @@ using Neo.VM.Types; using System; using System.Collections.Generic; -using System.Linq; -using System.Numerics; using System.Runtime.CompilerServices; -using Buffer = Neo.VM.Types.Buffer; -using VMArray = Neo.VM.Types.Array; namespace Neo.VM { @@ -26,7 +22,9 @@ namespace Neo.VM public class ExecutionEngine : IDisposable { private VMState state = VMState.BREAK; - private bool isJumping = false; + internal bool isJumping = false; + + public JumpTable JumpTable { get; } /// /// Restrictions on the VM. @@ -61,7 +59,7 @@ public class ExecutionEngine : IDisposable /// /// The VM object representing the uncaught exception. /// - public StackItem? UncaughtException { get; private set; } + public StackItem? UncaughtException { get; internal set; } /// /// The current state of the VM. @@ -85,45 +83,24 @@ protected internal set /// /// Initializes a new instance of the class. /// - public ExecutionEngine() : this(new ReferenceCounter(), ExecutionEngineLimits.Default) + public ExecutionEngine(JumpTable? jumpTable = null) : this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) { } /// /// Initializes a new instance of the class with the specified and . /// + /// The jump table to be used. /// The reference counter to be used. /// Restrictions on the VM. - protected ExecutionEngine(ReferenceCounter referenceCounter, ExecutionEngineLimits limits) + protected ExecutionEngine(JumpTable? jumpTable, ReferenceCounter referenceCounter, ExecutionEngineLimits limits) { + this.JumpTable = jumpTable ?? new JumpTable(); this.Limits = limits; this.ReferenceCounter = referenceCounter; this.ResultStack = new EvaluationStack(referenceCounter); } - /// - /// Called when a context is unloaded. - /// - /// The context being unloaded. - protected virtual void ContextUnloaded(ExecutionContext context) - { - if (InvocationStack.Count == 0) - { - CurrentContext = null; - EntryContext = null; - } - else - { - CurrentContext = InvocationStack.Peek(); - } - if (context.StaticFields != null && context.StaticFields != CurrentContext?.StaticFields) - { - context.StaticFields.ClearReferences(); - } - context.LocalVariables?.ClearReferences(); - context.Arguments?.ClearReferences(); - } - public virtual void Dispose() { InvocationStack.Clear(); @@ -142,1311 +119,6 @@ public virtual VMState Execute() return State; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExecuteCall(int position) - { - LoadContext(CurrentContext!.Clone(position)); - } - - private void ExecuteInstruction(Instruction instruction) - { - switch (instruction.OpCode) - { - //Push - case OpCode.PUSHINT8: - case OpCode.PUSHINT16: - case OpCode.PUSHINT32: - case OpCode.PUSHINT64: - case OpCode.PUSHINT128: - case OpCode.PUSHINT256: - { - Push(new BigInteger(instruction.Operand.Span)); - break; - } - case OpCode.PUSHT: - { - Push(StackItem.True); - break; - } - case OpCode.PUSHF: - { - Push(StackItem.False); - break; - } - case OpCode.PUSHA: - { - int position = checked(CurrentContext!.InstructionPointer + instruction.TokenI32); - if (position < 0 || position > CurrentContext.Script.Length) - throw new InvalidOperationException($"Bad pointer address: {position}"); - Push(new Pointer(CurrentContext.Script, position)); - break; - } - case OpCode.PUSHNULL: - { - Push(StackItem.Null); - break; - } - case OpCode.PUSHDATA1: - case OpCode.PUSHDATA2: - case OpCode.PUSHDATA4: - { - Limits.AssertMaxItemSize(instruction.Operand.Length); - Push(instruction.Operand); - break; - } - case OpCode.PUSHM1: - case OpCode.PUSH0: - case OpCode.PUSH1: - case OpCode.PUSH2: - case OpCode.PUSH3: - case OpCode.PUSH4: - case OpCode.PUSH5: - case OpCode.PUSH6: - case OpCode.PUSH7: - case OpCode.PUSH8: - case OpCode.PUSH9: - case OpCode.PUSH10: - case OpCode.PUSH11: - case OpCode.PUSH12: - case OpCode.PUSH13: - case OpCode.PUSH14: - case OpCode.PUSH15: - case OpCode.PUSH16: - { - Push((int)instruction.OpCode - (int)OpCode.PUSH0); - break; - } - - // Control - case OpCode.NOP: break; - case OpCode.JMP: - { - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMP_L: - { - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPIF: - { - if (Pop().GetBoolean()) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPIF_L: - { - if (Pop().GetBoolean()) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPIFNOT: - { - if (!Pop().GetBoolean()) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPIFNOT_L: - { - if (!Pop().GetBoolean()) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPEQ: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 == x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPEQ_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 == x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPNE: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 != x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPNE_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 != x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPGT: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 > x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPGT_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 > x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPGE: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 >= x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPGE_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 >= x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPLT: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 < x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPLT_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 < x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.JMPLE: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 <= x2) - ExecuteJumpOffset(instruction.TokenI8); - break; - } - case OpCode.JMPLE_L: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - if (x1 <= x2) - ExecuteJumpOffset(instruction.TokenI32); - break; - } - case OpCode.CALL: - { - ExecuteCall(checked(CurrentContext!.InstructionPointer + instruction.TokenI8)); - break; - } - case OpCode.CALL_L: - { - ExecuteCall(checked(CurrentContext!.InstructionPointer + instruction.TokenI32)); - break; - } - case OpCode.CALLA: - { - var x = Pop(); - if (x.Script != CurrentContext!.Script) - throw new InvalidOperationException("Pointers can't be shared between scripts"); - ExecuteCall(x.Position); - break; - } - case OpCode.CALLT: - { - LoadToken(instruction.TokenU16); - break; - } - case OpCode.ABORT: - { - throw new Exception($"{OpCode.ABORT} is executed."); - } - case OpCode.ASSERT: - { - var x = Pop().GetBoolean(); - if (!x) - throw new Exception($"{OpCode.ASSERT} is executed with false result."); - break; - } - case OpCode.THROW: - { - ExecuteThrow(Pop()); - break; - } - case OpCode.TRY: - { - int catchOffset = instruction.TokenI8; - int finallyOffset = instruction.TokenI8_1; - ExecuteTry(catchOffset, finallyOffset); - break; - } - case OpCode.TRY_L: - { - int catchOffset = instruction.TokenI32; - int finallyOffset = instruction.TokenI32_1; - ExecuteTry(catchOffset, finallyOffset); - break; - } - case OpCode.ENDTRY: - { - int endOffset = instruction.TokenI8; - ExecuteEndTry(endOffset); - break; - } - case OpCode.ENDTRY_L: - { - int endOffset = instruction.TokenI32; - ExecuteEndTry(endOffset); - break; - } - case OpCode.ENDFINALLY: - { - if (CurrentContext!.TryStack is null) - throw new InvalidOperationException($"The corresponding TRY block cannot be found."); - if (!CurrentContext.TryStack.TryPop(out ExceptionHandlingContext? currentTry)) - throw new InvalidOperationException($"The corresponding TRY block cannot be found."); - - if (UncaughtException is null) - CurrentContext.InstructionPointer = currentTry.EndPointer; - else - HandleException(); - - isJumping = true; - break; - } - case OpCode.RET: - { - ExecutionContext context_pop = InvocationStack.Pop(); - EvaluationStack stack_eval = InvocationStack.Count == 0 ? ResultStack : InvocationStack.Peek().EvaluationStack; - if (context_pop.EvaluationStack != stack_eval) - { - if (context_pop.RVCount >= 0 && context_pop.EvaluationStack.Count != context_pop.RVCount) - throw new InvalidOperationException("RVCount doesn't match with EvaluationStack"); - context_pop.EvaluationStack.CopyTo(stack_eval); - } - if (InvocationStack.Count == 0) - State = VMState.HALT; - ContextUnloaded(context_pop); - isJumping = true; - break; - } - case OpCode.SYSCALL: - { - OnSysCall(instruction.TokenU32); - break; - } - - // Stack ops - case OpCode.DEPTH: - { - Push(CurrentContext!.EvaluationStack.Count); - break; - } - case OpCode.DROP: - { - Pop(); - break; - } - case OpCode.NIP: - { - CurrentContext!.EvaluationStack.Remove(1); - break; - } - case OpCode.XDROP: - { - int n = (int)Pop().GetInteger(); - if (n < 0) - throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); - CurrentContext!.EvaluationStack.Remove(n); - break; - } - case OpCode.CLEAR: - { - CurrentContext!.EvaluationStack.Clear(); - break; - } - case OpCode.DUP: - { - Push(Peek()); - break; - } - case OpCode.OVER: - { - Push(Peek(1)); - break; - } - case OpCode.PICK: - { - int n = (int)Pop().GetInteger(); - if (n < 0) - throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); - Push(Peek(n)); - break; - } - case OpCode.TUCK: - { - CurrentContext!.EvaluationStack.Insert(2, Peek()); - break; - } - case OpCode.SWAP: - { - var x = CurrentContext!.EvaluationStack.Remove(1); - Push(x); - break; - } - case OpCode.ROT: - { - var x = CurrentContext!.EvaluationStack.Remove(2); - Push(x); - break; - } - case OpCode.ROLL: - { - int n = (int)Pop().GetInteger(); - if (n < 0) - throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); - if (n == 0) break; - var x = CurrentContext!.EvaluationStack.Remove(n); - Push(x); - break; - } - case OpCode.REVERSE3: - { - CurrentContext!.EvaluationStack.Reverse(3); - break; - } - case OpCode.REVERSE4: - { - CurrentContext!.EvaluationStack.Reverse(4); - break; - } - case OpCode.REVERSEN: - { - int n = (int)Pop().GetInteger(); - CurrentContext!.EvaluationStack.Reverse(n); - break; - } - - //Slot - case OpCode.INITSSLOT: - { - if (CurrentContext!.StaticFields != null) - throw new InvalidOperationException($"{instruction.OpCode} cannot be executed twice."); - if (instruction.TokenU8 == 0) - throw new InvalidOperationException($"The operand {instruction.TokenU8} is invalid for OpCode.{instruction.OpCode}."); - CurrentContext.StaticFields = new Slot(instruction.TokenU8, ReferenceCounter); - break; - } - case OpCode.INITSLOT: - { - if (CurrentContext!.LocalVariables != null || CurrentContext.Arguments != null) - throw new InvalidOperationException($"{instruction.OpCode} cannot be executed twice."); - if (instruction.TokenU16 == 0) - throw new InvalidOperationException($"The operand {instruction.TokenU16} is invalid for OpCode.{instruction.OpCode}."); - if (instruction.TokenU8 > 0) - { - CurrentContext.LocalVariables = new Slot(instruction.TokenU8, ReferenceCounter); - } - if (instruction.TokenU8_1 > 0) - { - StackItem[] items = new StackItem[instruction.TokenU8_1]; - for (int i = 0; i < instruction.TokenU8_1; i++) - { - items[i] = Pop(); - } - CurrentContext.Arguments = new Slot(items, ReferenceCounter); - } - break; - } - case OpCode.LDSFLD0: - case OpCode.LDSFLD1: - case OpCode.LDSFLD2: - case OpCode.LDSFLD3: - case OpCode.LDSFLD4: - case OpCode.LDSFLD5: - case OpCode.LDSFLD6: - { - ExecuteLoadFromSlot(CurrentContext!.StaticFields, instruction.OpCode - OpCode.LDSFLD0); - break; - } - case OpCode.LDSFLD: - { - ExecuteLoadFromSlot(CurrentContext!.StaticFields, instruction.TokenU8); - break; - } - case OpCode.STSFLD0: - case OpCode.STSFLD1: - case OpCode.STSFLD2: - case OpCode.STSFLD3: - case OpCode.STSFLD4: - case OpCode.STSFLD5: - case OpCode.STSFLD6: - { - ExecuteStoreToSlot(CurrentContext!.StaticFields, instruction.OpCode - OpCode.STSFLD0); - break; - } - case OpCode.STSFLD: - { - ExecuteStoreToSlot(CurrentContext!.StaticFields, instruction.TokenU8); - break; - } - case OpCode.LDLOC0: - case OpCode.LDLOC1: - case OpCode.LDLOC2: - case OpCode.LDLOC3: - case OpCode.LDLOC4: - case OpCode.LDLOC5: - case OpCode.LDLOC6: - { - ExecuteLoadFromSlot(CurrentContext!.LocalVariables, instruction.OpCode - OpCode.LDLOC0); - break; - } - case OpCode.LDLOC: - { - ExecuteLoadFromSlot(CurrentContext!.LocalVariables, instruction.TokenU8); - break; - } - case OpCode.STLOC0: - case OpCode.STLOC1: - case OpCode.STLOC2: - case OpCode.STLOC3: - case OpCode.STLOC4: - case OpCode.STLOC5: - case OpCode.STLOC6: - { - ExecuteStoreToSlot(CurrentContext!.LocalVariables, instruction.OpCode - OpCode.STLOC0); - break; - } - case OpCode.STLOC: - { - ExecuteStoreToSlot(CurrentContext!.LocalVariables, instruction.TokenU8); - break; - } - case OpCode.LDARG0: - case OpCode.LDARG1: - case OpCode.LDARG2: - case OpCode.LDARG3: - case OpCode.LDARG4: - case OpCode.LDARG5: - case OpCode.LDARG6: - { - ExecuteLoadFromSlot(CurrentContext!.Arguments, instruction.OpCode - OpCode.LDARG0); - break; - } - case OpCode.LDARG: - { - ExecuteLoadFromSlot(CurrentContext!.Arguments, instruction.TokenU8); - break; - } - case OpCode.STARG0: - case OpCode.STARG1: - case OpCode.STARG2: - case OpCode.STARG3: - case OpCode.STARG4: - case OpCode.STARG5: - case OpCode.STARG6: - { - ExecuteStoreToSlot(CurrentContext!.Arguments, instruction.OpCode - OpCode.STARG0); - break; - } - case OpCode.STARG: - { - ExecuteStoreToSlot(CurrentContext!.Arguments, instruction.TokenU8); - break; - } - - // Splice - case OpCode.NEWBUFFER: - { - int length = (int)Pop().GetInteger(); - Limits.AssertMaxItemSize(length); - Push(new Buffer(length)); - break; - } - case OpCode.MEMCPY: - { - int count = (int)Pop().GetInteger(); - if (count < 0) - throw new InvalidOperationException($"The value {count} is out of range."); - int si = (int)Pop().GetInteger(); - if (si < 0) - throw new InvalidOperationException($"The value {si} is out of range."); - ReadOnlySpan src = Pop().GetSpan(); - if (checked(si + count) > src.Length) - throw new InvalidOperationException($"The value {count} is out of range."); - int di = (int)Pop().GetInteger(); - if (di < 0) - throw new InvalidOperationException($"The value {di} is out of range."); - Buffer dst = Pop(); - if (checked(di + count) > dst.Size) - throw new InvalidOperationException($"The value {count} is out of range."); - src.Slice(si, count).CopyTo(dst.InnerBuffer.Span[di..]); - break; - } - case OpCode.CAT: - { - var x2 = Pop().GetSpan(); - var x1 = Pop().GetSpan(); - int length = x1.Length + x2.Length; - Limits.AssertMaxItemSize(length); - Buffer result = new(length, false); - x1.CopyTo(result.InnerBuffer.Span); - x2.CopyTo(result.InnerBuffer.Span[x1.Length..]); - Push(result); - break; - } - case OpCode.SUBSTR: - { - int count = (int)Pop().GetInteger(); - if (count < 0) - throw new InvalidOperationException($"The value {count} is out of range."); - int index = (int)Pop().GetInteger(); - if (index < 0) - throw new InvalidOperationException($"The value {index} is out of range."); - var x = Pop().GetSpan(); - if (index + count > x.Length) - throw new InvalidOperationException($"The value {count} is out of range."); - Buffer result = new(count, false); - x.Slice(index, count).CopyTo(result.InnerBuffer.Span); - Push(result); - break; - } - case OpCode.LEFT: - { - int count = (int)Pop().GetInteger(); - if (count < 0) - throw new InvalidOperationException($"The value {count} is out of range."); - var x = Pop().GetSpan(); - if (count > x.Length) - throw new InvalidOperationException($"The value {count} is out of range."); - Buffer result = new(count, false); - x[..count].CopyTo(result.InnerBuffer.Span); - Push(result); - break; - } - case OpCode.RIGHT: - { - int count = (int)Pop().GetInteger(); - if (count < 0) - throw new InvalidOperationException($"The value {count} is out of range."); - var x = Pop().GetSpan(); - if (count > x.Length) - throw new InvalidOperationException($"The value {count} is out of range."); - Buffer result = new(count, false); - x[^count..^0].CopyTo(result.InnerBuffer.Span); - Push(result); - break; - } - - // Bitwise logic - case OpCode.INVERT: - { - var x = Pop().GetInteger(); - Push(~x); - break; - } - case OpCode.AND: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 & x2); - break; - } - case OpCode.OR: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 | x2); - break; - } - case OpCode.XOR: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 ^ x2); - break; - } - case OpCode.EQUAL: - { - StackItem x2 = Pop(); - StackItem x1 = Pop(); - Push(x1.Equals(x2, Limits)); - break; - } - case OpCode.NOTEQUAL: - { - StackItem x2 = Pop(); - StackItem x1 = Pop(); - Push(!x1.Equals(x2, Limits)); - break; - } - - // Numeric - case OpCode.SIGN: - { - var x = Pop().GetInteger(); - Push(x.Sign); - break; - } - case OpCode.ABS: - { - var x = Pop().GetInteger(); - Push(BigInteger.Abs(x)); - break; - } - case OpCode.NEGATE: - { - var x = Pop().GetInteger(); - Push(-x); - break; - } - case OpCode.INC: - { - var x = Pop().GetInteger(); - Push(x + 1); - break; - } - case OpCode.DEC: - { - var x = Pop().GetInteger(); - Push(x - 1); - break; - } - case OpCode.ADD: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 + x2); - break; - } - case OpCode.SUB: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 - x2); - break; - } - case OpCode.MUL: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 * x2); - break; - } - case OpCode.DIV: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 / x2); - break; - } - case OpCode.MOD: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 % x2); - break; - } - case OpCode.POW: - { - var exponent = (int)Pop().GetInteger(); - Limits.AssertShift(exponent); - var value = Pop().GetInteger(); - Push(BigInteger.Pow(value, exponent)); - break; - } - case OpCode.SQRT: - { - Push(Pop().GetInteger().Sqrt()); - break; - } - case OpCode.MODMUL: - { - var modulus = Pop().GetInteger(); - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 * x2 % modulus); - break; - } - case OpCode.MODPOW: - { - var modulus = Pop().GetInteger(); - var exponent = Pop().GetInteger(); - var value = Pop().GetInteger(); - var result = exponent == -1 - ? value.ModInverse(modulus) - : BigInteger.ModPow(value, exponent, modulus); - Push(result); - break; - } - case OpCode.SHL: - { - int shift = (int)Pop().GetInteger(); - Limits.AssertShift(shift); - if (shift == 0) break; - var x = Pop().GetInteger(); - Push(x << shift); - break; - } - case OpCode.SHR: - { - int shift = (int)Pop().GetInteger(); - Limits.AssertShift(shift); - if (shift == 0) break; - var x = Pop().GetInteger(); - Push(x >> shift); - break; - } - case OpCode.NOT: - { - var x = Pop().GetBoolean(); - Push(!x); - break; - } - case OpCode.BOOLAND: - { - var x2 = Pop().GetBoolean(); - var x1 = Pop().GetBoolean(); - Push(x1 && x2); - break; - } - case OpCode.BOOLOR: - { - var x2 = Pop().GetBoolean(); - var x1 = Pop().GetBoolean(); - Push(x1 || x2); - break; - } - case OpCode.NZ: - { - var x = Pop().GetInteger(); - Push(!x.IsZero); - break; - } - case OpCode.NUMEQUAL: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 == x2); - break; - } - case OpCode.NUMNOTEQUAL: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(x1 != x2); - break; - } - case OpCode.LT: - { - var x2 = Pop(); - var x1 = Pop(); - if (x1.IsNull || x2.IsNull) - Push(false); - else - Push(x1.GetInteger() < x2.GetInteger()); - break; - } - case OpCode.LE: - { - var x2 = Pop(); - var x1 = Pop(); - if (x1.IsNull || x2.IsNull) - Push(false); - else - Push(x1.GetInteger() <= x2.GetInteger()); - break; - } - case OpCode.GT: - { - var x2 = Pop(); - var x1 = Pop(); - if (x1.IsNull || x2.IsNull) - Push(false); - else - Push(x1.GetInteger() > x2.GetInteger()); - break; - } - case OpCode.GE: - { - var x2 = Pop(); - var x1 = Pop(); - if (x1.IsNull || x2.IsNull) - Push(false); - else - Push(x1.GetInteger() >= x2.GetInteger()); - break; - } - case OpCode.MIN: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(BigInteger.Min(x1, x2)); - break; - } - case OpCode.MAX: - { - var x2 = Pop().GetInteger(); - var x1 = Pop().GetInteger(); - Push(BigInteger.Max(x1, x2)); - break; - } - case OpCode.WITHIN: - { - BigInteger b = Pop().GetInteger(); - BigInteger a = Pop().GetInteger(); - var x = Pop().GetInteger(); - Push(a <= x && x < b); - break; - } - - // Compound-type - case OpCode.PACKMAP: - { - int size = (int)Pop().GetInteger(); - if (size < 0 || size * 2 > CurrentContext!.EvaluationStack.Count) - throw new InvalidOperationException($"The value {size} is out of range."); - Map map = new(ReferenceCounter); - for (int i = 0; i < size; i++) - { - PrimitiveType key = Pop(); - StackItem value = Pop(); - map[key] = value; - } - Push(map); - break; - } - case OpCode.PACKSTRUCT: - { - int size = (int)Pop().GetInteger(); - if (size < 0 || size > CurrentContext!.EvaluationStack.Count) - throw new InvalidOperationException($"The value {size} is out of range."); - Struct @struct = new(ReferenceCounter); - for (int i = 0; i < size; i++) - { - StackItem item = Pop(); - @struct.Add(item); - } - Push(@struct); - break; - } - case OpCode.PACK: - { - int size = (int)Pop().GetInteger(); - if (size < 0 || size > CurrentContext!.EvaluationStack.Count) - throw new InvalidOperationException($"The value {size} is out of range."); - VMArray array = new(ReferenceCounter); - for (int i = 0; i < size; i++) - { - StackItem item = Pop(); - array.Add(item); - } - Push(array); - break; - } - case OpCode.UNPACK: - { - CompoundType compound = Pop(); - switch (compound) - { - case Map map: - foreach (var (key, value) in map.Reverse()) - { - Push(value); - Push(key); - } - break; - case VMArray array: - for (int i = array.Count - 1; i >= 0; i--) - { - Push(array[i]); - } - break; - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {compound.Type}"); - } - Push(compound.Count); - break; - } - case OpCode.NEWARRAY0: - { - Push(new VMArray(ReferenceCounter)); - break; - } - case OpCode.NEWARRAY: - case OpCode.NEWARRAY_T: - { - int n = (int)Pop().GetInteger(); - if (n < 0 || n > Limits.MaxStackSize) - throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - StackItem item; - if (instruction.OpCode == OpCode.NEWARRAY_T) - { - StackItemType type = (StackItemType)instruction.TokenU8; - if (!Enum.IsDefined(typeof(StackItemType), type)) - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {instruction.TokenU8}"); - item = instruction.TokenU8 switch - { - (byte)StackItemType.Boolean => StackItem.False, - (byte)StackItemType.Integer => Integer.Zero, - (byte)StackItemType.ByteString => ByteString.Empty, - _ => StackItem.Null - }; - } - else - { - item = StackItem.Null; - } - Push(new VMArray(ReferenceCounter, Enumerable.Repeat(item, n))); - break; - } - case OpCode.NEWSTRUCT0: - { - Push(new Struct(ReferenceCounter)); - break; - } - case OpCode.NEWSTRUCT: - { - int n = (int)Pop().GetInteger(); - if (n < 0 || n > Limits.MaxStackSize) - throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - Struct result = new(ReferenceCounter); - for (var i = 0; i < n; i++) - result.Add(StackItem.Null); - Push(result); - break; - } - case OpCode.NEWMAP: - { - Push(new Map(ReferenceCounter)); - break; - } - case OpCode.SIZE: - { - var x = Pop(); - switch (x) - { - case CompoundType compound: - Push(compound.Count); - break; - case PrimitiveType primitive: - Push(primitive.Size); - break; - case Buffer buffer: - Push(buffer.Size); - break; - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.HASKEY: - { - PrimitiveType key = Pop(); - var x = Pop(); - switch (x) - { - case VMArray array: - { - int index = (int)key.GetInteger(); - if (index < 0) - throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); - Push(index < array.Count); - break; - } - case Map map: - { - Push(map.ContainsKey(key)); - break; - } - case Buffer buffer: - { - int index = (int)key.GetInteger(); - if (index < 0) - throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); - Push(index < buffer.Size); - break; - } - case ByteString array: - { - int index = (int)key.GetInteger(); - if (index < 0) - throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); - Push(index < array.Size); - break; - } - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.KEYS: - { - Map map = Pop(); - Push(new VMArray(ReferenceCounter, map.Keys)); - break; - } - case OpCode.VALUES: - { - var x = Pop(); - IEnumerable values = x switch - { - VMArray array => array, - Map map => map.Values, - _ => throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"), - }; - VMArray newArray = new(ReferenceCounter); - foreach (StackItem item in values) - if (item is Struct s) - newArray.Add(s.Clone(Limits)); - else - newArray.Add(item); - Push(newArray); - break; - } - case OpCode.PICKITEM: - { - PrimitiveType key = Pop(); - var x = Pop(); - switch (x) - { - case VMArray array: - { - int index = (int)key.GetInteger(); - if (index < 0 || index >= array.Count) - throw new CatchableException($"The value {index} is out of range."); - Push(array[index]); - break; - } - case Map map: - { - if (!map.TryGetValue(key, out StackItem? value)) - throw new CatchableException($"Key not found in {nameof(Map)}"); - Push(value); - break; - } - case PrimitiveType primitive: - { - ReadOnlySpan byteArray = primitive.GetSpan(); - int index = (int)key.GetInteger(); - if (index < 0 || index >= byteArray.Length) - throw new CatchableException($"The value {index} is out of range."); - Push((BigInteger)byteArray[index]); - break; - } - case Buffer buffer: - { - int index = (int)key.GetInteger(); - if (index < 0 || index >= buffer.Size) - throw new CatchableException($"The value {index} is out of range."); - Push((BigInteger)buffer.InnerBuffer.Span[index]); - break; - } - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.APPEND: - { - StackItem newItem = Pop(); - VMArray array = Pop(); - if (newItem is Struct s) newItem = s.Clone(Limits); - array.Add(newItem); - break; - } - case OpCode.SETITEM: - { - StackItem value = Pop(); - if (value is Struct s) value = s.Clone(Limits); - PrimitiveType key = Pop(); - var x = Pop(); - switch (x) - { - case VMArray array: - { - int index = (int)key.GetInteger(); - if (index < 0 || index >= array.Count) - throw new CatchableException($"The value {index} is out of range."); - array[index] = value; - break; - } - case Map map: - { - map[key] = value; - break; - } - case Buffer buffer: - { - int index = (int)key.GetInteger(); - if (index < 0 || index >= buffer.Size) - throw new CatchableException($"The value {index} is out of range."); - if (value is not PrimitiveType p) - throw new InvalidOperationException($"Value must be a primitive type in {instruction.OpCode}"); - int b = (int)p.GetInteger(); - if (b < sbyte.MinValue || b > byte.MaxValue) - throw new InvalidOperationException($"Overflow in {instruction.OpCode}, {b} is not a byte type."); - buffer.InnerBuffer.Span[index] = (byte)b; - break; - } - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.REVERSEITEMS: - { - var x = Pop(); - switch (x) - { - case VMArray array: - array.Reverse(); - break; - case Buffer buffer: - buffer.InnerBuffer.Span.Reverse(); - break; - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.REMOVE: - { - PrimitiveType key = Pop(); - var x = Pop(); - switch (x) - { - case VMArray array: - int index = (int)key.GetInteger(); - if (index < 0 || index >= array.Count) - throw new InvalidOperationException($"The value {index} is out of range."); - array.RemoveAt(index); - break; - case Map map: - map.Remove(key); - break; - default: - throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); - } - break; - } - case OpCode.CLEARITEMS: - { - CompoundType x = Pop(); - x.Clear(); - break; - } - case OpCode.POPITEM: - { - VMArray x = Pop(); - int index = x.Count - 1; - Push(x[index]); - x.RemoveAt(index); - break; - } - - //Types - case OpCode.ISNULL: - { - var x = Pop(); - Push(x.IsNull); - break; - } - case OpCode.ISTYPE: - { - var x = Pop(); - StackItemType type = (StackItemType)instruction.TokenU8; - if (type == StackItemType.Any || !Enum.IsDefined(typeof(StackItemType), type)) - throw new InvalidOperationException($"Invalid type: {type}"); - Push(x.Type == type); - break; - } - case OpCode.CONVERT: - { - var x = Pop(); - Push(x.ConvertTo((StackItemType)instruction.TokenU8)); - break; - } - case OpCode.ABORTMSG: - { - var msg = Pop().GetString(); - throw new Exception($"{OpCode.ABORTMSG} is executed. Reason: {msg}"); - } - case OpCode.ASSERTMSG: - { - var msg = Pop().GetString(); - var x = Pop().GetBoolean(); - if (!x) - throw new Exception($"{OpCode.ASSERTMSG} is executed with false result. Reason: {msg}"); - break; - } - default: throw new InvalidOperationException($"Opcode {instruction.OpCode} is undefined."); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExecuteEndTry(int endOffset) - { - if (CurrentContext!.TryStack is null) - throw new InvalidOperationException($"The corresponding TRY block cannot be found."); - if (!CurrentContext.TryStack.TryPeek(out ExceptionHandlingContext? currentTry)) - throw new InvalidOperationException($"The corresponding TRY block cannot be found."); - if (currentTry.State == ExceptionHandlingState.Finally) - throw new InvalidOperationException($"The opcode {OpCode.ENDTRY} can't be executed in a FINALLY block."); - - int endPointer = checked(CurrentContext.InstructionPointer + endOffset); - if (currentTry.HasFinally) - { - currentTry.State = ExceptionHandlingState.Finally; - currentTry.EndPointer = endPointer; - CurrentContext.InstructionPointer = currentTry.FinallyPointer; - } - else - { - CurrentContext.TryStack.Pop(); - CurrentContext.InstructionPointer = endPointer; - } - isJumping = true; - } - - /// - /// Jump to the specified position. - /// - /// The position to jump to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ExecuteJump(int position) - { - if (position < 0 || position >= CurrentContext!.Script.Length) - throw new ArgumentOutOfRangeException($"Jump out of range for position: {position}"); - CurrentContext.InstructionPointer = position; - isJumping = true; - } - - /// - /// Jump to the specified offset from the current position. - /// - /// The offset from the current position to jump to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ExecuteJumpOffset(int offset) - { - ExecuteJump(checked(CurrentContext!.InstructionPointer + offset)); - } - private void ExecuteLoadFromSlot(Slot? slot, int index) { if (slot is null) @@ -1474,11 +146,11 @@ protected internal void ExecuteNext() PreExecuteInstruction(instruction); try { - ExecuteInstruction(instruction); + JumpTable[instruction.OpCode](this, instruction); } catch (CatchableException ex) when (Limits.CatchEngineExceptions) { - ExecuteThrow(ex.Message); + JumpTable.ExecuteThrow(this, ex.Message); } PostExecuteInstruction(instruction); if (!isJumping) context.MoveNext(); @@ -1500,75 +172,11 @@ private void ExecuteStoreToSlot(Slot? slot, int index) slot[index] = Pop(); } - /// - /// Throws a specified exception in the VM. - /// - /// The exception to be thrown. - protected void ExecuteThrow(StackItem ex) - { - UncaughtException = ex; - HandleException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExecuteTry(int catchOffset, int finallyOffset) - { - if (catchOffset == 0 && finallyOffset == 0) - throw new InvalidOperationException($"catchOffset and finallyOffset can't be 0 in a TRY block"); - if (CurrentContext!.TryStack is null) - CurrentContext.TryStack = new Stack(); - else if (CurrentContext.TryStack.Count >= Limits.MaxTryNestingDepth) - throw new InvalidOperationException("MaxTryNestingDepth exceed."); - int catchPointer = catchOffset == 0 ? -1 : checked(CurrentContext.InstructionPointer + catchOffset); - int finallyPointer = finallyOffset == 0 ? -1 : checked(CurrentContext.InstructionPointer + finallyOffset); - CurrentContext.TryStack.Push(new ExceptionHandlingContext(catchPointer, finallyPointer)); - } - - private void HandleException() - { - int pop = 0; - foreach (var executionContext in InvocationStack) - { - if (executionContext.TryStack != null) - { - while (executionContext.TryStack.TryPeek(out var tryContext)) - { - if (tryContext.State == ExceptionHandlingState.Finally || (tryContext.State == ExceptionHandlingState.Catch && !tryContext.HasFinally)) - { - executionContext.TryStack.Pop(); - continue; - } - for (int i = 0; i < pop; i++) - { - ContextUnloaded(InvocationStack.Pop()); - } - if (tryContext.State == ExceptionHandlingState.Try && tryContext.HasCatch) - { - tryContext.State = ExceptionHandlingState.Catch; - Push(UncaughtException!); - executionContext.InstructionPointer = tryContext.CatchPointer; - UncaughtException = null; - } - else - { - tryContext.State = ExceptionHandlingState.Finally; - executionContext.InstructionPointer = tryContext.FinallyPointer; - } - isJumping = true; - return; - } - } - ++pop; - } - - throw new VMUnhandledException(UncaughtException!); - } - /// /// Loads the specified context into the invocation stack. /// /// The context to load. - protected virtual void LoadContext(ExecutionContext context) + public virtual void LoadContext(ExecutionContext context) { if (InvocationStack.Count >= Limits.MaxInvocationStackSize) throw new InvalidOperationException($"MaxInvocationStackSize exceed: {InvocationStack.Count}"); @@ -1577,6 +185,29 @@ protected virtual void LoadContext(ExecutionContext context) CurrentContext = context; } + /// + /// Called when a context is unloaded. + /// + /// The context being unloaded. + public virtual void UnloadedContext(ExecutionContext context) + { + if (InvocationStack.Count == 0) + { + CurrentContext = null; + EntryContext = null; + } + else + { + CurrentContext = InvocationStack.Peek(); + } + if (context.StaticFields != null && context.StaticFields != CurrentContext?.StaticFields) + { + context.StaticFields.ClearReferences(); + } + context.LocalVariables?.ClearReferences(); + context.Arguments?.ClearReferences(); + } + /// /// Create a new context with the specified script without loading. /// @@ -1607,17 +238,6 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP return context; } - /// - /// When overridden in a derived class, loads the specified method token. - /// Called when is executed. - /// - /// The method token to be loaded. - /// The created context. - protected virtual ExecutionContext LoadToken(ushort token) - { - throw new InvalidOperationException($"Token not found: {token}"); - } - /// /// Called when an exception that cannot be caught by the VM is thrown. /// @@ -1634,16 +254,6 @@ protected virtual void OnStateChanged() { } - /// - /// When overridden in a derived class, invokes the specified system call. - /// Called when is executed. - /// - /// The system call to be invoked. - protected virtual void OnSysCall(uint method) - { - throw new InvalidOperationException($"Syscall not found: {method}"); - } - /// /// Returns the item at the specified index from the top of the current stack without removing it. /// diff --git a/src/Neo.VM/JumpTable.Control.cs b/src/Neo.VM/JumpTable.Control.cs new file mode 100644 index 0000000000..7d1b9f9542 --- /dev/null +++ b/src/Neo.VM/JumpTable.Control.cs @@ -0,0 +1,396 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Control.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public partial class JumpTable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NOP(ExecutionEngine engine, Instruction instruction) + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMP(ExecutionEngine engine, Instruction instruction) + { + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMP_L(ExecutionEngine engine, Instruction instruction) + { + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPIF(ExecutionEngine engine, Instruction instruction) + { + if (engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPIF_L(ExecutionEngine engine, Instruction instruction) + { + if (engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPIFNOT(ExecutionEngine engine, Instruction instruction) + { + if (!engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPIFNOT_L(ExecutionEngine engine, Instruction instruction) + { + if (!engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPEQ(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 == x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPEQ_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 == x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPNE(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 != x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPNE_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 != x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPGT(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 > x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPGT_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 > x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPGE(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 >= x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPGE_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 >= x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPLT(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 < x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPLT_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 < x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPLE(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 <= x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JMPLE_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 <= x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CALL(ExecutionEngine engine, Instruction instruction) + { + ExecuteCall(engine, checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CALL_L(ExecutionEngine engine, Instruction instruction) + { + ExecuteCall(engine, checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI32)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CALLA(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + if (x.Script != engine.CurrentContext!.Script) + throw new InvalidOperationException("Pointers can't be shared between scripts"); + ExecuteCall(engine, x.Position); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CALLT(ExecutionEngine engine, Instruction instruction) + { + throw new InvalidOperationException($"Token not found: {instruction.TokenU16}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ABORT(ExecutionEngine engine, Instruction instruction) + { + throw new Exception($"{OpCode.ABORT} is executed."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ASSERT(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetBoolean(); + if (!x) + throw new Exception($"{OpCode.ASSERT} is executed with false result."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void THROW(ExecutionEngine engine, Instruction instruction) + { + ExecuteThrow(engine, engine.Pop()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void TRY(ExecutionEngine engine, Instruction instruction) + { + int catchOffset = instruction.TokenI8; + int finallyOffset = instruction.TokenI8_1; + ExecuteTry(engine, catchOffset, finallyOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void TRY_L(ExecutionEngine engine, Instruction instruction) + { + int catchOffset = instruction.TokenI32; + int finallyOffset = instruction.TokenI32_1; + ExecuteTry(engine, catchOffset, finallyOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ENDTRY(ExecutionEngine engine, Instruction instruction) + { + int endOffset = instruction.TokenI8; + ExecuteEndTry(engine, endOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ENDTRY_L(ExecutionEngine engine, Instruction instruction) + { + int endOffset = instruction.TokenI32; + ExecuteEndTry(engine, endOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ENDFINALLY(ExecutionEngine engine, Instruction instruction) + { + if (engine.CurrentContext!.TryStack is null) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (!engine.CurrentContext.TryStack.TryPop(out ExceptionHandlingContext? currentTry)) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + + if (engine.UncaughtException is null) + engine.CurrentContext.InstructionPointer = currentTry.EndPointer; + else + ExecuteThrow(engine, engine.UncaughtException); + + engine.isJumping = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void RET(ExecutionEngine engine, Instruction instruction) + { + ExecutionContext context_pop = engine.InvocationStack.Pop(); + EvaluationStack stack_eval = engine.InvocationStack.Count == 0 ? engine.ResultStack : engine.InvocationStack.Peek().EvaluationStack; + if (context_pop.EvaluationStack != stack_eval) + { + if (context_pop.RVCount >= 0 && context_pop.EvaluationStack.Count != context_pop.RVCount) + throw new InvalidOperationException("RVCount doesn't match with EvaluationStack"); + context_pop.EvaluationStack.CopyTo(stack_eval); + } + if (engine.InvocationStack.Count == 0) + engine.State = VMState.HALT; + engine.UnloadedContext(context_pop); + engine.isJumping = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SYSCALL(ExecutionEngine engine, Instruction instruction) + { + throw new InvalidOperationException($"Syscall not found: {instruction.TokenU32}"); + } + + #region Execute methods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteCall(ExecutionEngine engine, int position) + { + engine.LoadContext(engine.CurrentContext!.Clone(position)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteEndTry(ExecutionEngine engine, int endOffset) + { + if (engine.CurrentContext!.TryStack is null) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (!engine.CurrentContext.TryStack.TryPeek(out ExceptionHandlingContext? currentTry)) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (currentTry.State == ExceptionHandlingState.Finally) + throw new InvalidOperationException($"The opcode {OpCode.ENDTRY} can't be executed in a FINALLY block."); + + int endPointer = checked(engine.CurrentContext.InstructionPointer + endOffset); + if (currentTry.HasFinally) + { + currentTry.State = ExceptionHandlingState.Finally; + currentTry.EndPointer = endPointer; + engine.CurrentContext.InstructionPointer = currentTry.FinallyPointer; + } + else + { + engine.CurrentContext.TryStack.Pop(); + engine.CurrentContext.InstructionPointer = endPointer; + } + engine.isJumping = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteJump(ExecutionEngine engine, int position) + { + if (position < 0 || position >= engine.CurrentContext!.Script.Length) + throw new ArgumentOutOfRangeException($"Jump out of range for position: {position}"); + engine.CurrentContext.InstructionPointer = position; + engine.isJumping = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteJumpOffset(ExecutionEngine engine, int offset) + { + ExecuteJump(engine, checked(engine.CurrentContext!.InstructionPointer + offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteTry(ExecutionEngine engine, int catchOffset, int finallyOffset) + { + if (catchOffset == 0 && finallyOffset == 0) + throw new InvalidOperationException($"catchOffset and finallyOffset can't be 0 in a TRY block"); + if (engine.CurrentContext!.TryStack is null) + engine.CurrentContext.TryStack = new Stack(); + else if (engine.CurrentContext.TryStack.Count >= engine.Limits.MaxTryNestingDepth) + throw new InvalidOperationException("MaxTryNestingDepth exceed."); + int catchPointer = catchOffset == 0 ? -1 : checked(engine.CurrentContext.InstructionPointer + catchOffset); + int finallyPointer = finallyOffset == 0 ? -1 : checked(engine.CurrentContext.InstructionPointer + finallyOffset); + engine.CurrentContext.TryStack.Push(new ExceptionHandlingContext(catchPointer, finallyPointer)); + } + + public void ExecuteThrow(ExecutionEngine engine, StackItem? ex) + { + engine.UncaughtException = ex; + + int pop = 0; + foreach (var executionContext in engine.InvocationStack) + { + if (executionContext.TryStack != null) + { + while (executionContext.TryStack.TryPeek(out var tryContext)) + { + if (tryContext.State == ExceptionHandlingState.Finally || (tryContext.State == ExceptionHandlingState.Catch && !tryContext.HasFinally)) + { + executionContext.TryStack.Pop(); + continue; + } + for (int i = 0; i < pop; i++) + { + engine.UnloadedContext(engine.InvocationStack.Pop()); + } + if (tryContext.State == ExceptionHandlingState.Try && tryContext.HasCatch) + { + tryContext.State = ExceptionHandlingState.Catch; + engine.Push(engine.UncaughtException!); + executionContext.InstructionPointer = tryContext.CatchPointer; + engine.UncaughtException = null; + } + else + { + tryContext.State = ExceptionHandlingState.Finally; + executionContext.InstructionPointer = tryContext.FinallyPointer; + } + engine.isJumping = true; + return; + } + } + ++pop; + } + + throw new VMUnhandledException(engine.UncaughtException!); + } + + #endregion + } +} diff --git a/src/Neo.VM/JumpTable.Push.cs b/src/Neo.VM/JumpTable.Push.cs new file mode 100644 index 0000000000..66b9f64c51 --- /dev/null +++ b/src/Neo.VM/JumpTable.Push.cs @@ -0,0 +1,213 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Push.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public partial class JumpTable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT8(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT16(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT32(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT64(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT128(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHINT256(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHT(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.True); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHF(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.False); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHA(ExecutionEngine engine, Instruction instruction) + { + var position = checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI32); + if (position < 0 || position > engine.CurrentContext.Script.Length) + throw new InvalidOperationException($"Bad pointer address(Instruction instruction) {position}"); + engine.Push(new Pointer(engine.CurrentContext.Script, position)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHNULL(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.Null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHDATA1(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHDATA2(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHDATA4(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSHM1(ExecutionEngine engine, Instruction instruction) + { + engine.Push(-1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH0(ExecutionEngine engine, Instruction instruction) + { + engine.Push(0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH1(ExecutionEngine engine, Instruction instruction) + { + engine.Push(1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH2(ExecutionEngine engine, Instruction instruction) + { + engine.Push(2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH3(ExecutionEngine engine, Instruction instruction) + { + engine.Push(3); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH4(ExecutionEngine engine, Instruction instruction) + { + engine.Push(4); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH5(ExecutionEngine engine, Instruction instruction) + { + engine.Push(5); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH6(ExecutionEngine engine, Instruction instruction) + { + engine.Push(6); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH7(ExecutionEngine engine, Instruction instruction) + { + engine.Push(7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH8(ExecutionEngine engine, Instruction instruction) + { + engine.Push(8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH9(ExecutionEngine engine, Instruction instruction) + { + engine.Push(9); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH10(ExecutionEngine engine, Instruction instruction) + { + engine.Push(10); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH11(ExecutionEngine engine, Instruction instruction) + { + engine.Push(11); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH12(ExecutionEngine engine, Instruction instruction) + { + engine.Push(12); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH13(ExecutionEngine engine, Instruction instruction) + { + engine.Push(13); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH14(ExecutionEngine engine, Instruction instruction) + { + engine.Push(14); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH15(ExecutionEngine engine, Instruction instruction) + { + engine.Push(15); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PUSH16(ExecutionEngine engine, Instruction instruction) + { + engine.Push(16); + } + } +} diff --git a/src/Neo.VM/JumpTable.Splice.cs b/src/Neo.VM/JumpTable.Splice.cs new file mode 100644 index 0000000000..f5aec89352 --- /dev/null +++ b/src/Neo.VM/JumpTable.Splice.cs @@ -0,0 +1,106 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Splice.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public partial class JumpTable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NEWBUFFER(ExecutionEngine engine, Instruction instruction) + { + int length = (int)engine.Pop().GetInteger(); + engine.Limits.AssertMaxItemSize(length); + engine.Push(new Types.Buffer(length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void MEMCPY(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + int si = (int)engine.Pop().GetInteger(); + if (si < 0) + throw new InvalidOperationException($"The value {si} is out of range."); + ReadOnlySpan src = engine.Pop().GetSpan(); + if (checked(si + count) > src.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + int di = (int)engine.Pop().GetInteger(); + if (di < 0) + throw new InvalidOperationException($"The value {di} is out of range."); + Types.Buffer dst = engine.Pop(); + if (checked(di + count) > dst.Size) + throw new InvalidOperationException($"The value {count} is out of range."); + src.Slice(si, count).CopyTo(dst.InnerBuffer.Span[di..]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CAT(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetSpan(); + var x1 = engine.Pop().GetSpan(); + int length = x1.Length + x2.Length; + engine.Limits.AssertMaxItemSize(length); + Types.Buffer result = new(length, false); + x1.CopyTo(result.InnerBuffer.Span); + x2.CopyTo(result.InnerBuffer.Span[x1.Length..]); + engine.Push(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SUBSTR(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + int index = (int)engine.Pop().GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The value {index} is out of range."); + var x = engine.Pop().GetSpan(); + if (index + count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x.Slice(index, count).CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LEFT(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + var x = engine.Pop().GetSpan(); + if (count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x[..count].CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void RIGHT(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + var x = engine.Pop().GetSpan(); + if (count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x[^count..^0].CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + } +} diff --git a/src/Neo.VM/JumpTable.Types.cs b/src/Neo.VM/JumpTable.Types.cs new file mode 100644 index 0000000000..46fd50749d --- /dev/null +++ b/src/Neo.VM/JumpTable.Types.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Types.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public partial class JumpTable + { + public virtual void ISNULL(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + engine.Push(x.IsNull); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ISTYPE(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + var type = (StackItemType)instruction.TokenU8; + if (type == StackItemType.Any || !Enum.IsDefined(typeof(StackItemType), type)) + throw new InvalidOperationException($"Invalid type: {type}"); + engine.Push(x.Type == type); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CONVERT(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + engine.Push(x.ConvertTo((StackItemType)instruction.TokenU8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ABORTMSG(ExecutionEngine engine, Instruction instruction) + { + var msg = engine.Pop().GetString(); + throw new Exception($"{OpCode.ABORTMSG} is executed. Reason: {msg}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ASSERTMSG(ExecutionEngine engine, Instruction instruction) + { + var msg = engine.Pop().GetString(); + var x = engine.Pop().GetBoolean(); + if (!x) + throw new Exception($"{OpCode.ASSERTMSG} is executed with false result. Reason: {msg}"); + } + } +} diff --git a/src/Neo.VM/JumpTable.cs b/src/Neo.VM/JumpTable.cs new file mode 100644 index 0000000000..e78ce5d606 --- /dev/null +++ b/src/Neo.VM/JumpTable.cs @@ -0,0 +1,369 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.VM +{ + public partial class JumpTable + { + public delegate void DelAction(ExecutionEngine engine, Instruction instruction); + protected readonly DelAction[] _table = new DelAction[byte.MaxValue]; + + public DelAction this[OpCode opCode] + { + get + { + return _table[(byte)opCode]; + } + set + { + _table[(byte)opCode] = value; + } + } + + public JumpTable() + { + // Fill defined + + foreach (var mi in GetType().GetMethods()) + { + if (Enum.TryParse(mi.Name, false, out var opCode)) + { + if (_table[(byte)opCode] is not null) + { + throw new InvalidOperationException($"Opcode {opCode} is already defined."); + } + + _table[(byte)opCode] = (DelAction)mi.CreateDelegate(typeof(DelAction), this); + } + } + + // Fill with undefined + + for (int x = 0; x < _table.Length; x++) + { + if (_table[x] is not null) continue; + + _table[x] = (engine, instruction) => + { + throw new InvalidOperationException($"Opcode {instruction.OpCode} is undefined."); + }; + } + + /* TODO: + + switch (instruction.OpCode) + { + // Stack ops + case OpCode.DEPTH: + Depth(instruction); + break; + case OpCode.DROP: + Drop(instruction); + break; + case OpCode.NIP: + Nip(instruction); + break; + case OpCode.XDROP: + XDrop(instruction); + break; + case OpCode.CLEAR: + Clear(instruction); + break; + case OpCode.DUP: + Dup(instruction); + break; + case OpCode.OVER: + Over(instruction); + break; + case OpCode.PICK: + Pick(instruction); + break; + case OpCode.TUCK: + Tuck(instruction); + break; + case OpCode.SWAP: + Swap(instruction); + break; + case OpCode.ROT: + Rot(instruction); + break; + case OpCode.ROLL: + Roll(instruction); + break; + case OpCode.REVERSE3: + Reverse3(instruction); + break; + case OpCode.REVERSE4: + Reverse4(instruction); + break; + case OpCode.REVERSEN: + ReverseN(instruction); + break; + + //Slot + case OpCode.INITSSLOT: + InitSSlot(instruction); + break; + case OpCode.INITSLOT: + InitSlot(instruction); + break; + case OpCode.LDSFLD0: + case OpCode.LDSFLD1: + case OpCode.LDSFLD2: + case OpCode.LDSFLD3: + case OpCode.LDSFLD4: + case OpCode.LDSFLD5: + case OpCode.LDSFLD6: + LdSFldM(instruction); + break; + case OpCode.LDSFLD: + LdSFld(instruction); + break; + case OpCode.STSFLD0: + case OpCode.STSFLD1: + case OpCode.STSFLD2: + case OpCode.STSFLD3: + case OpCode.STSFLD4: + case OpCode.STSFLD5: + case OpCode.STSFLD6: + StSFldM(instruction); + break; + case OpCode.STSFLD: + StSFld(instruction); + break; + case OpCode.LDLOC0: + case OpCode.LDLOC1: + case OpCode.LDLOC2: + case OpCode.LDLOC3: + case OpCode.LDLOC4: + case OpCode.LDLOC5: + case OpCode.LDLOC6: + LdLocM(instruction); + break; + case OpCode.LDLOC: + LdLoc(instruction); + break; + case OpCode.STLOC0: + case OpCode.STLOC1: + case OpCode.STLOC2: + case OpCode.STLOC3: + case OpCode.STLOC4: + case OpCode.STLOC5: + case OpCode.STLOC6: + StLocM(instruction); + break; + case OpCode.STLOC: + StLoc(instruction); + break; + case OpCode.LDARG0: + case OpCode.LDARG1: + case OpCode.LDARG2: + case OpCode.LDARG3: + case OpCode.LDARG4: + case OpCode.LDARG5: + case OpCode.LDARG6: + LdArgM(instruction); + break; + case OpCode.LDARG: + LdArg(instruction); + break; + case OpCode.STARG0: + case OpCode.STARG1: + case OpCode.STARG2: + case OpCode.STARG3: + case OpCode.STARG4: + case OpCode.STARG5: + case OpCode.STARG6: + StArgM(instruction); + break; + case OpCode.STARG: + StArg(instruction); + break; + + // Bitwise logic + case OpCode.INVERT: + Invert(instruction); + break; + case OpCode.AND: + And(instruction); + break; + case OpCode.OR: + Or(instruction); + break; + case OpCode.XOR: + Xor(instruction); + break; + case OpCode.EQUAL: + Equal(instruction); + break; + case OpCode.NOTEQUAL: + NotEqual(instruction); + break; + + // Numeric + case OpCode.SIGN: + Sign(instruction); + break; + case OpCode.ABS: + Abs(instruction); + break; + case OpCode.NEGATE: + Negate(instruction); + break; + case OpCode.INC: + Inc(instruction); + break; + case OpCode.DEC: + Dec(instruction); + break; + case OpCode.ADD: + Add(instruction); + break; + case OpCode.SUB: + Sub(instruction); + break; + case OpCode.MUL: + Mul(instruction); + break; + case OpCode.DIV: + Div(instruction); + break; + case OpCode.MOD: + Mod(instruction); + break; + case OpCode.POW: + Pow(instruction); + break; + case OpCode.SQRT: + Sqrt(instruction); + break; + case OpCode.MODMUL: + ModMul(instruction); + break; + case OpCode.MODPOW: + ModPow(instruction); + break; + case OpCode.SHL: + Shl(instruction); + break; + case OpCode.SHR: + Shr(instruction); + break; + case OpCode.NOT: + Not(instruction); + break; + case OpCode.BOOLAND: + BoolAnd(instruction); + break; + case OpCode.BOOLOR: + BoolOr(instruction); + break; + case OpCode.NZ: + Nz(instruction); + break; + case OpCode.NUMEQUAL: + NumEqual(instruction); + break; + case OpCode.NUMNOTEQUAL: + NumNotEqual(instruction); + break; + case OpCode.LT: + Lt(instruction); + break; + case OpCode.LE: + Le(instruction); + break; + case OpCode.GT: + Gt(instruction); + break; + case OpCode.GE: + Ge(instruction); + break; + case OpCode.MIN: + Min(instruction); + break; + case OpCode.MAX: + Max(instruction); + break; + case OpCode.WITHIN: + Within(instruction); + break; + + // Compound-type + case OpCode.PACKMAP: + PackMap(instruction); + break; + case OpCode.PACKSTRUCT: + PackStruct(instruction); + break; + case OpCode.PACK: + Pack(instruction); + break; + case OpCode.UNPACK: + Unpack(instruction); + break; + case OpCode.NEWARRAY0: + NewArray0(instruction); + break; + case OpCode.NEWARRAY: + case OpCode.NEWARRAY_T: + NewArray_T(instruction); + break; + case OpCode.NEWSTRUCT0: + NewStruct0(instruction); + break; + case OpCode.NEWSTRUCT: + NewStruct(instruction); + break; + case OpCode.NEWMAP: + NewMap(instruction); + break; + case OpCode.SIZE: + Size(instruction); + break; + case OpCode.HASKEY: + HasKey(instruction); + break; + case OpCode.KEYS: + Keys(instruction); + break; + case OpCode.VALUES: + Values(instruction); + break; + case OpCode.PICKITEM: + PickItem(instruction); + break; + case OpCode.APPEND: + Append(instruction); + break; + case OpCode.SETITEM: + SetItem(instruction); + break; + case OpCode.REVERSEITEMS: + ReverseItems(instruction); + break; + case OpCode.REMOVE: + Remove(instruction); + break; + case OpCode.CLEARITEMS: + ClearItems(instruction); + break; + case OpCode.POPITEM: + PopItem(instruction); + break; + } + */ + } + + } +} diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index ba161e6951..02b7691f87 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Configuration.Hocon; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; @@ -32,6 +33,8 @@ namespace Neo.SmartContract /// public partial class ApplicationEngine : ExecutionEngine { + private static readonly JumpTable JumpTable = ComposeJumpTable(); + /// /// The maximum cost that can be spent when a contract is executed in test mode. /// @@ -177,6 +180,58 @@ protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, D diagnostic?.Initialized(this); } + #region JumpTable + + private static JumpTable ComposeJumpTable() + { + var table = new JumpTable(); + + table[OpCode.SYSCALL] = OnSysCall; + table[OpCode.CALLT] = OnCallT; + + return table; + } + + private static void OnCallT(ExecutionEngine engine, Instruction instruction) + { + if (engine is ApplicationEngine app) + { + uint tokenId = instruction.TokenU16; + + app.ValidateCallFlags(CallFlags.ReadStates | CallFlags.AllowCall); + ContractState contract = app.CurrentContext.GetState().Contract; + if (contract is null || tokenId >= contract.Nef.Tokens.Length) + throw new InvalidOperationException(); + MethodToken token = contract.Nef.Tokens[tokenId]; + if (token.ParametersCount > app.CurrentContext.EvaluationStack.Count) + throw new InvalidOperationException(); + StackItem[] args = new StackItem[token.ParametersCount]; + for (int i = 0; i < token.ParametersCount; i++) + args[i] = app.Pop(); + app.CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); + } + else + { + throw new InvalidOperationException(); + } + } + + private static void OnSysCall(ExecutionEngine engine, Instruction instruction) + { + if (engine is ApplicationEngine app) + { + uint method = instruction.TokenU16; + + app.OnSysCall(services[method]); + } + else + { + throw new InvalidOperationException(); + } + } + + #endregion + /// /// Adds GAS to and checks if it has exceeded the maximum limit. /// @@ -270,9 +325,9 @@ internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UI return task; } - protected override void ContextUnloaded(ExecutionContext context) + public override void UnloadedContext(ExecutionContext context) { - base.ContextUnloaded(context); + base.UnloadedContext(context); if (context.Script != CurrentContext?.Script) { ExecutionContextState state = context.GetState(); @@ -324,7 +379,7 @@ public static ApplicationEngine Create(TriggerType trigger, IVerifiable containe ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic); } - protected override void LoadContext(ExecutionContext context) + public override void LoadContext(ExecutionContext context) { // Set default execution context state var state = context.GetState(); @@ -391,21 +446,6 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP return context; } - protected override ExecutionContext LoadToken(ushort tokenId) - { - ValidateCallFlags(CallFlags.ReadStates | CallFlags.AllowCall); - ContractState contract = CurrentContext.GetState().Contract; - if (contract is null || tokenId >= contract.Nef.Tokens.Length) - throw new InvalidOperationException(); - MethodToken token = contract.Nef.Tokens[tokenId]; - if (token.ParametersCount > CurrentContext.EvaluationStack.Count) - throw new InvalidOperationException(); - StackItem[] args = new StackItem[token.ParametersCount]; - for (int i = 0; i < token.ParametersCount; i++) - args[i] = Pop(); - return CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); - } - /// /// Converts an to a that used in the virtual machine. /// @@ -503,11 +543,6 @@ internal protected void ValidateCallFlags(CallFlags requiredCallFlags) throw new InvalidOperationException($"Cannot call this SYSCALL with the flag {state.CallFlags}."); } - protected override void OnSysCall(uint method) - { - OnSysCall(services[method]); - } - /// /// Invokes the specified interoperable service. /// diff --git a/tests/Neo.VM.Tests/Types/TestEngine.cs b/tests/Neo.VM.Tests/Types/TestEngine.cs index 07aa89a09f..cf99314892 100644 --- a/tests/Neo.VM.Tests/Types/TestEngine.cs +++ b/tests/Neo.VM.Tests/Types/TestEngine.cs @@ -19,21 +19,32 @@ class TestEngine : ExecutionEngine { public Exception FaultException { get; private set; } - protected override void OnSysCall(uint method) + public TestEngine() : base(ComposeJumpTable()) { } + + private static JumpTable ComposeJumpTable() { + JumpTable jumpTable = new JumpTable(); + jumpTable[OpCode.SYSCALL] = OnSysCall; + return jumpTable; + } + + private static void OnSysCall(ExecutionEngine engine, Instruction instruction) + { + uint method = instruction.TokenU32; + if (method == 0x77777777) { - CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new object())); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new object())); return; } if (method == 0xaddeadde) { - ExecuteThrow("error"); + engine.JumpTable.ExecuteThrow(engine, "error"); return; } - throw new System.Exception(); + throw new Exception(); } protected override void OnFault(Exception ex)