@@ -61,11 +61,12 @@ type Info struct {
61
61
62
62
func (info * Info ) newFuncInfo (n ast.Node ) * FuncInfo {
63
63
funcInfo := & FuncInfo {
64
- pkgInfo : info ,
65
- Flattened : make (map [ast.Node ]bool ),
66
- Blocking : make (map [ast.Node ]bool ),
67
- GotoLabel : make (map [* types.Label ]bool ),
68
- localCallees : make (map [* types.Func ][]astPath ),
64
+ pkgInfo : info ,
65
+ Flattened : make (map [ast.Node ]bool ),
66
+ Blocking : make (map [ast.Node ]bool ),
67
+ GotoLabel : make (map [* types.Label ]bool ),
68
+ localNamedCallees : make (map [* types.Func ][]astPath ),
69
+ literalFuncCallees : make (map [* ast.FuncLit ][]astPath ),
69
70
}
70
71
71
72
// Register the function in the appropriate map.
@@ -128,15 +129,30 @@ func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info
128
129
}
129
130
130
131
// Propagate information about blocking calls to the caller functions.
132
+ // For each function we check all other functions it may call and if any of
133
+ // them are blocking, we mark the caller blocking as well. The process is
134
+ // repeated until no new blocking functions is detected.
131
135
for {
132
136
done := true
133
137
for _ , caller := range info .allInfos {
134
- for callee , callSites := range caller .localCallees {
138
+ // Check calls to named functions and function-typed variables.
139
+ for callee , callSites := range caller .localNamedCallees {
135
140
if info .IsBlocking (callee ) {
136
141
for _ , callSite := range callSites {
137
142
caller .markBlocking (callSite )
138
143
}
139
- delete (caller .localCallees , callee )
144
+ delete (caller .localNamedCallees , callee )
145
+ done = false
146
+ }
147
+ }
148
+
149
+ // Check direct calls to function literals.
150
+ for callee , callSites := range caller .literalFuncCallees {
151
+ if len (info .FuncLitInfos [callee ].Blocking ) > 0 {
152
+ for _ , callSite := range callSites {
153
+ caller .markBlocking (callSite )
154
+ }
155
+ delete (caller .literalFuncCallees , callee )
140
156
done = false
141
157
}
142
158
}
@@ -177,9 +193,15 @@ type FuncInfo struct {
177
193
continueStmts []continueStmt
178
194
// List of return statements in the function.
179
195
returnStmts []astPath
180
- // List of other functions from the current package this function calls. If
181
- // any of them are blocking, this function will become blocking too.
182
- localCallees map [* types.Func ][]astPath
196
+ // List of other named functions from the current package this function calls.
197
+ // If any of them are blocking, this function will become blocking too.
198
+ localNamedCallees map [* types.Func ][]astPath
199
+ // List of function literals directly called from this function (for example:
200
+ // `func() { /* do stuff */ }()`). This is distinct from function literals
201
+ // assigned to named variables (for example: `doStuff := func() {};
202
+ // doStuff()`), which are handled by localNamedCallees. If any of them are
203
+ // identified as blocking, this function will become blocking too.
204
+ literalFuncCallees map [* ast.FuncLit ][]astPath
183
205
184
206
pkgInfo * Info // Function's parent package.
185
207
visitorStack astPath
@@ -296,13 +318,13 @@ func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor {
296
318
func (fi * FuncInfo ) visitCallExpr (n * ast.CallExpr ) ast.Visitor {
297
319
switch f := astutil .RemoveParens (n .Fun ).(type ) {
298
320
case * ast.Ident :
299
- fi .callTo (fi .pkgInfo .Uses [f ])
321
+ fi .callToNamedFunc (fi .pkgInfo .Uses [f ])
300
322
case * ast.SelectorExpr :
301
323
if sel := fi .pkgInfo .Selections [f ]; sel != nil && typesutil .IsJsObject (sel .Recv ()) {
302
324
// js.Object methods are known to be non-blocking, but we still must
303
325
// check its arguments.
304
326
} else {
305
- fi .callTo (fi .pkgInfo .Uses [f .Sel ])
327
+ fi .callToNamedFunc (fi .pkgInfo .Uses [f .Sel ])
306
328
}
307
329
case * ast.FuncLit :
308
330
// Collect info about the function literal itself.
@@ -312,13 +334,8 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor {
312
334
for _ , arg := range n .Args {
313
335
ast .Walk (fi , arg )
314
336
}
315
- // If the function literal is blocking, this function is blocking to.
316
- // FIXME(nevkontakte): What if the function literal is calling a blocking
317
- // function through several layers of indirection? This will only become
318
- // known at a later stage of analysis.
319
- if len (fi .pkgInfo .FuncLitInfos [f ].Blocking ) != 0 {
320
- fi .markBlocking (fi .visitorStack )
321
- }
337
+ // Register literal function call site in case it is identified as blocking.
338
+ fi .literalFuncCallees [f ] = append (fi .literalFuncCallees [f ], fi .visitorStack .copy ())
322
339
return nil // No need to walk under this CallExpr, we already did it manually.
323
340
default :
324
341
if astutil .IsTypeExpr (f , fi .pkgInfo .Info ) {
@@ -334,12 +351,12 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor {
334
351
return fi
335
352
}
336
353
337
- func (fi * FuncInfo ) callTo (callee types.Object ) {
354
+ func (fi * FuncInfo ) callToNamedFunc (callee types.Object ) {
338
355
switch o := callee .(type ) {
339
356
case * types.Func :
340
357
if recv := o .Type ().(* types.Signature ).Recv (); recv != nil {
341
358
if _ , ok := recv .Type ().Underlying ().(* types.Interface ); ok {
342
- // Conservatively assume that an interfact implementation might be blocking.
359
+ // Conservatively assume that an interface implementation may be blocking.
343
360
fi .markBlocking (fi .visitorStack )
344
361
return
345
362
}
@@ -352,7 +369,7 @@ func (fi *FuncInfo) callTo(callee types.Object) {
352
369
}
353
370
// We probably don't know yet whether the callee function is blocking.
354
371
// Record the calls site for the later stage.
355
- fi .localCallees [o ] = append (fi .localCallees [o ], fi .visitorStack .copy ())
372
+ fi .localNamedCallees [o ] = append (fi .localNamedCallees [o ], fi .visitorStack .copy ())
356
373
case * types.Var :
357
374
// Conservatively assume that a function in a variable might be blocking.
358
375
fi .markBlocking (fi .visitorStack )
0 commit comments