276 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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.
 | |
| 
 | |
| // This is a copy of https://github.com/golang/tools/blob/master/go/analysis/passes/nilness/nilness.go
 | |
| // from the commit f0bfdbff1f9c986484a9f02fc198b1efcfe76ebe.
 | |
| // Can't use the original one because of https://github.com/golang/go/issues/29612
 | |
| 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 and degenerate nil
 | |
| pointers. 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.
 | |
| 
 | |
| 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
 | |
| 	}
 | |
| `
 | |
| 
 | |
| 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:
 | |
| 				notNil(stack, instr, instr.X, "type assertion")
 | |
| 			case *ssa.UnOp:
 | |
| 				if instr.Op == token.MUL { // *X
 | |
| 					notNil(stack, instr, instr.X, "load")
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// 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 f fact
 | |
| 				if xnil == isnil {
 | |
| 					// x is nil, y is unknown:
 | |
| 					// t successor learns y is nil.
 | |
| 					f = fact{binop.Y, isnil}
 | |
| 				} else {
 | |
| 					// x is nil, y is unknown:
 | |
| 					// t successor learns x is nil.
 | |
| 					f = 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, f)
 | |
| 						} else if d == fsucc {
 | |
| 							s = append(s, f.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 {
 | |
| 	// 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
 | |
| }
 | 
