func TestLoadInitialPackages(t *testing.T) { ctxt := &importer.Config{Build: &build.Default} // Failed load: bad first import path causes parsePackageFiles to fail. args := []string{"nosuchpkg", "errors"} if _, _, err := importer.New(ctxt).LoadInitialPackages(args); err == nil { t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) } else { // cannot find package: ok. } // Failed load: bad second import path proceeds to doImport0, which fails. args = []string{"errors", "nosuchpkg"} if _, _, err := importer.New(ctxt).LoadInitialPackages(args); err == nil { t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) } else { // cannot find package: ok } // Successful load. args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"} imp := importer.New(ctxt) infos, rest, err := imp.LoadInitialPackages(args) if err != nil { t.Errorf("LoadInitialPackages(%q) failed: %s", args, err) return } if got, want := fmt.Sprint(rest), "[surplus]"; got != want { t.Errorf("LoadInitialPackages(%q) rest: got %s, want %s", got, want) } // Check list of initial packages. var pkgnames []string for _, info := range infos { pkgnames = append(pkgnames, info.Pkg.Path()) } // Only the first import path (currently) contributes tests. if got, want := fmt.Sprint(pkgnames), "[fmt fmt_test errors P]"; got != want { t.Errorf("InitialPackages: got %s, want %s", got, want) } // Check set of transitive packages. // There are >30 and the set may grow over time, so only check a few. all := map[string]struct{}{} for _, info := range imp.AllPackages() { all[info.Pkg.Path()] = struct{}{} } want := []string{"strings", "time", "runtime", "testing", "unicode"} for _, w := range want { if _, ok := all[w]; !ok { t.Errorf("AllPackages: want element %s, got set %v", w, all) } } }
// toSSA converts go source to SSA func toSSA(source io.Reader, fileName, packageName string, debug bool) ([]byte, error) { // adopted from saa package example imp := importer.New(&importer.Config{Build: &build.Default}) file, err := parser.ParseFile(imp.Fset, fileName, source, 0) if err != nil { return nil, err } mainInfo := imp.CreatePackage(packageName, file) var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { return nil, err } mainPkg := prog.Package(mainInfo.Pkg) out := new(bytes.Buffer) mainPkg.SetDebugMode(debug) mainPkg.DumpTo(out) mainPkg.Build() // grab just the functions funcs := members([]ssa.Member{}) for _, obj := range mainPkg.Members { if obj.Token() == token.FUNC { funcs = append(funcs, obj) } } // sort by Pos() sort.Sort(funcs) for _, f := range funcs { mainPkg.Func(f.Name()).DumpTo(out) } return out.Bytes(), nil }
func run(t *testing.T, dir, input string) bool { fmt.Printf("Input: %s\n", input) start := time.Now() var inputs []string for _, i := range strings.Split(input, " ") { inputs = append(inputs, dir+i) } imp := importer.New(&importer.Config{Build: &build.Default}) // TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles. files, err := importer.ParseFiles(imp.Fset, ".", inputs...) if err != nil { t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error()) return false } // Print a helpful hint if we don't make it to the end. var hint string defer func() { if hint != "" { fmt.Println("FAIL") fmt.Println(hint) } else { fmt.Println("PASS") } }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) mainInfo := imp.LoadMainPackage(files...) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Errorf("CreatePackages failed: %s", err) return false } prog.BuildAll() mainPkg := prog.Package(mainInfo.Pkg) mainPkg.CreateTestMainFunction() // (no-op if main already exists) hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 { t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode) return false } hint = "" // call off the hounds if false { fmt.Println(input, time.Since(start)) // test profiling } return true }
func TestSwitches(t *testing.T) { imp := importer.New(new(importer.Config)) // (uses GCImporter) f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments) if err != nil { t.Error(err) return } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } mainPkg := prog.Package(mainInfo.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 TestMultipleQueries(t *testing.T) { // Importer var buildContext = build.Default buildContext.GOPATH = "testdata" imp := importer.New(&importer.Config{Build: &buildContext}) // Oracle filename := "testdata/src/main/multi.go" o, err := oracle.New(imp, []string{filename}, nil, true) if err != nil { t.Fatalf("oracle.New failed: %s", err) } // QueryPos pos := filename + ":#54,#58" qpos, err := oracle.ParseQueryPos(imp, pos, true) if err != nil { t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err) } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. imp = nil // Run different query moes on same scope and selection. out := new(bytes.Buffer) for _, mode := range [...]string{"callers", "describe", "freevars"} { res, err := o.Query(mode, qpos) if err != nil { t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err) } capture := new(bytes.Buffer) // capture standard output res.WriteTo(capture) for _, line := range strings.Split(capture.String(), "\n") { fmt.Fprintf(out, "%s\n", stripLocation(line)) } } want := `multi.f is called from these 1 sites: static function call from multi.main function call (or conversion) of type () Free identifiers: var x int ` if got := out.String(); got != want { t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got) } }
// Query runs a single oracle query. // // args specify the main package in importer.LoadInitialPackages syntax. // mode is the query mode ("callers", etc). // ptalog is the (optional) pointer-analysis log file. // buildContext is the go/build configuration for locating packages. // reflection determines whether to model reflection soundly (currently slow). // // Clients that intend to perform multiple queries against the same // analysis scope should use this pattern instead: // // imp := importer.New(&importer.Config{Build: buildContext}) // o, err := oracle.New(imp, args, nil) // if err != nil { ... } // for ... { // qpos, err := oracle.ParseQueryPos(imp, pos, needExact) // if err != nil { ... } // // res, err := o.Query(mode, qpos) // if err != nil { ... } // // // use res // } // // TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos // depends on the query mode; how should we expose this? // func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) { if mode == "what" { // Bypass package loading, type checking, SSA construction. return what(pos, buildContext) } minfo := findMode(mode) if minfo == nil { return nil, fmt.Errorf("invalid mode type: %q", mode) } impcfg := importer.Config{Build: buildContext} // For queries needing only a single typed package, // reduce the analysis scope to that package. if minfo.needs&(needSSA|needRetainTypeInfo) == 0 { reduceScope(pos, &impcfg, &args) } // TODO(adonovan): report type errors to the user via Serial // types, not stderr? // impcfg.TypeChecker.Error = func(err error) { // E := err.(types.Error) // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg) // } imp := importer.New(&impcfg) o, err := newOracle(imp, args, ptalog, minfo.needs, reflection) if err != nil { return nil, err } var qpos *QueryPos if minfo.needs&(needPos|needExactPos) != 0 { qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0) if err != nil { return nil, err } } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. imp = nil return o.query(minfo, qpos) }
// main creates a Go program that adds to a github.com/0xfaded/eval // environment (of type eval.Env) the transitive closure of imports // for a given starting package. Here we use github.com/0xfaded/eval. func main() { startingImport := DefaultStartingImport if len(os.Args) == 2 { startingImport = os.Args[1] } else if len(os.Args) > 2 { fmt.Printf("usage: %s [starting-import]\n") os.Exit(1) } fmt.Printf("// starting import: \"%s\"\n", startingImport) impctx := importer.Config{Build: &build.Default} // Load, parse and type-check the program. imp := importer.New(&impctx) var pkgs_string []string = make([]string, 0, 10) pkgs_string = append(pkgs_string, startingImport) //pkgs_string = append(pkgs_string, "fmt") pkg_infos, _, err := imp.LoadInitialPackages(pkgs_string) if err != nil { log.Fatal(err) } pkg_infos = imp.AllPackages() var errpkgs []string pkg_infos = writePreamble(pkg_infos, "Eval", startingImport) for _, pkg_info := range pkg_infos { if pkg_info.Err != nil { errpkgs = append(errpkgs, pkg_info.Pkg.Path()) } else { extractPackageSymbols(pkg_info, imp) } } if errpkgs != nil { log.Fatal("couldn't create these SSA packages due to type errors: %s", strings.Join(errpkgs, ", ")) } writePostamble() }
// CreateTestMainPackage should return nil if there were no tests. func TestNullTestmainPackage(t *testing.T) { imp := importer.New(&importer.Config{Build: &build.Default}) files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go") if err != nil { t.Fatalf("ParseFiles failed: %s", err) } mainInfo := imp.CreatePackage("b", files...) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Fatalf("CreatePackages failed: %s", err) } mainPkg := prog.Package(mainInfo.Pkg) if mainPkg.Func("main") != nil { t.Fatalf("unexpected main function") } if prog.CreateTestMainPackage(mainPkg) != nil { t.Fatalf("CreateTestMainPackage returned non-nil") } }
func main() { flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) } flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { if err == flag.ErrHelp { fmt.Println(helpMessage) fmt.Println("Flags:") flag.PrintDefaults() } os.Exit(2) } args = flag.Args() if len(args) == 0 { fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp) os.Exit(2) } var err error imp = importer.New(&importer.Config{Build: &build.Default}) ora, err = oracle.New(imp, args, nil, false) if err != nil { log.Fatal(err) } files = scopeFiles(imp) packages = imp.AllPackages() sort.Sort(byPath(packages)) registerHandlers() srv := &http.Server{Addr: *httpAddr} l, err := net.Listen("tcp", srv.Addr) if err != nil { log.Fatal(err) } if *open { url := fmt.Sprintf("http://localhost%s/", *httpAddr) if !startBrowser(url) { fmt.Println(url) } } log.Fatal(srv.Serve(l)) }
// Query runs a single oracle query. // // args specify the main package in importer.LoadInitialPackages syntax. // mode is the query mode ("callers", etc). // ptalog is the (optional) pointer-analysis log file. // buildContext is the go/build configuration for locating packages. // reflection determines whether to model reflection soundly (currently slow). // // Clients that intend to perform multiple queries against the same // analysis scope should use this pattern instead: // // imp := importer.New(&importer.Config{Build: buildContext}) // o, err := oracle.New(imp, args, nil) // if err != nil { ... } // for ... { // qpos, err := oracle.ParseQueryPos(imp, pos, needExact) // if err != nil { ... } // // res, err := o.Query(mode, qpos) // if err != nil { ... } // // // use res // } // // TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos // depends on the query mode; how should we expose this? // func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) { minfo := findMode(mode) if minfo == nil { return nil, fmt.Errorf("invalid mode type: %q", mode) } imp := importer.New(&importer.Config{Build: buildContext}) o, err := New(imp, args, ptalog, reflection) if err != nil { return nil, err } // Phase timing diagnostics. // TODO(adonovan): needs more work. // if false { // defer func() { // fmt.Println() // for name, duration := range o.timers { // fmt.Printf("# %-30s %s\n", name, duration) // } // }() // } var qpos *QueryPos if minfo.needs&(needPos|needExactPos) != 0 { var err error qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0) if err != nil { return nil, err } } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. imp = nil return o.query(minfo, qpos) }
func doOneInput(input, filename string) bool { impctx := &importer.Config{Build: &build.Default} imp := importer.New(impctx) // Parsing. f, err := parser.ParseFile(imp.Fset, filename, input, 0) if err != nil { // TODO(adonovan): err is a scanner error list; // display all errors not just first? fmt.Println(err) return false } // Create single-file main package and import its dependencies. info := imp.CreatePackage("main", f) // SSA creation + building. prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { fmt.Println(err) return false } prog.BuildAll() mainpkg := prog.Package(info.Pkg) 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) } 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) t, _, err = types.EvalNode(imp.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 } } 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 probes []probe var log bytes.Buffer // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, Print: func(site *ssa.CallCommon, p pointer.Pointer) { probes = append(probes, probe{site, p}) }, } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result := pointer.Analyze(config) // Check the expectations. for _, e := range exps { var pr *probe if e.needsProbe() { if pr = findProbe(prog, probes, e); pr == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } if pr.arg0 == nil { ok = false e.errorf("expectation on non-pointerlike operand: %s", pr.instr.Args[0].Type()) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, pr, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pr) { 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 TestObjValueLookup(t *testing.T) { imp := importer.New(new(importer.Config)) // (uses GCImporter) f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments) if err != nil { t.Error(err) return } // 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 := imp.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 } } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() // Gather all idents and objects in file. objs := make(map[types.Object]bool) var ids []*ast.Ident ast.Inspect(f, func(n ast.Node) bool { if id, ok := n.(*ast.Ident); ok { ids = append(ids, id) if obj := mainInfo.ObjectOf(id); obj != nil { objs[obj] = true } } return true }) // Check invariants for func and const objects. for obj := range objs { switch obj := obj.(type) { case *types.Func: checkFuncValue(t, prog, obj) case *types.Const: checkConstValue(t, prog, obj) } } // Check invariants for var objects. // The result varies based on the specific Ident. for _, id := range ids { if id.Name == "_" { continue } if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok { ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos()) pos := imp.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) { imp := importer.New(new(importer.Config)) // (uses GCImporter) f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments) if err != nil { t.Error(err) return } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } 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.DumpTo(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 := imp.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, _ := importer.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.IsIdentical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } }
// 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 in this package if you want a // standalone tool with similar functionality. // func Example() { const hello = ` package main import "fmt" const message = "Hello, World!" func main() { fmt.Println(message) } ` // Construct an importer. Imports will be loaded as if by 'go build'. imp := importer.New(&importer.Config{Build: &build.Default}) // Parse the input file. file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0) if err != nil { fmt.Print(err) // parse error return } // Create single-file main package and import its dependencies. mainInfo := imp.CreatePackage("main", file) // Create SSA-form program representation. var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { fmt.Print(err) // type error in some package return } mainPkg := prog.Package(mainInfo.Pkg) // Print out the package. mainPkg.DumpTo(os.Stdout) // Build SSA code for bodies of functions in mainPkg. mainPkg.Build() // Print out the package-level functions. mainPkg.Func("init").DumpTo(os.Stdout) mainPkg.Func("main").DumpTo(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.init.done else 1.init.start // .1.init.start: P:1 S:1 // *init$guard = true:bool // t1 = fmt.init() () // jump 2.init.done // .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:untyped integer] *interface{} // t2 = make interface{} <- string ("Hello, World!":string) interface{} // *t1 = t2 // t3 = slice t0[:] []interface{} // t4 = fmt.Println(t3) (n int, err error) // return }
func TestStdlib(t *testing.T) { impctx := importer.Config{Build: &build.Default} // Load, parse and type-check the program. t0 := time.Now() imp := importer.New(&impctx) if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil { t.Errorf("LoadInitialPackages failed: %s", err) return } t1 := time.Now() runtime.GC() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Create SSA packages. prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Errorf("CreatePackages failed: %s", err) return } // Enable debug mode globally. for _, info := range imp.AllPackages() { prog.Package(info.Pkg).SetDebugMode(debugMode) } t2 := time.Now() // Build SSA IR... if it's safe. prog.BuildAll() t3 := time.Now() runtime.GC() runtime.ReadMemStats(&memstats) numPkgs := len(prog.AllPackages()) if want := 140; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Dump some statistics. allFuncs := ssa.AllFunctions(prog) var numInstrs int for fn := range allFuncs { for _, b := range fn.Blocks { numInstrs += len(b.Instrs) } } t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) t.Log("Load/parse/typecheck: ", t1.Sub(t0)) t.Log("SSA create: ", t2.Sub(t1)) t.Log("SSA build: ", t3.Sub(t2)) // SSA stats: t.Log("#Packages: ", numPkgs) t.Log("#Functions: ", len(allFuncs)) t.Log("#Instructions: ", numInstrs) t.Log("#MB: ", (memstats.Alloc-alloc)/1000000) }
// TestMethodSets tests that Package.TypesWithMethodSets includes all necessary types. func TestTypesWithMethodSets(t *testing.T) { tests := []struct { input string want []string }{ // An exported package-level type is needed. {`package p; type T struct{}; func (T) f() {}`, []string{"*p.T", "p.T"}, }, // An unexported package-level type is not needed. {`package p; type t struct{}; func (t) f() {}`, nil, }, // Subcomponents of type of exported package-level var are needed. {`package p; import "bytes"; var V struct {*bytes.Buffer}`, []string{"struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level var are not needed. {`package p; import "bytes"; var v struct {*bytes.Buffer}`, nil, }, // Subcomponents of type of exported package-level function are needed. {`package p; import "bytes"; func F(struct {*bytes.Buffer}) {}`, []string{"struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level function are not needed. {`package p; import "bytes"; func f(struct {*bytes.Buffer}) {}`, nil, }, // Subcomponents of type of exported method are needed. {`package p; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}`, []string{"*p.x", "p.x", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported method are not needed. {`package p; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, []string{"*p.X", "p.X", "struct{*bytes.Buffer}"}, }, // Local types aren't needed. {`package p; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, nil, }, // ...unless used by MakeInterface. {`package p; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, []string{"*p.T", "p.T"}, }, // Types used as operand of MakeInterface are needed. {`package p; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, []string{"struct{*bytes.Buffer}"}, }, // MakeInterface is optimized away when storing to a blank. {`package p; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, nil, }, } for i, test := range tests { imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0) if err != nil { t.Errorf("test %d: %s", i, err) continue } mainInfo := imp.CreatePackage("p", f) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Errorf("test %d: %s", i, err) continue } mainPkg := prog.Package(mainInfo.Pkg) prog.BuildAll() var typstrs []string for _, T := range mainPkg.TypesWithMethodSets() { typstrs = append(typstrs, T.String()) } sort.Strings(typstrs) if !reflect.DeepEqual(typstrs, test.want) { t.Errorf("test %d: got %q, want %q", i, typstrs, test.want) } } }
func (compiler *compiler) Compile(filenames []string, importpath string) (m *Module, err error) { // FIXME create a compilation state, rather than storing in 'compiler'. compiler.llvmtypes = NewLLVMTypeMap(compiler.target) buildctx, err := llgobuild.ContextFromTriple(compiler.TargetTriple) if err != nil { return nil, err } impcfg := &goimporter.Config{ TypeChecker: types.Config{ Import: llgoimporter.NewImporter(buildctx).Import, Sizes: compiler.llvmtypes, }, Build: &buildctx.Context, } compiler.typechecker = &impcfg.TypeChecker compiler.importer = goimporter.New(impcfg) program := ssa.NewProgram(compiler.importer.Fset, 0) astFiles, err := parseFiles(compiler.importer.Fset, filenames) if err != nil { return nil, err } // If no import path is specified, or the package's // name (not path) is "main", then set the import // path to be the same as the package's name. if pkgname := astFiles[0].Name.String(); importpath == "" || pkgname == "main" { importpath = pkgname } mainPkginfo := compiler.importer.CreatePackage(importpath, astFiles...) if mainPkginfo.Err != nil { return nil, mainPkginfo.Err } // First call CreatePackages to resolve imports, and then CreatePackage // to obtain the main package. The latter simply returns the package // created by the former. if err := program.CreatePackages(compiler.importer); err != nil { return nil, err } mainpkg := program.CreatePackage(mainPkginfo) // Create a Module, which contains the LLVM bitcode. Dispose it on panic, // otherwise we'll set a finalizer at the end. The caller may invoke // Dispose manually, which will render the finalizer a no-op. modulename := importpath compiler.module = &Module{llvm.NewModule(modulename), modulename, false} compiler.module.SetTarget(compiler.TargetTriple) compiler.module.SetDataLayout(compiler.target.String()) // Map runtime types and functions. runtimePkginfo := mainPkginfo runtimePkg := mainpkg if importpath != "runtime" { astFiles, err := parseRuntime(&buildctx.Context, compiler.importer.Fset) if err != nil { return nil, err } runtimePkginfo = compiler.importer.CreatePackage("runtime", astFiles...) if runtimePkginfo.Err != nil { return nil, err } runtimePkg = program.CreatePackage(runtimePkginfo) } // Create a new translation unit. unit := newUnit(compiler, mainpkg) // Create the runtime interface. compiler.runtime, err = newRuntimeInterface( runtimePkg.Object, compiler.module.Module, compiler.llvmtypes, FuncResolver(unit), ) if err != nil { return nil, err } // Create a struct responsible for mapping static types to LLVM types, // and to runtime/dynamic type values. compiler.types = NewTypeMap( importpath, compiler.llvmtypes, compiler.module.Module, compiler.runtime, MethodResolver(unit), ) // Create a Builder, for building LLVM instructions. compiler.builder = llvm.GlobalContext().NewBuilder() defer compiler.builder.Dispose() mainpkg.Build() unit.translatePackage(mainpkg) compiler.processAnnotations(unit, mainPkginfo) if runtimePkginfo != mainPkginfo { compiler.processAnnotations(unit, runtimePkginfo) } /* compiler.debug_info = &llvm.DebugInfo{} // Compile each file in the package. for _, file := range files { if compiler.GenerateDebug { cu := &llvm.CompileUnitDescriptor{ Language: llvm.DW_LANG_Go, Path: llvm.FileDescriptor(fset.File(file.Pos()).Name()), Producer: LLGOProducer, Runtime: LLGORuntimeVersion, } compiler.pushDebugContext(cu) compiler.pushDebugContext(&cu.Path) } for _, decl := range file.Decls { compiler.VisitDecl(decl) } if compiler.GenerateDebug { compiler.popDebugContext() cu := compiler.popDebugContext() if len(compiler.debug_context) > 0 { log.Panicln(compiler.debug_context) } compiler.module.AddNamedMetadataOperand( "llvm.dbg.cu", compiler.debug_info.MDNode(cu), ) } } */ // Export runtime type information. var exportedTypes []types.Type for _, m := range mainpkg.Members { if t, ok := m.(*ssa.Type); ok && ast.IsExported(t.Name()) { exportedTypes = append(exportedTypes, t.Type()) } } compiler.exportRuntimeTypes(exportedTypes, importpath == "runtime") if importpath == "main" { // Wrap "main.main" in a call to runtime.main. if err = compiler.createMainFunction(); err != nil { return nil, fmt.Errorf("failed to create main.main: %v", err) } } else { if err := llgoimporter.Export(buildctx, mainpkg.Object); err != nil { return nil, fmt.Errorf("failed to export package data: %v", err) } } return compiler.module, nil }
func run(t *testing.T, dir, input string, success successPredicate) bool { fmt.Printf("Input: %s\n", input) start := time.Now() var inputs []string for _, i := range strings.Split(input, " ") { inputs = append(inputs, dir+i) } imp := importer.New(&importer.Config{Build: &build.Default}) // TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles. // Then add the following packages' tests, which pass: // "flag", "unicode", "unicode/utf8", "testing", "log", "path". files, err := importer.ParseFiles(imp.Fset, ".", inputs...) if err != nil { t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error()) return false } // Print a helpful hint if we don't make it to the end. var hint string defer func() { if hint != "" { fmt.Println("FAIL") fmt.Println(hint) } else { fmt.Println("PASS") } interp.CapturedOutput = nil }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) mainInfo := imp.CreatePackage(files[0].Name.Name, files...) if _, err := imp.LoadPackage("runtime"); err != nil { t.Errorf("LoadPackage(runtime) failed: %s", err) } prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Errorf("CreatePackages failed: %s", err) return false } prog.BuildAll() mainPkg := prog.Package(mainInfo.Pkg) if mainPkg.Func("main") == nil { testmainPkg := prog.CreateTestMainPackage(mainPkg) if testmainPkg == nil { t.Errorf("CreateTestMainPackage(%s) returned nil", mainPkg) return false } if testmainPkg.Func("main") == nil { t.Errorf("synthetic testmain package has no main") return false } mainPkg = testmainPkg } var out bytes.Buffer interp.CapturedOutput = &out hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}) // The definition of success varies with each file. if err := success(exitCode, out.String()); err != nil { t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) return false } hint = "" // call off the hounds if false { fmt.Println(input, time.Since(start)) // test profiling } return true }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { imp := importer.New(new(importer.Config)) // (uses GCImporter) f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.DeclarationErrors|parser.ParseComments) if err != nil { t.Error(err) return } mainInfo := imp.LoadMainPackage(f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() fn := mainPkg.Func("f") if false { fn.DumpTo(os.Stderr) // debugging } // 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 := imp.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 } v := 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 { if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), v.Type()) } } } }
func main() { flag.Parse() args := flag.Args() impctx := importer.Config{Build: &build.Default} var debugMode bool var mode ssa.BuilderMode for _, c := range *buildFlag { switch c { case 'D': debugMode = true case 'P': mode |= ssa.LogPackages | ssa.BuildSerially case 'F': mode |= ssa.LogFunctions | ssa.BuildSerially case 'S': mode |= ssa.LogSource | ssa.BuildSerially case 'C': mode |= ssa.SanityCheckFunctions case 'N': mode |= ssa.NaiveForm case 'G': impctx.Build = nil case 'L': mode |= ssa.BuildSerially default: log.Fatalf("Unknown -build option: '%c'.", c) } } var interpMode interp.Mode for _, c := range *interpFlag { switch c { case 'T': interpMode |= interp.EnableTracing case 'R': interpMode |= interp.DisableRecover default: log.Fatalf("Unknown -interp option: '%c'.", c) } } if len(args) == 0 { fmt.Fprint(os.Stderr, usage) os.Exit(1) } // Profiling support. if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } // Load, parse and type-check the program. imp := importer.New(&impctx) infos, args, err := imp.LoadInitialPackages(args) if err != nil { log.Fatal(err) } // Create and build SSA-form program representation. prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { log.Fatal(err) } if debugMode { for _, pkg := range prog.AllPackages() { pkg.SetDebugMode(true) } } prog.BuildAll() // Run the interpreter on the first package with a main function. if *runFlag { var main *ssa.Package for _, info := range infos { pkg := prog.Package(info.Pkg) if pkg.Func("main") != nil || pkg.CreateTestMainFunction() != nil { main = pkg break } } if main == nil { log.Fatal("No main function") } interp.Interpret(main, interpMode, main.Object.Path(), args) } }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // func Example() { const myprog = ` package main import "fmt" type I interface { f() } type C struct{} func (C) f() { fmt.Println("C.f()") } func main() { var i I = C{} i.f() // dynamic method call } ` // Construct an importer. // Imports will be loaded as if by 'go build'. imp := importer.New(&importer.Config{Build: &build.Default}) // Parse the input file. file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, parser.DeclarationErrors) if err != nil { fmt.Print(err) // parse error return } // Create a "main" package containing one file. mainInfo := imp.LoadMainPackage(file) // Create SSA-form program representation. var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { fmt.Print(err) // type error in some package return } mainPkg := prog.Package(mainInfo.Pkg) // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() // Run the pointer analysis and build the complete callgraph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } result := pointer.Analyze(config) // 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 call.GraphVisitEdges(result.CallGraph, func(edge call.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) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f }
// TODO(adonovan): move this test into ssa. 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", "[email protected]"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "[email protected]"}, // 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", "[email protected]"}, } for _, test := range tests { imp := importer.New(new(importer.Config)) // (NB: no go/build.Config) f, start, end := findInterval(t, imp.Fset, test.input, test.substr) if f == nil { continue } path, exact := importer.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) continue } pkg := prog.Package(mainInfo.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 } } }
// Tests that programs partially loaded from gc object files contain // functions with no code for the external portions, but are otherwise ok. func TestExternalPackages(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 } ` imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors) if err != nil { t.Error(err) return } mainInfo := imp.LoadMainPackage(f) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } mainPkg := prog.Package(mainInfo.Pkg) mainPkg.Build() // Only the main package and its immediate dependencies are loaded. deps := []string{"bytes", "io", "testing"} all := prog.AllPackages() if len(all) != 1+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 := types.NewPointer(mem.Type()).MethodSet() 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) } }