Skip to content

gnovm: values defined in structure after an anonymous structure field panics #4225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
paulogarithm opened this issue Apr 28, 2025 · 2 comments
Labels
🐞 bug Something isn't working

Comments

@paulogarithm
Copy link

Invalid struct parsing after anonymous struct field

Description

When you create a structure, then you define an anonymous field and try to create a variable out of it, the parsing fails and it says "the variable after your anonymous field does not exist", like for example:

type Foo struct {
	Anon struct { Value int }
	Hello int
}

will say:

struct type struct{Anon struct{Value int}; Hello int} has no field Hello [recovered]

(the full example bellow)

Your environment

go version go1.24.1 linux/amd64
gno version: master.2244+84e53f51

Steps to reproduce

Here is a code snippet

package main

type Foo struct {
	Anon struct { Value int }
	Hello int
}

func main() {
	x := Foo{
		Anon: struct{Value int}{ Value: 42 },
		Hello: 80,
	}
	_=x
}

Expected behaviour

using golang:

$ go run a.go
$ echo $?
0
$ 

Actual behaviour

using gno

$ gno run a.go
panic: struct type struct{Anon struct{Value int}; Hello int} has no field Hello [recovered]
# ... more logs bellow
$ echo $?
2

Logs

here are the full logs after gno run a.go command:

panic: struct type struct{Anon struct{Value int}; Hello int} has no field Hello [recovered]
        panic: main/a.go:11:3: struct type struct{Anon struct{Value int}; Hello int} has no field Hello:
        --- preprocess stack ---
        stack 2: func main() { x<!VPBlock(1,0)> := Foo<VPBlock(3,0)>{Anon<VPField(0,0,Anon)>: struct { Value (const-type int) }{Value<VPField(0,0,Value)>: (const (42 int))}, Hello<VPUverse(0)>: 80}; _<VPUverse(0)> = x<VPUverse(0)> }
        stack 1: file{ package main; type Foo (const-type main.Foo); func main() { x<!VPBlock(1,0)> := Foo<VPBlock(3,0)>{Anon<VPField(0,0,Anon)>: struct { Value (const-type int) }{Value<VPField(0,0,Value)>: (const (42 int))}, Hello<VPUverse(0)>: 80}; _<VPUverse(0)> = x<VPUverse(0)> } }
        stack 0: package(main)
        ------------------------

goroutine 1 [running]:
github.com/gnolang/gno/gnovm/pkg/gnolang.doRecover({0xc00001b200, 0x3, 0x20}, {0x124fdc0, 0xc0004367e0})
        /home/pol/gno/gnovm/pkg/gnolang/preprocess.go:401 +0x37e
panic({0xdf5780?, 0xc000498a40?})
        /usr/local/go/src/runtime/panic.go:792 +0x132
github.com/gnolang/gno/gnovm/pkg/gnolang.(*StructType).GetPathForName(0xc0003ef980, {0xc000198620, 0x5})
        /home/pol/gno/gnovm/pkg/gnolang/types.go:801 +0x1d9
github.com/gnolang/gno/gnovm/pkg/gnolang.preprocess1.func1({0xc00001b400, 0x4, 0x20}, 0x12, 0x1, {0x124fdc0, 0xc0004367e0}, 0x3)
        /home/pol/gno/gnovm/pkg/gnolang/preprocess.go:986 +0xe37
github.com/gnolang/gno/gnovm/pkg/gnolang.transcribe(0xc000125728, {0xc00001b400, 0x4, 0x20}, 0x12, 0x1, {0x124fdc0, 0xc0004367e0}, 0xc000124bbf)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:696 +0x1c48
github.com/gnolang/gno/gnovm/pkg/gnolang.transcribe(0xc000125728, {0xc00001b400, 0x3, 0x20}, 0x25, 0x0, {0x124fa40, 0xc000494820}, 0xc000124e5f)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:244 +0x63b0
github.com/gnolang/gno/gnovm/pkg/gnolang.transcribe(0xc000125728, {0xc00001b400, 0x2, 0x20}, 0x46, 0x0, {0x124f7c0, 0xc000436840}, 0xc0001250ff)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:359 +0x5290
github.com/gnolang/gno/gnovm/pkg/gnolang.transcribe(0xc000125728, {0xc00001b400, 0x1, 0x20}, 0x4e, 0x1, {0x1250fc0, 0xc0003f1208}, 0xc00012539f)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:640 +0x77de
github.com/gnolang/gno/gnovm/pkg/gnolang.transcribe(0xc000125728, {0xc00001b400, 0x0, 0x20}, 0x0, 0x0, {0x1251140, 0xc0004a0708}, 0xc00012563f)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:678 +0x9f88
github.com/gnolang/gno/gnovm/pkg/gnolang.Transcribe({0x1251140, 0xc0004a0708}, 0xc000125728)
        /home/pol/gno/gnovm/pkg/gnolang/transcribe.go:132 +0xb6
github.com/gnolang/gno/gnovm/pkg/gnolang.preprocess1({0x125a540, 0xc00045e000}, {0x125d9f0, 0xc0004a0a88}, {0x1251140, 0xc0004a0708?})
        /home/pol/gno/gnovm/pkg/gnolang/preprocess.go:503 +0x2f6
