func loadProgram(args []string, tests bool) (*ssa.Program, error) { conf := loader.Config{} if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) os.Exit(1) } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return nil, err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return nil, err } // Create and build SSA-form program representation. prog := ssautil.CreateProgram(iprog, 0) prog.BuildAll() return prog, nil }
// Return count of unused vars func myloader(args []string) (count int) { var conf loader.Config conf.FromArgs(args, false) prog, err := conf.Load() if err != nil { log.Fatal(err) } for _, pi := range prog.Created { fmt.Println(pi) } info := prog.Package(args[0]).Info fset := prog.Fset ssaprog := ssautil.CreateProgram(prog, ssa.GlobalDebug) ssaprog.Build() for expr, object := range info.Defs { unused := checkObj(expr, object, prog, ssaprog, fset) if unused { fmt.Fprintf(os.Stderr, "Unused assignment for '%v' %v\n", expr, fset.Position(expr.Pos())) count++ } } for expr, object := range info.Uses { unused := checkObj(expr, object, prog, ssaprog, fset) if unused { fmt.Fprintf(os.Stderr, "Unused assignment for '%v' %v\n", expr, fset.Position(expr.Pos())) count++ } } return count }
// Build constructs the SSA IR using given config, and sets up pointer analysis. func (conf *Config) Build() (*SSAInfo, error) { var lconf = loader.Config{Build: &build.Default} buildLog := log.New(conf.BuildLog, "ssabuild: ", conf.LogFlags) if conf.BuildMode == FromFiles { args, err := lconf.FromArgs(conf.Files, false /* No tests */) if err != nil { return nil, err } if len(args) > 0 { return nil, fmt.Errorf("surplus arguments: %q", args) } } else if conf.BuildMode == FromString { f, err := lconf.ParseFile("", conf.Source) if err != nil { return nil, err } lconf.CreateFromFiles("", f) } else { buildLog.Fatal("Unknown build mode") } // Load, parse and type-check program lprog, err := lconf.Load() if err != nil { return nil, err } buildLog.Print("Program loaded and type checked") prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug|ssa.BareInits) // Prepare Config for whole-program pointer analysis. ptaConf, err := setupPTA(prog, lprog, conf.PtaLog) ignoredPkgs := []string{} if len(conf.BadPkgs) == 0 { prog.Build() } else { for _, info := range lprog.AllPackages { if reason, badPkg := conf.BadPkgs[info.Pkg.Name()]; badPkg { buildLog.Printf("Skip package: %s (%s)", info.Pkg.Name(), reason) ignoredPkgs = append(ignoredPkgs, info.Pkg.Name()) } else { prog.Package(info.Pkg).Build() } } } return &SSAInfo{ BuildConf: conf, IgnoredPkgs: ignoredPkgs, FSet: lprog.Fset, Prog: prog, PtaConf: ptaConf, Logger: buildLog, }, nil }
// 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) }
// addCode searches for main func in data, and updates AST code // adding tracing functions. func addCode(path string) ([]byte, error) { var conf loader.Config if _, err := conf.FromArgs([]string{path}, false); err != nil { return nil, err } prog, err := conf.Load() if err != nil { return nil, err } // check if runtime/trace already imported for i, _ := range prog.Imported { if i == "runtime/trace" { return nil, ErrImported } } pkg := prog.Created[0] // TODO: find file with main func inside astFile := pkg.Files[0] // add imports astutil.AddImport(prog.Fset, astFile, "os") astutil.AddImport(prog.Fset, astFile, "runtime/trace") astutil.AddImport(prog.Fset, astFile, "time") // add start/stop code ast.Inspect(astFile, func(n ast.Node) bool { switch x := n.(type) { case *ast.FuncDecl: // find 'main' function if x.Name.Name == "main" && x.Recv == nil { stmts := createTraceStmts() stmts = append(stmts, x.Body.List...) x.Body.List = stmts return true } } return true }) var buf bytes.Buffer err = printer.Fprint(&buf, prog.Fset, astFile) if err != nil { return nil, err } return buf.Bytes(), nil }
func setPTAScope(lconf *loader.Config, scope []string) error { if len(scope) == 0 { return fmt.Errorf("no packages specified for pointer analysis scope") } // Determine initial packages for PTA. args, err := lconf.FromArgs(scope, true) if err != nil { return err } if len(args) > 0 { return fmt.Errorf("surplus arguments: %q", args) } return nil }
func loadProgram(args []string) (*loader.Program, error) { var conf loader.Config rest, err := conf.FromArgs(args, true) if err != nil { return nil, fmt.Errorf("could not parse arguments: %s", err) } if len(rest) > 0 { return nil, fmt.Errorf("unhandled extra arguments: %v", rest) } prog, err := conf.Load() if err != nil { return nil, fmt.Errorf("could not load: %s", err) } return prog, nil }
// Main is like calling the 'gog' program. func Main(config *loader.Config, args []string) (*Output, error) { var importUnsafe bool for _, a := range args { if a == "unsafe" { importUnsafe = true break } } extraArgs, err := config.FromArgs(args, true) if err != nil { log.Fatal(err) } if len(extraArgs) > 0 { return nil, fmt.Errorf("extra args after pkgs list") } if importUnsafe { // Special-case "unsafe" because go/loader does not let you load it // directly. if config.ImportPkgs == nil { config.ImportPkgs = make(map[string]bool) } config.ImportPkgs["unsafe"] = true } prog, err := config.Load() if err != nil { return nil, err } g := New(prog) var pkgs []*loader.PackageInfo pkgs = append(pkgs, prog.Created...) for _, pkg := range prog.Imported { pkgs = append(pkgs, pkg) } for _, pkg := range pkgs { if err := g.Graph(pkg); err != nil { return nil, err } } return &g.Output, nil }
func (c *Checker) load(paths ...string) (*loader.Program, error) { ctx := build.Default for _, tag := range c.Tags { ctx.BuildTags = append(ctx.BuildTags, tag) } loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs(paths, !c.WithoutTests) if err != nil { return nil, fmt.Errorf("could not parse arguments: %s", err) } if len(rest) > 0 { return nil, fmt.Errorf("unhandled extra arguments: %v", rest) } return loadcfg.Load() }
func main() { flag.Parse() // The loader loads a complete Go program from source code. var conf loader.Config _, err := conf.FromArgs(flag.Args(), false) if err != nil { log.Fatal(err) // command syntax error } lprog, err := conf.Load() if err != nil { log.Fatal(err) // load error } for _, info := range lprog.InitialPackages() { PrintHugeParams(lprog.Fset, &info.Info, info.Files) } }
// This example loads a set of packages and all of their dependencies // from a typical command-line. FromArgs parses a command line and // makes calls to the other methods of Config shown in the examples that // follow. func ExampleConfig_FromArgs() { args := []string{"mytool", "unicode/utf8", "errors", "runtime", "--", "foo", "bar"} const wantTests = false var conf loader.Config rest, err := conf.FromArgs(args[1:], wantTests) prog, err := conf.Load() if err != nil { log.Fatal(err) } fmt.Printf("rest: %s\n", rest) printProgram(prog) // Output: // rest: [foo bar] // created: [] // imported: [errors runtime unicode/utf8] // initial: [errors runtime unicode/utf8] // all: [errors runtime unicode/utf8] }
func run(t *testing.T, dir, input string, success successPredicate) bool { fmt.Printf("Input: %s\n", input) start := time.Now() var inputs []string for _, i := range strings.Split(input, " ") { if strings.HasSuffix(i, ".go") { i = dir + i } inputs = append(inputs, i) } var conf loader.Config if _, err := conf.FromArgs(inputs, true); err != nil { t.Errorf("FromArgs(%s) failed: %s", inputs, err) return false } conf.Import("runtime") // Print a helpful hint if we don't make it to the end. var hint string defer func() { if hint != "" { fmt.Println("FAIL") fmt.Println(hint) } else { fmt.Println("PASS") } interp.CapturedOutput = nil }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) iprog, err := conf.Load() if err != nil { t.Errorf("conf.Load(%s) failed: %s", inputs, err) return false } prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog.BuildAll() var mainPkg *ssa.Package var initialPkgs []*ssa.Package for _, info := range iprog.InitialPackages() { if info.Pkg.Path() == "runtime" { continue // not an initial package } p := prog.Package(info.Pkg) initialPkgs = append(initialPkgs, p) if mainPkg == nil && p.Func("main") != nil { mainPkg = p } } if mainPkg == nil { testmainPkg := prog.CreateTestMainPackage(initialPkgs...) if testmainPkg == nil { t.Errorf("CreateTestMainPackage(%s) returned nil", mainPkg) return false } if testmainPkg.Func("main") == nil { t.Errorf("synthetic testmain package has no main") return false } mainPkg = testmainPkg } var out bytes.Buffer interp.CapturedOutput = &out hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{8, 8}, inputs[0], []string{}) // The definition of success varies with each file. if err := success(exitCode, out.String()); err != nil { t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) return false } hint = "" // call off the hounds if false { fmt.Println(input, time.Since(start)) // test profiling } return true }
// 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{ AllowErrors: true, } // Silence the default error handler. // Don't print all errors; we'll report just // one per errant package later. conf.TypeChecker.Error = func(e error) {} var roots, args []string // roots[i] ends with os.PathSeparator // Enumerate packages in $GOROOT. root := filepath.Join(runtime.GOROOT(), "src") + 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{"golang.org/x/tools/cmd/godoc"} //args = []string{"fmt"} if _, err := conf.FromArgs(args, true); err != nil { // TODO(adonovan): degrade gracefully, not fail totally. // (The crippling case is a parse error in an external test file.) result.setStatusf("Analysis failed: %s.", err) // import error return } result.setStatusf("Loading and type-checking packages...") iprog, err := conf.Load() if iprog != nil { // Report only the first error of each package. for _, info := range iprog.AllPackages { for _, err := range info.Errors { fmt.Fprintln(os.Stderr, err) break } } log.Printf("Loaded %d packages.", len(iprog.AllPackages)) } if err != nil { result.setStatusf("Loading failed: %s.\n", err) return } // Create SSA-form program representation. // Only the transitively error-free packages are used. prog := ssautil.CreateProgram(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) if p := testmain.Const("packages"); p != nil { log.Printf("Tested packages: %v", exact.StringVal(p.Value.Value)) } } for _, pkg := range allPackages { if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { mainPkgs = append(mainPkgs, pkg) } } log.Print("Transitively error-free main packages: ", mainPkgs) // Build SSA code for bodies of all functions in the whole program. result.setStatusf("Constructing SSA form...") prog.Build() log.Print("SSA construction 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/" plus path relative to GOROOT/src or GOPATH[i]/src. a.path2url = make(map[string]string) for _, info := range iprog.AllPackages { nextfile: for _, f := range info.Files { if f.Pos() == 0 { continue // e.g. files generated by cgo } 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/" + filepath.ToSlash(rel) continue nextfile } } log.Printf("Can't locate file %s (package %q) beneath any root", abs, info.Pkg.Path()) } } // Add links for scanner, parser, type-checker errors. // TODO(adonovan): fix: these links can overlap with // identifier markup, causing the renderer to emit some // characters twice. errors := make(map[token.Position][]string) for _, info := range iprog.AllPackages { for _, err := range info.Errors { switch err := err.(type) { case types.Error: posn := a.prog.Fset.Position(err.Pos) errors[posn] = append(errors[posn], err.Msg) case scanner.ErrorList: for _, e := range err { errors[e.Pos] = append(errors[e.Pos], e.Msg) } default: log.Printf("Package %q has error (%T) without position: %v\n", info.Pkg.Path(), err, err) } } } for posn, errs := range errors { fi, offset := a.fileAndOffsetPosn(posn) 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 relation...") 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) result.setStatusf("Type analysis complete.") if pta { a.pointer(mainPkgs) } }
// TestFromArgs checks that conf.FromArgs populates conf correctly. // It does no I/O. func TestFromArgs(t *testing.T) { type result struct { Err string Rest []string ImportPkgs map[string]bool CreatePkgs []loader.PkgSpec } for _, test := range []struct { args []string tests bool want result }{ // Mix of existing and non-existent packages. { args: []string{"nosuchpkg", "errors"}, want: result{ ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false}, }, }, // Same, with -test flag. { args: []string{"nosuchpkg", "errors"}, tests: true, want: result{ ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true}, }, }, // Surplus arguments. { args: []string{"fmt", "errors", "--", "surplus"}, want: result{ Rest: []string{"surplus"}, ImportPkgs: map[string]bool{"errors": false, "fmt": false}, }, }, // Ad hoc package specified as *.go files. { args: []string{"foo.go", "bar.go"}, want: result{CreatePkgs: []loader.PkgSpec{{ Filenames: []string{"foo.go", "bar.go"}, }}}, }, // Mixture of *.go and import paths. { args: []string{"foo.go", "fmt"}, want: result{ Err: "named files must be .go files: fmt", }, }, } { var conf loader.Config rest, err := conf.FromArgs(test.args, test.tests) got := result{ Rest: rest, ImportPkgs: conf.ImportPkgs, CreatePkgs: conf.CreatePkgs, } if err != nil { got.Err = err.Error() } if !reflect.DeepEqual(got, test.want) { t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want) } } }
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 doMain() error { flag.Parse() args := flag.Args() conf := loader.Config{Build: &build.Default} // Choose types.Sizes from conf.Build. var wordSize int64 = 8 switch conf.Build.GOARCH { case "386", "arm": wordSize = 4 } conf.TypeChecker.Sizes = &types.StdSizes{ MaxAlign: 8, WordSize: wordSize, } var interpMode interp.Mode for _, c := range *interpFlag { switch c { case 'T': interpMode |= interp.EnableTracing case 'R': interpMode |= interp.DisableRecover default: return fmt.Errorf("unknown -interp option: '%c'", c) } } if len(args) == 0 { fmt.Fprint(os.Stderr, usage) os.Exit(1) } // Profiling support. if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { 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 := ssautil.CreateProgram(iprog, *modeFlag) // Build and display only the initial packages // (and synthetic wrappers), unless -run is specified. for _, info := range iprog.InitialPackages() { prog.Package(info.Pkg).Build() } // Run the interpreter. if *runFlag { prog.Build() 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.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 runtime.GOARCH != build.Default.GOARCH { return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)", build.Default.GOARCH, runtime.GOARCH) } interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Pkg.Path(), args) } return nil }
func runExtract(cmd *Command, args []string) error { if len(args) == 0 { args = []string{"."} } conf := loader.Config{ Build: &build.Default, ParserMode: parser.ParseComments, } // Use the initial packages from the command line. args, err := conf.FromArgs(args, false) if err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // print returns Go syntax for the specified node. print := func(n ast.Node) string { var buf bytes.Buffer format.Node(&buf, conf.Fset, n) return buf.String() } var translations []Translation for _, info := range iprog.InitialPackages() { for _, f := range info.Files { // Associate comments with nodes. cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments) getComment := func(n ast.Node) string { cs := cmap.Filter(n).Comments() if len(cs) > 0 { return strings.TrimSpace(cs[0].Text()) } return "" } // Find function calls. ast.Inspect(f, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } // Skip calls of functions other than // (*message.Printer).{Sp,Fp,P}rintf. sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return true } meth := info.Selections[sel] if meth == nil || meth.Kind() != types.MethodVal { return true } // TODO: remove cheap hack and check if the type either // implements some interface or is specifically of type // "golang.org/x/text/message".Printer. m, ok := extractFuncs[path.Base(meth.Recv().String())] if !ok { return true } // argn is the index of the format string. argn, ok := m[meth.Obj().Name()] if !ok || argn >= len(call.Args) { return true } // Skip calls with non-constant format string. fmtstr := info.Types[call.Args[argn]].Value if fmtstr == nil || fmtstr.Kind() != constant.String { return true } posn := conf.Fset.Position(call.Lparen) filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column) // TODO: identify the type of the format argument. If it is not // a string, multiple keys may be defined. var key []string // TODO: replace substitutions (%v) with a translator friendly // notation. For instance: // "%d files remaining" -> "{numFiles} files remaining", or // "%d files remaining" -> "{arg1} files remaining" // Alternatively, this could be done at a later stage. msg := constant.StringVal(fmtstr) // Construct a Translation unit. c := Translation{ Key: key, Position: filepath.Join(info.Pkg.Path(), filepos), Original: Text{Msg: msg}, ExtractedComment: getComment(call.Args[0]), // TODO(fix): this doesn't get the before comment. // Comment: getComment(call), } for i, arg := range call.Args[argn+1:] { var val string if v := info.Types[arg].Value; v != nil { val = v.ExactString() } posn := conf.Fset.Position(arg.Pos()) filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column) c.Args = append(c.Args, Argument{ ID: i + 1, Type: info.Types[arg].Type.String(), UnderlyingType: info.Types[arg].Type.Underlying().String(), Expr: print(arg), Value: val, Comment: getComment(arg), Position: filepath.Join(info.Pkg.Path(), filepos), // TODO report whether it implements // interfaces plural.Interface, // gender.Interface. }) } translations = append(translations, c) return true }) } } data, err := json.MarshalIndent(translations, "", " ") if err != nil { return err } for _, tag := range getLangs() { // TODO: merge with existing files, don't overwrite. os.MkdirAll(*dir, 0744) file := filepath.Join(*dir, fmt.Sprintf("gotext_%v.out.json", tag)) if err := ioutil.WriteFile(file, data, 0744); err != nil { return fmt.Errorf("could not create file: %v", err) } } return nil }
func main() { flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } ctx := build.Default for _, pkgPath := range importPaths { visitor := &visitor{ m: make(map[types.Type]map[string]int), skip: make(map[types.Type]struct{}), } loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs([]string{pkgPath}, *loadTestFiles) if err != nil { fmt.Fprintf(os.Stderr, "could not parse arguments: %s", err) continue } if len(rest) > 0 { fmt.Fprintf(os.Stderr, "unhandled extra arguments: %v", rest) continue } program, err := loadcfg.Load() if err != nil { fmt.Fprintf(os.Stderr, "could not type check: %s", err) continue } pkg := program.InitialPackages()[0] visitor.prog = program visitor.pkg = pkg for _, f := range pkg.Files { ast.Walk(visitor, f) } for t := range visitor.m { if _, skip := visitor.skip[t]; skip { continue } for fieldName, v := range visitor.m[t] { if !*reportExported && ast.IsExported(fieldName) { continue } if v == 0 { field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName) if fieldName == "XMLName" { if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" { continue } } pos := program.Fset.Position(field.Pos()) fmt.Printf("%s: %s:%d:%d: %s.%s\n", pkgPath, pos.Filename, pos.Line, pos.Column, types.TypeString(t, nil), fieldName, ) exitStatus = 1 } } } } os.Exit(exitStatus) }
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.PrintPackages case 'F': mode |= ssa.PrintFunctions 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 case 'I': mode |= ssa.BareInits 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: return fmt.Errorf("unknown -interp option: '%c'", c) } } if len(args) == 0 { fmt.Fprint(os.Stderr, usage) os.Exit(1) } // Profiling support. if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { 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 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 main() { flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } ctx := build.Default loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs(importPaths, true) if err != nil { log.Fatalf("could not parse arguments: %s", err) } if len(rest) > 0 { log.Fatalf("unhandled extra arguments: %v", rest) } program, err := loadcfg.Load() if err != nil { log.Fatalf("could not type check: %s", err) } uses := make(map[object]int) positions := make(map[object]token.Position) for _, pkgInfo := range program.InitialPackages() { if pkgInfo.Pkg.Path() == "unsafe" { continue } v := &visitor{ prog: program, pkg: pkgInfo, uses: uses, positions: positions, } for _, f := range v.pkg.Files { ast.Walk(v, f) } } var lines []string for obj, useCount := range uses { if useCount == 0 && (*reportExported || !ast.IsExported(obj.name)) { pos := positions[obj] lines = append(lines, fmt.Sprintf("%s: %s:%d:%d: %s", obj.pkgPath, pos.Filename, pos.Line, pos.Column, obj.name)) exitStatus = 1 } } sort.Strings(lines) for _, line := range lines { fmt.Println(line) } os.Exit(exitStatus) }
func main() { vPtr := flag.Bool("v", false, "verbose output (outputs recommended alignments)") flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } ctx := build.Default loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs(importPaths, false) if err != nil { log.Fatalf("could not parse arguments: %s", err) } if len(rest) > 0 { log.Fatalf("unhandled extra arguments: %v", rest) } program, err := loadcfg.Load() if err != nil { log.Fatalf("could not type check: %s", err) } var lines []string for _, pkgInfo := range program.InitialPackages() { for _, obj := range pkgInfo.Defs { if obj == nil { continue } if _, ok := obj.(*types.TypeName); !ok { continue } typ, ok := obj.Type().(*types.Named) if !ok { continue } strukt, ok := typ.Underlying().(*types.Struct) if !ok { continue } structAlign := int(stdSizes.Alignof(strukt)) structSize := int(stdSizes.Sizeof(strukt)) if structSize%structAlign != 0 { structSize += structAlign - structSize%structAlign } fields := make(fieldList, 0, strukt.NumFields()) minSize := 0 for i := 0; i < strukt.NumFields(); i++ { field := strukt.Field(i) fieldType := field.Type() typeSize := int(stdSizes.Sizeof(fieldType)) minSize += typeSize fields = append(fields, structField{ name: field.Name(), size: typeSize, }) } if minSize%structAlign != 0 { minSize += structAlign - minSize%structAlign } if minSize != structSize { pos := program.Fset.Position(obj.Pos()) fieldLines := make([]string, 0, len(fields)) sort.Sort(fields) for _, v := range fields { fieldLines = append(fieldLines, fmt.Sprintf( "\t\t%s (size %d)", v.name, v.size, )) } line := fmt.Sprintf( "%s: %s:%d:%d: struct %s could have size %d (currently %d)", obj.Pkg().Path(), pos.Filename, pos.Line, pos.Column, obj.Name(), minSize, structSize, ) if *vPtr { line = line + fmt.Sprintf( ":\n\tRecommended alignment:\n%s", strings.Join(fieldLines, "\n"), ) } lines = append(lines, line) exitStatus = 1 } } } sort.Strings(lines) for _, line := range lines { fmt.Println(line) } os.Exit(exitStatus) }
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 !*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{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 := ssautil.CreateProgram(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 doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error { conf := loader.Config{ Build: ctxt, SourceImports: true, } if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) return nil } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return err } // 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, 0) prog.BuildAll() // Determine the main package. // TODO(adonovan): allow independent control over tests, mains // and libraries. // TODO(adonovan): put this logic in a library; we keep reinventing it. var main *ssa.Package pkgs := prog.AllPackages() if tests { // If -test, use all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } } else { // Otherwise, use 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") } } // Invariant: main package has a main() function. // -- call graph construction ------------------------------------------ var cg *callgraph.Graph switch algo { case "pta": config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, } ptares, err := pointer.Analyze(config) if err != nil { return err // internal error in pointer analysis } cg = ptares.CallGraph case "rta": roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } rtares := rta.Analyze(roots, true) cg = rtares.CallGraph // NB: RTA gives us Reachable and RuntimeTypes too. default: return fmt.Errorf("unknown algorithm: %s", algo) } cg.DeleteSyntheticNodes() // -- output------------------------------------------------------------ var before, after string // Pre-canned formats. switch format { case "digraph": format = `{{printf "%q %q" .Caller .Callee}}` case "graphviz": before = "digraph callgraph {\n" after = "}\n" format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"` } tmpl, err := template.New("-format").Parse(format) if err != nil { return fmt.Errorf("invalid -format template: %v", err) } // Allocate these once, outside the traversal. var buf bytes.Buffer data := Edge{fset: prog.Fset} fmt.Fprint(stdout, before) if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { data.position.Offset = -1 data.edge = edge data.Caller = edge.Caller.Func data.Callee = edge.Callee.Func buf.Reset() if err := tmpl.Execute(&buf, &data); err != nil { return err } stdout.Write(buf.Bytes()) if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' { fmt.Fprintln(stdout) } return nil }); err != nil { return err } fmt.Fprint(stdout, after) 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{ 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) } 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 := ssa.Create(iprog, mode) t2 := time.Now() // Build SSA. prog.BuildAll() 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) }
// CheckPackages checks packages for errors. func (c *Checker) CheckPackages(paths ...string) error { ctx := build.Default for _, tag := range c.Tags { ctx.BuildTags = append(ctx.BuildTags, tag) } loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs(paths, true) if err != nil { return fmt.Errorf("could not parse arguments: %s", err) } if len(rest) > 0 { return fmt.Errorf("unhandled extra arguments: %v", rest) } program, err := loadcfg.Load() if err != nil { return fmt.Errorf("could not type check: %s", err) } var errsMutex sync.Mutex var errs []UncheckedError var wg sync.WaitGroup for _, pkgInfo := range program.InitialPackages() { if pkgInfo.Pkg.Path() == "unsafe" { // not a real package continue } wg.Add(1) go func(pkgInfo *loader.PackageInfo) { defer wg.Done() c.logf("Checking %s", pkgInfo.Pkg.Path()) v := &visitor{ prog: program, pkg: pkgInfo, ignore: c.Ignore, blank: c.Blank, asserts: c.Asserts, lines: make(map[string][]string), errors: []UncheckedError{}, } for _, astFile := range v.pkg.Files { ast.Walk(v, astFile) } if len(v.errors) > 0 { errsMutex.Lock() defer errsMutex.Unlock() errs = append(errs, v.errors...) } }(pkgInfo) } wg.Wait() if len(errs) > 0 { u := UncheckedErrors{errs} sort.Sort(byName{u}) return u } return nil }
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error { conf := loader.Config{ Build: ctxt, SourceImports: true, } if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) return nil } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return err } // 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, 0) prog.BuildAll() // -- call graph construction ------------------------------------------ var cg *callgraph.Graph switch algo { case "static": cg = static.CallGraph(prog) case "cha": cg = cha.CallGraph(prog) case "pta": main, err := mainPackage(prog, tests) if err != nil { return err } config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, } ptares, err := pointer.Analyze(config) if err != nil { return err // internal error in pointer analysis } cg = ptares.CallGraph case "rta": main, err := mainPackage(prog, tests) if err != nil { return err } roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } rtares := rta.Analyze(roots, true) cg = rtares.CallGraph // NB: RTA gives us Reachable and RuntimeTypes too. default: return fmt.Errorf("unknown algorithm: %s", algo) } cg.DeleteSyntheticNodes() // -- output------------------------------------------------------------ var before, after string // Pre-canned formats. switch format { case "digraph": format = `{{printf "%q %q" .Caller .Callee}}` case "graphviz": before = "digraph callgraph {\n" after = "}\n" format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"` } tmpl, err := template.New("-format").Parse(format) if err != nil { return fmt.Errorf("invalid -format template: %v", err) } // Allocate these once, outside the traversal. var buf bytes.Buffer data := Edge{fset: prog.Fset} fmt.Fprint(stdout, before) if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { data.position.Offset = -1 data.edge = edge data.Caller = edge.Caller.Func data.Callee = edge.Callee.Func buf.Reset() if err := tmpl.Execute(&buf, &data); err != nil { return err } stdout.Write(buf.Bytes()) if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' { fmt.Fprintln(stdout) } return nil }); err != nil { return err } fmt.Fprint(stdout, after) return nil }
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 { // Run the before-edit command (e.g. "chmod +w", "checkout") if any. if *beforeeditFlag != "" { args := strings.Fields(*beforeeditFlag) // Replace "{}" with the filename, like find(1). for i := range args { if i > 0 { args[i] = strings.Replace(args[i], "{}", filename, -1) } } cmd := exec.Command(args[0], args[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n", args, err) } } if err := eg.WriteAST(iprog.Fset, filename, file); err != nil { fmt.Fprintf(os.Stderr, "eg: %s\n", err) hadErrors = true } } else { printer.Fprint(os.Stdout, iprog.Fset, file) } } } if hadErrors { os.Exit(1) } return nil }
func main() { flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } ctx := build.Default loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs(importPaths, false) if err != nil { log.Fatalf("could not parse arguments: %s", err) } if len(rest) > 0 { log.Fatalf("unhandled extra arguments: %v", rest) } program, err := loadcfg.Load() if err != nil { log.Fatalf("could not type check: %s", err) } var lines []string for _, pkgInfo := range program.InitialPackages() { for _, obj := range pkgInfo.Defs { if obj == nil { continue } if _, ok := obj.(*types.TypeName); !ok { continue } typ, ok := obj.Type().(*types.Named) if !ok { continue } strukt, ok := typ.Underlying().(*types.Struct) if !ok { continue } structAlign := int(stdSizes.Alignof(strukt)) structSize := int(stdSizes.Sizeof(strukt)) if structSize%structAlign != 0 { structSize += structAlign - structSize%structAlign } minSize := 0 for i := 0; i < strukt.NumFields(); i++ { field := strukt.Field(i) fieldType := field.Type() typeSize := int(stdSizes.Sizeof(fieldType)) minSize += typeSize } if minSize%structAlign != 0 { minSize += structAlign - minSize%structAlign } if minSize != structSize { pos := program.Fset.Position(obj.Pos()) lines = append(lines, fmt.Sprintf( "%s: %s:%d:%d: struct %s could have size %d (currently %d)", obj.Pkg().Path(), pos.Filename, pos.Line, pos.Column, obj.Name(), minSize, structSize, )) exitStatus = 1 } } } sort.Strings(lines) for _, line := range lines { fmt.Println(line) } os.Exit(exitStatus) }
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 }