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 }