1
0

Add labels to tasks (#45)

This commit is contained in:
konrad
2018-12-31 01:18:41 +00:00
committed by Gitea
parent d39007baa0
commit 6b40df50d3
45 changed files with 9101 additions and 57 deletions

View File

@ -0,0 +1,129 @@
// Copyright 2013 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 callgraph defines the call graph and various algorithms
and utilities to operate on it.
A call graph is a labelled directed graph whose nodes represent
functions and whose edge labels represent syntactic function call
sites. The presence of a labelled edge (caller, site, callee)
indicates that caller may call callee at the specified call site.
A call graph is a multigraph: it may contain multiple edges (caller,
*, callee) connecting the same pair of nodes, so long as the edges
differ by label; this occurs when one function calls another function
from multiple call sites. Also, it may contain multiple edges
(caller, site, *) that differ only by callee; this indicates a
polymorphic call.
A SOUND call graph is one that overapproximates the dynamic calling
behaviors of the program in all possible executions. One call graph
is more PRECISE than another if it is a smaller overapproximation of
the dynamic behavior.
All call graphs have a synthetic root node which is responsible for
calling main() and init().
Calls to built-in functions (e.g. panic, println) are not represented
in the call graph; they are treated like built-in operators of the
language.
*/
package callgraph // import "honnef.co/go/tools/callgraph"
// TODO(adonovan): add a function to eliminate wrappers from the
// callgraph, preserving topology.
// More generally, we could eliminate "uninteresting" nodes such as
// nodes from packages we don't care about.
import (
"fmt"
"go/token"
"honnef.co/go/tools/ssa"
)
// A Graph represents a call graph.
//
// A graph may contain nodes that are not reachable from the root.
// If the call graph is sound, such nodes indicate unreachable
// functions.
//
type Graph struct {
Root *Node // the distinguished root node
Nodes map[*ssa.Function]*Node // all nodes by function
}
// New returns a new Graph with the specified root node.
func New(root *ssa.Function) *Graph {
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
g.Root = g.CreateNode(root)
return g
}
// CreateNode returns the Node for fn, creating it if not present.
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
n, ok := g.Nodes[fn]
if !ok {
n = &Node{Func: fn, ID: len(g.Nodes)}
g.Nodes[fn] = n
}
return n
}
// A Node represents a node in a call graph.
type Node struct {
Func *ssa.Function // the function this node represents
ID int // 0-based sequence number
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
}
func (n *Node) String() string {
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
}
// A Edge represents an edge in the call graph.
//
// Site is nil for edges originating in synthetic or intrinsic
// functions, e.g. reflect.Call or the root of the call graph.
type Edge struct {
Caller *Node
Site ssa.CallInstruction
Callee *Node
}
func (e Edge) String() string {
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
}
func (e Edge) Description() string {
var prefix string
switch e.Site.(type) {
case nil:
return "synthetic call"
case *ssa.Go:
prefix = "concurrent "
case *ssa.Defer:
prefix = "deferred "
}
return prefix + e.Site.Common().Description()
}
func (e Edge) Pos() token.Pos {
if e.Site == nil {
return token.NoPos
}
return e.Site.Pos()
}
// AddEdge adds the edge (caller, site, callee) to the call graph.
// Elimination of duplicate edges is the caller's responsibility.
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
e := &Edge{caller, site, callee}
callee.In = append(callee.In, e)
caller.Out = append(caller.Out, e)
}

View File

@ -0,0 +1,35 @@
// Package static computes the call graph of a Go program containing
// only static call edges.
package static // import "honnef.co/go/tools/callgraph/static"
import (
"honnef.co/go/tools/callgraph"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/ssa/ssautil"
)
// CallGraph computes the call graph of the specified program
// considering only static calls.
//
func CallGraph(prog *ssa.Program) *callgraph.Graph {
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
// TODO(adonovan): opt: use only a single pass over the ssa.Program.
// TODO(adonovan): opt: this is slower than RTA (perhaps because
// the lower precision means so many edges are allocated)!
for f := range ssautil.AllFunctions(prog) {
fnode := cg.CreateNode(f)
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ssa.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
gnode := cg.CreateNode(g)
callgraph.AddEdge(fnode, site, gnode)
}
}
}
}
}
return cg
}

View File

