GoPLS Viewer

Home|gopls/go/analysis/passes/ctrlflow/ctrlflow.go
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package ctrlflow is an analysis that provides a syntactic
6// control-flow graph (CFG) for the body of a function.
7// It records whether a function cannot return.
8// By itself, it does not report any diagnostics.
9package ctrlflow
10
11import (
12    "go/ast"
13    "go/types"
14    "log"
15    "reflect"
16
17    "golang.org/x/tools/go/analysis"
18    "golang.org/x/tools/go/analysis/passes/inspect"
19    "golang.org/x/tools/go/ast/inspector"
20    "golang.org/x/tools/go/cfg"
21    "golang.org/x/tools/go/types/typeutil"
22)
23
24var Analyzer = &analysis.Analyzer{
25    Name:       "ctrlflow",
26    Doc:        "build a control-flow graph",
27    Run:        run,
28    ResultTypereflect.TypeOf(new(CFGs)),
29    FactTypes:  []analysis.Fact{new(noReturn)},
30    Requires:   []*analysis.Analyzer{inspect.Analyzer},
31}
32
33// noReturn is a fact indicating that a function does not return.
34type noReturn struct{}
35
36func (*noReturnAFact() {}
37
38func (*noReturnString() string { return "noReturn" }
39
40// A CFGs holds the control-flow graphs
41// for all the functions of the current package.
42type CFGs struct {
43    defs      map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
44    funcDecls map[*types.Func]*declInfo
45    funcLits  map[*ast.FuncLit]*litInfo
46    pass      *analysis.Pass // transient; nil after construction
47}
48
49// CFGs has two maps: funcDecls for named functions and funcLits for
50// unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its
51// syntax node, *ast.FuncDecl, because callMayReturn needs to do a
52// look-up by *types.Func, and you can get from an *ast.FuncDecl to a
53// *types.Func but not the other way.
54
55type declInfo struct {
56    decl     *ast.FuncDecl
57    cfg      *cfg.CFG // iff decl.Body != nil
58    started  bool     // to break cycles
59    noReturn bool
60}
61
62type litInfo struct {
63    cfg      *cfg.CFG
64    noReturn bool
65}
66
67// FuncDecl returns the control-flow graph for a named function.
68// It returns nil if decl.Body==nil.
69func (c *CFGsFuncDecl(decl *ast.FuncDecl) *cfg.CFG {
70    if decl.Body == nil {
71        return nil
72    }
73    fn := c.defs[decl.Name].(*types.Func)
74    return c.funcDecls[fn].cfg
75}
76
77// FuncLit returns the control-flow graph for a literal function.
78func (c *CFGsFuncLit(lit *ast.FuncLit) *cfg.CFG {
79    return c.funcLits[lit].cfg
80}
81
82func run(pass *analysis.Pass) (interface{}, error) {
83    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
84
85    // Because CFG construction consumes and produces noReturn
86    // facts, CFGs for exported FuncDecls must be built before 'run'
87    // returns; we cannot construct them lazily.
88    // (We could build CFGs for FuncLits lazily,
89    // but the benefit is marginal.)
90
91    // Pass 1. Map types.Funcs to ast.FuncDecls in this package.
92    funcDecls := make(map[*types.Func]*declInfo// functions and methods
93    funcLits := make(map[*ast.FuncLit]*litInfo)
94
95    var decls []*types.Func // keys(funcDecls), in order
96    var lits []*ast.FuncLit // keys(funcLits), in order
97
98    nodeFilter := []ast.Node{
99        (*ast.FuncDecl)(nil),
100        (*ast.FuncLit)(nil),
101    }
102    inspect.Preorder(nodeFilter, func(n ast.Node) {
103        switch n := n.(type) {
104        case *ast.FuncDecl:
105            // Type information may be incomplete.
106            if fnok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
107                funcDecls[fn] = &declInfo{decln}
108                decls = append(declsfn)
109            }
110        case *ast.FuncLit:
111            funcLits[n] = new(litInfo)
112            lits = append(litsn)
113        }
114    })
115
116    c := &CFGs{
117        defs:      pass.TypesInfo.Defs,
118        funcDeclsfuncDecls,
119        funcLits:  funcLits,
120        pass:      pass,
121    }
122
123    // Pass 2. Build CFGs.
124
125    // Build CFGs for named functions.
126    // Cycles in the static call graph are broken
127    // arbitrarily but deterministically.
128    // We create noReturn facts as discovered.
129    for _fn := range decls {
130        c.buildDecl(fnfuncDecls[fn])
131    }
132
133    // Build CFGs for literal functions.
134    // These aren't relevant to facts (since they aren't named)
135    // but are required for the CFGs.FuncLit API.
136    for _lit := range lits {
137        li := funcLits[lit]
138        if li.cfg == nil {
139            li.cfg = cfg.New(lit.Bodyc.callMayReturn)
140            if !hasReachableReturn(li.cfg) {
141                li.noReturn = true
142            }
143        }
144    }
145
146    // All CFGs are now built.
147    c.pass = nil
148
149    return cnil
150}
151
152// di.cfg may be nil on return.
153func (c *CFGsbuildDecl(fn *types.Funcdi *declInfo) {
154    // buildDecl may call itself recursively for the same function,
155    // because cfg.New is passed the callMayReturn method, which
156    // builds the CFG of the callee, leading to recursion.
157    // The buildDecl call tree thus resembles the static call graph.
158    // We mark each node when we start working on it to break cycles.
159
160    if !di.started { // break cycle
161        di.started = true
162
163        if isIntrinsicNoReturn(fn) {
164            di.noReturn = true
165        }
166        if di.decl.Body != nil {
167            di.cfg = cfg.New(di.decl.Bodyc.callMayReturn)
168            if !hasReachableReturn(di.cfg) {
169                di.noReturn = true
170            }
171        }
172        if di.noReturn {
173            c.pass.ExportObjectFact(fnnew(noReturn))
174        }
175
176        // debugging
177        if false {
178            log.Printf("CFG for %s:\n%s (noreturn=%t)\n"fndi.cfg.Format(c.pass.Fset), di.noReturn)
179        }
180    }
181}
182
183// callMayReturn reports whether the called function may return.
184// It is passed to the CFG builder.
185func (c *CFGscallMayReturn(call *ast.CallExpr) (r bool) {
186    if idok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
187        return false // panic never returns
188    }
189
190    // Is this a static call? Also includes static functions
191    // parameterized by a type. Such functions may or may not
192    // return depending on the parameter type, but in some
193    // cases the answer is definite. We let ctrlflow figure
194    // that out.
195    fn := typeutil.StaticCallee(c.pass.TypesInfocall)
196    if fn == nil {
197        return true // callee not statically known; be conservative
198    }
199
200    // Function or method declared in this package?
201    if diok := c.funcDecls[fn]; ok {
202        c.buildDecl(fndi)
203        return !di.noReturn
204    }
205
206    // Not declared in this package.
207    // Is there a fact from another package?
208    return !c.pass.ImportObjectFact(fnnew(noReturn))
209}
210
211var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
212
213func hasReachableReturn(g *cfg.CFGbool {
214    for _b := range g.Blocks {
215        if b.Live && b.Return() != nil {
216            return true
217        }
218    }
219    return false
220}
221
222// isIntrinsicNoReturn reports whether a function intrinsically never
223// returns because it stops execution of the calling thread.
224// It is the base case in the recursion.
225func isIntrinsicNoReturn(fn *types.Funcbool {
226    // Add functions here as the need arises, but don't allocate memory.
227    pathname := fn.Pkg().Path(), fn.Name()
228    return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
229        path == "runtime" && name == "Goexit"
230}
231
MembersX
noReturn.String
run
isIntrinsicNoReturn
analysis
hasReachableReturn.RangeStmt_6107.b
declInfo
CFGs.callMayReturn.c
declInfo.decl
CFGs.buildDecl.di
CFGs.callMayReturn.fn
inspector
CFGs.funcLits
isIntrinsicNoReturn.path
CFGs.FuncDecl
run.pass
run.funcLits
CFGs.buildDecl
hasReachableReturn
isIntrinsicNoReturn.fn
CFGs.pass
litInfo
litInfo.cfg
types
reflect
run.lits
declInfo.noReturn
run.RangeStmt_3736.fn
noReturn
CFGs
CFGs.FuncLit.lit
isIntrinsicNoReturn.name
inspect
CFGs.FuncLit
run.c
CFGs.FuncLit.c
run.funcDecls
CFGs.buildDecl.c
CFGs.callMayReturn.call
ast
log
CFGs.funcDecls
CFGs.callMayReturn
cfg
run.decls
CFGs.buildDecl.fn
noReturn.AFact
run.nodeFilter
run.RangeStmt_3947.lit
CFGs.FuncDecl.decl
hasReachableReturn.g
CFGs.defs
declInfo.cfg
litInfo.noReturn
CFGs.callMayReturn.r
typeutil
declInfo.started
CFGs.FuncDecl.c
Members
X