.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / go / pointer / pointer_test.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/pointer/pointer_test.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/pointer/pointer_test.go
new file mode 100644 (file)
index 0000000..2f6e069
--- /dev/null
@@ -0,0 +1,602 @@
+// 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.
+
+// No testdata on Android.
+
+//go:build !android
+// +build !android
+
+package pointer_test
+
+// This test uses 'expectation' comments embedded within testdata/*.go
+// files to specify the expected pointer analysis behaviour.
+// See below for grammar.
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "go/token"
+       "go/types"
+       "io/ioutil"
+       "os"
+       "regexp"
+       "strconv"
+       "strings"
+       "testing"
+
+       "golang.org/x/tools/go/callgraph"
+       "golang.org/x/tools/go/loader"
+       "golang.org/x/tools/go/pointer"
+       "golang.org/x/tools/go/ssa"
+       "golang.org/x/tools/go/ssa/ssautil"
+       "golang.org/x/tools/go/types/typeutil"
+)
+
+var inputs = []string{
+       "testdata/a_test.go",
+       "testdata/another.go",
+       "testdata/arrayreflect.go",
+       "testdata/arrays.go",
+       "testdata/channels.go",
+       "testdata/chanreflect.go",
+       "testdata/context.go",
+       "testdata/conv.go",
+       "testdata/extended.go",
+       "testdata/finalizer.go",
+       "testdata/flow.go",
+       "testdata/fmtexcerpt.go",
+       "testdata/func.go",
+       "testdata/funcreflect.go",
+       "testdata/hello.go", // NB: causes spurious failure of HVN cross-check
+       "testdata/interfaces.go",
+       "testdata/issue9002.go",
+       "testdata/mapreflect.go",
+       "testdata/maps.go",
+       "testdata/panic.go",
+       "testdata/recur.go",
+       "testdata/reflect.go",
+       "testdata/rtti.go",
+       "testdata/structreflect.go",
+       "testdata/structs.go",
+       // "testdata/timer.go", // TODO(adonovan): fix broken assumptions about runtime timers
+}
+
+// Expectation grammar:
+//
+// @calls f -> g
+//
+//   A 'calls' expectation asserts that edge (f, g) appears in the
+//   callgraph.  f and g are notated as per Function.String(), which
+//   may contain spaces (e.g. promoted method in anon struct).
+//
+// @pointsto a | b | c
+//
+//   A 'pointsto' expectation asserts that the points-to set of its
+//   operand contains exactly the set of labels {a,b,c} notated as per
+//   labelString.
+//
+//   A 'pointsto' expectation must appear on the same line as a
+//   print(x) statement; the expectation's operand is x.
+//
+//   If one of the strings is "...", the expectation asserts that the
+//   points-to set at least the other labels.
+//
+//   We use '|' because label names may contain spaces, e.g.  methods
+//   of anonymous structs.
+//
+//   From a theoretical perspective, concrete types in interfaces are
+//   labels too, but they are represented differently and so have a
+//   different expectation, @types, below.
+//
+// @types t | u | v
+//
+//   A 'types' expectation asserts that the set of possible dynamic
+//   types of its interface operand is exactly {t,u,v}, notated per
+//   go/types.Type.String(). In other words, it asserts that the type
+//   component of the interface may point to that set of concrete type
+//   literals.  It also works for reflect.Value, though the types
+//   needn't be concrete in that case.
+//
+//   A 'types' expectation must appear on the same line as a
+//   print(x) statement; the expectation's operand is x.
+//
+//   If one of the strings is "...", the expectation asserts that the
+//   interface's type may point to at least the other types.
+//
+//   We use '|' because type names may contain spaces.
+//
+// @warning "regexp"
+//
+//   A 'warning' expectation asserts that the analysis issues a
+//   warning that matches the regular expression within the string
+//   literal.
+//
+// @line id
+//
+//   A line directive associates the name "id" with the current
+//   file:line.  The string form of labels will use this id instead of
+//   a file:line, making @pointsto expectations more robust against
+//   perturbations in the source file.
+//   (NB, anon functions still include line numbers.)
+//
+type expectation struct {
+       kind     string // "pointsto" | "pointstoquery" | "types" | "calls" | "warning"
+       filename string
+       linenum  int // source line number, 1-based
+       args     []string
+       query    string           // extended query
+       extended *pointer.Pointer // extended query pointer
+       types    []types.Type     // for types
+}
+
+func (e *expectation) String() string {
+       return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | "))
+}
+
+func (e *expectation) errorf(format string, args ...interface{}) {
+       fmt.Printf("%s:%d: ", e.filename, e.linenum)
+       fmt.Printf(format, args...)
+       fmt.Println()
+}
+
+func (e *expectation) needsProbe() bool {
+       return e.kind == "pointsto" || e.kind == "pointstoquery" || e.kind == "types"
+}
+
+// Find probe (call to print(x)) of same source file/line as expectation.
+func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) {
+       for call := range probes {
+               pos := prog.Fset.Position(call.Pos())
+               if pos.Line == e.linenum && pos.Filename == e.filename {
+                       // TODO(adonovan): send this to test log (display only on failure).
+                       // fmt.Printf("%s:%d: info: found probe for %s: %s\n",
+                       //      e.filename, e.linenum, e, p.arg0) // debugging
+                       return call, queries[call.Args[0]].PointsTo()
+               }
+       }
+       return // e.g. analysis didn't reach this call
+}
+
+func doOneInput(input, filename string) bool {
+       var conf loader.Config
+
+       // Parsing.
+       f, err := conf.ParseFile(filename, input)
+       if err != nil {
+               fmt.Println(err)
+               return false
+       }
+
+       // Create single-file main package and import its dependencies.
+       conf.CreateFromFiles("main", f)
+       iprog, err := conf.Load()
+       if err != nil {
+               fmt.Println(err)
+               return false
+       }
+       mainPkgInfo := iprog.Created[0].Pkg
+
+       // SSA creation + building.
+       prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
+       prog.Build()
+
+       mainpkg := prog.Package(mainPkgInfo)
+       ptrmain := mainpkg // main package for the pointer analysis
+       if mainpkg.Func("main") == nil {
+               // No main function; assume it's a test.
+               ptrmain = prog.CreateTestMainPackage(mainpkg)
+       }
+
+       // Find all calls to the built-in print(x).  Analytically,
+       // print is a no-op, but it's a convenient hook for testing
+       // the PTS of an expression, so our tests use it.
+       probes := make(map[*ssa.CallCommon]bool)
+       for fn := range ssautil.AllFunctions(prog) {
+               if fn.Pkg == mainpkg {
+                       for _, b := range fn.Blocks {
+                               for _, instr := range b.Instrs {
+                                       if instr, ok := instr.(ssa.CallInstruction); ok {
+                                               call := instr.Common()
+                                               if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 {
+                                                       probes[instr.Common()] = true
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       ok := true
+
+       lineMapping := make(map[string]string) // maps "file:line" to @line tag
+
+       // Parse expectations in this input.
+       var exps []*expectation
+       re := regexp.MustCompile("// *@([a-z]*) *(.*)$")
+       lines := strings.Split(input, "\n")
+       for linenum, line := range lines {
+               linenum++ // make it 1-based
+               if matches := re.FindAllStringSubmatch(line, -1); matches != nil {
+                       match := matches[0]
+                       kind, rest := match[1], match[2]
+                       e := &expectation{kind: kind, filename: filename, linenum: linenum}
+
+                       if kind == "line" {
+                               if rest == "" {
+                                       ok = false
+                                       e.errorf("@%s expectation requires identifier", kind)
+                               } else {
+                                       lineMapping[fmt.Sprintf("%s:%d", filename, linenum)] = rest
+                               }
+                               continue
+                       }
+
+                       if e.needsProbe() && !strings.Contains(line, "print(") {
+                               ok = false
+                               e.errorf("@%s expectation must follow call to print(x)", kind)
+                               continue
+                       }
+
+                       switch kind {
+                       case "pointsto":
+                               e.args = split(rest, "|")
+
+                       case "pointstoquery":
+                               args := strings.SplitN(rest, " ", 2)
+                               e.query = args[0]
+                               e.args = split(args[1], "|")
+                       case "types":
+                               for _, typstr := range split(rest, "|") {
+                                       var t types.Type = types.Typ[types.Invalid] // means "..."
+                                       if typstr != "..." {
+                                               tv, err := types.Eval(prog.Fset, mainpkg.Pkg, f.Pos(), typstr)
+                                               if err != nil {
+                                                       ok = false
+                                                       // Don't print err since its location is bad.
+                                                       e.errorf("'%s' is not a valid type: %s", typstr, err)
+                                                       continue
+                                               }
+                                               t = tv.Type
+                                       }
+                                       e.types = append(e.types, t)
+                               }
+
+                       case "calls":
+                               e.args = split(rest, "->")
+                               // TODO(adonovan): eagerly reject the
+                               // expectation if fn doesn't denote
+                               // existing function, rather than fail
+                               // the expectation after analysis.
+                               if len(e.args) != 2 {
+                                       ok = false
+                                       e.errorf("@calls expectation wants 'caller -> callee' arguments")
+                                       continue
+                               }
+
+                       case "warning":
+                               lit, err := strconv.Unquote(strings.TrimSpace(rest))
+                               if err != nil {
+                                       ok = false
+                                       e.errorf("couldn't parse @warning operand: %s", err.Error())
+                                       continue
+                               }
+                               e.args = append(e.args, lit)
+
+                       default:
+                               ok = false
+                               e.errorf("unknown expectation kind: %s", e)
+                               continue
+                       }
+                       exps = append(exps, e)
+               }
+       }
+
+       var log bytes.Buffer
+       fmt.Fprintf(&log, "Input: %s\n", filename)
+
+       // Run the analysis.
+       config := &pointer.Config{
+               Reflection:     true,
+               BuildCallGraph: true,
+               Mains:          []*ssa.Package{ptrmain},
+               Log:            &log,
+       }
+probeLoop:
+       for probe := range probes {
+               v := probe.Args[0]
+               pos := prog.Fset.Position(probe.Pos())
+               for _, e := range exps {
+                       if e.linenum == pos.Line && e.filename == pos.Filename && e.kind == "pointstoquery" {
+                               var err error
+                               e.extended, err = config.AddExtendedQuery(v, e.query)
+                               if err != nil {
+                                       panic(err)
+                               }
+                               continue probeLoop
+                       }
+               }
+               if pointer.CanPoint(v.Type()) {
+                       config.AddQuery(v)
+               }
+       }
+
+       // Print the log is there was an error or a panic.
+       complete := false
+       defer func() {
+               if !complete || !ok {
+                       log.WriteTo(os.Stderr)
+               }
+       }()
+
+       result, err := pointer.Analyze(config)
+       if err != nil {
+               panic(err) // internal error in pointer analysis
+       }
+
+       // Check the expectations.
+       for _, e := range exps {
+               var call *ssa.CallCommon
+               var pts pointer.PointsToSet
+               var tProbe types.Type
+               if e.needsProbe() {
+                       if call, pts = findProbe(prog, probes, result.Queries, e); call == nil {
+                               ok = false
+                               e.errorf("unreachable print() statement has expectation %s", e)
+                               continue
+                       }
+                       if e.extended != nil {
+                               pts = e.extended.PointsTo()
+                       }
+                       tProbe = call.Args[0].Type()
+                       if !pointer.CanPoint(tProbe) {
+                               ok = false
+                               e.errorf("expectation on non-pointerlike operand: %s", tProbe)
+                               continue
+                       }
+               }
+
+               switch e.kind {
+               case "pointsto", "pointstoquery":
+                       if !checkPointsToExpectation(e, pts, lineMapping, prog) {
+                               ok = false
+                       }
+
+               case "types":
+                       if !checkTypesExpectation(e, pts, tProbe) {
+                               ok = false
+                       }
+
+               case "calls":
+                       if !checkCallsExpectation(prog, e, result.CallGraph) {
+                               ok = false
+                       }
+
+               case "warning":
+                       if !checkWarningExpectation(prog, e, result.Warnings) {
+                               ok = false
+                       }
+               }
+       }
+
+       complete = true
+
+       // ok = false // debugging: uncomment to always see log
+
+       return ok
+}
+
+func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string {
+       // Functions and Globals need no pos suffix,
+       // nor do allocations in intrinsic operations
+       // (for which we'll print the function name).
+       switch l.Value().(type) {
+       case nil, *ssa.Function, *ssa.Global:
+               return l.String()
+       }
+
+       str := l.String()
+       if pos := l.Pos(); pos != token.NoPos {
+               // Append the position, using a @line tag instead of a line number, if defined.
+               posn := prog.Fset.Position(pos)
+               s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
+               if tag, ok := lineMapping[s]; ok {
+                       return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column)
+               }
+               str = fmt.Sprintf("%s@%s", str, posn)
+       }
+       return str
+}
+
+func checkPointsToExpectation(e *expectation, pts pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool {
+       expected := make(map[string]int)
+       surplus := make(map[string]int)
+       exact := true
+       for _, g := range e.args {
+               if g == "..." {
+                       exact = false
+                       continue
+               }
+               expected[g]++
+       }
+       // Find the set of labels that the probe's
+       // argument (x in print(x)) may point to.
+       for _, label := range pts.Labels() {
+               name := labelString(label, lineMapping, prog)
+               if expected[name] > 0 {
+                       expected[name]--
+               } else if exact {
+                       surplus[name]++
+               }
+       }
+       // Report multiset difference:
+       ok := true
+       for _, count := range expected {
+               if count > 0 {
+                       ok = false
+                       e.errorf("value does not alias these expected labels: %s", join(expected))
+                       break
+               }
+       }
+       for _, count := range surplus {
+               if count > 0 {
+                       ok = false
+                       e.errorf("value may additionally alias these labels: %s", join(surplus))
+                       break
+               }
+       }
+       return ok
+}
+
+func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Type) bool {
+       var expected typeutil.Map
+       var surplus typeutil.Map
+       exact := true
+       for _, g := range e.types {
+               if g == types.Typ[types.Invalid] {
+                       exact = false
+                       continue
+               }
+               expected.Set(g, struct{}{})
+       }
+
+       if !pointer.CanHaveDynamicTypes(typ) {
+               e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", typ)
+               return false
+       }
+
+       // Find the set of types that the probe's
+       // argument (x in print(x)) may contain.
+       for _, T := range pts.DynamicTypes().Keys() {
+               if expected.At(T) != nil {
+                       expected.Delete(T)
+               } else if exact {
+                       surplus.Set(T, struct{}{})
+               }
+       }
+       // Report set difference:
+       ok := true
+       if expected.Len() > 0 {
+               ok = false
+               e.errorf("interface cannot contain these types: %s", expected.KeysString())
+       }
+       if surplus.Len() > 0 {
+               ok = false
+               e.errorf("interface may additionally contain these types: %s", surplus.KeysString())
+       }
+       return ok
+}
+
+var errOK = errors.New("OK")
+
+func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool {
+       found := make(map[string]int)
+       err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
+               // Name-based matching is inefficient but it allows us to
+               // match functions whose names that would not appear in an
+               // index ("<root>") or which are not unique ("func@1.2").
+               if edge.Caller.Func.String() == e.args[0] {
+                       calleeStr := edge.Callee.Func.String()
+                       if calleeStr == e.args[1] {
+                               return errOK // expectation satisfied; stop the search
+                       }
+                       found[calleeStr]++
+               }
+               return nil
+       })
+       if err == errOK {
+               return true
+       }
+       if len(found) == 0 {
+               e.errorf("didn't find any calls from %s", e.args[0])
+       }
+       e.errorf("found no call from %s to %s, but only to %s",
+               e.args[0], e.args[1], join(found))
+       return false
+}
+
+func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool {
+       // TODO(adonovan): check the position part of the warning too?
+       re, err := regexp.Compile(e.args[0])
+       if err != nil {
+               e.errorf("invalid regular expression in @warning expectation: %s", err.Error())
+               return false
+       }
+
+       if len(warnings) == 0 {
+               e.errorf("@warning %q expectation, but no warnings", e.args[0])
+               return false
+       }
+
+       for _, w := range warnings {
+               if re.MatchString(w.Message) {
+                       return true
+               }
+       }
+
+       e.errorf("@warning %q expectation not satisfied; found these warnings though:", e.args[0])
+       for _, w := range warnings {
+               fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message)
+       }
+       return false
+}
+
+func TestInput(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113")
+       }
+       ok := true
+
+       wd, err := os.Getwd()
+       if err != nil {
+               t.Errorf("os.Getwd: %s", err)
+               return
+       }
+
+       // 'go test' does a chdir so that relative paths in
+       // diagnostics no longer make sense relative to the invoking
+       // shell's cwd.  We print a special marker so that Emacs can
+       // make sense of them.
+       fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd)
+
+       for _, filename := range inputs {
+               content, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       t.Errorf("couldn't read file '%s': %s", filename, err)
+                       continue
+               }
+
+               if !doOneInput(string(content), filename) {
+                       ok = false
+               }
+       }
+       if !ok {
+               t.Fail()
+       }
+}
+
+// join joins the elements of multiset with " | "s.
+func join(set map[string]int) string {
+       var buf bytes.Buffer
+       sep := ""
+       for name, count := range set {
+               for i := 0; i < count; i++ {
+                       buf.WriteString(sep)
+                       sep = " | "
+                       buf.WriteString(name)
+               }
+       }
+       return buf.String()
+}
+
+// split returns the list of sep-delimited non-empty strings in s.
+func split(s, sep string) (r []string) {
+       for _, elem := range strings.Split(s, sep) {
+               elem = strings.TrimSpace(elem)
+               if elem != "" {
+                       r = append(r, elem)
+               }
+       }
+       return
+}