@ -0,0 +1,181 @@
// Copyright 2013 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 callgraph
import "honnef.co/go/tools/ssa"
// This file provides various utilities over call graphs, such as
// visitation and path search.
// CalleesOf returns a new set containing all direct callees of the
// caller node.
//
func CalleesOf(caller *Node) map[*Node]bool {
callees := make(map[*Node]bool)
for _, e := range caller.Out {
callees[e.Callee] = true
}
return callees
}
// GraphVisitEdges visits all the edges in graph g in depth-first order.
// The edge function is called for each edge in postorder. If it
// returns non-nil, visitation stops and GraphVisitEdges returns that
// value.
//
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
seen := make(map[*Node]bool)
var visit func(n *Node) error
visit = func(n *Node) error {
if !seen[n] {
seen[n] = true
for _, e := range n.Out {
if err := visit(e.Callee); err != nil {
return err
}
if err := edge(e); err != nil {
return err
}
}
}
return nil
}
for _, n := range g.Nodes {
if err := visit(n); err != nil {
return err
}
}
return nil
}
// PathSearch finds an arbitrary path starting at node start and
// ending at some node for which isEnd() returns true. On success,
// PathSearch returns the path as an ordered list of edges; on
// failure, it returns nil.
//
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
stack := make([]*Edge, 0, 32)
seen := make(map[*Node]bool)
var search func(n *Node) []*Edge
search = func(n *Node) []*Edge {
if !seen[n] {
seen[n] = true
if isEnd(n) {
return stack
}
for _, e := range n.Out {
stack = append(stack, e) // push
if found := search(e.Callee); found != nil {
return found
}
stack = stack[:len(stack)-1] // pop
}
}
return nil
}
return search(start)
}
// DeleteSyntheticNodes removes from call graph g all nodes for
// synthetic functions (except g.Root and package initializers),
// preserving the topology. In effect, calls to synthetic wrappers
// are "inlined".
//
func (g *Graph) DeleteSyntheticNodes() {
// Measurements on the standard library and go.tools show that
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
// than the input.
//
// Inlining a wrapper of in-degree m, out-degree n adds m*n
// and removes m+n edges. Since most wrappers are monomorphic
// (n=1) this results in a slight reduction. Polymorphic
// wrappers (n>1), e.g. from embedding an interface value
// inside a struct to satisfy some interface, cause an
// increase in the graph, but they seem to be uncommon.
// Hash all existing edges to avoid creating duplicates.
edges := make(map[Edge]bool)
for _, cgn := range g.Nodes {
for _, e := range cgn.Out {
edges[*e] = true
}
}
for fn, cgn := range g.Nodes {
if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) {
continue // keep
}
for _, eIn := range cgn.In {
for _, eOut := range cgn.Out {
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
if edges[newEdge] {
continue // don't add duplicate
}
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
edges[newEdge] = true
}
}
g.DeleteNode(cgn)
}
}
func isInit(fn *ssa.Function) bool {
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
}
// DeleteNode removes node n and its edges from the graph g.
// (NB: not efficient for batch deletion.)
func (g *Graph) DeleteNode(n *Node) {
n.deleteIns()
n.deleteOuts()
delete(g.Nodes, n.Func)
}
// deleteIns deletes all incoming edges to n.
func (n *Node) deleteIns() {
for _, e := range n.In {
removeOutEdge(e)
}
n.In = nil
}
// deleteOuts deletes all outgoing edges from n.
func (n *Node) deleteOuts() {
for _, e := range n.Out {
removeInEdge(e)
}
n.Out = nil
}
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
func removeOutEdge(edge *Edge) {
caller := edge.Caller
n := len(caller.Out)
for i, e := range caller.Out {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.Out[i] = caller.Out[n-1]
caller.Out[n-1] = nil // aid GC
caller.Out = caller.Out[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}
// removeInEdge removes edge.Callee's incoming edge 'edge'.
func removeInEdge(edge *Edge) {
caller := edge.Callee
n := len(caller.In)
for i, e := range caller.In {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.In[i] = caller.In[n-1]
caller.In[n-1] = nil // aid GC
caller.In = caller.In[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}

View File

@ -0,0 +1,16 @@
# staticcheck
_staticcheck_ is `go vet` on steroids, applying a ton of static analysis
checks you might be used to from tools like ReSharper for C#.
## Installation
Staticcheck requires Go 1.6 or later.
go get honnef.co/go/tools/cmd/staticcheck
## Documentation
Detailed documentation can be found on
[staticcheck.io](https://staticcheck.io/docs/staticcheck).

View File

@ -0,0 +1,23 @@
// staticcheck detects a myriad of bugs and inefficiencies in your
// code.
package main // import "honnef.co/go/tools/cmd/staticcheck"
import (
"os"
"honnef.co/go/tools/lint/lintutil"
"honnef.co/go/tools/staticcheck"
)
func main() {
fs := lintutil.FlagSet("staticcheck")
gen := fs.Bool("generated", false, "Check generated code")
fs.Parse(os.Args[1:])
c := staticcheck.NewChecker()
c.CheckGenerated = *gen
cfg := lintutil.CheckerConfig{
Checker: c,
ExitNonZero: true,
}
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
}

View File

@ -0,0 +1,54 @@
package deprecated
type Deprecation struct {
DeprecatedSince int
AlternativeAvailableSince int
}
var Stdlib = map[string]Deprecation{
"image/jpeg.Reader": {4, 0},
// FIXME(dh): AllowBinary isn't being detected as deprecated
// because the comment has a newline right after "Deprecated:"
"go/build.AllowBinary": {7, 7},
"(archive/zip.FileHeader).CompressedSize": {1, 1},
"(archive/zip.FileHeader).UncompressedSize": {1, 1},
"(go/doc.Package).Bugs": {1, 1},
"os.SEEK_SET": {7, 7},
"os.SEEK_CUR": {7, 7},
"os.SEEK_END": {7, 7},
"(net.Dialer).Cancel": {7, 7},
"runtime.CPUProfile": {9, 0},
"compress/flate.ReadError": {6, 6},
"compress/flate.WriteError": {6, 6},
"path/filepath.HasPrefix": {0, 0},
"(net/http.Transport).Dial": {7, 7},
"(*net/http.Transport).CancelRequest": {6, 5},
"net/http.ErrWriteAfterFlush": {7, 0},
"net/http.ErrHeaderTooLong": {8, 0},
"net/http.ErrShortBody": {8, 0},
"net/http.ErrMissingContentLength": {8, 0},
"net/http/httputil.ErrPersistEOF": {0, 0},
"net/http/httputil.ErrClosed": {0, 0},
"net/http/httputil.ErrPipeline": {0, 0},
"net/http/httputil.ServerConn": {0, 0},
"net/http/httputil.NewServerConn": {0, 0},
"net/http/httputil.ClientConn": {0, 0},
"net/http/httputil.NewClientConn": {0, 0},
"net/http/httputil.NewProxyClientConn": {0, 0},
"(net/http.Request).Cancel": {7, 7},
"(text/template/parse.PipeNode).Line": {1, 1},
"(text/template/parse.ActionNode).Line": {1, 1},
"(text/template/parse.BranchNode).Line": {1, 1},
"(text/template/parse.TemplateNode).Line": {1, 1},
"database/sql/driver.ColumnConverter": {9, 9},
"database/sql/driver.Execer": {8, 8},
"database/sql/driver.Queryer": {8, 8},
"(database/sql/driver.Conn).Begin": {8, 8},
"(database/sql/driver.Stmt).Exec": {8, 8},
"(database/sql/driver.Stmt).Query": {8, 8},
"syscall.StringByteSlice": {1, 1},
"syscall.StringBytePtr": {1, 1},
"syscall.StringSlicePtr": {1, 1},
"syscall.StringToUTF16": {1, 1},
"syscall.StringToUTF16Ptr": {1, 1},
}

View File

@ -0,0 +1,56 @@
package functions
import (
"go/token"
"go/types"
"honnef.co/go/tools/ssa"
)
func concreteReturnTypes(fn *ssa.Function) []*types.Tuple {
res := fn.Signature.Results()
if res == nil {
return nil
}
ifaces := make([]bool, res.Len())
any := false
for i := 0; i < res.Len(); i++ {
_, ifaces[i] = res.At(i).Type().Underlying().(*types.Interface)
any = any || ifaces[i]
}
if !any {
return []*types.Tuple{res}
}
var out []*types.Tuple
for _, block := range fn.Blocks {
if len(block.Instrs) == 0 {
continue
}
ret, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return)
if !ok {
continue
}
vars := make([]*types.Var, res.Len())
for i, v := range ret.Results {
var typ types.Type
if !ifaces[i] {
typ = res.At(i).Type()
} else if mi, ok := v.(*ssa.MakeInterface); ok {
// TODO(dh): if mi.X is a function call that returns
// an interface, call concreteReturnTypes on that
// function (or, really, go through Descriptions,
// avoid infinite recursion etc, just like nil error
// detection)
// TODO(dh): support Phi nodes
typ = mi.X.Type()
} else {
typ = res.At(i).Type()
}
vars[i] = types.NewParam(token.NoPos, nil, "", typ)
}
out = append(out, types.NewTuple(vars...))
}
// TODO(dh): deduplicate out
return out
}

View File

@ -0,0 +1,150 @@
package functions
import (
"go/types"
"sync"
"honnef.co/go/tools/callgraph"
"honnef.co/go/tools/callgraph/static"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/staticcheck/vrp"
)
var stdlibDescs = map[string]Description{
"errors.New": {Pure: true},
"fmt.Errorf": {Pure: true},
"fmt.Sprintf": {Pure: true},
"fmt.Sprint": {Pure: true},
"sort.Reverse": {Pure: true},
"strings.Map": {Pure: true},
"strings.Repeat": {Pure: true},
"strings.Replace": {Pure: true},
"strings.Title": {Pure: true},
"strings.ToLower": {Pure: true},
"strings.ToLowerSpecial": {Pure: true},
"strings.ToTitle": {Pure: true},
"strings.ToTitleSpecial": {Pure: true},
"strings.ToUpper": {Pure: true},
"strings.ToUpperSpecial": {Pure: true},
"strings.Trim": {Pure: true},
"strings.TrimFunc": {Pure: true},
"strings.TrimLeft": {Pure: true},
"strings.TrimLeftFunc": {Pure: true},
"strings.TrimPrefix": {Pure: true},
"strings.TrimRight": {Pure: true},
"strings.TrimRightFunc": {Pure: true},
"strings.TrimSpace": {Pure: true},
"strings.TrimSuffix": {Pure: true},
"(*net/http.Request).WithContext": {Pure: true},
"math/rand.Read": {NilError: true},
"(*math/rand.Rand).Read": {NilError: true},
}
type Description struct {
// The function is known to be pure
Pure bool
// The function is known to be a stub
Stub bool
// The function is known to never return (panics notwithstanding)
Infinite bool
// Variable ranges
Ranges vrp.Ranges
Loops []Loop
// Function returns an error as its last argument, but it is
// always nil
NilError bool
ConcreteReturnTypes []*types.Tuple
}
type descriptionEntry struct {
ready chan struct{}
result Description
}
type Descriptions struct {
CallGraph *callgraph.Graph
mu sync.Mutex
cache map[*ssa.Function]*descriptionEntry
}
func NewDescriptions(prog *ssa.Program) *Descriptions {
return &Descriptions{
CallGraph: static.CallGraph(prog),
cache: map[*ssa.Function]*descriptionEntry{},
}
}
func (d *Descriptions) Get(fn *ssa.Function) Description {
d.mu.Lock()
fd := d.cache[fn]
if fd == nil {
fd = &descriptionEntry{
ready: make(chan struct{}),
}
d.cache[fn] = fd
d.mu.Unlock()
{
fd.result = stdlibDescs[fn.RelString(nil)]
fd.result.Pure = fd.result.Pure || d.IsPure(fn)
fd.result.Stub = fd.result.Stub || d.IsStub(fn)
fd.result.Infinite = fd.result.Infinite || !terminates(fn)
fd.result.Ranges = vrp.BuildGraph(fn).Solve()
fd.result.Loops = findLoops(fn)
fd.result.NilError = fd.result.NilError || IsNilError(fn)
fd.result.ConcreteReturnTypes = concreteReturnTypes(fn)
}
close(fd.ready)
} else {
d.mu.Unlock()
<-fd.ready
}
return fd.result
}
func IsNilError(fn *ssa.Function) bool {
// TODO(dh): This is very simplistic, as we only look for constant
// nil returns. A more advanced approach would work transitively.
// An even more advanced approach would be context-aware and
// determine nil errors based on inputs (e.g. io.WriteString to a
// bytes.Buffer will always return nil, but an io.WriteString to
// an os.File might not). Similarly, an os.File opened for reading
// won't error on Close, but other files will.
res := fn.Signature.Results()
if res.Len() == 0 {
return false
}
last := res.At(res.Len() - 1)
if types.TypeString(last.Type(), nil) != "error" {
return false
}
if fn.Blocks == nil {
return false
}
for _, block := range fn.Blocks {
if len(block.Instrs) == 0 {
continue
}
ins := block.Instrs[len(block.Instrs)-1]
ret, ok := ins.(*ssa.Return)
if !ok {
continue
}
v := ret.Results[len(ret.Results)-1]
c, ok := v.(*ssa.Const)
if !ok {
return false
}
if !c.IsNil() {
return false
}
}
return true
}

View File

@ -0,0 +1,50 @@
package functions
import "honnef.co/go/tools/ssa"
type Loop map[*ssa.BasicBlock]bool
func findLoops(fn *ssa.Function) []Loop {
if fn.Blocks == nil {
return nil
}
tree := fn.DomPreorder()
var sets []Loop
for _, h := range tree {
for _, n := range h.Preds {
if !h.Dominates(n) {
continue
}
// n is a back-edge to h
// h is the loop header
if n == h {
sets = append(sets, Loop{n: true})
continue
}
set := Loop{h: true, n: true}
for _, b := range allPredsBut(n, h, nil) {
set[b] = true
}
sets = append(sets, set)
}
}
return sets
}
func allPredsBut(b, but *ssa.BasicBlock, list []*ssa.BasicBlock) []*ssa.BasicBlock {
outer:
for _, pred := range b.Preds {
if pred == but {
continue
}
for _, p := range list {
// TODO improve big-o complexity of this function
if pred == p {
continue outer
}
}
list = append(list, pred)
list = allPredsBut(pred, but, list)
}
return list
}

View File

@ -0,0 +1,123 @@
package functions
import (
"go/token"
"go/types"
"honnef.co/go/tools/callgraph"
"honnef.co/go/tools/lint/lintdsl"
"honnef.co/go/tools/ssa"
)
// IsStub reports whether a function is a stub. A function is
// considered a stub if it has no instructions or exactly one
// instruction, which must be either returning only constant values or
// a panic.
func (d *Descriptions) IsStub(fn *ssa.Function) bool {
if len(fn.Blocks) == 0 {
return true
}
if len(fn.Blocks) > 1 {
return false
}
instrs := lintdsl.FilterDebug(fn.Blocks[0].Instrs)
if len(instrs) != 1 {
return false
}
switch instrs[0].(type) {
case *ssa.Return:
// Since this is the only instruction, the return value must
// be a constant. We consider all constants as stubs, not just
// the zero value. This does not, unfortunately, cover zero
// initialised structs, as these cause additional
// instructions.
return true
case *ssa.Panic:
return true
default:
return false
}
}
func (d *Descriptions) IsPure(fn *ssa.Function) bool {
if fn.Signature.Results().Len() == 0 {
// A function with no return values is empty or is doing some
// work we cannot see (for example because of build tags);
// don't consider it pure.
return false
}
for _, param := range fn.Params {
if _, ok := param.Type().Underlying().(*types.Basic); !ok {
return false
}
}
if fn.Blocks == nil {
return false
}
checkCall := func(common *ssa.CallCommon) bool {
if common.IsInvoke() {
return false
}
builtin, ok := common.Value.(*ssa.Builtin)
if !ok {
if common.StaticCallee() != fn {
if common.StaticCallee() == nil {
return false
}
// TODO(dh): ideally, IsPure wouldn't be responsible
// for avoiding infinite recursion, but
// FunctionDescriptions would be.
node := d.CallGraph.CreateNode(common.StaticCallee())
if callgraph.PathSearch(node, func(other *callgraph.Node) bool {
return other.Func == fn
}) != nil {
return false
}
if !d.Get(common.StaticCallee()).Pure {
return false
}
}
} else {
switch builtin.Name() {
case "len", "cap", "make", "new":
default:
return false
}
}
return true
}
for _, b := range fn.Blocks {
for _, ins := range b.Instrs {
switch ins := ins.(type) {
case *ssa.Call:
if !checkCall(ins.Common()) {
return false
}
case *ssa.Defer:
if !checkCall(&ins.Call) {
return false
}
case *ssa.Select:
return false
case *ssa.Send:
return false
case *ssa.Go:
return false
case *ssa.Panic:
return false
case *ssa.Store:
return false
case *ssa.FieldAddr:
return false
case *ssa.UnOp:
if ins.Op == token.MUL || ins.Op == token.AND {
return false
}
}
}
}
return true
}

View File

@ -0,0 +1,24 @@
package functions
import "honnef.co/go/tools/ssa"
// terminates reports whether fn is supposed to return, that is if it
// has at least one theoretic path that returns from the function.
// Explicit panics do not count as terminating.
func terminates(fn *ssa.Function) bool {
if fn.Blocks == nil {
// assuming that a function terminates is the conservative
// choice
return true
}
for _, block := range fn.Blocks {
if len(block.Instrs) == 0 {
continue
}
if _, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return); ok {
return true
}
}
return false
}

View File

@ -0,0 +1,15 @@
# Contributing to staticcheck
## Before filing an issue:
### Are you having trouble building staticcheck?
Check you have the latest version of its dependencies. Run
```
go get -u honnef.co/go/tools/staticcheck
```
If you still have problems, consider searching for existing issues before filing a new issue.
## Before sending a pull request:
Have you understood the purpose of staticcheck? Make sure to carefully read `README`.

View File

@ -0,0 +1,21 @@
package staticcheck
import (
"go/ast"
"strings"
. "honnef.co/go/tools/lint/lintdsl"
)
func buildTags(f *ast.File) [][]string {
var out [][]string
for _, line := range strings.Split(Preamble(f), "\n") {
if !strings.HasPrefix(line, "+build ") {
continue
}
line = strings.TrimSpace(strings.TrimPrefix(line, "+build "))
fields := strings.Fields(line)
out = append(out, fields)
}
return out
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,322 @@
package staticcheck
import (
"fmt"
"go/constant"
"go/types"
"net"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"honnef.co/go/tools/lint"
. "honnef.co/go/tools/lint/lintdsl"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/staticcheck/vrp"
)
const (
MsgInvalidHostPort = "invalid port or service name in host:port pair"
MsgInvalidUTF8 = "argument is not a valid UTF-8 encoded string"
MsgNonUniqueCutset = "cutset contains duplicate characters"
)
type Call struct {
Job *lint.Job
Instr ssa.CallInstruction
Args []*Argument
Checker *Checker
Parent *ssa.Function
invalids []string
}
func (c *Call) Invalid(msg string) {
c.invalids = append(c.invalids, msg)
}
type Argument struct {
Value Value
invalids []string
}
func (arg *Argument) Invalid(msg string) {
arg.invalids = append(arg.invalids, msg)
}
type Value struct {
Value ssa.Value
Range vrp.Range
}
type CallCheck func(call *Call)
func extractConsts(v ssa.Value) []*ssa.Const {
switch v := v.(type) {
case *ssa.Const:
return []*ssa.Const{v}
case *ssa.MakeInterface:
return extractConsts(v.X)
default:
return nil
}
}
func ValidateRegexp(v Value) error {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
continue
}
if c.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(c.Value)
if _, err := regexp.Compile(s); err != nil {
return err
}
}
return nil
}
func ValidateTimeLayout(v Value) error {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
continue
}
if c.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(c.Value)
s = strings.Replace(s, "_", " ", -1)
s = strings.Replace(s, "Z", "-", -1)
_, err := time.Parse(s, s)
if err != nil {
return err
}
}
return nil
}
func ValidateURL(v Value) error {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
continue
}
if c.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(c.Value)
_, err := url.Parse(s)
if err != nil {
return fmt.Errorf("%q is not a valid URL: %s", s, err)
}
}
return nil
}
func IntValue(v Value, z vrp.Z) bool {
r, ok := v.Range.(vrp.IntInterval)
if !ok || !r.IsKnown() {
return false
}
if r.Lower != r.Upper {
return false
}
if r.Lower.Cmp(z) == 0 {
return true
}
return false
}
func InvalidUTF8(v Value) bool {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
continue
}
if c.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(c.Value)
if !utf8.ValidString(s) {
return true
}
}
return false
}
func UnbufferedChannel(v Value) bool {
r, ok := v.Range.(vrp.ChannelInterval)
if !ok || !r.IsKnown() {
return false
}
if r.Size.Lower.Cmp(vrp.NewZ(0)) == 0 &&
r.Size.Upper.Cmp(vrp.NewZ(0)) == 0 {
return true
}
return false
}
func Pointer(v Value) bool {
switch v.Value.Type().Underlying().(type) {
case *types.Pointer, *types.Interface:
return true
}
return false
}
func ConvertedFromInt(v Value) bool {
conv, ok := v.Value.(*ssa.Convert)
if !ok {
return false
}
b, ok := conv.X.Type().Underlying().(*types.Basic)
if !ok {
return false
}
if (b.Info() & types.IsInteger) == 0 {
return false
}
return true
}
func validEncodingBinaryType(j *lint.Job, typ types.Type) bool {
typ = typ.Underlying()
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Uint8, types.Uint16, types.Uint32, types.Uint64,
types.Int8, types.Int16, types.Int32, types.Int64,
types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid:
return true
case types.Bool:
return IsGoVersion(j, 8)
}
return false
case *types.Struct:
n := typ.NumFields()
for i := 0; i < n; i++ {
if !validEncodingBinaryType(j, typ.Field(i).Type()) {
return false
}
}
return true
case *types.Array:
return validEncodingBinaryType(j, typ.Elem())
case *types.Interface:
// we can't determine if it's a valid type or not
return true
}
return false
}
func CanBinaryMarshal(j *lint.Job, v Value) bool {
typ := v.Value.Type().Underlying()
if ttyp, ok := typ.(*types.Pointer); ok {
typ = ttyp.Elem().Underlying()
}
if ttyp, ok := typ.(interface {
Elem() types.Type
}); ok {
if _, ok := ttyp.(*types.Pointer); !ok {
typ = ttyp.Elem()
}
}
return validEncodingBinaryType(j, typ)
}
func RepeatZeroTimes(name string, arg int) CallCheck {
return func(call *Call) {
arg := call.Args[arg]
if IntValue(arg.Value, vrp.NewZ(0)) {
arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name))
}
}
}
func validateServiceName(s string) bool {
if len(s) < 1 || len(s) > 15 {
return false
}
if s[0] == '-' || s[len(s)-1] == '-' {
return false
}
if strings.Contains(s, "--") {
return false
}
hasLetter := false
for _, r := range s {
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
hasLetter = true
continue
}
if r >= '0' && r <= '9' {
continue
}
return false
}
return hasLetter
}
func validatePort(s string) bool {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return validateServiceName(s)
}
return n >= 0 && n <= 65535
}
func ValidHostPort(v Value) bool {
for _, k := range extractConsts(v.Value) {
if k.Value == nil {
continue
}
if k.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(k.Value)
_, port, err := net.SplitHostPort(s)
if err != nil {
return false
}
// TODO(dh): check hostname
if !validatePort(port) {
return false
}
}
return true
}
// ConvertedFrom reports whether value v was converted from type typ.
func ConvertedFrom(v Value, typ string) bool {
change, ok := v.Value.(*ssa.ChangeType)
return ok && IsType(change.X.Type(), typ)
}
func UniqueStringCutset(v Value) bool {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
continue
}
if c.Value.Kind() != constant.String {
continue
}
s := constant.StringVal(c.Value)
rs := runeSlice(s)
if len(rs) < 2 {
continue
}
sort.Sort(rs)
for i, r := range rs[1:] {
if rs[i] == r {
return false
}
}
}
return true
}

