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() }
func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) { o := &Oracle{fset: iprog.Fset} // Retain type info for all ASTs in the program. if needs&needRetainTypeInfo != 0 { o.typeInfo = iprog.AllPackages } // Create SSA package for the initial packages and their dependencies. if needs&needSSA != 0 { var mode ssa.BuilderMode if needs&needSSADebug != 0 { mode |= ssa.GlobalDebug } prog := ssa.Create(iprog, mode) // For each initial package (specified on the command line), // if it has a main function, analyze that, // otherwise analyze its tests, if any. var testPkgs, mains []*ssa.Package for _, info := range iprog.InitialPackages() { initialPkg := prog.Package(info.Pkg) // Add package to the pointer analysis scope. if initialPkg.Func("main") != nil { mains = append(mains, initialPkg) } else { testPkgs = append(testPkgs, initialPkg) } } if testPkgs != nil { if p := prog.CreateTestMainPackage(testPkgs...); p != nil { mains = append(mains, p) } } if mains == nil { return nil, fmt.Errorf("analysis scope has no main and no tests") } o.ptaConfig.Log = ptalog o.ptaConfig.Reflection = reflection o.ptaConfig.Mains = mains o.prog = prog } return o, nil }
// CreateTestMainPackage should return nil if there were no tests. func TestNullTestmainPackage(t *testing.T) { var conf loader.Config if err := conf.CreateFromFilenames("", "testdata/b_test.go"); err != nil { t.Fatalf("ParseFile failed: %s", err) } iprog, err := conf.Load() if err != nil { t.Fatalf("CreatePackages failed: %s", err) } prog := ssa.Create(iprog, ssa.SanityCheckFunctions) mainPkg := prog.Package(iprog.Created[0].Pkg) if mainPkg.Func("main") != nil { t.Fatalf("unexpected main function") } if prog.CreateTestMainPackage(mainPkg) != nil { t.Fatalf("CreateTestMainPackage returned non-nil") } }
// TestTypesWithMethodSets 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 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{"*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{"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{"*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{"*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{"*p.T", "p.T"}, }, // Types used as operand of MakeInterface are needed. {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, []string{"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. var conf loader.Config f, err := conf.ParseFile("<input>", test.input, 0) 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) mainPkg := prog.Package(iprog.Created[0].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 '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.LogFunctions*/) 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 doOneInput(input, filename string) bool { conf := loader.Config{SourceImports: true} // 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 { if b, ok := instr.Common().Value.(*ssa.Builtin); ok && b.Name() == "print" { 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) t, _, 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 } } 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 }
func doTestable(args []string) error { conf := loader.Config{ Build: &build.Default, SourceImports: true, } // TODO(adonovan): make go/types choose its default Sizes from // build.Default or a specified *build.Context. var wordSize int64 = 8 switch conf.Build.GOARCH { case "386", "arm": wordSize = 4 } wordSize = 4 // TARDIS Go addition to force default int size to 32 bits //conf.Build.GOARCH = "tardisgo" // TARDIS Go addition to ensure no architecure-specific code will compile //conf.Build.GOOS = "tardisgo" // TARDIS Go addition to ensure no OS-specific code will compile conf.TypeChecker.Sizes = &types.StdSizes{ MaxAlign: 8, WordSize: wordSize, } var mode ssa.BuilderMode for _, c := range *buildFlag { switch c { case 'D': mode |= ssa.GlobalDebug 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': conf.SourceImports = false 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) return fmt.Errorf("%v", usage) } // Profiling support. if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { return err } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } // TARDIS Go TEST // Really need to find a way to replace entire packages, this experiment did not work... /* conf.Fset = token.NewFileSet() f, err := parser.ParseFile(conf.Fset, conf.Build.GOPATH+"/src/github.com/tardisgo/tardisgo/golibruntime/runtime/runtime.go", nil, 0) if err != nil { fmt.Println(err) return err } conf.CreateFromFiles("", f) fmt.Printf("DEBUG %s %s\n", f.Name.Name, "") //, f.Name.Obj.Name) */ // end TARDIS Go TEST // Use the initial packages from the command line. args, err := conf.FromArgs(args, *testFlag) if err != nil { return err } // The interpreter needs the runtime package. if *runFlag { conf.Import("runtime") conf.Import("github.com/tardisgo/tardisgo/golibruntime/runtime") // This required for TARDIS go to run runtime } // TARDIS GO additional line to add the language specific go runtime code conf.Import(pogo.LanguageList[pogo.TargetLang].Goruntime) // TODO add code to set pogo.TargetLang when more than one of them // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. prog := ssa.Create(iprog, mode) prog.BuildAll() // Run the interpreter. if *runFlag { var main *ssa.Package pkgs := prog.AllPackages() if *testFlag { // If -test, run all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } } else { // Otherwise, run main.main. for _, pkg := range pkgs { if pkg.Object.Name() == "main" { main = pkg if main.Func("main") == nil { return fmt.Errorf("no func main() in main package") } break } } if main == nil { return fmt.Errorf("no main package") } } // NOTE TARDIS Go removal of this test required if we alter the GOARCH to stop architecture-specific code if runtime.GOARCH != build.Default.GOARCH { return fmt.Errorf("cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s)", build.Default.GOARCH, runtime.GOARCH) } interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args) } // TARDIS Go additions: copy run interpreter code above, but call pogo class if true { var main *ssa.Package pkgs := prog.AllPackages() if *testFlag { // If -test, run all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } } else { // Otherwise, run main.main. for _, pkg := range pkgs { if pkg.Object.Name() == "main" { main = pkg if main.Func("main") == nil { return fmt.Errorf("no func main() in main package") } break } } if main == nil { return fmt.Errorf("no main package") } } /* if runtime.GOARCH != build.Default.GOARCH { return fmt.Errorf("cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s)", build.Default.GOARCH, runtime.GOARCH) } interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args) */ err = pogo.EntryPoint(main) // TARDIS Go entry point, returns an error if err != nil { return err } if *allFlag { targets := [][][]string{ [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-cpp", "cpp"}, []string{"echo", `"CPP:"`}, []string{"./cpp/Go"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-java", "java"}, []string{"echo", `"Java:"`}, []string{"java", "-jar", "java/Go.jar"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-cs", "cs"}, []string{"echo", `"CS:"`}, []string{"mono", "./cs/bin/Go.exe"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-neko", "tardisgo.n"}, []string{"echo", `"Neko:"`}, []string{"neko", "tardisgo.n"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-js", "tardisgo.js"}, []string{"echo", `"Node/JS:"`}, []string{"node", "tardisgo.js"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-swf", "tardisgo.swf"}, []string{"echo", `"Opening swf file (Chrome as a file association for swf works to test on OSX):"` + "\n"}, []string{"open", "tardisgo.swf"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-dce", "full", "-php", "php", "--php-prefix", "tgo"}, []string{"echo", `"PHP:"`}, []string{"php", "php/index.php"}, }, [][]string{ []string{"echo", ``}, // Output from this line is ignored []string{"echo", `"Neko (haxe --interp):"`}, []string{"haxe", "-main", "tardis.Go", "--interp"}, }, } results := make(chan string, len(targets)) for id, cmd := range targets { go func(i int, cl [][]string) { res := "" for j, c := range cl { exe := c[0] if exe == "echo" { res += c[1] } else { _, err := exec.LookPath(exe) if err != nil { switch exe { case "node": exe = "nodejs" // for Ubuntu default: res += "TARDISgo error - executable not found: " + exe + "\n" exe = "" // nothing to execute } } if exe != "" { out, err := exec.Command(exe, c[1:]...).CombinedOutput() if err != nil { out = append(out, []byte(err.Error())...) } if j > 0 { // ignore the output from the compile phase res += string(out) } } } } results <- res }(id, cmd) } for t := 0; t < len(targets); t++ { fmt.Println(<-results) } } } return nil }
// Run runs program analysis and computes the resulting markup, // populating *result in a thread-safe manner, first with type // information then later with pointer analysis information if // enabled by the pta flag. // func Run(pta bool, result *Result) { conf := loader.Config{ SourceImports: true, AllowTypeErrors: true, } errors := make(map[token.Pos][]string) conf.TypeChecker.Error = func(e error) { err := e.(types.Error) errors[err.Pos] = append(errors[err.Pos], err.Msg) } var roots, args []string // roots[i] ends with os.PathSeparator // Enumerate packages in $GOROOT. root := filepath.Join(runtime.GOROOT(), "src", "pkg") + string(os.PathSeparator) roots = append(roots, root) args = allPackages(root) log.Printf("GOROOT=%s: %s\n", root, args) // Enumerate packages in $GOPATH. for i, dir := range filepath.SplitList(build.Default.GOPATH) { root := filepath.Join(dir, "src") + string(os.PathSeparator) roots = append(roots, root) pkgs := allPackages(root) log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs) args = append(args, pkgs...) } // Uncomment to make startup quicker during debugging. //args = []string{"code.google.com/p/go.tools/cmd/godoc"} //args = []string{"fmt"} if _, err := conf.FromArgs(args, true); err != nil { log.Print(err) // import error return } log.Print("Loading and type-checking packages...") iprog, err := conf.Load() if iprog != nil { log.Printf("Loaded %d packages.", len(iprog.AllPackages)) } if err != nil { // TODO(adonovan): loader: don't give up just because // of one parse error. log.Print(err) // parse error in some package return } // Create SSA-form program representation. // Only the transitively error-free packages are used. prog := ssa.Create(iprog, ssa.GlobalDebug) // Compute the set of main packages, including testmain. allPackages := prog.AllPackages() var mainPkgs []*ssa.Package if testmain := prog.CreateTestMainPackage(allPackages...); testmain != nil { mainPkgs = append(mainPkgs, testmain) } for _, pkg := range allPackages { if pkg.Object.Name() == "main" && pkg.Func("main") != nil { mainPkgs = append(mainPkgs, pkg) } } log.Print("Main packages: ", mainPkgs) // Build SSA code for bodies of all functions in the whole program. log.Print("Building SSA...") prog.BuildAll() log.Print("SSA building complete") a := analysis{ result: result, prog: prog, pcgs: make(map[*ssa.Package]*packageCallGraph), } // Build a mapping from openable filenames to godoc file URLs, // i.e. "/src/pkg/" plus path relative to GOROOT/src/pkg or GOPATH[i]/src. a.path2url = make(map[string]string) for _, info := range iprog.AllPackages { nextfile: for _, f := range info.Files { abs := iprog.Fset.File(f.Pos()).Name() // Find the root to which this file belongs. for _, root := range roots { rel := strings.TrimPrefix(abs, root) if len(rel) < len(abs) { a.path2url[abs] = "/src/pkg/" + filepath.ToSlash(rel) continue nextfile } } log.Printf("Can't locate file %s (package %q) beneath any root", abs, info.Pkg.Path()) } } // Add links for type-checker errors. // TODO(adonovan): fix: these links can overlap with // identifier markup, causing the renderer to emit some // characters twice. for pos, errs := range errors { fi, offset := a.fileAndOffset(pos) fi.addLink(errorLink{ start: offset, msg: strings.Join(errs, "\n"), }) } // ---------- type-based analyses ---------- // Compute the all-pairs IMPLEMENTS relation. // Collect all named types, even local types // (which can have methods via promotion) // and the built-in "error". errorType := types.Universe.Lookup("error").Type().(*types.Named) a.allNamed = append(a.allNamed, errorType) for _, info := range iprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { a.allNamed = append(a.allNamed, obj.Type().(*types.Named)) } } } log.Print("Computing implements...") facts := computeImplements(&a.prog.MethodSets, a.allNamed) // Add the type-based analysis results. log.Print("Extracting type info...") for _, info := range iprog.AllPackages { a.doTypeInfo(info, facts) } a.visitInstrs(pta) log.Print("Extracting type info complete") if pta { a.pointer(mainPkgs) } }
func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: errorpaths scope pkg-pattern\n") fmt.Fprint(os.Stderr, loader.FromArgsUsage) } flag.Parse() args := flag.Args() if len(args) != 2 { flag.Usage() } conf := loader.Config{ SourceImports: true, } _, err := conf.FromArgs(args[0:1]) if err != nil { log.Fatalf("cannot initialise loader: %v", err) } pkgPat, err := regexp.Compile("^" + args[1] + "$") if err != nil { log.Fatalf("cann compile regexp %q: %s", args[1], err) } lprog, err := conf.Load() if err != nil { log.Fatalf("cannot load program: %v", err) } or, err := oracle.New(lprog, nil, false) if err != nil { log.Fatalf("cannot make oracle: %v", err) } ssaProg := ssa.Create(lprog, ssa.SanityCheckFunctions) ctxt := &context{ lprog: lprog, ssaProg: ssaProg, oracle: or, infos: make(map[*ssa.Function]*errorInfo), locs: make(map[*ssa.Function]errorLocations), } var foundPkgs []*types.Package log.Printf("searching %d packages", len(lprog.AllPackages)) for pkg, _ := range lprog.AllPackages { if pkgPat.MatchString(pkg.Path()) { foundPkgs = append(foundPkgs, pkg) break } } if len(foundPkgs) == 0 { log.Fatalf("failed to find any matching packages") } for _, pkg := range foundPkgs { log.Printf("package %s", pkg.Name()) ssaPkg := ssaProg.Package(pkg) ssaPkg.Build() for name, m := range ssaPkg.Members { log.Printf("name %s", name) if f, ok := m.(*ssa.Function); ok && returnsError(f) { fmt.Printf("%s\n", f) locs := ctxt.errorLocations(f) ctxt.dumpErrorLocs(locs, os.Stdout, "\t") } } } }
// 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 %q, want %q", f.Name.Name, initbuf.String(), test.want) } } }
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, " ") { if strings.HasSuffix(i, ".go") { i = dir + i } inputs = append(inputs, i) } conf := loader.Config{SourceImports: true} if _, err := conf.FromArgs(inputs, true); err != nil { t.Errorf("FromArgs(%s) failed: %s", inputs, err) return false } conf.Import("runtime") // 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) iprog, err := conf.Load() if err != nil { t.Errorf("conf.Load(%s) failed: %s", inputs, err) return false } prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() var mainPkg *ssa.Package var initialPkgs []*ssa.Package for _, info := range iprog.InitialPackages() { if info.Pkg.Path() == "runtime" { continue // not an initial package } p := prog.Package(info.Pkg) initialPkgs = append(initialPkgs, p) if mainPkg == nil && p.Func("main") != nil { mainPkg = p } } if mainPkg == nil { testmainPkg := prog.CreateTestMainPackage(initialPkgs...) 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, &types.StdSizes{8, 8}, 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 }
// 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 } ` // Create a single-file main package. var conf loader.Config f, err := conf.ParseFile("<input>", test, 0) 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) } }
func doMain() error { flag.Parse() args := flag.Args() conf := loader.Config{ Build: &build.Default, SourceImports: true, } // TODO(adonovan): make go/types choose its default Sizes from // build.Default or a specified *build.Context. var wordSize int64 = 8 switch conf.Build.GOARCH { case "386", "arm": wordSize = 4 } conf.TypeChecker.Sizes = &types.StdSizes{ MaxAlign: 8, WordSize: wordSize, } var mode ssa.BuilderMode for _, c := range *buildFlag { switch c { case 'D': mode |= ssa.GlobalDebug 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': conf.SourceImports = false case 'L': mode |= ssa.BuildSerially default: return fmt.Errorf("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: fmt.Fprintf(os.Stderr, "ssadump: unknown -interp option: '%c'.", c) os.Exit(1) } } if len(args) == 0 { fmt.Fprint(os.Stderr, usage) os.Exit(1) } // Profiling support. if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } // Use the initial packages from the command line. args, err := conf.FromArgs(args, *testFlag) if err != nil { return err } // The interpreter needs the runtime package. if *runFlag { conf.Import("runtime") } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. prog := ssa.Create(iprog, mode) prog.BuildAll() // Run the interpreter. if *runFlag { var main *ssa.Package pkgs := prog.AllPackages() if *testFlag { // If -test, run all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } } else { // Otherwise, run main.main. for _, pkg := range pkgs { if pkg.Object.Name() == "main" { main = pkg if main.Func("main") == nil { return fmt.Errorf("no func main() in main package") } break } } if main == nil { return fmt.Errorf("no main package") } } if runtime.GOARCH != build.Default.GOARCH { return fmt.Errorf("cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s)", build.Default.GOARCH, runtime.GOARCH) } interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args) } return nil }
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", "f$1"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "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", "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 } } }
// 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 } ` // Construct a loader. conf := loader.Config{SourceImports: true} // Parse the input file. file, err := conf.ParseFile("myprog.go", myprog, 0) 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 }
func (compiler *compiler) compile(filenames []string, importpath string) (m *Module, err error) { buildctx, err := llgobuild.ContextFromTriple(compiler.TargetTriple) if err != nil { return nil, err } impcfg := &loader.Config{ Fset: token.NewFileSet(), TypeChecker: types.Config{ Import: llgoimporter.NewImporter(buildctx).Import, Sizes: compiler.llvmtypes, }, Build: &buildctx.Context, } // Must use parseFiles, so we retain comments; // this is important for annotation processing. astFiles, err := parseFiles(impcfg.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 } impcfg.CreateFromFiles(importpath, astFiles...) // Create a "runtime" package too, so we can reference // its types and functions in the compiler and generated // code. if importpath != "runtime" { astFiles, err := parseRuntime(&buildctx.Context, impcfg.Fset) if err != nil { return nil, err } impcfg.CreateFromFiles("runtime", astFiles...) } iprog, err := impcfg.Load() if err != nil { return nil, err } program := ssa.Create(iprog, 0) var mainPkginfo, runtimePkginfo *loader.PackageInfo if pkgs := iprog.InitialPackages(); len(pkgs) == 1 { mainPkginfo, runtimePkginfo = pkgs[0], pkgs[0] } else { mainPkginfo, runtimePkginfo = pkgs[0], pkgs[1] } mainPkg := program.CreatePackage(mainPkginfo) // Create a Module, which contains the LLVM bitcode. modulename := importpath compiler.module = &Module{Module: llvm.NewModule(modulename), Name: modulename} compiler.module.SetTarget(compiler.TargetTriple) compiler.module.SetDataLayout(compiler.dataLayout) // Create a new translation unit. unit := newUnit(compiler, mainPkg) // Create the runtime interface. compiler.runtime, err = newRuntimeInterface( runtimePkginfo.Pkg, 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() // Initialise debugging. compiler.debug.module = compiler.module.Module compiler.debug.Fset = impcfg.Fset compiler.debug.Sizes = compiler.llvmtypes mainPkg.Build() unit.translatePackage(mainPkg) compiler.processAnnotations(unit, mainPkginfo) if runtimePkginfo != mainPkginfo { compiler.processAnnotations(unit, runtimePkginfo) } // Finalise debugging. for _, cu := range compiler.debug.cu { compiler.module.AddNamedMetadataOperand( "llvm.dbg.cu", compiler.debug.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 }
// 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 // code.google.com/p/go.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.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: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 }
func TestStdlib(t *testing.T) { if !*runStdlibTest { t.Skip("skipping (slow) stdlib test (use --stdlib)") } // Load, parse and type-check the program. ctxt := build.Default // copy ctxt.GOPATH = "" // disable GOPATH conf := loader.Config{ SourceImports: true, Build: &ctxt, } if _, err := conf.FromArgs(buildutil.AllPackages(conf.Build), true); err != nil { t.Errorf("FromArgs failed: %v", err) return } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } // Create SSA packages. prog := ssa.Create(iprog, 0) prog.BuildAll() numPkgs := len(prog.AllPackages()) if want := 240; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Determine the set of packages/tests to analyze. var testPkgs []*ssa.Package for _, info := range iprog.InitialPackages() { testPkgs = append(testPkgs, prog.Package(info.Pkg)) } testmain := prog.CreateTestMainPackage(testPkgs...) if testmain == nil { t.Fatal("analysis scope has tests") } // Run the analysis. config := &Config{ Reflection: false, // TODO(adonovan): fix remaining bug in rVCallConstraint, then enable. BuildCallGraph: true, Mains: []*ssa.Package{testmain}, } // TODO(adonovan): add some query values (affects track bits). t0 := time.Now() result, err := Analyze(config) if err != nil { t.Fatal(err) // internal error in pointer analysis } _ = result // TODO(adonovan): measure something t1 := time.Now() // Dump some statistics. allFuncs := ssautil.AllFunctions(prog) var numInstrs int for fn := range allFuncs { for _, b := range fn.Blocks { numInstrs += len(b.Instrs) } } // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) t.Log("#Source lines: ", lineCount) t.Log("#Instructions: ", numInstrs) t.Log("Pointer analysis: ", t1.Sub(t0)) }
func TestStdlib(t *testing.T) { // Load, parse and type-check the program. t0 := time.Now() var conf loader.Config conf.SourceImports = true if _, err := conf.FromArgs(allPackages(), true); err != nil { t.Errorf("FromArgs failed: %v", err) return } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } t1 := time.Now() runtime.GC() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Create SSA packages. var mode ssa.BuilderMode // Comment out these lines during benchmarking. Approx SSA build costs are noted. mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time mode |= ssa.GlobalDebug // +30% space, +18% time prog := ssa.Create(iprog, mode) t2 := time.Now() // Build SSA. 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) } allFuncs := ssautil.AllFunctions(prog) // Check that all non-synthetic functions have distinct names. byName := make(map[string]*ssa.Function) for fn := range allFuncs { if fn.Synthetic == "" { str := fn.String() prev := byName[str] byName[str] = fn if prev != nil { t.Errorf("%s: duplicate function named %s", prog.Fset.Position(fn.Pos()), str) t.Errorf("%s: (previously defined here)", prog.Fset.Position(prev.Pos())) } } } // Dump some statistics. var numInstrs int for fn := range allFuncs { for _, b := range fn.Blocks { numInstrs += len(b.Instrs) } } // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) // NB: when benchmarking, don't forget to clear the debug + // sanity builder flags for better performance. t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) t.Log("#Source lines: ", lineCount) 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: ", int64(memstats.Alloc-alloc)/1000000) }