// CheckPackages checks packages for errors. // ignore is a map of package names to regular expressions. Identifiers from a package are // checked against its regular expressions and if any of the expressions match the call // is not checked. // If blank is true then assignments to the blank identifier are also considered to be // ignored errors. func CheckPackages(pkgPaths []string, ignore map[string]*regexp.Regexp, blank bool) error { var loadcfg = loader.Config{ SourceImports: true, AllowErrors: false, } for _, p := range pkgPaths { loadcfg.Import(p) } program, err := loadcfg.Load() if err != nil { return fmt.Errorf("could not type check: %s", err) } visitor := &checker{program, nil, ignore, blank, make(map[string][]string), []error{}} for _, p := range pkgPaths { if p == "unsafe" { // not a real package continue } visitor.pkg = program.Imported[p] for _, astFile := range visitor.pkg.Files { ast.Walk(visitor, astFile) } } if len(visitor.errors) > 0 { return UncheckedErrors{visitor.errors} } return nil }
func TestTransitivelyErrorFreeFlag(t *testing.T) { // Create an minimal custom build.Context // that fakes the following packages: // // a --> b --> c! c has an error // \ d and e are transitively error-free. // e --> d // // Each package [a-e] consists of one file, x.go. pkgs := map[string]string{ "a": `package a; import (_ "b"; _ "e")`, "b": `package b; import _ "c"`, "c": `package c; func f() { _ = int(false) }`, // type error within function body "d": `package d;`, "e": `package e; import _ "d"`, } conf := loader.Config{ AllowErrors: true, SourceImports: true, Build: fakeContext(pkgs), } conf.Import("a") prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %s", err) } if prog == nil { t.Fatalf("Load returned nil *Program") } for pkg, info := range prog.AllPackages { var wantErr, wantTEF bool switch pkg.Path() { case "a", "b": case "c": wantErr = true case "d", "e": wantTEF = true default: t.Errorf("unexpected package: %q", pkg.Path()) continue } if (info.Errors != nil) != wantErr { if wantErr { t.Errorf("Package %q.Error = nil, want error", pkg.Path()) } else { t.Errorf("Package %q has unexpected Errors: %v", pkg.Path(), info.Errors) } } if info.TransitivelyErrorFree != wantTEF { t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t", pkg.Path(), info.TransitivelyErrorFree, wantTEF) } } }
// Query runs a single oracle query. // // args specify the main package in (*loader.Config).FromArgs 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: // // conf := loader.Config{Build: buildContext, SourceImports: true} // ... populate config, e.g. conf.FromArgs(args) ... // iprog, err := conf.Load() // if err != nil { ... } // o, err := oracle.New(iprog, nil, false) // 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) } conf := loader.Config{Build: buildContext, SourceImports: true} // Determine initial packages. args, err := conf.FromArgs(args, true) if err != nil { return nil, err } if len(args) > 0 { return nil, fmt.Errorf("surplus arguments: %q", args) } // For queries needing only a single typed package, // reduce the analysis scope to that package. if minfo.needs&(needSSA|needRetainTypeInfo) == 0 { reduceScope(pos, &conf) } // TODO(adonovan): report type errors to the user via Serial // types, not stderr? // conf.TypeChecker.Error = func(err error) { // E := err.(types.Error) // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg) // } // Load/parse/type-check the program. iprog, err := conf.Load() if err != nil { return nil, err } o, err := newOracle(iprog, ptalog, minfo.needs, reflection) if err != nil { return nil, err } qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0) if err != nil && minfo.needs&(needPos|needExactPos) != 0 { return nil, err } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. iprog = nil return o.query(minfo, qpos) }
// Test that both syntax (scan/parse) and type errors are both recorded // (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error). func TestErrorReporting(t *testing.T) { pkgs := map[string]string{ "a": `package a; import _ "b"; var x int = false`, "b": `package b; 'syntax error!`, } conf := loader.Config{ AllowErrors: true, SourceImports: true, Build: fakeContext(pkgs), } var allErrors []error conf.TypeChecker.Error = func(err error) { allErrors = append(allErrors, err) } conf.Import("a") prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %s", err) } if prog == nil { t.Fatalf("Load returned nil *Program") } hasError := func(errors []error, substr string) bool { for _, err := range errors { if strings.Contains(err.Error(), substr) { return true } } return false } // TODO(adonovan): test keys of ImportMap. // Check errors recorded in each PackageInfo. for pkg, info := range prog.AllPackages { switch pkg.Path() { case "a": if !hasError(info.Errors, "cannot convert false") { t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors) } case "b": if !hasError(info.Errors, "rune literal not terminated") { t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors) } } } // Check errors reported via error handler. if !hasError(allErrors, "cannot convert false") || !hasError(allErrors, "rune literal not terminated") { t.Errorf("allErrors = %v, want both syntax and type errors", allErrors) } }
func TestMultipleQueries(t *testing.T) { // Loader var buildContext = build.Default buildContext.GOPATH = "testdata" conf := loader.Config{Build: &buildContext, SourceImports: true} filename := "testdata/src/main/multi.go" conf.CreateFromFilenames("", filename) iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %s", err) } // Oracle o, err := oracle.New(iprog, nil, true) if err != nil { t.Fatalf("oracle.New failed: %s", err) } // QueryPos pos := filename + ":#54,#58" qpos, err := oracle.ParseQueryPos(iprog, 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. iprog = nil // Run different query modes 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) } WriteResult(out, res) } 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) } }
// Oracle annotates `pkg` using go.tools/oracle interface implements detector. // It uses `scopes` as analysis scope. // If `scopes` is none of one of `scopes` is zero string, it uses unit tests as scope. func Oracle(pkg *ast.Package, scopes ...string) error { settings := build.Default settings.BuildTags = []string{} // TODO conf := loader.Config{Build: &settings, SourceImports: true} withTests := false if len(scopes) == 0 { withTests = true } for _, scope := range scopes { if scope == "" { withTests = true } else { conf.Import(scope) } } if withTests { conf.ImportWithTests(pkg.Name) } else { conf.Import(pkg.Name) } iprog, err := conf.Load() if err != nil { return fmt.Errorf("oracle annotator: conf load error: %+v", err) } o, err := oracle.New(iprog, nil, false) if err != nil { return fmt.Errorf("oracle annotator: create error: %+v", err) } for _, class := range pkg.Classes { qpos, err := oracle.ParseQueryPos(iprog, string(class.Pos), false) if err != nil { log.Printf("oracle annotator: parse query pos error: %+v, %+v", err, class.Pos) continue } res, err := o.Query("implements", qpos) if err != nil { return fmt.Errorf("oracle annotator: query error: %+v, %v", err, class.Pos) } impls := res.Serial().Implements for _, target := range impls.AssignableFromPtr { addImplements(class, target) } for _, target := range impls.AssignableFrom { addImplements(class, target) } } return nil }
// reduceScope is called for one-shot queries that need only a single // typed package. It attempts to guess the query package from pos and // reduce the analysis scope (set of loaded packages) to just that one // plus (the exported parts of) its dependencies. It leaves its // arguments unchanged on failure. // // TODO(adonovan): this is a real mess... but it's fast. // func reduceScope(pos string, conf *loader.Config) { fqpos, err := fastQueryPos(pos) if err != nil { return // bad query } // TODO(adonovan): fix: this gives the wrong results for files // in non-importable packages such as tests and ad-hoc packages // specified as a list of files (incl. the oracle's tests). _, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build) if err != nil { return // can't find GOPATH dir } if importPath == "" { return } // Check that it's possible to load the queried package. // (e.g. oracle tests contain different 'package' decls in same dir.) // Keep consistent with logic in loader/util.go! cfg2 := *conf.Build cfg2.CgoEnabled = false bp, err := cfg2.Import(importPath, "", 0) if err != nil { return // no files for package } // Check that the queried file appears in the package: // it might be a '// +build ignore' from an ad-hoc main // package, e.g. $GOROOT/src/pkg/net/http/triv.go. if !pkgContainsFile(bp, fqpos.fset.File(fqpos.start).Name()) { return // not found } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } // Ignore packages specified on command line. conf.CreatePkgs = nil conf.ImportPkgs = nil // Instead load just the one containing the query position // (and possibly its corresponding tests/production code). // TODO(adonovan): set 'augment' based on which file list // contains _ = conf.ImportWithTests(importPath) // ignore error }
// 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") } }
func TestStdlib(t *testing.T) { defer func(saved func(format string, args ...interface{})) { logf = saved }(logf) logf = t.Errorf ctxt := build.Default // copy // Enumerate $GOROOT packages. saved := ctxt.GOPATH ctxt.GOPATH = "" // disable GOPATH during AllPackages pkgs := buildutil.AllPackages(&ctxt) ctxt.GOPATH = saved // Throw in a number of go.tools packages too. pkgs = append(pkgs, "code.google.com/p/go.tools/cmd/godoc", "code.google.com/p/go.tools/refactor/lexical") // Load, parse and type-check the program. conf := loader.Config{ Build: &ctxt, SourceImports: true, } for _, path := range pkgs { if err := conf.ImportWithTests(path); err != nil { t.Error(err) } } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } // This test ensures that Structure doesn't panic and that // its internal sanity-checks against go/types don't fail. for pkg, info := range iprog.AllPackages { _ = Structure(iprog.Fset, pkg, &info.Info, info.Files) } }
// loadProgram loads the specified set of packages (plus their tests) // and all their dependencies, from source, through the specified build // context. Only packages in pkgs will have their functions bodies typechecked. func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) { conf := loader.Config{ Build: ctxt, SourceImports: true, ParserMode: parser.ParseComments, // TODO(adonovan): enable this. Requires making a lot of code more robust! AllowErrors: false, } // Optimization: don't type-check the bodies of functions in our // dependencies, since we only need exported package members. conf.TypeCheckFuncBodies = func(p string) bool { return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")] } if Verbose { var list []string for pkg := range pkgs { list = append(list, pkg) } sort.Strings(list) for _, pkg := range list { fmt.Fprintf(os.Stderr, "Loading package: %s\n", pkg) } } for pkg := range pkgs { if err := conf.ImportWithTests(pkg); err != nil { return nil, err } } return conf.Load() }
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 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") } } } }
func doMain() error { flag.Parse() args := flag.Args() if *helpFlag { fmt.Fprint(os.Stderr, eg.Help) os.Exit(2) } if *templateFlag == "" { return fmt.Errorf("no -t template.go file specified") } conf := loader.Config{ Fset: token.NewFileSet(), ParserMode: parser.ParseComments, SourceImports: true, } // The first Created package is the template. if err := conf.CreateFromFilenames("template", *templateFlag); err != nil { return err // e.g. "foo.go:1: syntax error" } if len(args) == 0 { fmt.Fprint(os.Stderr, usage) os.Exit(1) } if _, err := conf.FromArgs(args, true); err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Analyze the template. template := iprog.Created[0] xform, err := eg.NewTransformer(iprog.Fset, template, *verboseFlag) if err != nil { return err } // Apply it to the input packages. var pkgs []*loader.PackageInfo if *transitiveFlag { for _, info := range iprog.AllPackages { pkgs = append(pkgs, info) } } else { pkgs = iprog.InitialPackages() } var hadErrors bool for _, pkg := range pkgs { if pkg == template { continue } for _, file := range pkg.Files { n := xform.Transform(&pkg.Info, pkg.Pkg, file) if n == 0 { continue } filename := iprog.Fset.File(file.Pos()).Name() fmt.Fprintf(os.Stderr, "=== %s (%d matches):\n", filename, n) if *writeFlag { if err := eg.WriteAST(iprog.Fset, filename, file); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) hadErrors = true } } else { printer.Fprint(os.Stdout, iprog.Fset, file) } } } if hadErrors { os.Exit(1) } return nil }
func Test(t *testing.T) { switch runtime.GOOS { case "windows": t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS) } conf := loader.Config{ Fset: token.NewFileSet(), ParserMode: parser.ParseComments, SourceImports: true, } // Each entry is a single-file package. // (Multi-file packages aren't interesting for this test.) // Order matters: each non-template package is processed using // the preceding template package. for _, filename := range []string{ "testdata/A.template", "testdata/A1.go", "testdata/A2.go", "testdata/B.template", "testdata/B1.go", "testdata/C.template", "testdata/C1.go", "testdata/D.template", "testdata/D1.go", "testdata/E.template", "testdata/E1.go", "testdata/bad_type.template", "testdata/no_before.template", "testdata/no_after_return.template", "testdata/type_mismatch.template", "testdata/expr_type_mismatch.template", } { pkgname := strings.TrimSuffix(filepath.Base(filename), ".go") if err := conf.CreateFromFilenames(pkgname, filename); err != nil { t.Fatal(err) } } iprog, err := conf.Load() if err != nil { t.Fatal(err) } var xform *eg.Transformer for _, info := range iprog.Created { file := info.Files[0] filename := iprog.Fset.File(file.Pos()).Name() // foo.go if strings.HasSuffix(filename, "template") { // a new template shouldFail, _ := info.Pkg.Scope().Lookup("shouldFail").(*types.Const) xform, err = eg.NewTransformer(iprog.Fset, info, *verboseFlag) if err != nil { if shouldFail == nil { t.Errorf("NewTransformer(%s): %s", filename, err) } else if want := exact.StringVal(shouldFail.Val()); !strings.Contains(err.Error(), want) { t.Errorf("NewTransformer(%s): got error %q, want error %q", filename, err, want) } } else if shouldFail != nil { t.Errorf("NewTransformer(%s) succeeded unexpectedly; want error %q", filename, shouldFail.Val()) } continue } if xform == nil { t.Errorf("%s: no previous template", filename) continue } // apply previous template to this package n := xform.Transform(&info.Info, info.Pkg, file) if n == 0 { t.Errorf("%s: no matches", filename) continue } got := filename + "t" // foo.got golden := filename + "lden" // foo.golden // Write actual output to foo.got. if err := eg.WriteAST(iprog.Fset, got, file); err != nil { t.Error(err) } defer os.Remove(got) // Compare foo.got with foo.golden. var cmd *exec.Cmd switch runtime.GOOS { case "plan9": cmd = exec.Command("/bin/diff", "-c", golden, got) default: cmd = exec.Command("/usr/bin/diff", "-u", golden, got) } buf := new(bytes.Buffer) cmd.Stdout = buf cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Errorf("eg tests for %s failed: %s.\n%s\n", filename, err, buf) if *updateFlag { t.Logf("Updating %s...", golden) if err := exec.Command("/bin/cp", got, golden).Run(); err != nil { t.Errorf("Update failed: %s", err) } } } } }
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) } }
func TestStdlib(t *testing.T) { runtime.GC() t0 := time.Now() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Load, parse and type-check the program. var conf loader.Config for _, path := range allPackages() { if err := conf.ImportWithTests(path); err != nil { t.Error(err) } } prog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } t1 := time.Now() runtime.GC() runtime.ReadMemStats(&memstats) numPkgs := len(prog.AllPackages) if want := 205; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Dump package members. if false { for pkg := range prog.AllPackages { fmt.Printf("Package %s:\n", pkg.Path()) scope := pkg.Scope() for _, name := range scope.Names() { if ast.IsExported(name) { fmt.Printf("\t%s\n", types.ObjectString(pkg, scope.Lookup(name))) } } fmt.Println() } } // Check that Test functions for io/ioutil, regexp and // compress/bzip2 are all simultaneously present. // (The apparent cycle formed when augmenting all three of // these packages by their tests was the original motivation // for reporting b/7114.) // // compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil // io/ioutil.TestTempFile in tempfile_test.go imports regexp // regexp.TestRE2Search in exec_test.go imports compress/bzip2 for _, test := range []struct{ pkg, fn string }{ {"io/ioutil", "TestTempFile"}, {"regexp", "TestRE2Search"}, {"compress/bzip2", "TestBitReader"}, } { info := prog.Imported[test.pkg] if info == nil { t.Errorf("failed to load package %q", test.pkg) continue } obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func) if obj == nil { t.Errorf("package %q has no func %q", test.pkg, test.fn) continue } } // Dump some statistics. // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) t.Log("#Source lines: ", lineCount) t.Log("Load/parse/typecheck: ", t1.Sub(t0)) t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) }
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 }
// 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) } }
// 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 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 } } }
// 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) } } } }
// 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) } } }
// 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 TestTransitivelyErrorFreeFlag(t *testing.T) { conf := loader.Config{ AllowErrors: true, SourceImports: true, } // Create an minimal custom build.Context // that fakes the following packages: // // a --> b --> c! c has an error // \ d and e are transitively error-free. // e --> d // // Each package [a-e] consists of one file, x.go. pkgs := map[string]string{ "a": `package a; import (_ "b"; _ "e")`, "b": `package b; import _ "c"`, "c": `package c; func f() { _ = int(false) }`, // type error within function body "d": `package d;`, "e": `package e; import _ "d"`, } ctxt := build.Default // copy ctxt.GOROOT = "/go" ctxt.GOPATH = "" ctxt.IsDir = func(path string) bool { return true } ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) { return justXgo[:], nil } ctxt.OpenFile = func(path string) (io.ReadCloser, error) { path = path[len("/go/src/pkg/"):] return nopCloser{bytes.NewBufferString(pkgs[path[0:1]])}, nil } conf.Build = &ctxt conf.Import("a") prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %s", err) } if prog == nil { t.Fatalf("Load returned nil *Program") } for pkg, info := range prog.AllPackages { var wantErr, wantTEF bool switch pkg.Path() { case "a", "b": case "c": wantErr = true case "d", "e": wantTEF = true default: t.Errorf("unexpected package: %q", pkg.Path()) continue } if (info.Errors != nil) != wantErr { if wantErr { t.Errorf("Package %q.Error = nil, want error", pkg.Path()) } else { t.Errorf("Package %q has unexpected Errors: %v", pkg.Path(), info.Errors) } } if info.TransitivelyErrorFree != wantTEF { t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t", pkg.Path(), info.TransitivelyErrorFree, wantTEF) } } }
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) }