View File

@ -0,0 +1,73 @@
package vrp
import (
"fmt"
"honnef.co/go/tools/ssa"
)
type ChannelInterval struct {
Size IntInterval
}
func (c ChannelInterval) Union(other Range) Range {
i, ok := other.(ChannelInterval)
if !ok {
i = ChannelInterval{EmptyIntInterval}
}
if c.Size.Empty() || !c.Size.IsKnown() {
return i
}
if i.Size.Empty() || !i.Size.IsKnown() {
return c
}
return ChannelInterval{
Size: c.Size.Union(i.Size).(IntInterval),
}
}
func (c ChannelInterval) String() string {
return c.Size.String()
}
func (c ChannelInterval) IsKnown() bool {
return c.Size.IsKnown()
}
type MakeChannelConstraint struct {
aConstraint
Buffer ssa.Value
}
type ChannelChangeTypeConstraint struct {
aConstraint
X ssa.Value
}
func NewMakeChannelConstraint(buffer, y ssa.Value) Constraint {
return &MakeChannelConstraint{NewConstraint(y), buffer}
}
func NewChannelChangeTypeConstraint(x, y ssa.Value) Constraint {
return &ChannelChangeTypeConstraint{NewConstraint(y), x}
}
func (c *MakeChannelConstraint) Operands() []ssa.Value { return []ssa.Value{c.Buffer} }
func (c *ChannelChangeTypeConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *MakeChannelConstraint) String() string {
return fmt.Sprintf("%s = make(chan, %s)", c.Y().Name(), c.Buffer.Name())
}
func (c *ChannelChangeTypeConstraint) String() string {
return fmt.Sprintf("%s = changetype(%s)", c.Y().Name(), c.X.Name())
}
func (c *MakeChannelConstraint) Eval(g *Graph) Range {
i, ok := g.Range(c.Buffer).(IntInterval)
if !ok {
return ChannelInterval{NewIntInterval(NewZ(0), PInfinity)}
}
if i.Lower.Sign() == -1 {
i.Lower = NewZ(0)
}
return ChannelInterval{i}
}
func (c *ChannelChangeTypeConstraint) Eval(g *Graph) Range { return g.Range(c.X) }

