Skip to content
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

fix(gnovm): prevent cyclic references in type declarations #2081

Merged
merged 38 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
70cef5f
initial fix
ltzmaxwell May 7, 2024
a758b1b
revert
ltzmaxwell May 7, 2024
2776c88
fixup
ltzmaxwell May 7, 2024
faa3920
fixup
ltzmaxwell May 8, 2024
05ebcf2
check on predefineFileset, proper err messages
ltzmaxwell May 8, 2024
bff974b
optimize cycle check logic
ltzmaxwell May 9, 2024
e5d37f8
add txtar for recursive reference
ltzmaxwell May 10, 2024
d7c8839
clean
ltzmaxwell May 13, 2024
47362d9
clean
ltzmaxwell May 13, 2024
40fb20f
deprecate dependence graph, using the current logic instead
ltzmaxwell May 15, 2024
992d0d6
revert
ltzmaxwell May 15, 2024
24791a5
comments
ltzmaxwell May 15, 2024
0442b2a
fixup
ltzmaxwell May 16, 2024
2ef06ab
delete txtar test
ltzmaxwell Jun 28, 2024
134651f
Merge remote-tracking branch 'upstream/master' into fix/maxwell/2008
ltzmaxwell Jun 28, 2024
a5129a8
fixup
ltzmaxwell Jun 28, 2024
0469ef5
re-introduce dependency graph
ltzmaxwell Jun 30, 2024
ce21b4d
fixup
ltzmaxwell Jun 30, 2024
954fb82
fixup
ltzmaxwell Jun 30, 2024
da4fec1
test
ltzmaxwell Jun 30, 2024
c8fda76
fix test, resolve conflicts with debugger on line number
ltzmaxwell Jun 30, 2024
ef120b8
save
ltzmaxwell Jul 9, 2024
20148ea
save
ltzmaxwell Jul 25, 2024
60ab538
save
ltzmaxwell Jul 26, 2024
fd8bbc6
new way fix
ltzmaxwell Jul 29, 2024
ba68c13
clean
ltzmaxwell Jul 29, 2024
0116a43
clean
ltzmaxwell Jul 29, 2024
51deee2
clean
ltzmaxwell Jul 29, 2024
caf382c
fixup
ltzmaxwell Aug 5, 2024
a9282ba
simplify
ltzmaxwell Aug 5, 2024
f044f97
fixup
ltzmaxwell Aug 5, 2024
43c451c
fixup
ltzmaxwell Aug 6, 2024
7af4793
Merge remote-tracking branch 'upstream/master' into fix/maxwell/2008
ltzmaxwell Aug 6, 2024
ca4a14b
sync location
ltzmaxwell Aug 6, 2024
64fb685
check type alias
ltzmaxwell Aug 6, 2024
559564d
Update gnovm/pkg/gnolang/preprocess.go
ltzmaxwell Aug 8, 2024
9e76e22
clean
ltzmaxwell Aug 15, 2024
284805f
Merge remote-tracking branch 'origin/fix/maxwell/2008' into fix/maxwe…
ltzmaxwell Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 102 additions & 7 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -2906,6 +2906,92 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) {
}
}

func assertTypeDeclNoCycle(store Store, last BlockNode, td *TypeDecl, stack *[]Name) {
assertTypeDeclNoCycle2(store, last, td.Type, stack, false, td.IsAlias)
}

func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool, isAlias bool) {
if x == nil {
panic("unexpected nil expression when checking for type declaration cycles")
}

var lastX Expr
defer func() {
if _, ok := lastX.(*NameExpr); ok {
// pop stack
*stack = (*stack)[:len(*stack)-1]
}
}()

switch cx := x.(type) {
case *NameExpr:
var msg string

// Function to build the error message
buildMessage := func() string {
for j := 0; j < len(*stack); j++ {
msg += fmt.Sprintf("%s -> ", (*stack)[j])
}
return msg + string(cx.Name) // Append the current name last
}

// Check for existence of cx.Name in stack
findCycle := func() {
for _, n := range *stack {
if n == cx.Name {
msg = buildMessage()
panic(fmt.Sprintf("invalid recursive type: %s", msg))
}
}
}

if indirect && !isAlias {
*stack = (*stack)[:0]
} else {
findCycle()
*stack = append(*stack, cx.Name)
lastX = cx // Assuming lastX is declared somewhere in this context
}

return
case *SelectorExpr:
assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect, isAlias)
case *StarExpr:
assertTypeDeclNoCycle2(store, last, cx.X, stack, true, isAlias)
case *FieldTypeExpr:
assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect, isAlias)
case *ArrayTypeExpr:
if cx.Len != nil {
assertTypeDeclNoCycle2(store, last, cx.Len, stack, indirect, isAlias)
}
assertTypeDeclNoCycle2(store, last, cx.Elt, stack, indirect, isAlias)
case *SliceTypeExpr:
assertTypeDeclNoCycle2(store, last, cx.Elt, stack, true, isAlias)
case *InterfaceTypeExpr:
for i := range cx.Methods {
assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, indirect, isAlias)
}
case *ChanTypeExpr:
assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias)
case *FuncTypeExpr:
for i := range cx.Params {
assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true, isAlias)
}
for i := range cx.Results {
assertTypeDeclNoCycle2(store, last, &cx.Results[i], stack, true, isAlias)
}
case *MapTypeExpr:
assertTypeDeclNoCycle2(store, last, cx.Key, stack, true, isAlias)
assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias)
case *StructTypeExpr:
for i := range cx.Fields {
assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect, isAlias)
}
default:
}
return
}

