1package analysis
2
3import (
4	"go/ast"
5	"go/token"
6	"go/types"
7
8	"github.com/gopherjs/gopherjs/compiler/astutil"
9	"github.com/gopherjs/gopherjs/compiler/typesutil"
10)
11
12type continueStmt struct {
13	forStmt      *ast.ForStmt
14	analyzeStack []ast.Node
15}
16
17type Info struct {
18	*types.Info
19	Pkg           *types.Package
20	IsBlocking    func(*types.Func) bool
21	HasPointer    map[*types.Var]bool
22	FuncDeclInfos map[*types.Func]*FuncInfo
23	FuncLitInfos  map[*ast.FuncLit]*FuncInfo
24	InitFuncInfo  *FuncInfo
25	allInfos      []*FuncInfo
26	comments      ast.CommentMap
27}
28
29type FuncInfo struct {
30	HasDefer      bool
31	Flattened     map[ast.Node]bool
32	Blocking      map[ast.Node]bool
33	GotoLabel     map[*types.Label]bool
34	LocalCalls    map[*types.Func][][]ast.Node
35	ContinueStmts []continueStmt
36	p             *Info
37	analyzeStack  []ast.Node
38}
39
40func (info *Info) newFuncInfo() *FuncInfo {
41	funcInfo := &FuncInfo{
42		p:          info,
43		Flattened:  make(map[ast.Node]bool),
44		Blocking:   make(map[ast.Node]bool),
45		GotoLabel:  make(map[*types.Label]bool),
46		LocalCalls: make(map[*types.Func][][]ast.Node),
47	}
48	info.allInfos = append(info.allInfos, funcInfo)
49	return funcInfo
50}
51
52func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
53	info := &Info{
54		Info:          typesInfo,
55		Pkg:           typesPkg,
56		HasPointer:    make(map[*types.Var]bool),
57		comments:      make(ast.CommentMap),
58		IsBlocking:    isBlocking,
59		FuncDeclInfos: make(map[*types.Func]*FuncInfo),
60		FuncLitInfos:  make(map[*ast.FuncLit]*FuncInfo),
61	}
62	info.InitFuncInfo = info.newFuncInfo()
63
64	for _, file := range files {
65		for k, v := range ast.NewCommentMap(fileSet, file, file.Comments) {
66			info.comments[k] = v
67		}
68		ast.Walk(info.InitFuncInfo, file)
69	}
70
71	for {
72		done := true
73		for _, funcInfo := range info.allInfos {
74			for obj, calls := range funcInfo.LocalCalls {
75				if len(info.FuncDeclInfos[obj].Blocking) != 0 {
76					for _, call := range calls {
77						funcInfo.markBlocking(call)
78					}
79					delete(funcInfo.LocalCalls, obj)
80					done = false
81				}
82			}
83		}
84		if done {
85			break
86		}
87	}
88
89	for _, funcInfo := range info.allInfos {
90		for _, continueStmt := range funcInfo.ContinueStmts {
91			if funcInfo.Blocking[continueStmt.forStmt.Post] {
92				funcInfo.markBlocking(continueStmt.analyzeStack)
93			}
94		}
95	}
96
97	return info
98}
99
100func (c *FuncInfo) Visit(node ast.Node) ast.Visitor {
101	if node == nil {
102		if len(c.analyzeStack) != 0 {
103			c.analyzeStack = c.analyzeStack[:len(c.analyzeStack)-1]
104		}
105		return nil
106	}
107	c.analyzeStack = append(c.analyzeStack, node)
108
109	switch n := node.(type) {
110	case *ast.FuncDecl:
111		newInfo := c.p.newFuncInfo()
112		c.p.FuncDeclInfos[c.p.Defs[n.Name].(*types.Func)] = newInfo
113		return newInfo
114	case *ast.FuncLit:
115		newInfo := c.p.newFuncInfo()
116		c.p.FuncLitInfos[n] = newInfo
117		return newInfo
118	case *ast.BranchStmt:
119		switch n.Tok {
120		case token.GOTO:
121			for _, n2 := range c.analyzeStack {
122				c.Flattened[n2] = true
123			}
124			c.GotoLabel[c.p.Uses[n.Label].(*types.Label)] = true
125		case token.CONTINUE:
126			if n.Label != nil {
127				label := c.p.Uses[n.Label].(*types.Label)
128				for i := len(c.analyzeStack) - 1; i >= 0; i-- {
129					if labelStmt, ok := c.analyzeStack[i].(*ast.LabeledStmt); ok && c.p.Defs[labelStmt.Label] == label {
130						if _, ok := labelStmt.Stmt.(*ast.RangeStmt); ok {
131							return nil
132						}
133						stack := make([]ast.Node, len(c.analyzeStack))
134						copy(stack, c.analyzeStack)
135						c.ContinueStmts = append(c.ContinueStmts, continueStmt{labelStmt.Stmt.(*ast.ForStmt), stack})
136						return nil
137					}
138				}
139				return nil
140			}
141			for i := len(c.analyzeStack) - 1; i >= 0; i-- {
142				if _, ok := c.analyzeStack[i].(*ast.RangeStmt); ok {
143					return nil
144				}
145				if forStmt, ok := c.analyzeStack[i].(*ast.ForStmt); ok {
146					stack := make([]ast.Node, len(c.analyzeStack))
147					copy(stack, c.analyzeStack)
148					c.ContinueStmts = append(c.ContinueStmts, continueStmt{forStmt, stack})
149					return nil
150				}
151			}
152		}
153	case *ast.CallExpr:
154		callTo := func(obj types.Object) {
155			switch o := obj.(type) {
156			case *types.Func:
157				if recv := o.Type().(*types.Signature).Recv(); recv != nil {
158					if _, ok := recv.Type().Underlying().(*types.Interface); ok {
159						c.markBlocking(c.analyzeStack)
160						return
161					}
162				}
163				if o.Pkg() != c.p.Pkg {
164					if c.p.IsBlocking(o) {
165						c.markBlocking(c.analyzeStack)
166					}
167					return
168				}
169				stack := make([]ast.Node, len(c.analyzeStack))
170				copy(stack, c.analyzeStack)
171				c.LocalCalls[o] = append(c.LocalCalls[o], stack)
172			case *types.Var:
173				c.markBlocking(c.analyzeStack)
174			}
175		}
176		switch f := astutil.RemoveParens(n.Fun).(type) {
177		case *ast.Ident:
178			callTo(c.p.Uses[f])
179		case *ast.SelectorExpr:
180			if sel := c.p.Selections[f]; sel != nil && typesutil.IsJsObject(sel.Recv()) {
181				break
182			}
183			callTo(c.p.Uses[f.Sel])
184		case *ast.FuncLit:
185			ast.Walk(c, n.Fun)
186			for _, arg := range n.Args {
187				ast.Walk(c, arg)
188			}
189			if len(c.p.FuncLitInfos[f].Blocking) != 0 {
190				c.markBlocking(c.analyzeStack)
191			}
192			return nil
193		default:
194			if !astutil.IsTypeExpr(f, c.p.Info) {
195				c.markBlocking(c.analyzeStack)
196			}
197		}
198	case *ast.SendStmt:
199		c.markBlocking(c.analyzeStack)
200	case *ast.UnaryExpr:
201		switch n.Op {
202		case token.AND:
203			if id, ok := astutil.RemoveParens(n.X).(*ast.Ident); ok {
204				c.p.HasPointer[c.p.Uses[id].(*types.Var)] = true
205			}
206		case token.ARROW:
207			c.markBlocking(c.analyzeStack)
208		}
209	case *ast.RangeStmt:
210		if _, ok := c.p.TypeOf(n.X).Underlying().(*types.Chan); ok {
211			c.markBlocking(c.analyzeStack)
212		}
213	case *ast.SelectStmt:
214		for _, s := range n.Body.List {
215			if s.(*ast.CommClause).Comm == nil { // default clause
216				return c
217			}
218		}
219		c.markBlocking(c.analyzeStack)
220	case *ast.CommClause:
221		switch comm := n.Comm.(type) {
222		case *ast.SendStmt:
223			ast.Walk(c, comm.Chan)
224			ast.Walk(c, comm.Value)
225		case *ast.ExprStmt:
226			ast.Walk(c, comm.X.(*ast.UnaryExpr).X)
227		case *ast.AssignStmt:
228			ast.Walk(c, comm.Rhs[0].(*ast.UnaryExpr).X)
229		}
230		for _, s := range n.Body {
231			ast.Walk(c, s)
232		}
233		return nil
234	case *ast.GoStmt:
235		ast.Walk(c, n.Call.Fun)
236		for _, arg := range n.Call.Args {
237			ast.Walk(c, arg)
238		}
239		return nil
240	case *ast.DeferStmt:
241		c.HasDefer = true
242		if funcLit, ok := n.Call.Fun.(*ast.FuncLit); ok {
243			ast.Walk(c, funcLit.Body)
244		}
245	}
246	return c
247}
248
249func (c *FuncInfo) markBlocking(stack []ast.Node) {
250	for _, n := range stack {
251		c.Blocking[n] = true
252		c.Flattened[n] = true
253	}
254}
255