--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ctrlflow is an analysis that provides a syntactic
+// control-flow graph (CFG) for the body of a function.
+// It records whether a function cannot return.
+// By itself, it does not report any diagnostics.
+package ctrlflow
+
+import (
+ "go/ast"
+ "go/types"
+ "log"
+ "reflect"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/go/cfg"
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+var Analyzer = &analysis.Analyzer{
+ Name: "ctrlflow",
+ Doc: "build a control-flow graph",
+ Run: run,
+ ResultType: reflect.TypeOf(new(CFGs)),
+ FactTypes: []analysis.Fact{new(noReturn)},
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+}
+
+// noReturn is a fact indicating that a function does not return.
+type noReturn struct{}
+
+func (*noReturn) AFact() {}
+
+func (*noReturn) String() string { return "noReturn" }
+
+// A CFGs holds the control-flow graphs
+// for all the functions of the current package.
+type CFGs struct {
+ defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
+ funcDecls map[*types.Func]*declInfo
+ funcLits map[*ast.FuncLit]*litInfo
+ pass *analysis.Pass // transient; nil after construction
+}
+
+// CFGs has two maps: funcDecls for named functions and funcLits for
+// unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its
+// syntax node, *ast.FuncDecl, because callMayReturn needs to do a
+// look-up by *types.Func, and you can get from an *ast.FuncDecl to a
+// *types.Func but not the other way.
+
+type declInfo struct {
+ decl *ast.FuncDecl
+ cfg *cfg.CFG // iff decl.Body != nil
+ started bool // to break cycles
+ noReturn bool
+}
+
+type litInfo struct {
+ cfg *cfg.CFG
+ noReturn bool
+}
+
+// FuncDecl returns the control-flow graph for a named function.
+// It returns nil if decl.Body==nil.
+func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
+ if decl.Body == nil {
+ return nil
+ }
+ fn := c.defs[decl.Name].(*types.Func)
+ return c.funcDecls[fn].cfg
+}
+
+// FuncLit returns the control-flow graph for a literal function.
+func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
+ return c.funcLits[lit].cfg
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+ // Because CFG construction consumes and produces noReturn
+ // facts, CFGs for exported FuncDecls must be built before 'run'
+ // returns; we cannot construct them lazily.
+ // (We could build CFGs for FuncLits lazily,
+ // but the benefit is marginal.)
+
+ // Pass 1. Map types.Funcs to ast.FuncDecls in this package.
+ funcDecls := make(map[*types.Func]*declInfo) // functions and methods
+ funcLits := make(map[*ast.FuncLit]*litInfo)
+
+ var decls []*types.Func // keys(funcDecls), in order
+ var lits []*ast.FuncLit // keys(funcLits), in order
+
+ nodeFilter := []ast.Node{
+ (*ast.FuncDecl)(nil),
+ (*ast.FuncLit)(nil),
+ }
+ inspect.Preorder(nodeFilter, func(n ast.Node) {
+ switch n := n.(type) {
+ case *ast.FuncDecl:
+ // Type information may be incomplete.
+ if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
+ funcDecls[fn] = &declInfo{decl: n}
+ decls = append(decls, fn)
+ }
+ case *ast.FuncLit:
+ funcLits[n] = new(litInfo)
+ lits = append(lits, n)
+ }
+ })
+
+ c := &CFGs{
+ defs: pass.TypesInfo.Defs,
+ funcDecls: funcDecls,
+ funcLits: funcLits,
+ pass: pass,
+ }
+
+ // Pass 2. Build CFGs.
+
+ // Build CFGs for named functions.
+ // Cycles in the static call graph are broken
+ // arbitrarily but deterministically.
+ // We create noReturn facts as discovered.
+ for _, fn := range decls {
+ c.buildDecl(fn, funcDecls[fn])
+ }
+
+ // Build CFGs for literal functions.
+ // These aren't relevant to facts (since they aren't named)
+ // but are required for the CFGs.FuncLit API.
+ for _, lit := range lits {
+ li := funcLits[lit]
+ if li.cfg == nil {
+ li.cfg = cfg.New(lit.Body, c.callMayReturn)
+ if !hasReachableReturn(li.cfg) {
+ li.noReturn = true
+ }
+ }
+ }
+
+ // All CFGs are now built.
+ c.pass = nil
+
+ return c, nil
+}
+
+// di.cfg may be nil on return.
+func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
+ // buildDecl may call itself recursively for the same function,
+ // because cfg.New is passed the callMayReturn method, which
+ // builds the CFG of the callee, leading to recursion.
+ // The buildDecl call tree thus resembles the static call graph.
+ // We mark each node when we start working on it to break cycles.
+
+ if !di.started { // break cycle
+ di.started = true
+
+ if isIntrinsicNoReturn(fn) {
+ di.noReturn = true
+ }
+ if di.decl.Body != nil {
+ di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
+ if !hasReachableReturn(di.cfg) {
+ di.noReturn = true
+ }
+ }
+ if di.noReturn {
+ c.pass.ExportObjectFact(fn, new(noReturn))
+ }
+
+ // debugging
+ if false {
+ log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn)
+ }
+ }
+}
+
+// callMayReturn reports whether the called function may return.
+// It is passed to the CFG builder.
+func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
+ if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
+ return false // panic never returns
+ }
+
+ // Is this a static call?
+ fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
+ if fn == nil {
+ return true // callee not statically known; be conservative
+ }
+
+ // Function or method declared in this package?
+ if di, ok := c.funcDecls[fn]; ok {
+ c.buildDecl(fn, di)
+ return !di.noReturn
+ }
+
+ // Not declared in this package.
+ // Is there a fact from another package?
+ return !c.pass.ImportObjectFact(fn, new(noReturn))
+}
+
+var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
+
+func hasReachableReturn(g *cfg.CFG) bool {
+ for _, b := range g.Blocks {
+ if b.Live && b.Return() != nil {
+ return true
+ }
+ }
+ return false
+}
+
+// isIntrinsicNoReturn reports whether a function intrinsically never
+// returns because it stops execution of the calling thread.
+// It is the base case in the recursion.
+func isIntrinsicNoReturn(fn *types.Func) bool {
+ // Add functions here as the need arises, but don't allocate memory.
+ path, name := fn.Pkg().Path(), fn.Name()
+ return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
+ path == "runtime" && name == "Goexit"
+}