// visitInstrs visits all SSA instructions in the program. func (a *analysis) visitInstrs(pta bool) { log.Print("Visit instructions...") for fn := range ssautil.AllFunctions(a.prog) { for _, b := range fn.Blocks { for _, instr := range b.Instrs { // CALLEES (static) // (Dynamic calls require pointer analysis.) // // We use the SSA representation to find the static callee, // since in many cases it does better than the // types.Info.{Refs,Selection} information. For example: // // defer func(){}() // static call to anon function // f := func(){}; f() // static call to anon function // f := fmt.Println; f() // static call to named function // // The downside is that we get no static callee information // for packages that (transitively) contain errors. if site, ok := instr.(ssa.CallInstruction); ok { if callee := site.Common().StaticCallee(); callee != nil { // TODO(adonovan): callgraph: elide wrappers. // (Do static calls ever go to wrappers?) if site.Common().Pos() != token.NoPos { a.addCallees(site, []*ssa.Function{callee}) } } } if !pta { continue } // CHANNEL PEERS // Collect send/receive/close instructions in the whole ssa.Program. for _, op := range chanOps(instr) { a.ops = append(a.ops, op) a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query } } } } log.Print("Visit instructions complete") }
// translatePackage translates an *ssa.Package into an LLVM module, and returns // the translation unit information. func (u *unit) translatePackage(pkg *ssa.Package) { // Initialize global storage. for _, m := range pkg.Members { switch v := m.(type) { case *ssa.Global: llelemtyp := u.llvmtypes.ToLLVM(deref(v.Type())) global := llvm.AddGlobal(u.module.Module, llelemtyp, v.String()) global.SetInitializer(llvm.ConstNull(llelemtyp)) u.globals[v] = u.NewValue(global, v.Type()) } } // Define functions. // Sort if flag is set for deterministic behaviour (for debugging) functions := ssautil.AllFunctions(pkg.Prog) if !u.compiler.OrderedCompilation { for f, _ := range functions { u.defineFunction(f) } } else { fns := []*ssa.Function{} for f, _ := range functions { fns = append(fns, f) } sort.Sort(byName(fns)) for _, f := range fns { u.defineFunction(f) } } // Define remaining functions that were resolved during // runtime type mapping, but not defined. for f, _ := range u.undefinedFuncs { u.defineFunction(f) } }
func TestStdlib(t *testing.T) { if !*runStdlibTest { t.Skip("skipping (slow) stdlib test (use --stdlib)") } // Load, parse and type-check the program. ctxt := build.Default // copy ctxt.GOPATH = "" // disable GOPATH conf := loader.Config{ SourceImports: true, Build: &ctxt, } if _, err := conf.FromArgs(buildutil.AllPackages(conf.Build), true); err != nil { t.Errorf("FromArgs failed: %v", err) return } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } // Create SSA packages. prog := ssa.Create(iprog, 0) prog.BuildAll() numPkgs := len(prog.AllPackages()) if want := 240; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Determine the set of packages/tests to analyze. var testPkgs []*ssa.Package for _, info := range iprog.InitialPackages() { testPkgs = append(testPkgs, prog.Package(info.Pkg)) } testmain := prog.CreateTestMainPackage(testPkgs...) if testmain == nil { t.Fatal("analysis scope has tests") } // Run the analysis. config := &Config{ Reflection: false, // TODO(adonovan): fix remaining bug in rVCallConstraint, then enable. BuildCallGraph: true, Mains: []*ssa.Package{testmain}, } // TODO(adonovan): add some query values (affects track bits). t0 := time.Now() result, err := Analyze(config) if err != nil { t.Fatal(err) // internal error in pointer analysis } _ = result // TODO(adonovan): measure something t1 := time.Now() // Dump some statistics. allFuncs := ssautil.AllFunctions(prog) var numInstrs int for fn := range allFuncs { for _, b := range fn.Blocks { numInstrs += len(b.Instrs) } } // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) t.Log("#Source lines: ", lineCount) t.Log("#Instructions: ", numInstrs) t.Log("Pointer analysis: ", t1.Sub(t0)) }
func doOneInput(input, filename string) bool { conf := loader.Config{SourceImports: true} // Parsing. f, err := conf.ParseFile(filename, input) if err != nil { fmt.Println(err) return false } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { fmt.Println(err) return false } mainPkgInfo := iprog.Created[0].Pkg // SSA creation + building. prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() mainpkg := prog.Package(mainPkgInfo) ptrmain := mainpkg // main package for the pointer analysis if mainpkg.Func("main") == nil { // No main function; assume it's a test. ptrmain = prog.CreateTestMainPackage(mainpkg) } // Find all calls to the built-in print(x). Analytically, // print is a no-op, but it's a convenient hook for testing // the PTS of an expression, so our tests use it. probes := make(map[*ssa.CallCommon]bool) for fn := range ssautil.AllFunctions(prog) { if fn.Pkg == mainpkg { for _, b := range fn.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(ssa.CallInstruction); ok { if b, ok := instr.Common().Value.(*ssa.Builtin); ok && b.Name() == "print" { probes[instr.Common()] = true } } } } } } ok := true lineMapping := make(map[string]string) // maps "file:line" to @line tag // Parse expectations in this input. var exps []*expectation re := regexp.MustCompile("// *@([a-z]*) *(.*)$") lines := strings.Split(input, "\n") for linenum, line := range lines { linenum++ // make it 1-based if matches := re.FindAllStringSubmatch(line, -1); matches != nil { match := matches[0] kind, rest := match[1], match[2] e := &expectation{kind: kind, filename: filename, linenum: linenum} if kind == "line" { if rest == "" { ok = false e.errorf("@%s expectation requires identifier", kind) } else { lineMapping[fmt.Sprintf("%s:%d", filename, linenum)] = rest } continue } if e.needsProbe() && !strings.Contains(line, "print(") { ok = false e.errorf("@%s expectation must follow call to print(x)", kind) continue } switch kind { case "pointsto": e.args = split(rest, "|") case "types": for _, typstr := range split(rest, "|") { var t types.Type = types.Typ[types.Invalid] // means "..." if typstr != "..." { texpr, err := parser.ParseExpr(typstr) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type", typstr) continue } mainFileScope := mainpkg.Object.Scope().Child(0) t, _, err = types.EvalNode(prog.Fset, texpr, mainpkg.Object, mainFileScope) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type: %s", typstr, err) continue } } e.types = append(e.types, t) } case "calls": e.args = split(rest, "->") // TODO(adonovan): eagerly reject the // expectation if fn doesn't denote // existing function, rather than fail // the expectation after analysis. if len(e.args) != 2 { ok = false e.errorf("@calls expectation wants 'caller -> callee' arguments") continue } case "warning": lit, err := strconv.Unquote(strings.TrimSpace(rest)) if err != nil { ok = false e.errorf("couldn't parse @warning operand: %s", err.Error()) continue } e.args = append(e.args, lit) default: ok = false e.errorf("unknown expectation kind: %s", e) continue } exps = append(exps, e) } } var log bytes.Buffer fmt.Fprintf(&log, "Input: %s\n", filename) // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, } for probe := range probes { v := probe.Args[0] if pointer.CanPoint(v.Type()) { config.AddQuery(v) } } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // Check the expectations. for _, e := range exps { var call *ssa.CallCommon var pts pointer.PointsToSet var tProbe types.Type if e.needsProbe() { if call, pts = findProbe(prog, probes, result.Queries, e); call == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } tProbe = call.Args[0].Type() if !pointer.CanPoint(tProbe) { ok = false e.errorf("expectation on non-pointerlike operand: %s", tProbe) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, pts, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pts, tProbe) { ok = false } case "calls": if !checkCallsExpectation(prog, e, result.CallGraph) { ok = false } case "warning": if !checkWarningExpectation(prog, e, result.Warnings) { ok = false } } } complete = true // ok = false // debugging: uncomment to always see log return ok }
// peers enumerates, for a given channel send (or receive) operation, // the set of possible receives (or sends) that correspond to it. // // TODO(adonovan): support reflect.{Select,Recv,Send,Close}. // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv), // or the implicit receive in "for v := range ch". // TODO(adonovan): support "close" as a channel op. // func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { arrowPos := findArrow(qpos) if arrowPos == token.NoPos { return nil, fmt.Errorf("there is no send/receive here") } buildSSA(o) var queryOp chanOp // the originating send or receive operation var ops []chanOp // all sends/receives of opposite direction // Look at all send/receive instructions in the whole ssa.Program. // Build a list of those of same type to query. allFuncs := ssautil.AllFunctions(o.prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range chanOps(instr) { ops = append(ops, op) if op.pos == arrowPos { queryOp = op // we found the query op } } } } } if queryOp.ch == nil { return nil, fmt.Errorf("ssa.Instruction for send/receive not found") } // Discard operations of wrong channel element type. // Build set of channel ssa.Values as query to pointer analysis. // We compare channels by element types, not channel types, to // ignore both directionality and type names. queryType := queryOp.ch.Type() queryElemType := queryType.Underlying().(*types.Chan).Elem() o.ptaConfig.AddQuery(queryOp.ch) i := 0 for _, op := range ops { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { o.ptaConfig.AddQuery(op.ch) ops[i] = op i++ } } ops = ops[:i] // Run the pointer analysis. ptares := ptrAnalysis(o) // Find the points-to set. queryChanPtr := ptares.Queries[queryOp.ch] // Ascertain which make(chan) labels the query's channel can alias. var makes []token.Pos for _, label := range queryChanPtr.PointsTo().Labels() { makes = append(makes, label.Pos()) } sort.Sort(byPos(makes)) // Ascertain which send/receive operations can alias the same make(chan) labels. var sends, receives []token.Pos for _, op := range ops { if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { if op.dir == types.SendOnly { sends = append(sends, op.pos) } else { receives = append(receives, op.pos) } } } sort.Sort(byPos(sends)) sort.Sort(byPos(receives)) return &peersResult{ queryPos: arrowPos, queryType: queryType, makes: makes, sends: sends, receives: receives, }, nil }
func TestStdlib(t *testing.T) { // Load, parse and type-check the program. t0 := time.Now() var conf loader.Config conf.SourceImports = true if _, err := conf.FromArgs(allPackages(), true); err != nil { t.Errorf("FromArgs failed: %v", err) return } iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } t1 := time.Now() runtime.GC() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Create SSA packages. var mode ssa.BuilderMode // Comment out these lines during benchmarking. Approx SSA build costs are noted. mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time mode |= ssa.GlobalDebug // +30% space, +18% time prog := ssa.Create(iprog, mode) t2 := time.Now() // Build SSA. prog.BuildAll() t3 := time.Now() runtime.GC() runtime.ReadMemStats(&memstats) numPkgs := len(prog.AllPackages()) if want := 140; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } allFuncs := ssautil.AllFunctions(prog) // Check that all non-synthetic functions have distinct names. byName := make(map[string]*ssa.Function) for fn := range allFuncs { if fn.Synthetic == "" { str := fn.String() prev := byName[str] byName[str] = fn if prev != nil { t.Errorf("%s: duplicate function named %s", prog.Fset.Position(fn.Pos()), str) t.Errorf("%s: (previously defined here)", prog.Fset.Position(prev.Pos())) } } } // Dump some statistics. var numInstrs int for fn := range allFuncs { for _, b := range fn.Blocks { numInstrs += len(b.Instrs) } } // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) // NB: when benchmarking, don't forget to clear the debug + // sanity builder flags for better performance. t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) t.Log("#Source lines: ", lineCount) t.Log("Load/parse/typecheck: ", t1.Sub(t0)) t.Log("SSA create: ", t2.Sub(t1)) t.Log("SSA build: ", t3.Sub(t2)) // SSA stats: t.Log("#Packages: ", numPkgs) t.Log("#Functions: ", len(allFuncs)) t.Log("#Instructions: ", numInstrs) t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) }
// For every function, maybe emit the code... func emitFunctions() { fnMap := ssautil.AllFunctions(rootProgram) for f := range fnMap { pn := "unknown" // Defensive, as some synthetic or other edge-case functions may not have a valid package name rx := f.Signature.Recv() if rx == nil { // ordinary function if f.Pkg != nil { if f.Pkg.Object != nil { pn = f.Pkg.Object.Name() } } else { if f.Object() != nil { if f.Object().Pkg() != nil { pn = f.Object().Pkg().Name() } } } } else { // determine the package information from the type description typ := rx.Type() ts := typ.String() if ts[0:1] == "*" { ts = ts[1:] // loose the leading star } tss := strings.Split(ts, ".") if len(tss) >= 2 { ts = tss[len(tss)-2] // take the part before the final dot } else { ts = tss[0] // no dot! } tss = strings.Split(ts, "/") // TODO check this also works in Windows ts = tss[len(tss)-1] // take the last part of the path //fmt.Printf("DEBUG function method: fn, typ, pathEnd = %s %s %s\n", f, typ, ts) pn = ts } // exclude functions from emulated overloaded packages (initially none) _, _, pov := LanguageList[TargetLang].PackageOverloaded(pn) pnCount := 0 // how many packages have this package name? // TODO possible code duplication! Consider using isDupPkg() in language.go for this. ap := rootProgram.AllPackages() for p := range ap { if pn == ap[p].Object.Name() { pnCount++ } } //if pn == "haxegoruntime" { // DEBUG // fmt.Println("DEBUG RelString=", f.RelString(nil), "===", pn, "===", pnCount) //} if !pov && // the package is not overloaded and !LanguageList[TargetLang].FunctionOverloaded(pn, f.Name()) && !strings.HasPrefix(pn, "_") && // the package is not in the target language, signaled by a leading underscore and !(f.Name() == "init" && strings.HasPrefix(f.RelString(nil), LibRuntimePath) && pnCount > 1) { // not (an init function and in the libruntimepath and more than 1 package has this name) emitFunc(f) } else { //fmt.Println("DEBUG: function not emitted - RelString=", f.RelString(nil), "===", pn, "===", pnCount) } } }