From e19bd08621d437427a056693a0705446a3273517 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 15 Feb 2024 17:03:21 -0800 Subject: [PATCH 01/21] first working attempt --- gnovm/pkg/gnolang/frame.go | 2 + gnovm/pkg/gnolang/machine.go | 98 ++++++++++++++++++++--------- gnovm/pkg/gnolang/op_expressions.go | 18 ++++++ gnovm/pkg/gnolang/uverse.go | 14 +++-- 4 files changed, 100 insertions(+), 32 deletions(-) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 7f87fa26097..f966cb15401 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -26,6 +26,8 @@ type Frame struct { Defers []Defer // deferred calls LastPackage *PackageValue // previous package context LastRealm *Realm // previous realm context + + MachineExceptionsIdx int } func (fr Frame) String() string { diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index e9e8eba8adc..287408f43d3 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1662,20 +1662,21 @@ func (m *Machine) PushFrameBasic(s Stmt) { // bugs with frame pops. func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { fr := Frame{ - Source: cx, - NumOps: m.NumOps, - NumValues: m.NumValues - cx.NumArgs - 1, - NumExprs: len(m.Exprs), - NumStmts: len(m.Stmts), - NumBlocks: len(m.Blocks), - Func: fv, - GoFunc: nil, - Receiver: recv, - NumArgs: cx.NumArgs, - IsVarg: cx.Varg, - Defers: nil, - LastPackage: m.Package, - LastRealm: m.Realm, + Source: cx, + NumOps: m.NumOps, + NumValues: m.NumValues - cx.NumArgs - 1, + NumExprs: len(m.Exprs), + NumStmts: len(m.Stmts), + NumBlocks: len(m.Blocks), + Func: fv, + GoFunc: nil, + Receiver: recv, + NumArgs: cx.NumArgs, + IsVarg: cx.Varg, + Defers: nil, + LastPackage: m.Package, + LastRealm: m.Realm, + MachineExceptionsIdx: len(m.Exceptions), } if debug { if m.Package == nil { @@ -1699,20 +1700,21 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { func (m *Machine) PushFrameGoNative(cx *CallExpr, fv *NativeValue) { fr := Frame{ - Source: cx, - NumOps: m.NumOps, - NumValues: m.NumValues - cx.NumArgs - 1, - NumExprs: len(m.Exprs), - NumStmts: len(m.Stmts), - NumBlocks: len(m.Blocks), - Func: nil, - GoFunc: fv, - Receiver: TypedValue{}, - NumArgs: cx.NumArgs, - IsVarg: cx.Varg, - Defers: nil, - LastPackage: m.Package, - LastRealm: m.Realm, + Source: cx, + NumOps: m.NumOps, + NumValues: m.NumValues - cx.NumArgs - 1, + NumExprs: len(m.Exprs), + NumStmts: len(m.Stmts), + NumBlocks: len(m.Blocks), + Func: nil, + GoFunc: fv, + Receiver: TypedValue{}, + NumArgs: cx.NumArgs, + IsVarg: cx.Varg, + Defers: nil, + LastPackage: m.Package, + LastRealm: m.Realm, + MachineExceptionsIdx: len(m.Exceptions), } if debug { m.Printf("+F %#v\n", fr) @@ -1823,6 +1825,46 @@ func (m *Machine) LastCallFrame(n int) *Frame { panic("frame not found") } +func (m *Machine) GetRecoveryException() (TypedValue, int, bool) { + var tv TypedValue + if len(m.Exceptions) == 0 || len(m.Frames) < 3 { + return tv, 0, false + } + + n := 3 + var ( + // funcValue *FuncValue + // goFunc *NativeValue + exceptionsIdx int + ) + for i := len(m.Frames) - 1; i >= 0; i-- { + fr := &m.Frames[i] + if fr.Func != nil || fr.GoFunc != nil { + // TODO: optimize with fr.IsCall + if n == 1 { + if exceptionsIdx >= len(m.Exceptions) { + return tv, exceptionsIdx, false + } + // for _, def := range fr.Defers { + // if (def.Func != nil && def.Func == funcValue) || (def.GoFunc != nil && def.GoFunc == goFunc) { + return *m.Exceptions[len(m.Exceptions)-1], exceptionsIdx, true + //} + // } + + break + } else if n == 2 { + // funcValue = fr.Func + // goFunc = fr.GoFunc + exceptionsIdx = fr.MachineExceptionsIdx + } + + n-- + } + } + + return tv, exceptionsIdx, false +} + // pops the last non-call (loop) frames // and returns the last call frame (which is left on stack). func (m *Machine) PopUntilLastCallFrame() *Frame { diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index b3bf240aea1..80d613a5b62 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -280,6 +280,14 @@ func (m *Machine) doOpTypeAssert2() { xt := xv.T if t.Kind() == InterfaceKind { // is interface assert + // It's fine for the type of the value to be nil. If it is, + // then there is no way any interface type assertion can + // succeed, so we can just return false. + if xt == nil { + *tv = untypedBool(false) + return + } + if it, ok := baseOf(t).(*InterfaceType); ok { // t is Gno interface. // assert that x implements type. @@ -315,6 +323,16 @@ func (m *Machine) doOpTypeAssert2() { } } else { // is concrete assert tid := t.TypeID() + + // If the type value we are trying to assert is nil, then we can say + // the type assertion has failed. This prevents a panic. This is safe + // because we've already checked that the type we are trying to assert to + // is not an interface type. + if xt == nil { + *tv = untypedBool(false) + return + } + xtid := xt.TypeID() // assert that x is of type. same := tid == xtid diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index a0e9913eaad..70a624b818b 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -968,14 +968,20 @@ func UverseNode() *PackageNode { "exception", AnyT(), ), func(m *Machine) { - if len(m.Exceptions) == 0 { + var ( + exception TypedValue + deleteIndex int + ok bool + ) + + if exception, deleteIndex, ok = m.GetRecoveryException(); !ok { m.PushValue(TypedValue{}) return } - // Just like in go, only the last exception is returned to recover. - m.PushValue(*m.Exceptions[len(m.Exceptions)-1]) + + m.PushValue(exception) // The remaining exceptions are removed - m.Exceptions = nil + m.Exceptions = m.Exceptions[:deleteIndex] }, ) return uverseNode From 2f13df9bafcd25302975ad8e73de4f3b6344d5ed Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 16 Feb 2024 15:20:13 -0800 Subject: [PATCH 02/21] other recovery cases working now --- gnovm/pkg/gnolang/frame.go | 2 ++ gnovm/pkg/gnolang/machine.go | 54 +++++++++++++++--------------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index f966cb15401..0db28851571 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -28,6 +28,7 @@ type Frame struct { LastRealm *Realm // previous realm context MachineExceptionsIdx int + DeferInProgress bool } func (fr Frame) String() string { @@ -72,6 +73,7 @@ func (fr *Frame) PopDefer() (res Defer, ok bool) { if len(fr.Defers) > 0 { ok = true res = fr.Defers[len(fr.Defers)-1] + fr.DeferInProgress = true fr.Defers = fr.Defers[:len(fr.Defers)-1] } return diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 287408f43d3..c9e4ad8a29a 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1826,43 +1826,33 @@ func (m *Machine) LastCallFrame(n int) *Frame { } func (m *Machine) GetRecoveryException() (TypedValue, int, bool) { - var tv TypedValue - if len(m.Exceptions) == 0 || len(m.Frames) < 3 { - return tv, 0, false + if len(m.Exceptions) == 0 { + return TypedValue{}, 0, false + } + + // Get the first call frame. This should be for the call to recover(). If there is a defer in progress, + // then that means this recover was deferred directly and there is no need to search for the + // calling frame. + callFrame := m.LastCallFrame(1) + if callFrame.DeferInProgress { + var idx int + if idx = callFrame.MachineExceptionsIdx; idx >= len(m.Exceptions) { + return TypedValue{}, callFrame.MachineExceptionsIdx, false + } + return *m.Exceptions[len(m.Exceptions)-1], idx, true } - n := 3 - var ( - // funcValue *FuncValue - // goFunc *NativeValue - exceptionsIdx int - ) - for i := len(m.Frames) - 1; i >= 0; i-- { - fr := &m.Frames[i] - if fr.Func != nil || fr.GoFunc != nil { - // TODO: optimize with fr.IsCall - if n == 1 { - if exceptionsIdx >= len(m.Exceptions) { - return tv, exceptionsIdx, false - } - // for _, def := range fr.Defers { - // if (def.Func != nil && def.Func == funcValue) || (def.GoFunc != nil && def.GoFunc == goFunc) { - return *m.Exceptions[len(m.Exceptions)-1], exceptionsIdx, true - //} - // } - - break - } else if n == 2 { - // funcValue = fr.Func - // goFunc = fr.GoFunc - exceptionsIdx = fr.MachineExceptionsIdx - } - - n-- + // Not in the first frame, so check the second. + callFrame = m.LastCallFrame(2) + if callFrame.DeferInProgress { + var idx int + if idx = callFrame.MachineExceptionsIdx; idx >= len(m.Exceptions) { + return TypedValue{}, callFrame.MachineExceptionsIdx, false } + return *m.Exceptions[len(m.Exceptions)-1], idx, true } - return tv, exceptionsIdx, false + return TypedValue{}, 0, false } // pops the last non-call (loop) frames From 5a2f5f284c6ffb96d4de0b7aca4a23e54d8b5cf7 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 20 Feb 2024 18:17:36 -0800 Subject: [PATCH 03/21] getting closer. recover tests are passing --- gnovm/pkg/gnolang/frame.go | 15 ++-- gnovm/pkg/gnolang/machine.go | 153 ++++++++++++++++++----------------- gnovm/pkg/gnolang/op_call.go | 32 +++++--- gnovm/pkg/gnolang/uverse.go | 27 ++++--- 4 files changed, 122 insertions(+), 105 deletions(-) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 0db28851571..9d65126c786 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -27,8 +27,7 @@ type Frame struct { LastPackage *PackageValue // previous package context LastRealm *Realm // previous realm context - MachineExceptionsIdx int - DeferInProgress bool + Popped bool // true if frame has been popped } func (fr Frame) String() string { @@ -73,7 +72,6 @@ func (fr *Frame) PopDefer() (res Defer, ok bool) { if len(fr.Defers) > 0 { ok = true res = fr.Defers[len(fr.Defers)-1] - fr.DeferInProgress = true fr.Defers = fr.Defers[:len(fr.Defers)-1] } return @@ -83,9 +81,10 @@ func (fr *Frame) PopDefer() (res Defer, ok bool) { // Defer type Defer struct { - Func *FuncValue // function value - GoFunc *NativeValue // go function value - Args []TypedValue // arguments - Source *DeferStmt // source - Parent *Block + Func *FuncValue // function value + GoFunc *NativeValue // go function value + Args []TypedValue // arguments + Source *DeferStmt // source + Parent *Block + PanicScope uint } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index c9e4ad8a29a..c8ae6ba67ec 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -21,20 +21,21 @@ import ( type Machine struct { // State - Ops []Op // main operations - NumOps int - Values []TypedValue // buffer of values to be operated on - NumValues int // number of values - Exprs []Expr // pending expressions - Stmts []Stmt // pending statements - Blocks []*Block // block (scope) stack - Frames []Frame // func call stack - Package *PackageValue // active package - Realm *Realm // active realm - Alloc *Allocator // memory allocations - Exceptions []*TypedValue // if panic'd unless recovered - NumResults int // number of results returned - Cycles int64 // number of "cpu" cycles + Ops []Op // main operations + NumOps int + Values []TypedValue // buffer of values to be operated on + NumValues int // number of values + Exprs []Expr // pending expressions + Stmts []Stmt // pending statements + Blocks []*Block // block (scope) stack + Frames []*Frame // func call stack + Package *PackageValue // active package + Realm *Realm // active realm + Alloc *Allocator // memory allocations + Exceptions []*TypedValue // if panic'd unless recovered + ExceptionFrames []*Frame // if panic'd unless recovered + NumResults int // number of results returned + Cycles int64 // number of "cpu" cycles // Configuration CheckTypes bool // not yet used @@ -44,6 +45,9 @@ type Machine struct { Output io.Writer Store Store Context interface{} + + PanicScope uint + DeferPanicScope uint } // NewMachine initializes a new gno virtual machine, acting as a shorthand @@ -1425,6 +1429,11 @@ func (m *Machine) PushOp(op Op) { copy(newOps, m.Ops) m.Ops = newOps } + + // if op == OpReturnCallDefers || op == OpDefer { + // m.DeferDepth++ + // } + m.Ops[m.NumOps] = op m.NumOps++ } @@ -1642,7 +1651,7 @@ func (m *Machine) LastBlock() *Block { // Pushes a frame with one less statement. func (m *Machine) PushFrameBasic(s Stmt) { label := s.GetLabel() - fr := Frame{ + fr := &Frame{ Label: label, Source: s, NumOps: m.NumOps, @@ -1661,22 +1670,21 @@ func (m *Machine) PushFrameBasic(s Stmt) { // ensure the counts are consistent, otherwise we mask // bugs with frame pops. func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { - fr := Frame{ - Source: cx, - NumOps: m.NumOps, - NumValues: m.NumValues - cx.NumArgs - 1, - NumExprs: len(m.Exprs), - NumStmts: len(m.Stmts), - NumBlocks: len(m.Blocks), - Func: fv, - GoFunc: nil, - Receiver: recv, - NumArgs: cx.NumArgs, - IsVarg: cx.Varg, - Defers: nil, - LastPackage: m.Package, - LastRealm: m.Realm, - MachineExceptionsIdx: len(m.Exceptions), + fr := &Frame{ + Source: cx, + NumOps: m.NumOps, + NumValues: m.NumValues - cx.NumArgs - 1, + NumExprs: len(m.Exprs), + NumStmts: len(m.Stmts), + NumBlocks: len(m.Blocks), + Func: fv, + GoFunc: nil, + Receiver: recv, + NumArgs: cx.NumArgs, + IsVarg: cx.Varg, + Defers: nil, + LastPackage: m.Package, + LastRealm: m.Realm, } if debug { if m.Package == nil { @@ -1699,22 +1707,21 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { } func (m *Machine) PushFrameGoNative(cx *CallExpr, fv *NativeValue) { - fr := Frame{ - Source: cx, - NumOps: m.NumOps, - NumValues: m.NumValues - cx.NumArgs - 1, - NumExprs: len(m.Exprs), - NumStmts: len(m.Stmts), - NumBlocks: len(m.Blocks), - Func: nil, - GoFunc: fv, - Receiver: TypedValue{}, - NumArgs: cx.NumArgs, - IsVarg: cx.Varg, - Defers: nil, - LastPackage: m.Package, - LastRealm: m.Realm, - MachineExceptionsIdx: len(m.Exceptions), + fr := &Frame{ + Source: cx, + NumOps: m.NumOps, + NumValues: m.NumValues - cx.NumArgs - 1, + NumExprs: len(m.Exprs), + NumStmts: len(m.Stmts), + NumBlocks: len(m.Blocks), + Func: nil, + GoFunc: fv, + Receiver: TypedValue{}, + NumArgs: cx.NumArgs, + IsVarg: cx.Varg, + Defers: nil, + LastPackage: m.Package, + LastRealm: m.Realm, } if debug { m.Printf("+F %#v\n", fr) @@ -1726,15 +1733,17 @@ func (m *Machine) PushFrameGoNative(cx *CallExpr, fv *NativeValue) { func (m *Machine) PopFrame() Frame { numFrames := len(m.Frames) f := m.Frames[numFrames-1] + f.Popped = true if debug { m.Printf("-F %#v\n", f) } m.Frames = m.Frames[:numFrames-1] - return f + return *f } func (m *Machine) PopFrameAndReset() { fr := m.PopFrame() + fr.Popped = true m.NumOps = fr.NumOps m.NumValues = fr.NumValues m.Exprs = m.Exprs[:fr.NumExprs] @@ -1746,6 +1755,7 @@ func (m *Machine) PopFrameAndReset() { // TODO: optimize by passing in last frame. func (m *Machine) PopFrameAndReturn() { fr := m.PopFrame() + fr.Popped = true if debug { // TODO: optimize with fr.IsCall if fr.Func == nil && fr.GoFunc == nil { @@ -1801,7 +1811,7 @@ func (m *Machine) NumFrames() int { } func (m *Machine) LastFrame() *Frame { - return &m.Frames[len(m.Frames)-1] + return m.Frames[len(m.Frames)-1] } // TODO: this function and PopUntilLastCallFrame() is used in conjunction @@ -1812,7 +1822,7 @@ func (m *Machine) LastCallFrame(n int) *Frame { panic("n must be positive") } for i := len(m.Frames) - 1; i >= 0; i-- { - fr := &m.Frames[i] + fr := m.Frames[i] if fr.Func != nil || fr.GoFunc != nil { // TODO: optimize with fr.IsCall if n == 1 { @@ -1825,41 +1835,30 @@ func (m *Machine) LastCallFrame(n int) *Frame { panic("frame not found") } -func (m *Machine) GetRecoveryException() (TypedValue, int, bool) { - if len(m.Exceptions) == 0 { - return TypedValue{}, 0, false - } - - // Get the first call frame. This should be for the call to recover(). If there is a defer in progress, - // then that means this recover was deferred directly and there is no need to search for the - // calling frame. - callFrame := m.LastCallFrame(1) - if callFrame.DeferInProgress { - var idx int - if idx = callFrame.MachineExceptionsIdx; idx >= len(m.Exceptions) { - return TypedValue{}, callFrame.MachineExceptionsIdx, false - } - return *m.Exceptions[len(m.Exceptions)-1], idx, true +func (m *Machine) LastCallFrameSafe(n int) *Frame { + if n == 0 { + panic("n must be positive") } - - // Not in the first frame, so check the second. - callFrame = m.LastCallFrame(2) - if callFrame.DeferInProgress { - var idx int - if idx = callFrame.MachineExceptionsIdx; idx >= len(m.Exceptions) { - return TypedValue{}, callFrame.MachineExceptionsIdx, false + for i := len(m.Frames) - 1; i >= 0; i-- { + fr := m.Frames[i] + if fr.Func != nil || fr.GoFunc != nil { + // TODO: optimize with fr.IsCall + if n == 1 { + return fr + } else { + n-- // continue + } } - return *m.Exceptions[len(m.Exceptions)-1], idx, true } - - return TypedValue{}, 0, false + return nil } // pops the last non-call (loop) frames // and returns the last call frame (which is left on stack). func (m *Machine) PopUntilLastCallFrame() *Frame { for i := len(m.Frames) - 1; i >= 0; i-- { - fr := &m.Frames[i] + fr := m.Frames[i] + fr.Popped = true if fr.Func != nil || fr.GoFunc != nil { // TODO: optimize with fr.IsCall m.Frames = m.Frames[:i+1] @@ -1959,6 +1958,8 @@ func (m *Machine) CheckEmpty() error { func (m *Machine) Panic(ex TypedValue) { m.Exceptions = append(m.Exceptions, &ex) + m.ExceptionFrames = append(m.ExceptionFrames, m.LastCallFrame(1)) + m.PanicScope++ m.PopUntilLastCallFrame() m.PushOp(OpPanic2) m.PushOp(OpReturnCallDefers) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index c7274dd73af..abbea7fb1c8 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -264,6 +264,7 @@ func (m *Machine) doOpReturnCallDefers() { dfr, ok := cfr.PopDefer() if !ok { // Done with defers. + m.DeferPanicScope-- m.ForcePopOp() if len(m.Exceptions) > 0 { // In a state of panic (not return). @@ -272,6 +273,9 @@ func (m *Machine) doOpReturnCallDefers() { } return } + + m.DeferPanicScope = dfr.PanicScope + // Call last deferred call. // NOTE: the following logic is largely duplicated in doOpCall(). // Convert if variadic argument. @@ -361,10 +365,11 @@ func (m *Machine) doOpDefer() { case *FuncValue: // TODO what if value is NativeValue? cfr.PushDefer(Defer{ - Func: cv, - Args: args, - Source: ds, - Parent: lb, + Func: cv, + Args: args, + Source: ds, + Parent: lb, + PanicScope: m.PanicScope, }) case *BoundMethodValue: if debug { @@ -381,17 +386,19 @@ func (m *Machine) doOpDefer() { args2[0] = cv.Receiver copy(args2[1:], args) cfr.PushDefer(Defer{ - Func: cv.Func, - Args: args2, - Source: ds, - Parent: lb, + Func: cv.Func, + Args: args2, + Source: ds, + Parent: lb, + PanicScope: m.PanicScope, }) case *NativeValue: cfr.PushDefer(Defer{ - GoFunc: cv, - Args: args, - Source: ds, - Parent: lb, + GoFunc: cv, + Args: args, + Source: ds, + Parent: lb, + PanicScope: m.PanicScope, }) default: panic("should not happen") @@ -410,6 +417,7 @@ func (m *Machine) doOpPanic2() { // Recovered from panic m.PushOp(OpReturnFromBlock) m.PushOp(OpReturnCallDefers) + m.PanicScope-- } else { // Keep panicking last := m.PopUntilLastCallFrame() diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 70a624b818b..7e6587d76e2 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -968,20 +968,29 @@ func UverseNode() *PackageNode { "exception", AnyT(), ), func(m *Machine) { - var ( - exception TypedValue - deleteIndex int - ok bool - ) + if len(m.Exceptions) == 0 { + m.PushValue(TypedValue{}) + return + } - if exception, deleteIndex, ok = m.GetRecoveryException(); !ok { + numExceptions := len(m.Exceptions) + if m.PanicScope <= m.DeferPanicScope { m.PushValue(TypedValue{}) return } - m.PushValue(exception) - // The remaining exceptions are removed - m.Exceptions = m.Exceptions[:deleteIndex] + exceptionFrame := m.ExceptionFrames[numExceptions-1] + if !exceptionFrame.Popped { + if frame := m.LastCallFrameSafe(2); frame != nil && frame != m.ExceptionFrames[numExceptions-1] { + m.PushValue(TypedValue{}) + return + } + } + + m.PushValue(*m.Exceptions[numExceptions-1]) + // This exception is removed. + m.Exceptions = nil + m.ExceptionFrames = nil }, ) return uverseNode From 1942842017bba90da61881d0b963d2fcafb45a2c Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 20 Feb 2024 21:13:54 -0800 Subject: [PATCH 04/21] comments --- gnovm/pkg/gnolang/frame.go | 14 +++++++---- gnovm/pkg/gnolang/machine.go | 47 +++++++++++++++++++++--------------- gnovm/pkg/gnolang/uverse.go | 6 +++++ 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 9d65126c786..078448b550e 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -81,10 +81,14 @@ func (fr *Frame) PopDefer() (res Defer, ok bool) { // Defer type Defer struct { - Func *FuncValue // function value - GoFunc *NativeValue // go function value - Args []TypedValue // arguments - Source *DeferStmt // source - Parent *Block + Func *FuncValue // function value + GoFunc *NativeValue // go function value + Args []TypedValue // arguments + Source *DeferStmt // source + Parent *Block + + // PanicScope is set to the value of the Machine's PanicScope when the + // defer is created. The PanicScope of the Machine is incremented each time + // a panic occurrs and is decremented each time a panic is recovered. PanicScope uint } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index c8ae6ba67ec..f3b7b757bab 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -21,21 +21,25 @@ import ( type Machine struct { // State - Ops []Op // main operations - NumOps int - Values []TypedValue // buffer of values to be operated on - NumValues int // number of values - Exprs []Expr // pending expressions - Stmts []Stmt // pending statements - Blocks []*Block // block (scope) stack - Frames []*Frame // func call stack - Package *PackageValue // active package - Realm *Realm // active realm - Alloc *Allocator // memory allocations - Exceptions []*TypedValue // if panic'd unless recovered - ExceptionFrames []*Frame // if panic'd unless recovered - NumResults int // number of results returned - Cycles int64 // number of "cpu" cycles + Ops []Op // main operations + NumOps int + Values []TypedValue // buffer of values to be operated on + NumValues int // number of values + Exprs []Expr // pending expressions + Stmts []Stmt // pending statements + Blocks []*Block // block (scope) stack + Frames []*Frame // func call stack + Package *PackageValue // active package + Realm *Realm // active realm + Alloc *Allocator // memory allocations + Exceptions []*TypedValue // if panic'd unless recovered + + // ExceptionFrames is appended to each time a panic happens. It is used to + // reference the frame a panic occurred in so that recover() knows if the + // currently executing deferred function is able to recover from the panic. + ExceptionFrames []*Frame + NumResults int // number of results returned + Cycles int64 // number of "cpu" cycles // Configuration CheckTypes bool // not yet used @@ -46,7 +50,12 @@ type Machine struct { Store Store Context interface{} - PanicScope uint + // PanicScope is incremented each time a panic occurs and decremented + // when it is recovered. + PanicScope uint + // DeferPanicScope is set to the value of the defer's panic scope before + // it is executed. It is decremented after defer functions in the current + // scope have finished executing. DeferPanicScope uint } @@ -1430,10 +1439,6 @@ func (m *Machine) PushOp(op Op) { m.Ops = newOps } - // if op == OpReturnCallDefers || op == OpDefer { - // m.DeferDepth++ - // } - m.Ops[m.NumOps] = op m.NumOps++ } @@ -1835,6 +1840,8 @@ func (m *Machine) LastCallFrame(n int) *Frame { panic("frame not found") } +// LastCallFrameSafe behaves the same as LastCallFrame, but rather than panicking, +// returns nil if the frame is not found. func (m *Machine) LastCallFrameSafe(n int) *Frame { if n == 0 { panic("n must be positive") diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 7e6587d76e2..62bd8b047aa 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -973,14 +973,20 @@ func UverseNode() *PackageNode { return } + // If the exception is out of scope, this recover can't help; return nil. numExceptions := len(m.Exceptions) if m.PanicScope <= m.DeferPanicScope { m.PushValue(TypedValue{}) return } + // If the frame the exception ocurred in is not popped, it's possible that + // the exception is still in scope and can be recovered. exceptionFrame := m.ExceptionFrames[numExceptions-1] if !exceptionFrame.Popped { + // If the frame is not the current frame, the exception is not in scope; return nil. + // This retrieves the second most recent call frame because the first most recent + // is the call to recover itself. if frame := m.LastCallFrameSafe(2); frame != nil && frame != m.ExceptionFrames[numExceptions-1] { m.PushValue(TypedValue{}) return From d8d337c33b4b49b06f29f7cb992785f47178f851 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 20 Feb 2024 21:16:01 -0800 Subject: [PATCH 05/21] reset scopes to zero when they themselves go out of scope --- gnovm/pkg/gnolang/op_call.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index abbea7fb1c8..4cb59d57d3d 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -264,7 +264,7 @@ func (m *Machine) doOpReturnCallDefers() { dfr, ok := cfr.PopDefer() if !ok { // Done with defers. - m.DeferPanicScope-- + m.DeferPanicScope = 0 m.ForcePopOp() if len(m.Exceptions) > 0 { // In a state of panic (not return). @@ -417,7 +417,7 @@ func (m *Machine) doOpPanic2() { // Recovered from panic m.PushOp(OpReturnFromBlock) m.PushOp(OpReturnCallDefers) - m.PanicScope-- + m.PanicScope = 0 } else { // Keep panicking last := m.PopUntilLastCallFrame() From d6d9b9c61ba64eda01ff3c696b9e102a94dafb5e Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 20 Feb 2024 21:26:48 -0800 Subject: [PATCH 06/21] added new recover tests --- gnovm/tests/files/recover10.gno | 7 ++++++ .../tests/{challenges => files}/recover5b.gno | 1 - gnovm/tests/files/recover8.gno | 24 +++++++++++++++++++ gnovm/tests/files/recover9.gno | 14 +++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 gnovm/tests/files/recover10.gno rename gnovm/tests/{challenges => files}/recover5b.gno (98%) create mode 100644 gnovm/tests/files/recover8.gno create mode 100644 gnovm/tests/files/recover9.gno diff --git a/gnovm/tests/files/recover10.gno b/gnovm/tests/files/recover10.gno new file mode 100644 index 00000000000..83e6ab62c1a --- /dev/null +++ b/gnovm/tests/files/recover10.gno @@ -0,0 +1,7 @@ +package main + +func main() { + defer recover() + + panic("ahhhhh") +} diff --git a/gnovm/tests/challenges/recover5b.gno b/gnovm/tests/files/recover5b.gno similarity index 98% rename from gnovm/tests/challenges/recover5b.gno rename to gnovm/tests/files/recover5b.gno index 285bc386093..f94c7442efe 100644 --- a/gnovm/tests/challenges/recover5b.gno +++ b/gnovm/tests/files/recover5b.gno @@ -24,4 +24,3 @@ func g() { // Output: // g recover undefined // f recover wtf -// false diff --git a/gnovm/tests/files/recover8.gno b/gnovm/tests/files/recover8.gno new file mode 100644 index 00000000000..53b31f05468 --- /dev/null +++ b/gnovm/tests/files/recover8.gno @@ -0,0 +1,24 @@ +package main + +func doSomething() { + defer func() { + doSomethingElse() + if r := recover(); r != nil { + panic("do something panic") + } + }() + panic("first panic") +} + +func doSomethingElse() { + if r := recover(); r != nil { + panic("do something else panic") + } +} + +func main() { + doSomething() +} + +// Error: +// do something panic diff --git a/gnovm/tests/files/recover9.gno b/gnovm/tests/files/recover9.gno new file mode 100644 index 00000000000..d6c944aeb06 --- /dev/null +++ b/gnovm/tests/files/recover9.gno @@ -0,0 +1,14 @@ +package main + +func rec() { + println(recover()) +} + +func main() { + defer rec() + + panic("ahhhhh") +} + +// Output: +// ahhhhh From b0b7fc22a327233d7f1bce3baef2ed47ca1e1f62 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 21 Feb 2024 11:36:36 -0800 Subject: [PATCH 07/21] removed erroneous pop flag --- gnovm/pkg/gnolang/machine.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index f3b7b757bab..f8fb58813d5 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1865,7 +1865,6 @@ func (m *Machine) LastCallFrameSafe(n int) *Frame { func (m *Machine) PopUntilLastCallFrame() *Frame { for i := len(m.Frames) - 1; i >= 0; i-- { fr := m.Frames[i] - fr.Popped = true if fr.Func != nil || fr.GoFunc != nil { // TODO: optimize with fr.IsCall m.Frames = m.Frames[:i+1] From dcdd779451874c1ecbb601788e86125b815e1adc Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 21 Feb 2024 11:55:15 -0800 Subject: [PATCH 08/21] combined and added last call frame safety --- gnovm/pkg/gnolang/machine.go | 44 ++++++++++++++++++++++-------------- gnovm/pkg/gnolang/op_call.go | 6 ++--- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index f8fb58813d5..94954a190c3 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1823,26 +1823,16 @@ func (m *Machine) LastFrame() *Frame { // spanning two disjoint operations upon return. Optimize. // If n is 1, returns the immediately last call frame. func (m *Machine) LastCallFrame(n int) *Frame { - if n == 0 { - panic("n must be positive") - } - for i := len(m.Frames) - 1; i >= 0; i-- { - fr := m.Frames[i] - if fr.Func != nil || fr.GoFunc != nil { - // TODO: optimize with fr.IsCall - if n == 1 { - return fr - } else { - n-- // continue - } - } - } - panic("frame not found") + return m.lastCallFrame(n, false) } // LastCallFrameSafe behaves the same as LastCallFrame, but rather than panicking, // returns nil if the frame is not found. func (m *Machine) LastCallFrameSafe(n int) *Frame { + return m.lastCallFrame(n, true) +} + +func (m *Machine) lastCallFrame(n int, safe bool) *Frame { if n == 0 { panic("n must be positive") } @@ -1857,12 +1847,17 @@ func (m *Machine) LastCallFrameSafe(n int) *Frame { } } } + + if !safe { + panic("frame not found") + } + return nil } // pops the last non-call (loop) frames // and returns the last call frame (which is left on stack). -func (m *Machine) PopUntilLastCallFrame() *Frame { +func (m *Machine) PopUntilLastCallFrame(safe bool) *Frame { for i := len(m.Frames) - 1; i >= 0; i-- { fr := m.Frames[i] if fr.Func != nil || fr.GoFunc != nil { @@ -1870,7 +1865,22 @@ func (m *Machine) PopUntilLastCallFrame() *Frame { m.Frames = m.Frames[:i+1] return fr } + + fr.Popped = true + } + + // The frame wasn't found. If not in safe mode, panic to avoid any potential + // nil pointer dereferences. + if !safe { + panic("no last call frame found") } + + // In safe mode we return nil and no frames are popped, so update the frames' popped flag. + // This is expected to happen infrequently. + for _, frame := range m.Frames { + frame.Popped = false + } + return nil } @@ -1966,7 +1976,7 @@ func (m *Machine) Panic(ex TypedValue) { m.Exceptions = append(m.Exceptions, &ex) m.ExceptionFrames = append(m.ExceptionFrames, m.LastCallFrame(1)) m.PanicScope++ - m.PopUntilLastCallFrame() + m.PopUntilLastCallFrame(false) m.PushOp(OpPanic2) m.PushOp(OpReturnCallDefers) } diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 4cb59d57d3d..74b85a92d64 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -180,7 +180,7 @@ func (m *Machine) doOpCallDeferNativeBody() { // Assumes that result values are pushed onto the Values stack. func (m *Machine) doOpReturn() { - cfr := m.PopUntilLastCallFrame() + cfr := m.PopUntilLastCallFrame(true) // See if we are exiting a realm boundary. // NOTE: there are other ways to implement realm boundary transitions, // e.g. with independent Machine instances per realm for example, or @@ -212,7 +212,7 @@ func (m *Machine) doOpReturn() { // i.e. named result vars declared in func signatures. func (m *Machine) doOpReturnFromBlock() { // Copy results from block. - cfr := m.PopUntilLastCallFrame() + cfr := m.PopUntilLastCallFrame(true) ft := cfr.Func.GetType(m.Store) numParams := len(ft.Params) numResults := len(ft.Results) @@ -420,7 +420,7 @@ func (m *Machine) doOpPanic2() { m.PanicScope = 0 } else { // Keep panicking - last := m.PopUntilLastCallFrame() + last := m.PopUntilLastCallFrame(true) if last == nil { // Build exception string just as go, separated by \n\t. exs := make([]string, len(m.Exceptions)) From 985e8248deef38dfd356fd86c0cb32ffe563a9cd Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 13:36:13 -0800 Subject: [PATCH 09/21] added tests --- gnovm/tests/files/recover11.gno | 38 ++++++++++++++++++++++++++++++++ gnovm/tests/files/recover12a.gno | 26 ++++++++++++++++++++++ gnovm/tests/files/recover12b.gno | 28 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 gnovm/tests/files/recover11.gno create mode 100644 gnovm/tests/files/recover12a.gno create mode 100644 gnovm/tests/files/recover12b.gno diff --git a/gnovm/tests/files/recover11.gno b/gnovm/tests/files/recover11.gno new file mode 100644 index 00000000000..a8adaff2c34 --- /dev/null +++ b/gnovm/tests/files/recover11.gno @@ -0,0 +1,38 @@ +package main + +func level3() { + panic("level3") + defer func() { + if r := recover(); r != nil { + println("recover level3 in level3") + } + }() +} + +func level2() { + defer func() { + if r := recover(); r != nil { + println("recover level3 in level2") + } + }() + defer level3() + panic("level2") +} + +func level1() { + defer func() { + if r := recover(); r != nil { + println("recover level3 in level1") + } + }() + level2() + panic("level1") +} + +func main() { + level1() +} + +// Output: +// recover level3 in level2 +// recover level3 in level1 diff --git a/gnovm/tests/files/recover12a.gno b/gnovm/tests/files/recover12a.gno new file mode 100644 index 00000000000..c8b82511ad0 --- /dev/null +++ b/gnovm/tests/files/recover12a.gno @@ -0,0 +1,26 @@ +package main + +func anotherRecover() { + if r := recover(); r != nil { + println(r) + } +} + +func main() { + defer func() { + if r := recover(); r != nil { + println(r) + } + }() + defer anotherRecover() + defer func() { + if r := recover(); r != nil { + panic("panic in defer func") + } + }() + + panic("panic in main") +} + +// Output: +// panic in defer func diff --git a/gnovm/tests/files/recover12b.gno b/gnovm/tests/files/recover12b.gno new file mode 100644 index 00000000000..4735b9eb18d --- /dev/null +++ b/gnovm/tests/files/recover12b.gno @@ -0,0 +1,28 @@ +package main + +func anotherRecover() { + if r := recover(); r != nil { + println(r) + } +} + +func main() { + defer func() { + if r := recover(); r != nil { + println(r + ": not another recover") + } + }() + defer func() { + anotherRecover() + }() + defer func() { + if r := recover(); r != nil { + panic("panic in defer func") + } + }() + + panic("panic in main") +} + +// Output: +// panic in defer func: not another recover From 2cfe2816ee5761a268c2a8b1d63dc6d0e39b00a9 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 14:04:30 -0800 Subject: [PATCH 10/21] use exception struct for panic value and frame management --- gnovm/pkg/gnolang/machine.go | 34 ++++++++++++++++++++++++---------- gnovm/pkg/gnolang/uverse.go | 13 ++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 94954a190c3..175aa168565 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -16,6 +16,19 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) +// Exception represents an panic that originates from a gno program. +type Exception struct { + // Value is the value passed to panic. + Value TypedValue + // Frame is used to reference the frame a panic occurred in so that recover() knows if the + // currently executing deferred function is able to recover from the panic. + Frame *Frame +} + +func (e Exception) Sprint(m *Machine) string { + return e.Value.Sprint(m) +} + //---------------------------------------- // Machine @@ -32,14 +45,9 @@ type Machine struct { Package *PackageValue // active package Realm *Realm // active realm Alloc *Allocator // memory allocations - Exceptions []*TypedValue // if panic'd unless recovered - - // ExceptionFrames is appended to each time a panic happens. It is used to - // reference the frame a panic occurred in so that recover() knows if the - // currently executing deferred function is able to recover from the panic. - ExceptionFrames []*Frame - NumResults int // number of results returned - Cycles int64 // number of "cpu" cycles + Exceptions []Exception + NumResults int // number of results returned + Cycles int64 // number of "cpu" cycles // Configuration CheckTypes bool // not yet used @@ -1973,8 +1981,14 @@ func (m *Machine) CheckEmpty() error { } func (m *Machine) Panic(ex TypedValue) { - m.Exceptions = append(m.Exceptions, &ex) - m.ExceptionFrames = append(m.ExceptionFrames, m.LastCallFrame(1)) + m.Exceptions = append( + m.Exceptions, + Exception{ + Value: ex, + Frame: m.LastCallFrame(1), + }, + ) + m.PanicScope++ m.PopUntilLastCallFrame(false) m.PushOp(OpPanic2) diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 62bd8b047aa..246cac1f80c 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -974,29 +974,28 @@ func UverseNode() *PackageNode { } // If the exception is out of scope, this recover can't help; return nil. - numExceptions := len(m.Exceptions) if m.PanicScope <= m.DeferPanicScope { m.PushValue(TypedValue{}) return } + exception := &m.Exceptions[len(m.Exceptions)-1] + // If the frame the exception ocurred in is not popped, it's possible that // the exception is still in scope and can be recovered. - exceptionFrame := m.ExceptionFrames[numExceptions-1] - if !exceptionFrame.Popped { + if !exception.Frame.Popped { // If the frame is not the current frame, the exception is not in scope; return nil. // This retrieves the second most recent call frame because the first most recent // is the call to recover itself. - if frame := m.LastCallFrameSafe(2); frame != nil && frame != m.ExceptionFrames[numExceptions-1] { + if frame := m.LastCallFrameSafe(2); frame != nil && frame != exception.Frame { m.PushValue(TypedValue{}) return } } - m.PushValue(*m.Exceptions[numExceptions-1]) - // This exception is removed. + m.PushValue(exception.Value) + // Recover complete; remove exceptions. m.Exceptions = nil - m.ExceptionFrames = nil }, ) return uverseNode From 9d8cbb95121e29f03ce558046781ed5fb9423783 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 17:28:43 -0800 Subject: [PATCH 11/21] fixed testing pkg recover --- gnovm/cmd/gno/testdata/gno_test/recover.txtar | 18 ++++++++++++++---- gnovm/stdlibs/testing/testing.gno | 14 ++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/gnovm/cmd/gno/testdata/gno_test/recover.txtar b/gnovm/cmd/gno/testdata/gno_test/recover.txtar index b08ce6c9593..0a87e13a2b3 100644 --- a/gnovm/cmd/gno/testdata/gno_test/recover.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/recover.txtar @@ -32,20 +32,30 @@ package recov import "testing" +type RecoverySetter struct { + value interface{} +} + +func (s *RecoverySetter) Set(v interface{}) { + s.value = v +} + func TestRecover(t *testing.T) { + var setter RecoverySetter defer func() { - err := testing.Recover() - t.Log("recovered", err) + t.Log("recovered", setter.value) }() + defer testing.Recover(&setter) panic("bad panic!") } func TestRecoverSkip(t *testing.T) { + var setter RecoverySetter defer func() { - err := testing.Recover() - t.Log("recovered", err) + t.Log("recovered", setter.value) }() + defer testing.Recover(&setter) t.Skip("skipped") panic("bad panic!") diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index 36e8e7a6955..fb13c0f39cd 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -30,12 +30,18 @@ func (s skipErr) Error() string { // (and Skip* functions). // // NOTE: Recover() is likely to be removed. -func Recover() interface{} { +func Recover(result Setter) { r := recover() - if _, ok := r.(skipErr); ok { - panic(r) + if _, ok := r.(skipErr); !ok { + result.Set(r) + return } - return r + + panic(r) +} + +type Setter interface { + Set(v interface{}) } func Short() bool { From bf4a29c8cbde39395e4c50712ac2a021288b78f4 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 17:33:20 -0800 Subject: [PATCH 12/21] removed semi-related change to be included in subsequent PR --- gnovm/pkg/gnolang/op_expressions.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 80d613a5b62..b3bf240aea1 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -280,14 +280,6 @@ func (m *Machine) doOpTypeAssert2() { xt := xv.T if t.Kind() == InterfaceKind { // is interface assert - // It's fine for the type of the value to be nil. If it is, - // then there is no way any interface type assertion can - // succeed, so we can just return false. - if xt == nil { - *tv = untypedBool(false) - return - } - if it, ok := baseOf(t).(*InterfaceType); ok { // t is Gno interface. // assert that x implements type. @@ -323,16 +315,6 @@ func (m *Machine) doOpTypeAssert2() { } } else { // is concrete assert tid := t.TypeID() - - // If the type value we are trying to assert is nil, then we can say - // the type assertion has failed. This prevents a panic. This is safe - // because we've already checked that the type we are trying to assert to - // is not an interface type. - if xt == nil { - *tv = untypedBool(false) - return - } - xtid := xt.TypeID() // assert that x is of type. same := tid == xtid From 19852f964bbf15b3ef3e7f8ada9b667d0918449c Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 20:01:26 -0800 Subject: [PATCH 13/21] updated machine scope comments --- gnovm/pkg/gnolang/machine.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 175aa168565..3e3cd4aff9d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -58,12 +58,12 @@ type Machine struct { Store Store Context interface{} - // PanicScope is incremented each time a panic occurs and decremented - // when it is recovered. + // PanicScope is incremented each time a panic occurs and is reset to + // zero when recovered. PanicScope uint // DeferPanicScope is set to the value of the defer's panic scope before - // it is executed. It is decremented after defer functions in the current - // scope have finished executing. + // it is executed. It is reset to zero after the defer functions in the + // current scope have finished executing. DeferPanicScope uint } From 20ce35438aee3283f187ec3302b83d8119b4f8c4 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 23 Feb 2024 04:14:50 +0000 Subject: [PATCH 14/21] updated machine scope comments --- gnovm/pkg/gnolang/machine.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 175aa168565..ace45b535a6 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -58,11 +58,11 @@ type Machine struct { Store Store Context interface{} - // PanicScope is incremented each time a panic occurs and decremented - // when it is recovered. + // PanicScope is incremented each time a panic occurs and is reset to + // zero when it is recovered. PanicScope uint // DeferPanicScope is set to the value of the defer's panic scope before - // it is executed. It is decremented after defer functions in the current + // it is executed. It is reset to zero after the defer functions in the current // scope have finished executing. DeferPanicScope uint } From 7baab0c180cb27ceff7d84a69c2b156ed11b43a6 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 27 Feb 2024 10:42:33 -0800 Subject: [PATCH 15/21] account for recover that runs out of frames --- gnovm/pkg/gnolang/uverse.go | 2 +- gnovm/tests/files/recover10.gno | 3 +++ gnovm/tests/files/recover10a.gno | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 gnovm/tests/files/recover10a.gno diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 246cac1f80c..3f95d700a08 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -987,7 +987,7 @@ func UverseNode() *PackageNode { // If the frame is not the current frame, the exception is not in scope; return nil. // This retrieves the second most recent call frame because the first most recent // is the call to recover itself. - if frame := m.LastCallFrameSafe(2); frame != nil && frame != exception.Frame { + if frame := m.LastCallFrameSafe(2); frame == nil || (frame != nil && frame != exception.Frame) { m.PushValue(TypedValue{}) return } diff --git a/gnovm/tests/files/recover10.gno b/gnovm/tests/files/recover10.gno index 83e6ab62c1a..16dff4d4fed 100644 --- a/gnovm/tests/files/recover10.gno +++ b/gnovm/tests/files/recover10.gno @@ -5,3 +5,6 @@ func main() { panic("ahhhhh") } + +// Error: +// ahhhhh diff --git a/gnovm/tests/files/recover10a.gno b/gnovm/tests/files/recover10a.gno new file mode 100644 index 00000000000..4e9aa9301e7 --- /dev/null +++ b/gnovm/tests/files/recover10a.gno @@ -0,0 +1,12 @@ +package main + +func main() { + defer func() { + println(recover()) + }() + + panic("ahhhhh") +} + +// Output: +// ahhhhh From cc17f30d8041b044eb4bb4fc442bfc41d34195e6 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 27 Feb 2024 12:00:48 -0800 Subject: [PATCH 16/21] removed unnecessary changes --- gnovm/pkg/gnolang/machine.go | 12 +++--------- gnovm/pkg/gnolang/op_call.go | 6 +++--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index b81874cebbb..ab295fee752 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1865,7 +1865,7 @@ func (m *Machine) lastCallFrame(n int, safe bool) *Frame { // pops the last non-call (loop) frames // and returns the last call frame (which is left on stack). -func (m *Machine) PopUntilLastCallFrame(safe bool) *Frame { +func (m *Machine) PopUntilLastCallFrame() *Frame { for i := len(m.Frames) - 1; i >= 0; i-- { fr := m.Frames[i] if fr.Func != nil || fr.GoFunc != nil { @@ -1877,13 +1877,7 @@ func (m *Machine) PopUntilLastCallFrame(safe bool) *Frame { fr.Popped = true } - // The frame wasn't found. If not in safe mode, panic to avoid any potential - // nil pointer dereferences. - if !safe { - panic("no last call frame found") - } - - // In safe mode we return nil and no frames are popped, so update the frames' popped flag. + // No frames are popped, so revert all the frames' popped flag. // This is expected to happen infrequently. for _, frame := range m.Frames { frame.Popped = false @@ -1990,7 +1984,7 @@ func (m *Machine) Panic(ex TypedValue) { ) m.PanicScope++ - m.PopUntilLastCallFrame(false) + m.PopUntilLastCallFrame() m.PushOp(OpPanic2) m.PushOp(OpReturnCallDefers) } diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 74b85a92d64..4cb59d57d3d 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -180,7 +180,7 @@ func (m *Machine) doOpCallDeferNativeBody() { // Assumes that result values are pushed onto the Values stack. func (m *Machine) doOpReturn() { - cfr := m.PopUntilLastCallFrame(true) + cfr := m.PopUntilLastCallFrame() // See if we are exiting a realm boundary. // NOTE: there are other ways to implement realm boundary transitions, // e.g. with independent Machine instances per realm for example, or @@ -212,7 +212,7 @@ func (m *Machine) doOpReturn() { // i.e. named result vars declared in func signatures. func (m *Machine) doOpReturnFromBlock() { // Copy results from block. - cfr := m.PopUntilLastCallFrame(true) + cfr := m.PopUntilLastCallFrame() ft := cfr.Func.GetType(m.Store) numParams := len(ft.Params) numResults := len(ft.Results) @@ -420,7 +420,7 @@ func (m *Machine) doOpPanic2() { m.PanicScope = 0 } else { // Keep panicking - last := m.PopUntilLastCallFrame(true) + last := m.PopUntilLastCallFrame() if last == nil { // Build exception string just as go, separated by \n\t. exs := make([]string, len(m.Exceptions)) From 5cd9dd4e0aca748c83ae1ac1ac31b0e712c68f67 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 27 Feb 2024 12:14:39 -0800 Subject: [PATCH 17/21] Rename LastCallFrame that can panic to MustLastCallFrame --- gnovm/pkg/gnolang/machine.go | 23 ++++++++++++----------- gnovm/pkg/gnolang/op_call.go | 6 +++--- gnovm/pkg/gnolang/op_exec.go | 2 +- gnovm/pkg/gnolang/uverse.go | 2 +- gnovm/stdlibs/std/native.go | 2 +- gnovm/tests/stdlibs/std/std.go | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index ab295fee752..397c0147540 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1827,20 +1827,21 @@ func (m *Machine) LastFrame() *Frame { return m.Frames[len(m.Frames)-1] } -// TODO: this function and PopUntilLastCallFrame() is used in conjunction -// spanning two disjoint operations upon return. Optimize. -// If n is 1, returns the immediately last call frame. -func (m *Machine) LastCallFrame(n int) *Frame { - return m.lastCallFrame(n, false) +// MustLastCallFrame returns the last call frame with an offset of n. It panics if the frame is not found. +func (m *Machine) MustLastCallFrame(n int) *Frame { + return m.lastCallFrame(n, true) } -// LastCallFrameSafe behaves the same as LastCallFrame, but rather than panicking, +// LastCallFrame behaves the same as MustLastCallFrame, but rather than panicking, // returns nil if the frame is not found. -func (m *Machine) LastCallFrameSafe(n int) *Frame { - return m.lastCallFrame(n, true) +func (m *Machine) LastCallFrame(n int) *Frame { + return m.lastCallFrame(n, false) } -func (m *Machine) lastCallFrame(n int, safe bool) *Frame { +// TODO: this function and PopUntilLastCallFrame() is used in conjunction +// spanning two disjoint operations upon return. Optimize. +// If n is 1, returns the immediately last call frame. +func (m *Machine) lastCallFrame(n int, mustBeFound bool) *Frame { if n == 0 { panic("n must be positive") } @@ -1856,7 +1857,7 @@ func (m *Machine) lastCallFrame(n int, safe bool) *Frame { } } - if !safe { + if mustBeFound { panic("frame not found") } @@ -1979,7 +1980,7 @@ func (m *Machine) Panic(ex TypedValue) { m.Exceptions, Exception{ Value: ex, - Frame: m.LastCallFrame(1), + Frame: m.MustLastCallFrame(1), }, ) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 4cb59d57d3d..5479ee6d5ae 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -247,7 +247,7 @@ func (m *Machine) doOpReturnFromBlock() { // deferred statements can refer to results with name // expressions. func (m *Machine) doOpReturnToBlock() { - cfr := m.LastCallFrame(1) + cfr := m.MustLastCallFrame(1) ft := cfr.Func.GetType(m.Store) numParams := len(ft.Params) numResults := len(ft.Results) @@ -260,7 +260,7 @@ func (m *Machine) doOpReturnToBlock() { } func (m *Machine) doOpReturnCallDefers() { - cfr := m.LastCallFrame(1) + cfr := m.MustLastCallFrame(1) dfr, ok := cfr.PopDefer() if !ok { // Done with defers. @@ -351,7 +351,7 @@ func (m *Machine) doOpReturnCallDefers() { func (m *Machine) doOpDefer() { lb := m.LastBlock() - cfr := m.LastCallFrame(1) + cfr := m.MustLastCallFrame(1) ds := m.PopStmt().(*DeferStmt) // Pop arguments numArgs := len(ds.Call.Args) diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 300303135ad..12e0f9e26e3 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -541,7 +541,7 @@ EXEC_SWITCH: m.PushForPointer(cs.X) case *ReturnStmt: m.PopStmt() - fr := m.LastCallFrame(1) + fr := m.MustLastCallFrame(1) ft := fr.Func.GetType(m.Store) hasDefers := 0 < len(fr.Defers) hasResults := 0 < len(ft.Results) diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 3f95d700a08..66e7ce0a1ee 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -987,7 +987,7 @@ func UverseNode() *PackageNode { // If the frame is not the current frame, the exception is not in scope; return nil. // This retrieves the second most recent call frame because the first most recent // is the call to recover itself. - if frame := m.LastCallFrameSafe(2); frame == nil || (frame != nil && frame != exception.Frame) { + if frame := m.LastCallFrame(2); frame == nil || (frame != nil && frame != exception.Frame) { m.PushValue(TypedValue{}) return } diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 044badaa308..4a2c27ed313 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -126,7 +126,7 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { ctx := m.Context.(ExecContext) return ctx.OrigCaller } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32() } func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 27ad079b4a6..a13231979da 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -85,7 +85,7 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { ctx := m.Context.(stdlibs.ExecContext) return ctx.OrigCaller } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32() } func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { From 92e7a33ad52a2d97b6025f305c0795b69229488e Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 27 Feb 2024 12:21:08 -0800 Subject: [PATCH 18/21] lint --- gnovm/pkg/gnolang/uverse.go | 2 +- gnovm/tests/files/recover12b.gno | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 66e7ce0a1ee..9f836c789fe 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -981,7 +981,7 @@ func UverseNode() *PackageNode { exception := &m.Exceptions[len(m.Exceptions)-1] - // If the frame the exception ocurred in is not popped, it's possible that + // If the frame the exception occurred in is not popped, it's possible that // the exception is still in scope and can be recovered. if !exception.Frame.Popped { // If the frame is not the current frame, the exception is not in scope; return nil. diff --git a/gnovm/tests/files/recover12b.gno b/gnovm/tests/files/recover12b.gno index 4735b9eb18d..4cc0a48b9b0 100644 --- a/gnovm/tests/files/recover12b.gno +++ b/gnovm/tests/files/recover12b.gno @@ -9,7 +9,7 @@ func anotherRecover() { func main() { defer func() { if r := recover(); r != nil { - println(r + ": not another recover") + println(r.(string) + ": not another recover") } }() defer func() { From e34a322859250dc4d440a0fa19a88d448112ab2f Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 27 Mar 2024 16:16:23 -0700 Subject: [PATCH 19/21] don't allow max cycle configuration --- gno.land/cmd/gnoland/start.go | 10 +--------- gno.land/pkg/gnoland/app.go | 5 ++--- gno.land/pkg/gnoland/node_inmemory.go | 9 +++------ gno.land/pkg/sdk/vm/common_test.go | 2 +- gno.land/pkg/sdk/vm/keeper.go | 2 +- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 2b1757706f8..7fa7de32e5c 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -36,7 +36,6 @@ type startCfg struct { chainID string genesisRemote string dataDir string - genesisMaxVMCycles int64 config string txEventStoreType string @@ -125,13 +124,6 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "replacement for '%%REMOTE%%' in genesis", ) - fs.Int64Var( - &c.genesisMaxVMCycles, - "genesis-max-vm-cycles", - 10_000_000, - "set maximum allowed vm cycles per operation. Zero means no limit.", - ) - fs.StringVar( &c.config, flagConfigFlag, @@ -254,7 +246,7 @@ func execStart(c *startCfg, io commands.IO) error { cfg.TxEventStore = txEventStoreCfg // Create application and node. - gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger) if err != nil { return fmt.Errorf("error in creating new app: %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cc15f74134e..86fb6321386 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -31,7 +31,6 @@ type AppOptions struct { GnoRootDir string SkipFailingGenesisTxs bool Logger *slog.Logger - MaxCycles int64 } func NewAppOptions() *AppOptions { @@ -78,7 +77,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") - vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) + vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir) // Set InitChainer baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.SkipFailingGenesisTxs)) @@ -123,7 +122,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // NewApp creates the GnoLand application. -func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, maxCycles int64) (abci.Application, error) { +func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger) (abci.Application, error) { var err error cfg := NewAppOptions() diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 89f222738d0..d8dc3caa485 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -25,7 +25,6 @@ type InMemoryNodeConfig struct { Genesis *bft.GenesisDoc TMConfig *tmcfg.Config SkipFailingGenesisTxs bool - GenesisMaxVMCycles int64 } // NewMockedPrivValidator generate a new key @@ -79,10 +78,9 @@ func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig { } return &InMemoryNodeConfig{ - PrivValidator: pv, - TMConfig: tm, - Genesis: genesis, - GenesisMaxVMCycles: 10_000_000, + PrivValidator: pv, + TMConfig: tm, + Genesis: genesis, } } @@ -115,7 +113,6 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, Logger: logger, GnoRootDir: cfg.TMConfig.RootDir, SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs, - MaxCycles: cfg.GenesisMaxVMCycles, DB: memdb.NewMemDB(), }) if err != nil { diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index b65757da403..ec14ae8515b 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -39,7 +39,7 @@ func setupTestEnv() testEnv { acck := authm.NewAccountKeeper(iavlCapKey, std.ProtoBaseAccount) bank := bankm.NewBankKeeper(acck) stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") - vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir, 10_000_000) + vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir) vmk.Initialize(ms.MultiCacheWrap()) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 67710be620c..2e61b02a56d 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -22,6 +22,7 @@ import ( const ( maxAllocTx = 500 * 1000 * 1000 maxAllocQuery = 1500 * 1000 * 1000 // higher limit for queries + maxCycles = 10_000_000 ) // vm.VMKeeperI defines a module interface that supports Gno @@ -55,7 +56,6 @@ func NewVMKeeper( acck auth.AccountKeeper, bank bank.BankKeeper, stdlibsDir string, - maxCycles int64, ) *VMKeeper { // TODO: create an Options struct to avoid too many constructor parameters vmk := &VMKeeper{ From 2866d9787ecbbb50f6aae821524be913cd2ff4db Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 27 Mar 2024 16:17:48 -0700 Subject: [PATCH 20/21] Revert "don't allow max cycle configuration" This reverts commit e34a322859250dc4d440a0fa19a88d448112ab2f. --- gno.land/cmd/gnoland/start.go | 10 +++++++++- gno.land/pkg/gnoland/app.go | 5 +++-- gno.land/pkg/gnoland/node_inmemory.go | 9 ++++++--- gno.land/pkg/sdk/vm/common_test.go | 2 +- gno.land/pkg/sdk/vm/keeper.go | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 7fa7de32e5c..2b1757706f8 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -36,6 +36,7 @@ type startCfg struct { chainID string genesisRemote string dataDir string + genesisMaxVMCycles int64 config string txEventStoreType string @@ -124,6 +125,13 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "replacement for '%%REMOTE%%' in genesis", ) + fs.Int64Var( + &c.genesisMaxVMCycles, + "genesis-max-vm-cycles", + 10_000_000, + "set maximum allowed vm cycles per operation. Zero means no limit.", + ) + fs.StringVar( &c.config, flagConfigFlag, @@ -246,7 +254,7 @@ func execStart(c *startCfg, io commands.IO) error { cfg.TxEventStore = txEventStoreCfg // Create application and node. - gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger) + gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) if err != nil { return fmt.Errorf("error in creating new app: %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 86fb6321386..cc15f74134e 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -31,6 +31,7 @@ type AppOptions struct { GnoRootDir string SkipFailingGenesisTxs bool Logger *slog.Logger + MaxCycles int64 } func NewAppOptions() *AppOptions { @@ -77,7 +78,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") - vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir) + vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.SkipFailingGenesisTxs)) @@ -122,7 +123,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // NewApp creates the GnoLand application. -func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger) (abci.Application, error) { +func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, maxCycles int64) (abci.Application, error) { var err error cfg := NewAppOptions() diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index d8dc3caa485..89f222738d0 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -25,6 +25,7 @@ type InMemoryNodeConfig struct { Genesis *bft.GenesisDoc TMConfig *tmcfg.Config SkipFailingGenesisTxs bool + GenesisMaxVMCycles int64 } // NewMockedPrivValidator generate a new key @@ -78,9 +79,10 @@ func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig { } return &InMemoryNodeConfig{ - PrivValidator: pv, - TMConfig: tm, - Genesis: genesis, + PrivValidator: pv, + TMConfig: tm, + Genesis: genesis, + GenesisMaxVMCycles: 10_000_000, } } @@ -113,6 +115,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, Logger: logger, GnoRootDir: cfg.TMConfig.RootDir, SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs, + MaxCycles: cfg.GenesisMaxVMCycles, DB: memdb.NewMemDB(), }) if err != nil { diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index ec14ae8515b..b65757da403 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -39,7 +39,7 @@ func setupTestEnv() testEnv { acck := authm.NewAccountKeeper(iavlCapKey, std.ProtoBaseAccount) bank := bankm.NewBankKeeper(acck) stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") - vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir) + vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir, 10_000_000) vmk.Initialize(ms.MultiCacheWrap()) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 2e61b02a56d..67710be620c 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -22,7 +22,6 @@ import ( const ( maxAllocTx = 500 * 1000 * 1000 maxAllocQuery = 1500 * 1000 * 1000 // higher limit for queries - maxCycles = 10_000_000 ) // vm.VMKeeperI defines a module interface that supports Gno @@ -56,6 +55,7 @@ func NewVMKeeper( acck auth.AccountKeeper, bank bank.BankKeeper, stdlibsDir string, + maxCycles int64, ) *VMKeeper { // TODO: create an Options struct to avoid too many constructor parameters vmk := &VMKeeper{ From d1ecd5e91385067a5bc2e66db02d4e4f4de6fdc4 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 29 Mar 2024 08:25:15 -0700 Subject: [PATCH 21/21] fixed typos --- gnovm/pkg/gnolang/frame.go | 2 +- gnovm/pkg/gnolang/machine.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 078448b550e..c808fc111b0 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -89,6 +89,6 @@ type Defer struct { // PanicScope is set to the value of the Machine's PanicScope when the // defer is created. The PanicScope of the Machine is incremented each time - // a panic occurrs and is decremented each time a panic is recovered. + // a panic occurs and is decremented each time a panic is recovered. PanicScope uint } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 397c0147540..ea7be1d1f22 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -16,7 +16,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) -// Exception represents an panic that originates from a gno program. +// Exception represents a panic that originates from a gno program. type Exception struct { // Value is the value passed to panic. Value TypedValue