github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/functions/functions.go (about)

     1  package functions
     2  
     3  import (
     4  	"go/types"
     5  	"sync"
     6  
     7  	"github.com/golangci/go-tools/callgraph"
     8  	"github.com/golangci/go-tools/callgraph/static"
     9  	"github.com/golangci/go-tools/ssa"
    10  	"github.com/golangci/go-tools/staticcheck/vrp"
    11  )
    12  
    13  var stdlibDescs = map[string]Description{
    14  	"errors.New": {Pure: true},
    15  
    16  	"fmt.Errorf":  {Pure: true},
    17  	"fmt.Sprintf": {Pure: true},
    18  	"fmt.Sprint":  {Pure: true},
    19  
    20  	"sort.Reverse": {Pure: true},
    21  
    22  	"strings.Map":            {Pure: true},
    23  	"strings.Repeat":         {Pure: true},
    24  	"strings.Replace":        {Pure: true},
    25  	"strings.Title":          {Pure: true},
    26  	"strings.ToLower":        {Pure: true},
    27  	"strings.ToLowerSpecial": {Pure: true},
    28  	"strings.ToTitle":        {Pure: true},
    29  	"strings.ToTitleSpecial": {Pure: true},
    30  	"strings.ToUpper":        {Pure: true},
    31  	"strings.ToUpperSpecial": {Pure: true},
    32  	"strings.Trim":           {Pure: true},
    33  	"strings.TrimFunc":       {Pure: true},
    34  	"strings.TrimLeft":       {Pure: true},
    35  	"strings.TrimLeftFunc":   {Pure: true},
    36  	"strings.TrimPrefix":     {Pure: true},
    37  	"strings.TrimRight":      {Pure: true},
    38  	"strings.TrimRightFunc":  {Pure: true},
    39  	"strings.TrimSpace":      {Pure: true},
    40  	"strings.TrimSuffix":     {Pure: true},
    41  
    42  	"(*net/http.Request).WithContext": {Pure: true},
    43  
    44  	"math/rand.Read":         {NilError: true},
    45  	"(*math/rand.Rand).Read": {NilError: true},
    46  }
    47  
    48  type Description struct {
    49  	// The function is known to be pure
    50  	Pure bool
    51  	// The function is known to be a stub
    52  	Stub bool
    53  	// The function is known to never return (panics notwithstanding)
    54  	Infinite bool
    55  	// Variable ranges
    56  	Ranges vrp.Ranges
    57  	Loops  []Loop
    58  	// Function returns an error as its last argument, but it is
    59  	// always nil
    60  	NilError            bool
    61  	ConcreteReturnTypes []*types.Tuple
    62  }
    63  
    64  type descriptionEntry struct {
    65  	ready  chan struct{}
    66  	result Description
    67  }
    68  
    69  type Descriptions struct {
    70  	CallGraph *callgraph.Graph
    71  	mu        sync.Mutex
    72  	cache     map[*ssa.Function]*descriptionEntry
    73  }
    74  
    75  func NewDescriptions(prog *ssa.Program) *Descriptions {
    76  	return &Descriptions{
    77  		CallGraph: static.CallGraph(prog),
    78  		cache:     map[*ssa.Function]*descriptionEntry{},
    79  	}
    80  }
    81  
    82  func (d *Descriptions) Get(fn *ssa.Function) Description {
    83  	d.mu.Lock()
    84  	fd := d.cache[fn]
    85  	if fd == nil {
    86  		fd = &descriptionEntry{
    87  			ready: make(chan struct{}),
    88  		}
    89  		d.cache[fn] = fd
    90  		d.mu.Unlock()
    91  
    92  		{
    93  			fd.result = stdlibDescs[fn.RelString(nil)]
    94  			fd.result.Pure = fd.result.Pure || d.IsPure(fn)
    95  			fd.result.Stub = fd.result.Stub || d.IsStub(fn)
    96  			fd.result.Infinite = fd.result.Infinite || !terminates(fn)
    97  			fd.result.Ranges = vrp.BuildGraph(fn).Solve()
    98  			fd.result.Loops = findLoops(fn)
    99  			fd.result.NilError = fd.result.NilError || IsNilError(fn)
   100  			fd.result.ConcreteReturnTypes = concreteReturnTypes(fn)
   101  		}
   102  
   103  		close(fd.ready)
   104  	} else {
   105  		d.mu.Unlock()
   106  		<-fd.ready
   107  	}
   108  	return fd.result
   109  }
   110  
   111  func IsNilError(fn *ssa.Function) bool {
   112  	// TODO(dh): This is very simplistic, as we only look for constant
   113  	// nil returns. A more advanced approach would work transitively.
   114  	// An even more advanced approach would be context-aware and
   115  	// determine nil errors based on inputs (e.g. io.WriteString to a
   116  	// bytes.Buffer will always return nil, but an io.WriteString to
   117  	// an os.File might not). Similarly, an os.File opened for reading
   118  	// won't error on Close, but other files will.
   119  	res := fn.Signature.Results()
   120  	if res.Len() == 0 {
   121  		return false
   122  	}
   123  	last := res.At(res.Len() - 1)
   124  	if types.TypeString(last.Type(), nil) != "error" {
   125  		return false
   126  	}
   127  
   128  	if fn.Blocks == nil {
   129  		return false
   130  	}
   131  	for _, block := range fn.Blocks {
   132  		if len(block.Instrs) == 0 {
   133  			continue
   134  		}
   135  		ins := block.Instrs[len(block.Instrs)-1]
   136  		ret, ok := ins.(*ssa.Return)
   137  		if !ok {
   138  			continue
   139  		}
   140  		v := ret.Results[len(ret.Results)-1]
   141  		c, ok := v.(*ssa.Const)
   142  		if !ok {
   143  			return false
   144  		}
   145  		if !c.IsNil() {
   146  			return false
   147  		}
   148  	}
   149  	return true
   150  }