func TestSwitches(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/switches.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := ssa.Create(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) mainPkg.Build() for _, mem := range mainPkg.Members { if fn, ok := mem.(*ssa.Function); ok { if fn.Synthetic != "" { continue // e.g. init() } // Each (multi-line) "switch" comment within // this function must match the printed form // of a ConstSwitch. var wantSwitches []string for _, c := range f.Comments { if fn.Syntax().Pos() <= c.Pos() && c.Pos() < fn.Syntax().End() { text := strings.TrimSpace(c.Text()) if strings.HasPrefix(text, "switch ") { wantSwitches = append(wantSwitches, text) } } } switches := ssautil.Switches(fn) if len(switches) != len(wantSwitches) { t.Errorf("in %s, found %d switches, want %d", fn, len(switches), len(wantSwitches)) } for i, sw := range switches { got := sw.String() if i >= len(wantSwitches) { continue } want := wantSwitches[i] if got != want { t.Errorf("in %s, found switch %d: got <<%s>>, want <<%s>>", fn, i, got, want) } } } } }
func create(t *testing.T, content string) []*ssa.Package { var conf loader.Config f, err := conf.ParseFile("foo_test.go", content) if err != nil { t.Fatal(err) } conf.CreateFromFiles("foo", f) iprog, err := conf.Load() if err != nil { t.Fatal(err) } // We needn't call Build. return ssa.Create(iprog, ssa.SanityCheckFunctions).AllPackages() }
// TestRTA runs RTA on each file in inputs, prints the results, and // compares it with the golden results embedded in the WANT comment at // the end of the file. // // The results string consists of two parts: the set of dynamic call // edges, "f --> g", one per line, and the set of reachable functions, // one per line. Each set is sorted. // func TestRTA(t *testing.T) { for _, filename := range inputs { content, err := ioutil.ReadFile(filename) if err != nil { t.Errorf("couldn't read file '%s': %s", filename, err) continue } conf := loader.Config{ ParserMode: parser.ParseComments, } f, err := conf.ParseFile(filename, content) if err != nil { t.Error(err) continue } want, pos := expectation(f) if pos == token.NoPos { t.Errorf("No WANT: comment in %s", filename) continue } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) continue } prog := ssa.Create(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) prog.BuildAll() res := rta.Analyze([]*ssa.Function{ mainPkg.Func("main"), mainPkg.Func("init"), }, true) if got := printResult(res, mainPkg.Object); got != want { t.Errorf("%s: got:\n%s\nwant:\n%s", prog.Fset.Position(pos), got, want) } } }
func TestStatic(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("P.go", input) if err != nil { t.Fatal(err) } conf.CreateFromFiles("P", f) iprog, err := conf.Load() if err != nil { t.Fatal(err) } P := iprog.Created[0].Pkg prog := ssa.Create(iprog, 0) prog.BuildAll() cg := static.CallGraph(prog) var edges []string callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error { edges = append(edges, fmt.Sprintf("%s -> %s", e.Caller.Func.RelString(P), e.Callee.Func.RelString(P))) return nil }) sort.Strings(edges) want := []string{ "(*C).f -> (C).f", "f -> (C).f", "f -> f$1", "f -> g", } if !reflect.DeepEqual(edges, want) { t.Errorf("Got edges %v, want %v", edges, want) } }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // It also shows how to compute the points-to set of a variable, // in this case, (C).f's ch parameter. // func Example() { const myprog = ` package main import "fmt" type I interface { f(map[string]int) } type C struct{} func (C) f(m map[string]int) { fmt.Println("C.f()") } func main() { var i I = C{} x := map[string]int{"one":1} i.f(x) // dynamic method call } ` var conf loader.Config // Parse the input file, a string. // (Command-line tools should use conf.FromArgs.) file, err := conf.ParseFile("myprog.go", myprog) if err != nil { fmt.Print(err) // parse error return } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", file) iprog, err := conf.Load() if err != nil { fmt.Print(err) // type error in some package return } // Create SSA-form program representation. prog := ssa.Create(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() // Configure the pointer analysis to build a call-graph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } // Query points-to set of (C).f's parameter m, a map. C := mainPkg.Type("C").Type() Cfm := prog.LookupMethod(C, mainPkg.Object, "f").Params[1] config.AddQuery(Cfm) // Run the pointer analysis. result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // Find edges originating from the main package. // By converting to strings, we de-duplicate nodes // representing the same function due to context sensitivity. var edges []string callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error { caller := edge.Caller.Func if caller.Pkg == mainPkg { edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func)) } return nil }) // Print the edges in sorted order. sort.Strings(edges) for _, edge := range edges { fmt.Println(edge) } fmt.Println() // Print the labels of (C).f(m)'s points-to set. fmt.Println("m may point to:") var labels []string for _, l := range result.Queries[Cfm].PointsTo().Labels() { label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l) labels = append(labels, label) } sort.Strings(labels) for _, label := range labels { fmt.Println(label) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f // // m may point to: // myprog.go:18:21: makemap }
// TestSyntheticFuncs checks that the expected synthetic functions are // created, reachable, and not duplicated. func TestSyntheticFuncs(t *testing.T) { const input = `package P type T int func (T) f() int func (*T) g() int var ( // thunks a = T.f b = T.f c = (struct{T}).f d = (struct{T}).f e = (*T).g f = (*T).g g = (struct{*T}).g h = (struct{*T}).g // bounds i = T(0).f j = T(0).f k = new(T).g l = new(T).g // wrappers m interface{} = struct{T}{} n interface{} = struct{T}{} o interface{} = struct{*T}{} p interface{} = struct{*T}{} q interface{} = new(struct{T}) r interface{} = new(struct{T}) s interface{} = new(struct{*T}) t interface{} = new(struct{*T}) ) ` // Parse var conf loader.Config f, err := conf.ParseFile("<input>", input) if err != nil { t.Fatalf("parse: %v", err) } conf.CreateFromFiles(f.Name.Name, f) // Load iprog, err := conf.Load() if err != nil { t.Fatalf("Load: %v", err) } // Create and build SSA prog := ssa.Create(iprog, 0) prog.BuildAll() // Enumerate reachable synthetic functions want := map[string]string{ "(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int", "(P.T).f$bound": "bound method wrapper for func (P.T).f() int", "(*P.T).g$thunk": "thunk for func (*P.T).g() int", "(P.T).f$thunk": "thunk for func (P.T).f() int", "(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int", "(struct{P.T}).f$thunk": "thunk for func (P.T).f() int", "(*P.T).f": "wrapper for func (P.T).f() int", "(*struct{*P.T}).f": "wrapper for func (P.T).f() int", "(*struct{*P.T}).g": "wrapper for func (*P.T).g() int", "(*struct{P.T}).f": "wrapper for func (P.T).f() int", "(*struct{P.T}).g": "wrapper for func (*P.T).g() int", "(struct{*P.T}).f": "wrapper for func (P.T).f() int", "(struct{*P.T}).g": "wrapper for func (*P.T).g() int", "(struct{P.T}).f": "wrapper for func (P.T).f() int", "P.init": "package initializer", } for fn := range ssautil.AllFunctions(prog) { if fn.Synthetic == "" { continue } name := fn.String() wantDescr, ok := want[name] if !ok { t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic) continue } delete(want, name) if wantDescr != fn.Synthetic { t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr) } } for fn, descr := range want { t.Errorf("want func: %q: %q", fn, descr) } }
// Tests that synthesized init functions are correctly formed. // Bare init functions omit calls to dependent init functions and the use of // an init guard. They are useful in cases where the client uses a different // calling convention for init functions, or cases where it is easier for a // client to analyze bare init functions. Both of these aspects are used by // the llgo compiler for simpler integration with gccgo's runtime library, // and to simplify the analysis whereby it deduces which stores to globals // can be lowered to global initializers. func TestInit(t *testing.T) { tests := []struct { mode ssa.BuilderMode input, want string }{ {0, `package A; import _ "errors"; var i int = 42`, `# Name: A.init # Package: A # Synthetic: package initializer func init(): 0: entry P:0 S:2 t0 = *init$guard bool if t0 goto 2 else 1 1: init.start P:1 S:1 *init$guard = true:bool t1 = errors.init() () *i = 42:int jump 2 2: init.done P:2 S:0 return `}, {ssa.BareInits, `package B; import _ "errors"; var i int = 42`, `# Name: B.init # Package: B # Synthetic: package initializer func init(): 0: entry P:0 S:0 *i = 42:int return `}, } for _, test := range tests { // Create a single-file main package. var conf loader.Config f, err := conf.ParseFile("<input>", test.input) if err != nil { t.Errorf("test %q: %s", test.input[:15], err) continue } conf.CreateFromFiles(f.Name.Name, f) iprog, err := conf.Load() if err != nil { t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) continue } prog := ssa.Create(iprog, test.mode) mainPkg := prog.Package(iprog.Created[0].Pkg) prog.BuildAll() initFunc := mainPkg.Func("init") if initFunc == nil { t.Errorf("test 'package %s': no init function", f.Name.Name) continue } var initbuf bytes.Buffer _, err = initFunc.WriteTo(&initbuf) if err != nil { t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err) continue } if initbuf.String() != test.want { t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want) } } }
// Tests that programs partially loaded from gc object files contain // functions with no code for the external portions, but are otherwise ok. func TestImportFromBinary(t *testing.T) { test := ` package main import ( "bytes" "io" "testing" ) func main() { var t testing.T t.Parallel() // static call to external declared method t.Fail() // static call to promoted external declared method testing.Short() // static call to external package-level function var w io.Writer = new(bytes.Buffer) w.Write(nil) // interface invoke of external declared method } ` // Create a single-file main package. conf := loader.Config{ImportFromBinary: true} f, err := conf.ParseFile("<input>", test) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := ssa.Create(iprog, ssa.SanityCheckFunctions) mainPkg := prog.Package(iprog.Created[0].Pkg) mainPkg.Build() // The main package, its direct and indirect dependencies are loaded. deps := []string{ // directly imported dependencies: "bytes", "io", "testing", // indirect dependencies (partial list): "errors", "fmt", "os", "runtime", } all := prog.AllPackages() if len(all) <= len(deps) { t.Errorf("unexpected set of loaded packages: %q", all) } for _, path := range deps { pkg := prog.ImportedPackage(path) if pkg == nil { t.Errorf("package not loaded: %q", path) continue } // External packages should have no function bodies (except for wrappers). isExt := pkg != mainPkg // init() if isExt && !isEmpty(pkg.Func("init")) { t.Errorf("external package %s has non-empty init", pkg) } else if !isExt && isEmpty(pkg.Func("init")) { t.Errorf("main package %s has empty init", pkg) } for _, mem := range pkg.Members { switch mem := mem.(type) { case *ssa.Function: // Functions at package level. if isExt && !isEmpty(mem) { t.Errorf("external function %s is non-empty", mem) } else if !isExt && isEmpty(mem) { t.Errorf("function %s is empty", mem) } case *ssa.Type: // Methods of named types T. // (In this test, all exported methods belong to *T not T.) if !isExt { t.Fatalf("unexpected name type in main package: %s", mem) } mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { m := prog.Method(mset.At(i)) // For external types, only synthetic wrappers have code. expExt := !strings.Contains(m.Synthetic, "wrapper") if expExt && !isEmpty(m) { t.Errorf("external method %s is non-empty: %s", m, m.Synthetic) } else if !expExt && isEmpty(m) { t.Errorf("method function %s is empty: %s", m, m.Synthetic) } } } } } expectedCallee := []string{ "(*testing.T).Parallel", "(*testing.common).Fail", "testing.Short", "N/A", } callNum := 0 for _, b := range mainPkg.Func("main").Blocks { for _, instr := range b.Instrs { switch instr := instr.(type) { case ssa.CallInstruction: call := instr.Common() if want := expectedCallee[callNum]; want != "N/A" { got := call.StaticCallee().String() if want != got { t.Errorf("call #%d from main.main: got callee %s, want %s", callNum, got, want) } } callNum++ } } } if callNum != 4 { t.Errorf("in main.main: got %d calls, want %d", callNum, 4) } }
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types. func TestRuntimeTypes(t *testing.T) { tests := []struct { input string want []string }{ // An exported package-level type is needed. {`package A; type T struct{}; func (T) f() {}`, []string{"*p.T", "p.T"}, }, // An unexported package-level type is not needed. {`package B; type t struct{}; func (t) f() {}`, nil, }, // Subcomponents of type of exported package-level var are needed. {`package C; import "bytes"; var V struct {*bytes.Buffer}`, []string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level var are not needed. {`package D; import "bytes"; var v struct {*bytes.Buffer}`, nil, }, // Subcomponents of type of exported package-level function are needed. {`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`, []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level function are not needed. {`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`, nil, }, // Subcomponents of type of exported method of uninstantiated unexported type are not needed. {`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`, nil, }, // ...unless used by MakeInterface. {`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`, []string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported method are not needed. {`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, []string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"}, }, // Local types aren't needed. {`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, nil, }, // ...unless used by MakeInterface. {`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, []string{"*bytes.Buffer", "*p.T", "p.T"}, }, // Types used as operand of MakeInterface are needed. {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, }, // MakeInterface is optimized away when storing to a blank. {`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, nil, }, } for _, test := range tests { // Create a single-file main package. conf := loader.Config{ImportFromBinary: true} f, err := conf.ParseFile("<input>", test.input) if err != nil { t.Errorf("test %q: %s", test.input[:15], err) continue } conf.CreateFromFiles("p", f) iprog, err := conf.Load() if err != nil { t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) continue } prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() var typstrs []string for _, T := range prog.RuntimeTypes() { typstrs = append(typstrs, T.String()) } sort.Strings(typstrs) if !reflect.DeepEqual(typstrs, test.want) { t.Errorf("test 'package %s': got %q, want %q", f.Name.Name, typstrs, test.want) } } }
func TestObjValueLookup(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/objlookup.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) // Maps each var Ident (represented "name:linenum") to the // kind of ssa.Value we expect (represented "Constant", "&Alloc"). expectations := make(map[string]string) // Find all annotations of form x::BinOp, &y::Alloc, etc. re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`) for _, c := range f.Comments { text := c.Text() pos := conf.Fset.Position(c.Pos()) for _, m := range re.FindAllStringSubmatch(text, -1) { key := fmt.Sprintf("%s:%d", m[2], pos.Line) value := m[1] + m[3] expectations[key] = value } } iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := ssa.Create(iprog, 0 /*|ssa.PrintFunctions*/) mainInfo := iprog.Created[0] mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() var varIds []*ast.Ident var varObjs []*types.Var for id, obj := range mainInfo.Defs { // Check invariants for func and const objects. switch obj := obj.(type) { case *types.Func: checkFuncValue(t, prog, obj) case *types.Const: checkConstValue(t, prog, obj) case *types.Var: if id.Name == "_" { continue } varIds = append(varIds, id) varObjs = append(varObjs, obj) } } for id, obj := range mainInfo.Uses { if obj, ok := obj.(*types.Var); ok { varIds = append(varIds, id) varObjs = append(varObjs, obj) } } // Check invariants for var objects. // The result varies based on the specific Ident. for i, id := range varIds { obj := varObjs[i] ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) pos := prog.Fset.Position(id.Pos()) exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] if exp == "" { t.Errorf("%s: no expectation for var ident %s ", pos, id.Name) continue } wantAddr := false if exp[0] == '&' { wantAddr = true exp = exp[1:] } checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr) } }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/valueforexpr.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } mainInfo := iprog.Created[0] prog := ssa.Create(iprog, 0) mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() if false { // debugging for _, mem := range mainPkg.Members { if fn, ok := mem.(*ssa.Function); ok { fn.WriteTo(os.Stderr) } } } // Find the actual AST node for each canonical position. parenExprByPos := make(map[token.Pos]*ast.ParenExpr) ast.Inspect(f, func(n ast.Node) bool { if n != nil { if e, ok := n.(*ast.ParenExpr); ok { parenExprByPos[e.Pos()] = e } } return true }) // Find all annotations of form /*@kind*/. for _, c := range f.Comments { text := strings.TrimSpace(c.Text()) if text == "" || text[0] != '@' { continue } text = text[1:] pos := c.End() + 1 position := prog.Fset.Position(pos) var e ast.Expr if target := parenExprByPos[pos]; target == nil { t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text) continue } else { e = target.X } path, _ := astutil.PathEnclosingInterval(f, pos, pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, text) continue } fn := ssa.EnclosingFunction(mainPkg, path) if fn == nil { t.Errorf("%s: can't find enclosing function", position) continue } v, gotAddr := fn.ValueForExpr(e) // (may be nil) got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.") if want := text; got != want { t.Errorf("%s: got value %q, want %q", position, got, want) } if v != nil { T := v.Type() if gotAddr { T = T.Underlying().(*types.Pointer).Elem() // deref } if !types.Identical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } }
func TestEnclosingFunction(t *testing.T) { tests := []struct { input string // the input file substr string // first occurrence of this string denotes interval fn string // name of expected containing function }{ // We use distinctive numbers as syntactic landmarks. // Ordinary function: {`package main func f() { println(1003) }`, "100", "main.f"}, // Methods: {`package main type T int func (t T) f() { println(200) }`, "200", "(main.T).f"}, // Function literal: {`package main func f() { println(func() { print(300) }) }`, "300", "main.f$1"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "main.f$1$1"}, // Implicit init for package-level var initializer. {"package main; var a = 400", "400", "main.init"}, // No code for constants: {"package main; const a = 500", "500", "(none)"}, // Explicit init() {"package main; func init() { println(600) }", "600", "main.init#1"}, // Multiple explicit init functions: {`package main func init() { println("foo") } func init() { println(800) }`, "800", "main.init#2"}, // init() containing FuncLit. {`package main func init() { println(func(){print(900)}) }`, "900", "main.init#1$1"}, } for _, test := range tests { conf := loader.Config{Fset: token.NewFileSet()} f, start, end := findInterval(t, conf.Fset, test.input, test.substr) if f == nil { continue } path, exact := astutil.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) continue } prog := ssa.Create(iprog, 0) pkg := prog.Package(iprog.Created[0].Pkg) pkg.Build() name := "(none)" fn := ssa.EnclosingFunction(pkg, path) if fn != nil { name = fn.String() } if name != test.fn { t.Errorf("EnclosingFunction(%q in %q) got %s, want %s", test.substr, test.input, name, test.fn) continue } // While we're here: test HasEnclosingFunction. if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) { t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v", test.substr, test.input, has, fn != nil) continue } } }
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 := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() 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 "types": for _, typstr := range split(rest, "|") { var t types.Type = types.Typ[types.Invalid] // means "..." if typstr != "..." { texpr, err := parser.ParseExpr(typstr) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type", typstr) continue } mainFileScope := mainpkg.Object.Scope().Child(0) tv, err := types.EvalNode(prog.Fset, texpr, mainpkg.Object, mainFileScope) 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, } for probe := range probes { v := probe.Args[0] 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 } 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": 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 }
// This program demonstrates how to run the SSA builder on a "Hello, // World!" program and shows the printed representation of packages, // functions and instructions. // // Within the function listing, the name of each BasicBlock such as // ".0.entry" is printed left-aligned, followed by the block's // Instructions. // // For each instruction that defines an SSA virtual register // (i.e. implements Value), the type of that value is shown in the // right column. // // Build and run the ssadump.go program if you want a standalone tool // with similar functionality. It is located at // golang.org/x/tools/cmd/ssadump. // func Example() { const hello = ` package main import "fmt" const message = "Hello, World!" func main() { fmt.Println(message) } ` var conf loader.Config // Parse the input file. file, err := conf.ParseFile("hello.go", hello) if err != nil { fmt.Print(err) // parse error return } // Create single-file main package. conf.CreateFromFiles("main", file) // Load the main package and its dependencies. iprog, err := conf.Load() if err != nil { fmt.Print(err) // type error in some package return } // Create SSA-form program representation. prog := ssa.Create(iprog, ssa.SanityCheckFunctions) mainPkg := prog.Package(iprog.Created[0].Pkg) // Print out the package. mainPkg.WriteTo(os.Stdout) // Build SSA code for bodies of functions in mainPkg. mainPkg.Build() // Print out the package-level functions. mainPkg.Func("init").WriteTo(os.Stdout) mainPkg.Func("main").WriteTo(os.Stdout) // Output: // // package main: // func init func() // var init$guard bool // func main func() // const message message = "Hello, World!":untyped string // // # Name: main.init // # Package: main // # Synthetic: package initializer // func init(): // 0: entry P:0 S:2 // t0 = *init$guard bool // if t0 goto 2 else 1 // 1: init.start P:1 S:1 // *init$guard = true:bool // t1 = fmt.init() () // jump 2 // 2: init.done P:2 S:0 // return // // # Name: main.main // # Package: main // # Location: hello.go:8:6 // func main(): // 0: entry P:0 S:0 // t0 = new [1]interface{} (varargs) *[1]interface{} // t1 = &t0[0:int] *interface{} // t2 = make interface{} <- string ("Hello, World!":string) interface{} // *t1 = t2 // t3 = slice t0[:] []interface{} // t4 = fmt.Println(t3...) (n int, err error) // return }