--- /dev/null
+// 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.
+
+// +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
+}