diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index 421ac6528c9..805763ccb23 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -21,6 +21,10 @@ func CurrentRealmPath() string { return std.CurrentRealm().PkgPath() } +func PrintCurrentRealmPath() { + println(CurrentRealmPath()) +} + var initOrigCaller = std.GetOrigCaller() func InitOrigCaller() std.Address { diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 2ac1027eb32..9262405971e 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -88,11 +88,12 @@ 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 + Receiver TypedValue // for methods + 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 @@ -100,6 +101,36 @@ type Defer struct { PanicScope uint } +//---------------------------------------- +// Exception + +// Exception represents a panic that originates from a gno program. +type Exception struct { + // Value is the value passed to panic. + Value TypedValue + // Frames is a snapshot of the Machine frames at the time panic() is called. + // It is used to determine whether recover() can be called (it may only be + // called directly by a referred function) and to determine whether the + // current execution of a deferred function is happening in the context of + // a panic. + Frames []*Frame + + Stacktrace Stacktrace +} + +func (e Exception) Sprint(m *Machine) string { + return e.Value.Sprint(m) +} + +// UnhandledPanicError represents an error thrown when a panic is not handled in the realm. +type UnhandledPanicError struct { + Descriptor string // Description of the unhandled panic. +} + +func (e UnhandledPanicError) Error() string { + return e.Descriptor +} + type StacktraceCall struct { Stmt Stmt Frame *Frame diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 24f94abc10b..4787959f1eb 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -8,6 +8,7 @@ import ( "io" "os" "reflect" + "slices" "strings" "sync" "testing" @@ -18,30 +19,6 @@ import ( "github.com/gnolang/overflow" ) -// Exception represents a 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 - - Stacktrace Stacktrace -} - -func (e Exception) Sprint(m *Machine) string { - return e.Value.Sprint(m) -} - -// UnhandledPanicError represents an error thrown when a panic is not handled in the realm. -type UnhandledPanicError struct { - Descriptor string // Description of the unhandled panic. -} - -func (e UnhandledPanicError) Error() string { - return e.Descriptor -} - //---------------------------------------- // Machine @@ -72,13 +49,6 @@ type Machine struct { Store Store Context interface{} GasMeter store.GasMeter - // 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 reset to zero after the defer functions in the current - // scope have finished executing. - DeferPanicScope uint } // NewMachine initializes a new gno virtual machine, acting as a shorthand @@ -984,30 +954,29 @@ type Op uint8 const ( /* Control operators */ - OpInvalid Op = 0x00 // invalid - OpHalt Op = 0x01 // halt (e.g. last statement) - OpNoop Op = 0x02 // no-op - OpExec Op = 0x03 // exec next statement - OpPrecall Op = 0x04 // sets X (func) to frame - OpCall Op = 0x05 // call(Frame.Func, [...]) - OpCallNativeBody Op = 0x06 // call body is native - OpReturn Op = 0x07 // return ... - OpReturnFromBlock Op = 0x08 // return results (after defers) - OpReturnToBlock Op = 0x09 // copy results to block (before defer) - OpDefer Op = 0x0A // defer call(X, [...]) - OpCallDeferNativeBody Op = 0x0B // call body is native - OpGo Op = 0x0C // go call(X, [...]) - OpSelect Op = 0x0D // exec next select case - OpSwitchClause Op = 0x0E // exec next switch clause - OpSwitchClauseCase Op = 0x0F // exec next switch clause case - OpTypeSwitch Op = 0x10 // exec type switch clauses (all) - OpIfCond Op = 0x11 // eval cond - OpPopValue Op = 0x12 // pop X - OpPopResults Op = 0x13 // pop n call results - OpPopBlock Op = 0x14 // pop block NOTE breaks certain invariants. - OpPopFrameAndReset Op = 0x15 // pop frame and reset. - OpPanic1 Op = 0x16 // pop exception and pop call frames. - OpPanic2 Op = 0x17 // pop call frames. + OpInvalid Op = 0x00 // invalid + OpHalt Op = 0x01 // halt (e.g. last statement) + OpNoop Op = 0x02 // no-op + OpExec Op = 0x03 // exec next statement + OpPrecall Op = 0x04 // sets X (func) to frame + OpCall Op = 0x05 // call(Frame.Func, [...]) + OpCallNativeBody Op = 0x06 // call body is native + OpReturn Op = 0x07 // return ... + OpReturnFromBlock Op = 0x08 // return results (after defers) + OpReturnToBlock Op = 0x09 // copy results to block (before defer) + OpDefer Op = 0x0A // defer call(X, [...]) + OpGo Op = 0x0B // go call(X, [...]) + OpSelect Op = 0x0C // exec next select case + OpSwitchClause Op = 0x0D // exec next switch clause + OpSwitchClauseCase Op = 0x0E // exec next switch clause case + OpTypeSwitch Op = 0x0F // exec type switch clauses (all) + OpIfCond Op = 0x10 // eval cond + OpPopValue Op = 0x11 // pop X + OpPopResults Op = 0x12 // pop n call results + OpPopBlock Op = 0x13 // pop block NOTE breaks certain invariants. + OpPopFrameAndReset Op = 0x14 // pop frame and reset. + OpPanic1 Op = 0x15 // pop exception and pop call frames. + OpPanic2 Op = 0x16 // pop call frames. /* Unary & binary operators */ OpUpos Op = 0x20 // + (unary) @@ -1295,9 +1264,6 @@ func (m *Machine) Run() { case OpPanic2: m.incrCPU(OpCPUPanic2) m.doOpPanic2() - case OpCallDeferNativeBody: - m.incrCPU(OpCPUCallDeferNativeBody) - m.doOpCallDeferNativeBody() case OpGo: m.incrCPU(OpCPUGo) panic("not yet implemented") @@ -2140,16 +2106,18 @@ func (m *Machine) CheckEmpty() error { } func (m *Machine) Panic(ex TypedValue) { + // Skip the last frame, as it's the one of the panic() (or native) + // function call. + panicFrames := slices.Clone(m.Frames[:len(m.Frames)-1]) m.Exceptions = append( m.Exceptions, Exception{ Value: ex, - Frame: m.MustLastCallFrame(1), + Frames: panicFrames, Stacktrace: m.Stacktrace(), }, ) - 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 15531ec610d..8284e08e86d 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + "slices" "strings" ) @@ -173,11 +174,6 @@ func (m *Machine) doOpCallNativeBody() { m.LastFrame().Func.nativeBody(m) } -func (m *Machine) doOpCallDeferNativeBody() { - fv := m.PopValue().V.(*FuncValue) - fv.nativeBody(m) -} - // Assumes that result values are pushed onto the Values stack. func (m *Machine) doOpReturn() { cfr := m.PopUntilLastCallFrame() @@ -264,88 +260,45 @@ func (m *Machine) doOpReturnCallDefers() { dfr, ok := cfr.PopDefer() if !ok { // Done with defers. - m.DeferPanicScope = 0 m.ForcePopOp() if len(m.Exceptions) > 0 { - // In a state of panic (not return). - // Pop the containing function frame. - m.PopFrame() + exceptionFrames := m.Exceptions[len(m.Exceptions)-1].Frames + if slices.Contains(exceptionFrames, cfr) { + // In a state of panic (not return). + // Pop the containing function frame. + m.PopFrame() + } } return } - m.DeferPanicScope = dfr.PanicScope - - // Call last deferred call. - // NOTE: the following logic is largely duplicated in doOpCall(). - // Convert if variadic argument. + // Push onto value stack: function, receiver, arguments. if dfr.Func != nil { fv := dfr.Func ft := fv.GetType(m.Store) - pts := ft.Params - numParams := len(ft.Params) - // Create new block scope for defer. - clo := dfr.Func.GetClosure(m.Store) - b := m.Alloc.NewBlock(fv.GetSource(m.Store), clo) - m.PushBlock(b) - if fv.nativeBody == nil { - fbody := fv.GetBodyFromSource(m.Store) - // Exec body. - b.bodyStmt = bodyStmt{ - Body: fbody, - BodyLen: len(fbody), - NextBodyIndex: -2, - } - m.PushOp(OpBody) - m.PushStmt(b.GetBodyStmt()) - } else { - // Call native function. - m.PushValue(TypedValue{ - T: ft, - V: fv, - }) - m.PushOp(OpCallDeferNativeBody) - } - if ft.HasVarg() { - numArgs := len(dfr.Args) - nvar := numArgs - (numParams - 1) - if dfr.Source.Call.Varg { - if debug { - if nvar != 1 { - panic("should not happen") - } - } - // Do nothing, last arg type is already slice type - // called with form fncall(?, vargs...) - } else { - // Convert last nvar to slice. - vart := pts[len(pts)-1].Type.(*SliceType) - vargs := make([]TypedValue, nvar) - copy(vargs, dfr.Args[numArgs-nvar:numArgs]) - varg := m.Alloc.NewSliceFromList(vargs) - dfr.Args = dfr.Args[:numArgs-nvar] - dfr.Args = append(dfr.Args, TypedValue{ - T: vart, - V: varg, - }) - } - } - copy(b.Values, dfr.Args) + m.PushValue(TypedValue{ + T: ft, + V: fv, + }) } else if dfr.GoFunc != nil { - fv := dfr.GoFunc - ptvs := dfr.Args - prvs := make([]reflect.Value, len(ptvs)) - for i := 0; i < len(prvs); i++ { - // TODO consider when declared types can be - // converted, e.g. fmt.Println. See GoValue. - prvs[i] = gno2GoValue(&ptvs[i], reflect.Value{}) - } - // Call and ignore results. - fv.Value.Call(prvs) - // Cleanup. - m.NumResults = 0 + m.PushValue(TypedValue{}) // NOTE: this is ignored by doOpCallGoNative } else { - panic("should not happen") + panic("unexpected Defer in stack with nil Func and GoFunc") + } + if dfr.Receiver.T != nil { + m.PushValue(dfr.Receiver) + } + for _, arg := range dfr.Args { + m.PushValue(arg) + } + + // Push op and frame. + if dfr.Func != nil { + m.PushFrameCall(&dfr.Source.Call, dfr.Func, dfr.Receiver) + m.PushOp(OpCall) + } else { // GoFunc != nil + m.PushFrameGoNative(&dfr.Source.Call, dfr.GoFunc) + m.PushOp(OpCallGoNative) } } @@ -365,11 +318,10 @@ func (m *Machine) doOpDefer() { case *FuncValue: // TODO what if value is NativeValue? cfr.PushDefer(Defer{ - Func: cv, - Args: args, - Source: ds, - Parent: lb, - PanicScope: m.PanicScope, + Func: cv, + Args: args, + Source: ds, + Parent: lb, }) case *BoundMethodValue: if debug { @@ -382,23 +334,19 @@ func (m *Machine) doOpDefer() { rt.String())) } } - args2 := make([]TypedValue, len(args)+1) - args2[0] = cv.Receiver - copy(args2[1:], args) cfr.PushDefer(Defer{ - Func: cv.Func, - Args: args2, - Source: ds, - Parent: lb, - PanicScope: m.PanicScope, + Func: cv.Func, + Args: args, + Receiver: cv.Receiver, + Source: ds, + Parent: lb, }) case *NativeValue: cfr.PushDefer(Defer{ - GoFunc: cv, - Args: args, - Source: ds, - Parent: lb, - PanicScope: m.PanicScope, + GoFunc: cv, + Args: args, + Source: ds, + Parent: lb, }) default: panic("should not happen") @@ -417,18 +365,21 @@ func (m *Machine) doOpPanic2() { // Recovered from panic m.PushOp(OpReturnFromBlock) m.PushOp(OpReturnCallDefers) - m.PanicScope = 0 } else { // Keep panicking last := m.PopUntilLastCallFrame() if last == nil { // Build exception string just as go, separated by \n\t. - exs := make([]string, len(m.Exceptions)) + var bld strings.Builder for i, ex := range m.Exceptions { - exs[i] = ex.Sprint(m) + if i > 0 { + bld.WriteString("\n\t") + } + bld.WriteString("panic: ") + bld.WriteString(ex.Sprint(m)) } panic(UnhandledPanicError{ - Descriptor: strings.Join(exs, "\n\t"), + Descriptor: bld.String(), }) } m.PushOp(OpPanic2) diff --git a/gnovm/pkg/gnolang/op_string.go b/gnovm/pkg/gnolang/op_string.go index db52b4ff67b..b64bebf22a5 100644 --- a/gnovm/pkg/gnolang/op_string.go +++ b/gnovm/pkg/gnolang/op_string.go @@ -19,19 +19,18 @@ func _() { _ = x[OpReturnFromBlock-8] _ = x[OpReturnToBlock-9] _ = x[OpDefer-10] - _ = x[OpCallDeferNativeBody-11] - _ = x[OpGo-12] - _ = x[OpSelect-13] - _ = x[OpSwitchClause-14] - _ = x[OpSwitchClauseCase-15] - _ = x[OpTypeSwitch-16] - _ = x[OpIfCond-17] - _ = x[OpPopValue-18] - _ = x[OpPopResults-19] - _ = x[OpPopBlock-20] - _ = x[OpPopFrameAndReset-21] - _ = x[OpPanic1-22] - _ = x[OpPanic2-23] + _ = x[OpGo-11] + _ = x[OpSelect-12] + _ = x[OpSwitchClause-13] + _ = x[OpSwitchClauseCase-14] + _ = x[OpTypeSwitch-15] + _ = x[OpIfCond-16] + _ = x[OpPopValue-17] + _ = x[OpPopResults-18] + _ = x[OpPopBlock-19] + _ = x[OpPopFrameAndReset-20] + _ = x[OpPanic1-21] + _ = x[OpPanic2-22] _ = x[OpUpos-32] _ = x[OpUneg-33] _ = x[OpUnot-34] @@ -117,7 +116,7 @@ func _() { } const ( - _Op_name_0 = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpReturnOpReturnFromBlockOpReturnToBlockOpDeferOpCallDeferNativeBodyOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2" + _Op_name_0 = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpReturnOpReturnFromBlockOpReturnToBlockOpDeferOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2" _Op_name_1 = "OpUposOpUnegOpUnotOpUxor" _Op_name_2 = "OpUrecvOpLorOpLandOpEqlOpNeqOpLssOpLeqOpGtrOpGeqOpAddOpSubOpBorOpXorOpMulOpQuoOpRemOpShlOpShrOpBandOpBandn" _Op_name_3 = "OpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvert" @@ -129,7 +128,7 @@ const ( ) var ( - _Op_index_0 = [...]uint16{0, 9, 15, 21, 27, 36, 42, 58, 66, 83, 98, 105, 126, 130, 138, 152, 170, 182, 190, 200, 212, 222, 240, 248, 256} + _Op_index_0 = [...]uint8{0, 9, 15, 21, 27, 36, 42, 58, 66, 83, 98, 105, 109, 117, 131, 149, 161, 169, 179, 191, 201, 219, 227, 235} _Op_index_1 = [...]uint8{0, 6, 12, 18, 24} _Op_index_2 = [...]uint8{0, 7, 12, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88, 93, 99, 106} _Op_index_3 = [...]uint8{0, 6, 15, 23, 31, 41, 48, 54, 59, 72, 85, 99, 113, 123, 133, 144, 152, 163, 172, 181} @@ -142,7 +141,7 @@ var ( func (i Op) String() string { switch { - case i <= 23: + case i <= 22: return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]] case 32 <= i && i <= 35: i -= 32 diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 38ccdddea85..ae1e5942a98 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + "slices" "strings" ) @@ -982,24 +983,11 @@ func UverseNode() *PackageNode { return } - // If the exception is out of scope, this recover can't help; return nil. - if m.PanicScope <= m.DeferPanicScope { - m.PushValue(TypedValue{}) - return - } - exception := &m.Exceptions[len(m.Exceptions)-1] - // 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. - // This retrieves the second most recent call frame because the first most recent - // is the call to recover itself. - if frame := m.LastCallFrame(2); frame == nil || (frame != nil && frame != exception.Frame) { - m.PushValue(TypedValue{}) - return - } + if frame := m.LastCallFrame(3); !slices.Contains(exception.Frames, frame) { + m.PushValue(TypedValue{}) + return } m.PushValue(exception.Value) diff --git a/gnovm/tests/files/defer10.gno b/gnovm/tests/files/defer10.gno new file mode 100644 index 00000000000..2bc78bac625 --- /dev/null +++ b/gnovm/tests/files/defer10.gno @@ -0,0 +1,21 @@ +package main + +func Swap(values [3]int) { + values[0] = 1337 + println(values) +} + +func main() { + values := [3]int{1, 2, 3} + defer func() { + println(values) + }() + defer Swap(values) + + println("test") +} + +// Output: +// test +// array[(1337 int),(2 int),(3 int)] +// array[(1 int),(2 int),(3 int)] diff --git a/gnovm/tests/files/panic0.gno b/gnovm/tests/files/panic0.gno index 06460ca9d07..a8303f09f7f 100644 --- a/gnovm/tests/files/panic0.gno +++ b/gnovm/tests/files/panic0.gno @@ -10,4 +10,4 @@ func main() { // main/files/panic0.gno:4 // Error: -// wtf +// panic: wtf diff --git a/gnovm/tests/files/panic0b.gno b/gnovm/tests/files/panic0b.gno index 55a7b21015a..9a3be4dae59 100644 --- a/gnovm/tests/files/panic0b.gno +++ b/gnovm/tests/files/panic0b.gno @@ -28,6 +28,6 @@ func f() { // main/files/panic0b.gno:4 // Error: -// first -// second -// third +// panic: first +// panic: second +// panic: third diff --git a/gnovm/tests/files/panic1.gno b/gnovm/tests/files/panic1.gno index 235ba4a0b34..0847884a9fc 100644 --- a/gnovm/tests/files/panic1.gno +++ b/gnovm/tests/files/panic1.gno @@ -28,4 +28,4 @@ func main() { // main/files/panic1.gno:22 // Error: -// here +// panic: here diff --git a/gnovm/tests/files/recover10.gno b/gnovm/tests/files/recover10.gno index de083a322a4..1a64e564a14 100644 --- a/gnovm/tests/files/recover10.gno +++ b/gnovm/tests/files/recover10.gno @@ -12,4 +12,4 @@ func main() { // main/files/recover10.gno:6 // Error: -// ahhhhh +// panic: ahhhhh diff --git a/gnovm/tests/files/recover1b.gno b/gnovm/tests/files/recover1b.gno index 978b4988329..71f16cc0604 100644 --- a/gnovm/tests/files/recover1b.gno +++ b/gnovm/tests/files/recover1b.gno @@ -16,4 +16,4 @@ func main() { // main/files/recover1b.gno:8 // Error: -// other panic +// panic: other panic diff --git a/gnovm/tests/files/recover8.gno b/gnovm/tests/files/recover8.gno index d144a4f986f..4507d2a35fb 100644 --- a/gnovm/tests/files/recover8.gno +++ b/gnovm/tests/files/recover8.gno @@ -28,4 +28,4 @@ func main() { // main/files/recover8.gno:20 // Error: -// do something panic +// panic: do something panic diff --git a/gnovm/tests/files/std5_stdlibs.gno b/gnovm/tests/files/std5_stdlibs.gno index 4afa09da8d3..534a1483882 100644 --- a/gnovm/tests/files/std5_stdlibs.gno +++ b/gnovm/tests/files/std5_stdlibs.gno @@ -21,4 +21,4 @@ func main() { // main/files/std5_stdlibs.gno:10 // Error: -// frame not found +// panic: frame not found diff --git a/gnovm/tests/files/std8_stdlibs.gno b/gnovm/tests/files/std8_stdlibs.gno index ab5e15bd618..8872a067a1c 100644 --- a/gnovm/tests/files/std8_stdlibs.gno +++ b/gnovm/tests/files/std8_stdlibs.gno @@ -35,4 +35,4 @@ func main() { // main/files/std8_stdlibs.gno:21 // Error: -// frame not found +// panic: frame not found diff --git a/gnovm/tests/files/typeassert1.gno b/gnovm/tests/files/typeassert1.gno index f6609a3d18c..5f7a7d1424f 100644 --- a/gnovm/tests/files/typeassert1.gno +++ b/gnovm/tests/files/typeassert1.gno @@ -15,4 +15,4 @@ func main() { // main/files/typeassert1.gno:9 // Error: -// interface conversion: interface is nil, not main.A +// panic: interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert2a.gno b/gnovm/tests/files/typeassert2a.gno index bfbd24d38bd..d840138bae6 100644 --- a/gnovm/tests/files/typeassert2a.gno +++ b/gnovm/tests/files/typeassert2a.gno @@ -17,4 +17,4 @@ func main() { // main/files/typeassert2a.gno:10 // Error: -// interface conversion: interface is nil, not main.A +// panic: interface conversion: interface is nil, not main.A diff --git a/gnovm/tests/files/typeassert9.gno b/gnovm/tests/files/typeassert9.gno index 6ea072661c1..76f3ce79adc 100644 --- a/gnovm/tests/files/typeassert9.gno +++ b/gnovm/tests/files/typeassert9.gno @@ -22,4 +22,4 @@ func main() { // main/files/typeassert9.gno:16 // Error: -// interface conversion: interface is nil, not main.Writer +// panic: interface conversion: interface is nil, not main.Writer diff --git a/gnovm/tests/files/zrealm_std7_stdlibs.gno b/gnovm/tests/files/zrealm_std7_stdlibs.gno new file mode 100644 index 00000000000..60d5bcfb03d --- /dev/null +++ b/gnovm/tests/files/zrealm_std7_stdlibs.gno @@ -0,0 +1,15 @@ +// PKGPATH: gno.land/r/std_test +package std_test + +import ( + "gno.land/r/demo/tests" +) + +func main() { + defer tests.PrintCurrentRealmPath() + tests.PrintCurrentRealmPath() +} + +// Output: +// gno.land/r/demo/tests +// gno.land/r/demo/tests