// find all panic instructions in program // TODO: handle go panic() and defer panic(). They're handled // as builtin calls, but unlikely to show up in real code. func findPanics(prog *ssa.Program) []*ssa.Panic { var panics []*ssa.Panic for f := range ssautil.AllFunctions(prog) { for _, b := range f.Blocks { for _, i := range b.Instrs { p, ok := i.(*ssa.Panic) if ok { panics = append(panics, p) } } } } return panics }
// translatePackage translates an *ssa.Package into an LLVM module, and returns // the translation unit information. func (u *unit) translatePackage(pkg *ssa.Package) { ms := make([]ssa.Member, len(pkg.Members)) i := 0 for _, m := range pkg.Members { ms[i] = m i++ } sort.Sort(byMemberName(ms)) // Initialize global storage and type descriptors for this package. // We must create globals regardless of whether they're referenced, // hence the duplication in frame.value. for _, m := range ms { switch v := m.(type) { case *ssa.Global: elemtyp := deref(v.Type()) llelemtyp := u.llvmtypes.ToLLVM(elemtyp) vname := u.types.mc.mangleGlobalName(v) global := llvm.AddGlobal(u.module.Module, llelemtyp, vname) if !v.Object().Exported() { global.SetLinkage(llvm.InternalLinkage) } u.addGlobal(global, elemtyp) global = llvm.ConstBitCast(global, u.llvmtypes.ToLLVM(v.Type())) u.globals[v] = global case *ssa.Type: u.types.getTypeDescriptorPointer(v.Type()) } } // Define functions. u.defineFunctionsInOrder(ssautil.AllFunctions(pkg.Prog)) // Emit initializers for type descriptors, which may trigger // the resolution of additional functions. u.types.emitTypeDescInitializers() // Define remaining functions that were resolved during // runtime type mapping, but not defined. u.defineFunctionsInOrder(u.undefinedFuncs) // Set initializers for globals. for global, init := range u.globalInits { initval := init.build(global.Type().ElementType()) global.SetInitializer(initval) } }
// progChanOps extract all channels from a program. func progChanOps(prog *ssa.Program) []ChanOp { var ops []ChanOp // all sends/receives of opposite direction // Look at all channel operations in the whole ssa.Program. allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range chanOps(instr) { ops = append(ops, op) } } } } return ops }
// 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") }
// CallGraph computes the call graph of the specified program // considering only static calls. // func CallGraph(prog *ssa.Program) *callgraph.Graph { cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph // TODO(adonovan): opt: use only a single pass over the ssa.Program. for f := range ssautil.AllFunctions(prog) { fnode := cg.CreateNode(f) for _, b := range f.Blocks { for _, instr := range b.Instrs { if site, ok := instr.(ssa.CallInstruction); ok { if g := site.Common().StaticCallee(); g != nil { gnode := cg.CreateNode(g) callgraph.AddEdge(fnode, site, gnode) } } } } } return cg }
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) }
// CallGraph computes the call graph of the specified program using the // Class Hierarchy Analysis algorithm. // func CallGraph(prog *ssa.Program) *callgraph.Graph { cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph allFuncs := ssautil.AllFunctions(prog) // funcsBySig contains all functions, keyed by signature. It is // the effective set of address-taken functions used to resolve // a dynamic call of a particular signature. var funcsBySig typeutil.Map // value is []*ssa.Function // methodsByName contains all methods, // grouped by name for efficient lookup. methodsByName := make(map[string][]*ssa.Function) // methodsMemo records, for every abstract method call call I.f on // interface type I, the set of concrete methods C.f of all // types C that satisfy interface I. methodsMemo := make(map[*types.Func][]*ssa.Function) lookupMethods := func(m *types.Func) []*ssa.Function { methods, ok := methodsMemo[m] if !ok { I := m.Type().(*types.Signature).Recv().Type().Underlying().(*types.Interface) for _, f := range methodsByName[m.Name()] { C := f.Signature.Recv().Type() // named or *named if types.Implements(C, I) { methods = append(methods, f) } } methodsMemo[m] = methods } return methods } for f := range allFuncs { if f.Signature.Recv() == nil { // Package initializers can never be address-taken. if f.Name() == "init" && f.Synthetic == "package initializer" { continue } funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function) funcs = append(funcs, f) funcsBySig.Set(f.Signature, funcs) } else { methodsByName[f.Name()] = append(methodsByName[f.Name()], f) } } addEdge := func(fnode *callgraph.Node, site ssa.CallInstruction, g *ssa.Function) { gnode := cg.CreateNode(g) callgraph.AddEdge(fnode, site, gnode) } addEdges := func(fnode *callgraph.Node, site ssa.CallInstruction, callees []*ssa.Function) { // Because every call to a highly polymorphic and // frequently used abstract method such as // (io.Writer).Write is assumed to call every concrete // Write method in the program, the call graph can // contain a lot of duplication. // // TODO(adonovan): opt: consider factoring the callgraph // API so that the Callers component of each edge is a // slice of nodes, not a singleton. for _, g := range callees { addEdge(fnode, site, g) } } for f := range allFuncs { fnode := cg.CreateNode(f) for _, b := range f.Blocks { for _, instr := range b.Instrs { if site, ok := instr.(ssa.CallInstruction); ok { call := site.Common() if call.IsInvoke() { addEdges(fnode, site, lookupMethods(call.Method)) } else if g := call.StaticCallee(); g != nil { addEdge(fnode, site, g) } else if _, ok := call.Value.(*ssa.Builtin); !ok { callees, _ := funcsBySig.At(call.Signature()).([]*ssa.Function) addEdges(fnode, site, callees) } } } } } return cg }
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)) }
// 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". func peers(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // 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 } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } opPos := findOp(qpos) if opPos == token.NoPos { return fmt.Errorf("there is no channel operation here") } // Defer SSA construction till after errors are reported. prog.BuildAll() var queryOp chanOp // the originating send or receive operation var ops []chanOp // all sends/receives of opposite direction // Look at all channel operations in the whole ssa.Program. // Build a list of those of same type as the query. allFuncs := ssautil.AllFunctions(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 == opPos { queryOp = op // we found the query op } } } } } if queryOp.ch == nil { return 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() ptaConfig.AddQuery(queryOp.ch) i := 0 for _, op := range ops { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { ptaConfig.AddQuery(op.ch) ops[i] = op i++ } } ops = ops[:i] // Run the pointer analysis. ptares := ptrAnalysis(ptaConfig) // 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 channel operations can alias the same make(chan) labels. var sends, receives, closes []token.Pos for _, op := range ops { if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { switch op.dir { case types.SendOnly: sends = append(sends, op.pos) case types.RecvOnly: receives = append(receives, op.pos) case types.SendRecv: closes = append(closes, op.pos) } } } sort.Sort(byPos(sends)) sort.Sort(byPos(receives)) sort.Sort(byPos(closes)) q.result = &peersResult{ queryPos: opPos, queryType: queryType, makes: makes, sends: sends, receives: receives, closes: closes, } return nil }
// TestSyntheticFuncs checks that the expected synthetic functions are // created, reachable, and not duplicated. func TestSyntheticFuncs(t *testing.T) { const input = `package P type T int func (T) f() int func (*T) g() int var ( // thunks a = T.f b = T.f c = (struct{T}).f d = (struct{T}).f e = (*T).g f = (*T).g g = (struct{*T}).g h = (struct{*T}).g // bounds i = T(0).f j = T(0).f k = new(T).g l = new(T).g // wrappers m interface{} = struct{T}{} n interface{} = struct{T}{} o interface{} = struct{*T}{} p interface{} = struct{*T}{} q interface{} = new(struct{T}) r interface{} = new(struct{T}) s interface{} = new(struct{*T}) t interface{} = new(struct{*T}) ) ` // Parse var conf loader.Config f, err := conf.ParseFile("<input>", input) if err != nil { t.Fatalf("parse: %v", err) } conf.CreateFromFiles(f.Name.Name, f) // Load lprog, err := conf.Load() if err != nil { t.Fatalf("Load: %v", err) } // Create and build SSA prog := ssautil.CreateProgram(lprog, 0) prog.Build() // Enumerate reachable synthetic functions want := map[string]string{ "(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int", "(P.T).f$bound": "bound method wrapper for func (P.T).f() int", "(*P.T).g$thunk": "thunk for func (*P.T).g() int", "(P.T).f$thunk": "thunk for func (P.T).f() int", "(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int", "(struct{P.T}).f$thunk": "thunk for func (P.T).f() int", "(*P.T).f": "wrapper for func (P.T).f() int", "(*struct{*P.T}).f": "wrapper for func (P.T).f() int", "(*struct{*P.T}).g": "wrapper for func (*P.T).g() int", "(*struct{P.T}).f": "wrapper for func (P.T).f() int", "(*struct{P.T}).g": "wrapper for func (*P.T).g() int", "(struct{*P.T}).f": "wrapper for func (P.T).f() int", "(struct{*P.T}).g": "wrapper for func (*P.T).g() int", "(struct{P.T}).f": "wrapper for func (P.T).f() int", "P.init": "package initializer", } for fn := range ssautil.AllFunctions(prog) { if fn.Synthetic == "" { continue } name := fn.String() wantDescr, ok := want[name] if !ok { t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic) continue } delete(want, name) if wantDescr != fn.Synthetic { t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr) } } for fn, descr := range want { t.Errorf("want func: %q: %q", fn, descr) } }
// whicherrs takes an position to an error and tries to find all types, constants // and global value which a given error can point to and which can be checked from the // scope where the error lives. // In short, it returns a list of things that can be checked against in order to handle // an error properly. // // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err // can be queried recursively somehow. func whicherrs(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // 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, true) // needs exact pos if err != nil { return err } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return fmt.Errorf("whicherrs wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names return fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: return fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { return fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression. var value ssa.Value if obj != nil { // def/ref of func/var object value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { value, _, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { return err // e.g. trivially dead code } // Defer SSA construction till after errors are reported. prog.Build() globals := findVisibleErrs(prog, qpos) constants := findVisibleConsts(prog, qpos) res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), } // TODO(adonovan): the following code is heavily duplicated // w.r.t. "pointsto". Refactor? // Find the instruction which initialized the // global error. If more than one instruction has stored to the global // remove the global from the set of values that we want to query. allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { store, ok := instr.(*ssa.Store) if !ok { continue } gval, ok := store.Addr.(*ssa.Global) if !ok { continue } gbl, ok := globals[gval] if !ok { continue } // we already found a store to this global // The normal error define is just one store in the init // so we just remove this global from the set we want to query if gbl != nil { delete(globals, gval) } globals[gval] = store.Val } } } ptaConfig.AddQuery(value) for _, v := range globals { ptaConfig.AddQuery(v) } ptares := ptrAnalysis(ptaConfig) valueptr := ptares.Queries[value] for g, v := range globals { ptr, ok := ptares.Queries[v] if !ok { continue } if !ptr.MayAlias(valueptr) { continue } res.globals = append(res.globals, g) } pts := valueptr.PointsTo() dedup := make(map[*ssa.NamedConst]bool) for _, label := range pts.Labels() { // These values are either MakeInterfaces or reflect // generated interfaces. For the purposes of this // analysis, we don't care about reflect generated ones makeiface, ok := label.Value().(*ssa.MakeInterface) if !ok { continue } constval, ok := makeiface.X.(*ssa.Const) if !ok { continue } c := constants[*constval] if c != nil && !dedup[c] { dedup[c] = true res.consts = append(res.consts, c) } } concs := pts.DynamicTypes() concs.Iterate(func(conc types.Type, _ interface{}) { // go/types is a bit annoying here. // We want to find all the types that we can // typeswitch or assert to. This means finding out // if the type pointed to can be seen by us. // // For the purposes of this analysis, the type is always // either a Named type or a pointer to one. // There are cases where error can be implemented // by unnamed types, but in that case, we can't assert to // it, so we don't care about it for this analysis. var name *types.TypeName switch t := conc.(type) { case *types.Pointer: named, ok := t.Elem().(*types.Named) if !ok { return } name = named.Obj() case *types.Named: name = t.Obj() default: return } if !isAccessibleFrom(name, qpos.info.Pkg) { return } res.types = append(res.types, &errorType{conc, name}) }) sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types)) q.result = res return nil }
// directCallsTo inspects the whole program and returns a callgraph // containing edges for all direct calls to the target function. // directCallsTo returns nil if the function is ever address-taken. func directCallsTo(target *ssa.Function, entrypoints []*ssa.Function) *callgraph.Graph { cg := callgraph.New(nil) // use nil as root *Function targetNode := cg.CreateNode(target) // Is the function a program entry point? // If so, add edge from callgraph root. for _, f := range entrypoints { if f == target { callgraph.AddEdge(cg.Root, nil, targetNode) } } // Find receiver type (for methods). var recvType types.Type if recv := target.Signature.Recv(); recv != nil { recvType = recv.Type() } // Find all direct calls to function, // or a place where its address is taken. var space [32]*ssa.Value // preallocate for fn := range ssautil.AllFunctions(target.Prog) { for _, b := range fn.Blocks { for _, instr := range b.Instrs { // Is this a method (T).f of a concrete type T // whose runtime type descriptor is address-taken? // (To be fully sound, we would have to check that // the type doesn't make it to reflection as a // subelement of some other address-taken type.) if recvType != nil { if mi, ok := instr.(*ssa.MakeInterface); ok { if types.Identical(mi.X.Type(), recvType) { return nil // T is address-taken } if ptr, ok := mi.X.Type().(*types.Pointer); ok && types.Identical(ptr.Elem(), recvType) { return nil // *T is address-taken } } } // Direct call to target? rands := instr.Operands(space[:0]) if site, ok := instr.(ssa.CallInstruction); ok && site.Common().Value == target { callgraph.AddEdge(cg.CreateNode(fn), site, targetNode) rands = rands[1:] // skip .Value (rands[0]) } // Address-taken? for _, rand := range rands { if rand != nil && *rand == target { return nil } } } } } return cg }
func doOneInput(input, filename string) bool { var conf loader.Config // 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 { call := instr.Common() if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 { 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) tv, 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 } t = tv.Type } 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". func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { opPos := findOp(qpos) if opPos == token.NoPos { return nil, fmt.Errorf("there is no channel operation here") } buildSSA(o) var queryOp chanOp // the originating send or receive operation var ops []chanOp // all sends/receives of opposite direction // Look at all channel operations in the whole ssa.Program. // Build a list of those of same type as the 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 == opPos { 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 channel operations can alias the same make(chan) labels. var sends, receives, closes []token.Pos for _, op := range ops { if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { switch op.dir { case types.SendOnly: sends = append(sends, op.pos) case types.RecvOnly: receives = append(receives, op.pos) case types.SendRecv: closes = append(closes, op.pos) } } } sort.Sort(byPos(sends)) sort.Sort(byPos(receives)) sort.Sort(byPos(closes)) return &peersResult{ queryPos: opPos, queryType: queryType, makes: makes, sends: sends, receives: receives, closes: closes, }, nil }