+++ /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
-}