--- /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 nilness inspects the control-flow graph of an SSA function
+// and reports errors such as nil pointer dereferences and degenerate
+// nil pointer comparisons.
+package nilness
+
+import (
+ "fmt"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/buildssa"
+ "golang.org/x/tools/go/ssa"
+)
+
+const Doc = `check for redundant or impossible nil comparisons
+
+The nilness checker inspects the control-flow graph of each function in
+a package and reports nil pointer dereferences, degenerate nil
+pointers, and panics with nil values. A degenerate comparison is of the form
+x==nil or x!=nil where x is statically known to be nil or non-nil. These are
+often a mistake, especially in control flow related to errors. Panics with nil
+values are checked because they are not detectable by
+
+ if r := recover(); r != nil {
+
+This check reports conditions such as:
+
+ if f == nil { // impossible condition (f is a function)
+ }
+
+and:
+
+ p := &v
+ ...
+ if p != nil { // tautological condition
+ }
+
+and:
+
+ if p == nil {
+ print(*p) // nil dereference
+ }
+
+and:
+
+ if p == nil {
+ panic(p)
+ }
+`
+
+var Analyzer = &analysis.Analyzer{
+ Name: "nilness",
+ Doc: Doc,
+ Run: run,
+ Requires: []*analysis.Analyzer{buildssa.Analyzer},
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
+ for _, fn := range ssainput.SrcFuncs {
+ runFunc(pass, fn)
+ }
+ return nil, nil
+}
+
+func runFunc(pass *analysis.Pass, fn *ssa.Function) {
+ reportf := func(category string, pos token.Pos, format string, args ...interface{}) {
+ pass.Report(analysis.Diagnostic{
+ Pos: pos,
+ Category: category,
+ Message: fmt.Sprintf(format, args...),
+ })
+ }
+
+ // notNil reports an error if v is provably nil.
+ notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
+ if nilnessOf(stack, v) == isnil {
+ reportf("nilderef", instr.Pos(), "nil dereference in "+descr)
+ }
+ }
+
+ // visit visits reachable blocks of the CFG in dominance order,
+ // maintaining a stack of dominating nilness facts.
+ //
+ // By traversing the dom tree, we can pop facts off the stack as
+ // soon as we've visited a subtree. Had we traversed the CFG,
+ // we would need to retain the set of facts for each block.
+ seen := make([]bool, len(fn.Blocks)) // seen[i] means visit should ignore block i
+ var visit func(b *ssa.BasicBlock, stack []fact)
+ visit = func(b *ssa.BasicBlock, stack []fact) {
+ if seen[b.Index] {
+ return
+ }
+ seen[b.Index] = true
+
+ // Report nil dereferences.
+ for _, instr := range b.Instrs {
+ switch instr := instr.(type) {
+ case ssa.CallInstruction:
+ notNil(stack, instr, instr.Common().Value,
+ instr.Common().Description())
+ case *ssa.FieldAddr:
+ notNil(stack, instr, instr.X, "field selection")
+ case *ssa.IndexAddr:
+ notNil(stack, instr, instr.X, "index operation")
+ case *ssa.MapUpdate:
+ notNil(stack, instr, instr.Map, "map update")
+ case *ssa.Slice:
+ // A nilcheck occurs in ptr[:] iff ptr is a pointer to an array.
+ if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
+ notNil(stack, instr, instr.X, "slice operation")
+ }
+ case *ssa.Store:
+ notNil(stack, instr, instr.Addr, "store")
+ case *ssa.TypeAssert:
+ if !instr.CommaOk {
+ notNil(stack, instr, instr.X, "type assertion")
+ }
+ case *ssa.UnOp:
+ if instr.Op == token.MUL { // *X
+ notNil(stack, instr, instr.X, "load")
+ }
+ }
+ }
+
+ // Look for panics with nil value
+ for _, instr := range b.Instrs {
+ switch instr := instr.(type) {
+ case *ssa.Panic:
+ if nilnessOf(stack, instr.X) == isnil {
+ reportf("nilpanic", instr.Pos(), "panic with nil value")
+ }
+ }
+ }
+
+ // For nil comparison blocks, report an error if the condition
+ // is degenerate, and push a nilness fact on the stack when
+ // visiting its true and false successor blocks.
+ if binop, tsucc, fsucc := eq(b); binop != nil {
+ xnil := nilnessOf(stack, binop.X)
+ ynil := nilnessOf(stack, binop.Y)
+
+ if ynil != unknown && xnil != unknown && (xnil == isnil || ynil == isnil) {
+ // Degenerate condition:
+ // the nilness of both operands is known,
+ // and at least one of them is nil.
+ var adj string
+ if (xnil == ynil) == (binop.Op == token.EQL) {
+ adj = "tautological"
+ } else {
+ adj = "impossible"
+ }
+ reportf("cond", binop.Pos(), "%s condition: %s %s %s", adj, xnil, binop.Op, ynil)
+
+ // If tsucc's or fsucc's sole incoming edge is impossible,
+ // it is unreachable. Prune traversal of it and
+ // all the blocks it dominates.
+ // (We could be more precise with full dataflow
+ // analysis of control-flow joins.)
+ var skip *ssa.BasicBlock
+ if xnil == ynil {
+ skip = fsucc
+ } else {
+ skip = tsucc
+ }
+ for _, d := range b.Dominees() {
+ if d == skip && len(d.Preds) == 1 {
+ continue
+ }
+ visit(d, stack)
+ }
+ return
+ }
+
+ // "if x == nil" or "if nil == y" condition; x, y are unknown.
+ if xnil == isnil || ynil == isnil {
+ var newFacts facts
+ if xnil == isnil {
+ // x is nil, y is unknown:
+ // t successor learns y is nil.
+ newFacts = expandFacts(fact{binop.Y, isnil})
+ } else {
+ // x is nil, y is unknown:
+ // t successor learns x is nil.
+ newFacts = expandFacts(fact{binop.X, isnil})
+ }
+
+ for _, d := range b.Dominees() {
+ // Successor blocks learn a fact
+ // only at non-critical edges.
+ // (We could do be more precise with full dataflow
+ // analysis of control-flow joins.)
+ s := stack
+ if len(d.Preds) == 1 {
+ if d == tsucc {
+ s = append(s, newFacts...)
+ } else if d == fsucc {
+ s = append(s, newFacts.negate()...)
+ }
+ }
+ visit(d, s)
+ }
+ return
+ }
+ }
+
+ for _, d := range b.Dominees() {
+ visit(d, stack)
+ }
+ }
+
+ // Visit the entry block. No need to visit fn.Recover.
+ if fn.Blocks != nil {
+ visit(fn.Blocks[0], make([]fact, 0, 20)) // 20 is plenty
+ }
+}
+
+// A fact records that a block is dominated
+// by the condition v == nil or v != nil.
+type fact struct {
+ value ssa.Value
+ nilness nilness
+}
+
+func (f fact) negate() fact { return fact{f.value, -f.nilness} }
+
+type nilness int
+
+const (
+ isnonnil = -1
+ unknown nilness = 0
+ isnil = 1
+)
+
+var nilnessStrings = []string{"non-nil", "unknown", "nil"}
+
+func (n nilness) String() string { return nilnessStrings[n+1] }
+
+// nilnessOf reports whether v is definitely nil, definitely not nil,
+// or unknown given the dominating stack of facts.
+func nilnessOf(stack []fact, v ssa.Value) nilness {
+ switch v := v.(type) {
+ // unwrap ChangeInterface values recursively, to detect if underlying
+ // values have any facts recorded or are otherwise known with regard to nilness.
+ //
+ // This work must be in addition to expanding facts about
+ // ChangeInterfaces during inference/fact gathering because this covers
+ // cases where the nilness of a value is intrinsic, rather than based
+ // on inferred facts, such as a zero value interface variable. That
+ // said, this work alone would only inform us when facts are about
+ // underlying values, rather than outer values, when the analysis is
+ // transitive in both directions.
+ case *ssa.ChangeInterface:
+ if underlying := nilnessOf(stack, v.X); underlying != unknown {
+ return underlying
+ }
+ }
+
+ // Is value intrinsically nil or non-nil?
+ switch v := v.(type) {
+ case *ssa.Alloc,
+ *ssa.FieldAddr,
+ *ssa.FreeVar,
+ *ssa.Function,
+ *ssa.Global,
+ *ssa.IndexAddr,
+ *ssa.MakeChan,
+ *ssa.MakeClosure,
+ *ssa.MakeInterface,
+ *ssa.MakeMap,
+ *ssa.MakeSlice:
+ return isnonnil
+ case *ssa.Const:
+ if v.IsNil() {
+ return isnil
+ } else {
+ return isnonnil
+ }
+ }
+
+ // Search dominating control-flow facts.
+ for _, f := range stack {
+ if f.value == v {
+ return f.nilness
+ }
+ }
+ return unknown
+}
+
+// If b ends with an equality comparison, eq returns the operation and
+// its true (equal) and false (not equal) successors.
+func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {
+ if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
+ if binop, ok := If.Cond.(*ssa.BinOp); ok {
+ switch binop.Op {
+ case token.EQL:
+ return binop, b.Succs[0], b.Succs[1]
+ case token.NEQ:
+ return binop, b.Succs[1], b.Succs[0]
+ }
+ }
+ }
+ return nil, nil, nil
+}
+
+// expandFacts takes a single fact and returns the set of facts that can be
+// known about it or any of its related values. Some operations, like
+// ChangeInterface, have transitive nilness, such that if you know the
+// underlying value is nil, you also know the value itself is nil, and vice
+// versa. This operation allows callers to match on any of the related values
+// in analyses, rather than just the one form of the value that happend to
+// appear in a comparison.
+//
+// This work must be in addition to unwrapping values within nilnessOf because
+// while this work helps give facts about transitively known values based on
+// inferred facts, the recursive check within nilnessOf covers cases where
+// nilness facts are intrinsic to the underlying value, such as a zero value
+// interface variables.
+//
+// ChangeInterface is the only expansion currently supported, but others, like
+// Slice, could be added. At this time, this tool does not check slice
+// operations in a way this expansion could help. See
+// https://play.golang.org/p/mGqXEp7w4fR for an example.
+func expandFacts(f fact) []fact {
+ ff := []fact{f}
+
+Loop:
+ for {
+ switch v := f.value.(type) {
+ case *ssa.ChangeInterface:
+ f = fact{v.X, f.nilness}
+ ff = append(ff, f)
+ default:
+ break Loop
+ }
+ }
+
+ return ff
+}
+
+type facts []fact
+
+func (ff facts) negate() facts {
+ nn := make([]fact, len(ff))
+ for i, f := range ff {
+ nn[i] = f.negate()
+ }
+ return nn
+}