github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/compiler/analysis/info.go (about)

     1  package analysis
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  
     8  	"github.com/goplusjs/gopherjs/compiler/astutil"
     9  	"github.com/goplusjs/gopherjs/compiler/typesutil"
    10  )
    11  
    12  type continueStmt struct {
    13  	forStmt      *ast.ForStmt
    14  	analyzeStack []ast.Node
    15  }
    16  
    17  type 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  }
    27  
    28  type FuncInfo struct {
    29  	HasDefer bool
    30  	// Flattened map tracks which AST nodes within function body must be
    31  	// translated into re-enterant blocks.
    32  	//
    33  	// Function body needs to be "flattened" if an option to jump an arbitrary
    34  	// position in the code is required. Typical examples are a "goto" operator or
    35  	// resuming goroutine execution after a blocking call.
    36  	Flattened map[ast.Node]bool
    37  	// Blocking map tracks which AST nodes lead to potentially blocking calls.
    38  	//
    39  	// Blocking calls require special handling on JS side to avoid blocking the
    40  	// event loop and freezing the page.
    41  	Blocking map[ast.Node]bool
    42  	// GotoLabel keeps track of labels referenced by a goto operator.
    43  	//
    44  	// JS doesn't support "goto" natively and it needs to be emulated with a
    45  	// switch/case statement. This is distinct from labeled loop statements, which
    46  	// have native JS syntax and don't require special handling.
    47  	GotoLabel map[*types.Label]bool
    48  
    49  	// All callsite AST paths for all functions called by this function.
    50  	localCalls map[*types.Func][][]ast.Node
    51  	// All "continue" operators in the function body.
    52  	//
    53  	// "continue" operator may trigger blocking calls in for loop condition or
    54  	// post-iteration statement, so they may require special handling.
    55  	continueStmts []continueStmt
    56  	packageInfo   *Info
    57  	analyzeStack  []ast.Node
    58  }
    59  
    60  func (info *Info) newFuncInfo() *FuncInfo {
    61  	funcInfo := &FuncInfo{
    62  		packageInfo: info,
    63  		Flattened:   make(map[ast.Node]bool),
    64  		Blocking:    make(map[ast.Node]bool),
    65  		GotoLabel:   make(map[*types.Label]bool),
    66  		localCalls:  make(map[*types.Func][][]ast.Node),
    67  	}
    68  	info.allInfos = append(info.allInfos, funcInfo)
    69  	return funcInfo
    70  }
    71  
    72  func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
    73  	info := &Info{
    74  		Info:          typesInfo,
    75  		Pkg:           typesPkg,
    76  		HasPointer:    make(map[*types.Var]bool),
    77  		IsBlocking:    isBlocking,
    78  		FuncDeclInfos: make(map[*types.Func]*FuncInfo),
    79  		FuncLitInfos:  make(map[*ast.FuncLit]*FuncInfo),
    80  	}
    81  	info.InitFuncInfo = info.newFuncInfo()
    82  
    83  	for _, file := range files {
    84  		ast.Walk(info.InitFuncInfo, file)
    85  	}
    86  
    87  	// Propagate information about blocking calls through the AST tree.
    88  	// TODO: This can probably be done more efficiently while traversing the AST
    89  	// tree.
    90  	for {
    91  		done := true
    92  		for _, funcInfo := range info.allInfos {
    93  			for obj, calls := range funcInfo.localCalls {
    94  				if len(info.FuncDeclInfos[obj].Blocking) != 0 {
    95  					for _, call := range calls {
    96  						funcInfo.markBlocking(call)
    97  					}
    98  					delete(funcInfo.localCalls, obj)
    99  					done = false
   100  				}
   101  			}
   102  		}
   103  		if done {
   104  			break
   105  		}
   106  	}
   107  
   108  	// Detect all "continue" statements that lead to blocking calls.
   109  	for _, funcInfo := range info.allInfos {
   110  		for _, continueStmt := range funcInfo.continueStmts {
   111  			if funcInfo.Blocking[continueStmt.forStmt.Post] {
   112  				funcInfo.markBlocking(continueStmt.analyzeStack)
   113  			}
   114  		}
   115  		funcInfo.continueStmts = nil // We no longer need this information.
   116  	}
   117  
   118  	info.allInfos = nil // Let GC reclaim memory we no longer need.
   119  
   120  	return info
   121  }
   122  
   123  func (c *FuncInfo) Visit(node ast.Node) ast.Visitor {
   124  	if node == nil {
   125  		if len(c.analyzeStack) != 0 {
   126  			c.analyzeStack = c.analyzeStack[:len(c.analyzeStack)-1]
   127  		}
   128  		return nil
   129  	}
   130  	c.analyzeStack = append(c.analyzeStack, node)
   131  
   132  	switch n := node.(type) {
   133  	case *ast.FuncDecl:
   134  		newInfo := c.packageInfo.newFuncInfo()
   135  		c.packageInfo.FuncDeclInfos[c.packageInfo.Defs[n.Name].(*types.Func)] = newInfo
   136  		return newInfo
   137  	case *ast.FuncLit:
   138  		newInfo := c.packageInfo.newFuncInfo()
   139  		c.packageInfo.FuncLitInfos[n] = newInfo
   140  		return newInfo
   141  	case *ast.BranchStmt:
   142  		switch n.Tok {
   143  		case token.GOTO:
   144  			for _, n2 := range c.analyzeStack {
   145  				c.Flattened[n2] = true
   146  			}
   147  			c.GotoLabel[c.packageInfo.Uses[n.Label].(*types.Label)] = true
   148  		case token.CONTINUE:
   149  			if n.Label != nil {
   150  				label := c.packageInfo.Uses[n.Label].(*types.Label)
   151  				for i := len(c.analyzeStack) - 1; i >= 0; i-- {
   152  					if labelStmt, ok := c.analyzeStack[i].(*ast.LabeledStmt); ok && c.packageInfo.Defs[labelStmt.Label] == label {
   153  						if _, ok := labelStmt.Stmt.(*ast.RangeStmt); ok {
   154  							return nil
   155  						}
   156  						stack := make([]ast.Node, len(c.analyzeStack))
   157  						copy(stack, c.analyzeStack)
   158  						c.continueStmts = append(c.continueStmts, continueStmt{labelStmt.Stmt.(*ast.ForStmt), stack})
   159  						return nil
   160  					}
   161  				}
   162  				return nil
   163  			}
   164  			for i := len(c.analyzeStack) - 1; i >= 0; i-- {
   165  				if _, ok := c.analyzeStack[i].(*ast.RangeStmt); ok {
   166  					return nil
   167  				}
   168  				if forStmt, ok := c.analyzeStack[i].(*ast.ForStmt); ok {
   169  					stack := make([]ast.Node, len(c.analyzeStack))
   170  					copy(stack, c.analyzeStack)
   171  					c.continueStmts = append(c.continueStmts, continueStmt{forStmt, stack})
   172  					return nil
   173  				}
   174  			}
   175  		}
   176  	case *ast.CallExpr:
   177  		callTo := func(obj types.Object) {
   178  			switch o := obj.(type) {
   179  			case *types.Func:
   180  				if recv := o.Type().(*types.Signature).Recv(); recv != nil {
   181  					if _, ok := recv.Type().Underlying().(*types.Interface); ok {
   182  						c.markBlocking(c.analyzeStack)
   183  						return
   184  					}
   185  				}
   186  				if o.Pkg() != c.packageInfo.Pkg {
   187  					if c.packageInfo.IsBlocking(o) {
   188  						c.markBlocking(c.analyzeStack)
   189  					}
   190  					return
   191  				}
   192  				stack := make([]ast.Node, len(c.analyzeStack))
   193  				copy(stack, c.analyzeStack)
   194  				c.localCalls[o] = append(c.localCalls[o], stack)
   195  			case *types.Var:
   196  				c.markBlocking(c.analyzeStack)
   197  			}
   198  		}
   199  		switch f := astutil.RemoveParens(n.Fun).(type) {
   200  		case *ast.Ident:
   201  			callTo(c.packageInfo.Uses[f])
   202  		case *ast.SelectorExpr:
   203  			if sel := c.packageInfo.Selections[f]; sel != nil && typesutil.IsJsObject(sel.Recv()) {
   204  				break
   205  			}
   206  			callTo(c.packageInfo.Uses[f.Sel])
   207  		case *ast.FuncLit:
   208  			ast.Walk(c, n.Fun)
   209  			for _, arg := range n.Args {
   210  				ast.Walk(c, arg)
   211  			}
   212  			if len(c.packageInfo.FuncLitInfos[f].Blocking) != 0 {
   213  				c.markBlocking(c.analyzeStack)
   214  			}
   215  			return nil
   216  		default:
   217  			if !astutil.IsTypeExpr(f, c.packageInfo.Info) {
   218  				c.markBlocking(c.analyzeStack)
   219  			}
   220  		}
   221  	case *ast.SendStmt:
   222  		c.markBlocking(c.analyzeStack)
   223  	case *ast.UnaryExpr:
   224  		switch n.Op {
   225  		case token.AND:
   226  			if id, ok := astutil.RemoveParens(n.X).(*ast.Ident); ok {
   227  				c.packageInfo.HasPointer[c.packageInfo.Uses[id].(*types.Var)] = true
   228  			}
   229  		case token.ARROW:
   230  			c.markBlocking(c.analyzeStack)
   231  		}
   232  	case *ast.RangeStmt:
   233  		if _, ok := c.packageInfo.TypeOf(n.X).Underlying().(*types.Chan); ok {
   234  			c.markBlocking(c.analyzeStack)
   235  		}
   236  	case *ast.SelectStmt:
   237  		for _, s := range n.Body.List {
   238  			if s.(*ast.CommClause).Comm == nil { // default clause
   239  				return c
   240  			}
   241  		}
   242  		c.markBlocking(c.analyzeStack)
   243  	case *ast.CommClause:
   244  		switch comm := n.Comm.(type) {
   245  		case *ast.SendStmt:
   246  			ast.Walk(c, comm.Chan)
   247  			ast.Walk(c, comm.Value)
   248  		case *ast.ExprStmt:
   249  			ast.Walk(c, comm.X.(*ast.UnaryExpr).X)
   250  		case *ast.AssignStmt:
   251  			ast.Walk(c, comm.Rhs[0].(*ast.UnaryExpr).X)
   252  		}
   253  		for _, s := range n.Body {
   254  			ast.Walk(c, s)
   255  		}
   256  		return nil
   257  	case *ast.GoStmt:
   258  		ast.Walk(c, n.Call.Fun)
   259  		for _, arg := range n.Call.Args {
   260  			ast.Walk(c, arg)
   261  		}
   262  		return nil
   263  	case *ast.DeferStmt:
   264  		c.HasDefer = true
   265  		if funcLit, ok := n.Call.Fun.(*ast.FuncLit); ok {
   266  			ast.Walk(c, funcLit.Body)
   267  		}
   268  	}
   269  	return c
   270  }
   271  
   272  func (c *FuncInfo) markBlocking(stack []ast.Node) {
   273  	for _, n := range stack {
   274  		c.Blocking[n] = true
   275  		c.Flattened[n] = true
   276  	}
   277  }