github.com/gnolang/gno/gnovm/pkg/gnolang.Preprocess({0x125a540, 0xc00045e000}, {0x125d9f0, 0xc0004a0a88}, {0x1251140, 0xc0004a0708})
        /home/pol/gno/gnovm/pkg/gnolang/preprocess.go:451 +0xac
github.com/gnolang/gno/gnovm/pkg/gnolang.(*Machine).runFileDecls(0xc000422008, 0x1, {0xc00008c098, 0x1, 0x1})
        /home/pol/gno/gnovm/pkg/gnolang/machine.go:539 +0x38b
github.com/gnolang/gno/gnovm/pkg/gnolang.(*Machine).RunFiles(0xc000422008, {0xc00008c098, 0x1, 0x1})
        /home/pol/gno/gnovm/pkg/gnolang/machine.go:427 +0x173
main.execRun(0xc0000d3740, {0xc0004984f0, 0x1, 0x1}, {0x1254170, 0xc0004943c0})
        /home/pol/gno/gnovm/cmd/gno/run.go:135 +0x48a
main.newRunCmd.func1({0xe8b180?, 0xc00018c4d0?}, {0xc0004984f0?, 0xc0004362a0?, 0x0?})
        /home/pol/gno/gnovm/cmd/gno/run.go:39 +0x30
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0xb?, {0x1245520?, 0x19ae7c0?})
        /home/pol/gno/tm2/pkg/commands/command.go:265 +0x192
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0xc0001d0000?, {0x1245520?, 0x19ae7c0?})
        /home/pol/gno/tm2/pkg/commands/command.go:269 +0x13e
github.com/gnolang/gno/tm2/pkg/commands.(*Command).ParseAndRun(0xc0001d0000, {0x1245520, 0x19ae7c0}, {0xc00018c4c0?, 0xc0001d06c0?, 0xc0001d0780?})
        /home/pol/gno/tm2/pkg/commands/command.go:150 +0x49
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Execute(0x1254170?, {0x1245520?, 0x19ae7c0?}, {0xc00018c4c0?, 0x18f4c68?, 0xc000002380?})
        /home/pol/gno/tm2/pkg/commands/command.go:127 +0x2c
main.main()
        /home/pol/gno/gnovm/cmd/gno/main.go:13 +0x5f

Proposed solution

ig its just a parsing typo somewhere in the code

this is what i found tho, it does skip somewhere in the GetPathForName, see bellow

// gnovm/pkg/gnolang/preprocess.go:986

					case *StructType:
						n.Path = bt.GetPathForName(n.Name) // here, bt.Fields have all the fields !
						return n, TRANS_CONTINUE

i checked doing that:

					case *StructType:
						println("fields in structure:")
						for _, v := range bt.Fields {
							println("-", v.String())
						}
						println("field to check for:")
						println(n.Name, "\n")
						n.Path = bt.GetPathForName(n.Name)

// Outputs:
// fields in structure:
// - Anon struct{Value int}
// - Hello int
// field to check for:
// Hello

so it is included well in the parsing, but the error is just bellow here:

// gnovm/pkg/gnolang/types.go:785

func (st *StructType) GetPathForName(n Name) ValuePath {
	for i := 0; i < len(st.Fields); i++ { // it itters through the fields
		ft := st.Fields[i]
		if ft.Name == n {
			if i > 2<<16-1 {
				panic("too many fields")
			}
			return NewValuePathField(0, uint16(i), n)
		}
		if st, ok := ft.Type.(*StructType); ok { // if its anon struct it just skips... the number of fields IN the anon struct... but skips them in the parent struct ? why ?
			if ft.Name != "" {
				// skip fields not promoted. (this comment is not from me, its from the code)
				i += len(st.Fields)
			}
		}
	}
	panic(fmt.Sprintf("struct type %s has no field %s",
		st.String(), n))
}

so for me the fix would be to get rid of the condition like that:

func (st *StructType) GetPathForName(n Name) ValuePath {
	for i := 0; i < len(st.Fields); i++ {
		ft := st.Fields[i]
		if ft.Name == n {
			if i > 2<<16-1 {
				panic("too many fields")
			}
			return NewValuePathField(0, uint16(i), n)
		}
-		if st, ok := ft.Type.(*StructType); ok {
-			if ft.Name != "" {
-				// skip fields not promoted.
-				i += len(st.Fields)
-			}
-		}
	}
	panic(fmt.Sprintf("struct type %s has no field %s",
		st.String(), n))
}

but this may be really usefull for something else, but i dont see why tho... ?

Note

removing the condition make the code works, but idk if it break everything

TL;DR

there is a condition when checking fields that just skips the fields in anon structure in gnovm/pkg/gnolang/types.go:794 and it seems useless, but im too scared to remove it because maybe it will break everything

@paulogarithm
Copy link
Author

@ltzmaxwell
Copy link
Contributor

ltzmaxwell commented Apr 29, 2025

Thank you for spotting this.

It seems buggy — you can probably just remove it. Then run make test in gnovm/Makefile, (maybe also examples/Makefile, gno.land/Makefile) to verify that everything still works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 bug Something isn't working
Projects
Status: Triage
Development

No branches or pull requests

2 participants