diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 5a710723b86..dd077c18a9e 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -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 + } + + 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 @@ -3197,11 +3283,11 @@ 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() { @@ -3209,15 +3295,24 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De 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) @@ -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 } diff --git a/gnovm/tests/files/circular_constant.gno b/gnovm/tests/files/circular_constant.gno new file mode 100644 index 00000000000..ff25da7428d --- /dev/null +++ b/gnovm/tests/files/circular_constant.gno @@ -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 diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno new file mode 100644 index 00000000000..8279e247d84 --- /dev/null +++ b/gnovm/tests/files/recursive1.gno @@ -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 diff --git a/gnovm/tests/files/recursive1a.gno b/gnovm/tests/files/recursive1a.gno new file mode 100644 index 00000000000..87681e1fcdd --- /dev/null +++ b/gnovm/tests/files/recursive1a.gno @@ -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 \ No newline at end of file diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno new file mode 100644 index 00000000000..2893baf8fca --- /dev/null +++ b/gnovm/tests/files/recursive1b.gno @@ -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 diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno new file mode 100644 index 00000000000..7797f375027 --- /dev/null +++ b/gnovm/tests/files/recursive1c.gno @@ -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 diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno new file mode 100644 index 00000000000..22bf172b5ac --- /dev/null +++ b/gnovm/tests/files/recursive1d.gno @@ -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 diff --git a/gnovm/tests/files/recursive1e.gno b/gnovm/tests/files/recursive1e.gno new file mode 100644 index 00000000000..6d1636ba9f3 --- /dev/null +++ b/gnovm/tests/files/recursive1e.gno @@ -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) diff --git a/gnovm/tests/files/recursive1f.gno b/gnovm/tests/files/recursive1f.gno new file mode 100644 index 00000000000..81fe2a5699c --- /dev/null +++ b/gnovm/tests/files/recursive1f.gno @@ -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 diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno new file mode 100644 index 00000000000..4ed86f03d58 --- /dev/null +++ b/gnovm/tests/files/recursive2.gno @@ -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 diff --git a/gnovm/tests/files/recursive2a.gno b/gnovm/tests/files/recursive2a.gno new file mode 100644 index 00000000000..9c7dd3e179f --- /dev/null +++ b/gnovm/tests/files/recursive2a.gno @@ -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 diff --git a/gnovm/tests/files/recursive2b.gno b/gnovm/tests/files/recursive2b.gno new file mode 100644 index 00000000000..92d633cdda1 --- /dev/null +++ b/gnovm/tests/files/recursive2b.gno @@ -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 diff --git a/gnovm/tests/files/recursive2c.gno b/gnovm/tests/files/recursive2c.gno new file mode 100644 index 00000000000..3b5c27ed8ea --- /dev/null +++ b/gnovm/tests/files/recursive2c.gno @@ -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] diff --git a/gnovm/tests/files/recursive2d.gno b/gnovm/tests/files/recursive2d.gno new file mode 100644 index 00000000000..b2439ba6259 --- /dev/null +++ b/gnovm/tests/files/recursive2d.gno @@ -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 diff --git a/gnovm/tests/files/recursive3.gno b/gnovm/tests/files/recursive3.gno new file mode 100644 index 00000000000..552c086c91b --- /dev/null +++ b/gnovm/tests/files/recursive3.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T *S +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive4.gno b/gnovm/tests/files/recursive4.gno new file mode 100644 index 00000000000..29392cb35ab --- /dev/null +++ b/gnovm/tests/files/recursive4.gno @@ -0,0 +1,15 @@ +package main + +import "time" + +type Duration struct { + t time.Duration +} + +func main() { + var a, b Duration + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive4a.gno b/gnovm/tests/files/recursive4a.gno new file mode 100644 index 00000000000..8b4d13b4785 --- /dev/null +++ b/gnovm/tests/files/recursive4a.gno @@ -0,0 +1,9 @@ +package main + +type time time.Duration + +func main() { +} + +// Error: +// main/files/recursive4a.gno:1:1: invalid recursive type: time -> time diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno new file mode 100644 index 00000000000..1c2fbd89fb8 --- /dev/null +++ b/gnovm/tests/files/recursive5.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// main/files/recursive5.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive6.gno b/gnovm/tests/files/recursive6.gno new file mode 100644 index 00000000000..73858b2ea1b --- /dev/null +++ b/gnovm/tests/files/recursive6.gno @@ -0,0 +1,23 @@ +package main + +type SelfReferencing interface { + Self() SelfReferencing +} + +type Implementation struct { + // Some implementation details... +} + +func (impl Implementation) Self() SelfReferencing { + return &impl +} + +func main() { + var obj Implementation + var intf SelfReferencing = obj + _ = intf.Self() + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/recursive6a.gno b/gnovm/tests/files/recursive6a.gno new file mode 100644 index 00000000000..8123fc626a5 --- /dev/null +++ b/gnovm/tests/files/recursive6a.gno @@ -0,0 +1,12 @@ +package main + +type SelfReferencing interface { + SelfReferencing +} + +func main() { + println("ok") +} + +// Error: +// main/files/recursive6a.gno:1:1: invalid recursive type: SelfReferencing -> SelfReferencing diff --git a/gnovm/tests/files/recursive7.gno b/gnovm/tests/files/recursive7.gno new file mode 100644 index 00000000000..9bd8a56995d --- /dev/null +++ b/gnovm/tests/files/recursive7.gno @@ -0,0 +1,10 @@ +package main + +type S []S + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/recursive7a.gno b/gnovm/tests/files/recursive7a.gno new file mode 100644 index 00000000000..b3c57516f13 --- /dev/null +++ b/gnovm/tests/files/recursive7a.gno @@ -0,0 +1,8 @@ +package main + +type S [2]S + +func main() {} + +// Error: +// main/files/recursive7a.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive8.gno b/gnovm/tests/files/recursive8.gno new file mode 100644 index 00000000000..1f9325ae35c --- /dev/null +++ b/gnovm/tests/files/recursive8.gno @@ -0,0 +1,8 @@ +package main + +type Int Int + +func main() {} + +// Error: +// main/files/recursive8.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9.gno b/gnovm/tests/files/recursive9.gno new file mode 100644 index 00000000000..8181be55d33 --- /dev/null +++ b/gnovm/tests/files/recursive9.gno @@ -0,0 +1,8 @@ +package main + +type Int = Int + +func main() {} + +// Error: +// main/files/recursive9.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9a.gno b/gnovm/tests/files/recursive9a.gno new file mode 100644 index 00000000000..b96efa090e4 --- /dev/null +++ b/gnovm/tests/files/recursive9a.gno @@ -0,0 +1,8 @@ +package main + +type Int = *Int + +func main() {} + +// Error: +// main/files/recursive9a.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9b.gno b/gnovm/tests/files/recursive9b.gno new file mode 100644 index 00000000000..e033349d597 --- /dev/null +++ b/gnovm/tests/files/recursive9b.gno @@ -0,0 +1,8 @@ +package main + +type Int = func() Int + +func main() {} + +// Error: +// main/files/recursive9b.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9c.gno b/gnovm/tests/files/recursive9c.gno new file mode 100644 index 00000000000..ad865978920 --- /dev/null +++ b/gnovm/tests/files/recursive9c.gno @@ -0,0 +1,8 @@ +package main + +type Int = []Int + +func main() {} + +// Error: +// main/files/recursive9c.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9d.gno b/gnovm/tests/files/recursive9d.gno new file mode 100644 index 00000000000..ae7310ede0f --- /dev/null +++ b/gnovm/tests/files/recursive9d.gno @@ -0,0 +1,10 @@ +package main + +type S = struct { + *S +} + +func main() {} + +// Error: +// main/files/recursive9d.gno:1:1: invalid recursive type: S -> S