diff --git a/docs/resources/gno-interrealm.md b/docs/resources/gno-interrealm.md
new file mode 100644
index 00000000000..74aed39aeaa
--- /dev/null
+++ b/docs/resources/gno-interrealm.md
@@ -0,0 +1,108 @@
+# Interrealm Specification
+
+Gno extends Go's type system with a interrealm rules. These rules can be
+checked during the static type-checking phase (but at the moment they are
+partially dependent on runtime checks).
+
+All functions in Gno execute under a realm context as determined by the call
+stack. Objects that reside in a realm can only be modified if the realm context
+matches.
+
+A function declared in p packages when called:
+
+ * inherits the last realm for package declared functions and closures.
+ * inherits the last realm when a method is called on unreal receiver.
+ * implicitly crosses to the receiver's resident realm when a method of the
+ receiver is called. The receiver's realm is also called the "borrow realm".
+
+A function declared in a realm package when called:
+
+ * explicitly crosses to the realm in which the function is declared if the
+ function begins with a `crossing()` statement. The new realm is called the
+ "current realm".
+ * otherwise follows the same rules as for p packages.
+
+The `crossing()` statement must be the first statement of a function's body.
+It is illegal to use anywhere else, and cannot be used in p packages. Functions
+that begin with the `crossing()` statement are called "crossing" functions".
+
+A crossing function declared in a realm different than the last explicitly
+crossed realm *must* be called like `cross(fn)(...)`. That is, functions of
+calls that result in explicit realm crossings must be wrapped with `cross()`.
+
+`std.CurrentRealm()` returns the current realm last explicitly crossed to.
+
+`std.PreviousRealm()` returns the realm explicitly crossed to before that.
+
+A crossing function declared in the same realm package as the callee may be
+called normally OR like `cross(fn)(...)`. When called normally there will be no
+realm crossing, but when called like `cross(fn)(...)` there is technically a
+realm crossing and the current realm and previous realm returned are the same.
+
+The current realm and previous realm do not depend on any implicit crossing to
+the receiver's borrowed/storage realm even if the borrowed realm is the last
+realm of the call stack equal to `m.Realm`. In other words `std.CurrentRealm()`
+may be different than `m.Realm` (the borrow realm) when a receiver is called on
+a foreign object.
+
+Calls of methods on receivers residing in realms different than the current
+realm must not be called like `cross(fn)(...)` if the method is not a
+crossing function itself, and vice versa. Or it could be said that implicit
+crossing is not real realm crossing. (When you sign a document with someone
+else's pen it is still your signature; signature:pen :: current:borrowed.
+
+A crossing method declared in a realm cannot modify the receiver if the object
+resides in a different realm. However not all methods are required to be
+crossing methods, and crossing methods may still read the state of the
+receiver (and in general anything reacheable is readible).
+
+New unreal objects reachable from the borrowed realm (or current realm if there
+was no method call that borrowed) become persisted in the borrowed realm (or
+current realm) upon finalization of the foreign object's method (or function).
+(When you put an unlabeled photo in someone else's scrap book the photo now
+belongs to the other person). In the future we will introduce an `attach()`
+function to prevent a new unreal object from being taken.
+
+MsgCall can only call (realm) crossing functions.
+
+MsgRun will run a file's `main()` function in the user's realm and may call
+both crossing functions and non-crossing functions.
+
+A realm package's initialization (including init() calls) execute with current
+realm of itself, and it `std.PreviousRealm()` will panic unless the call stack
+includes a crossing function called like `cross(fn)(...)`.
+
+### Justifications
+
+P package code should behave the same even when copied verbatim in a realm
+package.
+
+Realm crossing with respect to `std.CurrentRealm()` and `std.PreviousRealm()`
+is important enough to be explicit and warrants type-checking.
+
+A crossing function of a realm should be able to call another crossing function
+of the same realm without necessarily explicitly crossing realms.
+
+Sometimes the previous realm and current realm must be the same realm, such as
+when a realm consumes a service that it offers to external realms and users.
+
+A method should be able to modify the receiver and associated objects of the
+same borrowed realm.
+
+A method should be able to create new objects that reside in the same realm by
+default in order to maintain storage realm consistency and encapsulation and
+reduce fragmentation.
+
+In the future an object may be migrated from one realm to another when it loses
+all references in one realm and gains references in another. The behavior of
+the object should not change after migration because this type of migration is
+implicit and generally not obvious without more language features.
+
+Code declared in p packages (or declared in "immutable" realm packages) can
+help different realms enforce contracts trustlessly, even those that involve
+the caller's current realm. Otherwise two mutable (upgreadeable) realms cannot
+export trust unto the chain because functions declared in those two realms can
+be upgraded.
+
+Both `crossing()` and `cross(fn)(...)` statements may become special syntax in
+future Gno versions.
diff --git a/docs/resources/gno-memory-model.md b/docs/resources/gno-memory-model.md
new file mode 100644
index 00000000000..4b00c3b3f6d
--- /dev/null
+++ b/docs/resources/gno-memory-model.md
@@ -0,0 +1,218 @@
+# Gno Memory Model
+
+## The Typed Value
+
+```go
+type TypedValue struct {
+ T Type
+ V Value
+ N [8]byte
+}
+```
+
+Both `Type` and `Value` are Go interface values. Go does not support union
+types, so primitive values like bools and ints are stored in the N field for
+performance.
+
+All values in Gno are stored represented as (type, value) tuples. Vars in
+scope blocks, fields in structs, elements in arrays, keys and values of maps
+are all respresented by the same `TypedValue` struct.
+
+This tuple representation lets the Gno VM implementation logic be simpler with
+less code. Reading and writing values are the same whether the static type of
+the value is an interface or something concrete with no special logic for
+memory optimizations which is less relevant in a massive multi-user
+transactional operating system where most of the data resides in disk anyways.
+
+Another benefit is that it promotes the development of new types of client
+interfaces that can make use of the type information for object display and
+interaction. The original vision of Tim Burners Lee's HTML DOM based World Wide
+Web with restful HTTP requests like GET and POST has been steamrolled over by
+continuous developments in HTML, CSS, Javascript, and the browser. The internet
+is alive but the World Wide Web is dead. Gno is a reboot of the original vision
+of the Web but better because everything is integrated based on a singular
+well designed object-oriented language. Instead of POST requests Gno has
+typed method calls. All values are annotated with their types making the
+environment REST-ful to the core.
+
+> REST (Representational State Transfer) is a software architectural style that
+> was created to describe the design and guide the development of the
+> architecture for the World Wide Web. REST defines a set of constraints for
+> how the architecture of a distributed, Internet-scale hypermedia system, such
+> as the Web, should behave. - wikipedia
+
+While the Gno VM implements the memory model described here in the most
+straightforward way, future alternative implementations may represent values
+differently in machine memory even while conforming to the spec as implemented
+by the Gno VM.
+
+
+## Objects and Values
+
+There are many types of Values, and some of these value types are also Objects.
+The types below are all values and those that are bolded are also objects.
+
+ * Primitive // bool, uint, int, uint8, ... int64
+ * StringValue
+ * BigintValue // only used for constant expressions
+ * BigdecValue // only used for constant expressions
+ * DataByteValue // invisible type for byte array optimization
+ * PointerValue // base is always an object
+ * **ArrayValue**
+ * SliceValue
+ * **StructValue**
+ * **FuncValue**
+ * **MapValue**
+ * **BoundMethodValue** // func & receiver
+ * TypeValue
+ * PackageValue
+ * **BlockValue** // for package, file, if, range, switch, func
+ * RefValue // reference to an object stored in disk
+ * **HeapItemValue** // invisible type for loopvars and closure captures
+
+
+## Pointers
+
+```go
+type PointerValue struct {
+ TV *TypedValue // escape val if pointer to var.
+ Base Value // array/struct/block, or heapitem.
+ Index int // list/fields/values index, or -1 or -2 (see below).
+}
+```
+
+The pointer is a reference to a typed value slot in an array, struct, block,
+or heap item. It is also used internally in the VM for assigning to slots.
+Even internally the Base is used to tell the realm finalizer when the base
+has been updated.
+
+## Blocks and Heap Items
+
+All statements enclosed in {} parentheses will allocate a new block
+and push it onto the block stack of the VM. The size of the block
+is determined by the number of variables declared in the block statement.
+
+```
+type BlockValue struct {
+ ObjectInfo
+ Source BlockNode
+ Values []TypedValue
+ Parent Value // Parent block if any, or RefValue{} to one.
+ Blank TypedValue // Captures "_" underscore names.
+ bodyStmt bodyStmt // Holds a pointer to the current statement.
+}
+```
+
+The following Gno AST nodes when executed will create a new block:
+
+ * FuncLitStmt
+ * BlockStmt // a list of statements wrapped in {}
+ * ForStmt
+ * IfCaseStmt
+ * RangeStmt
+ * SwitchCaseStmt
+ * FuncDecl
+ * FileNode
+ * PackageNode
+
+`IfStmt`s and `SwitchStmt`s also produce faux blocks that get merged onto the
+following `IfCaseStmt` and `SwitchCaseStmt` respectively, but this is an
+invisible implementation detail and the behavior may change.
+
+Heap items are only used in blocks. Conceptually they are an object container
+around a singleton typed value slot. It is not visible to the gno developer
+but it is important to understand how they work when inspecting the block
+space.
+
+```go
+func Example(arg int) (res *int) {
+ var x int = arg + 1
+ return &x
+}
+```
+
+The above code when executed will first produce the following block:
+
+```
+BlockValue{
+ ...
+ Source: <*FuncDecl node>,
+ Values: [
+ {T: nil, V: nil}, // 'arg' parameter
+ {T: nil, V: nil}, // 'res' result
+ {T: HeapItemType{},
+ V: &HeapItemValue{{T: nil, V: nil}}, // 'x' variable
+ ],
+ ...
+}
+```
+
+In the above example the third slot for `x` is not initialized to the zero
+value of a typed value slot, but rather it is prefilled with a heap item.
+
+Variables declared in a closure or passed by reference are first discovered and
+marked as such from the preprocessor, and NewBlock() will prepopulate these
+slots with `*HeapItemValues`. When a `*HeapItemValue` is present in a block
+slot it is not written over but instead the value is written into the heap
+item's slot.
+
+When the example code executes `return &x` instead of returning a
+`PointerValue` with `.Base` set to the `BlockValue` and `.Index` of 2, it sets
+`.Base` to the `*HeapItemValue` with `.Index` of 0 since a heap item only
+contains one slot. The pointer's `.TV` is set to the single slot of of the heap
+item. This way the when the pointer is used later in another transaction there
+is no need to load the whole original block value, but rather a single heap
+item object. If `Example()` returned only `x` rather than a pointer `&x` it
+would not be initialized with a heap item for the slot.
+
+```go
+func Example2(arg int) (res func()) {
+ var x int = arg + 1
+ return func() {
+ println(x)
+ }
+}
+```
+
+The above example illustrates another use for heap items. Here we don't
+reference `x`, but it is captured by the anonymous function literal (closure).
+At runtime the closure `*FuncValue` captures the heap item object such that the
+closure does not depend on the block at all.
+
+Variables declared at the package (global) level may also be referred to by
+pointer in anonymous functions. In the future we will allow limited upgrading
+features for mutable realm packages (e.g. the ability to add new functions or
+replace or "swizzle" existing ones), so all package level declared variables
+are wrapped in heap item objects.
+
+Since all global package values referenced closures can be captured as heap
+objects, the execution and persistence of a closure function value does not
+depend on any parent blocks. (Omitted here is how references to package
+level declared functions and methods are replaced by a selector expression
+on the package itself; otherwise closures would still in general depend
+on their parent blocks).
+
+## Loopvars
+
+Go1.22 introduced loopvars to reduce programmer errors. Gno uses
+heap items to implement loopvars.
+
+```go
+for _, v := range values {
+ saveClosure(func() {
+ fmt.Println(v)
+ })
+}
+```
+
+The Gno VM does something special for loopvars. Instead of assigning the new
+value `v` to the same slot, or even the same heap item object's slot, it
+replaces the existing heap item object with a new one. This allows the closure
+to capture a new heap item object with every iteration. This is called
+a heap definition.
+
+The behavior is applied for implicit loops with `goto` statements. The
+preprocessor first identifies all such variable definitions whether explicit in
+range statements or implicit via `goto` statements that are captured by
+closures or passed by pointer reference, and directs the VM to execute the
+define statement by replacing the existing heap item object with a new one.
diff --git a/docs/resources/gno-stdlibs.md b/docs/resources/gno-stdlibs.md
index 2ca8bb0a400..59615ab88e7 100644
--- a/docs/resources/gno-stdlibs.md
+++ b/docs/resources/gno-stdlibs.md
@@ -420,7 +420,7 @@ currentRealm := std.CurrentRealm()
```
---
-### PrevRealm
+### PreviousRealm
```go
func PreviousRealm() Realm
```
diff --git a/docs/resources/realms.md b/docs/resources/realms.md
index ab551b9935f..f81871dd7d4 100644
--- a/docs/resources/realms.md
+++ b/docs/resources/realms.md
@@ -1,15 +1,26 @@
-# Realms
+# Realms and Packages
-In gno.land, realms are entities that are addressable and identifiable by a
-[Gno address](./gno-stdlibs.md#address). These can be user
-realms (EOAs), as well as smart contract realms. Realms have several
-properties:
-- They can own, receive & send [Coins](./gno-stdlibs.md#coin) through the
- [Banker](./gno-stdlibs.md#banker) module
-- They can be part of a transaction call stack, as a caller or a callee
-- They can be with or without code - smart contracts, or EOAs
+Gno.land Realms are Packages of Gno code that are identified by their "package
+paths" and are also "addressable" into an [Address](./gno-stdlibs.md#address)
+which has prefix "g1...".
+
+A Realm is created when a Gno code Package is added to "gno.land/r/...", and
+the state of realm packages are mutated by signed function call messages from
+users calling exposed functions of realms. Realm functions can in turn call
+other functions creating a call stack beginning at origin with a user Account.
+
+A "P" Package is created when a Package is added to "gno.land/p/...". "P"
+Packages are immutable and cannot be modified by any message after creation.
+
+Realm and "P" Packages have an Account and Address derived from its package
+path. Users too have an Account and Address determined cryptographically from a
+BIP39 mnemonic phrase or secret.
+
+Realms and users can both send and receive [Coins](./gno-stdlibs.md#coin) using
+the [Banker](./gno-stdlibs.md#banker) module by Address.
Realms are represented by a `Realm` type in Gno:
+
```go
type Realm struct {
addr Address // Gno address in the bech32 format
@@ -44,15 +55,17 @@ Currently, EOAs are the only realms that can initiate a transaction. They can do
this by calling any of the possible messages in gno.land, which can be
found [here](../users/interact-with-gnokey.md#making-transactions).
-### Working with realms
+### Working with Realms
+
+Every Gno transaction produce a call stack that can switch across functions
+declared in realm packages and functions declared in p packages. The `std`
+package contains functions that return the current realm, previous realm, and
+the origin caller's address.
-In Gno, each transaction contains a realm call stack. Every item in the stack and
-its properties can be accessed via different functions defined in the `std`
-package in Gno:
- `std.GetOrigCaller()` - returns the address of the original signer of the
transaction
-- `std.PrevRealm()` - returns the previous realm instance, which can be a user realm
- or a smart contract realm
+- `std.PreviousRealm()` - returns the previous realm instance, which can be a user
+ realm or a smart contract realm
- `std.CurrentRealm()` - returns the instance of the realm that has called it
Let's look at the return values of these functions in two distinct situations:
@@ -61,15 +74,18 @@ Let's look at the return values of these functions in two distinct situations:
#### 1. EOA calling a realm
+When an EOA calls a realm, the call stack is initiated by the EOA, and the realm
+becomes the current context.
+
Take these two actors in the call stack:
```
EOA:
- addr: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5
+ addr: `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
pkgPath: "" // empty as this is a user realm
Realm A:
- addr: g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s
- pkgPath: gno.land/r/demo/users
+ addr: `g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s`
+ pkgPath: `gno.land/r/demo/users`
┌─────────────────────┐ ┌─────────────────────────┐
│ EOA │ │ Realm A │
@@ -82,20 +98,26 @@ Realm A:
└─────────────────────┘ └─────────────────────────┘
```
-Let's look at return values for each of the methods, called from within `Realm A`:
+Let's look at return values for each of the methods, called from within
+`Realm A`:
```
std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
-std.PrevRealm() => Realm {
+std.PreviousRealm() => Realm {
addr: `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
pkgPath: ``
}
std.CurrentRealm() => Realm {
addr: `g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s`
- pkgPath: `gno.land/r/demo/users`}
+ pkgPath: `gno.land/r/demo/users`
+}
```
#### 2. EOA calling a sequence of realms
+Assuming that you use interrealm switching, when an EOA calls a sequence of
+realms, the call stack transitions through multiple realms. Each realm in the
+sequence becomes the current context as the call progresses.
+
Take these three actors in the call stack:
```
EOA:
@@ -125,7 +147,7 @@ Depending on which realm the methods are called in, the values will change. For
`Realm A`:
```
std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
-std.PrevRealm() => Realm {
+std.PreviousRealm() => Realm {
addr: `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
pkgPath: ``
}
@@ -138,7 +160,7 @@ std.CurrentRealm() => Realm {
For `Realm B`:
```
std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`
-std.PrevRealm() => Realm {
+std.PreviousRealm() => Realm {
addr: `g1dvqd8qgvavqayxklzfdmccd2eps263p43pu2c6`
pkgPath: `gno.land/r/demo/a`
}
@@ -148,6 +170,12 @@ std.CurrentRealm() => Realm {
}
```
+### Resources
+
+See the [Gno Interrealm Specification](./gno-interrealm.md) for more
+information on language rules for interrealm (cross) safety including how and
+when to use the `cross()` and `crossing()` functions and more.
+
For more information about realms and how they fit into the gno.land ecosystem,
see the [Package Path Structure](./gno-packages.md#package-path-structure)
documentation.
diff --git a/examples/gno.land/p/agherasie/forms/create_test.gno b/examples/gno.land/p/agherasie/forms/create_test.gno
index bd5eb7497ee..c27000e403c 100644
--- a/examples/gno.land/p/agherasie/forms/create_test.gno
+++ b/examples/gno.land/p/agherasie/forms/create_test.gno
@@ -1,6 +1,7 @@
package forms
import (
+ "std"
"testing"
"gno.land/p/demo/testutils"
@@ -10,6 +11,7 @@ import (
func TestCreateForm(t *testing.T) {
alice := testutils.TestAddress("alice")
testing.SetOriginCaller(alice)
+ testing.SetRealm(std.NewUserRealm("g1user"))
db := NewDB()
title := "Simple Form"
description := "This is a form"
diff --git a/examples/gno.land/p/agherasie/forms/submit_test.gno b/examples/gno.land/p/agherasie/forms/submit_test.gno
index e5125acbae2..c6883a75a30 100644
--- a/examples/gno.land/p/agherasie/forms/submit_test.gno
+++ b/examples/gno.land/p/agherasie/forms/submit_test.gno
@@ -1,6 +1,7 @@
package forms
import (
+ "std"
"testing"
"time"
@@ -8,6 +9,8 @@ import (
)
func TestAnswerForm(t *testing.T) {
+ testing.SetRealm(std.NewUserRealm("g1user"))
+
db := NewDB()
data := `[
@@ -51,6 +54,7 @@ func TestAnswerForm(t *testing.T) {
}
func TestAnswerFormDates(t *testing.T) {
+ testing.SetRealm(std.NewUserRealm("g1user"))
db := NewDB()
now := time.Now()
diff --git a/examples/gno.land/p/agherasie/forms/validate_test.gno b/examples/gno.land/p/agherasie/forms/validate_test.gno
index 952cb6d96d4..ea2d09ed984 100644
--- a/examples/gno.land/p/agherasie/forms/validate_test.gno
+++ b/examples/gno.land/p/agherasie/forms/validate_test.gno
@@ -1,10 +1,12 @@
package forms
import (
+ "std"
"testing"
)
func TestAnswerFormInvalidForm(t *testing.T) {
+ testing.SetRealm(std.NewUserRealm("g1user"))
db := NewDB()
dataAllTypes := `[
diff --git a/examples/gno.land/p/moul/debug/debug_test.gno b/examples/gno.land/p/moul/debug/debug_test.gno
new file mode 100644
index 00000000000..02ce5f7c9c5
--- /dev/null
+++ b/examples/gno.land/p/moul/debug/debug_test.gno
@@ -0,0 +1,71 @@
+package debug
+
+import (
+ "std"
+ "strings"
+ "testing"
+
+ "gno.land/p/demo/uassert"
+)
+
+func TestPackage(t *testing.T) {
+ testing.SetRealm(std.NewUserRealm("g1user"))
+
+ // no debug
+ got := Render("")
+ expected := ``
+ uassert.Equal(t, got, expected)
+
+ // debug without logs
+ got = Render("?debug=1")
+ expected = `debug
+
+### Metadata
+| Key | Value |
+| --- | --- |
+| ±std.CurrentRealm().PkgPath()± | |
+| ±std.CurrentRealm().Address()± | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+| ±std.PreviousRealm().PkgPath()± | |
+| ±std.PreviousRealm().Address()± | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+| ±std.ChainHeight()± | 123 |
+| ±time.Now().Format(time.RFC3339)± | 2009-02-13T23:31:30Z |
+
+
+`
+ expected = strings.ReplaceAll(expected, "±", "`")
+
+ println("###################")
+ println(got)
+ println("###################")
+ println(expected)
+ println("###################")
+
+ uassert.Equal(t, got, expected)
+
+ return
+
+ // debug with logs
+ var d Debug
+ d.Log("hello world!")
+ d.Log("foobar")
+ got = d.Render("?debug=1")
+ expected = `debug
+
+### Logs
+- hello world!
+- foobar
+### Metadata
+| Key | Value |
+| --- | --- |
+| ±std.CurrentRealm().PkgPath()± | |
+| ±std.CurrentRealm().Address()± | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+| ±std.PreviousRealm().PkgPath()± | |
+| ±std.PreviousRealm().Address()± | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+| ±std.ChainHeight()± | 123 |
+| ±time.Now().Format(time.RFC3339)± | 2009-02-13T23:31:30Z |
+
+
+`
+ expected = strings.ReplaceAll(expected, "±", "`")
+ uassert.Equal(t, got, expected)
+}
diff --git a/examples/gno.land/p/moul/debug/z1_filetest.gno b/examples/gno.land/p/moul/debug/z1_filetest.gno
deleted file mode 100644
index 5016fdab605..00000000000
--- a/examples/gno.land/p/moul/debug/z1_filetest.gno
+++ /dev/null
@@ -1,31 +0,0 @@
-package main
-
-import "gno.land/p/moul/debug"
-
-func main() {
- println("---")
- println(debug.Render(""))
- println("---")
- println(debug.Render("?debug=1"))
- println("---")
-}
-
-// Output:
-// ---
-//
-// ---
-// debug
-//
-// ### Metadata
-// | Key | Value |
-// | --- | --- |
-// | `std.CurrentRealm().PkgPath()` | |
-// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
-// | `std.PreviousRealm().PkgPath()` | |
-// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
-// | `std.ChainHeight()` | 123 |
-// | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z |
-//
-//
-//
-// ---
diff --git a/examples/gno.land/p/moul/debug/z2_filetest.gno b/examples/gno.land/p/moul/debug/z2_filetest.gno
deleted file mode 100644
index fab7e57cdc2..00000000000
--- a/examples/gno.land/p/moul/debug/z2_filetest.gno
+++ /dev/null
@@ -1,37 +0,0 @@
-package main
-
-import "gno.land/p/moul/debug"
-
-func main() {
- var d debug.Debug
- d.Log("hello world!")
- d.Log("foobar")
- println("---")
- println(d.Render(""))
- println("---")
- println(d.Render("?debug=1"))
- println("---")
-}
-
-// Output:
-// ---
-//
-// ---
-// debug
-//
-// ### Logs
-// - hello world!
-// - foobar
-// ### Metadata
-// | Key | Value |
-// | --- | --- |
-// | `std.CurrentRealm().PkgPath()` | |
-// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
-// | `std.PreviousRealm().PkgPath()` | |
-// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
-// | `std.ChainHeight()` | 123 |
-// | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z |
-//
-//
-//
-// ---
diff --git a/examples/gno.land/r/demo/nft/nft.gno b/examples/gno.land/r/demo/nft/nft.gno
index fe328a5be17..45c0d41f592 100644
--- a/examples/gno.land/r/demo/nft/nft.gno
+++ b/examples/gno.land/r/demo/nft/nft.gno
@@ -74,7 +74,7 @@ func (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {
}
func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {
- caller := std.CallerAt(2)
+ caller := std.CallerAt(2) // XXX use CurrentRealm or PreviousRealm().
token, ok := grc.getToken(tid)
// Throws if `_tokenId` is not a valid NFT.
if !ok {
diff --git a/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno
index 1cc5a3f8e18..d3c2ec54026 100644
--- a/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno
+++ b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno
@@ -33,6 +33,8 @@ type Fooer interface{ Foo() }
var fooer Fooer
func SetFooer(f Fooer) Fooer {
+ crossing()
+
fooer = f
return fooer
}
@@ -41,11 +43,25 @@ func GetFooer() Fooer { return fooer }
func CallFooerFoo() { fooer.Foo() }
+func CallFooerFooSR() {
+ crossing()
+
+ fooer.Foo()
+}
+
+func CallFooerFooSR2() {
+ crossing()
+
+ cross(fooer.Foo)()
+}
+
type FooerGetter func() Fooer
var fooerGetter FooerGetter
func SetFooerGetter(fg FooerGetter) FooerGetter {
+ crossing()
+
fooerGetter = fg
return fg
}
@@ -55,3 +71,41 @@ func GetFooerGetter() FooerGetter {
}
func CallFooerGetterFoo() { fooerGetter().Foo() }
+
+func CallFooerGetterFooSR2() {
+ crossing()
+
+ cross(fooerGetter().Foo)()
+}
+
+// This is a top function that does switch realms.
+func ExecSR(cb func() string) string {
+ crossing()
+
+ return cb()
+}
+
+// This is a top function that doesn't switch realms.
+func Exec(cb func() string) string {
+ return cb()
+}
+
+var Closure func()
+
+func SetClosure(f func()) {
+ crossing()
+
+ Closure = f
+}
+
+var Object any
+
+func SetObject(x any) {
+ crossing()
+
+ Object = x
+}
+
+func GetObject() any {
+ return Object
+}
diff --git a/examples/gno.land/r/demo/tests/crossrealm/switchrealm.gno b/examples/gno.land/r/demo/tests/crossrealm/switchrealm.gno
new file mode 100644
index 00000000000..efd3ac30be4
--- /dev/null
+++ b/examples/gno.land/r/demo/tests/crossrealm/switchrealm.gno
@@ -0,0 +1 @@
+package crossrealm
diff --git a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno
index 058c81ed034..1cc51322213 100644
--- a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno
+++ b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno
@@ -15,6 +15,8 @@ func (f *fooer) SetS(newVal string) {
}
func (f *fooer) Foo() {
+ crossing()
+
println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PreviousRealm().PkgPath())
}
@@ -23,3 +25,23 @@ var (
FooerGetter = func() crossrealm.Fooer { return Fooer }
FooerGetterBuilder = func() crossrealm.FooerGetter { return func() crossrealm.Fooer { return Fooer } }
)
+
+var Closure func()
+
+func SetClosure(f func()) {
+ crossing()
+
+ Closure = f
+}
+
+var Object any
+
+func SetObject(x any) {
+ crossing()
+
+ Object = x
+}
+
+func GetObject() any {
+ return Object
+}
diff --git a/examples/gno.land/r/demo/tests/exploit.gno b/examples/gno.land/r/demo/tests/exploit.gno
new file mode 100644
index 00000000000..db79194c9eb
--- /dev/null
+++ b/examples/gno.land/r/demo/tests/exploit.gno
@@ -0,0 +1,23 @@
+package tests
+
+var MyFoo *Foo
+
+type Foo struct {
+ A int
+ B *Foo
+}
+
+// method to mutate
+
+func (f *Foo) UpdateFoo(x int) {
+ f.A = x
+}
+
+func init() {
+ MyFoo = &Foo{
+ A: 1,
+ B: &Foo{
+ A: 2,
+ },
+ }
+}
diff --git a/examples/gno.land/r/demo/tests/interfaces.gno b/examples/gno.land/r/demo/tests/interfaces.gno
index d8f64e1577b..0e76730f9fb 100644
--- a/examples/gno.land/r/demo/tests/interfaces.gno
+++ b/examples/gno.land/r/demo/tests/interfaces.gno
@@ -11,6 +11,8 @@ type Stringer interface {
var stringers []Stringer
func AddStringer(str Stringer) {
+ crossing()
+
// NOTE: this is ridiculous, a slice that will become too long
// eventually. Don't do this in production programs; use
// gno.land/p/demo/avl or similar structures.
diff --git a/examples/gno.land/r/demo/tests/realm_method38d.gno b/examples/gno.land/r/demo/tests/realm_method38d.gno
index b1dbab67e1f..029dd00b630 100644
--- a/examples/gno.land/r/demo/tests/realm_method38d.gno
+++ b/examples/gno.land/r/demo/tests/realm_method38d.gno
@@ -7,13 +7,15 @@ func (n nat) Add() nat {
}
func GetAbs() nat {
- abs = []Word{0}
+ crossing()
+ abs = []Word{0}
return abs
}
func AbsAdd() nat {
- rt := GetAbs().Add()
+ crossing()
+ rt := GetAbs().Add()
return rt
}
diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno
index ddfe54cd4b3..b8ba89ac788 100644
--- a/examples/gno.land/r/demo/tests/subtests/subtests.gno
+++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno
@@ -5,10 +5,14 @@ import (
)
func GetCurrentRealm() std.Realm {
+ crossing()
+
return std.CurrentRealm()
}
func GetPreviousRealm() std.Realm {
+ crossing()
+
return std.PreviousRealm()
}
@@ -17,9 +21,13 @@ func Exec(fn func()) {
}
func CallAssertOriginCall() {
+ crossing()
+
std.AssertOriginCall()
}
func CallIsOriginCall() bool {
+ crossing()
+
return std.PreviousRealm().IsUser()
}
diff --git a/examples/gno.land/r/demo/tests/test20/test20.gno b/examples/gno.land/r/demo/tests/test20/test20.gno
index 9c4df58d1c4..518d855d8e7 100644
--- a/examples/gno.land/r/demo/tests/test20/test20.gno
+++ b/examples/gno.land/r/demo/tests/test20/test20.gno
@@ -16,5 +16,7 @@ import (
var Token, PrivateLedger = grc20.NewToken("Test20", "TST", 4)
func init() {
+ crossing()
+
grc20reg.Register(Token.Getter(), "")
}
diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno
index 452319c7aca..08b64538cf2 100644
--- a/examples/gno.land/r/demo/tests/tests.gno
+++ b/examples/gno.land/r/demo/tests/tests.gno
@@ -10,36 +10,52 @@ import (
var counter int
func IncCounter() {
+ crossing()
+
counter++
}
func Counter() int {
+ crossing()
+
return counter
}
func CurrentRealmPath() string {
+ crossing()
+
return std.CurrentRealm().PkgPath()
}
var initOriginCaller = std.OriginCaller()
func InitOriginCaller() std.Address {
+ crossing()
+
return initOriginCaller
}
func CallAssertOriginCall() {
+ crossing()
+
std.AssertOriginCall()
}
func CallIsOriginCall() bool {
+ crossing()
+
return std.PreviousRealm().IsUser()
}
func CallSubtestsAssertOriginCall() {
+ crossing()
+
rsubtests.CallAssertOriginCall()
}
func CallSubtestsIsOriginCall() bool {
+ crossing()
+
return rsubtests.CallIsOriginCall()
}
@@ -53,6 +69,8 @@ type TestRealmObject struct {
var TestRealmObjectValue TestRealmObject
func ModifyTestRealmObject(t *TestRealmObject) {
+ crossing()
+
t.Field += "_modified"
}
@@ -75,11 +93,15 @@ var (
)
func InitTestNodes() {
+ crossing()
+
gTestNode1 = &TestNode{Name: "first"}
gTestNode2 = &TestNode{Name: "second", Child: &TestNode{Name: "second's child"}}
}
func ModTestNodes() {
+ crossing()
+
tmp := &TestNode{}
tmp.Child = gTestNode2.Child
gTestNode3 = tmp // set to new-real
@@ -92,25 +114,42 @@ func PrintTestNodes() {
}
func GetPreviousRealm() std.Realm {
+ crossing()
+
return std.PreviousRealm()
}
func GetRSubtestsPreviousRealm() std.Realm {
- return rsubtests.GetPreviousRealm()
+ crossing()
+
+ return cross(rsubtests.GetPreviousRealm)()
}
func Exec(fn func()) {
+ // no realm switching.
+ fn()
+}
+
+func ExecSwitch(fn func()) {
+ crossing()
+
fn()
}
func IsCallerSubPath() bool {
+ crossing()
+
return nestedpkg.IsCallerSubPath()
}
func IsCallerParentPath() bool {
+ crossing()
+
return nestedpkg.IsCallerParentPath()
}
func HasCallerSameNamespace() bool {
+ crossing()
+
return nestedpkg.IsSameNamespace()
}
diff --git a/examples/gno.land/r/demo/tests_foo/foo.gno b/examples/gno.land/r/demo/tests_foo/foo.gno
index 560d85552ab..4dc36a51077 100644
--- a/examples/gno.land/r/demo/tests_foo/foo.gno
+++ b/examples/gno.land/r/demo/tests_foo/foo.gno
@@ -15,5 +15,5 @@ func (fs *FooStringer) String() string {
}
func AddFooStringer(fa string) {
- tests.AddStringer(&FooStringer{fa})
+ cross(tests.AddStringer)(&FooStringer{fa})
}
diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno
deleted file mode 100644
index 222ca98ebfa..00000000000
--- a/examples/gno.land/r/leon/config/config.gno
+++ /dev/null
@@ -1,85 +0,0 @@
-package config
-
-import (
- "errors"
- "std"
- "strings"
- "time"
-
- "gno.land/p/demo/avl"
- "gno.land/p/demo/ownable"
- "gno.land/p/demo/seqid"
-)
-
-var (
- cfgID seqid.ID
- configs = avl.NewTree()
-
- absPath = strings.TrimPrefix(std.CurrentRealm().PkgPath(), std.ChainDomain())
-
- // SafeObjects
- OwnableMain = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")
- OwnableBackup = ownable.NewWithAddress("g1lavlav7zwsjqlzzl3qdl3nl242qtf638vnhdjh")
-
- ErrUnauthorized = errors.New("leon's config: unauthorized")
-)
-
-type Config struct {
- id seqid.ID
- name string
- lines string
- updated time.Time
-}
-
-func AddConfig(name, lines string) {
- if !IsAuthorized(std.PreviousRealm().Address()) {
- panic(ErrUnauthorized)
- }
-
- id := cfgID.Next()
- configs.Set(id.String(), Config{
- id: id,
- name: name,
- lines: lines,
- updated: time.Now(),
- })
-}
-
-func EditConfig(id string, name, lines string) {
- if !IsAuthorized(std.PreviousRealm().Address()) {
- panic(ErrUnauthorized)
- }
-
- raw, ok := configs.Remove(id)
- if !ok {
- panic("no config with that id")
- }
-
- conf := raw.(Config)
- // Overwrites data
- conf.lines = lines
- conf.name = name
- conf.updated = time.Now()
-}
-
-func RemoveConfig(id string) {
- if !IsAuthorized(std.PreviousRealm().Address()) {
- panic(ErrUnauthorized)
- }
-
- if _, ok := configs.Remove(id); !ok {
- panic("no config with that id")
- }
-}
-
-func UpdateBanner(newBanner string) {
- if !IsAuthorized(std.PreviousRealm().Address()) {
- panic(ErrUnauthorized)
- }
-
- banner = newBanner
-}
-
-func IsAuthorized(addr std.Address) bool {
- return addr == OwnableMain.Owner() || addr == OwnableBackup.Owner()
-}
diff --git a/examples/gno.land/r/leon/config/gno.mod b/examples/gno.land/r/leon/config/gno.mod
deleted file mode 100644
index e8cd5cd85b7..00000000000
--- a/examples/gno.land/r/leon/config/gno.mod
+++ /dev/null
@@ -1 +0,0 @@
-module gno.land/r/leon/config
diff --git a/examples/gno.land/r/leon/config/render.gno b/examples/gno.land/r/leon/config/render.gno
deleted file mode 100644
index 4d2c7dffcba..00000000000
--- a/examples/gno.land/r/leon/config/render.gno
+++ /dev/null
@@ -1,69 +0,0 @@
-package config
-
-import (
- "strconv"
-
- p "gno.land/p/demo/avl/pager"
- "gno.land/p/demo/ufmt"
- "gno.land/p/moul/md"
- "gno.land/p/moul/realmpath"
- "gno.land/p/moul/txlink"
-)
-
-var (
- banner = "---\n[[Leon's Home page]](/r/leon/home) | [[Leon's snippets]](/r/leon/config) | [[GitHub: @leohhhn]](https://github.com/leohhhn)\n\n---"
- pager = p.NewPager(configs, 10, true)
-)
-
-func Banner() string {
- return banner
-}
-
-func Render(path string) (out string) {
- req := realmpath.Parse(path)
- if req.Path == "" {
- out += md.H1("Leon's configs & snippets")
-
- out += ufmt.Sprintf("Leon's main address: %s\n\n", OwnableMain.Owner().String())
- out += ufmt.Sprintf("Leon's backup address: %s\n\n", OwnableBackup.Owner().String())
-
- out += md.H2("Snippets")
-
- if configs.Size() == 0 {
- out += "No configs yet :c\n\n"
- } else {
- page := pager.MustGetPageByPath(path)
- for _, item := range page.Items {
- out += ufmt.Sprintf("- [%s](%s:%s)\n\n", item.Value.(Config).name, absPath, item.Key)
- }
-
- out += page.Picker(path)
- out += "\n\n"
- out += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n"
- }
-
- out += Banner()
-
- return out
- }
-
- return renderConfPage(req.Path)
-}
-
-func renderConfPage(id string) (out string) {
- raw, ok := configs.Get(id)
- if !ok {
- out += md.H1("404")
- out += "That config does not exist :/"
- return out
- }
-
- conf := raw.(Config)
- out += md.H1(conf.name)
- out += ufmt.Sprintf("```\n%s\n```\n\n", conf.lines)
- out += ufmt.Sprintf("_Last updated on %s_\n\n", conf.updated.Format("02 Jan, 2006"))
- out += md.HorizontalRule()
- out += ufmt.Sprintf("[[EDIT]](%s) - [[DELETE]](%s)", txlink.Call("EditConfig", "id", conf.id.String()), txlink.Call("RemoveConfig", "id", conf.id.String()))
-
- return out
-}
diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno
deleted file mode 100644
index 66afffb4ea2..00000000000
--- a/examples/gno.land/r/leon/hof/datasource.gno
+++ /dev/null
@@ -1,79 +0,0 @@
-package hof
-
-import (
- "errors"
-
- "gno.land/p/demo/avl"
- "gno.land/p/demo/ufmt"
- "gno.land/p/jeronimoalbi/datasource"
- "gno.land/p/moul/md"
-)
-
-func NewDatasource() Datasource {
- return Datasource{exhibition}
-}
-
-type Datasource struct {
- exhibition *Exhibition
-}
-
-func (ds Datasource) Size() int { return ds.exhibition.items.Size() }
-
-func (ds Datasource) Records(q datasource.Query) datasource.Iterator {
- return &iterator{
- exhibition: ds.exhibition,
- index: q.Offset,
- maxIndex: q.Offset + q.Count,
- }
-}
-
-func (ds Datasource) Record(id string) (datasource.Record, error) {
- v, found := ds.exhibition.items.Get(id)
- if !found {
- return nil, errors.New("realm submission not found")
- }
- return record{v.(*Item)}, nil
-}
-
-type record struct {
- item *Item
-}
-
-func (r record) ID() string { return r.item.id.String() }
-func (r record) String() string { return r.item.pkgpath }
-
-func (r record) Fields() (datasource.Fields, error) {
- fields := avl.NewTree()
- fields.Set(
- "details",
- ufmt.Sprintf("Votes: ⏶ %d - ⏷ %d", r.item.upvote.Size(), r.item.downvote.Size()),
- )
- return fields, nil
-}
-
-func (r record) Content() (string, error) {
- content := md.H1(r.item.title)
- content += md.H2(r.item.description)
- content += r.item.Render(false)
- return content, nil
-}
-
-type iterator struct {
- exhibition *Exhibition
- index, maxIndex int
- record *record
-}
-
-func (it iterator) Record() datasource.Record { return it.record }
-func (it iterator) Err() error { return nil }
-
-func (it *iterator) Next() bool {
- if it.index >= it.maxIndex || it.index >= it.exhibition.items.Size() {
- return false
- }
-
- _, v := it.exhibition.items.GetByIndex(it.index)
- it.record = &record{v.(*Item)}
- it.index++
- return true
-}
diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno
deleted file mode 100644
index b5dfcbf7f60..00000000000
--- a/examples/gno.land/r/leon/hof/datasource_test.gno
+++ /dev/null
@@ -1,171 +0,0 @@
-package hof
-
-import (
- "strings"
- "testing"
-
- "gno.land/p/demo/avl"
- "gno.land/p/demo/uassert"
- "gno.land/p/demo/ufmt"
- "gno.land/p/demo/urequire"
- "gno.land/p/jeronimoalbi/datasource"
- "gno.land/p/moul/addrset"
- "gno.land/p/moul/md"
- "gno.land/p/moul/txlink"
-)
-
-var (
- _ datasource.Datasource = (*Datasource)(nil)
- _ datasource.Record = (*record)(nil)
- _ datasource.ContentRecord = (*record)(nil)
- _ datasource.Iterator = (*iterator)(nil)
-)
-
-func TestDatasourceRecords(t *testing.T) {
- cases := []struct {
- name string
- items []*Item
- recordIDs []string
- options []datasource.QueryOption
- }{
- {
- name: "all items",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- recordIDs: []string{"0000001", "0000002", "0000003"},
- },
- {
- name: "with offset",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- recordIDs: []string{"0000002", "0000003"},
- options: []datasource.QueryOption{datasource.WithOffset(1)},
- },
- {
- name: "with count",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- recordIDs: []string{"0000001", "0000002"},
- options: []datasource.QueryOption{datasource.WithCount(2)},
- },
- {
- name: "with offset and count",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- recordIDs: []string{"0000002"},
- options: []datasource.QueryOption{
- datasource.WithOffset(1),
- datasource.WithCount(1),
- },
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- // Initialize a local instance of exhibition
- exhibition := &Exhibition{items: avl.NewTree()}
- for _, item := range tc.items {
- exhibition.items.Set(item.id.String(), item)
- }
-
- // Get a records iterator
- ds := Datasource{exhibition}
- query := datasource.NewQuery(tc.options...)
- iter := ds.Records(query)
-
- // Start asserting
- urequire.Equal(t, len(tc.items), ds.Size(), "datasource size")
-
- var records []datasource.Record
- for iter.Next() {
- records = append(records, iter.Record())
- }
- urequire.Equal(t, len(tc.recordIDs), len(records), "record count")
-
- for i, r := range records {
- uassert.Equal(t, tc.recordIDs[i], r.ID())
- }
- })
- }
-}
-
-func TestDatasourceRecord(t *testing.T) {
- cases := []struct {
- name string
- items []*Item
- id string
- err string
- }{
- {
- name: "found",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- id: "0000001",
- },
- {
- name: "no found",
- items: []*Item{{id: 1}, {id: 2}, {id: 3}},
- id: "42",
- err: "realm submission not found",
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- // Initialize a local instance of exhibition
- exhibition := &Exhibition{items: avl.NewTree()}
- for _, item := range tc.items {
- exhibition.items.Set(item.id.String(), item)
- }
-
- // Get a single record
- ds := Datasource{exhibition}
- r, err := ds.Record(tc.id)
-
- // Start asserting
- if tc.err != "" {
- uassert.ErrorContains(t, err, tc.err)
- return
- }
-
- urequire.NoError(t, err, "no error")
- urequire.NotEqual(t, nil, r, "record not nil")
- uassert.Equal(t, tc.id, r.ID())
- })
- }
-}
-
-func TestItemRecord(t *testing.T) {
- pkgpath := "gno.land/r/demo/test"
- item := Item{
- id: 1,
- pkgpath: pkgpath,
- title: "Test Realm",
- description: "This is a test realm in the Hall of Fame",
- blockNum: 42,
- upvote: &addrset.Set{},
- downvote: &addrset.Set{},
- }
- item.downvote.Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
- item.upvote.Add("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
- item.upvote.Add("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc")
-
- r := record{&item}
-
- uassert.Equal(t, "0000001", r.ID())
- uassert.Equal(t, pkgpath, r.String())
-
- fields, _ := r.Fields()
- details, found := fields.Get("details")
- urequire.True(t, found, "details field")
- uassert.Equal(t, "Votes: ⏶ 2 - ⏷ 1", details)
-
- content, _ := r.Content()
- wantContent := md.H1(item.title) +
- md.H2(r.item.description) +
- ufmt.Sprintf("\n%s\n\n", md.CodeBlock(item.pkgpath)) +
- ufmt.Sprintf("%s\n\n", item.description) +
- ufmt.Sprintf("by %s\n\n", strings.Split(item.pkgpath, "/")[2]) +
- md.Link("View Realm", strings.TrimPrefix(item.pkgpath, "gno.land")) + "\n\n" +
- ufmt.Sprintf("Submitted at Block #%d\n\n", item.blockNum) +
- md.Bold(ufmt.Sprintf("[%d👍](%s) - [%d👎](%s)",
- item.upvote.Size(), txlink.Call("Upvote", "pkgpath", item.pkgpath),
- item.downvote.Size(), txlink.Call("Downvote", "pkgpath", item.pkgpath),
- ))
- uassert.Equal(t, wantContent, content)
-}
diff --git a/examples/gno.land/r/leon/hof/errors.gno b/examples/gno.land/r/leon/hof/errors.gno
deleted file mode 100644
index 5fe51c922cd..00000000000
--- a/examples/gno.land/r/leon/hof/errors.gno
+++ /dev/null
@@ -1,11 +0,0 @@
-package hof
-
-import (
- "gno.land/p/leon/pkgerr"
-)
-
-var (
- ErrNoSuchItem = pkgerr.New("no such item exists")
- ErrDoubleUpvote = pkgerr.New("cannot upvote twice")
- ErrDoubleDownvote = pkgerr.New("cannot downvote twice")
-)
diff --git a/examples/gno.land/r/leon/hof/gno.mod b/examples/gno.land/r/leon/hof/gno.mod
deleted file mode 100644
index f4720eb2b5a..00000000000
--- a/examples/gno.land/r/leon/hof/gno.mod
+++ /dev/null
@@ -1 +0,0 @@
-module gno.land/r/leon/hof
diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno
deleted file mode 100644
index 82b7333d1d2..00000000000
--- a/examples/gno.land/r/leon/hof/hof.gno
+++ /dev/null
@@ -1,200 +0,0 @@
-// Package hof is the hall of fame realm.
-// The Hall of Fame is an exhibition that holds items. Users can add their realms to the Hall of Fame by
-// importing the Hall of Fame realm and calling hof.Register() from their init function.
-package hof
-
-import (
- "std"
- "strconv"
- "strings"
-
- "gno.land/p/demo/avl"
- "gno.land/p/demo/ownable"
- "gno.land/p/demo/pausable"
- "gno.land/p/demo/seqid"
- "gno.land/p/moul/addrset"
- "gno.land/r/leon/config"
-)
-
-const (
- maxTitleLength = 30
- maxDescriptionLength = 50
-)
-
-var (
- exhibition *Exhibition
-
- // Safe objects
- Ownable *ownable.Ownable
- Pausable *pausable.Pausable
-)
-
-type (
- Exhibition struct {
- itemCounter seqid.ID
- description string
- items *avl.Tree // pkgPath > *Item
- itemsSortedByCreation *avl.Tree // same data but sorted by creation time
- itemsSortedByUpvotes *avl.Tree // same data but sorted by upvotes
- itemsSortedByDownvotes *avl.Tree // same data but sorted by downvotes
- }
-
- Item struct {
- id seqid.ID
- title string
- description string
- pkgpath string
- blockNum int64
- upvote *addrset.Set
- downvote *addrset.Set
- }
-)
-
-func init() {
- exhibition = &Exhibition{
- items: avl.NewTree(),
- itemsSortedByCreation: avl.NewTree(),
- itemsSortedByUpvotes: avl.NewTree(),
- itemsSortedByDownvotes: avl.NewTree(),
- }
-
- Ownable = ownable.NewWithAddress(config.OwnableMain.Owner()) // OrigSendOwnable?
- Pausable = pausable.NewFromOwnable(Ownable)
-}
-
-// Register registers your realm to the Hall of Fame
-// Should be called from within code
-func Register(title, description string) {
- if Pausable.IsPaused() {
- return
- }
-
- submission := std.PreviousRealm()
- pkgpath := submission.PkgPath()
-
- // Must be called from code
- if submission.IsUser() {
- return
- }
-
- // Must not yet exist
- if exhibition.items.Has(pkgpath) {
- return
- }
-
- // Title must be between 1 maxTitleLength long
- if title == "" || len(title) > maxTitleLength {
- return
- }
-
- // Description must be between 1 maxDescriptionLength long
- if len(description) > maxDescriptionLength {
- return
- }
-
- id := exhibition.itemCounter.Next()
- i := &Item{
- id: id,
- title: title,
- description: description,
- pkgpath: pkgpath,
- blockNum: std.ChainHeight(),
- upvote: &addrset.Set{},
- downvote: &addrset.Set{},
- }
-
- exhibition.items.Set(pkgpath, i)
- exhibition.itemsSortedByCreation.Set(getCreationSortKey(i.blockNum, i.id), i)
- exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(i.upvote.Size(), i.id), i)
- exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(i.downvote.Size(), i.id), i)
-
- std.Emit("Registration")
-}
-
-func Upvote(pkgpath string) {
- rawItem, ok := exhibition.items.Get(pkgpath)
- if !ok {
- panic(ErrNoSuchItem)
- }
-
- item := rawItem.(*Item)
- caller := std.PreviousRealm().Address()
-
- if item.upvote.Has(caller) {
- panic(ErrDoubleUpvote)
- }
-
- if _, exists := exhibition.itemsSortedByUpvotes.Remove(getVoteSortKey(item.upvote.Size(), item.id)); !exists {
- panic("error removing old upvote entry")
- }
-
- item.upvote.Add(caller)
-
- exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(item.upvote.Size(), item.id), item)
-}
-
-func Downvote(pkgpath string) {
- rawItem, ok := exhibition.items.Get(pkgpath)
- if !ok {
- panic(ErrNoSuchItem)
- }
-
- item := rawItem.(*Item)
- caller := std.PreviousRealm().Address()
-
- if item.downvote.Has(caller) {
- panic(ErrDoubleDownvote)
- }
-
- if _, exist := exhibition.itemsSortedByDownvotes.Remove(getVoteSortKey(item.downvote.Size(), item.id)); !exist {
- panic("error removing old downvote entry")
-
- }
-
- item.downvote.Add(caller)
-
- exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(item.downvote.Size(), item.id), item)
-}
-
-func Delete(pkgpath string) {
- if !Ownable.CallerIsOwner() {
- panic(ownable.ErrUnauthorized)
- }
-
- i, ok := exhibition.items.Get(pkgpath)
- if !ok {
- panic(ErrNoSuchItem)
- }
-
- item := i.(*Item)
- upvoteKey := getVoteSortKey(item.upvote.Size(), item.id)
- downvoteKey := getVoteSortKey(item.downvote.Size(), item.id)
-
- if _, removed := exhibition.items.Remove(pkgpath); !removed {
- panic(ErrNoSuchItem)
- }
-
- if _, removed := exhibition.itemsSortedByUpvotes.Remove(upvoteKey); !removed {
- panic(ErrNoSuchItem)
- }
-
- if _, removed := exhibition.itemsSortedByDownvotes.Remove(downvoteKey); !removed {
- panic(ErrNoSuchItem)
- }
-
- if _, removed := exhibition.itemsSortedByCreation.Remove(getCreationSortKey(item.blockNum, item.id)); !removed {
- panic(ErrNoSuchItem)
- }
-}
-
-func getVoteSortKey(votes int, id seqid.ID) string {
- votesStr := strconv.Itoa(votes)
- paddedVotes := strings.Repeat("0", 10-len(votesStr)) + votesStr
- return paddedVotes + ":" + strconv.FormatUint(uint64(id), 10)
-}
-
-func getCreationSortKey(blockNum int64, id seqid.ID) string {
- blockNumStr := strconv.Itoa(int(blockNum))
- paddedBlockNum := strings.Repeat("0", 10-len(blockNumStr)) + blockNumStr
- return paddedBlockNum + ":" + strconv.FormatUint(uint64(id), 10)
-}
diff --git a/examples/gno.land/r/leon/hof/hof_test.gno b/examples/gno.land/r/leon/hof/hof_test.gno
deleted file mode 100644
index 3ee2ac29a4b..00000000000
--- a/examples/gno.land/r/leon/hof/hof_test.gno
+++ /dev/null
@@ -1,327 +0,0 @@
-package hof
-
-import (
- "std"
- "testing"
-
- "gno.land/p/demo/testutils"
- "gno.land/p/demo/uassert"
- "gno.land/p/demo/urequire"
- "gno.land/p/moul/addrset"
-)
-
-const (
- rlmPath = "gno.land/r/gnoland/home"
- rlmPath2 = "gno.land/r/gnoland/test2"
-
- rlmPath3 = "gno.land/r/gnoland/test3"
- rlmPath4 = "gno.land/r/gnoland/test4"
- rlmPath5 = "gno.land/r/gnoland/test5"
-
- validTitle = "valid title"
- invalidTitle = "This title is very very very long, longer than 30 characters"
- validDesc = "valid description"
- invalidDescription = "This description is very very very long, longer than 50 characters"
-)
-
-var (
- admin = Ownable.Owner()
- adminRealm = std.NewUserRealm(admin)
- alice = testutils.TestAddress("alice")
-)
-
-func TestRegister(t *testing.T) {
- // Test user realm register
- aliceRealm := std.NewUserRealm(alice)
- testing.SetRealm(aliceRealm)
-
- Register(validTitle, validDesc)
- uassert.False(t, itemExists(t, rlmPath))
-
- // Test register while paused
- testing.SetRealm(adminRealm)
- Pausable.Pause()
-
- // Set legitimate caller
- testing.SetRealm(std.NewCodeRealm(rlmPath))
-
- Register(validTitle, validDesc)
- uassert.False(t, itemExists(t, rlmPath))
-
- // Unpause
- testing.SetRealm(adminRealm)
- Pausable.Unpause()
-
- // Set legitimate caller
- testing.SetRealm(std.NewCodeRealm(rlmPath))
- Register(validTitle, validDesc)
-
- // Find registered items
- uassert.True(t, itemExists(t, rlmPath))
-
- // Test register with invalid title
- testing.SetRealm(std.NewCodeRealm(rlmPath2))
- Register(invalidTitle, validDesc)
- uassert.False(t, itemExists(t, rlmPath2))
-
- // Test register with invalid description
- testing.SetRealm(std.NewCodeRealm(rlmPath2))
- Register(validTitle, invalidDescription)
- uassert.False(t, itemExists(t, rlmPath2))
-}
-
-func TestUpvote(t *testing.T) {
- raw, _ := exhibition.items.Get(rlmPath)
- item := raw.(*Item)
-
- // 0 upvotes by default
- urequire.Equal(t, item.upvote.Size(), 0)
-
- testing.SetRealm(adminRealm)
-
- urequire.NotPanics(t, func() {
- Upvote(rlmPath)
- })
-
- // Check both trees for 1 upvote
- uassert.Equal(t, item.upvote.Size(), 1)
-
- // Check double upvote
- uassert.PanicsWithMessage(t, ErrDoubleUpvote.Error(), func() {
- Upvote(rlmPath)
- })
-}
-
-func TestDownvote(t *testing.T) {
- raw, _ := exhibition.items.Get(rlmPath)
- item := raw.(*Item)
-
- // 0 downvotes by default
- urequire.Equal(t, item.downvote.Size(), 0)
-
- userRealm := std.NewUserRealm(alice)
- testing.SetRealm(userRealm)
-
- urequire.NotPanics(t, func() {
- Downvote(rlmPath)
- })
-
- // Check both trees for 1 upvote
- uassert.Equal(t, item.downvote.Size(), 1)
-
- // Check double downvote
- uassert.PanicsWithMessage(t, ErrDoubleDownvote.Error(), func() {
- Downvote(rlmPath)
- })
-}
-
-func TestDelete(t *testing.T) {
- userRealm := std.NewUserRealm(admin)
- testing.SetRealm(userRealm)
- testing.SetOriginCaller(admin)
-
- uassert.PanicsWithMessage(t, ErrNoSuchItem.Error(), func() {
- Delete("nonexistentpkgpath")
- })
-
- i, _ := exhibition.items.Get(rlmPath)
- id := i.(*Item).id
-
- uassert.NotPanics(t, func() {
- Delete(rlmPath)
- })
-
- uassert.False(t, exhibition.items.Has(rlmPath))
-}
-
-func itemExists(t *testing.T, rlmPath string) bool {
- t.Helper()
-
- i, ok1 := exhibition.items.Get(rlmPath)
-
- return ok1
-}
-
-func TestgetVoteSortKey(t *testing.T) {
- i := &Item{
- id: 1,
- title: validTitle,
- description: validDesc,
- pkgpath: rlmPath,
- blockNum: std.ChainHeight(),
- upvote: &addrset.Set{},
- downvote: &addrset.Set{},
- }
-
- i.upvote.Add(alice)
-
- generatedKey := getVoteSortKey(i.upvote.Size(), i.id)
- expectedKey := "0000000001:1"
-
- urequire.Equal(t, generatedKey, expectedKey)
-}
-
-func TestSortByUpvote(t *testing.T) {
- // Remove all items from all trees
- exhibition.items.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.items.Remove(key)
- return false
- })
- exhibition.itemsSortedByUpvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByUpvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByDownvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByDownvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByCreation.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByCreation.Remove(key)
- return false
- })
-
- // Add items
- testing.SetRealm(std.NewCodeRealm(rlmPath3))
- Register(validTitle, validDesc)
-
- testing.SetRealm(std.NewCodeRealm(rlmPath4))
- Register(validTitle, validDesc)
-
- testing.SetRealm(std.NewCodeRealm(rlmPath5))
- Register(validTitle, validDesc)
-
- user1 := testutils.TestAddress("user1")
- user2 := testutils.TestAddress("user2")
- user3 := testutils.TestAddress("user3")
-
- testing.SetOriginCaller(user1)
- testing.SetRealm(std.NewUserRealm(user1))
- Upvote(rlmPath3)
- Upvote(rlmPath4)
- Upvote(rlmPath5)
-
- testing.SetOriginCaller(user2)
- testing.SetRealm(std.NewUserRealm(user2))
- Upvote(rlmPath4)
- Upvote(rlmPath5)
-
- testing.SetOriginCaller(user3)
- testing.SetRealm(std.NewUserRealm(user3))
- Upvote(rlmPath5)
-
- // We are displaying data in reverse order in render, so items should be sorted in reverse order
- firstKey, firstRawValue := exhibition.itemsSortedByUpvotes.GetByIndex(0)
- firstValue := firstRawValue.(*Item)
- uassert.Equal(t, firstValue.pkgpath, rlmPath3)
-
- secondKey, secondRawValue := exhibition.itemsSortedByUpvotes.GetByIndex(1)
- secondValue := secondRawValue.(*Item)
- uassert.Equal(t, secondValue.pkgpath, rlmPath4)
-}
-
-func TestSortByDownvote(t *testing.T) {
- // Remove all items from all trees
- exhibition.items.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.items.Remove(key)
- return false
- })
- exhibition.itemsSortedByUpvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByUpvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByDownvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByDownvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByCreation.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByCreation.Remove(key)
- return false
- })
-
- // Add items
- testing.SetRealm(std.NewCodeRealm(rlmPath3))
- Register(validTitle, validDesc)
-
- testing.SetRealm(std.NewCodeRealm(rlmPath4))
- Register(validTitle, validDesc)
-
- testing.SetRealm(std.NewCodeRealm(rlmPath5))
- Register(validTitle, validDesc)
-
- user1 := testutils.TestAddress("user1")
- user2 := testutils.TestAddress("user2")
- user3 := testutils.TestAddress("user3")
-
- testing.SetOriginCaller(user1)
- testing.SetRealm(std.NewUserRealm(user1))
- Downvote(rlmPath3)
- Downvote(rlmPath4)
- Downvote(rlmPath5)
-
- testing.SetOriginCaller(user2)
- testing.SetRealm(std.NewUserRealm(user2))
- Downvote(rlmPath4)
- Downvote(rlmPath5)
-
- testing.SetOriginCaller(user3)
- testing.SetRealm(std.NewUserRealm(user3))
- Downvote(rlmPath5)
-
- // We are dispalying data is reverse order in render, so items should be sorted in reverse order
- firstKey, firstRawValue := exhibition.itemsSortedByDownvotes.GetByIndex(0)
-
- firstValue := firstRawValue.(*Item)
-
- uassert.Equal(t, firstValue.pkgpath, rlmPath3)
-
- secondKey, secondRawValue := exhibition.itemsSortedByDownvotes.GetByIndex(1)
-
- secondValue := secondRawValue.(*Item)
-
- uassert.Equal(t, secondValue.pkgpath, rlmPath4)
-}
-
-func TestSortByCreation(t *testing.T) {
- // Remove all items from all trees
- exhibition.items.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.items.Remove(key)
- return false
- })
- exhibition.itemsSortedByUpvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByUpvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByDownvotes.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByDownvotes.Remove(key)
- return false
- })
- exhibition.itemsSortedByCreation.Iterate("", "", func(key string, value interface{}) bool {
- exhibition.itemsSortedByCreation.Remove(key)
- return false
- })
-
- testing.SkipHeights(10)
- testing.SetRealm(std.NewCodeRealm(rlmPath3))
- Register(validTitle, validDesc)
-
- testing.SkipHeights(10)
- testing.SetRealm(std.NewCodeRealm(rlmPath4))
- Register(validTitle, validDesc)
-
- testing.SkipHeights(10)
- testing.SetRealm(std.NewCodeRealm(rlmPath5))
- Register(validTitle, validDesc)
-
- // We are dispalying data is reverse order in render, so items should be sorted in reverse order
- firstKey, firstRawValue := exhibition.itemsSortedByCreation.GetByIndex(0)
-
- firstValue := firstRawValue.(*Item)
-
- uassert.Equal(t, firstValue.pkgpath, rlmPath3)
-
- secondKey, secondRawValue := exhibition.itemsSortedByCreation.GetByIndex(1)
-
- secondValue := secondRawValue.(*Item)
-
- uassert.Equal(t, secondValue.pkgpath, rlmPath4)
-}
diff --git a/examples/gno.land/r/leon/hof/render.gno b/examples/gno.land/r/leon/hof/render.gno
deleted file mode 100644
index 35b31cb87e0..00000000000
--- a/examples/gno.land/r/leon/hof/render.gno
+++ /dev/null
@@ -1,140 +0,0 @@
-package hof
-
-import (
- "net/url"
- "strings"
-
- "gno.land/p/demo/avl"
- "gno.land/p/demo/avl/pager"
- "gno.land/p/demo/fqname"
- "gno.land/p/demo/ufmt"
- "gno.land/p/moul/md"
- "gno.land/p/moul/txlink"
-)
-
-const (
- pageSize = 5
-)
-
-func Render(path string) string {
- out := md.H1("Hall of Fame\n\n")
- out += md.Link("Reset Sort", "?") + " | "
- out += md.Link("Sort by Upvotes", "?sort=upvotes") + " | "
- out += md.Link("Sort by Downvotes", "?sort=downvotes") + " | "
- out += md.Link("Sort by Most Recent", "?sort=creation") + " | "
- out += md.Link("Sort by Oldest", "?sort=oldest") + "\n\n"
-
- dashboardEnabled := path == "dashboard"
-
- if dashboardEnabled {
- out += renderDashboard()
- }
-
- out += exhibition.Render(path, dashboardEnabled)
-
- return out
-}
-
-func (e Exhibition) Render(path string, dashboard bool) string {
- tree := getTreeByPath(&e, path)
-
- u, _ := url.Parse(path)
- reversed := u.Query().Get("sort") != "oldest"
-
- page := pager.NewPager(tree, pageSize, reversed).MustGetPageByPath(path)
-
- out := ufmt.Sprintf("%s\n\n", e.description)
-
- if e.items.Size() == 0 {
- out += "No items in this exhibition currently.\n\n"
- return out
- }
-
- out += "
\n\n"
-
- for _, item := range page.Items {
- out += "
\n\n"
- itemValue := item.Value.(*Item)
- out += md.H3(itemValue.title + "\n\n")
- out += itemValue.Render(dashboard)
- out += "
"
- }
-
- out += "
\n\n"
-
- out += page.Picker(path)
-
- return out
-}
-
-func (i Item) Render(dashboard bool) string {
- out := ufmt.Sprintf("\n%s\n\n", md.CodeBlock(i.pkgpath))
- out += ufmt.Sprintf("%s\n\n", i.description)
- out += ufmt.Sprintf("by %s\n\n", strings.Split(i.pkgpath, "/")[2])
- out += md.Link("View Realm", strings.TrimPrefix(i.pkgpath, "gno.land")) + "\n\n"
- out += ufmt.Sprintf("Submitted at Block #%d\n\n", i.blockNum)
-
- out += md.Bold(ufmt.Sprintf("[%d👍](%s) - [%d👎](%s)",
- i.upvote.Size(), txlink.Call("Upvote", "pkgpath", i.pkgpath),
- i.downvote.Size(), txlink.Call("Downvote", "pkgpath", i.pkgpath),
- ))
-
- if dashboard {
- out += md.Link("Delete", txlink.Call("Delete", "pkgpath", i.pkgpath))
- }
-
- return out
-}
-
-func renderDashboard() string {
- out := md.HorizontalRule()
- out += md.H2("Dashboard\n\n")
- out += ufmt.Sprintf("Total submissions: %d\n\n", exhibition.items.Size())
-
- out += ufmt.Sprintf("Exhibition admin: %s\n\n", Ownable.Owner().String())
-
- if !Pausable.IsPaused() {
- out += md.Link("Pause exhibition", txlink.Call("Pause"))
- } else {
- out += md.Link("Unpause exhibition", txlink.Call("Unpause"))
- }
-
- out += md.HorizontalRule()
-
- return out
-}
-
-func RenderExhibWidget(itemsToRender int) string {
- if itemsToRender < 1 {
- return ""
- }
-
- out := ""
- i := 0
- exhibition.items.Iterate("", "", func(key string, value any) bool {
- item := value.(*Item)
-
- out += ufmt.Sprintf("- %s\n", fqname.RenderLink(item.pkgpath, ""))
-
- i++
- return i >= itemsToRender
- })
-
- return out
-}
-
-func getTreeByPath(e *Exhibition, path string) *avl.Tree {
- u, _ := url.Parse(path)
- switch u.Query().Get("sort") {
- case "upvotes":
- return e.itemsSortedByUpvotes
- case "downvotes":
- return e.itemsSortedByDownvotes
- case "creation":
- return e.itemsSortedByCreation
- case "oldest":
- return e.itemsSortedByCreation
- default:
- return e.items
- }
-}
diff --git a/examples/gno.land/r/leon/home/gno.mod b/examples/gno.land/r/leon/home/gno.mod
deleted file mode 100644
index 56fea265e29..00000000000
--- a/examples/gno.land/r/leon/home/gno.mod
+++ /dev/null
@@ -1 +0,0 @@
-module gno.land/r/leon/home
diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno
deleted file mode 100644
index 657df7f3d6e..00000000000
--- a/examples/gno.land/r/leon/home/home.gno
+++ /dev/null
@@ -1,134 +0,0 @@
-package home
-
-import (
- "std"
- "strconv"
-
- "gno.land/p/demo/ufmt"
-
- "gno.land/r/demo/art/gnoface"
- "gno.land/r/demo/art/millipede"
- "gno.land/r/demo/mirror"
- "gno.land/r/leon/config"
- "gno.land/r/leon/hof"
-)
-
-var (
- pfp string // link to profile picture
- pfpCaption string // profile picture caption
- abtMe [2]string
-)
-
-func Render(path string) string {
- out := "# Leon's Homepage\n\n"
-
- out += renderAboutMe()
- out += renderBlogPosts()
- out += "\n\n"
- out += renderArt()
- out += "\n\n"
- out += config.Banner()
- out += "\n\n"
-
- return out
-}
-
-func init() {
- hof.Register("Leon's Home Realm", "")
- mirror.Register(std.CurrentRealm().PkgPath(), Render)
-
- pfp = "https://i.imgflip.com/91vskx.jpg"
- pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)"
- abtMe = [2]string{
- `### About me
-Hi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast,
-life-long learner, and sharer of knowledge.`,
- `### Contributions
-My contributions to gno.land can mainly be found
-[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).
-
-TODO import r/gh`,
- }
-}
-
-func UpdatePFP(url, caption string) {
- if !config.IsAuthorized(std.PreviousRealm().Address()) {
- panic(config.ErrUnauthorized)
- }
-
- pfp = url
- pfpCaption = caption
-}
-
-func UpdateAboutMe(col1, col2 string) {
- if !config.IsAuthorized(std.PreviousRealm().Address()) {
- panic(config.ErrUnauthorized)
- }
-
- abtMe[0] = col1
- abtMe[1] = col2
-}
-
-func renderBlogPosts() string {
- out := ""
- // out += "## Leon's Blog Posts"
-
- // todo fetch blog posts authored by @leohhhn
- // and render them
- return out
-}
-
-func renderAboutMe() string {
- out := ""
-
- out += "
\n\n"
- out += ufmt.Sprintf("\n\n%s\n\n", pfp, pfpCaption)
- out += "
\n\n"
-
- out += "
\n\n"
- out += abtMe[0] + "\n\n"
- out += "
\n\n"
-
- out += "
\n\n"
- out += abtMe[1] + "\n\n"
- out += "
\n\n"
-
- out += "
\n\n"
-
- return out
-}
-
-func renderArt() string {
- out := `` + "\n\n"
- out += "# Gno Art\n\n"
-
- out += "
"
-
- out += renderGnoFace()
- out += renderMillipede()
- out += "Empty spot :/"
-
- out += "
\n\n"
-
- out += "This art is dynamic; it will change with every new block.\n\n"
- out += `
` + "\n"
-
- return out
-}
-
-func renderGnoFace() string {
- out := "\n\n"
- out += gnoface.Render(strconv.Itoa(int(std.ChainHeight())))
- out += "
\n\n"
-
- return out
-}
-
-func renderMillipede() string {
- out := "\n\n"
- out += "Millipede\n\n"
- out += "```\n" + millipede.Draw(int(std.ChainHeight())%10+1) + "```\n"
- out += "
\n\n"
-
- return out
-}
diff --git a/examples/gno.land/r/leon/registree/gno.mod b/examples/gno.land/r/leon/registree/gno.mod
new file mode 100644
index 00000000000..3af7024740f
--- /dev/null
+++ b/examples/gno.land/r/leon/registree/gno.mod
@@ -0,0 +1 @@
+module gno.land/r/leon/registree
diff --git a/examples/gno.land/r/leon/registree/item.gno b/examples/gno.land/r/leon/registree/item.gno
new file mode 100644
index 00000000000..87f3d64e3bc
--- /dev/null
+++ b/examples/gno.land/r/leon/registree/item.gno
@@ -0,0 +1,11 @@
+package registree
+
+import registry "gno.land/r/leon/registry"
+
+func ManualRegister() {
+ crossing(registry.Register)
+}
+
+func init() {
+ crossing(registry.Register)
+}
diff --git a/examples/gno.land/r/leon/registry/gno.mod b/examples/gno.land/r/leon/registry/gno.mod
new file mode 100644
index 00000000000..fbde31071c0
--- /dev/null
+++ b/examples/gno.land/r/leon/registry/gno.mod
@@ -0,0 +1 @@
+module gno.land/r/leon/registry
diff --git a/examples/gno.land/r/leon/registry/reg.gno b/examples/gno.land/r/leon/registry/reg.gno
new file mode 100644
index 00000000000..e8b3cac35ac
--- /dev/null
+++ b/examples/gno.land/r/leon/registry/reg.gno
@@ -0,0 +1,34 @@
+package registry
+
+import (
+ "std"
+
+ "gno.land/p/demo/avl"
+ "gno.land/p/demo/ufmt"
+)
+
+var tree = avl.NewTree()
+
+func Register() {
+ crossing()
+ if std.PreviousRealm().IsUser() {
+ return
+ }
+
+ if tree.Has(std.PreviousRealm().PkgPath()) {
+ return
+ }
+
+ tree.Set(std.PreviousRealm().PkgPath(), std.PreviousRealm().Address())
+}
+
+func Render(_ string) string {
+ out := "# Registry\n\n"
+
+ tree.Iterate("", "", func(key string, value any) bool {
+ out += ufmt.Sprintf("%s: %s", key, value.(string))
+ return false
+ })
+
+ return out
+}
diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go
index 1b2a729c693..4e2340f3609 100644
--- a/gno.land/pkg/sdk/vm/keeper.go
+++ b/gno.land/pkg/sdk/vm/keeper.go
@@ -437,7 +437,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) {
}
argslist += fmt.Sprintf("arg%d", i)
}
- expr := fmt.Sprintf(`pkg.%s(%s)`, fnc, argslist)
+ expr := fmt.Sprintf(`cross(pkg.%s)(%s)`, fnc, argslist)
xn := gno.MustParseExpr(expr)
// Send send-coins to pkg from caller.
pkgAddr := gno.DerivePkgAddr(pkgPath)
diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go
index a785cf17f1b..1b59a766d5b 100644
--- a/gnovm/cmd/gno/run.go
+++ b/gnovm/cmd/gno/run.go
@@ -112,7 +112,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error {
var send std.Coins
pkgPath := string(files[0].PkgName)
- ctx := test.Context(pkgPath, send)
+ ctx := test.Context("", pkgPath, send)
m := gno.NewMachineWithOptions(gno.MachineOptions{
PkgPath: pkgPath,
Output: output,
diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go
index 2ed3c6321cb..c6cab2be43e 100644
--- a/gnovm/cmd/gno/test.go
+++ b/gnovm/cmd/gno/test.go
@@ -15,7 +15,6 @@ import (
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/gnovm/pkg/test"
"github.com/gnolang/gno/tm2/pkg/commands"
- "github.com/gnolang/gno/tm2/pkg/random"
)
type testCfg struct {
@@ -51,8 +50,8 @@ functions. Benchmark and fuzz functions aren't supported yet. Similarly, only
tests that belong to the same package are supported for now (no "xxx_test").
The package path used to execute the "*_test.gno" file is fetched from the
-module name found in 'gno.mod', or else it is randomly generated like
-"gno.land/r/XXXXXXXX".
+module name found in 'gno.mod', or else it is set to
+"gno.land/r/txtar".
- "*_filetest.gno" files on the other hand are kind of unique. They exist to
provide a way to interact and assert a gno contract, thanks to a set of
@@ -236,9 +235,9 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
} else {
gnoPkgPath = pkgPathFromRootDir(pkg.Dir, cfg.rootDir)
if gnoPkgPath == "" {
- // unable to read pkgPath from gno.mod, generate a random realm path
+ // unable to read pkgPath from gno.mod, use a deterministic path.
io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file")
- gnoPkgPath = "gno.land/r/" + strings.ToLower(random.RandStr(8)) // XXX: gno.land hardcoded for convenience.
+ gnoPkgPath = "gno.land/r/txtar" // XXX: gno.land hardcoded for convenience.
}
}
diff --git a/gnovm/cmd/gno/testdata/test/filetest_events.txtar b/gnovm/cmd/gno/testdata/test/filetest_events.txtar
index 87f873980d5..12ba6110699 100644
--- a/gnovm/cmd/gno/testdata/test/filetest_events.txtar
+++ b/gnovm/cmd/gno/testdata/test/filetest_events.txtar
@@ -34,8 +34,7 @@ func main() {
// {
// "type": "EventA",
// "attrs": [],
-// "pkg_path": "",
-// "func": "main"
+// "pkg_path": ""
// },
// {
// "type": "EventB",
@@ -45,7 +44,6 @@ func main() {
// "value": "valA"
// }
// ],
-// "pkg_path": "",
-// "func": "main"
+// "pkg_path": ""
// }
// ]
diff --git a/gnovm/cmd/gno/testdata/test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar
index 5cb134f46a1..28d4fe1fe9a 100644
--- a/gnovm/cmd/gno/testdata/test/multitest_events.txtar
+++ b/gnovm/cmd/gno/testdata/test/multitest_events.txtar
@@ -3,8 +3,8 @@
gno test -print-events .
! stdout .+
-stderr 'EVENTS: \[{\"type\":\"EventA\",\"attrs\":\[\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestA\"}\]'
-stderr 'EVENTS: \[{\"type\":\"EventB\",\"attrs\":\[{\"key\":\"keyA\",\"value\":\"valA\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"},{\"type\":\"EventC\",\"attrs\":\[{\"key\":\"keyD\",\"value\":\"valD\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"}\]'
+stderr 'EVENTS: \[{\"type\":\"EventA\",\"attrs\":\[\],\"pkg_path\":\"gno.land/r/.*\"\}]'
+stderr 'EVENTS: \[{\"type\":\"EventB\",\"attrs\":\[{\"key\":\"keyA\",\"value\":\"valA\"}\],\"pkg_path\":\"gno.land/r/.*\"},{\"type\":\"EventC\",\"attrs\":\[{\"key\":\"keyD\",\"value\":\"valD\"}\],\"pkg_path\":\"gno.land/r/.*\"}\]'
stderr 'ok \. \d+\.\d\ds'
-- valid.gno --
diff --git a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar
index 0d6ffd24a92..273b1809f93 100644
--- a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar
+++ b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar
@@ -55,6 +55,8 @@ package tests
import "gno.land/r/demo/realm2"
func main() {
+ crossing()
+
realm2.Do()
println("OK")
}
diff --git a/gnovm/cmd/gno/testdata/test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar
index c1763860271..c5db2719a63 100644
--- a/gnovm/cmd/gno/testdata/test/realm_correct.txtar
+++ b/gnovm/cmd/gno/testdata/test/realm_correct.txtar
@@ -14,17 +14,25 @@ package xx
var x int
func main() {
+ crossing()
+
x = 1
}
// Realm:
// switchrealm["gno.land/r/xx"]
-// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]=
-// @@ -19,6 +19,7 @@
+// u[aea84df38908f9569d0f552575606e6e6e7e22dd:3]=
+// @@ -1,11 +1,12 @@
+// {
+// "ObjectInfo": {
+// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3",
+// - "ModTime": "0",
+// + "ModTime": "5",
+// "OwnerID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2",
+// "RefCount": "1"
// },
-// "Values": [
-// {
-// + "N": "AQAAAAAAAAA=",
-// "T": {
-// "@type": "/gno.PrimitiveType",
-// "value": "32"
+// "Value": {
+// + "N": "AQAAAAAAAAA=",
+// "T": {
+// "@type": "/gno.PrimitiveType",
+// "value": "32"
diff --git a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar
index 34a4c62f591..c64e9a1104d 100644
--- a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar
+++ b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar
@@ -6,10 +6,10 @@
stderr '=== RUN file/x_filetest.gno'
stderr 'Realm diff:'
stderr '--- Expected'
-stderr '@@ -1,2 \+1,11 @@'
stderr '-xxxx'
stderr '-xxx'
stderr '\+switchrealm\["gno.land/r/xx"\]'
+stderr '\+ @@ -1,11 \+1,12 @@'
stderr 'x_filetest.gno failed'
-- x_filetest.gno --
@@ -19,6 +19,8 @@ package xx
var x int
func main() {
+ crossing()
+
x = 1
}
diff --git a/gnovm/cmd/gno/testdata/test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar
index c08287e3e21..a2c2980bf37 100644
--- a/gnovm/cmd/gno/testdata/test/realm_sync.txtar
+++ b/gnovm/cmd/gno/testdata/test/realm_sync.txtar
@@ -16,6 +16,8 @@ package xx
var x int
func main() {
+ crossing()
+
x = 1
}
@@ -28,17 +30,25 @@ package xx
var x int
func main() {
+ crossing()
+
x = 1
}
// Realm:
// switchrealm["gno.land/r/xx"]
-// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]=
-// @@ -19,6 +19,7 @@
+// u[aea84df38908f9569d0f552575606e6e6e7e22dd:3]=
+// @@ -1,11 +1,12 @@
+// {
+// "ObjectInfo": {
+// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3",
+// - "ModTime": "0",
+// + "ModTime": "5",
+// "OwnerID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2",
+// "RefCount": "1"
// },
-// "Values": [
-// {
-// + "N": "AQAAAAAAAAA=",
-// "T": {
-// "@type": "/gno.PrimitiveType",
-// "value": "32"
+// "Value": {
+// + "N": "AQAAAAAAAAA=",
+// "T": {
+// "@type": "/gno.PrimitiveType",
+// "value": "32"
diff --git a/gnovm/cmd/gno/testdata/test/two_packages_init.txtar b/gnovm/cmd/gno/testdata/test/two_packages_init.txtar
index 280fd1d529e..1cafa8767b7 100644
--- a/gnovm/cmd/gno/testdata/test/two_packages_init.txtar
+++ b/gnovm/cmd/gno/testdata/test/two_packages_init.txtar
@@ -15,7 +15,7 @@ package aa
import "gno.land/r/demo/bb"
func init() {
- bb.Call()
+ cross(bb).Call()
}
-- examples/gno.land/r/demo/aa/a_test.gno --
@@ -31,6 +31,8 @@ package bb
var called int
func Call() {
+ crossing()
+
called++
}
diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go
index b8ed7b76c35..aa504c73f14 100644
--- a/gnovm/pkg/gnolang/debugger_test.go
+++ b/gnovm/pkg/gnolang/debugger_test.go
@@ -47,7 +47,7 @@ func evalTest(debugAddr, in, file string) (out, err string) {
Input: stdin,
Output: output,
Store: testStore,
- Context: test.Context(string(f.PkgName), nil),
+ Context: test.Context(test.DefaultCaller, string(f.PkgName), nil),
Debug: true,
})
diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go
index cedcb97f50b..0b27dd3eee3 100644
--- a/gnovm/pkg/gnolang/frame.go
+++ b/gnovm/pkg/gnolang/frame.go
@@ -27,15 +27,17 @@ type Frame struct {
NumArgs int // number of arguments in call
IsVarg bool // is form fncall(???, vargs...)
Defers []Defer // deferred calls
- LastPackage *PackageValue // previous package context
- LastRealm *Realm // previous realm context
+ LastPackage *PackageValue // previous frame's package
+ LastRealm *Realm // previous frame's realm
+ WithCross bool // true if called like cross(fn)(...). expects crossing() after.
+ DidCross bool // true if crossing() was called.
Popped bool // true if frame has been popped
}
func (fr Frame) String() string {
if fr.Func != nil {
- return fmt.Sprintf("[FRAME FUNC:%v RECV:%s (%d args) %d/%d/%d/%d/%d LASTPKG:%s LASTRLM:%v]",
+ return fmt.Sprintf("[FRAME FUNC:%v RECV:%s (%d args) %d/%d/%d/%d/%d LASTPKG:%s LASTRLM:%v WSW:%v DSW:%v]",
fr.Func,
fr.Receiver,
fr.NumArgs,
@@ -45,7 +47,9 @@ func (fr Frame) String() string {
fr.NumStmts,
fr.NumBlocks,
fr.LastPackage.PkgPath,
- fr.LastRealm)
+ fr.LastRealm,
+ fr.WithCross,
+ fr.DidCross)
} else {
return fmt.Sprintf("[FRAME LABEL: %s %d/%d/%d/%d/%d]",
fr.Label,
@@ -74,6 +78,20 @@ func (fr *Frame) PopDefer() (res Defer, ok bool) {
return
}
+func (fr *Frame) SetWithCross() {
+ if fr.WithCross {
+ panic("fr.WithCross already set")
+ }
+ fr.WithCross = true
+}
+
+func (fr *Frame) SetDidCross() {
+ if fr.DidCross {
+ panic("fr.DidCross already set")
+ }
+ fr.DidCross = true
+}
+
//----------------------------------------
// Defer
@@ -205,7 +223,7 @@ func toConstExpTrace(cte *ConstExpr) string {
}
}
- return tv.T.String()
+ return tv.V.String()
}
//----------------------------------------
diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go
index a50a727dd6b..b6c0f3af683 100644
--- a/gnovm/pkg/gnolang/go2gno.go
+++ b/gnovm/pkg/gnolang/go2gno.go
@@ -241,15 +241,15 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) {
case *ast.Field:
if len(gon.Names) == 0 {
return &FieldTypeExpr{
- Name: "",
- Type: toExpr(fs, gon.Type),
- Tag: toExpr(fs, gon.Tag),
+ NameExpr: *Nx(""),
+ Type: toExpr(fs, gon.Type),
+ Tag: toExpr(fs, gon.Tag),
}
} else if len(gon.Names) == 1 {
return &FieldTypeExpr{
- Name: toName(gon.Names[0]),
- Type: toExpr(fs, gon.Type),
- Tag: toExpr(fs, gon.Tag),
+ NameExpr: *Nx(toName(gon.Names[0])),
+ Type: toExpr(fs, gon.Type),
+ Tag: toExpr(fs, gon.Tag),
}
} else {
panicWithPos(
@@ -764,17 +764,17 @@ func toFields(fs *token.FileSet, fields ...*ast.Field) (ftxs []FieldTypeExpr) {
if len(f.Names) == 0 {
// a single unnamed field w/ type
ftxs = append(ftxs, FieldTypeExpr{
- Name: "",
- Type: toExpr(fs, f.Type),
- Tag: toExpr(fs, f.Tag),
+ NameExpr: *Nx(""),
+ Type: toExpr(fs, f.Type),
+ Tag: toExpr(fs, f.Tag),
})
} else {
// one or more named fields
for _, n := range f.Names {
ftxs = append(ftxs, FieldTypeExpr{
- Name: toName(n),
- Type: toExpr(fs, f.Type),
- Tag: toExpr(fs, f.Tag),
+ NameExpr: *Nx(toName(n)),
+ Type: toExpr(fs, f.Type),
+ Tag: toExpr(fs, f.Tag),
})
}
}
diff --git a/gnovm/pkg/gnolang/gotypecheck.go b/gnovm/pkg/gnolang/gotypecheck.go
index 8f86deb3dc5..22867e5745f 100644
--- a/gnovm/pkg/gnolang/gotypecheck.go
+++ b/gnovm/pkg/gnolang/gotypecheck.go
@@ -13,6 +13,7 @@ import (
"github.com/gnolang/gno/gnovm"
"go.uber.org/multierr"
+ "golang.org/x/tools/go/ast/astutil"
)
// type checking (using go/types)
@@ -140,6 +141,8 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*t
deleteOldIdents(delFunc, f)
}
+ filterCrossing(f)
+
// enforce formatting
if fmt {
var buf bytes.Buffer
@@ -177,3 +180,27 @@ func deleteOldIdents(idents map[string]func(), f *ast.File) {
}
}
}
+
+func filterCrossing(f *ast.File) {
+ astutil.Apply(f, nil, func(c *astutil.Cursor) bool {
+ switch n := c.Node().(type) {
+ case *ast.ExprStmt:
+ if ce, ok := n.X.(*ast.CallExpr); ok {
+ if id, ok := ce.Fun.(*ast.Ident); ok && id.Name == "crossing" {
+ // Delete statement 'crossing()'.
+ c.Delete()
+ }
+ }
+ case *ast.CallExpr:
+ if id, ok := n.Fun.(*ast.Ident); ok && id.Name == "cross" {
+ // Replace expression 'cross(x)' by 'x'.
+ var a ast.Node
+ if len(n.Args) > 0 {
+ a = n.Args[0]
+ }
+ c.Replace(a)
+ }
+ }
+ return true
+ })
+}
diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go
index 0384c7a9976..f032def4229 100644
--- a/gnovm/pkg/gnolang/helpers.go
+++ b/gnovm/pkg/gnolang/helpers.go
@@ -72,7 +72,7 @@ func N(n interface{}) Name {
case Name:
return n
default:
- panic("unexpected name arg")
+ panic("unexpected name type")
}
}
@@ -136,8 +136,8 @@ func Flds(args ...interface{}) FieldTypeExprs {
list := FieldTypeExprs{}
for i := 0; i < len(args); i += 2 {
list = append(list, FieldTypeExpr{
- Name: N(args[i]),
- Type: X(args[i+1]),
+ NameExpr: *Nx(args[i]),
+ Type: X(args[i+1]),
})
}
return list
@@ -148,8 +148,8 @@ func Recv(n, t interface{}) FieldTypeExpr {
n = blankIdentifier
}
return FieldTypeExpr{
- Name: N(n),
- Type: X(t),
+ NameExpr: *Nx(n),
+ Type: X(t),
}
}
@@ -843,6 +843,24 @@ func SIf(cond bool, then_, else_ Stmt) Stmt {
}
}
+// ----------------------------------------
+// AST Query
+
+// Unwraps IndexExpr and SelectorExpr only.
+// (defensive, in case malformed exprs that mix).
+func LeftmostX(x Expr) Expr {
+ for {
+ switch x := x.(type) {
+ case *IndexExpr:
+ return x.X
+ case *SelectorExpr:
+ return x.X
+ default:
+ return x
+ }
+ }
+}
+
// ----------------------------------------
// chop functions
diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go
index 9c2aa5e8c7e..a4e779f8086 100644
--- a/gnovm/pkg/gnolang/machine.go
+++ b/gnovm/pkg/gnolang/machine.go
@@ -419,8 +419,21 @@ func (m *Machine) RunFiles(fns ...*FileNode) {
if pv == nil {
panic("RunFiles requires Machine.Package")
}
+ rlm := pv.GetRealm()
+ if rlm == nil && pv.IsRealm() {
+ rlm = NewRealm(pv.PkgPath) // throwaway
+ }
updates := m.runFileDecls(IsStdlib(pv.PkgPath), fns...)
+ if rlm != nil {
+ pb := pv.GetBlock(m.Store)
+ for _, update := range updates {
+ rlm.DidUpdate(pb, nil, update.GetFirstObject(m.Store))
+ }
+ }
m.runInitFromUpdates(pv, updates)
+ if rlm != nil {
+ rlm.FinalizeRealmTransaction(m.Store)
+ }
}
// PreprocessFiles runs Preprocess on the given files. It is used to detect
@@ -613,6 +626,8 @@ func (m *Machine) runFileDecls(withOverrides bool, fns ...*FileNode) []TypedValu
// multiple files belonging to the same package in
// lexical file name order to a compiler."
func (m *Machine) runInitFromUpdates(pv *PackageValue, updates []TypedValue) {
+ // Only for the init functions make the origin caller
+ // the package addr.
for _, tv := range updates {
if tv.IsDefined() && tv.T.Kind() == FuncKind && tv.V != nil {
fv, ok := tv.V.(*FuncValue)
@@ -622,7 +637,7 @@ func (m *Machine) runInitFromUpdates(pv *PackageValue, updates []TypedValue) {
if strings.HasPrefix(string(fv.Name), "init.") {
fb := pv.GetFileBlock(m.Store, fv.FileName)
m.PushBlock(fb)
- m.RunFunc(fv.Name)
+ m.RunFunc(fv.Name, false)
m.PopBlock()
}
}
@@ -680,21 +695,25 @@ func (m *Machine) resavePackageValues(rlm *Realm) {
// even after running the init function.
}
-func (m *Machine) RunFunc(fn Name) {
+func (m *Machine) RunFunc(fn Name, withSwitch bool) {
defer func() {
if r := recover(); r != nil {
switch r := r.(type) {
case UnhandledPanicError:
- fmt.Printf("Machine.RunFunc(%q) panic: %s\nStacktrace:\n%s\n",
- fn, r.Error(), m.ExceptionsStacktrace())
+ fmt.Printf("Machine.RunFunc(%q,%v) panic: %s\nStacktrace:\n%s\n",
+ fn, withSwitch, r.Error(), m.ExceptionsStacktrace())
default:
- fmt.Printf("Machine.RunFunc(%q) panic: %v\nMachine State:%s\nStacktrace:\n%s\n",
- fn, r, m.String(), m.Stacktrace().String())
+ fmt.Printf("Machine.RunFunc(%q,%v) panic: %v\nMachine State:%s\nStacktrace:\n%s\n",
+ fn, withSwitch, r, m.String(), m.Stacktrace().String())
}
panic(r)
}
}()
- m.RunStatement(S(Call(Nx(fn))))
+ if withSwitch {
+ m.RunStatement(S(Call(Call(Nx("cross"), Nx(fn)))))
+ } else {
+ m.RunStatement(S(Call(Nx(fn))))
+ }
}
func (m *Machine) RunMain() {
@@ -713,7 +732,11 @@ func (m *Machine) RunMain() {
panic(r)
}
}()
- m.RunStatement(S(Call(X("main"))))
+ if m.Package.IsRealm() {
+ m.RunStatement(S(Call(Nx("cross"), Nx("main"))))
+ } else {
+ m.RunStatement(S(Call(X("main"))))
+ }
}
// Evaluate throwaway expression in new block scope.
@@ -899,9 +922,6 @@ const (
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, [...])
@@ -916,6 +936,10 @@ const (
OpPopFrameAndReset Op = 0x15 // pop frame and reset.
OpPanic1 Op = 0x16 // pop exception and pop call frames.
OpPanic2 Op = 0x17 // pop call frames.
+ OpReturn Op = 0x1A // return ...
+ OpReturnAfterCopy Op = 0x1B // return ... (with named results)
+ OpReturnFromBlock Op = 0x1C // return results (after defers)
+ OpReturnToBlock Op = 0x1D // copy results to block (before defer) XXX rename to OpCopyResultsToBlock
/* Unary & binary operators */
OpUpos Op = 0x20 // + (unary)
@@ -1004,7 +1028,7 @@ const (
OpRangeIterString Op = 0xD4
OpRangeIterMap Op = 0xD5
OpRangeIterArrayPtr Op = 0xD6
- OpReturnCallDefers Op = 0xD7 // TODO rename?
+ OpReturnCallDefers Op = 0xD7 // XXX rename to OpCallDefers
OpVoid Op = 0xFF // For profiling simple operation
)
@@ -1031,9 +1055,6 @@ const (
OpCPUPrecall = 207
OpCPUCall = 256
OpCPUCallNativeBody = 424
- OpCPUReturn = 38
- OpCPUReturnFromBlock = 36
- OpCPUReturnToBlock = 23
OpCPUDefer = 64
OpCPUCallDeferNativeBody = 33
OpCPUGo = 1 // Not yet implemented
@@ -1048,6 +1069,10 @@ const (
OpCPUPopFrameAndReset = 15
OpCPUPanic1 = 121
OpCPUPanic2 = 21
+ OpCPUReturn = 38
+ OpCPUReturnAfterCopy = 38 // XXX
+ OpCPUReturnFromBlock = 36
+ OpCPUReturnToBlock = 23
/* Unary & binary operators */
OpCPUUpos = 7
@@ -1206,6 +1231,9 @@ func (m *Machine) Run() {
case OpReturn:
m.incrCPU(OpCPUReturn)
m.doOpReturn()
+ case OpReturnAfterCopy:
+ m.incrCPU(OpCPUReturnAfterCopy)
+ m.doOpReturnAfterCopy()
case OpReturnFromBlock:
m.incrCPU(OpCPUReturnFromBlock)
m.doOpReturnFromBlock()
@@ -1626,11 +1654,24 @@ func (m *Machine) PeekValue(offset int) *TypedValue {
return &m.Values[m.NumValues-offset]
}
+// Returns a slice of the values stack.
+// Use or copy the result, as well as the slice.
+func (m *Machine) PeekValues(n int) []TypedValue {
+ return m.Values[m.NumValues-n : m.NumValues]
+}
+
// XXX delete?
func (m *Machine) PeekType(offset int) Type {
return m.Values[m.NumValues-offset].T
}
+func (m *Machine) PushValueFromBlock(tv TypedValue) {
+ if hiv, ok := tv.V.(*HeapItemValue); ok {
+ tv = hiv.Value
+ }
+ m.PushValue(tv)
+}
+
func (m *Machine) PushValue(tv TypedValue) {
if debug {
m.Printf("+v %v\n", tv)
@@ -1747,6 +1788,7 @@ 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) {
+ withSwitch := cx.IsWithCross()
fr := &Frame{
Source: cx,
NumOps: m.NumOps,
@@ -1761,6 +1803,8 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) {
Defers: nil,
LastPackage: m.Package,
LastRealm: m.Realm,
+ WithCross: withSwitch,
+ DidCross: false,
}
if debug {
if m.Package == nil {
@@ -1771,29 +1815,81 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) {
m.Printf("+F %#v\n", fr)
}
m.Frames = append(m.Frames, fr)
+
+ // Set the package.
+ // .Package always refers to the code being run,
+ // and may differ from .Realm.
pv := fv.GetPackage(m.Store)
if pv == nil {
panic(fmt.Sprintf("package value missing in store: %s", fv.PkgPath))
}
- rlm := pv.GetRealm()
- if rlm == nil && recv.IsDefined() {
+ m.Package = pv
+
+ // If no realm, return early.
+ if m.Realm == nil {
+ return
+ }
+
+ // If cross, always switch to pv.Realm.
+ // If method, this means the object cannot be modified if
+ // stored externally by this method; but other methods can.
+ if withSwitch {
+ if !fv.IsSwitchRealm() {
+ panic(fmt.Sprintf(
+ "missing crossing() after cross call in %v from %s to %s",
+ fr.Func.String(),
+ m.Realm.Path,
+ pv.Realm.Path,
+ ))
+ }
+ m.Realm = pv.GetRealm()
+ return
+ }
+
+ // Not called like cross(fn)(...).
+ if fv.IsSwitchRealm() {
+ if m.Realm != pv.Realm {
+ // panic; not explicit
+ panic(fmt.Sprintf(
+ "missing cross before external crossing() in %v from %s to %s",
+ fr.Func.String(),
+ m.Realm.Path,
+ pv.Realm.Path,
+ ))
+ } else {
+ // ok
+ // Technically OK even if recv.Realm is different.
+ return
+ }
+ }
+
+ // Not cross nor crossing.
+ // Only "soft" switch to storage realm of receiver.
+ var rlm *Realm
+ if recv.IsDefined() { // method call
obj := recv.GetFirstObject(m.Store)
- if obj == nil {
- // could be a nil receiver.
- // just ignore.
+ if obj == nil { // nil receiver
+ // no switch
+ return
} else {
recvOID := obj.GetObjectInfo().ID
- if !recvOID.IsZero() {
- // override the pv and rlm with receiver's.
+ if recvOID.IsZero() || recvOID.PkgID == m.Realm.ID {
+ // no switch
+ return
+ } else {
+ // implicit switch to storage realm.
+ // neither cross nor didswitch.
recvPkgOID := ObjectIDFromPkgID(recvOID.PkgID)
- pv = m.Store.GetObject(recvPkgOID).(*PackageValue)
- rlm = pv.GetRealm() // done
+ objpv := m.Store.GetObject(recvPkgOID).(*PackageValue)
+ rlm = objpv.GetRealm()
+ m.Realm = rlm
+ fr.DidCross = true
+ return
}
}
- }
- m.Package = pv
- if rlm != nil && m.Realm != rlm {
- m.Realm = rlm // enter new realm
+ } else { // top level function
+ // no switch
+ return
}
}
@@ -1877,25 +1973,26 @@ func (m *Machine) NumFrames() int {
return len(m.Frames)
}
+// Returns the current frame.
func (m *Machine) LastFrame() *Frame {
return m.Frames[len(m.Frames)-1]
}
-// 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)
+// MustPeekCallFrame returns the last call frame with an offset of n. It panics if the frame is not found.
+func (m *Machine) MustPeekCallFrame(n int) *Frame {
+ return m.peekCallFrame(n, true)
}
-// LastCallFrame behaves the same as MustLastCallFrame, but rather than panicking,
+// PeekCallFrame behaves the same as MustPeekCallFrame, but rather than panicking,
// returns nil if the frame is not found.
-func (m *Machine) LastCallFrame(n int) *Frame {
- return m.lastCallFrame(n, false)
+func (m *Machine) PeekCallFrame(n int) *Frame {
+ return m.peekCallFrame(n, false)
}
// 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 {
+func (m *Machine) peekCallFrame(n int, mustBeFound bool) *Frame {
if n == 0 {
panic("n must be positive")
}
@@ -1969,16 +2066,55 @@ func (m *Machine) PushForPointer(lx Expr) {
}
}
+// Pop a pointer (for writing only).
func (m *Machine) PopAsPointer(lx Expr) PointerValue {
+ pv, ro := m.PopAsPointer2(lx)
+ if ro {
+ panic("cannot modify external-realm or non-realm object")
+ }
+ return pv
+}
+
+// Returns true if tv is N_Readonly or, its "first object" resides in a
+// different non-zero realm. Returns false for non-N_Readonly StringValue, free
+// floating pointers, and unreal objects.
+func (m *Machine) IsReadonly(tv *TypedValue) bool {
+ if m.Realm == nil {
+ return false
+ }
+ if tv.IsReadonly() {
+ return true
+ }
+ tvoid, ok := tv.GetFirstObjectID()
+ if !ok {
+ // e.g. if tv is a string, or free floating pointers.
+ return false
+ }
+ if tvoid.IsZero() {
+ return false
+ }
+ if tvoid.PkgID != m.Realm.ID {
+ return true
+ }
+ return false
+}
+
+// Returns ro = true if the base is readonly,
+// or if the base's storage realm != m.Realm and both are non-nil,
+// and the lx isn't a name (base is a block),
+// and the lx isn't a composit lit expr.
+func (m *Machine) PopAsPointer2(lx Expr) (pv PointerValue, ro bool) {
switch lx := lx.(type) {
case *NameExpr:
switch lx.Type {
case NameExprTypeNormal:
lb := m.LastBlock()
- return lb.GetPointerTo(m.Store, lx.Path)
+ pv = lb.GetPointerTo(m.Store, lx.Path)
+ ro = false // always mutable
case NameExprTypeHeapUse:
lb := m.LastBlock()
- return lb.GetPointerToHeapUse(m.Store, lx.Path)
+ pv = lb.GetPointerTo(m.Store, lx.Path)
+ ro = false // always mutable
case NameExprTypeHeapClosure:
panic("should not happen")
default:
@@ -1987,24 +2123,29 @@ func (m *Machine) PopAsPointer(lx Expr) PointerValue {
case *IndexExpr:
iv := m.PopValue()
xv := m.PopValue()
- return xv.GetPointerAtIndex(m.Alloc, m.Store, iv)
+ pv = xv.GetPointerAtIndex(m.Alloc, m.Store, iv)
+ ro = m.IsReadonly(xv)
case *SelectorExpr:
xv := m.PopValue()
- return xv.GetPointerToFromTV(m.Alloc, m.Store, lx.Path)
+ pv = xv.GetPointerToFromTV(m.Alloc, m.Store, lx.Path)
+ ro = m.IsReadonly(xv)
case *StarExpr:
- ptr := m.PopValue().V.(PointerValue)
- return ptr
+ xv := m.PopValue()
+ pv = xv.V.(PointerValue)
+ ro = m.IsReadonly(xv)
case *CompositeLitExpr: // for *RefExpr
tv := *m.PopValue()
hv := m.Alloc.NewHeapItem(tv)
- return PointerValue{
+ pv = PointerValue{
TV: &hv.Value,
Base: hv,
Index: 0,
}
+ ro = false // always mutable
default:
panic("should not happen")
}
+ return
}
// for testing.
@@ -2044,7 +2185,7 @@ func (m *Machine) Panic(ex TypedValue) {
m.Exceptions,
Exception{
Value: ex,
- Frame: m.MustLastCallFrame(1),
+ Frame: m.MustPeekCallFrame(1),
Stacktrace: m.Stacktrace(),
},
)
@@ -2076,7 +2217,7 @@ func (m *Machine) Recover() *Exception {
// 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 != exception.Frame {
+ if frame := m.PeekCallFrame(2); frame == nil || frame != exception.Frame {
return nil
}
}
@@ -2175,14 +2316,6 @@ func (m *Machine) String() string {
} else {
bsi := b.StringIndented(" ")
fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, bsi)
-
- if b.Source != nil {
- sb := b.GetSource(m.Store).GetStaticBlock().GetBlock()
- fmt.Fprintf(builder, " (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" "))
-
- sts := b.GetSource(m.Store).GetStaticBlock().Types
- fmt.Fprintf(builder, " (s typs) %s(%d) %s\n", gens, gen, sts)
- }
}
// Update b
@@ -2213,11 +2346,6 @@ func (m *Machine) String() string {
} else {
fmt.Fprintf(builder, " #%d %s\n", i,
b.StringIndented(" "))
- if b.Source != nil {
- sb := b.GetSource(m.Store).GetStaticBlock().GetBlock()
- fmt.Fprintf(builder, " (static) #%d %s\n", i,
- sb.StringIndented(" "))
- }
}
}
diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go
index 55e95c9450a..d2059ec99bb 100644
--- a/gnovm/pkg/gnolang/nodes.go
+++ b/gnovm/pkg/gnolang/nodes.go
@@ -153,11 +153,13 @@ const (
ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE"
ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE"
ATTR_IOTA GnoAttribute = "ATTR_IOTA"
- ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops.
- ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used.
+ ATTR_HEAP_DEFINES GnoAttribute = "ATTR_HEAP_DEFINES" // []Name heap items.
+ ATTR_HEAP_USES GnoAttribute = "ATTR_HEAP_USES" // []Name heap items used.
ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS"
ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT"
ATTR_GLOBAL GnoAttribute = "ATTR_GLOBAL"
+ ATTR_PACKAGE_REF GnoAttribute = "ATTR_PACKAGE_REF"
+ ATTR_PACKAGE_DECL GnoAttribute = "ATTR_PACKAGE_DECL"
)
type Attributes struct {
@@ -426,8 +428,53 @@ type CallExpr struct { // Func(Args)
Func Expr // function expression
Args Exprs // function arguments, if any.
Varg bool // if true, final arg is variadic.
- NumArgs int // len(Args) or len(Args[0].Results)
+ NumArgs int // len(Args) or len(Args[0].Results).
Addressability addressabilityStatus
+ WithCross bool // if called like cross(fn)(...).
+}
+
+// if x is *ConstExpr returns its source.
+func unwrapConstExpr(x Expr) Expr {
+ if cx, ok := x.(*ConstExpr); ok {
+ return cx.Source
+ }
+ return x
+}
+
+// returns true if x is of form cross(fn)(...).
+func (x *CallExpr) isWithCross() bool {
+ if fnc, ok := x.Func.(*CallExpr); ok {
+ if nx, ok := unwrapConstExpr(fnc.Func).(*NameExpr); ok {
+ if nx.Name == "cross" {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// returns true if x is of form crossing().
+func (x *CallExpr) isSwitchRealm() bool {
+ if x == nil {
+ return false
+ }
+ if nx, ok := unwrapConstExpr(x.Func).(*NameExpr); ok {
+ if nx.Name == "crossing" {
+ return true
+ }
+ }
+ return false
+}
+
+func (x *CallExpr) SetWithCross() {
+ if !x.isWithCross() {
+ panic("expected cross(fn)(...)")
+ }
+ x.WithCross = true
+}
+
+func (x *CallExpr) IsWithCross() bool {
+ return x.WithCross
}
func (x *CallExpr) addressability() addressabilityStatus {
@@ -655,7 +702,7 @@ var (
type FieldTypeExpr struct {
Attributes
- Name
+ NameExpr
Type Expr
// Currently only BasicLitExpr allowed.
@@ -669,19 +716,21 @@ func (x *FieldTypeExpr) addressability() addressabilityStatus {
type FieldTypeExprs []FieldTypeExpr
+// Keep it slow, validating.
+// If you need it faster, memoize it elsewhere.
func (ftxz FieldTypeExprs) IsNamed() bool {
named := false
for i, ftx := range ftxz {
if i == 0 {
- if ftx.Name == "" || isHiddenResultVariable(string(ftx.Name)) {
+ if ftx.Name == "" || isMissingResult(ftx.Name) {
named = false
} else {
named = true
}
} else {
- if named && ftx.Name == "" {
+ if named && (ftx.Name == "" || isMissingResult(ftx.Name)) {
panic("[]FieldTypeExpr has inconsistent namedness (starts named)")
- } else if !named && (ftx.Name != "" || !isHiddenResultVariable(string(ftx.Name))) {
+ } else if !named && (ftx.Name != "" && !isMissingResult(ftx.Name)) {
panic("[]FieldTypeExpr has inconsistent namedness (starts unnamed)")
}
}
@@ -810,6 +859,25 @@ func (ss Body) GetLabeledStmt(label Name) (stmt Stmt, idx int) {
return nil, -1
}
+// Convenience, returns true if first statement is crossing()
+func (ss Body) IsSwitchRealm() bool {
+ return ss.isSwitchRealm()
+}
+
+// XXX deprecate
+func (ss Body) isSwitchRealm() bool {
+ if len(ss) == 0 {
+ return false
+ }
+ fs := ss[0]
+ xs, ok := fs.(*ExprStmt)
+ if !ok {
+ return false
+ }
+ cx, ok := xs.X.(*CallExpr)
+ return cx.isSwitchRealm()
+}
+
// ----------------------------------------
// non-pointer receiver to help make immutable.
@@ -953,7 +1021,8 @@ type RangeStmt struct {
type ReturnStmt struct {
Attributes
- Results Exprs // result expressions; or nil
+ Results Exprs // result expressions; or nil
+ CopyResults bool // copy results to block first
}
type PanicStmt struct {
@@ -1051,12 +1120,13 @@ func (x *bodyStmt) String() string {
active = fmt.Sprintf(" unexpected active: %v", x.Active)
}
}
- return fmt.Sprintf("bodyStmt[%d/%d/%d]=%s%s",
+ return fmt.Sprintf("bodyStmt[%d/%d/%d]=%s%s Active:%v",
x.ListLen,
x.ListIndex,
x.NextBodyIndex,
next,
- active)
+ active,
+ x.Active)
}
// ----------------------------------------
@@ -1111,6 +1181,8 @@ type FuncDecl struct {
Recv FieldTypeExpr // receiver (if method); or empty (if function)
Type FuncTypeExpr // function signature: parameters and results
Body // function body; or empty for external (non-Go) function
+
+ unboundType *FuncTypeExpr // memoized
}
func (x *FuncDecl) GetDeclNames() []Name {
@@ -1121,6 +1193,22 @@ func (x *FuncDecl) GetDeclNames() []Name {
}
}
+// If FuncDecl is for method, construct a FuncTypeExpr with receiver as first
+// parameter.
+func (x *FuncDecl) GetUnboundTypeExpr() *FuncTypeExpr {
+ if x.IsMethod {
+ if x.unboundType == nil {
+ x.unboundType = &FuncTypeExpr{
+ Attributes: x.Type.Attributes,
+ Params: append([]FieldTypeExpr{x.Recv}, x.Type.Params...),
+ Results: x.Type.Results,
+ }
+ }
+ return x.unboundType
+ }
+ return &x.Type
+}
+
type ImportDecl struct {
Attributes
NameExpr // local package name. required.
@@ -1521,20 +1609,31 @@ func (x *PackageNode) PrepareNewValues(pv *PackageValue) []TypedValue {
pnl := len(x.Values)
// copy new top-level defined values/types.
if pvl < pnl {
- // XXX: deep copy heap values
nvs := make([]TypedValue, pnl-pvl)
copy(nvs, x.Values[pvl:pnl])
for i, tv := range nvs {
if fv, ok := tv.V.(*FuncValue); ok {
// copy function value and assign closure from package value.
fv = fv.Copy(nilAllocator)
- fv.Closure = pv.fBlocksMap[fv.FileName]
- if fv.Closure == nil {
+ fv.Parent = pv.fBlocksMap[fv.FileName]
+ if fv.Parent == nil {
panic(fmt.Sprintf("file block missing for file %q", fv.FileName))
}
nvs[i].V = fv
}
}
+ heapItems := x.GetHeapItems()
+ for i, tv := range nvs {
+ if _, ok := tv.T.(heapItemType); ok {
+ panic("unexpected heap item")
+ }
+ if heapItems[pvl+i] {
+ nvs[i] = TypedValue{
+ T: heapItemType{},
+ V: &HeapItemValue{Value: nvs[i]},
+ }
+ }
+ }
block.Values = append(block.Values, nvs...)
return block.Values[pvl:]
} else if pvl > pnl {
@@ -1609,6 +1708,8 @@ type BlockNode interface {
GetBlockNames() []Name
GetExternNames() []Name
GetNumNames() uint16
+ SetIsHeapItem(n Name)
+ GetHeapItems() []bool
GetParentNode(Store) BlockNode
GetPathForName(Store, Name) ValuePath
GetBlockNodeForPath(Store, ValuePath) BlockNode
@@ -1634,9 +1735,11 @@ type StaticBlock struct {
Types []Type
NumNames uint16
Names []Name
+ HeapItems []bool
UnassignableNames []Name
Consts []Name // TODO consider merging with Names.
Externs []Name
+ Parent BlockNode
Loc Location
// temporary storage for rolling back redefinitions.
@@ -1668,16 +1771,36 @@ func (sb *StaticBlock) InitStaticBlock(source BlockNode, parent BlockNode) {
Parent: nil,
}
} else {
- sb.Block = Block{
- Source: source,
- Values: nil,
- Parent: parent.GetStaticBlock().GetBlock(),
+ switch source.(type) {
+ case *IfCaseStmt, *SwitchClauseStmt:
+ if parent == nil {
+ sb.Block = Block{
+ Source: source,
+ Values: nil,
+ Parent: nil,
+ }
+ } else {
+ parent2 := parent.GetParentNode(nil)
+ sb.Block = Block{
+ Source: source,
+ Values: nil,
+ Parent: parent2.GetStaticBlock().GetBlock(),
+ }
+ }
+ default:
+ sb.Block = Block{
+ Source: source,
+ Values: nil,
+ Parent: parent.GetStaticBlock().GetBlock(),
+ }
}
}
sb.NumNames = 0
sb.Names = make([]Name, 0, 16)
+ sb.HeapItems = make([]bool, 0, 16)
sb.Consts = make([]Name, 0, 16)
sb.Externs = make([]Name, 0, 16)
+ sb.Parent = parent
return
}
@@ -1709,14 +1832,14 @@ func (sb *StaticBlock) GetBlock() *Block {
// Implements BlockNode.
func (sb *StaticBlock) GetBlockNames() (ns []Name) {
- return sb.Names // copy?
+ return sb.Names
}
// Implements BlockNode.
// NOTE: Extern names may also be local, if declared after usage as an extern
// (thus shadowing the extern name).
func (sb *StaticBlock) GetExternNames() (ns []Name) {
- return sb.Externs // copy?
+ return sb.Externs
}
func (sb *StaticBlock) addExternName(n Name) {
@@ -1734,13 +1857,22 @@ func (sb *StaticBlock) GetNumNames() (nn uint16) {
}
// Implements BlockNode.
-func (sb *StaticBlock) GetParentNode(store Store) BlockNode {
- pblock := sb.Block.GetParent(store)
- if pblock == nil {
- return nil
- } else {
- return pblock.GetSource(store)
+func (sb *StaticBlock) GetHeapItems() []bool {
+ return sb.HeapItems
+}
+
+// Implements BlockNode.
+func (sb *StaticBlock) SetIsHeapItem(n Name) {
+ idx, ok := sb.GetLocalIndex(n)
+ if !ok {
+ panic("name not found in block")
}
+ sb.HeapItems[idx] = true
+}
+
+// Implements BlockNode.
+func (sb *StaticBlock) GetParentNode(store Store) BlockNode {
+ return sb.Parent
}
// Implements BlockNode.
@@ -1754,26 +1886,34 @@ func (sb *StaticBlock) GetPathForName(store Store, n Name) ValuePath {
if idx, ok := sb.GetLocalIndex(n); ok {
return NewValuePathBlock(uint8(gen), idx, n)
}
+ sn := sb.GetSource(store)
// Register as extern.
// NOTE: uverse names are externs too.
// NOTE: externs may also be shadowed later in the block. Thus, usages
// before the declaration will have depth > 1; following it, depth == 1,
// matching the two different identifiers they refer to.
- if !isFile(sb.GetSource(store)) {
+ if !isFile(sn) {
sb.GetStaticBlock().addExternName(n)
}
// Check ancestors.
gen++
- bp := sb.GetParentNode(store)
- for bp != nil {
- if idx, ok := bp.GetLocalIndex(n); ok {
- return NewValuePathBlock(uint8(gen), idx, n)
+ faux := 0
+ sn = sn.GetParentNode(store)
+ if fauxBlockNode(sn) {
+ faux++
+ }
+ for sn != nil {
+ if idx, ok := sn.GetLocalIndex(n); ok {
+ return NewValuePathBlock(uint8(gen-faux), idx, n)
} else {
- if !isFile(bp) {
- bp.GetStaticBlock().addExternName(n)
+ if !isFile(sn) {
+ sn.GetStaticBlock().addExternName(n)
}
- bp = bp.GetParentNode(store)
gen++
+ sn = sn.GetParentNode(store)
+ if fauxBlockNode(sn) {
+ faux++
+ }
if 0xff < gen {
panic("value path depth overflow")
}
@@ -1797,6 +1937,18 @@ func (sb *StaticBlock) GetBlockNodeForPath(store Store, path ValuePath) BlockNod
bn := sb.GetSource(store)
for i := 1; i < int(path.Depth); i++ {
bn = bn.GetParentNode(store)
+ if fauxBlockNode(bn) {
+ bn = bn.GetParentNode(store)
+ }
+ }
+
+ // If bn is a faux block node, check also its parent.
+ switch bn := bn.(type) {
+ case *IfCaseStmt, *SwitchClauseStmt:
+ pn := bn.GetParentNode(store)
+ if path.Index < pn.GetNumNames() {
+ return pn
+ }
}
return bn
@@ -1956,7 +2108,10 @@ func (sb *StaticBlock) Define(n Name, tv TypedValue) {
// Set type to nil, only reserving the name.
func (sb *StaticBlock) Predefine(isConst bool, n Name) {
- sb.Define2(isConst, n, nil, anyValue(nil))
+ _, exists := sb.GetLocalIndex(n)
+ if !exists {
+ sb.Define2(isConst, n, nil, anyValue(nil))
+ }
}
// The declared type st may not be the same as the static tv;
@@ -2024,6 +2179,7 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) {
} else {
// The general case without re-definition.
sb.Names = append(sb.Names, n)
+ sb.HeapItems = append(sb.HeapItems, false)
if isConst {
sb.Consts = append(sb.Consts, n)
}
@@ -2299,13 +2455,21 @@ func validatePkgName(name string) error {
return nil
}
-const hiddenResultVariable = ".res_"
+// The distinction is used for validation to work
+// both before and after preprocessing.
+const missingResultNamePrefix = ".res." // if there was no name
+const underscoreResultNamePrefix = ".res_" // if was underscore
-func isHiddenResultVariable(name string) bool {
- if strings.HasPrefix(name, hiddenResultVariable) {
- return true
- }
- return false
+func isUnnamedResult(name Name) bool {
+ return isMissingResult(name) || isUnderscoreResult(name)
+}
+
+func isMissingResult(name Name) bool {
+ return strings.HasPrefix(string(name), missingResultNamePrefix)
+}
+
+func isUnderscoreResult(name Name) bool {
+ return strings.HasPrefix(string(name), underscoreResultNamePrefix)
}
type addressabilityStatus int
diff --git a/gnovm/pkg/gnolang/nodes_copy.go b/gnovm/pkg/gnolang/nodes_copy.go
index b5b42764802..c0ae59b1aa5 100644
--- a/gnovm/pkg/gnolang/nodes_copy.go
+++ b/gnovm/pkg/gnolang/nodes_copy.go
@@ -118,9 +118,9 @@ func (x *FuncLitExpr) Copy() Node {
func (x *FieldTypeExpr) Copy() Node {
return &FieldTypeExpr{
- Name: x.Name,
- Type: x.Type.Copy().(Expr),
- Tag: copyExpr(x.Tag),
+ NameExpr: *(x.NameExpr.Copy().(*NameExpr)),
+ Type: x.Type.Copy().(Expr),
+ Tag: copyExpr(x.Tag),
}
}
diff --git a/gnovm/pkg/gnolang/nodes_string.go b/gnovm/pkg/gnolang/nodes_string.go
index e90ea006079..9d2a48d62b4 100644
--- a/gnovm/pkg/gnolang/nodes_string.go
+++ b/gnovm/pkg/gnolang/nodes_string.go
@@ -134,8 +134,6 @@ func (x IndexExpr) String() string {
}
func (x SelectorExpr) String() string {
- // NOTE: for debugging selector issues:
- // return fmt.Sprintf("%s.(%v).%s", n.X, n.Path.Type, n.Sel)
return fmt.Sprintf("%s.%s", x.X, x.Sel)
}
@@ -198,10 +196,14 @@ func (x KeyValueExpr) String() string {
}
func (x FieldTypeExpr) String() string {
+ hd := ""
+ if x.NameExpr.Type == NameExprTypeHeapDefine {
+ hd = "~"
+ }
if x.Tag == nil {
- return fmt.Sprintf("%s %s", x.Name, x.Type)
+ return fmt.Sprintf("%s%s %s", x.Name, hd, x.Type)
}
- return fmt.Sprintf("%s %s %s", x.Name, x.Type, x.Tag)
+ return fmt.Sprintf("%s%s %s %s", x.Name, hd, x.Type, x.Tag)
}
func (x ArrayTypeExpr) String() string {
@@ -316,14 +318,14 @@ func (x IfStmt) String() string {
then := x.Then.String()
els_ := x.Else.String()
if x.Else.Body == nil {
- return fmt.Sprintf("if %s%s { %s }", init, cond, then)
+ return fmt.Sprintf("if %s%s %s", init, cond, then)
}
- return fmt.Sprintf("if %s%s { %s } else { %s }",
+ return fmt.Sprintf("if %s%s %s else %s",
init, cond, then, els_)
}
func (x IfCaseStmt) String() string {
- return x.Body.String()
+ return "{ " + x.Body.String() + " }"
}
func (x IncDecStmt) String() string {
diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go
index a6d959612bc..efebea582e1 100644
--- a/gnovm/pkg/gnolang/op_binary.go
+++ b/gnovm/pkg/gnolang/op_binary.go
@@ -343,7 +343,7 @@ func (m *Machine) doOpBandn() {
// TODO: can be much faster.
func isEql(store Store, lv, rv *TypedValue) bool {
// If one is undefined, the other must be as well.
- // Fields/items are set to defaultValue along the way.
+ // Fields/items are set to defaultTypedValue along the way.
lvu := lv.IsUndefined()
rvu := rv.IsUndefined()
if lvu {
@@ -454,24 +454,7 @@ func isEql(store Store, lv, rv *TypedValue) bool {
panic("function can only be compared with `nil`")
}
}
- if _, ok := lv.V.(*BoundMethodValue); ok {
- // BoundMethodValues are objects so just compare.
- return lv.V == rv.V
- } else if lv.V == nil && rv.V == nil {
- return true
- } else {
- lfv := lv.V.(*FuncValue)
- rfv, ok := rv.V.(*FuncValue)
- if !ok {
- return false
- }
- if lfv.Source.GetLocation() !=
- rfv.Source.GetLocation() {
- return false
- }
- return lfv.GetClosure(store) ==
- rfv.GetClosure(store)
- }
+ return lv.V == rv.V
case PointerKind:
if lv.T != rv.T &&
lv.T.Elem() != DataByteType &&
@@ -483,7 +466,7 @@ func isEql(store Store, lv, rv *TypedValue) bool {
lpv := lv.V.(PointerValue)
rpv := rv.V.(PointerValue)
if lpv.TV.T == DataByteType && rpv.TV.T == DataByteType {
- return *(lpv.TV) == *(rpv.TV) && lpv.Base == rpv.Base && lpv.Index == rpv.Index && lpv.Key == rpv.Key
+ return *(lpv.TV) == *(rpv.TV) && lpv.Base == rpv.Base && lpv.Index == rpv.Index
}
}
return lv.V == rv.V
diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go
index 58155309cc6..42814cd400d 100644
--- a/gnovm/pkg/gnolang/op_call.go
+++ b/gnovm/pkg/gnolang/op_call.go
@@ -17,12 +17,15 @@ func (m *Machine) doOpPrecall() {
panic("should not happen")
}
}
+
+ // handle cross().
switch fv := v.(type) {
case *FuncValue:
m.PushFrameCall(cx, fv, TypedValue{})
m.PushOp(OpCall)
case *BoundMethodValue:
- m.PushFrameCall(cx, fv.Func, fv.Receiver)
+ recv := fv.Receiver
+ m.PushFrameCall(cx, fv.Func, recv)
m.PushOp(OpCall)
case TypeValue:
// Do not pop type yet.
@@ -31,7 +34,6 @@ func (m *Machine) doOpPrecall() {
if cx.GetAttribute(ATTR_SHIFT_RHS) == true {
xv.AssertNonNegative("runtime error: negative shift amount")
}
-
m.PushOp(OpConvert)
if debug {
if len(cx.Args) != 1 {
@@ -47,18 +49,27 @@ func (m *Machine) doOpPrecall() {
var gReturnStmt = &ReturnStmt{}
+func getFuncTypeExprFromSource(source Node) *FuncTypeExpr {
+ if fd, ok := source.(*FuncDecl); ok {
+ return fd.GetUnboundTypeExpr()
+ } else {
+ return &source.(*FuncLitExpr).Type
+ }
+}
+
func (m *Machine) doOpCall() {
// NOTE: Frame won't be popped until the statement is complete, to
// discard the correct number of results for func calls in ExprStmts.
fr := m.LastFrame()
fv := fr.Func
+ fs := fv.GetSource(m.Store)
ft := fr.Func.GetType(m.Store)
pts := ft.Params
numParams := len(pts)
isMethod := 0 // 1 if true
// Create new block scope.
- clo := fr.Func.GetClosure(m.Store)
- b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo)
+ pb := fr.Func.GetParent(m.Store)
+ b := m.Alloc.NewBlock(fs, pb)
// Copy *FuncValue.Captures into block
// NOTE: addHeapCapture in preprocess ensures order.
@@ -90,12 +101,11 @@ func (m *Machine) doOpCall() {
m.PushOp(OpExec)
} else {
// Initialize return variables with default value.
- numParams := len(ft.Params)
for i, rt := range ft.Results {
- // results/parameters never are heap use/closure.
- ptr := b.GetPointerToInt(nil, numParams+i)
dtv := defaultTypedValue(m.Alloc, rt.Type)
- ptr.Assign2(m.Alloc, nil, nil, dtv, false)
+ ptr := b.GetPointerToInt(nil, numParams+i)
+ // Write to existing heap item if result is heap defined.
+ ptr.TV.AssignToBlock(dtv)
}
}
// Exec body.
@@ -117,17 +127,6 @@ func (m *Machine) doOpCall() {
}
// Assign receiver as first parameter, if any.
if !fr.Receiver.IsUndefined() {
- if debug {
- pt := pts[0].Type
- rt := fr.Receiver.T
- if pt.TypeID() != rt.TypeID() {
- panic(fmt.Sprintf(
- "expected %s but got %s",
- pt.String(),
- rt.String()))
- }
- }
- b.Values[0] = fr.Receiver
isMethod = 1
}
// Convert variadic argument to slice argument.
@@ -157,30 +156,19 @@ func (m *Machine) doOpCall() {
}
// Assign non-receiver parameters in forward order.
pvs := m.PopValues(numParams - isMethod)
- for i := isMethod; i < numParams; i++ {
- pv := pvs[i-isMethod]
- if debug {
- // This is how run-time untyped const
- // conversions would work, but we
- // expect the preprocessor to convert
- // these to *ConstExpr.
- /*
- // Convert if untyped const.
- if isUntyped(pv.T) {
- ConvertUntypedTo(&pv, pv.Type)
- }
- */
- if isUntyped(pv.T) {
- panic("unexpected untyped const type for assign during runtime")
- }
+ for i := 0; i < numParams; i++ {
+ var pv TypedValue
+ if i >= isMethod {
+ pv = pvs[i-isMethod]
+ // Make a copy so that a reference to the argument isn't used
+ // in cases where the non-primitive value type is represented
+ // as a pointer, but the declared type is not; e.g. *StructValue
+ // otherwise the struct won't actually be copied by value.
+ pv = pv.Copy(m.Alloc)
+ } else {
+ pv = fr.Receiver
}
- // TODO: some more pt <> pv.Type
- // reconciliations/conversions necessary.
-
- // Make a copy so that a reference to the argument isn't used
- // in cases where the non-primitive value type is represented
- // as a pointer, *StructValue, for example.
- b.Values[i] = pv.Copy(m.Alloc)
+ b.Values[i].AssignToBlock(pv)
}
}
@@ -195,13 +183,43 @@ func (m *Machine) doOpCallDeferNativeBody() {
// Assumes that result values are pushed onto the Values stack.
func (m *Machine) doOpReturn() {
+ // Unwind stack.
cfr := m.PopUntilLastCallFrame()
+
+ // See if we are exiting a realm boundary.
+ crlm := m.Realm
+ if crlm != nil {
+ if cfr.DidCross {
+ // Finalize realm updates!
+ // NOTE: This is a resource intensive undertaking.
+ crlm.FinalizeRealmTransaction(m.Store)
+ }
+ }
+
+ // Finalize
+ m.PopFrameAndReturn()
+}
+
+// Like doOpReturn but first copies results to block.
+func (m *Machine) doOpReturnAfterCopy() {
+ // If there are named results that are heap defined,
+ // need to write to those from stack before returning.
+ cfr := m.MustPeekCallFrame(1)
+ fv := cfr.Func
+ ft := fv.GetType(m.Store)
+ numParams := len(ft.Params)
+ numResults := len(ft.Results)
+ fblock := m.Blocks[cfr.NumBlocks] // frame +1
+ results := m.PeekValues(numResults)
+ for i := 0; i < numResults; i++ {
+ rtv := results[i].Copy(m.Alloc)
+ fblock.Values[numParams+i].AssignToBlock(rtv)
+ }
+
+ // Unwind stack.
+ 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
- // even only finalizing all realm transactions at the end of the
- // original statement execution, but for now we handle them like this,
- // per OpReturn*.
crlm := m.Realm
if crlm != nil {
lrlm := cfr.LastRealm
@@ -219,22 +237,25 @@ func (m *Machine) doOpReturn() {
crlm.FinalizeRealmTransaction(m.Store)
}
}
- // finalize
+
+ // Finalize
m.PopFrameAndReturn()
}
// Like doOpReturn, but with results from the block;
-// i.e. named result vars declared in func signatures.
+// i.e. named result vars declared in func signatures,
+// because return was called with no return arguments.
func (m *Machine) doOpReturnFromBlock() {
// Copy results from block.
cfr := m.PopUntilLastCallFrame()
- ft := cfr.Func.GetType(m.Store)
+ fv := cfr.Func
+ ft := fv.GetType(m.Store)
numParams := len(ft.Params)
numResults := len(ft.Results)
fblock := m.Blocks[cfr.NumBlocks] // frame +1
for i := 0; i < numResults; i++ {
- rtv := fillValueTV(m.Store, &fblock.Values[i+numParams])
- m.PushValue(*rtv)
+ rtv := *fillValueTV(m.Store, &fblock.Values[i+numParams])
+ m.PushValueFromBlock(rtv)
}
// See if we are exiting a realm boundary.
crlm := m.Realm
@@ -262,20 +283,21 @@ func (m *Machine) doOpReturnFromBlock() {
// deferred statements can refer to results with name
// expressions.
func (m *Machine) doOpReturnToBlock() {
- cfr := m.MustLastCallFrame(1)
- ft := cfr.Func.GetType(m.Store)
+ cfr := m.MustPeekCallFrame(1)
+ fv := cfr.Func
+ ft := fv.GetType(m.Store)
numParams := len(ft.Params)
numResults := len(ft.Results)
fblock := m.Blocks[cfr.NumBlocks] // frame +1
results := m.PopValues(numResults)
for i := 0; i < numResults; i++ {
rtv := results[i]
- fblock.Values[numParams+i] = rtv
+ fblock.Values[numParams+i].AssignToBlock(rtv)
}
}
func (m *Machine) doOpReturnCallDefers() {
- cfr := m.MustLastCallFrame(1)
+ cfr := m.MustPeekCallFrame(1)
dfr, ok := cfr.PopDefer()
if !ok {
// Done with defers.
@@ -297,11 +319,9 @@ func (m *Machine) doOpReturnCallDefers() {
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)
+ pb := dfr.Func.GetParent(m.Store)
+ b := m.Alloc.NewBlock(fv.GetSource(m.Store), pb)
// copy values from captures
if len(fv.Captures) != 0 {
if len(fv.Captures) > len(b.Values) {
@@ -330,31 +350,13 @@ func (m *Machine) doOpReturnCallDefers() {
})
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,
- })
- }
+ // Assign parameters in forward order.
+ for i, arg := range dfr.Args {
+ // We need to define, but b was already populated
+ // with new empty heap items, so AssignToBlock is
+ // faster.
+ b.Values[i].AssignToBlock(arg)
}
- copy(b.Values, dfr.Args)
} else {
panic("should not happen")
}
@@ -362,7 +364,7 @@ func (m *Machine) doOpReturnCallDefers() {
func (m *Machine) doOpDefer() {
lb := m.LastBlock()
- cfr := m.MustLastCallFrame(1)
+ cfr := m.MustPeekCallFrame(1)
ds := m.PopStmt().(*DeferStmt)
// Pop arguments
numArgs := len(ds.Call.Args)
@@ -372,6 +374,39 @@ func (m *Machine) doOpDefer() {
// Push defer.
switch cv := ftv.V.(type) {
case *FuncValue:
+ fv := cv
+ ft := fv.GetType(m.Store)
+ pts := ft.Params
+ numParams := len(pts)
+ isMethod := 0
+ nvar := numArgs - (numParams - 1 - isMethod)
+ if ft.HasVarg() {
+ if ds.Call.Varg {
+ // Do nothing, last arg type is already slice
+ // type called with form fncall(?, vargs...)
+ if debug {
+ if nvar != 1 {
+ panic("should not happen")
+ }
+ }
+ } else {
+ // Convert last nvar to slice.
+ vart := pts[numParams-1].Type.(*SliceType)
+ list := make([]TypedValue, nvar)
+ copy(list, args[numParams-1-isMethod:])
+ varg := m.Alloc.NewSliceFromList(list)
+ args = append(args[:numParams-1-isMethod], TypedValue{
+ T: vart,
+ V: varg,
+ })
+ }
+ }
+ /*
+ for i := 0; i < numParams; i++ {
+ // args will be copy()'d to block later.
+ args[i].DefineToBlock(args[i])
+ }
+ */
cfr.PushDefer(Defer{
Func: cv,
Args: args,
@@ -380,21 +415,47 @@ func (m *Machine) doOpDefer() {
PanicScope: m.PanicScope,
})
case *BoundMethodValue:
- if debug {
- pt := cv.Func.GetType(m.Store).Params[0]
- rt := cv.Receiver.T
- if pt.TypeID() != rt.TypeID() {
- panic(fmt.Sprintf(
- "expected %s but got %s",
- pt.String(),
- rt.String()))
+ fv := cv.Func
+ ft := fv.GetType(m.Store)
+ pts := ft.Params
+ numParams := len(pts)
+ isMethod := 1
+ nvar := numArgs - (numParams - 1 - isMethod)
+ if ft.HasVarg() {
+ if ds.Call.Varg {
+ // Do nothing, last arg type is already slice
+ // type called with form fncall(?, vargs...)
+ if debug {
+ if nvar != 1 {
+ panic("should not happen")
+ }
+ }
+ } else {
+ // Convert last nvar to slice.
+ vart := pts[numParams-1].Type.(*SliceType)
+ list := make([]TypedValue, nvar)
+ copy(list, args[numParams-1-isMethod:])
+ varg := m.Alloc.NewSliceFromList(list)
+ args = append(args[:numParams-1-isMethod], TypedValue{
+ T: vart,
+ V: varg,
+ })
}
}
args2 := make([]TypedValue, len(args)+1)
- args2[0] = cv.Receiver
- copy(args2[1:], args)
+ // Make heap item if param is heap defined.
+ // This also heap escapes the receiver.
+ for i := 0; i < numParams; i++ {
+ var pv TypedValue
+ if i >= isMethod {
+ pv = args[i-isMethod]
+ } else {
+ pv = cv.Receiver
+ }
+ args2[i] = pv
+ }
cfr.PushDefer(Defer{
- Func: cv.Func,
+ Func: fv,
Args: args2,
Source: ds,
Parent: lb,
diff --git a/gnovm/pkg/gnolang/op_decl.go b/gnovm/pkg/gnolang/op_decl.go
index 6dbae2d3edf..bfed8efb89d 100644
--- a/gnovm/pkg/gnolang/op_decl.go
+++ b/gnovm/pkg/gnolang/op_decl.go
@@ -23,8 +23,10 @@ func (m *Machine) doOpValueDecl() {
// requiring the consideration of the typed-nil case.
if nt == nil {
tv = TypedValue{}
+ } else if nt.Kind() == InterfaceKind {
+ tv = TypedValue{}
} else {
- tv = TypedValue{T: nt, V: defaultValue(m.Alloc, nt)}
+ tv = defaultTypedValue(m.Alloc, nt)
}
} else {
tv = rvs[i]
diff --git a/gnovm/pkg/gnolang/op_eval.go b/gnovm/pkg/gnolang/op_eval.go
index 5a9da93fbbd..bdb6f272dff 100644
--- a/gnovm/pkg/gnolang/op_eval.go
+++ b/gnovm/pkg/gnolang/op_eval.go
@@ -21,7 +21,6 @@ func (m *Machine) doOpEval() {
x := m.PeekExpr(1)
if debug {
debug.Printf("EVAL: (%T) %v\n", x, x)
- // fmt.Println(m.String())
}
// This case moved out of switch for performance.
// TODO: understand this better.
@@ -36,7 +35,7 @@ func (m *Machine) doOpEval() {
// Get value from scope.
lb := m.LastBlock()
// Push value, done.
- ptr := lb.GetPointerToMaybeHeapUse(m.Store, nx)
+ ptr := lb.GetPointerTo(m.Store, nx.Path)
m.PushValue(ptr.Deref())
return
}
@@ -248,7 +247,11 @@ func (m *Machine) doOpEval() {
m.PushOp(OpEval)
}
// evaluate func
- m.PushExpr(x.Func)
+ if x.IsWithCross() {
+ m.PushExpr(x.Func.(*CallExpr).Args[0])
+ } else {
+ m.PushExpr(x.Func)
+ }
m.PushOp(OpEval)
case *IndexExpr:
if x.HasOK {
diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go
index 03060803a46..c59ba7ea026 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.MustLastCallFrame(1)
+ fr := m.MustPeekCallFrame(1)
ft := fr.Func.GetType(m.Store)
hasDefers := 0 < len(fr.Defers)
hasResults := 0 < len(ft.Results)
@@ -561,6 +561,8 @@ EXEC_SWITCH:
} else {
if cs.Results == nil {
m.PushOp(OpReturnFromBlock)
+ } else if cs.CopyResults {
+ m.PushOp(OpReturnAfterCopy)
} else {
m.PushOp(OpReturn)
}
@@ -686,10 +688,8 @@ EXEC_SWITCH:
// compute next switch clause from BodyIndex (assigned in preprocess)
nextClause := cs.BodyIndex + 1
// expand block size
- cl := ss.Clauses[nextClause]
- if nn := cl.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
+ cl := &ss.Clauses[nextClause]
+ b.ExpandWith(m.Alloc, cl)
// exec clause body
b.bodyStmt = bodyStmt{
Body: cl.Body,
@@ -783,9 +783,7 @@ func (m *Machine) doOpIfCond() {
if cond.GetBool() {
if len(is.Then.Body) != 0 {
// expand block size
- if nn := is.Then.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
+ b.ExpandWith(m.Alloc, &is.Then)
// exec then body
b.bodyStmt = bodyStmt{
Body: is.Then.Body,
@@ -798,9 +796,7 @@ func (m *Machine) doOpIfCond() {
} else {
if len(is.Else.Body) != 0 {
// expand block size
- if nn := is.Else.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
+ b.ExpandWith(m.Alloc, &is.Else)
// exec then body
b.bodyStmt = bodyStmt{
Body: is.Else.Body,
@@ -865,20 +861,20 @@ func (m *Machine) doOpTypeSwitch() {
if match { // did match
if len(cs.Body) != 0 {
b := m.LastBlock()
+ // remember size (from init)
+ size := len(b.Values)
+ // expand block size
+ b.ExpandWith(m.Alloc, cs)
// define if varname
if ss.VarName != "" {
- // NOTE: assumes the var is first in block.
+ // NOTE: assumes the var is first after size.
vp := NewValuePath(
- VPBlock, 1, 0, ss.VarName)
+ VPBlock, 1, uint16(size), ss.VarName)
// NOTE: GetPointerToMaybeHeapDefine not needed,
// because this type is in new type switch clause block.
ptr := b.GetPointerTo(m.Store, vp)
ptr.TV.Assign(m.Alloc, *xv, false)
}
- // expand block size
- if nn := cs.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
// exec clause body
b.bodyStmt = bodyStmt{
Body: cs.Body,
@@ -907,7 +903,7 @@ func (m *Machine) doOpSwitchClause() {
m.PopValue() // pop clause index
// done!
} else {
- cl := ss.Clauses[idx]
+ cl := &ss.Clauses[idx]
if len(cl.Cases) == 0 {
// default clause
m.PopStmt() // pop switch stmt
@@ -916,9 +912,7 @@ func (m *Machine) doOpSwitchClause() {
m.PopValue() // clause index no longer needed
// expand block size
b := m.LastBlock()
- if nn := cl.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
+ b.ExpandWith(m.Alloc, cl)
// exec clause body
b.bodyStmt = bodyStmt{
Body: cl.Body,
@@ -956,11 +950,9 @@ func (m *Machine) doOpSwitchClauseCase() {
m.PopValue() // pop clause index
// expand block size
clidx := cliv.GetInt()
- cl := ss.Clauses[clidx]
+ cl := &ss.Clauses[clidx]
b := m.LastBlock()
- if nn := cl.GetNumNames(); int(nn) > len(b.Values) {
- b.ExpandToSize(m.Alloc, nn)
- }
+ b.ExpandWith(m.Alloc, cl)
// exec clause body
b.bodyStmt = bodyStmt{
Body: cl.Body,
diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go
index 79e9e077af2..29988a19109 100644
--- a/gnovm/pkg/gnolang/op_expressions.go
+++ b/gnovm/pkg/gnolang/op_expressions.go
@@ -8,13 +8,10 @@ import (
// NOTE: keep in sync with doOpIndex2.
func (m *Machine) doOpIndex1() {
- if debug {
- _ = m.PopExpr().(*IndexExpr)
- } else {
- m.PopExpr()
- }
+ m.PopExpr()
iv := m.PopValue() // index
xv := m.PeekValue(1) // x
+ ro := m.IsReadonly(xv)
switch ct := baseOf(xv.T).(type) {
case *MapType:
mv := xv.V.(*MapValue)
@@ -23,35 +20,27 @@ func (m *Machine) doOpIndex1() {
*xv = vv // reuse as result
} else {
vt := ct.Value
- *xv = TypedValue{ // reuse as result
- T: vt,
- V: defaultValue(m.Alloc, vt),
- }
+ *xv = defaultTypedValue(m.Alloc, vt) // reuse as result
}
default:
res := xv.GetPointerAtIndex(m.Alloc, m.Store, iv)
*xv = res.Deref() // reuse as result
}
+ xv.SetReadonly(ro)
}
// NOTE: keep in sync with doOpIndex1.
func (m *Machine) doOpIndex2() {
- if debug {
- _ = m.PopExpr().(*IndexExpr)
- } else {
- m.PopExpr()
- }
+ m.PopExpr()
iv := m.PeekValue(1) // index
xv := m.PeekValue(2) // x
+ ro := m.IsReadonly(xv)
switch ct := baseOf(xv.T).(type) {
case *MapType:
vt := ct.Value
if xv.V == nil { // uninitialized map
- *xv = TypedValue{ // reuse as result
- T: vt,
- V: defaultValue(m.Alloc, vt),
- }
- *iv = untypedBool(false) // reuse as result
+ *xv = defaultTypedValue(m.Alloc, vt) // reuse as result
+ *iv = untypedBool(false) // reuse as result
} else {
mv := xv.V.(*MapValue)
vv, exists := mv.GetValueForKey(m.Store, iv)
@@ -59,27 +48,27 @@ func (m *Machine) doOpIndex2() {
*xv = vv // reuse as result
*iv = untypedBool(true) // reuse as result
} else {
- *xv = TypedValue{ // reuse as result
- T: vt,
- V: defaultValue(m.Alloc, vt),
- }
- *iv = untypedBool(false) // reuse as result
+ *xv = defaultTypedValue(m.Alloc, vt) // reuse as result
+ *iv = untypedBool(false) // reuse as result
}
}
default:
panic("should not happen")
}
+ xv.SetReadonly(ro)
}
func (m *Machine) doOpSelector() {
sx := m.PopExpr().(*SelectorExpr)
- xv := m.PeekValue(1)
+ xv := m.PeekValue(1) // package, struct, whatever.
+ ro := m.IsReadonly(xv)
res := xv.GetPointerToFromTV(m.Alloc, m.Store, sx.Path).Deref()
if debug {
m.Printf("-v[S] %v\n", xv)
m.Printf("+v[S] %v\n", res)
}
*xv = res // reuse as result
+ xv.SetReadonly(ro)
}
func (m *Machine) doOpSlice() {
@@ -101,12 +90,18 @@ func (m *Machine) doOpSlice() {
}
// slice base x
xv := m.PopValue()
+ ro := m.IsReadonly(xv)
// if a is a pointer to an array, a[low : high : max] is
// shorthand for (*a)[low : high : max]
+ // XXX fix this in precompile instead.
if xv.T.Kind() == PointerKind &&
xv.T.Elem().Kind() == ArrayKind {
// simply deref xv.
*xv = xv.V.(PointerValue).Deref()
+ // check array also for ro.
+ if !ro {
+ ro = xv.IsReadonly()
+ }
}
// fill default based on xv
if sx.High == nil {
@@ -115,10 +110,10 @@ func (m *Machine) doOpSlice() {
// all low:high:max cases
if maxVal == -1 {
sv := xv.GetSlice(m.Alloc, lowVal, highVal)
- m.PushValue(sv)
+ m.PushValue(sv.WithReadonly(ro))
} else {
sv := xv.GetSlice2(m.Alloc, lowVal, highVal, maxVal)
- m.PushValue(sv)
+ m.PushValue(sv.WithReadonly(ro))
}
}
@@ -152,17 +147,13 @@ func (m *Machine) doOpStar() {
tv.SetUint8(dbv.GetByte())
m.PushValue(tv)
} else {
- if pv.TV.IsUndefined() && bt.Elt.Kind() != InterfaceKind {
- refv := TypedValue{T: bt.Elt}
- m.PushValue(refv)
- } else {
- m.PushValue(*pv.TV)
- }
+ ro := m.IsReadonly(xv)
+ pvtv := (*pv.TV).WithReadonly(ro)
+ m.PushValue(pvtv)
}
case *TypeType:
t := xv.GetType()
pt := &PointerType{Elt: t}
-
m.PushValue(asValue(pt))
default:
panic(fmt.Sprintf(
@@ -174,17 +165,16 @@ func (m *Machine) doOpStar() {
// XXX this is wrong, for var i interface{}; &i is *interface{}.
func (m *Machine) doOpRef() {
rx := m.PopExpr().(*RefExpr)
- m.Alloc.AllocatePointer()
- xv := m.PopAsPointer(rx.X)
- // when obtaining a pointer of the databyte type, use the ElemType of databyte
+ xv, ro := m.PopAsPointer2(rx.X)
elt := xv.TV.T
if elt == DataByteType {
elt = xv.TV.V.(DataByteValue).ElemType
}
+ m.Alloc.AllocatePointer()
m.PushValue(TypedValue{
T: m.Alloc.NewType(&PointerType{Elt: elt}),
V: xv,
- })
+ }.WithReadonly(ro))
}
// NOTE: keep in sync with doOpTypeAssert2.
@@ -317,10 +307,7 @@ func (m *Machine) doOpTypeAssert2() {
}
} else { // is concrete assert
if xt == nil {
- *xv = TypedValue{
- T: t,
- V: defaultValue(m.Alloc, t),
- }
+ *xv = defaultTypedValue(m.Alloc, t)
*tv = untypedBool(false)
return
}
@@ -334,10 +321,7 @@ func (m *Machine) doOpTypeAssert2() {
// *xv = *xv
*tv = untypedBool(true)
} else {
- *xv = TypedValue{
- T: t,
- V: defaultValue(m.Alloc, t),
- }
+ *xv = defaultTypedValue(m.Alloc, t)
*tv = untypedBool(false)
}
}
@@ -673,30 +657,32 @@ func (m *Machine) doOpFuncLit() {
// every invocation of the function.
captures := []TypedValue(nil)
for _, nx := range x.HeapCaptures {
- ptr := lb.GetPointerTo(m.Store, nx.Path)
+ ptr := lb.GetPointerToDirect(m.Store, nx.Path)
// check that ptr.TV is a heap item value.
// it must be in the form of:
// {T:heapItemType{},V:HeapItemValue{...}}
if _, ok := ptr.TV.T.(heapItemType); !ok {
- panic("should not happen, should be heapItemType")
+ panic("should not happen, should be heapItemType: " + nx.String())
}
if _, ok := ptr.TV.V.(*HeapItemValue); !ok {
- panic("should not happen, should be heapItemValue")
+ panic("should not happen, should be heapItemValue: " + nx.String())
}
captures = append(captures, *ptr.TV)
}
m.PushValue(TypedValue{
T: ft,
V: &FuncValue{
- Type: ft,
- IsMethod: false,
- Source: x,
- Name: "",
- Closure: lb,
- Captures: captures,
- PkgPath: m.Package.PkgPath,
- body: x.Body,
- nativeBody: nil,
+ Type: ft,
+ IsMethod: false,
+ IsClosure: true,
+ Source: x,
+ Name: "",
+ Parent: nil,
+ Captures: captures,
+ PkgPath: m.Package.PkgPath,
+ SwitchRealm: x.Body.isSwitchRealm(),
+ body: x.Body,
+ nativeBody: nil,
},
})
}
@@ -704,6 +690,37 @@ func (m *Machine) doOpFuncLit() {
func (m *Machine) doOpConvert() {
xv := m.PopValue()
t := m.PopValue().GetType()
+
+ // BEGIN conversion checks
+ // These protect against inter-realm conversion exploits.
+
+ // Case 1.
+ // Do not allow conversion of value stored in eternal realm.
+ // Otherwise anyone could convert an external object insecurely.
+ if xv.T != nil && !xv.T.IsImmutable() && m.IsReadonly(xv) {
+ if xvdt, ok := xv.T.(*DeclaredType); ok &&
+ xvdt.PkgPath == m.Realm.Path {
+ // Except allow if xv.T is m.Realm.
+ // XXX do we need/want this?
+ } else {
+ panic("illegal conversion of readonly or externally stored value")
+ }
+ }
+
+ // Case 2.
+ // Do not allow conversion to type of external realm.
+ // Only code declared within the same realm my perform such
+ // conversions, otherwise the realm could be tricked
+ // into executing a subtle exploit of mutating some
+ // value (say a pointer) stored in its own realm by
+ // a hostile construction converted to look safe.
+ if tdt, ok := t.(*DeclaredType); ok && !tdt.IsImmutable() && m.Realm != nil {
+ if IsRealmPath(tdt.PkgPath) && tdt.PkgPath != m.Realm.Path {
+ panic("illegal conversion to external realm type")
+ }
+ }
+ // END conversion checks
+
ConvertTo(m.Alloc, m.Store, xv, t, false)
m.PushValue(*xv)
}
diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go
index 73d83e32983..d3ed2267e40 100644
--- a/gnovm/pkg/gnolang/op_unary.go
+++ b/gnovm/pkg/gnolang/op_unary.go
@@ -78,7 +78,7 @@ func (m *Machine) doOpUnot() {
case BoolType, UntypedBoolType:
xv.SetBool(!xv.GetBool())
default:
- panic(fmt.Sprintf("unexpected type %s in operation",
+ panic(fmt.Sprintf("unexpected type %v in operation",
baseOf(xv.T)))
}
}
diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go
index 6ce92c19ca4..752c944098f 100644
--- a/gnovm/pkg/gnolang/ownership.go
+++ b/gnovm/pkg/gnolang/ownership.go
@@ -84,6 +84,10 @@ func (oid ObjectID) IsZero() bool {
return oid.PkgID.IsZero()
}
+type ObjectIDer interface {
+ GetObjectID() ObjectID
+}
+
type Object interface {
Value
GetObjectInfo() *ObjectInfo
@@ -126,6 +130,7 @@ type Object interface {
var (
_ Object = &ArrayValue{}
_ Object = &StructValue{}
+ _ Object = &FuncValue{}
_ Object = &BoundMethodValue{}
_ Object = &MapValue{}
_ Object = &Block{}
@@ -354,11 +359,13 @@ func (tv *TypedValue) GetFirstObject(store Store) Object {
case *StructValue:
return cv
case *FuncValue:
- return cv.GetClosure(store)
+ return cv
case *MapValue:
return cv
case *BoundMethodValue:
return cv
+ case *PackageValue:
+ return cv.GetBlock(store)
case *Block:
return cv
case RefValue:
@@ -372,3 +379,37 @@ func (tv *TypedValue) GetFirstObject(store Store) Object {
return nil
}
}
+
+// returns false if there is no object.
+func (tv *TypedValue) GetFirstObjectID() (ObjectID, bool) {
+ switch cv := tv.V.(type) {
+ case PointerValue:
+ if cv.Base == nil {
+ return ObjectID{}, false
+ }
+ return cv.Base.(ObjectIDer).GetObjectID(), true
+ case *ArrayValue:
+ return cv.GetObjectID(), true
+ case *SliceValue:
+ return cv.Base.(ObjectIDer).GetObjectID(), true
+ case *StructValue:
+ return cv.GetObjectID(), true
+ case *FuncValue:
+ return cv.GetObjectID(), true
+ case *MapValue:
+ return cv.GetObjectID(), true
+ case *BoundMethodValue:
+ return cv.GetObjectID(), true
+ case *PackageValue:
+ return cv.Block.(ObjectIDer).GetObjectID(), true
+ case *Block:
+ return cv.GetObjectID(), true
+ case RefValue:
+ return cv.GetObjectID(), true
+ case *HeapItemValue:
+ // should only appear in PointerValue.Base
+ panic("heap item value should only appear as a pointer's base")
+ default:
+ return ObjectID{}, false
+ }
+}
diff --git a/gnovm/pkg/gnolang/package.go b/gnovm/pkg/gnolang/package.go
index 5d80b40451e..328623f0608 100644
--- a/gnovm/pkg/gnolang/package.go
+++ b/gnovm/pkg/gnolang/package.go
@@ -124,4 +124,5 @@ var Package = amino.RegisterPackage(amino.NewPackage(
blockType{},
&tupleType{},
RefType{},
+ heapItemType{},
))
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index 89396716c69..6a3eb52f7f2 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -116,6 +116,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) {
Attributes: base.NameExprs[j].Attributes,
Path: base.NameExprs[j].Path,
Name: base.NameExprs[j].Name,
+ Type: NameExprTypeDefine,
}}
if j < len(base.Values) {
@@ -140,6 +141,8 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) {
}
// Initialize static block info.
+// TODO: ensure and keep idempotent.
+// PrpedefineFileSet may precede Preprocess.
func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
// create stack of BlockNodes.
var stack []BlockNode = make([]BlockNode, 0, 32)
@@ -166,7 +169,7 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
continue
}
if !isLocallyDefined2(last, ln) {
- // if loop extern, will promote to
+ // if loopvar, will promote to
// NameExprTypeHeapDefine later.
nx.Type = NameExprTypeDefine
last.Predefine(false, ln)
@@ -224,6 +227,11 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
// this also makes them unreferenceable.
dname := Name(fmt.Sprintf("init.%d", idx))
n.Name = dname
+ } else if n.Name == blankIdentifier {
+ idx := pkg.GetNumNames()
+ dname := Name(fmt.Sprintf("._%d", idx))
+ n.Name = dname
+
}
nx := &n.NameExpr
nx.Type = NameExprTypeDefine
@@ -242,11 +250,13 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
}
for i := range n.Results {
r := &n.Results[i]
+ if r.Name == "" {
+ // create an unnamed name with leading dot.
+ r.Name = Name(fmt.Sprintf("%s%d", missingResultNamePrefix, i))
+ }
if r.Name == blankIdentifier {
- // create a hidden var with leading dot.
- // NOTE: document somewhere.
- rn := fmt.Sprintf("%s%d", hiddenResultVariable, i)
- r.Name = Name(rn)
+ // create an underscore name with leading dot.
+ r.Name = Name(fmt.Sprintf("%s%d", underscoreResultNamePrefix, i))
}
}
}
@@ -268,38 +278,35 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
if n.Key != nil {
nx := n.Key.(*NameExpr)
if nx.Name != blankIdentifier {
- // XXX, this should be uncommented when fully
- // support Go1.22 loopvar, to make it consistent
- // with for i := 0; i < 10; i++ {...}.
- // nx.Type = NameExprTypeDefine
-
+ nx.Type = NameExprTypeDefine
last.Predefine(false, nx.Name)
}
}
if n.Value != nil {
nx := n.Value.(*NameExpr)
if nx.Name != blankIdentifier {
- // nx.Type = NameExprTypeDefine // XXX,ditto
+ nx.Type = NameExprTypeDefine
last.Predefine(false, nx.Name)
}
}
}
case *FuncLitExpr:
- for _, p := range n.Type.Params {
- last.Predefine(false, p.Name)
+ for i := range n.Type.Params {
+ px := &n.Type.Params[i].NameExpr
+ px.Type = NameExprTypeDefine
+ last.Predefine(false, px.Name)
}
- for _, rf := range n.Type.Results {
- if rf.Name != "" {
- last.Predefine(false, rf.Name)
+ for i := range n.Type.Results {
+ rx := &n.Type.Results[i].NameExpr
+ if rx.Name == "" {
+ rn := fmt.Sprintf("%s%d", missingResultNamePrefix, i)
+ rx.Name = Name(rn)
}
+ rx.Type = NameExprTypeDefine
+ last.Predefine(false, rx.Name)
}
case *SwitchStmt:
- if n.VarName != "" {
- // NOTE: this defines for default clauses too,
- // see comment on block copying @
- // SwitchClauseStmt:TRANS_BLOCK.
- last.Predefine(false, n.VarName)
- }
+ // n.Varname is declared in each clause.
case *SwitchClauseStmt:
blen := len(n.Body)
if blen > 0 {
@@ -308,13 +315,14 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
// parent switch statement.
ss := ns[len(ns)-1].(*SwitchStmt)
- // anything declared in ss are copied,
- // namely ss.VarName if defined.
+ // anything declared in ss.init are copied.
for _, n := range ss.GetBlockNames() {
last.Predefine(false, n)
}
if ss.IsTypeSwitch {
if ss.VarName != "" {
+ // XXX NameExprTypeDefine in NameExpr?
+ // XXX XXX fix and test.
last.Predefine(false, ss.VarName)
}
} else {
@@ -324,20 +332,25 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
}
case *FuncDecl:
if n.IsMethod {
+ n.Recv.NameExpr.Type = NameExprTypeDefine
n.Predefine(false, n.Recv.Name)
}
- for _, pte := range n.Type.Params {
- if pte.Name == "" {
+ for i := range n.Type.Params {
+ px := &n.Type.Params[i].NameExpr
+ if px.Name == "" {
panic("should not happen")
}
- n.Predefine(false, pte.Name)
+ px.Type = NameExprTypeDefine
+ n.Predefine(false, px.Name)
}
- for i, rte := range n.Type.Results {
- if rte.Name == "" {
- rn := fmt.Sprintf("%s%d", hiddenResultVariable, i)
- rte.Name = Name(rn)
+ for i := range n.Type.Results {
+ rx := &n.Type.Results[i].NameExpr
+ if rx.Name == "" {
+ rn := fmt.Sprintf("%s%d", missingResultNamePrefix, i)
+ rx.Name = Name(rn)
}
- n.Predefine(false, rte.Name)
+ rx.Type = NameExprTypeDefine
+ n.Predefine(false, rx.Name)
}
}
return n, TRANS_CONTINUE
@@ -415,12 +428,16 @@ var preprocessing atomic.Int32
// - Assigns BlockValuePath to NameExprs.
// - TODO document what it does.
func Preprocess(store Store, ctx BlockNode, n Node) Node {
- // If initStaticBlocks doesn't happen here,
- // it means Preprocess on blocks might fail.
- // it works for now because preprocess also does pushInitBlock,
- // but it's kinda weird.
- // maybe consider moving initStaticBlocks here and ensure idempotency of it.
+ // First init static blocks if blocknode.
+ // This may have already happened.
+ // Keep this function idemponent.
+ if bn, ok := n.(BlockNode); ok {
+ initStaticBlocks(store, ctx, bn)
+ }
+
+ // Bulk of the preprocessor function
n = preprocess1(store, ctx, n)
+
// XXX check node lines and locations
checkNodeLinesLocations("XXXpkgPath", "XXXfileName", n)
// XXX what about the fact that preprocess1 sets the PREPROCESSED attr on all nodes?
@@ -428,9 +445,10 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
// XXX well the following may be isn't idempotent,
// XXX so it is currently strange.
if bn, ok := n.(BlockNode); ok {
- findGotoLoopDefines(ctx, bn)
- findLoopUses1(ctx, bn)
- findLoopUses2(ctx, bn)
+ //findGotoLoopDefines(ctx, bn)
+ findHeapDefinesByUse(ctx, bn)
+ findHeapUsesDemoteDefines(ctx, bn)
+ findPackageSelectors(bn)
}
return n
}
@@ -651,19 +669,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// push func body block.
pushInitBlock(n, &last, &stack)
// define parameters in new block.
- for _, p := range ft.Params {
+ for i, p := range ft.Params {
last.Define(p.Name, anyValue(p.Type))
+ n.Type.Params[i].Path = n.GetPathForName(nil, p.Name)
}
// define results in new block.
for i, rf := range ft.Results {
- if 0 < len(rf.Name) {
- last.Define(rf.Name, anyValue(rf.Type))
- } else {
- // create a hidden var with leading dot.
- // NOTE: document somewhere.
- rn := fmt.Sprintf("%s%d", hiddenResultVariable, i)
- last.Define(Name(rn), anyValue(rf.Type))
- }
+ name := rf.Name
+ last.Define(name, anyValue(rf.Type))
+ n.Type.Results[i].Path = n.GetPathForName(nil, name)
}
// TRANS_BLOCK -----------------------
@@ -684,20 +698,13 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// know which clause will match, we expand the
// block once a clause has matched.
pushInitBlock(n, &last, &stack)
- if n.VarName != "" {
- // NOTE: this defines for default clauses too,
- // see comment on block copying @
- // SwitchClauseStmt:TRANS_BLOCK.
- last.Define(n.VarName, anyValue(nil))
- }
// TRANS_BLOCK -----------------------
case *SwitchClauseStmt:
pushInitBlockAndCopy(n, &last, &stack)
// parent switch statement.
ss := ns[len(ns)-1].(*SwitchStmt)
- // anything declared in ss are copied,
- // namely ss.VarName if defined.
+ // anything declared in ss.Init are copied.
for _, n := range ss.GetBlockNames() {
tv := ss.GetValueRef(nil, n, false)
last.Define(n, *tv)
@@ -767,25 +774,24 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
pushInitBlock(n, &last, &stack)
// define receiver in new block, if method.
if n.IsMethod {
- if 0 < len(n.Recv.Name) {
+ name := n.Recv.Name
+ if name != "" {
rft := getType(&n.Recv).(FieldType)
rt := rft.Type
- last.Define(n.Recv.Name, anyValue(rt))
+ last.Define(name, anyValue(rt))
+ n.Recv.Path = n.GetPathForName(nil, name)
}
}
// define parameters in new block.
- for _, p := range ft.Params {
+ for i, p := range ft.Params {
last.Define(p.Name, anyValue(p.Type))
+ n.Type.Params[i].Path = n.GetPathForName(nil, p.Name)
}
// define results in new block.
for i, rf := range ft.Results {
- if 0 < len(rf.Name) {
- last.Define(rf.Name, anyValue(rf.Type))
- } else {
- // create a hidden var with leading dot.
- rn := fmt.Sprintf("%s%d", hiddenResultVariable, i)
- last.Define(Name(rn), anyValue(rf.Type))
- }
+ name := rf.Name
+ last.Define(name, anyValue(rf.Type))
+ n.Type.Results[i].Path = n.GetPathForName(nil, name)
}
// functions that don't return a value do not need termination analysis
// functions that are externally defined or builtin implemented in the vm can't be analysed
@@ -1023,20 +1029,31 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
cx := evalConst(store, last, n)
return cx, TRANS_CONTINUE
}
- // If name refers to a package, and this is not in
- // the context of a selector, fail. Packages cannot
- // be used as a value, for go compatibility but also
- // to preserve the security expectation regarding imports.
+ // Special handling of packages
nt := evalStaticTypeOf(store, last, n)
if nt == nil {
// this is fine, e.g. for TRANS_ASSIGN_LHS (define) etc.
- } else if ftype != TRANS_SELECTOR_X {
- nk := nt.Kind()
- if nk == PackageKind {
+ } else if nt.Kind() == PackageKind {
+ // If name refers to a package, and this is not in
+ // the context of a selector, fail. Packages cannot
+ // be used as a value, for go compatibility but also
+ // to preserve the security expectation regarding imports.
+ if ftype != TRANS_SELECTOR_X {
panic(fmt.Sprintf(
"package %s cannot only be referred to in a selector expression",
n.Name))
}
+ // Remember the package path
+ // for findPackageSelectors().
+ pvc := evalConst(store, last, n)
+ pv, ok := pvc.V.(*PackageValue)
+ if !ok {
+ panic(fmt.Sprintf(
+ "missing package %s",
+ n.String()))
+ }
+ pref := toRefValue(pv)
+ n.SetAttribute(ATTR_PACKAGE_REF, pref)
}
}
@@ -1092,13 +1109,18 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
if ric {
// Left const, Right const ----------------------
// Replace with *ConstExpr if const operands.
+ //
// First, convert untyped as necessary.
- if !shouldSwapOnSpecificity(lcx.T, rcx.T) {
- // convert n.Left to right type.
- checkOrConvertType(store, last, n, &n.Left, rcx.T, false)
- } else {
- // convert n.Right to left type.
- checkOrConvertType(store, last, n, &n.Right, lcx.T, false)
+ // If either is interface type no conversion is required.
+ if (lt == nil || lt.Kind() != InterfaceKind) &&
+ (rt == nil || rt.Kind() != InterfaceKind) {
+ if !shouldSwapOnSpecificity(lcx.T, rcx.T) {
+ // convert n.Left to right type.
+ checkOrConvertType(store, last, n, &n.Left, rcx.T, false)
+ } else {
+ // convert n.Right to left type.
+ checkOrConvertType(store, last, n, &n.Right, lcx.T, false)
+ }
}
// Then, evaluate the expression.
cx := evalConst(store, last, n)
@@ -1354,6 +1376,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// The pointer value returned is not addressable, but maybe some selector
// will make it addressable. For now mark it as not addressable.
n.Addressability = addressabilityStatusUnsatisfied
+ } else if fv.PkgPath == uversePkgPath && fv.Name == "cross" {
+ // Memoize *CallExpr.WithCross.
+ pc, ok := ns[len(ns)-1].(*CallExpr)
+ if !ok {
+ panic("cross(fn) must be followed by a call")
+ }
+ pc.SetWithCross()
+ } else if fv.PkgPath == uversePkgPath && fv.Name == "crossing" {
+ // XXX Make sure it's only used in a realm.
}
}
@@ -1783,7 +1814,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
var pv *PackageValue
if cx, ok := n.X.(*ConstExpr); ok {
// NOTE: *Machine.TestMemPackage() needs this
- // to pass in an imported package as *ConstEzpr.
+ // to pass in an imported package as *ConstExpr.
pv = cx.V.(*PackageValue)
} else {
// otherwise, packages can only be referred to by
@@ -2099,6 +2130,19 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// TRANS_LEAVE -----------------------
case *ReturnStmt:
fnode, ft := funcOf(last)
+ // Mark return statement as needing to copy
+ // results to named heap items of block.
+ // This is necessary because if the results
+ // are unnamed, they are omitted from block.
+ if ft.Results.IsNamed() && len(n.Results) != 0 {
+ // NOTE: We don't know yet whether any
+ // results are heap items or not, as
+ // they are found after this
+ // preprocessor step. Either find heap
+ // items before, or do another pass to
+ // demote for speed.
+ n.CopyResults = true
+ }
// Check number of return arguments.
if len(n.Results) != len(ft.Results) {
if len(n.Results) == 0 {
@@ -2322,12 +2366,13 @@ func defineOrDecl(
node := skipFile(bn)
- for i := 0; i < len(sts); i++ {
- nx := nameExprs[i]
+ skip := 0 // skip unnamed names like _.
+ for i, nx := range nameExprs {
if nx.Name == blankIdentifier {
- nx.Path = NewValuePathBlock(0, 0, blankIdentifier)
+ skip++
+ nx.Path = NewValuePathBlock(0, 0, nx.Name)
} else {
- node.Define2(isConst, nx.Name, sts[i], tvs[i])
+ node.Define2(isConst, nx.Name, sts[i-skip], tvs[i-skip])
nx.Path = bn.GetPathForName(nil, nx.Name)
}
}
@@ -2390,6 +2435,7 @@ func parseAssignFromExprList(
}
// Evaluate typed value for static definition.
+
for i := range nameExprs {
// Consider value if specified.
if len(valueExprs) > 0 {
@@ -2481,7 +2527,13 @@ func parseMultipleAssignFromOneExpr(
st = evalStaticType(store, bn, typeExpr)
}
- for i := 0; i < numNames; i++ {
+ skip := 0 // skip unnamed names like _.
+ for i, nx := range nameExprs {
+ if nx.Name == blankIdentifier {
+ skip++
+ continue
+ }
+ // := 0; i < numNames; i++ {
if st != nil {
tt := tuple.Elts[i]
@@ -2496,18 +2548,22 @@ func parseMultipleAssignFromOneExpr(
)
}
- sts[i] = st
+ sts[i-skip] = st
} else {
// Set types as return types.
- sts[i] = tuple.Elts[i]
+ sts[i-skip] = tuple.Elts[i]
}
- tvs[i] = anyValue(sts[i])
+ tvs[i-skip] = anyValue(sts[i-skip])
}
}
// Identifies NameExprTypeHeapDefines.
-// Also finds GotoLoopStmts, XXX but probably remove, not needed.
+// Also finds GotoLoopStmts.
+// XXX DEPRECATED but kept here in case needed in the future.
+// We may still want this for optimizing heap defines;
+// the current implementation of findHeapDefinesByUse/findHeapUsesDemoteDefines
+// produces false positives.
func findGotoLoopDefines(ctx BlockNode, bn BlockNode) {
// create stack of BlockNodes.
var stack []BlockNode = make([]BlockNode, 0, 32)
@@ -2661,12 +2717,13 @@ func findGotoLoopDefines(ctx BlockNode, bn BlockNode) {
})
}
-// Find uses of loop names; those names that are defined as loop defines;
-// defines within loops that are used as reference or captured in a closure
-// later. Also happens to adjust the type (but not paths) of such usage.
-// If there is no usage of the &name or as closure capture, a
-// NameExprTypeHeapDefine gets demoted to NameExprTypeDefine in demoteHeapDefines().
-func findLoopUses1(ctx BlockNode, bn BlockNode) {
+// Finds heap defines by their use in ref expressions or
+// closures (captures). Also adjusts the name expr type,
+// and sets new closure captures' path to refer to local
+// capture.
+// Also happens to declare all package and file names
+// as heap use, so that functions added later may use them.
+func findHeapDefinesByUse(ctx BlockNode, bn BlockNode) {
// create stack of BlockNodes.
var stack []BlockNode = make([]BlockNode, 0, 32)
var last BlockNode = ctx
@@ -2677,7 +2734,7 @@ func findLoopUses1(ctx BlockNode, bn BlockNode) {
defer doRecover(stack, n)
if debug {
- debug.Printf("findLoopUses1 %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage)
+ debug.Printf("findHeapDefinesByUse %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage)
}
switch stage {
@@ -2686,103 +2743,125 @@ func findLoopUses1(ctx BlockNode, bn BlockNode) {
pushInitBlock(n.(BlockNode), &last, &stack)
// ----------------------------------------
- case TRANS_ENTER:
+ case TRANS_LEAVE:
+
+ // Pop block from stack.
+ // NOTE: DO NOT USE TRANS_SKIP WITHIN BLOCK
+ // NODES, AS TRANS_LEAVE WILL BE SKIPPED; OR
+ // POP BLOCK YOURSELF.
+ defer func() {
+ switch n.(type) {
+ case BlockNode:
+ stack = stack[:len(stack)-1]
+ last = stack[len(stack)-1]
+ }
+ }()
+
switch n := n.(type) {
+ case *ValueDecl:
+ // Top level value decls are always heap escaped.
+ // See also corresponding case in findHeapUsesDemoteDefines.
+ if !n.Const {
+ switch last.(type) {
+ case *PackageNode, *FileNode:
+ pn := skipFile(last)
+ for _, nx := range n.NameExprs {
+ if nx.Name == "_" {
+ continue
+ }
+ addAttrHeapUse(pn, nx.Name)
+ }
+ }
+ }
+ case *RefExpr:
+ lmx := LeftmostX(n.X)
+ if nx, ok := lmx.(*NameExpr); ok {
+ // Find the block where name is defined
+ dbn := last.GetBlockNodeForPath(nil, nx.Path)
+ // The leftmost name of possibly nested index
+ // and selector exprs.
+ // e.g. leftmost.middle[0][2].rightmost
+ // Mark name for heap use.
+ addAttrHeapUse(dbn, nx.Name)
+ // adjust NameExpr type.
+ nx.Type = NameExprTypeHeapUse
+ }
case *NameExpr:
// Ignore non-block type paths
if n.Path.Type != VPBlock {
return n, TRANS_CONTINUE
}
+ // Ignore package names
+ if n.GetAttribute(ATTR_PACKAGE_REF) != nil {
+ return n, TRANS_CONTINUE
+ }
+ // Ignore decls names.
+ if ftype == TRANS_VAR_NAME {
+ return n, TRANS_CONTINUE
+ }
+ // Find the block where name is defined
+ dbn := last.GetBlockNodeForPath(nil, n.Path)
switch n.Type {
case NameExprTypeNormal:
- // Find the block where name is defined
- dbn := last.GetBlockNodeForPath(nil, n.Path)
- // if the name is loop defined,
- lds, _ := dbn.GetAttribute(ATTR_LOOP_DEFINES).([]Name)
- if slices.Contains(lds, n.Name) {
- fle, depth, found := findFirstClosure(stack, dbn)
- if found {
- // If across a closure,
- // mark name as loop used.
- addAttrHeapUse(dbn, n.Name)
- // The path must stay same for now,
- // used later in findLoopUses2.
- idx := addHeapCapture(dbn, fle, n.Name)
- // adjust NameExpr type.
- n.Type = NameExprTypeHeapUse
- n.Path.SetDepth(uint8(depth))
- n.Path.Index = idx
- } else {
- if ftype == TRANS_REF_X {
- // if used as a reference,
- // mark name as loop used.
- addAttrHeapUse(dbn, n.Name)
- // Also adjust NameExpr type.
- // We could do this later too.
- n.Type = NameExprTypeHeapUse
+ for {
+ // If used as closure capture, mark as heap use.
+ flx, depth, found := findFirstClosure(stack, dbn)
+ if !found {
+ return n, TRANS_CONTINUE
+ }
+ // Ignore top level declarations.
+ // This get replaced by findPackageSelectors.
+ if pn, ok := dbn.(*PackageNode); ok {
+ if pn.PkgPath != ".uverse" {
+ n.SetAttribute(ATTR_PACKAGE_DECL, true)
+ return n, TRANS_CONTINUE
}
}
- } else {
- // if the name is not loop defined,
- // do nothing.
+
+ // Found a heap item closure capture.
+ addAttrHeapUse(dbn, n.Name)
+ // The path must stay same for now,
+ // used later in findHeapUsesDemoteDefines.
+ idx := addHeapCapture(dbn, flx, depth, n)
+ // adjust NameExpr type.
+ n.Type = NameExprTypeHeapUse
+ n.Path.SetDepth(uint8(depth))
+ n.Path.Index = idx
+ // Loop again for more closures.
+ dbn = flx
}
- case NameExprTypeDefine:
- // nothing to do.
- case NameExprTypeHeapDefine:
- // Set name in attribute, so later matches
- // on NameExpr can know that this was loop defined
- // on this block.
- setAttrHeapDefine(last, n.Name)
- case NameExprTypeHeapUse, NameExprTypeHeapClosure:
- panic("unexpected node type")
}
}
return n, TRANS_CONTINUE
-
- // ----------------------------------------
- case TRANS_LEAVE:
- // Pop block from stack.
- // NOTE: DO NOT USE TRANS_SKIP WITHIN BLOCK
- // NODES, AS TRANS_LEAVE WILL BE SKIPPED; OR
- // POP BLOCK YOURSELF.
- switch n.(type) {
- case BlockNode:
- stack = stack[:len(stack)-1]
- last = stack[len(stack)-1]
- }
-
- return n, TRANS_CONTINUE
}
return n, TRANS_CONTINUE
})
}
-func assertNotHasName(names []Name, name Name) {
- if slices.Contains(names, name) {
- panic(fmt.Sprintf("name: %s already contained in names: %v", name, names))
+func addName(names []Name, name Name) []Name {
+ if !slices.Contains(names, name) {
+ names = append(names, name)
}
+ return names
}
-func setAttrHeapDefine(bn BlockNode, name Name) {
- bnLDs, _ := bn.GetAttribute(ATTR_LOOP_DEFINES).([]Name)
- assertNotHasName(bnLDs, name)
- bnLDs = append(bnLDs, name)
- bn.SetAttribute(ATTR_LOOP_DEFINES, bnLDs)
+func addAttrHeapUse(bn BlockNode, name Name) {
+ lus, _ := bn.GetAttribute(ATTR_HEAP_USES).([]Name)
+ lus = addName(lus, name)
+ bn.SetAttribute(ATTR_HEAP_USES, lus)
}
-func addAttrHeapUse(bn BlockNode, name Name) {
- bnLUs, _ := bn.GetAttribute(ATTR_LOOP_USES).([]Name)
- if slices.Contains(bnLUs, name) {
- return
- } else {
- bnLUs = append(bnLUs, name)
- bn.SetAttribute(ATTR_LOOP_USES, bnLUs)
- return
- }
+func hasAttrHeapUse(bn BlockNode, name Name) bool {
+ hds, _ := bn.GetAttribute(ATTR_HEAP_USES).([]Name)
+ return slices.Contains(hds, name)
}
-// adds ~name to fle static block and to heap captures atomically.
-func addHeapCapture(dbn BlockNode, fle *FuncLitExpr, name Name) (idx uint16) {
+// adds ~name to func lit static block and to heap captures atomically.
+func addHeapCapture(dbn BlockNode, fle *FuncLitExpr, depth int, nx *NameExpr) (idx uint16) {
+ if depth <= 0 {
+ panic("invalid depth")
+ }
+ name := nx.Name
for _, ne := range fle.HeapCaptures {
if ne.Name == name {
// assert ~name also already defined.
@@ -2805,8 +2884,11 @@ func addHeapCapture(dbn BlockNode, fle *FuncLitExpr, name Name) (idx uint16) {
fle.Define("~"+name, tv.Copy(nil))
// add name to fle.HeapCaptures.
- vp := fle.GetPathForName(nil, name)
- vp.SetDepth(vp.Depth - 1) // minus 1 for fle itself.
+ // NOTE: this doesn't work with shadowing, see define1.gno.
+ // vp := fle.GetPathForName(nil, name)
+ vp := nx.Path
+ vp.SetDepth(vp.Depth - uint8(depth))
+ //vp.SetDepth(vp.Depth - 1) // minus 1 for fle itself.
ne := NameExpr{
Path: vp,
Name: name,
@@ -2840,8 +2922,8 @@ func findFirstClosure(stack []BlockNode, stop BlockNode) (fle *FuncLitExpr, dept
fle = stbn
depth = len(stack) - 1 - redundant - i + 1 // +1 since 1 is lowest.
found = true
- // even if found, continue iteration in case
- // an earlier *FuncLitExpr is found.
+ // even if found, continue iteration in case
+ // an earlier *FuncLitExpr is found.
case *IfCaseStmt, *SwitchClauseStmt:
if stbn == stop {
return
@@ -2856,10 +2938,10 @@ func findFirstClosure(stack []BlockNode, stop BlockNode) (fle *FuncLitExpr, dept
panic("stop not found in stack")
}
-// Convert non-loop uses of loop names to NameExprTypeHeapUse.
-// Also, NameExprTypeHeapDefine gets demoted to NameExprTypeDefine if no actual
-// usage was found that warrants a NameExprTypeHeapDefine.
-func findLoopUses2(ctx BlockNode, bn BlockNode) {
+// If a name is used as a heap item, Convert all other uses of such names
+// for heap use. If a name of type heap define is not actually used
+// as heap use, demotes them.
+func findHeapUsesDemoteDefines(ctx BlockNode, bn BlockNode) {
// create stack of BlockNodes.
var stack []BlockNode = make([]BlockNode, 0, 32)
var last BlockNode = ctx
@@ -2870,7 +2952,7 @@ func findLoopUses2(ctx BlockNode, bn BlockNode) {
defer doRecover(stack, n)
if debug {
- debug.Printf("findLoopUses2 %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage)
+ debug.Printf("findHeapUsesDemoteDefines %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage)
}
switch stage {
@@ -2890,29 +2972,42 @@ func findLoopUses2(ctx BlockNode, bn BlockNode) {
case NameExprTypeNormal:
// Find the block where name is defined
dbn := last.GetBlockNodeForPath(nil, n.Path)
- // if the name is loop defined,
- lds, _ := dbn.GetAttribute(ATTR_LOOP_DEFINES).([]Name)
- if slices.Contains(lds, n.Name) {
- // if the name is actually loop used,
- lus, _ := dbn.GetAttribute(ATTR_LOOP_USES).([]Name)
- if slices.Contains(lus, n.Name) {
- // change type finally to HeapUse.
- n.Type = NameExprTypeHeapUse
- } else {
- // else, will be demoted in later clause.
- }
+ // If the name is heap used,
+ if hasAttrHeapUse(dbn, n.Name) {
+ // Change type to heap use.
+ n.Type = NameExprTypeHeapUse
}
- case NameExprTypeHeapDefine:
+ case NameExprTypeDefine, NameExprTypeHeapDefine:
// Find the block where name is defined
dbn := last.GetBlockNodeForPath(nil, n.Path)
- // if the name is loop defined
- lds, _ := dbn.GetAttribute(ATTR_LOOP_DEFINES).([]Name)
- if slices.Contains(lds, n.Name) {
- // if the name is actually loop used
- lus, _ := dbn.GetAttribute(ATTR_LOOP_USES).([]Name)
- if !slices.Contains(lus, n.Name) {
- // demote type finally to Define.
- n.Type = NameExprTypeDefine
+ // If the name is actually heap used:
+ if hasAttrHeapUse(dbn, n.Name) {
+ // Promote type to heap define.
+ n.Type = NameExprTypeHeapDefine
+ // Make record in static block.
+ dbn.SetIsHeapItem(n.Name)
+ } else {
+ // Demote type to regular define.
+ n.Type = NameExprTypeDefine
+ }
+ }
+ case *ValueDecl:
+ // Top level var value decls are always heap escaped.
+ // See also corresponding case in findHeapDefinesByUse.
+ if !n.Const {
+ switch last.(type) {
+ case *PackageNode, *FileNode:
+ pn := skipFile(last)
+ for i := range n.NameExprs {
+ nx := &n.NameExprs[i]
+ if nx.Name == "_" {
+ continue
+ }
+ if !hasAttrHeapUse(pn, nx.Name) {
+ panic("expected heap use for top level value decl")
+ }
+ nx.Type = NameExprTypeHeapDefine
+ pn.SetIsHeapItem(nx.Name)
}
}
}
@@ -2936,14 +3031,47 @@ func findLoopUses2(ctx BlockNode, bn BlockNode) {
switch n := n.(type) {
case BlockNode:
- lds, _ := n.GetAttribute(ATTR_LOOP_DEFINES).([]Name)
- lus, _ := n.GetAttribute(ATTR_LOOP_USES).([]Name)
- if len(lds) < len(lus) {
- panic("defines should be a superset of used-defines")
+ switch fd := n.(type) {
+ case *FuncDecl:
+ recv := &fd.Recv
+ if hasAttrHeapUse(fd, recv.Name) {
+ recv.NameExpr.Type = NameExprTypeHeapDefine
+ fd.SetIsHeapItem(recv.Name)
+ }
+ for i := 0; i < len(fd.Type.Params); i++ {
+ name := fd.Type.Params[i].Name
+ if hasAttrHeapUse(fd, name) {
+ fd.Type.Params[i].NameExpr.Type = NameExprTypeHeapDefine
+ fd.SetIsHeapItem(name)
+ }
+ }
+ for i := 0; i < len(fd.Type.Results); i++ {
+ name := fd.Type.Results[i].Name
+ if hasAttrHeapUse(fd, name) {
+ fd.Type.Results[i].NameExpr.Type = NameExprTypeHeapDefine
+ fd.SetIsHeapItem(name)
+ }
+ }
+ case *FuncLitExpr:
+ for i := 0; i < len(fd.Type.Params); i++ {
+ name := fd.Type.Params[i].Name
+ if hasAttrHeapUse(fd, name) {
+ fd.Type.Params[i].NameExpr.Type = NameExprTypeHeapDefine
+ fd.SetIsHeapItem(name)
+ }
+ }
+ for i := 0; i < len(fd.Type.Results); i++ {
+ name := fd.Type.Results[i].Name
+ if hasAttrHeapUse(fd, name) {
+ fd.Type.Results[i].NameExpr.Type = NameExprTypeHeapDefine
+ fd.SetIsHeapItem(name)
+ }
+ }
}
+
// no need anymore
- n.DelAttribute(ATTR_LOOP_USES)
- n.DelAttribute(ATTR_LOOP_DEFINES)
+ n.DelAttribute(ATTR_HEAP_USES)
+ n.DelAttribute(ATTR_HEAP_DEFINES)
}
return n, TRANS_CONTINUE
}
@@ -2951,6 +3079,60 @@ func findLoopUses2(ctx BlockNode, bn BlockNode) {
})
}
+// Replaces all pkg.name selectors with const exprs containing refs.
+// TODO Do not perform this transform unless the name is used
+// inside of a closure. Top level declared functions and methods
+// do not need this indirection. XXX
+func findPackageSelectors(bn BlockNode) {
+ // Iterate over all nodes recursively.
+ _ = Transcribe(bn, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) {
+ switch stage {
+ case TRANS_ENTER:
+ switch n := n.(type) {
+ case *NameExpr:
+ // Replace a package name with RefValue{PkgPath}
+ prefi := n.GetAttribute(ATTR_PACKAGE_REF)
+ if prefi != nil {
+ pref := prefi.(RefValue)
+ cx := &ConstExpr{
+ Source: n,
+ TypedValue: TypedValue{
+ T: gPackageType,
+ V: pref,
+ },
+ }
+ return cx, TRANS_CONTINUE
+ }
+ // Replace a local package declared name with
+ // SelectorExpr{X:RefValue{PkgPath},Sel:name}
+ pdi := n.GetAttribute(ATTR_PACKAGE_DECL)
+ if pdi != nil { // is true
+ if n.Path.Type != VPBlock {
+ panic("expected block path")
+ }
+ pn := packageOf(bn)
+ cx := &ConstExpr{
+ Source: n,
+ TypedValue: TypedValue{
+ T: gPackageType,
+ V: RefValue{
+ PkgPath: pn.PkgPath,
+ },
+ },
+ }
+ sx := &SelectorExpr{
+ X: cx,
+ Path: NewValuePathBlock(1, n.Path.Index, n.Name),
+ Sel: n.Name,
+ }
+ return sx, TRANS_CONTINUE
+ }
+ }
+ }
+ return n, TRANS_CONTINUE
+ })
+}
+
func isSwitchLabel(ns []Node, label Name) bool {
for {
swch := lastSwitch(ns)
@@ -2972,13 +3154,7 @@ func isSwitchLabel(ns []Node, label Name) bool {
// Also makes sure the stack doesn't reach MaxUint8 in length.
func pushInitBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) {
if !bn.IsInitialized() {
- switch bn.(type) {
- case *IfCaseStmt, *SwitchClauseStmt:
- // skip faux block
- bn.InitStaticBlock(bn, (*last).GetParentNode(nil))
- default:
- bn.InitStaticBlock(bn, *last)
- }
+ bn.InitStaticBlock(bn, *last)
}
if bn.GetStaticBlock().Source != bn {
panic("expected the source of a block node to be itself")
@@ -3304,7 +3480,7 @@ func findBranchLabel(last BlockNode, label Name) (
bn = cbn
return
}
- last = cbn.GetParentNode(nil)
+ last = skipFaux(cbn.GetParentNode(nil))
depth += 1
case *IfStmt:
// These are faux blocks -- shouldn't happen.
@@ -3363,7 +3539,7 @@ func findGotoLabel(last BlockNode, label Name) (
bn = cbn
return
} else {
- last = cbn.GetParentNode(nil)
+ last = skipFaux(cbn.GetParentNode(nil))
depth += 1
}
default:
@@ -3531,8 +3707,11 @@ func convertType(store Store, last BlockNode, n Node, x *Expr, t Type) {
// convert x to destination type t
doConvertType(store, last, x, t)
} else {
- // if one side is declared name type and the other side is unnamed type
- if isNamedConversion(xt, t) {
+ // if t is interface do nothing
+ if t != nil && t.Kind() == InterfaceKind {
+ // do nothing
+ } else if isNamedConversion(xt, t) {
+ // if one side is declared name type and the other side is unnamed type
// covert right (xt) to the type of the left (t)
doConvertType(store, last, x, t)
}
@@ -3542,6 +3721,7 @@ func convertType(store Store, last BlockNode, n Node, x *Expr, t Type) {
// convert x to destination type t
func doConvertType(store Store, last BlockNode, x *Expr, t Type) {
+ // XXX
cx := Expr(Call(constType(*x, t), *x))
cx = Preprocess(store, last, cx).(Expr)
*x = cx
@@ -4534,15 +4714,16 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo
}
// The body may get altered during preprocessing later.
if !dt.TryDefineMethod(&FuncValue{
- Type: ft,
- IsMethod: true,
- Source: cd,
- Name: cd.Name,
- Closure: nil, // set lazily.
- FileName: fileNameOf(last),
- PkgPath: pkg.PkgPath,
- body: cd.Body,
- nativeBody: nil,
+ Type: ft,
+ IsMethod: true,
+ Source: cd,
+ Name: cd.Name,
+ Parent: nil, // set lazily
+ FileName: fileNameOf(last),
+ PkgPath: pkg.PkgPath,
+ SwitchRealm: cd.Body.isSwitchRealm(),
+ body: cd.Body,
+ nativeBody: nil,
}) {
// Revert to old function declarations in the package we're preprocessing.
pkg := packageOf(last)
@@ -4805,15 +4986,16 @@ func tryPredefine(store Store, pkg *PackageNode, last BlockNode, d Decl) (un Nam
// fill in later during *FuncDecl:BLOCK.
// The body may get altered during preprocessing later.
fv := &FuncValue{
- Type: ft,
- IsMethod: false,
- Source: d,
- Name: d.Name,
- Closure: nil, // set lazily.
- FileName: fileNameOf(last),
- PkgPath: pkg.PkgPath,
- body: d.Body,
- nativeBody: nil,
+ Type: ft,
+ IsMethod: false,
+ Source: d,
+ Name: d.Name,
+ Parent: nil, // set lazily.
+ FileName: fileNameOf(last),
+ PkgPath: pkg.PkgPath,
+ SwitchRealm: d.Body.isSwitchRealm(),
+ body: d.Body,
+ nativeBody: nil,
}
// NOTE: fv.body == nil means no body (ie. not even curly braces)
// len(fv.body) == 0 could mean also {} (ie. no statements inside)
@@ -4875,6 +5057,21 @@ func constUntypedBigint(source Expr, i64 int64) *ConstExpr {
return cx
}
+func skipFaux(bn BlockNode) BlockNode {
+ if fauxBlockNode(bn) {
+ return bn.GetParentNode(nil)
+ }
+ return bn
+}
+
+func fauxBlockNode(bn BlockNode) bool {
+ switch bn.(type) {
+ case *IfStmt, *SwitchStmt:
+ return true
+ }
+ return false
+}
+
func fillNameExprPath(last BlockNode, nx *NameExpr, isDefineLHS bool) {
if nx.Name == blankIdentifier {
// Blank name has no path; caller error.
@@ -4889,9 +5086,13 @@ func fillNameExprPath(last BlockNode, nx *NameExpr, isDefineLHS bool) {
// and declared variables. See tests/files/define1.go for test case.
var path ValuePath
var i int = 0
+ var faux int = 0
for {
i++
last = last.GetParentNode(nil)
+ if fauxBlockNode(last) {
+ faux++
+ }
if last == nil {
if isUverseName(nx.Name) {
idx, ok := UverseNode().GetLocalIndex(nx.Name)
@@ -4915,7 +5116,7 @@ func fillNameExprPath(last BlockNode, nx *NameExpr, isDefineLHS bool) {
break
}
}
- path.SetDepth(path.Depth + uint8(i))
+ path.SetDepth(path.Depth + uint8(i) - uint8(faux))
path.Validate()
nx.Path = path
return
diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go
index 56f80d63882..5bd4d735907 100644
--- a/gnovm/pkg/gnolang/realm.go
+++ b/gnovm/pkg/gnolang/realm.go
@@ -1,11 +1,14 @@
package gnolang
+// XXX test that p is not actually mutable
+
+// XXX finalize should consider hard boundaries only
+
import (
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
- "strings"
"sync"
bm "github.com/gnolang/gno/gnovm/pkg/benchops"
@@ -116,8 +119,8 @@ type Realm struct {
Time uint64
newCreated []Object
- newEscaped []Object
newDeleted []Object
+ newEscaped []Object
created []Object // about to become real.
updated []Object // real objects that were modified.
@@ -333,8 +336,8 @@ func (rlm *Realm) FinalizeRealmTransaction(store Store) {
// * newEscaped - may become escaped unless new-real and refcount 0 or 1.
// * updated - includes all real updated objects, and will be appended with ancestors
ensureUniq(rlm.newCreated)
- ensureUniq(rlm.newEscaped)
ensureUniq(rlm.newDeleted)
+ ensureUniq(rlm.newEscaped)
ensureUniq(rlm.updated)
if false ||
rlm.created != nil ||
@@ -347,12 +350,12 @@ func (rlm *Realm) FinalizeRealmTransaction(store Store) {
store.LogSwitchRealm(rlm.Path)
// increment recursively for created descendants.
// also assigns object ids for all.
- rlm.processNewCreatedMarks(store)
+ rlm.processNewCreatedMarks(store, 0)
// decrement recursively for deleted descendants.
rlm.processNewDeletedMarks(store)
// at this point, all ref-counts are final.
// demote any escaped if ref-count is 1.
- rlm.processNewEscapedMarks(store)
+ rlm.processNewEscapedMarks(store, 0)
// given created and updated objects,
// mark all owned-ancestors also as dirty.
rlm.markDirtyAncestors(store)
@@ -378,9 +381,10 @@ func (rlm *Realm) FinalizeRealmTransaction(store Store) {
// finding more newly created objects recursively.
// All newly created objects become appended to .created,
// and get assigned ids.
-func (rlm *Realm) processNewCreatedMarks(store Store) {
+// Starts processing with index 'start', returns len(newCreated).
+func (rlm *Realm) processNewCreatedMarks(store Store, start int) int {
// Create new objects and their new descendants.
- for _, oo := range rlm.newCreated {
+ for _, oo := range rlm.newCreated[start:] {
if debug {
if oo.GetIsDirty() {
panic("new created mark cannot be dirty")
@@ -404,6 +408,7 @@ func (rlm *Realm) processNewCreatedMarks(store Store) {
if len(rlm.newCreated) > 0 {
store.SetPackageRealm(rlm)
}
+ return len(rlm.newCreated)
}
// oo must be marked new-real, and ref-count already incremented.
@@ -544,7 +549,8 @@ func (rlm *Realm) decRefDeletedDescendants(store Store, oo Object) {
// demotes new-real escaped objects with refcount 0 or 1. remaining
// objects get their original owners marked dirty (to be further
// marked via markDirtyAncestors).
-func (rlm *Realm) processNewEscapedMarks(store Store) {
+// Starts processing with index 'start', returns len(newEscaped).
+func (rlm *Realm) processNewEscapedMarks(store Store, start int) int {
escaped := make([]Object, 0, len(rlm.newEscaped))
// These are those marked by MarkNewEscaped(),
// regardless of whether new-real or was real,
@@ -552,7 +558,9 @@ func (rlm *Realm) processNewEscapedMarks(store Store) {
// (and never can be unescaped,)
// except for new-reals that get demoted
// because ref-count isn't >= 2.
- for _, eo := range rlm.newEscaped {
+ //for _, eo := range rlm.newEscaped[start:] {
+ for i := 0; i < len(rlm.newEscaped[start:]); i++ { // may expand.
+ eo := rlm.newEscaped[i]
if debug {
if !eo.GetIsNewEscaped() {
panic("new escaped mark not marked as new escaped")
@@ -589,7 +597,9 @@ func (rlm *Realm) processNewEscapedMarks(store Store) {
rlm.MarkDirty(po)
}
if eo.GetObjectID().IsZero() {
- panic("new escaped mark has no object ID")
+ // eo was passed from caller.
+ rlm.incRefCreatedDescendants(store, eo)
+ eo.SetIsNewReal(true)
}
// escaped has no owner.
eo.SetOwner(nil)
@@ -597,6 +607,7 @@ func (rlm *Realm) processNewEscapedMarks(store Store) {
}
}
rlm.escaped = escaped // XXX is this actually used?
+ return len(rlm.newEscaped)
}
//----------------------------------------
@@ -864,7 +875,7 @@ func getChildObjects(val Value, more []Value) []Value {
}
return more
case *FuncValue:
- if bv, ok := cv.Closure.(*Block); ok {
+ if bv, ok := cv.Parent.(*Block); ok {
more = getSelfOrChildObjects(bv, more)
}
for _, c := range cv.Captures {
@@ -872,7 +883,7 @@ func getChildObjects(val Value, more []Value) []Value {
}
return more
case *BoundMethodValue:
- more = getChildObjects(cv.Func, more) // *FuncValue not object
+ more = getSelfOrChildObjects(cv.Func, more)
more = getSelfOrChildObjects(cv.Receiver.V, more)
return more
case *MapValue:
@@ -1068,6 +1079,8 @@ func copyTypeWithRefs(typ Type) Type {
return RefType{
ID: ct.ID,
}
+ case heapItemType:
+ return ct
default:
panic(fmt.Sprintf(
"unexpected type %v", typ))
@@ -1142,13 +1155,13 @@ func copyValueWithRefs(val Value) Value {
}
case *FuncValue:
source := toRefNode(cv.Source)
- if strings.HasSuffix(source.Location.File, "_test.gno") {
- // Ignore _test files
- return nil
+ var parent Value
+ if cv.Parent != nil {
+ parent = toRefValue(cv.Parent)
}
- var closure Value
- if cv.Closure != nil {
- closure = toRefValue(cv.Closure)
+ captures := make([]TypedValue, len(cv.Captures))
+ for i, ctv := range cv.Captures {
+ captures[i] = refOrCopyValue(ctv)
}
// nativeBody funcs which don't come from NativeResolver (and thus don't
// have NativePkg/Name) can't be persisted, and should not be able
@@ -1158,22 +1171,24 @@ func copyValueWithRefs(val Value) Value {
}
ft := copyTypeWithRefs(cv.Type)
return &FuncValue{
- Type: ft,
- IsMethod: cv.IsMethod,
- Source: source,
- Name: cv.Name,
- Closure: closure,
- Captures: cv.Captures,
- FileName: cv.FileName,
- PkgPath: cv.PkgPath,
- NativePkg: cv.NativePkg,
- NativeName: cv.NativeName,
+ ObjectInfo: cv.ObjectInfo.Copy(),
+ Type: ft,
+ IsMethod: cv.IsMethod,
+ Source: source,
+ Name: cv.Name,
+ Parent: parent,
+ Captures: captures,
+ FileName: cv.FileName,
+ PkgPath: cv.PkgPath,
+ NativePkg: cv.NativePkg,
+ NativeName: cv.NativeName,
+ SwitchRealm: cv.SwitchRealm,
}
case *BoundMethodValue:
fnc := copyValueWithRefs(cv.Func).(*FuncValue)
rtv := refOrCopyValue(cv.Receiver)
return &BoundMethodValue{
- ObjectInfo: cv.ObjectInfo.Copy(), // XXX ???
+ ObjectInfo: cv.ObjectInfo.Copy(),
Func: fnc,
Receiver: rtv,
}
@@ -1226,18 +1241,6 @@ func copyValueWithRefs(val Value) Value {
case RefValue:
return cv
case *HeapItemValue:
- // NOTE: While this could be eliminated sometimes with some
- // intelligence prior to persistence, to unwrap the
- // HeapItemValue in case where the HeapItemValue only has
- // refcount of 1,
- //
- // 1. The HeapItemValue is necessary when the .Value is a
- // primitive non-object anyways, and
- // 2. This would mean PointerValue.Base is nil, and we'd need
- // additional logic to re-wrap when necessary, and
- // 3. And with the above point, it's not clear the result
- // would be any faster. But this is something we could
- // explore after launch.
hiv := &HeapItemValue{
ObjectInfo: cv.ObjectInfo.Copy(),
Value: refOrCopyValue(cv.Value),
@@ -1329,6 +1332,8 @@ func fillType(store Store, typ Type) Type {
return ct
case RefType:
return store.GetType(ct.TypeID())
+ case heapItemType:
+ return ct
default:
panic(fmt.Sprintf(
"unexpected type %v", reflect.TypeOf(typ)))
diff --git a/gnovm/pkg/gnolang/string_methods.go b/gnovm/pkg/gnolang/string_methods.go
index ae49fe2ca9c..22aad75bbf6 100644
--- a/gnovm/pkg/gnolang/string_methods.go
+++ b/gnovm/pkg/gnolang/string_methods.go
@@ -1,4 +1,4 @@
-// Code generated by "stringer -type=Kind,Op,TransCtrl,TransField,VPType,Word -output string_methods.go ."; DO NOT EDIT.
+// Code generated by "stringer -type=Kind,Op,TransCtrl,TransField,VPType,Word -output string_methods.go"; DO NOT EDIT.
package gnolang
@@ -62,9 +62,6 @@ func _() {
_ = x[OpPrecall-4]
_ = x[OpCall-5]
_ = x[OpCallNativeBody-6]
- _ = x[OpReturn-7]
- _ = x[OpReturnFromBlock-8]
- _ = x[OpReturnToBlock-9]
_ = x[OpDefer-10]
_ = x[OpCallDeferNativeBody-11]
_ = x[OpGo-12]
@@ -79,6 +76,10 @@ func _() {
_ = x[OpPopFrameAndReset-21]
_ = x[OpPanic1-22]
_ = x[OpPanic2-23]
+ _ = x[OpReturn-26]
+ _ = x[OpReturnAfterCopy-27]
+ _ = x[OpReturnFromBlock-28]
+ _ = x[OpReturnToBlock-29]
_ = x[OpUpos-32]
_ = x[OpUneg-33]
_ = x[OpUnot-34]
@@ -159,59 +160,119 @@ func _() {
_ = x[OpVoid-255]
}
-const (
- _Op_name_0 = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpReturnOpReturnFromBlockOpReturnToBlockOpDeferOpCallDeferNativeBodyOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2"
- _Op_name_1 = "OpUposOpUnegOpUnotOpUxor"
- _Op_name_2 = "OpUrecvOpLorOpLandOpEqlOpNeqOpLssOpLeqOpGtrOpGeqOpAddOpSubOpBorOpXorOpMulOpQuoOpRemOpShlOpShrOpBandOpBandn"
- _Op_name_3 = "OpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvert"
- _Op_name_4 = "OpFieldTypeOpArrayTypeOpSliceTypeOpPointerTypeOpInterfaceTypeOpChanTypeOpFuncTypeOpMapTypeOpStructType"
- _Op_name_5 = "OpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDec"
- _Op_name_6 = "OpValueDeclOpTypeDecl"
- _Op_name_7 = "OpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefers"
- _Op_name_8 = "OpVoid"
-)
+const _Op_name = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpDeferOpCallDeferNativeBodyOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2OpReturnOpReturnAfterCopyOpReturnFromBlockOpReturnToBlockOpUposOpUnegOpUnotOpUxorOpUrecvOpLorOpLandOpEqlOpNeqOpLssOpLeqOpGtrOpGeqOpAddOpSubOpBorOpXorOpMulOpQuoOpRemOpShlOpShrOpBandOpBandnOpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvertOpFieldTypeOpArrayTypeOpSliceTypeOpPointerTypeOpInterfaceTypeOpChanTypeOpFuncTypeOpMapTypeOpStructTypeOpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDecOpValueDeclOpTypeDeclOpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefersOpVoid"
-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_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}
- _Op_index_4 = [...]uint8{0, 11, 22, 33, 46, 61, 71, 81, 90, 102}
- _Op_index_5 = [...]uint8{0, 8, 19, 30, 41, 52, 63, 75, 88, 99, 110, 121, 132, 140, 145, 150}
- _Op_index_6 = [...]uint8{0, 11, 21}
- _Op_index_7 = [...]uint8{0, 8, 14, 23, 34, 51, 65, 84, 102}
-)
+var _Op_map = map[Op]string{
+ 0: _Op_name[0:9],
+ 1: _Op_name[9:15],
+ 2: _Op_name[15:21],
+ 3: _Op_name[21:27],
+ 4: _Op_name[27:36],
+ 5: _Op_name[36:42],
+ 6: _Op_name[42:58],
+ 10: _Op_name[58:65],
+ 11: _Op_name[65:86],
+ 12: _Op_name[86:90],
+ 13: _Op_name[90:98],
+ 14: _Op_name[98:112],
+ 15: _Op_name[112:130],
+ 16: _Op_name[130:142],
+ 17: _Op_name[142:150],
+ 18: _Op_name[150:160],
+ 19: _Op_name[160:172],
+ 20: _Op_name[172:182],
+ 21: _Op_name[182:200],
+ 22: _Op_name[200:208],
+ 23: _Op_name[208:216],
+ 26: _Op_name[216:224],
+ 27: _Op_name[224:241],
+ 28: _Op_name[241:258],
+ 29: _Op_name[258:273],
+ 32: _Op_name[273:279],
+ 33: _Op_name[279:285],
+ 34: _Op_name[285:291],
+ 35: _Op_name[291:297],
+ 37: _Op_name[297:304],
+ 38: _Op_name[304:309],
+ 39: _Op_name[309:315],
+ 40: _Op_name[315:320],
+ 41: _Op_name[320:325],
+ 42: _Op_name[325:330],
+ 43: _Op_name[330:335],
+ 44: _Op_name[335:340],
+ 45: _Op_name[340:345],
+ 46: _Op_name[345:350],
+ 47: _Op_name[350:355],
+ 48: _Op_name[355:360],
+ 49: _Op_name[360:365],
+ 50: _Op_name[365:370],
+ 51: _Op_name[370:375],
+ 52: _Op_name[375:380],
+ 53: _Op_name[380:385],
+ 54: _Op_name[385:390],
+ 55: _Op_name[390:396],
+ 56: _Op_name[396:403],
+ 64: _Op_name[403:409],
+ 65: _Op_name[409:418],
+ 66: _Op_name[418:426],
+ 67: _Op_name[426:434],
+ 68: _Op_name[434:444],
+ 69: _Op_name[444:451],
+ 70: _Op_name[451:457],
+ 71: _Op_name[457:462],
+ 72: _Op_name[462:475],
+ 73: _Op_name[475:488],
+ 74: _Op_name[488:502],
+ 75: _Op_name[502:516],
+ 76: _Op_name[516:526],
+ 77: _Op_name[526:536],
+ 78: _Op_name[536:547],
+ 79: _Op_name[547:555],
+ 80: _Op_name[555:566],
+ 81: _Op_name[566:575],
+ 82: _Op_name[575:584],
+ 112: _Op_name[584:595],
+ 113: _Op_name[595:606],
+ 114: _Op_name[606:617],
+ 115: _Op_name[617:630],
+ 116: _Op_name[630:645],
+ 117: _Op_name[645:655],
+ 118: _Op_name[655:665],
+ 119: _Op_name[665:674],
+ 120: _Op_name[674:686],
+ 128: _Op_name[686:694],
+ 129: _Op_name[694:705],
+ 130: _Op_name[705:716],
+ 131: _Op_name[716:727],
+ 132: _Op_name[727:738],
+ 133: _Op_name[738:749],
+ 134: _Op_name[749:761],
+ 135: _Op_name[761:774],
+ 136: _Op_name[774:785],
+ 137: _Op_name[785:796],
+ 138: _Op_name[796:807],
+ 139: _Op_name[807:818],
+ 140: _Op_name[818:826],
+ 141: _Op_name[826:831],
+ 142: _Op_name[831:836],
+ 144: _Op_name[836:847],
+ 145: _Op_name[847:857],
+ 208: _Op_name[857:865],
+ 209: _Op_name[865:871],
+ 210: _Op_name[871:880],
+ 211: _Op_name[880:891],
+ 212: _Op_name[891:908],
+ 213: _Op_name[908:922],
+ 214: _Op_name[922:941],
+ 215: _Op_name[941:959],
+ 255: _Op_name[959:965],
+}
func (i Op) String() string {
- switch {
- case i <= 23:
- return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]]
- case 32 <= i && i <= 35:
- i -= 32
- return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]]
- case 37 <= i && i <= 56:
- i -= 37
- return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]]
- case 64 <= i && i <= 82:
- i -= 64
- return _Op_name_3[_Op_index_3[i]:_Op_index_3[i+1]]
- case 112 <= i && i <= 120:
- i -= 112
- return _Op_name_4[_Op_index_4[i]:_Op_index_4[i+1]]
- case 128 <= i && i <= 142:
- i -= 128
- return _Op_name_5[_Op_index_5[i]:_Op_index_5[i+1]]
- case 144 <= i && i <= 145:
- i -= 144
- return _Op_name_6[_Op_index_6[i]:_Op_index_6[i+1]]
- case 208 <= i && i <= 215:
- i -= 208
- return _Op_name_7[_Op_index_7[i]:_Op_index_7[i+1]]
- case i == 255:
- return _Op_name_8
- default:
- return "Op(" + strconv.FormatInt(int64(i), 10) + ")"
+ if str, ok := _Op_map[i]; ok {
+ return str
}
+ return "Op(" + strconv.FormatInt(int64(i), 10) + ")"
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
@@ -259,67 +320,68 @@ func _() {
_ = x[TRANS_FUNCLIT_TYPE-20]
_ = x[TRANS_FUNCLIT_HEAP_CAPTURE-21]
_ = x[TRANS_FUNCLIT_BODY-22]
- _ = x[TRANS_FIELDTYPE_TYPE-23]
- _ = x[TRANS_FIELDTYPE_TAG-24]
- _ = x[TRANS_ARRAYTYPE_LEN-25]
- _ = x[TRANS_ARRAYTYPE_ELT-26]
- _ = x[TRANS_SLICETYPE_ELT-27]
- _ = x[TRANS_INTERFACETYPE_METHOD-28]
- _ = x[TRANS_CHANTYPE_VALUE-29]
- _ = x[TRANS_FUNCTYPE_PARAM-30]
- _ = x[TRANS_FUNCTYPE_RESULT-31]
- _ = x[TRANS_MAPTYPE_KEY-32]
- _ = x[TRANS_MAPTYPE_VALUE-33]
- _ = x[TRANS_STRUCTTYPE_FIELD-34]
- _ = x[TRANS_ASSIGN_LHS-35]
- _ = x[TRANS_ASSIGN_RHS-36]
- _ = x[TRANS_BLOCK_BODY-37]
- _ = x[TRANS_DECL_BODY-38]
- _ = x[TRANS_DEFER_CALL-39]
- _ = x[TRANS_EXPR_X-40]
- _ = x[TRANS_FOR_INIT-41]
- _ = x[TRANS_FOR_COND-42]
- _ = x[TRANS_FOR_POST-43]
- _ = x[TRANS_FOR_BODY-44]
- _ = x[TRANS_GO_CALL-45]
- _ = x[TRANS_IF_INIT-46]
- _ = x[TRANS_IF_COND-47]
- _ = x[TRANS_IF_BODY-48]
- _ = x[TRANS_IF_ELSE-49]
- _ = x[TRANS_IF_CASE_BODY-50]
- _ = x[TRANS_INCDEC_X-51]
- _ = x[TRANS_RANGE_X-52]
- _ = x[TRANS_RANGE_KEY-53]
- _ = x[TRANS_RANGE_VALUE-54]
- _ = x[TRANS_RANGE_BODY-55]
- _ = x[TRANS_RETURN_RESULT-56]
- _ = x[TRANS_PANIC_EXCEPTION-57]
- _ = x[TRANS_SELECT_CASE-58]
- _ = x[TRANS_SELECTCASE_COMM-59]
- _ = x[TRANS_SELECTCASE_BODY-60]
- _ = x[TRANS_SEND_CHAN-61]
- _ = x[TRANS_SEND_VALUE-62]
- _ = x[TRANS_SWITCH_INIT-63]
- _ = x[TRANS_SWITCH_X-64]
- _ = x[TRANS_SWITCH_CASE-65]
- _ = x[TRANS_SWITCHCASE_CASE-66]
- _ = x[TRANS_SWITCHCASE_BODY-67]
- _ = x[TRANS_FUNC_RECV-68]
- _ = x[TRANS_FUNC_TYPE-69]
- _ = x[TRANS_FUNC_BODY-70]
- _ = x[TRANS_IMPORT_PATH-71]
- _ = x[TRANS_CONST_TYPE-72]
- _ = x[TRANS_CONST_VALUE-73]
- _ = x[TRANS_VAR_NAME-74]
- _ = x[TRANS_VAR_TYPE-75]
- _ = x[TRANS_VAR_VALUE-76]
- _ = x[TRANS_TYPE_TYPE-77]
- _ = x[TRANS_FILE_BODY-78]
+ _ = x[TRANS_FIELDTYPE_NAME-23]
+ _ = x[TRANS_FIELDTYPE_TYPE-24]
+ _ = x[TRANS_FIELDTYPE_TAG-25]
+ _ = x[TRANS_ARRAYTYPE_LEN-26]
+ _ = x[TRANS_ARRAYTYPE_ELT-27]
+ _ = x[TRANS_SLICETYPE_ELT-28]
+ _ = x[TRANS_INTERFACETYPE_METHOD-29]
+ _ = x[TRANS_CHANTYPE_VALUE-30]
+ _ = x[TRANS_FUNCTYPE_PARAM-31]
+ _ = x[TRANS_FUNCTYPE_RESULT-32]
+ _ = x[TRANS_MAPTYPE_KEY-33]
+ _ = x[TRANS_MAPTYPE_VALUE-34]
+ _ = x[TRANS_STRUCTTYPE_FIELD-35]
+ _ = x[TRANS_ASSIGN_LHS-36]
+ _ = x[TRANS_ASSIGN_RHS-37]
+ _ = x[TRANS_BLOCK_BODY-38]
+ _ = x[TRANS_DECL_BODY-39]
+ _ = x[TRANS_DEFER_CALL-40]
+ _ = x[TRANS_EXPR_X-41]
+ _ = x[TRANS_FOR_INIT-42]
+ _ = x[TRANS_FOR_COND-43]
+ _ = x[TRANS_FOR_POST-44]
+ _ = x[TRANS_FOR_BODY-45]
+ _ = x[TRANS_GO_CALL-46]
+ _ = x[TRANS_IF_INIT-47]
+ _ = x[TRANS_IF_COND-48]
+ _ = x[TRANS_IF_BODY-49]
+ _ = x[TRANS_IF_ELSE-50]
+ _ = x[TRANS_IF_CASE_BODY-51]
+ _ = x[TRANS_INCDEC_X-52]
+ _ = x[TRANS_RANGE_X-53]
+ _ = x[TRANS_RANGE_KEY-54]
+ _ = x[TRANS_RANGE_VALUE-55]
+ _ = x[TRANS_RANGE_BODY-56]
+ _ = x[TRANS_RETURN_RESULT-57]
+ _ = x[TRANS_PANIC_EXCEPTION-58]
+ _ = x[TRANS_SELECT_CASE-59]
+ _ = x[TRANS_SELECTCASE_COMM-60]
+ _ = x[TRANS_SELECTCASE_BODY-61]
+ _ = x[TRANS_SEND_CHAN-62]
+ _ = x[TRANS_SEND_VALUE-63]
+ _ = x[TRANS_SWITCH_INIT-64]
+ _ = x[TRANS_SWITCH_X-65]
+ _ = x[TRANS_SWITCH_CASE-66]
+ _ = x[TRANS_SWITCHCASE_CASE-67]
+ _ = x[TRANS_SWITCHCASE_BODY-68]
+ _ = x[TRANS_FUNC_RECV-69]
+ _ = x[TRANS_FUNC_TYPE-70]
+ _ = x[TRANS_FUNC_BODY-71]
+ _ = x[TRANS_IMPORT_PATH-72]
+ _ = x[TRANS_CONST_TYPE-73]
+ _ = x[TRANS_CONST_VALUE-74]
+ _ = x[TRANS_VAR_NAME-75]
+ _ = x[TRANS_VAR_TYPE-76]
+ _ = x[TRANS_VAR_VALUE-77]
+ _ = x[TRANS_TYPE_TYPE-78]
+ _ = x[TRANS_FILE_BODY-79]
}
-const _TransField_name = "TRANS_ROOTTRANS_BINARY_LEFTTRANS_BINARY_RIGHTTRANS_CALL_FUNCTRANS_CALL_ARGTRANS_INDEX_XTRANS_INDEX_INDEXTRANS_SELECTOR_XTRANS_SLICE_XTRANS_SLICE_LOWTRANS_SLICE_HIGHTRANS_SLICE_MAXTRANS_STAR_XTRANS_REF_XTRANS_TYPEASSERT_XTRANS_TYPEASSERT_TYPETRANS_UNARY_XTRANS_COMPOSITE_TYPETRANS_COMPOSITE_KEYTRANS_COMPOSITE_VALUETRANS_FUNCLIT_TYPETRANS_FUNCLIT_HEAP_CAPTURETRANS_FUNCLIT_BODYTRANS_FIELDTYPE_TYPETRANS_FIELDTYPE_TAGTRANS_ARRAYTYPE_LENTRANS_ARRAYTYPE_ELTTRANS_SLICETYPE_ELTTRANS_INTERFACETYPE_METHODTRANS_CHANTYPE_VALUETRANS_FUNCTYPE_PARAMTRANS_FUNCTYPE_RESULTTRANS_MAPTYPE_KEYTRANS_MAPTYPE_VALUETRANS_STRUCTTYPE_FIELDTRANS_ASSIGN_LHSTRANS_ASSIGN_RHSTRANS_BLOCK_BODYTRANS_DECL_BODYTRANS_DEFER_CALLTRANS_EXPR_XTRANS_FOR_INITTRANS_FOR_CONDTRANS_FOR_POSTTRANS_FOR_BODYTRANS_GO_CALLTRANS_IF_INITTRANS_IF_CONDTRANS_IF_BODYTRANS_IF_ELSETRANS_IF_CASE_BODYTRANS_INCDEC_XTRANS_RANGE_XTRANS_RANGE_KEYTRANS_RANGE_VALUETRANS_RANGE_BODYTRANS_RETURN_RESULTTRANS_PANIC_EXCEPTIONTRANS_SELECT_CASETRANS_SELECTCASE_COMMTRANS_SELECTCASE_BODYTRANS_SEND_CHANTRANS_SEND_VALUETRANS_SWITCH_INITTRANS_SWITCH_XTRANS_SWITCH_CASETRANS_SWITCHCASE_CASETRANS_SWITCHCASE_BODYTRANS_FUNC_RECVTRANS_FUNC_TYPETRANS_FUNC_BODYTRANS_IMPORT_PATHTRANS_CONST_TYPETRANS_CONST_VALUETRANS_VAR_NAMETRANS_VAR_TYPETRANS_VAR_VALUETRANS_TYPE_TYPETRANS_FILE_BODY"
+const _TransField_name = "TRANS_ROOTTRANS_BINARY_LEFTTRANS_BINARY_RIGHTTRANS_CALL_FUNCTRANS_CALL_ARGTRANS_INDEX_XTRANS_INDEX_INDEXTRANS_SELECTOR_XTRANS_SLICE_XTRANS_SLICE_LOWTRANS_SLICE_HIGHTRANS_SLICE_MAXTRANS_STAR_XTRANS_REF_XTRANS_TYPEASSERT_XTRANS_TYPEASSERT_TYPETRANS_UNARY_XTRANS_COMPOSITE_TYPETRANS_COMPOSITE_KEYTRANS_COMPOSITE_VALUETRANS_FUNCLIT_TYPETRANS_FUNCLIT_HEAP_CAPTURETRANS_FUNCLIT_BODYTRANS_FIELDTYPE_NAMETRANS_FIELDTYPE_TYPETRANS_FIELDTYPE_TAGTRANS_ARRAYTYPE_LENTRANS_ARRAYTYPE_ELTTRANS_SLICETYPE_ELTTRANS_INTERFACETYPE_METHODTRANS_CHANTYPE_VALUETRANS_FUNCTYPE_PARAMTRANS_FUNCTYPE_RESULTTRANS_MAPTYPE_KEYTRANS_MAPTYPE_VALUETRANS_STRUCTTYPE_FIELDTRANS_ASSIGN_LHSTRANS_ASSIGN_RHSTRANS_BLOCK_BODYTRANS_DECL_BODYTRANS_DEFER_CALLTRANS_EXPR_XTRANS_FOR_INITTRANS_FOR_CONDTRANS_FOR_POSTTRANS_FOR_BODYTRANS_GO_CALLTRANS_IF_INITTRANS_IF_CONDTRANS_IF_BODYTRANS_IF_ELSETRANS_IF_CASE_BODYTRANS_INCDEC_XTRANS_RANGE_XTRANS_RANGE_KEYTRANS_RANGE_VALUETRANS_RANGE_BODYTRANS_RETURN_RESULTTRANS_PANIC_EXCEPTIONTRANS_SELECT_CASETRANS_SELECTCASE_COMMTRANS_SELECTCASE_BODYTRANS_SEND_CHANTRANS_SEND_VALUETRANS_SWITCH_INITTRANS_SWITCH_XTRANS_SWITCH_CASETRANS_SWITCHCASE_CASETRANS_SWITCHCASE_BODYTRANS_FUNC_RECVTRANS_FUNC_TYPETRANS_FUNC_BODYTRANS_IMPORT_PATHTRANS_CONST_TYPETRANS_CONST_VALUETRANS_VAR_NAMETRANS_VAR_TYPETRANS_VAR_VALUETRANS_TYPE_TYPETRANS_FILE_BODY"
-var _TransField_index = [...]uint16{0, 10, 27, 45, 60, 74, 87, 104, 120, 133, 148, 164, 179, 191, 202, 220, 241, 254, 274, 293, 314, 332, 358, 376, 396, 415, 434, 453, 472, 498, 518, 538, 559, 576, 595, 617, 633, 649, 665, 680, 696, 708, 722, 736, 750, 764, 777, 790, 803, 816, 829, 847, 861, 874, 889, 906, 922, 941, 962, 979, 1000, 1021, 1036, 1052, 1069, 1083, 1100, 1121, 1142, 1157, 1172, 1187, 1204, 1220, 1237, 1251, 1265, 1280, 1295, 1310}
+var _TransField_index = [...]uint16{0, 10, 27, 45, 60, 74, 87, 104, 120, 133, 148, 164, 179, 191, 202, 220, 241, 254, 274, 293, 314, 332, 358, 376, 396, 416, 435, 454, 473, 492, 518, 538, 558, 579, 596, 615, 637, 653, 669, 685, 700, 716, 728, 742, 756, 770, 784, 797, 810, 823, 836, 849, 867, 881, 894, 909, 926, 942, 961, 982, 999, 1020, 1041, 1056, 1072, 1089, 1103, 1120, 1141, 1162, 1177, 1192, 1207, 1224, 1240, 1257, 1271, 1285, 1300, 1315, 1330}
func (i TransField) String() string {
if i >= TransField(len(_TransField_index)-1) {
diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go
index d14013b34de..565a7f8b3e8 100644
--- a/gnovm/pkg/gnolang/transcribe.go
+++ b/gnovm/pkg/gnolang/transcribe.go
@@ -48,6 +48,7 @@ const (
TRANS_FUNCLIT_TYPE
TRANS_FUNCLIT_HEAP_CAPTURE
TRANS_FUNCLIT_BODY
+ TRANS_FIELDTYPE_NAME
TRANS_FIELDTYPE_TYPE
TRANS_FIELDTYPE_TAG
TRANS_ARRAYTYPE_LEN
@@ -278,6 +279,9 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
}
}
case *FieldTypeExpr:
+ /* XXX make this an option. these are not normal names.
+ cnn.NameExpr = *(transcribe(t, nns, TRANS_FIELDTYPE_NAME, 0, &cnn.NameExpr, &c).(*NameExpr))
+ */
cnn.Type = transcribe(t, nns, TRANS_FIELDTYPE_TYPE, 0, cnn.Type, &c).(Expr)
if isStopOrSkip(nc, c) {
return
diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go
index 4c28fa519cd..2f588733116 100644
--- a/gnovm/pkg/gnolang/types.go
+++ b/gnovm/pkg/gnolang/types.go
@@ -23,7 +23,8 @@ type Type interface {
String() string // for dev/debugging
Elem() Type // for TODO... types
GetPkgPath() string
- IsNamed() bool // named vs unname type. property as a method
+ IsNamed() bool // named vs unname type. property as a method
+ IsImmutable() bool // immutable types
}
type TypeID string
@@ -79,6 +80,25 @@ func (heapItemType) assertType() {}
func (*tupleType) assertType() {}
func (RefType) assertType() {}
+// IsImmutable
+func (PrimitiveType) IsImmutable() bool { return true }
+func (*PointerType) IsImmutable() bool { return false }
+func (FieldType) IsImmutable() bool { panic("should not happen") }
+func (*ArrayType) IsImmutable() bool { return false }
+func (*SliceType) IsImmutable() bool { return false }
+func (*StructType) IsImmutable() bool { return false }
+func (*FuncType) IsImmutable() bool { return true }
+func (*MapType) IsImmutable() bool { return false }
+func (*InterfaceType) IsImmutable() bool { return false } // preprocessor only
+func (*TypeType) IsImmutable() bool { return true }
+func (dt *DeclaredType) IsImmutable() bool { return dt.Base.IsImmutable() }
+func (*PackageType) IsImmutable() bool { return false }
+func (*ChanType) IsImmutable() bool { return true }
+func (blockType) IsImmutable() bool { return false }
+func (heapItemType) IsImmutable() bool { return false }
+func (*tupleType) IsImmutable() bool { panic("should not happen") }
+func (RefType) IsImmutable() bool { panic("should not happen") }
+
// ----------------------------------------
// Primitive types
@@ -860,10 +880,6 @@ func (pt *PackageType) Kind() Kind {
func (pt *PackageType) TypeID() TypeID {
if pt.typeid.IsZero() {
- // NOTE Different package types may have the same
- // TypeID if and only if neither have unexported fields.
- // pt.Path is only included in field names that are not
- // uppercase.
pt.typeid = typeid("package{}")
}
return pt.typeid
@@ -1657,10 +1673,10 @@ func (dt *DeclaredType) GetValueAt(alloc *Allocator, store Store, path ValuePath
case VPValMethod, VPPtrMethod, VPField:
if path.Depth == 0 {
mtv := dt.Methods[path.Index]
- // Fill in *FV.Closure.
+ // Fill in *FV.Parent.
ft := mtv.T
fv := mtv.V.(*FuncValue).Copy(alloc)
- fv.Closure = fv.GetClosure(store)
+ fv.Parent = fv.GetParent(store)
return TypedValue{T: ft, V: fv}
} else {
panic("DeclaredType.GetValueAt() expects depth == 0")
@@ -1672,7 +1688,7 @@ func (dt *DeclaredType) GetValueAt(alloc *Allocator, store Store, path ValuePath
}
}
-// Like GetValueAt, but doesn't fill *FuncValue closures.
+// Like GetValueAt, but doesn't fill *FuncValue parent blocks.
func (dt *DeclaredType) GetStaticValueAt(path ValuePath) TypedValue {
switch path.Type {
case VPInterface:
diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go
index ce6260fffcf..cd286d512d8 100644
--- a/gnovm/pkg/gnolang/uverse.go
+++ b/gnovm/pkg/gnolang/uverse.go
@@ -1,5 +1,7 @@
package gnolang
+// XXX append and delete need checks too.
+
import (
"bytes"
"fmt"
@@ -690,12 +692,9 @@ func makeUverseNode() {
func(m *Machine) {
arg0 := m.LastBlock().GetParams1()
tt := arg0.TV.GetType()
- vv := defaultValue(m.Alloc, tt)
+ tv := defaultTypedValue(m.Alloc, tt)
m.Alloc.AllocatePointer()
- hi := m.Alloc.NewHeapItem(TypedValue{
- T: tt,
- V: vv,
- })
+ hi := m.Alloc.NewHeapItem(tv)
m.PushValue(TypedValue{
T: m.Alloc.NewType(&PointerType{
Elt: tt,
@@ -745,6 +744,65 @@ func makeUverseNode() {
}
},
)
+ defNative("crossing",
+ nil, // params
+ nil, // results
+ func(m *Machine) {
+ stmt := m.PeekStmt(1)
+ bs, ok := stmt.(*bodyStmt)
+ if !ok {
+ panic("unexpected origin of crossing call")
+ }
+ if bs.NextBodyIndex != 1 {
+ panic("crossing call must be the first call of a function or method")
+ }
+ fr1 := m.PeekCallFrame(1) // fr1.LastPackage created fr.
+ if !fr1.LastPackage.IsRealm() {
+ panic("crossing call only allowed in realm packages") // XXX test
+ }
+ // Verify prior fr.WithCross or fr.DidCross.
+ // NOTE: fr.WithCross may or may not be true,
+ // switcherealm() (which sets fr.DidCross) can be
+ // stacked.
+ for i := 1 + 1; ; i++ {
+ fri := m.PeekCallFrame(i)
+ if fri == nil {
+ panic("crossing could not find corresponding cross(fn)(...) call")
+ }
+ if fri.WithCross || fri.DidCross {
+ // NOTE: fri.DidCross implies
+ // everything under it is also valid.
+ // fri.DidCross && !fri.WithCross
+ // can happen with an implicit switch.
+ fr2 := m.PeekCallFrame(2)
+ fr2.SetDidCross()
+ return
+ }
+ // Neither fri.WithCross nor fri.DidCross, yet
+ // Realm already switched implicitly.
+ if fri.LastRealm != m.Realm {
+ panic("crossing could not find corresponding cross(fn)(...) call")
+ }
+ }
+ panic("should not happen") // defensive
+ },
+ )
+ defNative("cross",
+ Flds( // param
+ "x", GenT("X", nil),
+ ),
+ Flds( // results
+ "x", GenT("X", nil),
+ ),
+ func(m *Machine) {
+ // This is handled by op_call instead.
+ panic("cross is a virtual function")
+ /*
+ arg0 := m.LastBlock().GetParams1()
+ m.PushValue(arg0.Deref())
+ */
+ },
+ )
uverseValue = uverseNode.NewPackage()
}
diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go
index d60c074316b..3c1c872ca5d 100644
--- a/gnovm/pkg/gnolang/values.go
+++ b/gnovm/pkg/gnolang/values.go
@@ -1,12 +1,13 @@
package gnolang
+// XXX TODO address "this is wrong, for var i interface{}; &i is *interface{}."
+
import (
"encoding/binary"
"fmt"
"math/big"
"reflect"
"strconv"
- "strings"
"unsafe"
"github.com/cockroachdb/apd/v3"
@@ -64,8 +65,8 @@ var (
_ Value = BigdecValue{}
_ Value = DataByteValue{}
_ Value = PointerValue{}
- _ Value = &ArrayValue{} // TODO doesn't have to be pointer?
- _ Value = &SliceValue{} // TODO doesn't have to be pointer?
+ _ Value = &ArrayValue{}
+ _ Value = &SliceValue{}
_ Value = &StructValue{}
_ Value = &FuncValue{}
_ Value = &MapValue{}
@@ -165,34 +166,26 @@ func (dbv DataByteValue) SetByte(b byte) {
// ----------------------------------------
// PointerValue
-// Base is set if the pointer refers to an array index or
-// struct field or block var.
-// A pointer constructed via a &x{} composite lit
-// expression or constructed via new() or make() are
-// independent objects, and have nil Base.
-// A pointer to a block var may end up pointing to an escape
-// value after a block var escapes "to the heap".
-// *(PointerValue.TypedValue) must have already become
-// initialized, namely T set if a typed-nil.
-// Index is -1 for the shared "_" block var,
-// and -2 for (gno and native) map items.
+// *(PointerValue.TypedValue) must have already become initialized, namely T
+// set if a typed-nil. Index is -1 for the shared "_" block var, and -2 for
+// (gno and native) map items.
//
// A pointer constructed via a &x{} composite lit expression or constructed via
// new() or make() will have a virtual HeapItemValue as base.
//
-// Allocation for PointerValue is not immediate,
-// as usually PointerValues are temporary for assignment
-// or binary operations. When a pointer is to be
-// allocated, *Allocator.AllocatePointer() is called separately,
-// as in OpRef.
+// The Base is only nil for references to certain values that cannot
+// be modified anyways, such as top level functions.
//
-// Since PointerValue is used internally for assignment etc,
-// it MUST stay minimal for computational efficiency.
+// Allocation for PointerValue is not immediate, as usually PointerValues are
+// temporary for assignment or binary operations. When a pointer is to be
+// allocated, *Allocator.AllocatePointer() is called separately, as in OpRef.
+//
+// Since PointerValue is used internally for assignment etc, it MUST stay
+// minimal for computational efficiency.
type PointerValue struct {
- TV *TypedValue // escape val if pointer to var.
+ TV *TypedValue // &Base[Index] or &Base.Index.
Base Value // array/struct/block, or heapitem.
Index int // list/fields/values index, or -1 or -2 (see below).
- Key *TypedValue `json:",omitempty"` // for maps.
}
const (
@@ -226,7 +219,10 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty
return
}
// General case
- if rlm != nil && pv.Base != nil {
+ if rlm != nil {
+ if debug && pv.Base == nil {
+ panic("expected non-nil base for assignment")
+ }
oo1 := pv.TV.GetFirstObject(store)
pv.TV.Assign(alloc, tv2, cu)
oo2 := pv.TV.GetFirstObject(store)
@@ -305,7 +301,7 @@ func (av *ArrayValue) GetPointerAtIndexInt2(store Store, ii int, et Type) Pointe
Index: ii,
}
}
- bv := &TypedValue{ // heap alloc, so need to compare value rather than pointer
+ btv := &TypedValue{ // heap alloc, so need to compare value rather than pointer
T: DataByteType,
V: DataByteValue{
Base: av,
@@ -315,7 +311,7 @@ func (av *ArrayValue) GetPointerAtIndexInt2(store Store, ii int, et Type) Pointe
}
return PointerValue{
- TV: bv,
+ TV: btv,
Base: av,
Index: ii,
}
@@ -478,16 +474,19 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue {
// makes construction TypedValue{T:*FuncType{},V:*FuncValue{}}
// faster.
type FuncValue struct {
- Type Type // includes unbound receiver(s)
- IsMethod bool // is an (unbound) method
- Source BlockNode // for block mem allocation
- Name Name // name of function/method
- Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy)
- Captures []TypedValue `json:",omitempty"` // HeapItemValues captured from closure.
- FileName Name // file name where declared
- PkgPath string
- NativePkg string // for native bindings through NativeResolver
- NativeName Name // not redundant with Name; this cannot be changed in userspace
+ ObjectInfo
+ Type Type // includes unbound receiver(s)
+ IsMethod bool // is an (unbound) method
+ IsClosure bool // is a func lit expr closure (not decl)
+ Source BlockNode // for block mem allocation
+ Name Name // name of function/method
+ Parent Value // *Block or RefValue to closure (may be nil for file blocks; lazy)
+ Captures []TypedValue `json:",omitempty"` // HeapItemValues captured from closure.
+ FileName Name // file name where declared
+ PkgPath string // package path in which func declared
+ NativePkg string // for native bindings through NativeResolver
+ NativeName Name // not redundant with Name; this cannot be changed in userspace
+ SwitchRealm bool // true if .body's first statement is crossing().
body []Stmt // function body
nativeBody func(*Machine) // alternative to Body
@@ -508,17 +507,18 @@ func (fv *FuncValue) IsNative() bool {
func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue {
alloc.AllocateFunc()
return &FuncValue{
- Type: fv.Type,
- IsMethod: fv.IsMethod,
- Source: fv.Source,
- Name: fv.Name,
- Closure: fv.Closure,
- FileName: fv.FileName,
- PkgPath: fv.PkgPath,
- NativePkg: fv.NativePkg,
- NativeName: fv.NativeName,
- body: fv.body,
- nativeBody: fv.nativeBody,
+ Type: fv.Type,
+ IsMethod: fv.IsMethod,
+ Source: fv.Source,
+ Name: fv.Name,
+ Parent: fv.Parent,
+ FileName: fv.FileName,
+ PkgPath: fv.PkgPath,
+ NativePkg: fv.NativePkg,
+ NativeName: fv.NativeName,
+ SwitchRealm: fv.SwitchRealm,
+ body: fv.body,
+ nativeBody: fv.nativeBody,
}
}
@@ -569,11 +569,11 @@ func (fv *FuncValue) GetPackage(store Store) *PackageValue {
return pv
}
-// NOTE: this function does not automatically memoize the closure for
-// file-level declared methods and functions. For those, caller
-// should set .Closure manually after *FuncValue.Copy().
-func (fv *FuncValue) GetClosure(store Store) *Block {
- switch cv := fv.Closure.(type) {
+func (fv *FuncValue) GetParent(store Store) *Block {
+ if fv.IsClosure {
+ return nil
+ }
+ switch cv := fv.Parent.(type) {
case nil:
if fv.FileName == "" {
return nil
@@ -583,10 +583,11 @@ func (fv *FuncValue) GetClosure(store Store) *Block {
if fb == nil {
panic(fmt.Sprintf("file block missing for file %q", fv.FileName))
}
+ fv.Parent = fb
return fb
case RefValue:
block := store.GetObject(cv.ObjectID).(*Block)
- fv.Closure = block
+ fv.Parent = block
return block
case *Block:
return cv
@@ -595,6 +596,10 @@ func (fv *FuncValue) GetClosure(store Store) *Block {
}
}
+func (fv *FuncValue) IsSwitchRealm() bool {
+ return fv.SwitchRealm
+}
+
// ----------------------------------------
// BoundMethodValue
@@ -611,6 +616,10 @@ type BoundMethodValue struct {
Receiver TypedValue
}
+func (bmv *BoundMethodValue) IsSwitchRealm() bool {
+ return bmv.Func.IsSwitchRealm()
+}
+
// ----------------------------------------
// MapValue
@@ -708,28 +717,22 @@ func (mv *MapValue) GetLength() int {
return mv.List.Size // panics if uninitialized
}
-// NOTE: Go doesn't support referencing into maps, and maybe
-// Gno will, but here we just use this method signature as we
-// do for structs and arrays for assigning new entries. If key
-// doesn't exist, a new slot is created.
+// GetPointerForKey is only used for assignment, so the key
+// is not returned as part of the pointer, and TV is not filled.
func (mv *MapValue) GetPointerForKey(alloc *Allocator, store Store, key *TypedValue) PointerValue {
kmk := key.ComputeMapKey(store, false)
if mli, ok := mv.vmap[kmk]; ok {
- key2 := key.Copy(alloc)
return PointerValue{
TV: fillValueTV(store, &mli.Value),
Base: mv,
- Key: &key2,
Index: PointerIndexMap,
}
}
mli := mv.List.Append(alloc, *key)
mv.vmap[kmk] = mli
- key2 := key.Copy(alloc)
return PointerValue{
TV: fillValueTV(store, &mli.Value),
Base: mv,
- Key: &key2,
Index: PointerIndexMap,
}
}
@@ -885,9 +888,50 @@ func (pv *PackageValue) GetPkgAddr() crypto.Address {
// TypedValue (is not a value, but a tuple)
type TypedValue struct {
- T Type `json:",omitempty"` // never nil
- V Value `json:",omitempty"` // an untyped value
- N [8]byte `json:",omitempty"` // numeric bytes
+ T Type `json:",omitempty"`
+ V Value `json:",omitempty"`
+ N [8]byte `json:",omitempty"`
+}
+
+// Magic 8 bytes to denote a readonly wrapped non-nil V of mutable type that is
+// readonly. This happens when subvalues are retrieved from an externally
+// stored realm value, such as external realm package vars, or slices or
+// pointers to.
+// NOTE: most of the code except copy methods do not consider N_Readonly.
+// Instead the op functions should with m.IsReadonly() and tv.SetReadonly() and
+// tv.WithReadonly().
+var N_Readonly [8]byte = [8]byte{'R', 'e', 'a', 'D', 'o', 'N', 'L', 'Y'} // ReaDoNLY
+
+// Returns true if mutable .V is readonly "wrapped".
+func (tv *TypedValue) IsReadonly() bool {
+ return tv.N == N_Readonly && tv.V != nil
+}
+
+// Sets tv.N to N_Readonly if ro and tv is not already immutable. If ro is
+// false does nothing. See also Type.IsImmutable().
+func (tv *TypedValue) SetReadonly(ro bool) {
+ if tv.V == nil {
+ return // do nothing
+ }
+ if tv.T.IsImmutable() {
+ return // do nothing
+ }
+ if ro {
+ tv.N = N_Readonly
+ return
+ } else {
+ return // perserve prior tv.N
+ }
+}
+
+// Convenience, makes readonly if ro is true.
+func (tv TypedValue) WithReadonly(ro bool) TypedValue {
+ tv.SetReadonly(ro)
+ return tv
+}
+
+func (tv *TypedValue) IsImmutable() bool {
+ return tv.T == nil || tv.T.IsImmutable()
}
func (tv *TypedValue) IsDefined() bool {
@@ -896,22 +940,19 @@ func (tv *TypedValue) IsDefined() bool {
func (tv *TypedValue) IsUndefined() bool {
if debug {
- if tv == nil {
- panic("should not happen")
- }
- }
- if tv.T == nil {
- if debug {
- if tv.V != nil || tv.N != [8]byte{} {
- panic(fmt.Sprintf(
- "corrupted TypeValue (nil T)"))
+ if tv.T == nil {
+ if tv.V != nil {
+ panic("should not happen")
+ }
+ if tv.N != [8]byte{} {
+ panic("should not happen")
}
}
- return true
}
- return tv.IsNilInterface()
+ return tv.T == nil
}
+// (this is used mostly by the preprocessor)
func (tv *TypedValue) IsNilInterface() bool {
if tv.T != nil && tv.T.Kind() == InterfaceKind {
if tv.V == nil {
@@ -962,9 +1003,11 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) {
case *ArrayValue:
cp.T = tv.T
cp.V = cv.Copy(alloc)
+ cp.N = tv.N // preserve N_Readonly
case *StructValue:
cp.T = tv.T
cp.V = cv.Copy(alloc)
+ cp.N = tv.N // preserve N_Readonly
default:
cp = tv
}
@@ -976,15 +1019,13 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) {
func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) (cp TypedValue) {
switch tv.V.(type) {
case RefValue:
- cp.T = tv.T
+ cp = tv // preserve N_Readonly
refObject := tv.GetFirstObject(store)
switch refObjectValue := refObject.(type) {
case *ArrayValue:
cp.V = refObjectValue.Copy(alloc)
case *StructValue:
cp.V = refObjectValue.Copy(alloc)
- default:
- cp = tv
}
default:
cp = tv.Copy(alloc)
@@ -1561,6 +1602,32 @@ func (tv *TypedValue) Assign(alloc *Allocator, tv2 TypedValue, cu bool) {
}
}
+// Define to a block slot that takes into account heap escapes.
+// (only blocks can contain heap items).
+// This should only be used when both the base parent and the value are unreal
+// new values, or call rlm.DidUpdate manually.
+func (tv *TypedValue) AssignToBlock(other TypedValue) {
+ if _, ok := tv.T.(heapItemType); ok {
+ tv.V.(*HeapItemValue).Value = other
+ } else {
+ *tv = other
+ }
+}
+
+// Like AssignToBlock but creates a new heap item instead.
+// This should only be used when both the base parent and the value are unreal
+// new values, or call rlm.DidUpdate manually.
+func (tv *TypedValue) DefineToBlock(other TypedValue) {
+ if _, ok := tv.T.(heapItemType); ok {
+ *tv = TypedValue{
+ T: heapItemType{},
+ V: &HeapItemValue{Value: other},
+ }
+ } else {
+ *tv = other
+ }
+}
+
// NOTE: Allocation for PointerValue is not immediate,
// as usually PointerValues are temporary for assignment
// or binary operations. When a pointer is to be
@@ -1649,7 +1716,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val
path.Type = VPValMethod
case VPDerefPtrMethod:
// dtv = tv.V.(PointerValue).TV
- // dtv not needed for nil receivers.
+ // dtv not used due to possible nil receivers.
isPtr = true
path.Type = VPPtrMethod // XXX pseudo
case VPDerefInterface:
@@ -1724,6 +1791,12 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val
}
}
dtv2 := dtv.Copy(alloc)
+ if dtv2.V != nil {
+ // Clear readonly for receivers.
+ // Other rules still apply such as in DidUpdate.
+ // NOTE: dtv2 is a copy, orig is untouched.
+ dtv2.N = [8]byte{}
+ }
alloc.AllocateBoundMethod()
bmv := &BoundMethodValue{
Func: mv,
@@ -1754,10 +1827,17 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val
panic("should not happen")
}
}
+ ptv := *tv
+ if ptv.V != nil {
+ // Clear readonly for receivers.
+ // Other rules still apply such as in DidUpdate.
+ // NOTE: ptv is a copy, orig is untouched.
+ ptv.N = [8]byte{}
+ }
alloc.AllocateBoundMethod()
bmv := &BoundMethodValue{
Func: mv,
- Receiver: *tv, // bound to ptr, not dtv.
+ Receiver: ptv, // bound to tv ptr, not dtv.
}
return PointerValue{
TV: &TypedValue{
@@ -1776,13 +1856,13 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val
panic(fmt.Sprintf("method %s not found in type %s",
path.Name, dtv.T.String()))
}
- bv := *dtv
+ btv := *dtv
for i, path := range tr {
- ptr := bv.GetPointerToFromTV(alloc, store, path)
+ ptr := btv.GetPointerToFromTV(alloc, store, path)
if i == len(tr)-1 {
return ptr // done
}
- bv = ptr.Deref() // deref
+ btv = ptr.Deref() // deref
}
panic("should not happen")
default:
@@ -1790,7 +1870,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val
}
}
-// Convenience for GetPointerAtIndex(). Slow.
+// Convenience for GetPointerAtIndex(). Slow.
func (tv *TypedValue) GetPointerAtIndexInt(store Store, ii int) PointerValue {
iv := TypedValue{T: IntType}
iv.SetInt(int64(ii))
@@ -1803,7 +1883,7 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed
if bt == StringType || bt == UntypedStringType {
sv := tv.GetString()
ii := int(iv.ConvertGetInt())
- bv := &TypedValue{ // heap alloc
+ btv := &TypedValue{ // heap alloc
T: Uint8Type,
}
@@ -1814,9 +1894,9 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed
panic(&Exception{Value: typedString(fmt.Sprintf("invalid slice index %d (index must be non-negative)", ii))})
}
- bv.SetUint8(sv[ii])
+ btv.SetUint8(sv[ii])
return PointerValue{
- TV: bv,
+ TV: btv,
Base: nil, // free floating
}
}
@@ -2003,6 +2083,7 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue {
),
}
case *SliceType:
+ // XXX consider restricting slice expansion if slice is readonly.
if tv.GetCapacity() < high {
panic(&Exception{Value: typedString(fmt.Sprintf(
"slice bounds out of range [%d:%d] with capacity %d",
@@ -2087,6 +2168,7 @@ func (tv *TypedValue) GetSlice2(alloc *Allocator, lowVal, highVal, maxVal int) T
),
}
case *SliceType:
+ // XXX consider restricting slice expansion if slice is readonly.
if tv.V == nil {
if lowVal != 0 || highVal != 0 || maxVal != 0 {
panic("nil slice index out of range")
@@ -2123,21 +2205,23 @@ func (tv *TypedValue) DeepFill(store Store) {
// ----------------------------------------
// Block
//
-// Blocks hold values referred to by var/const/func/type
-// declarations in BlockNodes such as packages, functions,
-// and switch statements. Unlike structs or packages,
-// names and paths may refer to parent blocks. (In the
-// future, the same mechanism may be used to support
-// inheritance or prototype-like functionality for structs
-// and packages.)
+// Blocks hold values referred to by var/const/func/type declarations in
+// BlockNodes such as packages, functions, and switch statements. Unlike
+// structs or packages, names and paths may refer to parent blocks. (In the
+// future, the same mechanism may be used to support inheritance or
+// prototype-like functionality for structs and packages.)
//
-// When a block would otherwise become gc'd because it is no
-// longer used except for escaped reference pointers to
-// variables, and there are no closures that reference the
-// block, the remaining references to objects become detached
-// from the block and become ownerless.
-
-// TODO rename to BlockValue.
+// When a block would otherwise become gc'd because it is no longer used, the
+// block is forgotten and GC'd.
+//
+// Variables declared in a closure or passed by reference are first discovered
+// and marked as such from the preprocessor, and NewBlock() will prepopulate
+// these slots with *HeapItemValues. When a *HeapItemValue (or sometimes
+// HeapItemType in .T) is present in a block slot it is not written over but
+// instead the value is written into the heap item's slot--except for loopvars
+// assignments which may replace the heap item with another one. This is
+// how Gno supports Go1.22 loopvars.
+// TODO XXX rename to BlockValue
type Block struct {
ObjectInfo
Source BlockNode
@@ -2148,10 +2232,20 @@ type Block struct {
}
// NOTE: for allocation, use *Allocator.NewBlock.
+// XXX pass allocator in for heap items.
func NewBlock(source BlockNode, parent *Block) *Block {
- var values []TypedValue
- if source != nil {
- values = make([]TypedValue, source.GetNumNames())
+ numNames := source.GetNumNames()
+ values := make([]TypedValue, numNames)
+ // Keep in sync with ExpandWith().
+ for i, isHeap := range source.GetHeapItems() {
+ if !isHeap {
+ continue
+ }
+ // indicates must always be heap item.
+ values[i] = TypedValue{
+ T: heapItemType{},
+ V: &HeapItemValue{},
+ }
}
return &Block{
Source: source,
@@ -2160,39 +2254,6 @@ func NewBlock(source BlockNode, parent *Block) *Block {
}
}
-func (b *Block) String() string {
- return b.StringIndented(" ")
-}
-
-func (b *Block) StringIndented(indent string) string {
- source := toString(b.Source)
- if len(source) > 32 {
- source = source[:32] + "..."
- }
- lines := make([]string, 0, 3)
- lines = append(lines,
- fmt.Sprintf("Block(ID:%v,Addr:%p,Source:%s,Parent:%p)",
- b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}.
- if b.Source != nil {
- if _, ok := b.Source.(RefNode); ok {
- lines = append(lines,
- fmt.Sprintf("%s(RefNode names not shown)", indent))
- } else {
- for i, n := range b.Source.GetBlockNames() {
- if len(b.Values) <= i {
- lines = append(lines,
- fmt.Sprintf("%s%s: undefined", indent, n))
- } else {
- lines = append(lines,
- fmt.Sprintf("%s%s: %s",
- indent, n, b.Values[i].String()))
- }
- }
- }
- }
- return strings.Join(lines, "\n")
-}
-
func (b *Block) GetSource(store Store) BlockNode {
if rn, ok := b.Source.(RefNode); ok {
source := store.GetBlockNode(rn.GetLocation())
@@ -2218,6 +2279,24 @@ func (b *Block) GetParent(store Store) *Block {
}
func (b *Block) GetPointerToInt(store Store, index int) PointerValue {
+ vv := fillValueTV(store, &b.Values[index])
+ if hiv, ok := vv.V.(*HeapItemValue); ok {
+ fillValueTV(store, &hiv.Value)
+ return PointerValue{
+ TV: &hiv.Value,
+ Base: vv.V,
+ Index: 0,
+ }
+ } else {
+ return PointerValue{
+ TV: vv,
+ Base: b,
+ Index: index,
+ }
+ }
+}
+
+func (b *Block) GetPointerToIntDirect(store Store, index int) PointerValue {
vv := fillValueTV(store, &b.Values[index])
return PointerValue{
TV: vv,
@@ -2251,70 +2330,65 @@ func (b *Block) GetPointerTo(store Store, path ValuePath) PointerValue {
return b.GetPointerToInt(store, int(path.Index))
}
-// Convenience
-func (b *Block) GetPointerToMaybeHeapUse(store Store, nx *NameExpr) PointerValue {
- switch nx.Type {
- case NameExprTypeNormal:
- return b.GetPointerTo(store, nx.Path)
- case NameExprTypeHeapUse:
- return b.GetPointerToHeapUse(store, nx.Path)
- case NameExprTypeHeapClosure:
- panic("should not happen with type heap closure")
- default:
- panic("unexpected NameExpr type for GetPointerToMaybeHeapUse")
+func (b *Block) GetPointerToDirect(store Store, path ValuePath) PointerValue {
+ if path.IsBlockBlankPath() {
+ if debug {
+ if path.Name != blankIdentifier {
+ panic(fmt.Sprintf(
+ "zero value path is reserved for \"_\", but got %s",
+ path.Name))
+ }
+ }
+ return PointerValue{
+ TV: b.GetBlankRef(),
+ Base: b,
+ Index: PointerIndexBlockBlank, // -1
+ }
+ }
+ // NOTE: For most block paths, Depth starts at 1, but
+ // the generation for uverse is 0. If path.Depth is
+ // 0, it implies that b == uverse, and the condition
+ // would fail as if it were 1.
+ for i := uint8(1); i < path.Depth; i++ {
+ b = b.GetParent(store)
}
+ return b.GetPointerToIntDirect(store, int(path.Index))
}
-// Convenience
+// First defines a new HeapItemValue if heap slot.
func (b *Block) GetPointerToMaybeHeapDefine(store Store, nx *NameExpr) PointerValue {
switch nx.Type {
case NameExprTypeNormal:
+ // XXX convert rangestmt switchstmt names
+ // into NameExpr and then panic here instead.
return b.GetPointerTo(store, nx.Path)
case NameExprTypeDefine:
return b.GetPointerTo(store, nx.Path)
case NameExprTypeHeapDefine:
- return b.GetPointerToHeapDefine(store, nx.Path)
+ path := nx.Path
+ ptr := b.GetPointerToDirect(store, path)
+ if _, ok := ptr.TV.T.(heapItemType); ok {
+ if nx.Type != NameExprTypeHeapDefine {
+ panic("expected name expr heap define type")
+ }
+ hiv := &HeapItemValue{}
+ *ptr.TV = TypedValue{
+ T: heapItemType{},
+ V: hiv,
+ }
+ return PointerValue{
+ TV: &hiv.Value,
+ Base: hiv,
+ Index: 0,
+ }
+ } else {
+ return ptr
+ }
default:
panic("unexpected NameExpr type for GetPointerToMaybeHeapDefine")
}
}
-// First defines a new HeapItemValue.
-// This gets called from NameExprTypeHeapDefine name expressions.
-func (b *Block) GetPointerToHeapDefine(store Store, path ValuePath) PointerValue {
- ptr := b.GetPointerTo(store, path)
- hiv := &HeapItemValue{}
- // point to a heapItem
- *ptr.TV = TypedValue{
- T: heapItemType{},
- V: hiv,
- }
-
- return PointerValue{
- TV: &hiv.Value,
- Base: hiv,
- Index: 0,
- }
-}
-
-// Assumes a HeapItemValue, and gets inner pointer.
-// This gets called from NameExprTypeHeapUse name expressions.
-func (b *Block) GetPointerToHeapUse(store Store, path ValuePath) PointerValue {
- ptr := b.GetPointerTo(store, path)
- if _, ok := ptr.TV.T.(heapItemType); !ok {
- panic("should not happen, should be heapItemType")
- }
- if _, ok := ptr.TV.V.(*HeapItemValue); !ok {
- panic("should not happen, should be HeapItemValue")
- }
-
- return PointerValue{
- TV: &ptr.TV.V.(*HeapItemValue).Value,
- Base: ptr.TV.V,
- Index: 0,
- }
-}
-
// Result is used has lhs for any assignments to "_".
func (b *Block) GetBlankRef() *TypedValue {
return &b.Blank
@@ -2345,18 +2419,44 @@ func (b *Block) GetBodyStmt() *bodyStmt {
return &b.bodyStmt
}
-// Used by SwitchStmt upon clause match.
-func (b *Block) ExpandToSize(alloc *Allocator, size uint16) {
+// Used by faux blocks like IfCond and SwitchStmt upon clause match.
+func (b *Block) ExpandWith(alloc *Allocator, source BlockNode) {
+ // XXX make more efficient by only storing new names in source.
+ numNames := source.GetNumNames()
+ if len(b.Values) > int(numNames) {
+ panic(fmt.Sprintf(
+ "unexpected block size shrinkage: %v vs %v",
+ len(b.Values), numNames))
+ }
if debug {
- if len(b.Values) >= int(size) {
+ if len(b.Values) >= int(numNames) {
panic(fmt.Sprintf(
"unexpected block size shrinkage: %v vs %v",
- len(b.Values), size))
+ len(b.Values), numNames))
}
}
- alloc.AllocateBlockItems(int64(size) - int64(len(b.Values)))
- values := make([]TypedValue, int(size))
+ if int(numNames) == len(b.Values) {
+ return // nothing to do
+ }
+ alloc.AllocateBlockItems(int64(numNames) - int64(len(b.Values)))
+ values := make([]TypedValue, numNames)
copy(values, b.Values)
+ // NOTE this is a bit confusing because of the faux offset.
+ // The heap item values are always false for the old names.
+ // Keep in sync with NewBlock().
+ // XXX pass allocator in for heap items.
+ heapItems := source.GetHeapItems()
+ for i := len(b.Values); i < int(numNames); i++ {
+ isHeap := heapItems[i]
+ if !isHeap {
+ continue
+ }
+ // indicates must always be heap item.
+ values[i] = TypedValue{
+ T: heapItemType{},
+ V: &HeapItemValue{},
+ }
+ }
b.Values = values
}
@@ -2368,6 +2468,10 @@ type RefValue struct {
Hash ValueHash `json:",omitempty"`
}
+func (ref RefValue) GetObjectID() ObjectID {
+ return ref.ObjectID
+}
+
// Base for a detached singleton (e.g. new(int) or &struct{})
// Conceptually like a Block that holds one value.
// NOTE: could be renamed to HeapItemBaseValue.
@@ -2383,8 +2487,7 @@ func defaultStructFields(alloc *Allocator, st *StructType) []TypedValue {
tvs := alloc.NewStructFields(len(st.Fields))
for i, ft := range st.Fields {
if ft.Type.Kind() != InterfaceKind {
- tvs[i].T = ft.Type
- tvs[i].V = defaultValue(alloc, ft.Type)
+ tvs[i] = defaultTypedValue(alloc, ft.Type)
}
}
return tvs
@@ -2404,37 +2507,43 @@ func defaultArrayValue(alloc *Allocator, at *ArrayType) *ArrayValue {
tvs := av.List
if et := at.Elem(); et.Kind() != InterfaceKind {
for i := 0; i < at.Len; i++ {
- tvs[i].T = et
- tvs[i].V = defaultValue(alloc, et)
+ tvs[i] = defaultTypedValue(alloc, et)
}
}
return av
}
-func defaultValue(alloc *Allocator, t Type) Value {
+func defaultTypedValue(alloc *Allocator, t Type) TypedValue {
switch ct := baseOf(t).(type) {
case nil:
panic("unexpected nil type")
+ case *InterfaceType:
+ return TypedValue{}
case *ArrayType:
- return defaultArrayValue(alloc, ct)
+ return TypedValue{
+ T: t,
+ V: defaultArrayValue(alloc, ct),
+ }
case *StructType:
- return defaultStructValue(alloc, ct)
+ return TypedValue{
+ T: t,
+ V: defaultStructValue(alloc, ct),
+ }
case *SliceType:
- return nil
+ return TypedValue{
+ T: t,
+ V: nil,
+ }
case *MapType:
- return nil
+ return TypedValue{
+ T: t,
+ V: nil,
+ }
default:
- return nil
- }
-}
-
-func defaultTypedValue(alloc *Allocator, t Type) TypedValue {
- if t.Kind() == InterfaceKind {
- return TypedValue{}
- }
- return TypedValue{
- T: t,
- V: defaultValue(alloc, t),
+ return TypedValue{
+ T: t,
+ V: nil,
+ }
}
}
@@ -2463,8 +2572,11 @@ func typedString(s string) TypedValue {
return tv
}
+// returns the same tv instance for convenience.
func fillValueTV(store Store, tv *TypedValue) *TypedValue {
switch cv := tv.V.(type) {
+ case *HeapItemValue:
+ fillValueTV(store, &cv.Value)
case RefValue:
if cv.PkgPath != "" { // load package
tv.V = store.GetPackage(cv.PkgPath, false)
@@ -2475,6 +2587,7 @@ func fillValueTV(store Store, tv *TypedValue) *TypedValue {
case PointerValue:
// As a special case, cv.Base is filled
// and cv.TV set appropriately.
+ // XXX but why, isn't lazy better?
// Alternatively, could implement
// `PointerValue.Deref(store) *TypedValue`,
// but for execution speed traded off for
diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go
index 75a66bf7a6e..d784bb9f514 100644
--- a/gnovm/pkg/gnolang/values_conversions.go
+++ b/gnovm/pkg/gnolang/values_conversions.go
@@ -30,9 +30,6 @@ func ConvertTo(alloc *Allocator, store Store, tv *TypedValue, t Type, isConst bo
}
// special case for interface target
if t.Kind() == InterfaceKind {
- if tv.IsUndefined() && tv.T == nil {
- tv.T = t
- }
return
}
// special case for undefined/nil source
diff --git a/gnovm/pkg/gnolang/values_fill.go b/gnovm/pkg/gnolang/values_fill.go
index fc3e01a3092..de911d2d6c6 100644
--- a/gnovm/pkg/gnolang/values_fill.go
+++ b/gnovm/pkg/gnolang/values_fill.go
@@ -18,10 +18,6 @@ func (dbv DataByteValue) DeepFill(store Store) Value {
}
func (pv PointerValue) DeepFill(store Store) Value {
- if pv.Key != nil {
- // only used transiently for assignment!
- panic("should not happen")
- }
// No need to fill pv.TV.V because
// either it will be filled with .Base,
// or, it was never persisted anyways.
@@ -30,7 +26,6 @@ func (pv PointerValue) DeepFill(store Store) Value {
TV: pv.TV,
Base: pv.Base.DeepFill(store),
Index: pv.Index,
- Key: nil,
}
}
return pv
diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go
index e64b5f05845..68b9621d8d4 100644
--- a/gnovm/pkg/gnolang/values_string.go
+++ b/gnovm/pkg/gnolang/values_string.go
@@ -256,6 +256,40 @@ func (pv *PackageValue) String() string {
return fmt.Sprintf("package(%s %s)", pv.PkgName, pv.PkgPath)
}
+func (b *Block) String() string {
+ return b.StringIndented(" ")
+}
+
+func (b *Block) StringIndented(indent string) string {
+ source := toString(b.Source)
+ if len(source) > 32 {
+ source = source[:32] + "..."
+ }
+ lines := make([]string, 0, 3)
+ lines = append(lines,
+ fmt.Sprintf("Block(ID:%v,Addr:%p,Source:%s,Parent:%p)",
+ b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}.
+ if b.Source != nil {
+ if _, ok := b.Source.(RefNode); ok {
+ lines = append(lines,
+ fmt.Sprintf("%s(RefNode names not shown)", indent))
+ } else {
+ types := b.Source.GetStaticBlock().Types
+ for i, n := range b.Source.GetBlockNames() {
+ if len(b.Values) <= i {
+ lines = append(lines,
+ fmt.Sprintf("%s%s: undefined static:%s", indent, n, types[i]))
+ } else {
+ lines = append(lines,
+ fmt.Sprintf("%s%s: %s static:%s",
+ indent, n, b.Values[i].String(), types[i]))
+ }
+ }
+ }
+ }
+ return strings.Join(lines, "\n")
+}
+
func (rv RefValue) String() string {
if rv.PkgPath == "" {
return fmt.Sprintf("ref(%v)",
diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go
index 5ddca5162d2..c02077d3094 100644
--- a/gnovm/pkg/test/filetest.go
+++ b/gnovm/pkg/test/filetest.go
@@ -47,10 +47,7 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er
if err != nil {
return "", err
}
- ctx := Context(
- pkgPath,
- coins,
- )
+ ctx := Context("", pkgPath, coins)
maxAllocRaw := dirs.FirstDefault(DirectiveMaxAlloc, "0")
maxAlloc, err := strconv.ParseInt(maxAllocRaw, 10, 64)
if err != nil {
@@ -240,6 +237,7 @@ func (opts *TestOptions) runTest(m *gno.Machine, pkgPath, filename string, conte
m.Store.SetBlockNode(pn)
m.Store.SetCachePackage(pv)
m.SetActivePackage(pv)
+ m.Context.(*teststd.TestExecContext).OriginCaller = DefaultCaller
n := gno.MustParseFile(filename, string(content))
m.RunFiles(n)
m.RunStatement(gno.S(gno.Call(gno.X("main"))))
@@ -264,6 +262,12 @@ func (opts *TestOptions) runTest(m *gno.Machine, pkgPath, filename string, conte
}
orig, tx := m.Store, m.Store.BeginTransaction(nil, nil, nil)
m.Store = tx
+
+ // Validate Gno syntax and type check.
+ if err := gno.TypeCheckMemPackageTest(memPkg, m.Store); err != nil {
+ return runResult{Error: err.Error()}
+ }
+
// Run decls and init functions.
m.RunMemPackage(memPkg, true)
// Clear store cache and reconstruct machine from committed info
@@ -272,11 +276,15 @@ func (opts *TestOptions) runTest(m *gno.Machine, pkgPath, filename string, conte
m.Store = orig
pv2 := m.Store.GetPackage(pkgPath, false)
- m.SetActivePackage(pv2)
+ m.SetActivePackage(pv2) // XXX should it set the realm?
+ m.Context.(*teststd.TestExecContext).OriginCaller = DefaultCaller
gno.EnableDebug()
// clear store.opslog from init function(s).
m.Store.SetLogStoreOps(opslog) // resets.
- m.RunStatement(gno.S(gno.Call(gno.X("main"))))
+ // Call main() like withrealm(main)().
+ // This will switch the realm to the package.
+ // main() must start with crossing().
+ m.RunStatement(gno.S(gno.Call(gno.Call(gno.X("cross"), gno.X("main"))))) // switch realm.
}
return runResult{
diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go
index dcf5b4d3a50..5dcaac6fa28 100644
--- a/gnovm/pkg/test/imports.go
+++ b/gnovm/pkg/test/imports.go
@@ -75,7 +75,7 @@ func StoreWithOptions(
baseDir := filepath.Join(rootDir, "gnovm", "tests", "files", "extern", pkgPath[len(testPath):])
memPkg := gno.MustReadMemPackage(baseDir, pkgPath)
send := std.Coins{}
- ctx := Context(pkgPath, send)
+ ctx := Context("", pkgPath, send)
m2 := gno.NewMachineWithOptions(gno.MachineOptions{
PkgPath: "test",
Output: output,
@@ -101,7 +101,7 @@ func StoreWithOptions(
}
send := std.Coins{}
- ctx := Context(pkgPath, send)
+ ctx := Context("", pkgPath, send)
m2 := gno.NewMachineWithOptions(gno.MachineOptions{
PkgPath: "test",
Output: output,
diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go
index fe87944f913..e89e660bcba 100644
--- a/gnovm/pkg/test/test.go
+++ b/gnovm/pkg/test/test.go
@@ -37,11 +37,12 @@ const (
)
// Context returns a TestExecContext. Usable for test purpose only.
+// The caller should be empty for package initialization.
// The returned context has a mock banker, params and event logger. It will give
// the pkgAddr the coins in `send` by default, and only that.
// The Height and Timestamp parameters are set to the [DefaultHeight] and
// [DefaultTimestamp].
-func Context(pkgPath string, send std.Coins) *teststd.TestExecContext {
+func Context(caller crypto.Bech32Address, pkgPath string, send std.Coins) *teststd.TestExecContext {
// FIXME: create a better package to manage this, with custom constructors
pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called.
@@ -55,7 +56,7 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext {
ChainDomain: "gno.land", // TODO: make this configurable
Height: DefaultHeight,
Timestamp: DefaultTimestamp,
- OriginCaller: DefaultCaller,
+ OriginCaller: caller,
OriginPkgAddr: pkgAddr.Bech32(),
OriginSend: send,
OriginSendSpent: new(std.Coins),
@@ -70,11 +71,12 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext {
}
// Machine is a minimal machine, set up with just the Store, Output and Context.
+// It is only used for linting/preprocessing.
func Machine(testStore gno.Store, output io.Writer, pkgPath string, debug bool) *gno.Machine {
return gno.NewMachineWithOptions(gno.MachineOptions{
Store: testStore,
Output: output,
- Context: Context(pkgPath, nil),
+ Context: Context("", pkgPath, nil),
Debug: debug,
})
}
@@ -337,6 +339,7 @@ func (opts *TestOptions) runTestFiles(
}
pv := m.Package
+ // Load the test files into package and save.
m.RunFiles(files.Files...)
for _, tf := range tests {
diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go
index 739bb7bf702..902e6996729 100644
--- a/gnovm/stdlibs/generated.go
+++ b/gnovm/stdlibs/generated.go
@@ -39,12 +39,12 @@ var nativeFuncs = [...]NativeFunc{
"crypto/ed25519",
"verify",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("[]byte")},
- {Name: gno.N("p1"), Type: gno.X("[]byte")},
- {Name: gno.N("p2"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("[]byte")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("bool")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("bool")},
},
false,
func(m *gno.Machine) {
@@ -81,10 +81,10 @@ var nativeFuncs = [...]NativeFunc{
"crypto/sha256",
"sum256",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("[]byte")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("[32]byte")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("[32]byte")},
},
false,
func(m *gno.Machine) {
@@ -111,10 +111,10 @@ var nativeFuncs = [...]NativeFunc{
"math",
"Float32bits",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("float32")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("float32")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("uint32")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("uint32")},
},
false,
func(m *gno.Machine) {
@@ -141,10 +141,10 @@ var nativeFuncs = [...]NativeFunc{
"math",
"Float32frombits",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint32")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint32")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("float32")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("float32")},
},
false,
func(m *gno.Machine) {
@@ -171,10 +171,10 @@ var nativeFuncs = [...]NativeFunc{
"math",
"Float64bits",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("float64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("float64")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("uint64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("uint64")},
},
false,
func(m *gno.Machine) {
@@ -201,10 +201,10 @@ var nativeFuncs = [...]NativeFunc{
"math",
"Float64frombits",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint64")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("float64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("float64")},
},
false,
func(m *gno.Machine) {
@@ -231,12 +231,12 @@ var nativeFuncs = [...]NativeFunc{
"std",
"bankerGetCoins",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint8")},
- {Name: gno.N("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint8")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("[]string")},
- {Name: gno.N("r1"), Type: gno.X("[]int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("[]int64")},
},
true,
func(m *gno.Machine) {
@@ -275,11 +275,11 @@ var nativeFuncs = [...]NativeFunc{
"std",
"bankerSendCoins",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint8")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("[]string")},
- {Name: gno.N("p4"), Type: gno.X("[]int64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint8")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("p4"), Type: gno.X("[]int64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -323,11 +323,11 @@ var nativeFuncs = [...]NativeFunc{
"std",
"bankerTotalCoin",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint8")},
- {Name: gno.N("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint8")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("int64")},
},
true,
func(m *gno.Machine) {
@@ -361,10 +361,10 @@ var nativeFuncs = [...]NativeFunc{
"std",
"bankerIssueCoin",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint8")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint8")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("int64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -403,10 +403,10 @@ var nativeFuncs = [...]NativeFunc{
"std",
"bankerRemoveCoin",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("uint8")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("uint8")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("int64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -445,8 +445,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"emit",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("[]string")},
},
[]gno.FieldTypeExpr{},
true,
@@ -488,7 +488,7 @@ var nativeFuncs = [...]NativeFunc{
"ChainID",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")},
},
true,
func(m *gno.Machine) {
@@ -508,7 +508,7 @@ var nativeFuncs = [...]NativeFunc{
"ChainDomain",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")},
},
true,
func(m *gno.Machine) {
@@ -528,7 +528,7 @@ var nativeFuncs = [...]NativeFunc{
"ChainHeight",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("int64")},
},
true,
func(m *gno.Machine) {
@@ -548,8 +548,8 @@ var nativeFuncs = [...]NativeFunc{
"originSend",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("[]string")},
- {Name: gno.N("r1"), Type: gno.X("[]int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("[]int64")},
},
true,
func(m *gno.Machine) {
@@ -574,7 +574,7 @@ var nativeFuncs = [...]NativeFunc{
"originCaller",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")},
},
true,
func(m *gno.Machine) {
@@ -594,7 +594,7 @@ var nativeFuncs = [...]NativeFunc{
"originPkgAddr",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")},
},
true,
func(m *gno.Machine) {
@@ -609,47 +609,15 @@ var nativeFuncs = [...]NativeFunc{
))
},
},
- {
- "std",
- "callerAt",
- []gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("int")},
- },
- []gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
- },
- true,
- func(m *gno.Machine) {
- b := m.LastBlock()
- var (
- p0 int
- rp0 = reflect.ValueOf(&p0).Elem()
- )
-
- tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV
- tv0.DeepFill(m.Store)
- gno.Gno2GoValue(tv0, rp0)
-
- r0 := libs_std.X_callerAt(
- m,
- p0)
-
- m.PushValue(gno.Go2GnoValue(
- m.Alloc,
- m.Store,
- reflect.ValueOf(&r0).Elem(),
- ))
- },
- },
{
"std",
"getRealm",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("int")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("int")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
- {Name: gno.N("r1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("string")},
},
true,
func(m *gno.Machine) {
@@ -695,8 +663,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamString",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{},
true,
@@ -725,8 +693,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamBool",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("bool")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("bool")},
},
[]gno.FieldTypeExpr{},
true,
@@ -755,8 +723,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamInt64",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("int64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -785,8 +753,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamUint64",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("uint64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("uint64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -815,8 +783,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamBytes",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("[]byte")},
},
[]gno.FieldTypeExpr{},
true,
@@ -845,8 +813,8 @@ var nativeFuncs = [...]NativeFunc{
"std",
"setParamStrings",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("[]string")},
},
[]gno.FieldTypeExpr{},
true,
@@ -875,10 +843,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamString",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{},
true,
@@ -917,10 +885,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamBool",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("bool")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("bool")},
},
[]gno.FieldTypeExpr{},
true,
@@ -959,10 +927,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamInt64",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("int64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -1001,10 +969,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamUint64",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("uint64")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("uint64")},
},
[]gno.FieldTypeExpr{},
true,
@@ -1043,10 +1011,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamBytes",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("[]byte")},
},
[]gno.FieldTypeExpr{},
true,
@@ -1085,10 +1053,10 @@ var nativeFuncs = [...]NativeFunc{
"sys/params",
"setSysParamStrings",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
- {Name: gno.N("p2"), Type: gno.X("string")},
- {Name: gno.N("p3"), Type: gno.X("[]string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p2"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p3"), Type: gno.X("[]string")},
},
[]gno.FieldTypeExpr{},
true,
@@ -1128,7 +1096,7 @@ var nativeFuncs = [...]NativeFunc{
"unixNano",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("int64")},
},
false,
func(m *gno.Machine) {
@@ -1146,8 +1114,8 @@ var nativeFuncs = [...]NativeFunc{
"recoverWithStacktrace",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.AnyT()},
- {Name: gno.N("r1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.AnyT()},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("string")},
},
false,
func(m *gno.Machine) {
@@ -1165,12 +1133,12 @@ var nativeFuncs = [...]NativeFunc{
"testing",
"matchString",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
- {Name: gno.N("p1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p1"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("bool")},
- {Name: gno.N("r1"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("bool")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("string")},
},
false,
func(m *gno.Machine) {
@@ -1208,9 +1176,9 @@ var nativeFuncs = [...]NativeFunc{
"now",
[]gno.FieldTypeExpr{},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("int64")},
- {Name: gno.N("r1"), Type: gno.X("int32")},
- {Name: gno.N("r2"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("int64")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("int32")},
+ {NameExpr: *gno.Nx("r2"), Type: gno.X("int64")},
},
true,
func(m *gno.Machine) {
@@ -1239,11 +1207,11 @@ var nativeFuncs = [...]NativeFunc{
"time",
"loadFromEmbeddedTZData",
[]gno.FieldTypeExpr{
- {Name: gno.N("p0"), Type: gno.X("string")},
+ {NameExpr: *gno.Nx("p0"), Type: gno.X("string")},
},
[]gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("[]byte")},
- {Name: gno.N("r1"), Type: gno.X("bool")},
+ {NameExpr: *gno.Nx("r0"), Type: gno.X("[]byte")},
+ {NameExpr: *gno.Nx("r1"), Type: gno.X("bool")},
},
false,
func(m *gno.Machine) {
diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go
index 6e8f00ee3e8..f12d4081835 100644
--- a/gnovm/stdlibs/std/emit_event.go
+++ b/gnovm/stdlibs/std/emit_event.go
@@ -18,7 +18,6 @@ func X_emit(m *gno.Machine, typ string, attrs []string) {
}
_, pkgPath := currentRealm(m)
- fnIdent := getPreviousFunctionNameFromTarget(m, "Emit")
ctx := GetContext(m)
@@ -26,7 +25,6 @@ func X_emit(m *gno.Machine, typ string, attrs []string) {
Type: typ,
Attributes: eventAttrs,
PkgPath: pkgPath,
- Func: fnIdent,
}
ctx.EventLogger.EmitEvent(evt)
@@ -47,15 +45,16 @@ func attrKeysAndValues(attrs []string) ([]GnoEventAttribute, error) {
return eventAttrs, nil
}
+// XXX rename to std/events.Event?
type GnoEvent struct {
Type string `json:"type"`
Attributes []GnoEventAttribute `json:"attrs"`
PkgPath string `json:"pkg_path"`
- Func string `json:"func"`
}
func (e GnoEvent) AssertABCIEvent() {}
+// XXX rename to std/events.Attribute?
type GnoEventAttribute struct {
Key string `json:"key"`
Value string `json:"value"`
diff --git a/gnovm/stdlibs/std/emit_event_test.go b/gnovm/stdlibs/std/emit_event_test.go
index ecf08eebd60..474cd6e90b1 100644
--- a/gnovm/stdlibs/std/emit_event_test.go
+++ b/gnovm/stdlibs/std/emit_event_test.go
@@ -2,8 +2,6 @@ package std
import (
"encoding/json"
- "strconv"
- "strings"
"testing"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
@@ -11,12 +9,20 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestEmit(t *testing.T) {
- m := gno.NewMachine("emit", nil)
+func pushFuncFrame(m *gno.Machine, name gno.Name) {
+ fv := &gno.FuncValue{Name: name, PkgPath: m.Package.PkgPath}
+ m.PushFrameCall(gno.Call(name), fv, gno.TypedValue{}) // fake frame
+}
+func TestEmit(t *testing.T) {
+ m := gno.NewMachine("emit_test", nil)
m.Context = ExecContext{}
-
+ pushFuncFrame(m, "main")
+ pushFuncFrame(m, "Emit")
_, pkgPath := X_getRealm(m, 0)
+ if pkgPath != "emit_test" || m.Package.PkgPath != "emit_test" {
+ panic("inconsistent package paths")
+ }
tests := []struct {
name string
eventType string
@@ -31,8 +37,7 @@ func TestEmit(t *testing.T) {
expectedEvents: []GnoEvent{
{
Type: "test",
- PkgPath: pkgPath,
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "key1", Value: "value1"},
{Key: "key2", Value: "value2"},
@@ -54,8 +59,7 @@ func TestEmit(t *testing.T) {
expectedEvents: []GnoEvent{
{
Type: "test",
- PkgPath: pkgPath,
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "key1", Value: ""},
{Key: "key2", Value: "value2"},
@@ -71,8 +75,7 @@ func TestEmit(t *testing.T) {
expectedEvents: []GnoEvent{
{
Type: "",
- PkgPath: pkgPath,
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "key1", Value: "value1"},
{Key: "key2", Value: "value2"},
@@ -88,8 +91,7 @@ func TestEmit(t *testing.T) {
expectedEvents: []GnoEvent{
{
Type: "test",
- PkgPath: pkgPath,
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "", Value: "value1"},
{Key: "key2", Value: "value2"},
@@ -106,9 +108,8 @@ func TestEmit(t *testing.T) {
m.Context = ExecContext{EventLogger: elgs}
if tt.expectPanic {
- assert.Panics(t, func() {
- X_emit(m, tt.eventType, tt.attrs)
- })
+ X_emit(m, tt.eventType, tt.attrs)
+ assert.Equal(t, 1, len(m.Exceptions))
} else {
X_emit(m, tt.eventType, tt.attrs)
assert.Equal(t, len(tt.expectedEvents), len(elgs.Events()))
@@ -131,7 +132,9 @@ func TestEmit(t *testing.T) {
func TestEmit_MultipleEvents(t *testing.T) {
t.Parallel()
- m := gno.NewMachine("emit", nil)
+ m := gno.NewMachine("emit_test", nil)
+ pushFuncFrame(m, "main")
+ pushFuncFrame(m, "Emit")
elgs := sdk.NewEventLogger()
m.Context = ExecContext{EventLogger: elgs}
@@ -151,8 +154,7 @@ func TestEmit_MultipleEvents(t *testing.T) {
expect := []GnoEvent{
{
Type: "test1",
- PkgPath: "",
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "key1", Value: "value1"},
{Key: "key2", Value: "value2"},
@@ -160,8 +162,7 @@ func TestEmit_MultipleEvents(t *testing.T) {
},
{
Type: "test2",
- PkgPath: "",
- Func: "",
+ PkgPath: "emit_test",
Attributes: []GnoEventAttribute{
{Key: "key3", Value: "value3"},
{Key: "key4", Value: "value4"},
@@ -176,141 +177,3 @@ func TestEmit_MultipleEvents(t *testing.T) {
assert.Equal(t, string(expectRes), string(res))
}
-
-func TestEmit_ContractInteraction(t *testing.T) {
- const (
- testFoo = "foo"
- testQux = "qux"
- )
-
- type (
- contractA struct {
- foo func(*gno.Machine, func())
- }
-
- contractB struct {
- qux func(m *gno.Machine)
- }
- )
-
- t.Parallel()
- m := gno.NewMachine("emit", nil)
- elgs := sdk.NewEventLogger()
- m.Context = ExecContext{EventLogger: elgs}
-
- baz := func(m *gno.Machine) {
- X_emit(m, testFoo, []string{"k1", "v1", "k2", "v2"})
- }
-
- a := &contractA{
- foo: func(m *gno.Machine, cb func()) {
- baz(m)
- cb()
- },
- }
- b := &contractB{
- qux: func(m *gno.Machine) {
- X_emit(m, testQux, []string{"bar", "baz"})
- },
- }
-
- a.foo(m, func() {
- b.qux(m)
- })
-
- assert.Equal(t, 2, len(elgs.Events()))
-
- res, err := json.Marshal(elgs.Events())
- if err != nil {
- t.Fatal(err)
- }
-
- expected := `[{"type":"foo","attrs":[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}],"pkg_path":"","func":""},{"type":"qux","attrs":[{"key":"bar","value":"baz"}],"pkg_path":"","func":""}]`
-
- assert.Equal(t, expected, string(res))
-}
-
-func TestEmit_Iteration(t *testing.T) {
- const testBar = "bar"
- m := gno.NewMachine("emit", nil)
-
- elgs := sdk.NewEventLogger()
- m.Context = ExecContext{EventLogger: elgs}
-
- iterEvent := func(m *gno.Machine) {
- for i := 0; i < 10; i++ {
- X_emit(m, testBar, []string{"qux", "value1"})
- }
- }
- iterEvent(m)
- assert.Equal(t, 10, len(elgs.Events()))
-
- res, err := json.Marshal(elgs.Events())
- if err != nil {
- t.Fatal(err)
- }
-
- var builder strings.Builder
- builder.WriteString("[")
- for i := 0; i < 10; i++ {
- builder.WriteString(`{"type":"bar","attrs":[{"key":"qux","value":"value1"}],"pkg_path":"","func":""},`)
- }
- expected := builder.String()[:builder.Len()-1] + "]"
-
- assert.Equal(t, expected, string(res))
-}
-
-func complexInteraction(m *gno.Machine) {
- deferEmitExample(m)
-}
-
-func deferEmitExample(m *gno.Machine) {
- defer func() {
- X_emit(m, "DeferEvent", []string{"key1", "value1", "key2", "value2"})
- }()
-
- forLoopEmitExample(m, 3, func(i int) {
- X_emit(m, "ForLoopEvent", []string{"iteration", strconv.Itoa(i), "key", "value"})
- })
-
- callbackEmitExample(m, func() {
- X_emit(m, "CallbackEvent", []string{"key1", "value1", "key2", "value2"})
- })
-}
-
-func forLoopEmitExample(m *gno.Machine, count int, callback func(int)) {
- defer func() {
- X_emit(m, "ForLoopCompletionEvent", []string{"count", strconv.Itoa(count)})
- }()
-
- for i := 0; i < count; i++ {
- callback(i)
- }
-}
-
-func callbackEmitExample(m *gno.Machine, callback func()) {
- defer func() {
- X_emit(m, "CallbackCompletionEvent", []string{"key", "value"})
- }()
-
- callback()
-}
-
-func TestEmit_ComplexInteraction(t *testing.T) {
- m := gno.NewMachine("emit", nil)
-
- elgs := sdk.NewEventLogger()
- m.Context = ExecContext{EventLogger: elgs}
-
- complexInteraction(m)
-
- assert.Equal(t, 7, len(elgs.Events()))
-
- res, err := json.Marshal(elgs.Events())
- if err != nil {
- t.Fatal(err)
- }
-
- expected := `[{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"0"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"1"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopEvent","attrs":[{"key":"iteration","value":"2"},{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"ForLoopCompletionEvent","attrs":[{"key":"count","value":"3"}],"pkg_path":"","func":""},{"type":"CallbackEvent","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}],"pkg_path":"","func":""},{"type":"CallbackCompletionEvent","attrs":[{"key":"key","value":"value"}],"pkg_path":"","func":""},{"type":"DeferEvent","attrs":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}],"pkg_path":"","func":""}]`
- assert.Equal(t, expected, string(res))
-}
diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno
index 7366494825f..b047a094e22 100644
--- a/gnovm/stdlibs/std/native.gno
+++ b/gnovm/stdlibs/std/native.gno
@@ -37,14 +37,21 @@ func OriginPkgAddress() Address {
return Address(originPkgAddr())
}
+/*
+This function and variations are a literal genie/demon that will create a layer
+of middlemen who will take away your agency. Keep it unavailable for a thousand
+years. Then uncomment, see the harm it produces, and then delete it forever.
+
func CallerAt(n int) Address {
return Address(callerAt(n))
}
+func callerAt(n int) string
+*/
+
// Variations which don't use named types.
func originSend() (denoms []string, amounts []int64)
func originCaller() string
func originPkgAddr() string
-func callerAt(n int) string
func getRealm(height int) (address string, pkgPath string)
func assertCallerIsRealm()
diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go
index b226ca03afa..841c75bbd6d 100644
--- a/gnovm/stdlibs/std/native.go
+++ b/gnovm/stdlibs/std/native.go
@@ -1,8 +1,9 @@
package std
import (
+ "fmt"
+
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
- "github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -34,38 +35,6 @@ func ChainHeight(m *gno.Machine) int64 {
return GetContext(m).Height
}
-// getPreviousFunctionNameFromTarget returns the last called function name (identifier) from the call stack.
-func getPreviousFunctionNameFromTarget(m *gno.Machine, targetFunc string) string {
- targetIndex := findTargetFunctionIndex(m, targetFunc)
- if targetIndex == -1 {
- return ""
- }
- return findPreviousFunctionName(m, targetIndex)
-}
-
-// findTargetFunctionIndex finds and returns the index of the target function in the call stack.
-func findTargetFunctionIndex(m *gno.Machine, targetFunc string) int {
- for i := len(m.Frames) - 1; i >= 0; i-- {
- currFunc := m.Frames[i].Func
- if currFunc != nil && currFunc.Name == gno.Name(targetFunc) {
- return i
- }
- }
- return -1
-}
-
-// findPreviousFunctionName returns the function name before the given index in the call stack.
-func findPreviousFunctionName(m *gno.Machine, targetIndex int) string {
- for i := targetIndex - 1; i >= 0; i-- {
- currFunc := m.Frames[i].Func
- if currFunc != nil {
- return string(currFunc.Name)
- }
- }
-
- panic("function name not found")
-}
-
func X_originSend(m *gno.Machine) (denoms []string, amounts []int64) {
os := GetContext(m).OriginSend
return ExpandCoins(os)
@@ -79,6 +48,7 @@ func X_originPkgAddr(m *gno.Machine) string {
return string(GetContext(m).OriginPkgAddr)
}
+/* See comment in stdlibs/std/native.gno
func X_callerAt(m *gno.Machine, n int) string {
if n <= 0 {
m.Panic(typedString("CallerAt requires positive arg"))
@@ -98,40 +68,60 @@ func X_callerAt(m *gno.Machine, n int) string {
ctx := GetContext(m)
return string(ctx.OriginCaller)
}
- return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32())
+ return string(m.MustPeekCallFrame(n).LastPackage.GetPkgAddr().Bech32())
}
+*/
func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) {
// NOTE: keep in sync with test/stdlibs/std.getRealm
var (
- ctx = GetContext(m)
- currentCaller crypto.Bech32Address
- // Keeps track of the number of times currentCaller
- // has changed.
- changes int
+ ctx = GetContext(m)
+ crosses int // track realm crosses
+ lfr *gno.Frame = m.LastFrame() // last call frame
)
for i := m.NumFrames() - 1; i >= 0; i-- {
fr := m.Frames[i]
- if fr.LastPackage == nil || !fr.LastPackage.IsRealm() {
+
+ // Skip over (non-realm) non-crosses.
+ if !fr.IsCall() {
+ continue
+ }
+ if !fr.WithCross {
+ lfr = fr
continue
}
- // LastPackage is a realm. Get caller and pkgPath, and compare against
- // current* values.
- caller := fr.LastPackage.GetPkgAddr().Bech32()
- pkgPath := fr.LastPackage.PkgPath
- if caller != currentCaller {
- if changes == height {
- return string(caller), pkgPath
- }
- currentCaller = caller
- changes++
+ // Sanity check
+ if !fr.DidCross {
+ panic(fmt.Sprintf(
+ "call to cross(fn) did not call crossing : %s",
+ fr.Func.String()))
+ }
+
+ crosses++
+ if crosses > height {
+ currlm := lfr.LastRealm
+ caller, rlmPath := gno.DerivePkgAddr(currlm.Path).Bech32(), currlm.Path
+ return string(caller), rlmPath
}
+ lfr = fr
+ }
+
+ if crosses != height {
+ m.Panic(typedString("frame not found"))
+ }
+
+ // Special case if package initialization.
+ if ctx.OriginCaller == "" {
+ fr := m.Frames[0]
+ caller := string(fr.LastPackage.GetPkgAddr().Bech32())
+ pkgPath := fr.LastPackage.PkgPath
+ return string(caller), pkgPath
}
- // Fallback case: return OriginCaller.
+ // Base case: return OriginCaller.
return string(ctx.OriginCaller), ""
}
diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go
index e51badd4e2e..1b61743c886 100644
--- a/gnovm/stdlibs/std/native_test.go
+++ b/gnovm/stdlibs/std/native_test.go
@@ -1,6 +1,7 @@
package std
import (
+ "fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -18,6 +19,12 @@ func TestPreviousRealmIsOrigin(t *testing.T) {
msgCallFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "main"}}
msgRunFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/g1337/run"}}
)
+ type expectations struct {
+ addr crypto.Bech32Address
+ pkgPath string
+ isOriginCall bool
+ doesPanic bool
+ }
tests := []struct {
name string
machine *gno.Machine
@@ -179,8 +186,13 @@ func TestPreviousRealmIsOrigin(t *testing.T) {
expectedIsOriginCall: false,
},
}
- for _, tt := range tests {
+ for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("fail", i)
+ }
+ }()
assert := assert.New(t)
addr, pkgPath := X_getRealm(tt.machine, 1)
diff --git a/gnovm/tests/files/access2.gno b/gnovm/tests/files/access2.gno
index 46b17136b54..10ab2ffc352 100644
--- a/gnovm/tests/files/access2.gno
+++ b/gnovm/tests/files/access2.gno
@@ -6,6 +6,8 @@ import (
)
func main() {
+ crossing()
+
testutils.TestVar1 += 1
println(testutils.TestVar1)
}
diff --git a/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno
index 92e3a1e3075..aa5de45d2ac 100644
--- a/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno
+++ b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface_filetest.gno
@@ -33,7 +33,7 @@ func main() {
}
// Output:
-// (undefined)
+// undefined
// (slice[(1 int)] main.myIntSlice)
// (slice[(1 int)] main.myIntSlice)
// (slice[(1 int)] main.myIntSlice)
diff --git a/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno
index 1bc5add0440..2b6e85caef1 100644
--- a/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno
+++ b/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno
@@ -6,6 +6,8 @@ import (
)
func main() {
+ crossing()
+
println(tests.GetZeroType())
}
diff --git a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno
index e3cc50ab330..a1589623f4b 100644
--- a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno
+++ b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno
@@ -14,6 +14,8 @@ type Int struct {
}
func main() {
+ crossing()
+
zero = &Int{
abs: []word{0},
}
@@ -26,7 +28,7 @@ func main() {
// Realm:
// switchrealm["gno.land/r/test"]
-// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={
+// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={
// "Data": null,
// "List": [
// {
@@ -37,13 +39,13 @@ func main() {
// }
// ],
// "ObjectInfo": {
-// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6",
+// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8",
// "ModTime": "0",
-// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5",
+// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7",
// "RefCount": "1"
// }
// }
-// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={
+// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={
// "Fields": [
// {
// "T": {
@@ -54,8 +56,8 @@ func main() {
// "@type": "/gno.SliceValue",
// "Base": {
// "@type": "/gno.RefValue",
-// "Hash": "e256933ba4dfda233a7edb0902880d554118ba6e",
-// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6"
+// "Hash": "053ebe7d3e2087ff390f1c09b3f36cf0763f0967",
+// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8"
// },
// "Length": "1",
// "Maxcap": "1",
@@ -64,17 +66,17 @@ func main() {
// }
// ],
// "ObjectInfo": {
-// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5",
+// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7",
// "ModTime": "0",
-// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4",
+// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6",
// "RefCount": "1"
// }
// }
-// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={
+// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={
// "ObjectInfo": {
-// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4",
+// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6",
// "ModTime": "0",
-// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2",
+// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3",
// "RefCount": "1"
// },
// "Value": {
@@ -84,26 +86,35 @@ func main() {
// },
// "V": {
// "@type": "/gno.RefValue",
-// "Hash": "2b8a024c53e94431e6203115feaf86b36413d7b2",
-// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5"
+// "Hash": "e757ea3d88983d3fc397e089882a1e31ee2c5e10",
+// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7"
// }
// }
// }
-// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]=
-// @@ -65,6 +65,16 @@
-// "@type": "/gno.RefType",
-// "ID": "gno.land/r/test.Int"
-// }
-// + },
-// + "V": {
-// + "@type": "/gno.PointerValue",
-// + "Base": {
-// + "@type": "/gno.RefValue",
-// + "Hash": "3c89d875f7d6daa94113aa4c7e03432ba56202c2",
-// + "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4"
-// + },
-// + "Index": "0",
-// + "TV": null
+// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]=
+// @@ -1,7 +1,7 @@
+// {
+// "ObjectInfo": {
+// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3",
+// - "ModTime": "0",
+// + "ModTime": "5",
+// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2",
+// "RefCount": "1"
+// },
+// @@ -12,6 +12,16 @@
+// "@type": "/gno.RefType",
+// "ID": "gno.land/r/test.Int"
// }
-// },
-// {
+// + },
+// + "V": {
+// + "@type": "/gno.PointerValue",
+// + "Base": {
+// + "@type": "/gno.RefValue",
+// + "Hash": "afc8a8a4c127ea7b6713ec59220d7c6cdd6e842e",
+// + "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6"
+// + },
+// + "Index": "0",
+// + "TV": null
+// }
+// }
+// }
diff --git a/gnovm/tests/files/closure10.gno b/gnovm/tests/files/closure10.gno
new file mode 100644
index 00000000000..ab2eabaeca0
--- /dev/null
+++ b/gnovm/tests/files/closure10.gno
@@ -0,0 +1,18 @@
+package main
+
+func main() {
+ var x any = int(1)
+ if _, ok := x.(string); !ok { // !ok
+ x = func() bool {
+ return ok
+ }
+ }
+ ok2 := x.(func() bool)()
+ println(ok2)
+}
+
+// Preprocessed:
+// file{ package main; func main() { var x (const-type interface {}) = (const (1 int)); if _, ok := x.((const-type string)); !ok<~VPBlock(1,0)> { x = func func() .res.0 (const-type bool){ return ok<~VPBlock(1,1)> }> }; ok2 := x.(func() .res.0 (const-type bool))(); (const (println func(...interface {})))(ok2) } }
+
+// Output:
+// false
diff --git a/gnovm/tests/files/closure11.gno b/gnovm/tests/files/closure11.gno
new file mode 100644
index 00000000000..19358c0d428
--- /dev/null
+++ b/gnovm/tests/files/closure11.gno
@@ -0,0 +1,19 @@
+package main
+
+type fooer interface{}
+
+var f fooer = 1
+
+func trymake() func() {
+ switch v := f.(type) {
+ case int:
+ return func() {
+ println(v)
+ }
+ default:
+ }
+ return func() {}
+}
+func main() {
+ trymake()()
+}
diff --git a/gnovm/tests/files/closure12.gno b/gnovm/tests/files/closure12.gno
new file mode 100644
index 00000000000..c1dda1a1ed0
--- /dev/null
+++ b/gnovm/tests/files/closure12.gno
@@ -0,0 +1,18 @@
+package main
+
+func trymake() func() {
+ switch i := 1; i {
+ case 1:
+ return func() {
+ println(i)
+ }
+ default:
+ }
+ return nil
+}
+func main() {
+ trymake()()
+}
+
+// Output:
+// 1
diff --git a/gnovm/tests/files/closure9.gno b/gnovm/tests/files/closure9.gno
new file mode 100644
index 00000000000..b774b2ae656
--- /dev/null
+++ b/gnovm/tests/files/closure9.gno
@@ -0,0 +1,26 @@
+package main
+
+import "fmt"
+
+var p int
+
+func main() {
+ b, x := makeClosure(1)
+ x(true)
+}
+
+func makeClosure(a int) (b int, c func(bool)) {
+ return 2, func(recurse bool) {
+ fmt.Println(a, b, p)
+ if recurse {
+ c(false)
+ }
+ }
+}
+
+// Preprocessed:
+// file{ package main; import fmt fmt; var p (const-type int); func main() { b, x := makeClosure((const (1 int))); x((const (true bool))) }; func makeClosure(a~ (const-type int)) b~ (const-type int), c~ func(.arg_0 (const-type bool)) { return (const (2 int)), func func(recurse (const-type bool)){ (const (ref(fmt) package{})).Println(a<~VPBlock(1,1)>, b<~VPBlock(1,2)>, (const (ref(main) package{})).p); if recurse { c<~VPBlock(2,3)>((const (false bool))) } }, b<()~VPBlock(1,1)>, c<()~VPBlock(1,2)>> } }
+
+// Output:
+// 1 2 0
+// 1 2 0
diff --git a/gnovm/tests/files/defer/defer1.gno b/gnovm/tests/files/defer/defer1.gno
new file mode 100644
index 00000000000..0c885343a10
--- /dev/null
+++ b/gnovm/tests/files/defer/defer1.gno
@@ -0,0 +1,27 @@
+package main
+
+func bar() (r int) {
+ defer func() { // defer 1
+ r += 4
+ if recover() != nil {
+ r += 3
+ }
+ }()
+
+
+ var f func() // This is a nil function.
+ // The nil function will be pushed onto the stack and executed later.
+ // It will panic, and the panic will be caught by the defer statement above (defer 1).
+ defer f()
+ f = func() { // this has no effect since f (nil) is already pushed on the stack.
+ r += 2
+ }
+ return 1
+}
+
+func main() {
+ println(bar())
+}
+
+// Output:
+// 8
diff --git a/gnovm/tests/files/defer0.gno b/gnovm/tests/files/defer0.gno
index 6233bffb347..f747ee58048 100644
--- a/gnovm/tests/files/defer0.gno
+++ b/gnovm/tests/files/defer0.gno
@@ -2,15 +2,41 @@ package main
import "fmt"
+type Foo struct {
+}
+
+func (f Foo) Println(s ...string) {
+ println(s...)
+}
+
func main() {
println("hello")
- defer fmt.Println("bye")
- defer fmt.Println("au revoir")
+ f := Foo{}
+ defer func() {
+ println("bye1")
+ }()
+ defer println()
+ defer println("p1")
+ defer println("p2", "p3")
+ defer f.Println()
+ defer f.Println("f1")
+ defer f.Println("f2", "f3")
+ defer fmt.Println()
+ defer fmt.Println("fmt1")
+ defer fmt.Println("fmt2", "fmt3")
println("world")
}
// Output:
// hello
// world
-// au revoir
-// bye
+// fmt2 fmt3
+// fmt1
+//
+// f2 f3
+// f1
+//
+// p2 p3
+// p1
+//
+// bye1
diff --git a/gnovm/tests/files/exploit/realm_exploiter.gno b/gnovm/tests/files/exploit/realm_exploiter.gno
new file mode 100644
index 00000000000..ad9a84861e1
--- /dev/null
+++ b/gnovm/tests/files/exploit/realm_exploiter.gno
@@ -0,0 +1,37 @@
+// PKGPATH: gno.land/r/exploiter_test
+package exploiter_test
+
+import (
+ "gno.land/r/demo/tests"
+)
+
+type MyBar struct {
+ A int
+ B *tests.Foo
+}
+
+func (f *MyBar) Bar(x int) {
+ f.B.A = x
+}
+
+func main() {
+ crossing()
+
+ x := (struct {
+ A int
+ B *tests.Foo
+ })(*tests.MyFoo) // <-- this should panic
+
+ y := MyBar(x)
+ z := &y
+ println(tests.MyFoo)
+ z.Bar(10)
+ println(tests.MyFoo)
+}
+
+// The exploiter cannot update tests.MyFoo, but would output:
+// (struct{(1 int),(&(struct{(2 int),(nil *gno.land/r/demo/tests.Foo)} gno.land/r/demo/tests.Foo) *gno.land/r/demo/tests.Foo)} gno.land/r/demo/tests.Foo)
+// (struct{(1 int),(&(struct{(10 int),(nil *gno.land/r/demo/tests.Foo)} gno.land/r/demo/tests.Foo) *gno.land/r/demo/tests.Foo)} gno.land/r/demo/tests.Foo)
+
+// Error:
+// illegal conversion of readonly or externally stored value
diff --git a/gnovm/tests/files/extern/timtadh/data_structures/types/string.gno b/gnovm/tests/files/extern/timtadh/data_structures/types/string.gno
index 13f94950dfa..5ccf12fd8a1 100644
--- a/gnovm/tests/files/extern/timtadh/data_structures/types/string.gno
+++ b/gnovm/tests/files/extern/timtadh/data_structures/types/string.gno
@@ -70,8 +70,8 @@ func (self ByteSlice) Hash() int {
func hash(s []byte) int {
res := sha256.Sum256(s)
- return int(s[0]) |
- int(s[1]<<8) |
- int(s[2]<<16) |
- int(s[3]<<24)
+ return int(res[0]) |
+ int(res[1]<<8) |
+ int(res[2]<<16) |
+ int(res[3]<<24)
}
diff --git a/gnovm/tests/files/heap_alloc_defer.gno b/gnovm/tests/files/heap_alloc_defer.gno
index 788d9326695..db027d494b8 100644
--- a/gnovm/tests/files/heap_alloc_defer.gno
+++ b/gnovm/tests/files/heap_alloc_defer.gno
@@ -17,8 +17,6 @@ func main() {
},
}
- // tt is heap defined every iteration,
- // different with for loopvar spec.
for _, tt := range s {
f := func() {
println(tt.num)
@@ -34,4 +32,4 @@ func main() {
// 1
// 2
// hola
-// hola
+// hello
diff --git a/gnovm/tests/files/heap_alloc_forloop1.gno b/gnovm/tests/files/heap_alloc_forloop1.gno
index c746dd2e215..64e25ef8922 100644
--- a/gnovm/tests/files/heap_alloc_forloop1.gno
+++ b/gnovm/tests/files/heap_alloc_forloop1.gno
@@ -23,7 +23,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { s1 = (const (append func([]*int, ...*int) []*int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
+// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range (const (ref(main) package{})).s1 { (const (ref(fmt) package{})).Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { s1<~VPBlock(4,0)> = (const (append func([]*int, ...*int) []*int))(s1<~VPBlock(4,0)>, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
// Output:
// s1[0] is: 3
diff --git a/gnovm/tests/files/heap_alloc_forloop1a.gno b/gnovm/tests/files/heap_alloc_forloop1a.gno
index 82e7df17e1d..bcbd7d4ce6b 100644
--- a/gnovm/tests/files/heap_alloc_forloop1a.gno
+++ b/gnovm/tests/files/heap_alloc_forloop1a.gno
@@ -29,7 +29,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; import fmt fmt; type Int (const-type main.Int); var s1 []*(Int); func inc2(j *(Int)) { *(j) = *(j) + (const (2 main.Int)) }; func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 main.Int)); i<~VPBlock(1,0)> < (const (10 main.Int)); inc2(&(i<~VPBlock(1,0)>)) { s1 = (const (append func([]*main.Int, ...*main.Int) []*main.Int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
+// file{ package main; import fmt fmt; type Int (const-type main.Int); var s1 []*(Int); func inc2(j *(Int)) { *(j) = *(j) + (const (2 main.Int)) }; func forLoopRef() { defer func func(){ for i, e := range (const (ref(main) package{})).s1 { (const (ref(fmt) package{})).Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 main.Int)); i<~VPBlock(1,0)> < (const (10 main.Int)); inc2(&(i<~VPBlock(1,0)>)) { s1<~VPBlock(4,1)> = (const (append func([]*main.Int, ...*main.Int) []*main.Int))(s1<~VPBlock(4,1)>, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
// Output:
// s1[0] is: 10
diff --git a/gnovm/tests/files/heap_alloc_forloop1b.gno b/gnovm/tests/files/heap_alloc_forloop1b.gno
index de8217003a5..7817ee0d0b5 100644
--- a/gnovm/tests/files/heap_alloc_forloop1b.gno
+++ b/gnovm/tests/files/heap_alloc_forloop1b.gno
@@ -26,7 +26,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { r := i<~VPBlock(1,0)>; r, ok := (const (0 int)), (const (true bool)); (const (println func(...interface {})))(ok, r); s1 = (const (append func([]*int, ...*int) []*int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
+// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range (const (ref(main) package{})).s1 { (const (ref(fmt) package{})).Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { r := i<~VPBlock(1,0)>; r, ok := (const (0 int)), (const (true bool)); (const (println func(...interface {})))(ok, r); s1<~VPBlock(4,0)> = (const (append func([]*int, ...*int) []*int))(s1<~VPBlock(4,0)>, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } }
// Output:
// true 0
diff --git a/gnovm/tests/files/heap_alloc_forloop2.gno b/gnovm/tests/files/heap_alloc_forloop2.gno
index 43bd6259b36..b85ae39099f 100644
--- a/gnovm/tests/files/heap_alloc_forloop2.gno
+++ b/gnovm/tests/files/heap_alloc_forloop2.gno
@@ -25,7 +25,7 @@ func main() {
// You can tell by the preprocess printout of z and z<~...>.
// Preprocessed:
-// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i + (const (1 int)); s1 = (const (append func([]*int, ...*int) []*int))(s1, &(z<~VPBlock(1,1)>)) } }; func main() { forLoopRef() } }
+// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range (const (ref(main) package{})).s1 { (const (ref(fmt) package{})).Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i + (const (1 int)); s1<~VPBlock(4,0)> = (const (append func([]*int, ...*int) []*int))(s1<~VPBlock(4,0)>, &(z<~VPBlock(1,1)>)) } }; func main() { forLoopRef() } }
// Output:
// s1[0] is: 1
diff --git a/gnovm/tests/files/heap_alloc_forloop2a.gno b/gnovm/tests/files/heap_alloc_forloop2a.gno
index 7e9b59b4451..aa3494f3421 100644
--- a/gnovm/tests/files/heap_alloc_forloop2a.gno
+++ b/gnovm/tests/files/heap_alloc_forloop2a.gno
@@ -26,7 +26,7 @@ func main() {
// You can tell by the preprocess printout of z and z<~...>.
// Preprocessed:
-// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; s1 = (const (append func([]*int, ...*int) []*int))(s1, &(z<~VPBlock(1,1)>)); z<~VPBlock(1,1)>++ } }; func main() { forLoopRef() } }
+// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range (const (ref(main) package{})).s1 { (const (ref(fmt) package{})).Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; s1<~VPBlock(4,0)> = (const (append func([]*int, ...*int) []*int))(s1<~VPBlock(4,0)>, &(z<~VPBlock(1,1)>)); z<~VPBlock(1,1)>++ } }; func main() { forLoopRef() } }
// Output:
// s1[0] is: 1
diff --git a/gnovm/tests/files/heap_alloc_forloop3.gno b/gnovm/tests/files/heap_alloc_forloop3.gno
index 8fd2da59c37..939fa9785fb 100644
--- a/gnovm/tests/files/heap_alloc_forloop3.gno
+++ b/gnovm/tests/files/heap_alloc_forloop3.gno
@@ -25,7 +25,7 @@ func main() {
// You can tell by the preprocess printout of z and z<()~...>.
// Preprocessed:
-// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
+// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range (const (ref(main) package{})).fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; fs<~VPBlock(4,1)> = (const (append func([]main.f, ...main.f) []main.f))(fs<~VPBlock(4,1)>, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
// Output:
// 0
diff --git a/gnovm/tests/files/heap_alloc_forloop3a.gno b/gnovm/tests/files/heap_alloc_forloop3a.gno
index c8504cc58a5..5b5b2309e7b 100644
--- a/gnovm/tests/files/heap_alloc_forloop3a.gno
+++ b/gnovm/tests/files/heap_alloc_forloop3a.gno
@@ -27,7 +27,7 @@ func main() {
// You can tell by the preprocess printout of z and z<()~...>.
// Preprocessed:
-// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; (const (println func(...interface {})))(x); z := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
+// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range (const (ref(main) package{})).fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; (const (println func(...interface {})))(x); z := i; fs<~VPBlock(4,1)> = (const (append func([]main.f, ...main.f) []main.f))(fs<~VPBlock(4,1)>, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
// Output:
// 0
diff --git a/gnovm/tests/files/heap_alloc_forloop4.gno b/gnovm/tests/files/heap_alloc_forloop4.gno
index 7ddcea37d25..33aad1e2fac 100644
--- a/gnovm/tests/files/heap_alloc_forloop4.gno
+++ b/gnovm/tests/files/heap_alloc_forloop4.gno
@@ -23,7 +23,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(i<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
+// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range (const (ref(main) package{})).fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs<~VPBlock(4,1)> = (const (append func([]main.f, ...main.f) []main.f))(fs<~VPBlock(4,1)>, (const-type main.f)(func func(){ (const (println func(...interface {})))(i<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } }
// Output:
// 3
diff --git a/gnovm/tests/files/heap_alloc_forloop5.gno b/gnovm/tests/files/heap_alloc_forloop5.gno
index 1e53e6bbe1f..373b88ff8b2 100644
--- a/gnovm/tests/files/heap_alloc_forloop5.gno
+++ b/gnovm/tests/files/heap_alloc_forloop5.gno
@@ -24,7 +24,7 @@ func main() {
}
// Preprocessed:
-// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ z := i<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } }
+// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range (const (ref(main) package{})).fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs<~VPBlock(4,1)> = (const (append func([]main.f, ...main.f) []main.f))(fs<~VPBlock(4,1)>, (const-type main.f)(func func(){ z := i<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } }
// Output:
// 3
diff --git a/gnovm/tests/files/heap_alloc_forloop5a.gno b/gnovm/tests/files/heap_alloc_forloop5a.gno
index 9664ca18e85..110dd83541e 100644
--- a/gnovm/tests/files/heap_alloc_forloop5a.gno
+++ b/gnovm/tests/files/heap_alloc_forloop5a.gno
@@ -25,7 +25,7 @@ func main() {
}
// Preprocessed:
-// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ z := x<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } }
+// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range (const (ref(main) package{})).fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; fs<~VPBlock(4,1)> = (const (append func([]main.f, ...main.f) []main.f))(fs<~VPBlock(4,1)>, (const-type main.f)(func func(){ z := x<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } }
// Output:
// 0
diff --git a/gnovm/tests/files/heap_alloc_forloop6.gno b/gnovm/tests/files/heap_alloc_forloop6.gno
index 1177626ac73..bea81c89924 100644
--- a/gnovm/tests/files/heap_alloc_forloop6.gno
+++ b/gnovm/tests/files/heap_alloc_forloop6.gno
@@ -17,7 +17,7 @@ func main() {
}
// Preprocessed:
-// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; f := func func() (const-type int){ return z<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
+// file{ package main; func main() { var fns []func() .res.0 (const-type int); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; f := func func() .res.0 (const-type int){ return z<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
// Output:
// 0
diff --git a/gnovm/tests/files/heap_alloc_forloop6a.gno b/gnovm/tests/files/heap_alloc_forloop6a.gno
index c465a8cef03..ec24f562ed6 100644
--- a/gnovm/tests/files/heap_alloc_forloop6a.gno
+++ b/gnovm/tests/files/heap_alloc_forloop6a.gno
@@ -16,7 +16,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (5 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
+// file{ package main; func main() { var fns []func() .res.0 (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (5 int)); i<~VPBlock(1,0)>++ { f := func func() .res.0 (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
// Output:
// 5
diff --git a/gnovm/tests/files/heap_alloc_forloop6b.gno b/gnovm/tests/files/heap_alloc_forloop6b.gno
index 2e77a580ce8..6b884d661f1 100644
--- a/gnovm/tests/files/heap_alloc_forloop6b.gno
+++ b/gnovm/tests/files/heap_alloc_forloop6b.gno
@@ -18,7 +18,7 @@ func main() {
}
// Preprocessed:
-// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (0 int)); f := func func() (const-type int){ x<~VPBlock(1,1)> += y<~VPBlock(1,2)>; x<~VPBlock(1,1)> += (const (1 int)); return x<~VPBlock(1,1)> }, y<()~VPBlock(1,2)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
+// file{ package main; func main() { var fns []func() .res.0 (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (0 int)); f := func func() .res.0 (const-type int){ x<~VPBlock(1,1)> += y<~VPBlock(1,2)>; x<~VPBlock(1,1)> += (const (1 int)); return x<~VPBlock(1,1)> }, y<()~VPBlock(1,2)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } }
// Output:
// 1
diff --git a/gnovm/tests/files/heap_alloc_forloop6c.gno b/gnovm/tests/files/heap_alloc_forloop6c.gno
index 1d6a372b8da..752c7e6b4fe 100644
--- a/gnovm/tests/files/heap_alloc_forloop6c.gno
+++ b/gnovm/tests/files/heap_alloc_forloop6c.gno
@@ -16,7 +16,7 @@ func main() {
// go 1.22 loop var is not supported for now.
// Preprocessed:
-// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (2 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns