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, "golang.org/x/tools/cmd/godoc", "golang.org/x/tools/refactor/lexical") // Load, parse and type-check the program. conf := loader.Config{Build: &ctxt} for _, path := range pkgs { conf.ImportWithTests(path) } 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() }
// Create unexporter func NewUnexporter(o *Config) (*unexporter, error) { var conf loader.Config for _, package_name := range buildutil.AllPackages(&build.Default) { conf.ImportWithTests(package_name) } program, err := conf.Load() if err != nil { return nil, err } if program.Package(o.Pkg) == nil { return nil, errors.New(fmt.Sprintf("'%s' is not a valid package", o.Pkg)) } if o.Debug { pkg := program.Package(o.Pkg).Pkg fmt.Printf("finding unused identifiers for %s (%s)\n", pkg.Name(), pkg.Path()) } unexporter := &unexporter{ Program: program, MainPackage: program.Package(o.Pkg), Verbose: o.Debug, } unexporter.run() return unexporter, nil }
func TestLoad_FromImports_Success(t *testing.T) { var conf loader.Config conf.ImportWithTests("fmt") conf.ImportWithTests("errors") prog, err := conf.Load() if err != nil { t.Errorf("Load failed unexpectedly: %v", err) } if prog == nil { t.Fatalf("Load returned a nil Program") } if got, want := created(prog), "errors_test fmt_test"; got != want { t.Errorf("Created = %q, want %s", got, want) } if got, want := imported(prog), "errors fmt"; got != want { t.Errorf("Imported = %s, want %s", got, want) } // Check set of transitive packages. // There are >30 and the set may grow over time, so only check a few. want := map[string]bool{ "strings": true, "time": true, "runtime": true, "testing": true, "unicode": true, } for _, path := range all(prog) { delete(want, path) } if len(want) > 0 { t.Errorf("AllPackages is missing these keys: %q", keys(want)) } }
// This example imports three packages, including the tests for one of // them, and loads all their dependencies. func ExampleConfig_Import() { // ImportWithTest("strconv") causes strconv to include // internal_test.go, and creates an external test package, // strconv_test. // (Compare with the example of CreateFromFiles.) var conf loader.Config conf.Import("unicode/utf8") conf.Import("errors") conf.ImportWithTests("strconv") prog, err := conf.Load() if err != nil { log.Fatal(err) } printProgram(prog) printFilenames(prog.Fset, prog.Package("strconv")) printFilenames(prog.Fset, prog.Package("strconv_test")) // Output: // created: [strconv_test] // imported: [errors strconv unicode/utf8] // initial: [errors strconv strconv_test unicode/utf8] // all: [bufio bytes errors flag fmt io log math math/rand os reflect runtime runtime/pprof runtime/trace sort strconv strconv_test strings sync sync/atomic syscall testing text/tabwriter time unicode unicode/utf8] // strconv.Files: [atob.go atof.go atoi.go decimal.go doc.go extfloat.go ftoa.go isprint.go itoa.go quote.go internal_test.go] // strconv_test.Files: [atob_test.go atof_test.go atoi_test.go decimal_test.go example_test.go fp_test.go ftoa_test.go itoa_test.go quote_test.go strconv_test.go] }
func invalidProgram(name string) *loader.Program { var ldr loader.Config ldr.ParserMode = goparser.ParseComments ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/" + name) prog, err := ldr.Load() if err != nil { log.Fatal(err) } return prog }
// 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/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 }
func lint(pkgs []string, tags []string) error { ctx := build.Default for _, tag := range tags { ctx.BuildTags = append(ctx.BuildTags, tag) } conf := loader.Config{ Build: &ctx, } gotoolCtx := gotool.Context{ BuildContext: ctx, } for _, name := range gotoolCtx.ImportPaths(pkgs) { conf.ImportWithTests(name) } prog, err := conf.Load() if err != nil { return err } foundError := false for _, pkg := range prog.InitialPackages() { if pkg.Errors != nil { fmt.Printf("%s: %#v", pkg.String(), pkg.Errors) foundError = true } } if foundError { return fmt.Errorf("Found some initializing errors") } for _, pkg := range prog.InitialPackages() { for _, file := range pkg.Files { ast.Walk(walkForFunctions{ FileSet: prog.Fset, }, file) } } return nil }
// importQueryPackage finds the package P containing the // query position and tells conf to import it. // It returns the package's path. func importQueryPackage(pos string, conf *loader.Config) (string, error) { fqpos, err := fastQueryPos(pos) if err != nil { return "", err // bad query } filename := fqpos.fset.File(fqpos.start).Name() // This will not work for ad-hoc packages // such as $GOROOT/src/net/http/triv.go. // TODO(adonovan): ensure we report a clear error. _, importPath, err := guessImportPath(filename, conf.Build) if err != nil { return "", err // can't find GOPATH dir } if importPath == "" { return "", fmt.Errorf("can't guess import path from %s", filename) } // 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 "", err // no files for package } switch pkgContainsFile(bp, filename) { case 'T': conf.ImportWithTests(importPath) case 'X': conf.ImportWithTests(importPath) importPath += "_test" // for TypeCheckFuncBodies case 'G': conf.Import(importPath) default: return "", fmt.Errorf("package %q doesn't contain file %s", importPath, filename) } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } return importPath, nil }
// ParsePathsFromArgs parses arguments from command line and looks at import // paths to rename objects. func ParsePathsFromArgs() { flag.Parse() for _, dir := range flag.Args() { var conf loader.Config conf.ParserMode = parser.ParseComments conf.ImportWithTests(dir) prog, err := conf.Load() if err != nil { panic(err) } r := renamer{prog, map[*token.File]bool{}} r.parse() if !*dryRun { r.write() } } }
// importQueryPackage finds the package P containing the // query position and tells conf to import it. // It returns the package's path. func importQueryPackage(pos string, conf *loader.Config) (string, error) { fqpos, err := fastQueryPos(conf.Build, pos) if err != nil { return "", err // bad query } filename := fqpos.fset.File(fqpos.start).Name() _, importPath, err := guessImportPath(filename, conf.Build) if err != nil { // Can't find GOPATH dir. // Treat the query file as its own package. importPath = "command-line-arguments" conf.CreateFromFilenames(importPath, filename) } else { // Check that it's possible to load the queried package. // (e.g. guru 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 "", err // no files for package } switch pkgContainsFile(bp, filename) { case 'T': conf.ImportWithTests(importPath) case 'X': conf.ImportWithTests(importPath) importPath += "_test" // for TypeCheckFuncBodies case 'G': conf.Import(importPath) default: // This happens for ad-hoc packages like // $GOROOT/src/net/http/triv.go. return "", fmt.Errorf("package %q doesn't contain file %s", importPath, filename) } } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } return importPath, nil }
func (u *Unexporter) loadProgram(pkgPath string) (err error) { wd, err := os.Getwd() if err != nil { return err } bpkg, err := u.ctxt.Import(pkgPath, wd, build.ImportComment) if err != nil { return err } _, rev, _ := importgraph.Build(u.ctxt) pkgs := rev.Search(bpkg.ImportPath) conf := loader.Config{ Build: u.ctxt, ParserMode: parser.ParseComments, 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")] } for pkg := range pkgs { conf.ImportWithTests(pkg) } u.prog, err = conf.Load() if err != nil { return } for p, info := range u.prog.AllPackages { if p.Path() == bpkg.ImportPath { u.pkgInfo = info break } } return }
func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) { var conf loader.Config conf.AllowErrors = true conf.Import("nosuchpkg") conf.ImportWithTests("errors") prog, err := conf.Load() if err != nil { t.Errorf("Load failed unexpectedly: %v", err) } if prog == nil { t.Fatalf("Load returned a nil Program") } if got, want := created(prog), "errors_test"; got != want { t.Errorf("Created = %s, want %s", got, want) } if got, want := imported(prog), "errors"; got != want { t.Errorf("Imported = %s, want %s", got, want) } }
// newAppScanner creates a new api parser func newAppScanner(opts *Opts, includes, excludes packageFilters) (*appScanner, error) { var ldr loader.Config ldr.ParserMode = goparser.ParseComments ldr.ImportWithTests(opts.BasePath) prog, err := ldr.Load() if err != nil { return nil, err } input := opts.Input if input == nil { input = new(spec.Swagger) input.Swagger = "2.0" } if input.Paths == nil { input.Paths = new(spec.Paths) } if input.Definitions == nil { input.Definitions = make(map[string]spec.Schema) } if input.Responses == nil { input.Responses = make(map[string]spec.Response) } return &appScanner{ MainPackage: opts.BasePath, prog: prog, input: input, loader: &ldr, operations: collectOperationsFromInput(input), definitions: input.Definitions, responses: input.Responses, scanModels: opts.ScanModels, classifier: &programClassifier{ Includes: includes, Excludes: excludes, }, }, nil }
func classifierProgram() *loader.Program { var ldr loader.Config ldr.ParserMode = goparser.ParseComments ldr.Build = &gobuild.Default ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/classification") ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/classification/models") ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/classification/operations") prog, err := ldr.Load() if err != nil { log.Fatal(err) } return prog }
func petstoreProgram() *loader.Program { var ldr loader.Config ldr.ParserMode = goparser.ParseComments ldr.Build = &gobuild.Default ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/petstore") ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/petstore/models") ldr.ImportWithTests("github.com/go-swagger/go-swagger/fixtures/goparsing/petstore/rest/handlers") prog, err := ldr.Load() if err != nil { log.Fatal(err) } return prog }
// Implements displays the "implements" relation as it pertains to the // selected type. // If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. // func implements(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) qpkg, err := importQueryPackage(q.Pos, &lconf) if err != nil { return err } // Set the packages to search. if len(q.Scope) > 0 { // Inspect all packages in the analysis scope, if specified. if err := setPTAScope(&lconf, q.Scope); err != nil { return err } } else { // Otherwise inspect the forward and reverse // transitive closure of the selected package. // (In theory even this is incomplete.) _, rev, _ := importgraph.Build(q.Build) for path := range rev.Search(qpkg) { lconf.ImportWithTests(path) } // TODO(adonovan): for completeness, we should also // type-check and inspect function bodies in all // imported packages. This would be expensive, but we // could optimize by skipping functions that do not // contain type declarations. This would require // changing the loader's TypeCheckFuncBodies hook to // provide the []*ast.File. } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err } // Find the selected type. path, action := findInterestingNode(qpos.info, qpos.path) var method *types.Func var T types.Type // selected type (receiver if method != nil) switch action { case actionExpr: // method? if id, ok := path[0].(*ast.Ident); ok { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv == nil { return fmt.Errorf("this function is not a method") } method = obj T = recv.Type() } } case actionType: T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { return fmt.Errorf("no type or method here") } // Find all named types, even local types (which can have // methods via promotion) and the built-in "error". var allNamed []types.Type for _, info := range lprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { allNamed = append(allNamed, obj.Type()) } } } allNamed = append(allNamed, types.Universe.Lookup("error").Type()) var msets typeutil.MethodSetCache // Test each named type. var to, from, fromPtr []types.Type for _, U := range allNamed { if isInterface(T) { if msets.MethodSet(T).Len() == 0 { continue // empty interface } if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T interface, U interface if !types.Identical(T, U) { if types.AssignableTo(U, T) { to = append(to, U) } if types.AssignableTo(T, U) { from = append(from, U) } } } else { // T interface, U concrete if types.AssignableTo(U, T) { to = append(to, U) } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { to = append(to, pU) } } } else if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T concrete, U interface if types.AssignableTo(T, U) { from = append(from, U) } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { fromPtr = append(fromPtr, U) } } } var pos interface{} = qpos if nt, ok := deref(T).(*types.Named); ok { pos = nt.Obj() } // Sort types (arbitrarily) to ensure test determinism. sort.Sort(typesByString(to)) sort.Sort(typesByString(from)) sort.Sort(typesByString(fromPtr)) var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils if method != nil { for _, t := range to { toMethod = append(toMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range from { fromMethod = append(fromMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range fromPtr { fromPtrMethod = append(fromPtrMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } } q.result = &implementsResult{ qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, } return nil }
// globalReferrers reports references throughout the entire workspace to the // object at the specified source position. Its defining package is defpkg, // and the query package is qpkg. isPkgLevel indicates whether the object // is defined at package-level. func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPkgLevel bool) error { // Scan the workspace and build the import graph. // Ignore broken packages. _, rev, _ := importgraph.Build(q.Build) // Find the set of packages that depend on defpkg. // Only function bodies in those packages need type-checking. var users map[string]bool if isPkgLevel { users = rev[defpkg] // direct importers if users == nil { users = make(map[string]bool) } users[defpkg] = true // plus the defining package itself } else { users = rev.Search(defpkg) // transitive importers } // Prepare to load the larger program. fset := token.NewFileSet() lconf := loader.Config{ Fset: fset, Build: q.Build, TypeCheckFuncBodies: func(p string) bool { return users[strings.TrimSuffix(p, "_test")] }, } allowErrors(&lconf) // The importgraph doesn't treat external test packages // as separate nodes, so we must use ImportWithTests. for path := range users { lconf.ImportWithTests(path) } // The remainder of this function is somewhat tricky because it // operates on the concurrent stream of packages observed by the // loader's AfterTypeCheck hook. Most of guru's helper // functions assume the entire program has already been loaded, // so we can't use them here. // TODO(adonovan): smooth things out once the other changes have landed. // Results are reported concurrently from within the // AfterTypeCheck hook. The program may provide a useful stream // of information even if the user doesn't let the program run // to completion. var ( mu sync.Mutex qobj types.Object qinfo *loader.PackageInfo // info for qpkg ) // For efficiency, we scan each package for references // just after it has been type-checked. The loader calls // AfterTypeCheck (concurrently), providing us with a stream of // packages. lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { // AfterTypeCheck may be called twice for the same package due to augmentation. // Only inspect packages that depend on the declaring package // (and thus were type-checked). if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { // Record the query object and its package when we see it. mu.Lock() if qobj == nil && info.Pkg.Path() == defpkg { // Find the object by its position (slightly ugly). qobj = findObject(fset, &info.Info, objposn) if qobj == nil { // It really ought to be there; // we found it once already. log.Fatalf("object at %s not found in package %s", objposn, defpkg) } // Object found. qinfo = info q.Output(fset, &referrersInitialResult{ qinfo: qinfo, obj: qobj, }) } obj := qobj mu.Unlock() // Look for references to the query object. if obj != nil { outputUses(q, fset, usesOf(obj, info), info.Pkg) } } clearInfoFields(info) // save memory } lconf.Load() // ignore error if qobj == nil { log.Fatal("query object not found during reloading") } return nil // success }
// packageReferrers reports all references to the specified package // throughout the workspace. func packageReferrers(q *Query, path string) error { // Scan the workspace and build the import graph. // Ignore broken packages. _, rev, _ := importgraph.Build(q.Build) // Find the set of packages that directly import the query package. // Only those packages need typechecking of function bodies. users := rev[path] // Load the larger program. fset := token.NewFileSet() lconf := loader.Config{ Fset: fset, Build: q.Build, TypeCheckFuncBodies: func(p string) bool { return users[strings.TrimSuffix(p, "_test")] }, } allowErrors(&lconf) // The importgraph doesn't treat external test packages // as separate nodes, so we must use ImportWithTests. for path := range users { lconf.ImportWithTests(path) } // Subtle! AfterTypeCheck needs no mutex for qpkg because the // topological import order gives us the necessary happens-before edges. // TODO(adonovan): what about import cycles? var qpkg *types.Package // For efficiency, we scan each package for references // just after it has been type-checked. The loader calls // AfterTypeCheck (concurrently), providing us with a stream of // packages. lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { // AfterTypeCheck may be called twice for the same package due to augmentation. if info.Pkg.Path() == path && qpkg == nil { // Found the package of interest. qpkg = info.Pkg fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) q.Output(fset, &referrersInitialResult{ qinfo: info, obj: fakepkgname, // bogus }) } // Only inspect packages that directly import the // declaring package (and thus were type-checked). if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { // Find PkgNames that refer to qpkg. // TODO(adonovan): perhaps more useful would be to show imports // of the package instead of qualified identifiers. var refs []*ast.Ident for id, obj := range info.Uses { if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { refs = append(refs, id) } } outputUses(q, fset, refs, info.Pkg) } clearInfoFields(info) // save memory } lconf.Load() // ignore error if qpkg == nil { log.Fatalf("query package %q not found during reloading", path) } return nil }
func TestCycles(t *testing.T) { for _, test := range []struct { descr string ctxt *build.Context wantErr string }{ { "self-cycle", fakeContext(map[string]string{ "main": `package main; import _ "selfcycle"`, "selfcycle": `package selfcycle; import _ "selfcycle"`, }), `import cycle: selfcycle -> selfcycle`, }, { "three-package cycle", fakeContext(map[string]string{ "main": `package main; import _ "a"`, "a": `package a; import _ "b"`, "b": `package b; import _ "c"`, "c": `package c; import _ "a"`, }), `import cycle: c -> a -> b -> c`, }, { "self-cycle in dependency of test file", buildutil.FakeContext(map[string]map[string]string{ "main": { "main.go": `package main`, "main_test.go": `package main; import _ "a"`, }, "a": { "a.go": `package a; import _ "a"`, }, }), `import cycle: a -> a`, }, // TODO(adonovan): fix: these fail // { // "two-package cycle in dependency of test file", // buildutil.FakeContext(map[string]map[string]string{ // "main": { // "main.go": `package main`, // "main_test.go": `package main; import _ "a"`, // }, // "a": { // "a.go": `package a; import _ "main"`, // }, // }), // `import cycle: main -> a -> main`, // }, // { // "self-cycle in augmented package", // buildutil.FakeContext(map[string]map[string]string{ // "main": { // "main.go": `package main`, // "main_test.go": `package main; import _ "main"`, // }, // }), // `import cycle: main -> main`, // }, } { conf := loader.Config{ AllowErrors: true, Build: test.ctxt, } var mu sync.Mutex var allErrors []error conf.TypeChecker.Error = func(err error) { mu.Lock() allErrors = append(allErrors, err) mu.Unlock() } conf.ImportWithTests("main") prog, err := conf.Load() if err != nil { t.Errorf("%s: Load failed: %s", test.descr, err) } if prog == nil { t.Fatalf("%s: Load returned nil *Program", test.descr) } if !hasError(allErrors, test.wantErr) { t.Errorf("%s: Load() errors = %q, want %q", test.descr, allErrors, test.wantErr) } } // TODO(adonovan): // - Test that in a legal test cycle, none of the symbols // defined by augmentation are visible via import. }
func doTestable(args []string) error { conf := loader.Config{ Build: &build.Default, } // 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 } //if !(*runFlag) { wordSize = 4 // TARDIS Go addition to force default int size to 32 bits conf.Build.GOARCH = "haxe" // TARDIS Go addition - 32-bit int conf.Build.GOOS = "nacl" // or haxe? TARDIS Go addition - simplest OS-specific code to emulate? //} conf.Build.BuildTags = strings.Split(*buidTags, " ") conf.TypeChecker.Sizes = &types.StdSizes{ // must equal haxe.haxeStdSizes when (!*runFlag) MaxAlign: 8, WordSize: wordSize, } var mode ssa.BuilderMode /* for _, c := range *buildFlag { switch c { case 'D': mode |= ssa.GlobalDebug case 'P': mode |= ssa.PrintPackages case 'F': mode |= ssa.PrintFunctions case 'S': mode |= ssa.LogSource | ssa.BuildSerially case 'C': mode |= ssa.SanityCheckFunctions case 'N': mode |= ssa.NaiveForm case 'L': mode |= ssa.BuildSerially case 'I': mode |= ssa.BareInits default: return fmt.Errorf("unknown -build option: '%c'", c) } } */ // TARDIS go addition if *debugFlag { mode |= ssa.GlobalDebug } 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 } err = pprof.StartCPUProfile(f) if err != nil { return err } defer pprof.StopCPUProfile() } // TODO Eventually this might be better as an environment variable //if !(*runFlag) { if *tgoroot == "" { if conf.Build.GOPATH == "" { return fmt.Errorf("GOPATH must be set") } conf.Build.GOROOT = strings.Split(conf.Build.GOPATH, ":")[0] + "/src/github.com/tardisgo/tardisgo/goroot/haxe/go1.4" } else { conf.Build.GOROOT = *tgoroot } //} //fmt.Println("DEBUG GOPATH", conf.Build.GOPATH) //fmt.Println("DEBUG GOROOT", conf.Build.GOROOT) if *testFlag { conf.ImportWithTests(args[0]) // assumes you give the full cannonical name of the package to test args = args[1:] } // Use the initial packages from the command line. _, err := conf.FromArgs(args, *testFlag) if err != nil { return err } // The interpreter needs the runtime package. //if *runFlag { conf.Import("runtime") //} else { // 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, including the type definitions. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. *modeFlag |= mode | ssa.SanityCheckFunctions prog := ssautil.CreateProgram(iprog, *modeFlag) 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) } else { // if not interpreting... // TARDIS Go additions: copy run interpreter code above, but call pogo class var main *ssa.Package pkgs := prog.AllPackages() //fmt.Println("DEBUG pkgs:", pkgs) if *testFlag { // If -test, run all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } fd, err := os.Open(TestFS) fd.Close() if err == nil { LoadTestZipFS = true for l := range pogo.LanguageList { pogo.LanguageList[l].TestFS = TestFS } } } 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) */ pogo.DebugFlag = *debugFlag pogo.TraceFlag = *traceFlag err = pogo.EntryPoint(main) // TARDIS Go entry point, returns an error if err != nil { return err } results := make(chan resChan) switch *allFlag { case "": // NoOp case "all", "bench": //for _, dir := range dirs { // err := os.RemoveAll(dir) // if err != nil { // fmt.Println("Error deleting existing '" + dir + "' directory: " + err.Error()) // } //} var targets [][][]string if *allFlag == "bench" { targets = allBenchmark // fast execution time } else { targets = allCompile // fast compile time } for _, cmd := range targets { go doTarget(cmd, results) } for _ = range targets { r := <-results fmt.Println(r.output) if (r.err != nil || len(strings.TrimSpace(r.output)) == 0) && *allFlag != "bench" { os.Exit(1) // exit with an error if the test fails, but not for benchmarking } r.backChan <- true } case "math": // which is faster for the test with correct math processing, cpp or js? //err := os.RemoveAll("tardis/cpp") //if err != nil { // fmt.Println("Error deleting existing '" + "tardis/cpp" + "' directory: " + err.Error()) //} mathCmds := [][][]string{ [][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-cpp", "tardis/cpp"}, []string{"echo", `"CPP:"`}, []string{"time", "./tardis/cpp/Go"}, }, [][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-D", "fullunsafe", "-js", "tardis/go-fu.js"}, []string{"echo", `"Node/JS using fullunsafe memory mode (js dataview):"`}, []string{"time", "node", "tardis/go-fu.js"}, }, } for _, cmd := range mathCmds { go doTarget(cmd, results) } for _ = range mathCmds { r := <-results fmt.Println(r.output) if r.err != nil { os.Exit(1) // exit with an error if the test fails } r.backChan <- true } case "interp", "cpp", "cs", "js", "jsfu", "java": // for running tests switch *allFlag { case "interp": go doTarget([][]string{ []string{"echo", ``}, // Output from this line is ignored []string{"echo", `"Neko (haxe --interp):"`}, []string{"time", "haxe", "-main", "tardis.Go", "-cp", "tardis", "--interp"}, }, results) case "cpp": go doTarget([][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-cpp", "tardis/cpp"}, []string{"echo", `"CPP:"`}, []string{"time", "./tardis/cpp/Go"}, }, results) case "cs": go doTarget([][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-cs", "tardis/cs"}, []string{"echo", `"CS:"`}, []string{"time", "mono", "./tardis/cs/bin/Go.exe"}, }, results) case "js": go doTarget([][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-D", "uselocalfunctions", "-js", "tardis/go.js"}, []string{"echo", `"Node/JS:"`}, []string{"time", "node", "tardis/go.js"}, }, results) case "jsfu": go doTarget([][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-D", "uselocalfunctions", "-D", "fullunsafe", "-js", "tardis/go-fu.js"}, []string{"echo", `"Node/JS using fullunsafe memory mode (js dataview):"`}, []string{"time", "node", "tardis/go-fu.js"}, }, results) case "java": go doTarget([][]string{ []string{"haxe", "-main", "tardis.Go", "-cp", "tardis", "-dce", "full", "-D", "inlinepointers", "-java", "tardis/java"}, []string{"echo", `"Java:"`}, []string{"time", "java", "-jar", "tardis/java/Go.jar"}, }, results) } r := <-results fmt.Println(r.output) if r.err != nil { os.Exit(1) // exit with an error if the test fails } r.backChan <- true default: panic("invalid value for -haxe flag: " + *allFlag) } } return nil }
func buildSSA(testFlag bool, args []string) (*ssa.Program, []*ssa.Package, []*ssa.Package, error) { conf := loader.Config{ Build: &build.Default, } conf.Build.BuildTags = strings.Split(*buidTags, " ") //fmt.Println("DEBUG GOPATH", conf.Build.GOPATH) //fmt.Println("DEBUG GOROOT", conf.Build.GOROOT) if testFlag { conf.ImportWithTests(args[0]) // assumes you give the full cannonical name of the package to test args = args[1:] } // Use the initial packages from the command line. _, err := conf.FromArgs(args, testFlag) if err != nil { return nil, nil, nil, err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return nil, nil, nil, err } // Create and build SSA-form program representation. prog := ssautil.CreateProgram(iprog, *modeFlag) prog.Build() pkgs := prog.AllPackages() //fmt.Println("DEBUG pkgs:", pkgs) main := make([]*ssa.Package, 0, 1) if testFlag { if len(pkgs) > 0 { main = append(main, prog.CreateTestMainPackage(pkgs...)) } if len(main) == 0 { return nil, nil, nil, fmt.Errorf("no tests") } pkgs = append(pkgs, main...) //fmt.Println("Test main package created:", main) return prog, pkgs, main, nil } foundMain := false for _, pkg := range pkgs { if pkg.Pkg.Name() == "main" { if pkg.Func("main") == nil { return nil, nil, nil, fmt.Errorf("no func main() in main package") } main = append(main, pkg) foundMain = true } if foundMain { return prog, pkgs, main, nil } } fmt.Println("*** no main package found, using tests") return buildSSA(true, args) }
func TestStdlib(t *testing.T) { if runtime.GOOS == "android" { t.Skipf("incomplete std lib on %s", runtime.GOOS) } runtime.GC() t0 := time.Now() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Load, parse and type-check the program. ctxt := build.Default // copy ctxt.GOPATH = "" // disable GOPATH conf := loader.Config{Build: &ctxt} for _, path := range buildutil.AllPackages(conf.Build) { conf.ImportWithTests(path) } 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() qualifier := types.RelativeTo(pkg) for _, name := range scope.Names() { if ast.IsExported(name) { fmt.Printf("\t%s\n", types.ObjectString(scope.Lookup(name), qualifier)) } } 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) }
// Referrers reports all identifiers that resolve to the same object // as the queried identifier, within any package in the analysis scope. func referrers(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } var id *ast.Ident var obj types.Object var lprog *loader.Program var pass2 bool var qpos *queryPos for { // Load/parse/type-check the program. var err error lprog, err = lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err = parseQueryPos(lprog, q.Pos, false) if err != nil { return err } id, _ = qpos.path[0].(*ast.Ident) if id == nil { return fmt.Errorf("no identifier here") } obj = qpos.info.ObjectOf(id) if obj == nil { // Happens for y in "switch y := x.(type)", // the package declaration, // and unresolved identifiers. if _, ok := qpos.path[1].(*ast.File); ok { // package decl? pkg := qpos.info.Pkg obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg) } else { return fmt.Errorf("no object for identifier: %T", qpos.path[1]) } } if pass2 { break } // If the identifier is exported, we must load all packages that // depend transitively upon the package that defines it. // Treat PkgNames as exported, even though they're lowercase. if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) { break // not exported } // Scan the workspace and build the import graph. // Ignore broken packages. _, rev, _ := importgraph.Build(q.Build) // Re-load the larger program. // Create a new file set so that ... // External test packages are never imported, // so they will never appear in the graph. // (We must reset the Config here, not just reset the Fset field.) lconf = loader.Config{ Fset: token.NewFileSet(), Build: q.Build, } allowErrors(&lconf) for path := range rev.Search(obj.Pkg().Path()) { lconf.ImportWithTests(path) } pass2 = true } // Iterate over all go/types' Uses facts for the entire program. var refs []*ast.Ident for _, info := range lprog.AllPackages { for id2, obj2 := range info.Uses { if sameObj(obj, obj2) { refs = append(refs, id2) } } } sort.Sort(byNamePos{q.Fset, refs}) q.result = &referrersResult{ qpos: qpos, query: id, obj: obj, refs: refs, } return nil }
func TestStdlib(t *testing.T) { // Load, parse and type-check the program. t0 := time.Now() alloc0 := bytesAllocated() // Load, parse and type-check the program. ctxt := build.Default // copy ctxt.GOPATH = "" // disable GOPATH conf := loader.Config{Build: &ctxt} for _, path := range buildutil.AllPackages(conf.Build) { if skip[path] { continue } conf.ImportWithTests(path) } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } t1 := time.Now() alloc1 := bytesAllocated() // 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 := ssautil.CreateProgram(iprog, mode) t2 := time.Now() // Build SSA. prog.Build() t3 := time.Now() alloc3 := bytesAllocated() numPkgs := len(prog.AllPackages()) if want := 140; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Keep iprog reachable until after we've measured memory usage. if len(iprog.AllPackages) == 0 { print() // unreachable } allFuncs := ssautil.AllFunctions(prog) // Check that all non-synthetic functions have distinct names. // Synthetic wrappers for exported methods should be distinct too, // except for unexported ones (explained at (*Function).RelString). byName := make(map[string]*ssa.Function) for fn := range allFuncs { if fn.Synthetic == "" || ast.IsExported(fn.Name()) { 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 AST+types: ", int64(alloc1-alloc0)/1e6) t.Log("#MB SSA: ", int64(alloc3-alloc1)/1e6) }
func doTestable(args []string) error { conf := loader.Config{ Build: &build.Default, } // TARDISgo addition langName := *targetFlag langEntry, e := pogo.FindTargetLang(langName) if e != nil { return e } // 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 } if *runFlag { // nothing here at the moment } else { wordSize = 4 // TARDIS Go addition to force default int size to 32 bits conf.Build.GOOS = "nacl" // TARDIS Go addition - simplest OS-specific code to emulate? conf.Build.GOARCH = langName // TARDIS Go addition } conf.Build.BuildTags = strings.Split(*buidTags, " ") conf.TypeChecker.Sizes = &types.StdSizes{ // must equal haxe.haxeStdSizes when (!*runFlag) MaxAlign: 8, WordSize: wordSize, } var mode ssa.BuilderMode /* for _, c := range *buildFlag { switch c { case 'D': mode |= ssa.GlobalDebug case 'P': mode |= ssa.PrintPackages case 'F': mode |= ssa.PrintFunctions case 'S': mode |= ssa.LogSource | ssa.BuildSerially case 'C': mode |= ssa.SanityCheckFunctions case 'N': mode |= ssa.NaiveForm case 'L': mode |= ssa.BuildSerially case 'I': mode |= ssa.BareInits default: return fmt.Errorf("unknown -build option: '%c'", c) } } */ // TARDIS go addition if *debugFlag { mode |= ssa.GlobalDebug } 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 } err = pprof.StartCPUProfile(f) if err != nil { return err } defer pprof.StopCPUProfile() } if !(*runFlag) { if *tgoroot == "" { if conf.Build.GOPATH == "" { return fmt.Errorf("GOPATH must be set") } langGOROOT := pogo.LanguageList[langEntry].GOROOT if langGOROOT != "" { conf.Build.GOROOT = strings.Split(conf.Build.GOPATH, ":")[0] + langGOROOT } else { if conf.Build.GOROOT == "" { return fmt.Errorf("GOROOT must be set (hint: use -tgoroot flag)") } } } else { conf.Build.GOROOT = *tgoroot } } //fmt.Println("DEBUG GOPATH", conf.Build.GOPATH) //fmt.Println("DEBUG GOROOT", conf.Build.GOROOT) if *testFlag { conf.ImportWithTests(args[0]) // assumes you give the full cannonical name of the package to test args = args[1:] } // Use the initial packages from the command line. _, err := conf.FromArgs(args, *testFlag) if err != nil { return err } // The interpreter needs the runtime package. if *runFlag { conf.Import("runtime") } else { // TARDIS GO additional line to add the language specific go runtime code rt := pogo.LanguageList[langEntry].Goruntime if rt != "" { conf.Import(rt) } } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. *modeFlag |= mode | ssa.SanityCheckFunctions prog := ssautil.CreateProgram(iprog, *modeFlag) prog.Build() var main *ssa.Package pkgs := prog.AllPackages() //fmt.Println("DEBUG pkgs:", pkgs) testFSname := "" if *testFlag { // If -test, run all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } fd, openErr := os.Open(testFS) closeErr := fd.Close() if openErr == nil && closeErr == nil { loadTestZipFS = true testFSname = testFS } } else { // Otherwise, run main.main. for _, pkg := range pkgs { if pkg.Pkg.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 *runFlag { // Run the golang.org/x/tools/go/ssa/interp interpreter. interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Pkg.Path(), args) } else { comp, err := pogo.Compile(main, *debugFlag, *traceFlag, langName, testFSname) // TARDIS Go entry point, returns an error if err != nil { return err } comp.Recycle() switch langName { case "haxe": haxe.RunHaxe(allFlag, loadTestZipFS, testFSname) } } return nil }