// Returns any names not yet defined nor predefined in expr. These happen
// upon transcribe:enter from the top, so value paths cannot be used. If no
// names are un and x is TypeExpr, evalStaticType(store,last, x) must not
Expand Down Expand Up @@ -3197,27 +3283,36 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) {
}
}
}()
m := make(map[Name]struct{})
return predefineNow2(store, last, d, m)
stack := &[]Name{}
return predefineNow2(store, last, d, stack)
}

func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (Decl, bool) {
func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bool) {
pkg := packageOf(last)
// pre-register d.GetName() to detect circular definition.
for _, dn := range d.GetDeclNames() {
if isUverseName(dn) {
panic(fmt.Sprintf(
"builtin identifiers cannot be shadowed: %s", dn))
}
m[dn] = struct{}{}
*stack = append(*stack, dn)
}

// check type decl cycle
if td, ok := d.(*TypeDecl); ok {
// recursively check
assertTypeDeclNoCycle(store, last, td, stack)
}

// recursively predefine dependencies.
for {
un := tryPredefine(store, last, d)
if un != "" {
// check circularity.
if _, ok := m[un]; ok {
panic(fmt.Sprintf("constant definition loop with %s", un))
for _, n := range *stack {
if n == un {
panic(fmt.Sprintf("constant definition loop with %s", un))
}
}
// look up dependency declaration from fileset.
file, decl := pkg.FileSet.GetDeclFor(un)
Expand All @@ -3226,7 +3321,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De
panic("all types from files in file-set should have already been predefined")
}
// predefine dependency (recursive).
*decl, _ = predefineNow2(store, file, *decl, m)
*decl, _ = predefineNow2(store, file, *decl, stack)
} else {
break
}
Expand Down
10 changes: 10 additions & 0 deletions gnovm/tests/files/circular_constant.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

const A = B
const B = A + 1

func main() {
}

// Error:
// main/files/circular_constant.gno:3:7: constant definition loop with A
13 changes: 13 additions & 0 deletions gnovm/tests/files/recursive1.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

type S struct {
T S
}

func main() {
var a, b S
println(a == b)
}

// Error:
// main/files/recursive1.gno:1:1: invalid recursive type: S -> S
15 changes: 15 additions & 0 deletions gnovm/tests/files/recursive1a.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

type S1 *S

type S struct {
T S1
}

func main() {
var a, b S
println(a == b)
}

// Output:
// true
16 changes: 16 additions & 0 deletions gnovm/tests/files/recursive1b.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

type S struct {
T *S
B Integer
}

type Integer int

func main() {
var a, b S
println(a == b)
}

// Output:
// true
17 changes: 17 additions & 0 deletions gnovm/tests/files/recursive1c.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "fmt"

type S struct {
A [2][2]S
}

func main() {
var a, b S

fmt.Println(a)
fmt.Println(b)
}

// Error:
// main/files/recursive1c.gno:1:1: invalid recursive type: S -> S
17 changes: 17 additions & 0 deletions gnovm/tests/files/recursive1d.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "fmt"

type S struct {
A [2]S
}

func main() {
var a, b S

fmt.Println(a)
fmt.Println(b)
}

// Error:
// main/files/recursive1d.gno:1:1: invalid recursive type: S -> S
13 changes: 13 additions & 0 deletions gnovm/tests/files/recursive1e.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

type S struct {
A [2][]S
}

func main() {
var a, b S
println(a)
}

// Output:
// (struct{(array[(nil []main.S),(nil []main.S)] [2][]main.S)} main.S)
13 changes: 13 additions & 0 deletions gnovm/tests/files/recursive1f.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

func main() {
type S struct {
T S
}

var a, b S
println(a == b)
}

// Error:
// main/files/recursive1f.gno:3:1: invalid recursive type: S -> S
21 changes: 21 additions & 0 deletions gnovm/tests/files/recursive2.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

type A struct {
X B
}

type B struct {
X C
}

type C struct {
X A
}

func main() {
var p, q A
println(p == q)
}

// Error:
// main/files/recursive2.gno:1:1: invalid recursive type: A -> B -> C -> A
21 changes: 21 additions & 0 deletions gnovm/tests/files/recursive2a.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

type A struct {
X B
}

type B struct {
X int
}

type C struct {
X A
}

func main() {
var p, q A
println(p == q)
}

// Output:
// true
21 changes: 21 additions & 0 deletions gnovm/tests/files/recursive2b.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

type A struct {
X B
}

type B struct {
X C
}

type C struct {
X *A
}

func main() {
var p, q A
println(p == q)
}

// Output:
// true
21 changes: 21 additions & 0 deletions gnovm/tests/files/recursive2c.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

func main() {
type A struct {
X B
}

type B struct {
X C
}

type C struct {
X A
}

var p, q A
println(p == q)
}

// Error:
// main/files/recursive2c.gno:3:1: name B not defined in fileset with files [files/recursive2c.gno]
21 changes: 21 additions & 0 deletions gnovm/tests/files/recursive2d.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

type A struct {
X *B
}

type B struct {
X int
}

type C struct {
X A
}

func main() {
var p, q A
println(p == q)
}

// Output:
// true
13 changes: 13 additions & 0 deletions gnovm/tests/files/recursive3.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

type S struct {
T *S
}

func main() {
var a, b S
println(a == b)
}

// Output:
// true
Loading
Loading