View File

@ -0,0 +1,476 @@
package vrp
import (
"fmt"
"go/token"
"go/types"
"math/big"
"honnef.co/go/tools/ssa"
)
type Zs []Z
func (zs Zs) Len() int {
return len(zs)
}
func (zs Zs) Less(i int, j int) bool {
return zs[i].Cmp(zs[j]) == -1
}
func (zs Zs) Swap(i int, j int) {
zs[i], zs[j] = zs[j], zs[i]
}
type Z struct {
infinity int8
integer *big.Int
}
func NewZ(n int64) Z {
return NewBigZ(big.NewInt(n))
}
func NewBigZ(n *big.Int) Z {
return Z{integer: n}
}
func (z1 Z) Infinite() bool {
return z1.infinity != 0
}
func (z1 Z) Add(z2 Z) Z {
if z2.Sign() == -1 {
return z1.Sub(z2.Negate())
}
if z1 == NInfinity {
return NInfinity
}
if z1 == PInfinity {
return PInfinity
}
if z2 == PInfinity {
return PInfinity
}
if !z1.Infinite() && !z2.Infinite() {
n := &big.Int{}
n.Add(z1.integer, z2.integer)
return NewBigZ(n)
}
panic(fmt.Sprintf("%s + %s is not defined", z1, z2))
}
func (z1 Z) Sub(z2 Z) Z {
if z2.Sign() == -1 {
return z1.Add(z2.Negate())
}
if !z1.Infinite() && !z2.Infinite() {
n := &big.Int{}
n.Sub(z1.integer, z2.integer)
return NewBigZ(n)
}
if z1 != PInfinity && z2 == PInfinity {
return NInfinity
}
if z1.Infinite() && !z2.Infinite() {
return Z{infinity: z1.infinity}
}
if z1 == PInfinity && z2 == PInfinity {
return PInfinity
}
panic(fmt.Sprintf("%s - %s is not defined", z1, z2))
}
func (z1 Z) Mul(z2 Z) Z {
if (z1.integer != nil && z1.integer.Sign() == 0) ||
(z2.integer != nil && z2.integer.Sign() == 0) {
return NewBigZ(&big.Int{})
}
if z1.infinity != 0 || z2.infinity != 0 {
return Z{infinity: int8(z1.Sign() * z2.Sign())}
}
n := &big.Int{}
n.Mul(z1.integer, z2.integer)
return NewBigZ(n)
}
func (z1 Z) Negate() Z {
if z1.infinity == 1 {
return NInfinity
}
if z1.infinity == -1 {
return PInfinity
}
n := &big.Int{}
n.Neg(z1.integer)
return NewBigZ(n)
}
func (z1 Z) Sign() int {
if z1.infinity != 0 {
return int(z1.infinity)
}
return z1.integer.Sign()
}
func (z1 Z) String() string {
if z1 == NInfinity {
return "-∞"
}
if z1 == PInfinity {
return "∞"
}
return fmt.Sprintf("%d", z1.integer)
}
func (z1 Z) Cmp(z2 Z) int {
if z1.infinity == z2.infinity && z1.infinity != 0 {
return 0
}
if z1 == PInfinity {
return 1
}
if z1 == NInfinity {
return -1
}
if z2 == NInfinity {
return 1
}
if z2 == PInfinity {
return -1
}
return z1.integer.Cmp(z2.integer)
}
func MaxZ(zs ...Z) Z {
if len(zs) == 0 {
panic("Max called with no arguments")
}
if len(zs) == 1 {
return zs[0]
}
ret := zs[0]
for _, z := range zs[1:] {
if z.Cmp(ret) == 1 {
ret = z
}
}
return ret
}
func MinZ(zs ...Z) Z {
if len(zs) == 0 {
panic("Min called with no arguments")
}
if len(zs) == 1 {
return zs[0]
}
ret := zs[0]
for _, z := range zs[1:] {
if z.Cmp(ret) == -1 {
ret = z
}
}
return ret
}
var NInfinity = Z{infinity: -1}
var PInfinity = Z{infinity: 1}
var EmptyIntInterval = IntInterval{true, PInfinity, NInfinity}
func InfinityFor(v ssa.Value) IntInterval {
if b, ok := v.Type().Underlying().(*types.Basic); ok {
if (b.Info() & types.IsUnsigned) != 0 {
return NewIntInterval(NewZ(0), PInfinity)
}
}
return NewIntInterval(NInfinity, PInfinity)
}
type IntInterval struct {
known bool
Lower Z
Upper Z
}
func NewIntInterval(l, u Z) IntInterval {
if u.Cmp(l) == -1 {
return EmptyIntInterval
}
return IntInterval{known: true, Lower: l, Upper: u}
}
func (i IntInterval) IsKnown() bool {
return i.known
}
func (i IntInterval) Empty() bool {
return i.Lower == PInfinity && i.Upper == NInfinity
}
func (i IntInterval) IsMaxRange() bool {
return i.Lower == NInfinity && i.Upper == PInfinity
}
func (i1 IntInterval) Intersection(i2 IntInterval) IntInterval {
if !i1.IsKnown() {
return i2
}
if !i2.IsKnown() {
return i1
}
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
i3 := NewIntInterval(MaxZ(i1.Lower, i2.Lower), MinZ(i1.Upper, i2.Upper))
if i3.Lower.Cmp(i3.Upper) == 1 {
return EmptyIntInterval
}
return i3
}
func (i1 IntInterval) Union(other Range) Range {
i2, ok := other.(IntInterval)
if !ok {
i2 = EmptyIntInterval
}
if i1.Empty() || !i1.IsKnown() {
return i2
}
if i2.Empty() || !i2.IsKnown() {
return i1
}
return NewIntInterval(MinZ(i1.Lower, i2.Lower), MaxZ(i1.Upper, i2.Upper))
}
func (i1 IntInterval) Add(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
return NewIntInterval(l1.Add(l2), u1.Add(u2))
}
func (i1 IntInterval) Sub(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
return NewIntInterval(l1.Sub(u2), u1.Sub(l2))
}
func (i1 IntInterval) Mul(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
x1, x2 := i1.Lower, i1.Upper
y1, y2 := i2.Lower, i2.Upper
return NewIntInterval(
MinZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
MaxZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
)
}
func (i1 IntInterval) String() string {
if !i1.IsKnown() {
return "[⊥, ⊥]"
}
if i1.Empty() {
return "{}"
}
return fmt.Sprintf("[%s, %s]", i1.Lower, i1.Upper)
}
type IntArithmeticConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
Op token.Token
Fn func(IntInterval, IntInterval) IntInterval
}
type IntAddConstraint struct{ *IntArithmeticConstraint }
type IntSubConstraint struct{ *IntArithmeticConstraint }
type IntMulConstraint struct{ *IntArithmeticConstraint }
type IntConversionConstraint struct {
aConstraint
X ssa.Value
}
type IntIntersectionConstraint struct {
aConstraint
ranges Ranges
A ssa.Value
B ssa.Value
Op token.Token
I IntInterval
resolved bool
}
type IntIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewIntArithmeticConstraint(a, b, y ssa.Value, op token.Token, fn func(IntInterval, IntInterval) IntInterval) *IntArithmeticConstraint {
return &IntArithmeticConstraint{NewConstraint(y), a, b, op, fn}
}
func NewIntAddConstraint(a, b, y ssa.Value) Constraint {
return &IntAddConstraint{NewIntArithmeticConstraint(a, b, y, token.ADD, IntInterval.Add)}
}
func NewIntSubConstraint(a, b, y ssa.Value) Constraint {
return &IntSubConstraint{NewIntArithmeticConstraint(a, b, y, token.SUB, IntInterval.Sub)}
}
func NewIntMulConstraint(a, b, y ssa.Value) Constraint {
return &IntMulConstraint{NewIntArithmeticConstraint(a, b, y, token.MUL, IntInterval.Mul)}
}
func NewIntConversionConstraint(x, y ssa.Value) Constraint {
return &IntConversionConstraint{NewConstraint(y), x}
}
func NewIntIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
return &IntIntersectionConstraint{
aConstraint: NewConstraint(y),
ranges: ranges,
A: a,
B: b,
Op: op,
}
}
func NewIntIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &IntIntervalConstraint{NewConstraint(y), i}
}
func (c *IntArithmeticConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *IntConversionConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *IntIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
func (s *IntIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *IntArithmeticConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s", c.Y().Name(), c.A.Name(), c.Op, c.B.Name())
}
func (c *IntConversionConstraint) String() string {
return fmt.Sprintf("%s = %s(%s)", c.Y().Name(), c.Y().Type(), c.X.Name())
}
func (c *IntIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
}
func (c *IntIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *IntArithmeticConstraint) Eval(g *Graph) Range {
i1, i2 := g.Range(c.A).(IntInterval), g.Range(c.B).(IntInterval)
if !i1.IsKnown() || !i2.IsKnown() {
return IntInterval{}
}
return c.Fn(i1, i2)
}
func (c *IntConversionConstraint) Eval(g *Graph) Range {
s := &types.StdSizes{
// XXX is it okay to assume the largest word size, or do we
// need to be platform specific?
WordSize: 8,
MaxAlign: 1,
}
fromI := g.Range(c.X).(IntInterval)
toI := g.Range(c.Y()).(IntInterval)
fromT := c.X.Type().Underlying().(*types.Basic)
toT := c.Y().Type().Underlying().(*types.Basic)
fromB := s.Sizeof(c.X.Type())
toB := s.Sizeof(c.Y().Type())
if !fromI.IsKnown() {
return toI
}
if !toI.IsKnown() {
return fromI
}
// uint<N> -> sint/uint<M>, M > N: [max(0, l1), min(2**N-1, u2)]
if (fromT.Info()&types.IsUnsigned != 0) &&
toB > fromB {
n := big.NewInt(1)
n.Lsh(n, uint(fromB*8))
n.Sub(n, big.NewInt(1))
return NewIntInterval(
MaxZ(NewZ(0), fromI.Lower),
MinZ(NewBigZ(n), toI.Upper),
)
}
// sint<N> -> sint<M>, M > N; [max(-∞, l1), min(2**N-1, u2)]
if (fromT.Info()&types.IsUnsigned == 0) &&
(toT.Info()&types.IsUnsigned == 0) &&
toB > fromB {
n := big.NewInt(1)
n.Lsh(n, uint(fromB*8))
n.Sub(n, big.NewInt(1))
return NewIntInterval(
MaxZ(NInfinity, fromI.Lower),
MinZ(NewBigZ(n), toI.Upper),
)
}
return fromI
}
func (c *IntIntersectionConstraint) Eval(g *Graph) Range {
xi := g.Range(c.A).(IntInterval)
if !xi.IsKnown() {
return c.I
}
return xi.Intersection(c.I)
}
func (c *IntIntervalConstraint) Eval(*Graph) Range { return c.I }
func (c *IntIntersectionConstraint) Futures() []ssa.Value {
return []ssa.Value{c.B}
}
func (c *IntIntersectionConstraint) Resolve() {
r, ok := c.ranges[c.B].(IntInterval)
if !ok {
c.I = InfinityFor(c.Y())
return
}
switch c.Op {
case token.EQL:
c.I = r
case token.GTR:
c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
case token.GEQ:
c.I = NewIntInterval(r.Lower, PInfinity)
case token.LSS:
// TODO(dh): do we need 0 instead of NInfinity for uints?
c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
case token.LEQ:
c.I = NewIntInterval(NInfinity, r.Upper)
case token.NEQ:
c.I = InfinityFor(c.Y())
default:
panic("unsupported op " + c.Op.String())
}
}
func (c *IntIntersectionConstraint) IsKnown() bool {
return c.I.IsKnown()
}
func (c *IntIntersectionConstraint) MarkUnresolved() {
c.resolved = false
}
func (c *IntIntersectionConstraint) MarkResolved() {
c.resolved = true
}
func (c *IntIntersectionConstraint) IsResolved() bool {
return c.resolved
}

View File

@ -0,0 +1,273 @@
package vrp
// TODO(dh): most of the constraints have implementations identical to
// that of strings. Consider reusing them.
import (
"fmt"
"go/types"
"honnef.co/go/tools/ssa"
)
type SliceInterval struct {
Length IntInterval
}
func (s SliceInterval) Union(other Range) Range {
i, ok := other.(SliceInterval)
if !ok {
i = SliceInterval{EmptyIntInterval}
}
if s.Length.Empty() || !s.Length.IsKnown() {
return i
}
if i.Length.Empty() || !i.Length.IsKnown() {
return s
}
return SliceInterval{
Length: s.Length.Union(i.Length).(IntInterval),
}
}
func (s SliceInterval) String() string { return s.Length.String() }
func (s SliceInterval) IsKnown() bool { return s.Length.IsKnown() }
type SliceAppendConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
}
type SliceSliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type ArraySliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type SliceIntersectionConstraint struct {
aConstraint
X ssa.Value
I IntInterval
}
type SliceLengthConstraint struct {
aConstraint
X ssa.Value
}
type MakeSliceConstraint struct {
aConstraint
Size ssa.Value
}
type SliceIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewSliceAppendConstraint(a, b, y ssa.Value) Constraint {
return &SliceAppendConstraint{NewConstraint(y), a, b}
}
func NewSliceSliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &SliceSliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewArraySliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &ArraySliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewSliceIntersectionConstraint(x ssa.Value, i IntInterval, y ssa.Value) Constraint {
return &SliceIntersectionConstraint{NewConstraint(y), x, i}
}
func NewSliceLengthConstraint(x, y ssa.Value) Constraint {
return &SliceLengthConstraint{NewConstraint(y), x}
}
func NewMakeSliceConstraint(size, y ssa.Value) Constraint {
return &MakeSliceConstraint{NewConstraint(y), size}
}
func NewSliceIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &SliceIntervalConstraint{NewConstraint(y), i}
}
func (c *SliceAppendConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *SliceSliceConstraint) Operands() []ssa.Value {
ops := []ssa.Value{c.X}
if c.Lower != nil {
ops = append(ops, c.Lower)
}
if c.Upper != nil {
ops = append(ops, c.Upper)
}
return ops
}
func (c *ArraySliceConstraint) Operands() []ssa.Value {
ops := []ssa.Value{c.X}
if c.Lower != nil {
ops = append(ops, c.Lower)
}
if c.Upper != nil {
ops = append(ops, c.Upper)
}
return ops
}
func (c *SliceIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *SliceLengthConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *MakeSliceConstraint) Operands() []ssa.Value { return []ssa.Value{c.Size} }
func (s *SliceIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *SliceAppendConstraint) String() string {
return fmt.Sprintf("%s = append(%s, %s)", c.Y().Name(), c.A.Name(), c.B.Name())
}
func (c *SliceSliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *ArraySliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *SliceIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s.%t ⊓ %s", c.Y().Name(), c.X.Name(), c.Y().(*ssa.Sigma).Branch, c.I)
}
func (c *SliceLengthConstraint) String() string {
return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}
func (c *MakeSliceConstraint) String() string {
return fmt.Sprintf("%s = make(slice, %s)", c.Y().Name(), c.Size.Name())
}
func (c *SliceIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *SliceAppendConstraint) Eval(g *Graph) Range {
l1 := g.Range(c.A).(SliceInterval).Length
var l2 IntInterval
switch r := g.Range(c.B).(type) {
case SliceInterval:
l2 = r.Length
case StringInterval:
l2 = r.Length
default:
return SliceInterval{}
}
if !l1.IsKnown() || !l2.IsKnown() {
return SliceInterval{}
}
return SliceInterval{
Length: l1.Add(l2),
}
}
func (c *SliceSliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
ur := g.Range(c.X).(SliceInterval).Length
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return SliceInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return SliceInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *ArraySliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
var l int64
switch typ := c.X.Type().(type) {
case *types.Array:
l = typ.Len()
case *types.Pointer:
l = typ.Elem().(*types.Array).Len()
}
ur := NewIntInterval(NewZ(l), NewZ(l))
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return SliceInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return SliceInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *SliceIntersectionConstraint) Eval(g *Graph) Range {
xi := g.Range(c.X).(SliceInterval)
if !xi.IsKnown() {
return c.I
}
return SliceInterval{
Length: xi.Length.Intersection(c.I),
}
}
func (c *SliceLengthConstraint) Eval(g *Graph) Range {
i := g.Range(c.X).(SliceInterval).Length
if !i.IsKnown() {
return NewIntInterval(NewZ(0), PInfinity)
}
return i
}
func (c *MakeSliceConstraint) Eval(g *Graph) Range {
i, ok := g.Range(c.Size).(IntInterval)
if !ok {
return SliceInterval{NewIntInterval(NewZ(0), PInfinity)}
}
if i.Lower.Sign() == -1 {
i.Lower = NewZ(0)
}
return SliceInterval{i}
}
func (c *SliceIntervalConstraint) Eval(*Graph) Range { return SliceInterval{c.I} }

