@@ -10,6 +10,7 @@ import (
10
10
"go/ast"
11
11
"go/token"
12
12
"io/fs"
13
+ "runtime"
13
14
"strings"
14
15
"testing"
15
16
)
@@ -577,3 +578,171 @@ type x int // comment
577
578
t .Errorf ("got %q, want %q" , comment , "// comment" )
578
579
}
579
580
}
581
+
582
+ var parseDepthTests = []struct {
583
+ name string
584
+ format string
585
+ // multipler is used when a single statement may result in more than one
586
+ // change in the depth level, for instance "1+(..." produces a BinaryExpr
587
+ // followed by a UnaryExpr, which increments the depth twice. The test
588
+ // case comment explains which nodes are triggering the multiple depth
589
+ // changes.
590
+ parseMultiplier int
591
+ // scope is true if we should also test the statement for the resolver scope
592
+ // depth limit.
593
+ scope bool
594
+ // scopeMultiplier does the same as parseMultiplier, but for the scope
595
+ // depths.
596
+ scopeMultiplier int
597
+ }{
598
+ // The format expands the part inside « » many times.
599
+ // A second set of brackets nested inside the first stops the repetition,
600
+ // so that for example «(«1»)» expands to (((...((((1))))...))).
601
+ {name : "array" , format : "package main; var x «[1]»int" },
602
+ {name : "slice" , format : "package main; var x «[]»int" },
603
+ {name : "struct" , format : "package main; var x «struct { X «int» }»" , scope : true },
604
+ {name : "pointer" , format : "package main; var x «*»int" },
605
+ {name : "func" , format : "package main; var x «func()»int" , scope : true },
606
+ {name : "chan" , format : "package main; var x «chan »int" },
607
+ {name : "chan2" , format : "package main; var x «<-chan »int" },
608
+ {name : "interface" , format : "package main; var x «interface { M() «int» }»" , scope : true , scopeMultiplier : 2 }, // Scopes: InterfaceType, FuncType
609
+ {name : "map" , format : "package main; var x «map[int]»int" },
610
+ {name : "slicelit" , format : "package main; var x = «[]any{«»}»" , parseMultiplier : 2 }, // Parser nodes: UnaryExpr, CompositeLit
611
+ {name : "arraylit" , format : "package main; var x = «[1]any{«nil»}»" , parseMultiplier : 2 }, // Parser nodes: UnaryExpr, CompositeLit
612
+ {name : "structlit" , format : "package main; var x = «struct{x any}{«nil»}»" , parseMultiplier : 2 }, // Parser nodes: UnaryExpr, CompositeLit
613
+ {name : "maplit" , format : "package main; var x = «map[int]any{1:«nil»}»" , parseMultiplier : 2 }, // Parser nodes: CompositeLit, KeyValueExpr
614
+ {name : "dot" , format : "package main; var x = «x.»x" },
615
+ {name : "index" , format : "package main; var x = x«[1]»" },
616
+ {name : "slice" , format : "package main; var x = x«[1:2]»" },
617
+ {name : "slice3" , format : "package main; var x = x«[1:2:3]»" },
618
+ {name : "dottype" , format : "package main; var x = x«.(any)»" },
619
+ {name : "callseq" , format : "package main; var x = x«()»" },
620
+ {name : "methseq" , format : "package main; var x = x«.m()»" , parseMultiplier : 2 }, // Parser nodes: SelectorExpr, CallExpr
621
+ {name : "binary" , format : "package main; var x = «1+»1" },
622
+ {name : "binaryparen" , format : "package main; var x = «1+(«1»)»" , parseMultiplier : 2 }, // Parser nodes: BinaryExpr, ParenExpr
623
+ {name : "unary" , format : "package main; var x = «^»1" },
624
+ {name : "addr" , format : "package main; var x = «& »x" },
625
+ {name : "star" , format : "package main; var x = «*»x" },
626
+ {name : "recv" , format : "package main; var x = «<-»x" },
627
+ {name : "call" , format : "package main; var x = «f(«1»)»" , parseMultiplier : 2 }, // Parser nodes: Ident, CallExpr
628
+ {name : "conv" , format : "package main; var x = «(*T)(«1»)»" , parseMultiplier : 2 }, // Parser nodes: ParenExpr, CallExpr
629
+ {name : "label" , format : "package main; func main() { «Label:» }" },
630
+ {name : "if" , format : "package main; func main() { «if true { «» }»}" , parseMultiplier : 2 , scope : true , scopeMultiplier : 2 }, // Parser nodes: IfStmt, BlockStmt. Scopes: IfStmt, BlockStmt
631
+ {name : "ifelse" , format : "package main; func main() { «if true {} else » {} }" , scope : true },
632
+ {name : "switch" , format : "package main; func main() { «switch { default: «» }»}" , scope : true , scopeMultiplier : 2 }, // Scopes: TypeSwitchStmt, CaseClause
633
+ {name : "typeswitch" , format : "package main; func main() { «switch x.(type) { default: «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: TypeSwitchStmt, CaseClause
634
+ {name : "for0" , format : "package main; func main() { «for { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: ForStmt, BlockStmt
635
+ {name : "for1" , format : "package main; func main() { «for x { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: ForStmt, BlockStmt
636
+ {name : "for3" , format : "package main; func main() { «for f(); g(); h() { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: ForStmt, BlockStmt
637
+ {name : "forrange0" , format : "package main; func main() { «for range x { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: RangeStmt, BlockStmt
638
+ {name : "forrange1" , format : "package main; func main() { «for x = range z { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: RangeStmt, BlockStmt
639
+ {name : "forrange2" , format : "package main; func main() { «for x, y = range z { «» }» }" , scope : true , scopeMultiplier : 2 }, // Scopes: RangeStmt, BlockStmt
640
+ {name : "go" , format : "package main; func main() { «go func() { «» }()» }" , parseMultiplier : 2 , scope : true }, // Parser nodes: GoStmt, FuncLit
641
+ {name : "defer" , format : "package main; func main() { «defer func() { «» }()» }" , parseMultiplier : 2 , scope : true }, // Parser nodes: DeferStmt, FuncLit
642
+ {name : "select" , format : "package main; func main() { «select { default: «» }» }" , scope : true },
643
+ }
644
+
645
+ // split splits pre«mid»post into pre, mid, post.
646
+ // If the string does not have that form, split returns x, "", "".
647
+ func split (x string ) (pre , mid , post string ) {
648
+ start , end := strings .Index (x , "«" ), strings .LastIndex (x , "»" )
649
+ if start < 0 || end < 0 {
650
+ return x , "" , ""
651
+ }
652
+ return x [:start ], x [start + len ("«" ) : end ], x [end + len ("»" ):]
653
+ }
654
+
655
+ func TestParseDepthLimit (t * testing.T ) {
656
+ if runtime .GOARCH == "wasm" {
657
+ t .Skip ("causes call stack exhaustion on js/wasm" )
658
+ }
659
+ for _ , tt := range parseDepthTests {
660
+ for _ , size := range []string {"small" , "big" } {
661
+ t .Run (tt .name + "/" + size , func (t * testing.T ) {
662
+ n := maxNestLev + 1
663
+ if tt .parseMultiplier > 0 {
664
+ n /= tt .parseMultiplier
665
+ }
666
+ if size == "small" {
667
+ // Decrease the number of statements by 10, in order to check
668
+ // that we do not fail when under the limit. 10 is used to
669
+ // provide some wiggle room for cases where the surrounding
670
+ // scaffolding syntax adds some noise to the depth that changes
671
+ // on a per testcase basis.
672
+ n -= 10
673
+ }
674
+
675
+ pre , mid , post := split (tt .format )
676
+ if strings .Contains (mid , "«" ) {
677
+ left , base , right := split (mid )
678
+ mid = strings .Repeat (left , n ) + base + strings .Repeat (right , n )
679
+ } else {
680
+ mid = strings .Repeat (mid , n )
681
+ }
682
+ input := pre + mid + post
683
+
684
+ fset := token .NewFileSet ()
685
+ _ , err := ParseFile (fset , "" , input , ParseComments | SkipObjectResolution )
686
+ if size == "small" {
687
+ if err != nil {
688
+ t .Errorf ("ParseFile(...): %v (want success)" , err )
689
+ }
690
+ } else {
691
+ expected := "exceeded max nesting depth"
692
+ if err == nil || ! strings .HasSuffix (err .Error (), expected ) {
693
+ t .Errorf ("ParseFile(...) = _, %v, want %q" , err , expected )
694
+ }
695
+ }
696
+ })
697
+ }
698
+ }
699
+ }
700
+
701
+ func TestScopeDepthLimit (t * testing.T ) {
702
+ if runtime .GOARCH == "wasm" {
703
+ t .Skip ("causes call stack exhaustion on js/wasm" )
704
+ }
705
+ for _ , tt := range parseDepthTests {
706
+ if ! tt .scope {
707
+ continue
708
+ }
709
+ for _ , size := range []string {"small" , "big" } {
710
+ t .Run (tt .name + "/" + size , func (t * testing.T ) {
711
+ n := maxScopeDepth + 1
712
+ if tt .scopeMultiplier > 0 {
713
+ n /= tt .scopeMultiplier
714
+ }
715
+ if size == "small" {
716
+ // Decrease the number of statements by 10, in order to check
717
+ // that we do not fail when under the limit. 10 is used to
718
+ // provide some wiggle room for cases where the surrounding
719
+ // scaffolding syntax adds some noise to the depth that changes
720
+ // on a per testcase basis.
721
+ n -= 10
722
+ }
723
+
724
+ pre , mid , post := split (tt .format )
725
+ if strings .Contains (mid , "«" ) {
726
+ left , base , right := split (mid )
727
+ mid = strings .Repeat (left , n ) + base + strings .Repeat (right , n )
728
+ } else {
729
+ mid = strings .Repeat (mid , n )
730
+ }
731
+ input := pre + mid + post
732
+
733
+ fset := token .NewFileSet ()
734
+ _ , err := ParseFile (fset , "" , input , DeclarationErrors )
735
+ if size == "small" {
736
+ if err != nil {
737
+ t .Errorf ("ParseFile(...): %v (want success)" , err )
738
+ }
739
+ } else {
740
+ expected := "exceeded max scope depth during object resolution"
741
+ if err == nil || ! strings .HasSuffix (err .Error (), expected ) {
742
+ t .Errorf ("ParseFile(...) = _, %v, want %q" , err , expected )
743
+ }
744
+ }
745
+ })
746
+ }
747
+ }
748
+ }
0 commit comments