github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/staticcheck/functions/functions.go (about)

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