View File

@ -0,0 +1,258 @@
package vrp
import (
"fmt"
"go/token"
"go/types"
"honnef.co/go/tools/ssa"
)
type StringInterval struct {
Length IntInterval
}
func (s StringInterval) Union(other Range) Range {
i, ok := other.(StringInterval)
if !ok {
i = StringInterval{EmptyIntInterval}
}
if s.Length.Empty() || !s.Length.IsKnown() {
return i
}
if i.Length.Empty() || !i.Length.IsKnown() {
return s
}
return StringInterval{
Length: s.Length.Union(i.Length).(IntInterval),
}
}
func (s StringInterval) String() string {
return s.Length.String()
}
func (s StringInterval) IsKnown() bool {
return s.Length.IsKnown()
}
type StringSliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type StringIntersectionConstraint struct {
aConstraint
ranges Ranges
A ssa.Value
B ssa.Value
Op token.Token
I IntInterval
resolved bool
}
type StringConcatConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
}
type StringLengthConstraint struct {
aConstraint
X ssa.Value
}
type StringIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewStringSliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &StringSliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewStringIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
return &StringIntersectionConstraint{
aConstraint: NewConstraint(y),
ranges: ranges,
A: a,
B: b,
Op: op,
}
}
func NewStringConcatConstraint(a, b, y ssa.Value) Constraint {
return &StringConcatConstraint{NewConstraint(y), a, b}
}
func NewStringLengthConstraint(x ssa.Value, y ssa.Value) Constraint {
return &StringLengthConstraint{NewConstraint(y), x}
}
func NewStringIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &StringIntervalConstraint{NewConstraint(y), i}
}
func (c *StringSliceConstraint) Operands() []ssa.Value {
vs := []ssa.Value{c.X}
if c.Lower != nil {
vs = append(vs, c.Lower)
}
if c.Upper != nil {
vs = append(vs, c.Upper)
}
return vs
}
func (c *StringIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
func (c StringConcatConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *StringLengthConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (s *StringIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *StringSliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *StringIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
}
func (c StringConcatConstraint) String() string {
return fmt.Sprintf("%s = %s + %s", c.Y().Name(), c.A.Name(), c.B.Name())
}
func (c *StringLengthConstraint) String() string {
return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}
func (c *StringIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *StringSliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
ur := g.Range(c.X).(StringInterval).Length
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return StringInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return StringInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *StringIntersectionConstraint) Eval(g *Graph) Range {
var l IntInterval
switch r := g.Range(c.A).(type) {
case StringInterval:
l = r.Length
case IntInterval:
l = r
}
if !l.IsKnown() {
return StringInterval{c.I}
}
return StringInterval{
Length: l.Intersection(c.I),
}
}
func (c StringConcatConstraint) Eval(g *Graph) Range {
i1, i2 := g.Range(c.A).(StringInterval), g.Range(c.B).(StringInterval)
if !i1.Length.IsKnown() || !i2.Length.IsKnown() {
return StringInterval{}
}
return StringInterval{
Length: i1.Length.Add(i2.Length),
}
}
func (c *StringLengthConstraint) Eval(g *Graph) Range {
i := g.Range(c.X).(StringInterval).Length
if !i.IsKnown() {
return NewIntInterval(NewZ(0), PInfinity)
}
return i
}
func (c *StringIntervalConstraint) Eval(*Graph) Range { return StringInterval{c.I} }
func (c *StringIntersectionConstraint) Futures() []ssa.Value {
return []ssa.Value{c.B}
}
func (c *StringIntersectionConstraint) Resolve() {
if (c.A.Type().Underlying().(*types.Basic).Info() & types.IsString) != 0 {
// comparing two strings
r, ok := c.ranges[c.B].(StringInterval)
if !ok {
c.I = NewIntInterval(NewZ(0), PInfinity)
return
}
switch c.Op {
case token.EQL:
c.I = r.Length
case token.GTR, token.GEQ:
c.I = NewIntInterval(r.Length.Lower, PInfinity)
case token.LSS, token.LEQ:
c.I = NewIntInterval(NewZ(0), r.Length.Upper)
case token.NEQ:
default:
panic("unsupported op " + c.Op.String())
}
} else {
r, ok := c.ranges[c.B].(IntInterval)
if !ok {
c.I = NewIntInterval(NewZ(0), PInfinity)
return
}
// comparing two lengths
switch c.Op {
case token.EQL:
c.I = r
case token.GTR:
c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
case token.GEQ:
c.I = NewIntInterval(r.Lower, PInfinity)
case token.LSS:
c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
case token.LEQ:
c.I = NewIntInterval(NInfinity, r.Upper)
case token.NEQ:
default:
panic("unsupported op " + c.Op.String())
}
}
}
func (c *StringIntersectionConstraint) IsKnown() bool {
return c.I.IsKnown()
}
func (c *StringIntersectionConstraint) MarkUnresolved() {
c.resolved = false
}
func (c *StringIntersectionConstraint) MarkResolved() {
c.resolved = true
}
func (c *StringIntersectionConstraint) IsResolved() bool {
return c.resolved
}

File diff suppressed because it is too large Load Diff

7
vendor/modules.txt vendored
View File

@ -196,13 +196,20 @@ gopkg.in/testfixtures.v2
gopkg.in/yaml.v2
# honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3
honnef.co/go/tools/cmd/gosimple
honnef.co/go/tools/cmd/staticcheck
honnef.co/go/tools/cmd/unused
honnef.co/go/tools/lint/lintutil
honnef.co/go/tools/simple
honnef.co/go/tools/staticcheck
honnef.co/go/tools/unused
honnef.co/go/tools/lint
honnef.co/go/tools/version
honnef.co/go/tools/internal/sharedcheck
honnef.co/go/tools/lint/lintdsl
honnef.co/go/tools/deprecated
honnef.co/go/tools/functions
honnef.co/go/tools/ssa
honnef.co/go/tools/staticcheck/vrp
honnef.co/go/tools/ssa/ssautil
honnef.co/go/tools/callgraph
honnef.co/go/tools/callgraph/static