You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
6.3 KiB
255 lines
6.3 KiB
package analysis
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"github.com/gopherjs/gopherjs/compiler/astutil"
|
|
"github.com/gopherjs/gopherjs/compiler/typesutil"
|
|
)
|
|
|
|
type continueStmt struct {
|
|
forStmt *ast.ForStmt
|
|
analyzeStack []ast.Node
|
|
}
|
|
|
|
type Info struct {
|
|
*types.Info
|
|
Pkg *types.Package
|
|
IsBlocking func(*types.Func) bool
|
|
HasPointer map[*types.Var]bool
|
|
FuncDeclInfos map[*types.Func]*FuncInfo
|
|
FuncLitInfos map[*ast.FuncLit]*FuncInfo
|
|
InitFuncInfo *FuncInfo
|
|
allInfos []*FuncInfo
|
|
comments ast.CommentMap
|
|
}
|
|
|
|
type FuncInfo struct {
|
|
HasDefer bool
|
|
Flattened map[ast.Node]bool
|
|
Blocking map[ast.Node]bool
|
|
GotoLabel map[*types.Label]bool
|
|
LocalCalls map[*types.Func][][]ast.Node
|
|
ContinueStmts []continueStmt
|
|
p *Info
|
|
analyzeStack []ast.Node
|
|
}
|
|
|
|
func (info *Info) newFuncInfo() *FuncInfo {
|
|
funcInfo := &FuncInfo{
|
|
p: info,
|
|
Flattened: make(map[ast.Node]bool),
|
|
Blocking: make(map[ast.Node]bool),
|
|
GotoLabel: make(map[*types.Label]bool),
|
|
LocalCalls: make(map[*types.Func][][]ast.Node),
|
|
}
|
|
info.allInfos = append(info.allInfos, funcInfo)
|
|
return funcInfo
|
|
}
|
|
|
|
func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
|
|
info := &Info{
|
|
Info: typesInfo,
|
|
Pkg: typesPkg,
|
|
HasPointer: make(map[*types.Var]bool),
|
|
comments: make(ast.CommentMap),
|
|
IsBlocking: isBlocking,
|
|
FuncDeclInfos: make(map[*types.Func]*FuncInfo),
|
|
FuncLitInfos: make(map[*ast.FuncLit]*FuncInfo),
|
|
}
|
|
info.InitFuncInfo = info.newFuncInfo()
|
|
|
|
for _, file := range files {
|
|
for k, v := range ast.NewCommentMap(fileSet, file, file.Comments) {
|
|
info.comments[k] = v
|
|
}
|
|
ast.Walk(info.InitFuncInfo, file)
|
|
}
|
|
|
|
for {
|
|
done := true
|
|
for _, funcInfo := range info.allInfos {
|
|
for obj, calls := range funcInfo.LocalCalls {
|
|
if len(info.FuncDeclInfos[obj].Blocking) != 0 {
|
|
for _, call := range calls {
|
|
funcInfo.markBlocking(call)
|
|
}
|
|
delete(funcInfo.LocalCalls, obj)
|
|
done = false
|
|
}
|
|
}
|
|
}
|
|
if done {
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, funcInfo := range info.allInfos {
|
|
for _, continueStmt := range funcInfo.ContinueStmts {
|
|
if funcInfo.Blocking[continueStmt.forStmt.Post] {
|
|
funcInfo.markBlocking(continueStmt.analyzeStack)
|
|
}
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func (c *FuncInfo) Visit(node ast.Node) ast.Visitor {
|
|
if node == nil {
|
|
if len(c.analyzeStack) != 0 {
|
|
c.analyzeStack = c.analyzeStack[:len(c.analyzeStack)-1]
|
|
}
|
|
return nil
|
|
}
|
|
c.analyzeStack = append(c.analyzeStack, node)
|
|
|
|
switch n := node.(type) {
|
|
case *ast.FuncDecl:
|
|
newInfo := c.p.newFuncInfo()
|
|
c.p.FuncDeclInfos[c.p.Defs[n.Name].(*types.Func)] = newInfo
|
|
return newInfo
|
|
case *ast.FuncLit:
|
|
newInfo := c.p.newFuncInfo()
|
|
c.p.FuncLitInfos[n] = newInfo
|
|
return newInfo
|
|
case *ast.BranchStmt:
|
|
switch n.Tok {
|
|
case token.GOTO:
|
|
for _, n2 := range c.analyzeStack {
|
|
c.Flattened[n2] = true
|
|
}
|
|
c.GotoLabel[c.p.Uses[n.Label].(*types.Label)] = true
|
|
case token.CONTINUE:
|
|
if n.Label != nil {
|
|
label := c.p.Uses[n.Label].(*types.Label)
|
|
for i := len(c.analyzeStack) - 1; i >= 0; i-- {
|
|
if labelStmt, ok := c.analyzeStack[i].(*ast.LabeledStmt); ok && c.p.Defs[labelStmt.Label] == label {
|
|
if _, ok := labelStmt.Stmt.(*ast.RangeStmt); ok {
|
|
return nil
|
|
}
|
|
stack := make([]ast.Node, len(c.analyzeStack))
|
|
copy(stack, c.analyzeStack)
|
|
c.ContinueStmts = append(c.ContinueStmts, continueStmt{labelStmt.Stmt.(*ast.ForStmt), stack})
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
for i := len(c.analyzeStack) - 1; i >= 0; i-- {
|
|
if _, ok := c.analyzeStack[i].(*ast.RangeStmt); ok {
|
|
return nil
|
|
}
|
|
if forStmt, ok := c.analyzeStack[i].(*ast.ForStmt); ok {
|
|
stack := make([]ast.Node, len(c.analyzeStack))
|
|
copy(stack, c.analyzeStack)
|
|
c.ContinueStmts = append(c.ContinueStmts, continueStmt{forStmt, stack})
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
case *ast.CallExpr:
|
|
callTo := func(obj types.Object) {
|
|
switch o := obj.(type) {
|
|
case *types.Func:
|
|
if recv := o.Type().(*types.Signature).Recv(); recv != nil {
|
|
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
|
c.markBlocking(c.analyzeStack)
|
|
return
|
|
}
|
|
}
|
|
if o.Pkg() != c.p.Pkg {
|
|
if c.p.IsBlocking(o) {
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
return
|
|
}
|
|
stack := make([]ast.Node, len(c.analyzeStack))
|
|
copy(stack, c.analyzeStack)
|
|
c.LocalCalls[o] = append(c.LocalCalls[o], stack)
|
|
case *types.Var:
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
}
|
|
switch f := astutil.RemoveParens(n.Fun).(type) {
|
|
case *ast.Ident:
|
|
callTo(c.p.Uses[f])
|
|
case *ast.SelectorExpr:
|
|
if sel := c.p.Selections[f]; sel != nil && typesutil.IsJsObject(sel.Recv()) {
|
|
break
|
|
}
|
|
callTo(c.p.Uses[f.Sel])
|
|
case *ast.FuncLit:
|
|
ast.Walk(c, n.Fun)
|
|
for _, arg := range n.Args {
|
|
ast.Walk(c, arg)
|
|
}
|
|
if len(c.p.FuncLitInfos[f].Blocking) != 0 {
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
return nil
|
|
default:
|
|
if !astutil.IsTypeExpr(f, c.p.Info) {
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
}
|
|
case *ast.SendStmt:
|
|
c.markBlocking(c.analyzeStack)
|
|
case *ast.UnaryExpr:
|
|
switch n.Op {
|
|
case token.AND:
|
|
if id, ok := astutil.RemoveParens(n.X).(*ast.Ident); ok {
|
|
c.p.HasPointer[c.p.Uses[id].(*types.Var)] = true
|
|
}
|
|
case token.ARROW:
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
case *ast.RangeStmt:
|
|
if _, ok := c.p.TypeOf(n.X).Underlying().(*types.Chan); ok {
|
|
c.markBlocking(c.analyzeStack)
|
|
}
|
|
case *ast.SelectStmt:
|
|
for _, s := range n.Body.List {
|
|
if s.(*ast.CommClause).Comm == nil { // default clause
|
|
return c
|
|
}
|
|
}
|
|
c.markBlocking(c.analyzeStack)
|
|
case *ast.CommClause:
|
|
switch comm := n.Comm.(type) {
|
|
case *ast.SendStmt:
|
|
ast.Walk(c, comm.Chan)
|
|
ast.Walk(c, comm.Value)
|
|
case *ast.ExprStmt:
|
|
ast.Walk(c, comm.X.(*ast.UnaryExpr).X)
|
|
case *ast.AssignStmt:
|
|
ast.Walk(c, comm.Rhs[0].(*ast.UnaryExpr).X)
|
|
}
|
|
for _, s := range n.Body {
|
|
ast.Walk(c, s)
|
|
}
|
|
return nil
|
|
case *ast.GoStmt:
|
|
ast.Walk(c, n.Call.Fun)
|
|
for _, arg := range n.Call.Args {
|
|
ast.Walk(c, arg)
|
|
}
|
|
return nil
|
|
case *ast.DeferStmt:
|
|
c.HasDefer = true
|
|
if funcLit, ok := n.Call.Fun.(*ast.FuncLit); ok {
|
|
ast.Walk(c, funcLit.Body)
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *FuncInfo) markBlocking(stack []ast.Node) {
|
|
for _, n := range stack {
|
|
c.Blocking[n] = true
|
|
c.Flattened[n] = true
|
|
}
|
|
}
|