// emitImplicitSelections emits to f code to apply the sequence of // implicit field selections specified by indices to base value v, and // returns the selected value. // // If v is the address of a struct, the result will be the address of // a field; if it is the value of a struct, the result will be the // value of a field. // func emitImplicitSelections(f *Function, v Value, indices []int) Value { for _, index := range indices { fld := deref(v.Type()).Underlying().(*types.Struct).Field(index) if isPointer(v.Type()) { instr := &FieldAddr{ X: v, Field: index, } instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr) // Load the field's value iff indirectly embedded. if isPointer(fld.Type()) { v = emitLoad(f, v) } } else { instr := &Field{ X: v, Field: index, } instr.setType(fld.Type()) v = f.emit(instr) } } return v }
// emitNew emits to f a new (heap Alloc) instruction allocating an // object of type typ. pos is the optional source location. // func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc { v := &Alloc{Heap: true} v.setType(types.NewPointer(typ)) v.setPos(pos) f.emit(v) return v }
// emitFieldSelection emits to f code to select the index'th field of v. // // If wantAddr, the input must be a pointer-to-struct and the result // will be the field's address; otherwise the result will be the // field's value. // Ident id is used for position and debug info. // func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value { fld := deref(v.Type()).Underlying().(*types.Struct).Field(index) if isPointer(v.Type()) { instr := &FieldAddr{ X: v, Field: index, } instr.setPos(id.Pos()) instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr) // Load the field's value iff we don't want its address. if !wantAddr { v = emitLoad(f, v) } } else { instr := &Field{ X: v, Field: index, } instr.setPos(id.Pos()) instr.setType(fld.Type()) v = f.emit(instr) } emitDebugRef(f, id, v, wantAddr) return v }
// addLocal creates an anonymous local variable of type typ, adds it // to function f and returns it. pos is the optional source location. // func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc { v := &Alloc{} v.setType(types.NewPointer(typ)) v.setPos(pos) f.Locals = append(f.Locals, v) f.emit(v) return v }
// addSpilledParam declares a parameter that is pre-spilled to the // stack; the function body will load/store the spilled location. // Subsequent lifting will eliminate spills where possible. // func (f *Function) addSpilledParam(obj types.Object) { param := f.addParamObj(obj) spill := &Alloc{Comment: obj.Name()} spill.setType(types.NewPointer(obj.Type())) spill.setPos(obj.Pos()) f.objects[obj] = spill f.Locals = append(f.Locals, spill) f.emit(spill) f.emit(&Store{Addr: spill, Val: param}) }
// testMainSlice emits to fn code to construct a slice of type slice // (one of []testing.Internal{Test,Benchmark,Example}) for all // functions in testfuncs. It returns the slice value. // func testMainSlice(fn *Function, testfuncs []*Function, slice types.Type) Value { if testfuncs == nil { return nilConst(slice) } tElem := slice.(*types.Slice).Elem() tPtrString := types.NewPointer(tString) tPtrElem := types.NewPointer(tElem) tPtrFunc := types.NewPointer(funcField(slice)) // Emit: array = new [n]testing.InternalTest tArray := types.NewArray(tElem, int64(len(testfuncs))) array := emitNew(fn, tArray, token.NoPos) array.Comment = "test main" for i, testfunc := range testfuncs { // Emit: pitem = &array[i] ia := &IndexAddr{X: array, Index: intConst(int64(i))} ia.setType(tPtrElem) pitem := fn.emit(ia) // Emit: pname = &pitem.Name fa := &FieldAddr{X: pitem, Field: 0} // .Name fa.setType(tPtrString) pname := fn.emit(fa) // Emit: *pname = "testfunc" emitStore(fn, pname, stringConst(testfunc.Name()), token.NoPos) // Emit: pfunc = &pitem.F fa = &FieldAddr{X: pitem, Field: 1} // .F fa.setType(tPtrFunc) pfunc := fn.emit(fa) // Emit: *pfunc = testfunc emitStore(fn, pfunc, testfunc, token.NoPos) } // Emit: slice array[:] sl := &Slice{X: array} sl.setType(slice) return fn.emit(sl) }
// memberFromObject populates package pkg with a member for the // typechecker object obj. // // For objects from Go source code, syntax is the associated syntax // tree (for funcs and vars only); it will be used during the build // phase. // func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { name := obj.Name() switch obj := obj.(type) { case *types.TypeName: pkg.Members[name] = &Type{ object: obj, pkg: pkg, } case *types.Const: c := &NamedConst{ object: obj, Value: NewConst(obj.Val(), obj.Type()), pkg: pkg, } pkg.values[obj] = c.Value pkg.Members[name] = c case *types.Var: g := &Global{ Pkg: pkg, name: name, object: obj, typ: types.NewPointer(obj.Type()), // address pos: obj.Pos(), } pkg.values[obj] = g pkg.Members[name] = g case *types.Func: fn := &Function{ name: name, object: obj, Signature: obj.Type().(*types.Signature), syntax: syntax, pos: obj.Pos(), Pkg: pkg, Prog: pkg.Prog, } if syntax == nil { fn.Synthetic = "loaded from gc object file" } pkg.values[obj] = fn if fn.Signature.Recv() == nil { pkg.Members[name] = fn // package-level function } default: // (incl. *types.Package) panic("unexpected Object type: " + obj.String()) } }
// Type = // BasicType | TypeName | ArrayType | SliceType | StructType | // PointerType | FuncType | InterfaceType | MapType | ChanType | // "(" Type ")" . // // BasicType = ident . // TypeName = ExportedName . // SliceType = "[" "]" Type . // PointerType = "*" Type . // FuncType = "func" Signature . // func (p *parser) parseType() types.Type { switch p.tok { case scanner.Ident: switch p.lit { default: return p.parseBasicType() case "struct": return p.parseStructType() case "func": // FuncType p.next() return p.parseSignature(nil) case "interface": return p.parseInterfaceType() case "map": return p.parseMapType() case "chan": return p.parseChanType() } case '@': // TypeName pkg, name := p.parseExportedName() return declTypeName(pkg, name).Type() case '[': p.next() // look ahead if p.tok == ']' { // SliceType p.next() return types.NewSlice(p.parseType()) } return p.parseArrayType() case '*': // PointerType p.next() return types.NewPointer(p.parseType()) case '<': return p.parseChanType() case '(': // "(" Type ")" p.next() typ := p.parseType() p.expect(')') return typ } p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit) return nil }
// IntuitiveMethodSet returns the intuitive method set of a type, T. // // The result contains MethodSet(T) and additionally, if T is a // concrete type, methods belonging to *T if there is no identically // named method on T itself. This corresponds to user intuition about // method sets; this function is intended only for user interfaces. // // The order of the result is as for types.MethodSet(T). // func IntuitiveMethodSet(T types.Type, msets *types.MethodSetCache) []*types.Selection { var result []*types.Selection mset := msets.MethodSet(T) if _, ok := T.Underlying().(*types.Interface); ok { for i, n := 0, mset.Len(); i < n; i++ { result = append(result, mset.At(i)) } } else { pmset := msets.MethodSet(types.NewPointer(T)) for i, n := 0, pmset.Len(); i < n; i++ { meth := pmset.At(i) if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil { meth = m } result = append(result, meth) } } return result }
// Smoke test to ensure that imported methods get the correct package. func TestCorrectMethodPackage(t *testing.T) { // This package does not handle gccgo export data. if runtime.Compiler == "gccgo" { return } imports := make(map[string]*types.Package) _, err := Import(imports, "net/http") if err != nil { t.Fatal(err) } mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex sel := mset.Lookup(nil, "Lock") lock := sel.Obj().(*types.Func) if got, want := lock.Pkg().Path(), "sync"; got != want { t.Errorf("got package path %q; want %q", got, want) } }
// findNamedFunc returns the named function whose FuncDecl.Ident is at // position pos. // func findNamedFunc(pkg *Package, pos token.Pos) *Function { // Look at all package members and method sets of named types. // Not very efficient. for _, mem := range pkg.Members { switch mem := mem.(type) { case *Function: if mem.Pos() == pos { return mem } case *Type: mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { // Don't call Program.Method: avoid creating wrappers. obj := mset.At(i).Obj().(*types.Func) if obj.Pos() == pos { return pkg.values[obj].(*Function) } } } } return nil }
func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) { // The prefix of all assertions messages. prefix := fmt.Sprintf("VarValue(%s @ L%d)", obj, prog.Fset.Position(ref[0].Pos()).Line) v, gotAddr := prog.VarValue(obj, pkg, ref) // Kind is the concrete type of the ssa Value. gotKind := "nil" if v != nil { gotKind = fmt.Sprintf("%T", v)[len("*ssa."):] } // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging // Check the kinds match. // "nil" indicates expected failure (e.g. optimized away). if expKind != gotKind { t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind) } // Check the types match. // If wantAddr, the expected type is the object's address. if v != nil { expType := obj.Type() if wantAddr { expType = types.NewPointer(expType) if !gotAddr { t.Errorf("%s: got value, want address", prefix) } } else if gotAddr { t.Errorf("%s: got address, want value", prefix) } if !types.Identical(v.Type(), expType) { t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType) } } }
// TODO(adonovan): // - test use of explicit hasher across two maps. // - test hashcodes are consistent with equals for a range of types // (e.g. all types generated by type-checking some body of real code). import ( "testing" "github.com/fzipp/pythia/internal/tools/go/types" "github.com/fzipp/pythia/internal/tools/go/types/typeutil" ) var ( tStr = types.Typ[types.String] // string tPStr1 = types.NewPointer(tStr) // *string tPStr2 = types.NewPointer(tStr) // *string, again tInt = types.Typ[types.Int] // int tChanInt1 = types.NewChan(types.RecvOnly, tInt) // <-chan int tChanInt2 = types.NewChan(types.RecvOnly, tInt) // <-chan int, again ) func checkEqualButNotIdentical(t *testing.T, x, y types.Type, comment string) { if !types.Identical(x, y) { t.Errorf("%s: not equal: %s, %s", comment, x, y) } if x == y { t.Errorf("%s: identical: %v, %v", comment, x, y) } }
// Tests that programs partially loaded from gc object files contain // functions with no code for the external portions, but are otherwise ok. func TestImportFromBinary(t *testing.T) { test := ` package main import ( "bytes" "io" "testing" ) func main() { var t testing.T t.Parallel() // static call to external declared method t.Fail() // static call to promoted external declared method testing.Short() // static call to external package-level function var w io.Writer = new(bytes.Buffer) w.Write(nil) // interface invoke of external declared method } ` // Create a single-file main package. conf := loader.Config{ImportFromBinary: true} f, err := conf.ParseFile("<input>", test) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := ssa.Create(iprog, ssa.SanityCheckFunctions) mainPkg := prog.Package(iprog.Created[0].Pkg) mainPkg.Build() // The main package, its direct and indirect dependencies are loaded. deps := []string{ // directly imported dependencies: "bytes", "io", "testing", // indirect dependencies (partial list): "errors", "fmt", "os", "runtime", } all := prog.AllPackages() if len(all) <= len(deps) { t.Errorf("unexpected set of loaded packages: %q", all) } for _, path := range deps { pkg := prog.ImportedPackage(path) if pkg == nil { t.Errorf("package not loaded: %q", path) continue } // External packages should have no function bodies (except for wrappers). isExt := pkg != mainPkg // init() if isExt && !isEmpty(pkg.Func("init")) { t.Errorf("external package %s has non-empty init", pkg) } else if !isExt && isEmpty(pkg.Func("init")) { t.Errorf("main package %s has empty init", pkg) } for _, mem := range pkg.Members { switch mem := mem.(type) { case *ssa.Function: // Functions at package level. if isExt && !isEmpty(mem) { t.Errorf("external function %s is non-empty", mem) } else if !isExt && isEmpty(mem) { t.Errorf("function %s is empty", mem) } case *ssa.Type: // Methods of named types T. // (In this test, all exported methods belong to *T not T.) if !isExt { t.Fatalf("unexpected name type in main package: %s", mem) } mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { m := prog.Method(mset.At(i)) // For external types, only synthetic wrappers have code. expExt := !strings.Contains(m.Synthetic, "wrapper") if expExt && !isEmpty(m) { t.Errorf("external method %s is non-empty: %s", m, m.Synthetic) } else if !expExt && isEmpty(m) { t.Errorf("method function %s is empty: %s", m, m.Synthetic) } } } } } expectedCallee := []string{ "(*testing.T).Parallel", "(*testing.common).Fail", "testing.Short", "N/A", } callNum := 0 for _, b := range mainPkg.Func("main").Blocks { for _, instr := range b.Instrs { switch instr := instr.(type) { case ssa.CallInstruction: call := instr.Common() if want := expectedCallee[callNum]; want != "N/A" { got := call.StaticCallee().String() if want != got { t.Errorf("call #%d from main.main: got callee %s, want %s", callNum, got, want) } } callNum++ } } } if callNum != 4 { t.Errorf("in main.main: got %d calls, want %d", callNum, 4) } }
// CreatePackage constructs and returns an SSA Package from an // error-free package described by info, and populates its Members // mapping. // // Repeated calls with the same info return the same Package. // // The real work of building SSA form for each function is not done // until a subsequent call to Package.Build(). // func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package { if p := prog.packages[info.Pkg]; p != nil { return p // already loaded } p := &Package{ Prog: prog, Members: make(map[string]Member), values: make(map[types.Object]Value), Object: info.Pkg, info: info, // transient (CREATE and BUILD phases) } // Add init() function. p.init = &Function{ name: "init", Signature: new(types.Signature), Synthetic: "package initializer", Pkg: p, Prog: prog, } p.Members[p.init.name] = p.init // CREATE phase. // Allocate all package members: vars, funcs, consts and types. if len(info.Files) > 0 { // Go source package. for _, file := range info.Files { for _, decl := range file.Decls { membersFromDecl(p, decl) } } } else { // GC-compiled binary package. // No code. // No position information. scope := p.Object.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) memberFromObject(p, obj, nil) if obj, ok := obj.(*types.TypeName); ok { named := obj.Type().(*types.Named) for i, n := 0, named.NumMethods(); i < n; i++ { memberFromObject(p, named.Method(i), nil) } } } } if prog.mode&BareInits == 0 { // Add initializer guard variable. initguard := &Global{ Pkg: p, name: "init$guard", typ: types.NewPointer(tBool), } p.Members[initguard.Name()] = initguard } if prog.mode&GlobalDebug != 0 { p.SetDebugMode(true) } if prog.mode&PrintPackages != 0 { printMu.Lock() p.WriteTo(os.Stdout) printMu.Unlock() } if info.Importable { prog.imported[info.Pkg.Path()] = p } prog.packages[p.Object] = p return p }
// Implements displays the "implements" relation as it pertains to the // selected type. If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. // func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { // Find the selected type. path, action := findInterestingNode(qpos.info, qpos.path) var method *types.Func var T types.Type // selected type (receiver if method != nil) switch action { case actionExpr: // method? if id, ok := path[0].(*ast.Ident); ok { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv == nil { return nil, fmt.Errorf("this function is not a method") } method = obj T = recv.Type() } } case actionType: T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { return nil, fmt.Errorf("no type or method here") } // Find all named types, even local types (which can have // methods via promotion) and the built-in "error". // // TODO(adonovan): include all packages in PTA scope too? // i.e. don't reduceScope? // var allNamed []types.Type for _, info := range o.typeInfo { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { allNamed = append(allNamed, obj.Type()) } } } allNamed = append(allNamed, types.Universe.Lookup("error").Type()) var msets types.MethodSetCache // Test each named type. var to, from, fromPtr []types.Type for _, U := range allNamed { if isInterface(T) { if msets.MethodSet(T).Len() == 0 { continue // empty interface } if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T interface, U interface if !types.Identical(T, U) { if types.AssignableTo(U, T) { to = append(to, U) } if types.AssignableTo(T, U) { from = append(from, U) } } } else { // T interface, U concrete if types.AssignableTo(U, T) { to = append(to, U) } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { to = append(to, pU) } } } else if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T concrete, U interface if types.AssignableTo(T, U) { from = append(from, U) } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { fromPtr = append(fromPtr, U) } } } var pos interface{} = qpos if nt, ok := deref(T).(*types.Named); ok { pos = nt.Obj() } // Sort types (arbitrarily) to ensure test determinism. sort.Sort(typesByString(to)) sort.Sort(typesByString(from)) sort.Sort(typesByString(fromPtr)) var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils if method != nil { for _, t := range to { toMethod = append(toMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range from { fromMethod = append(fromMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range fromPtr { fromPtrMethod = append(fromPtrMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } } return &implementsResult{qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod}, nil }
// Precondition: T is not a method signature (*Signature with Recv()!=nil). // Recursive case: skip => don't create methods for T. // // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // func (prog *Program) needMethods(T types.Type, skip bool) { // Each package maintains its own set of types it has visited. if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok { // needMethods(T) was previously called if !prevSkip || skip { return // already seen, with same or false 'skip' value } } prog.runtimeTypes.Set(T, skip) tmset := prog.MethodSets.MethodSet(T) if !skip && !isInterface(T) && tmset.Len() > 0 { // Create methods of T. mset := prog.createMethodSet(T) if !mset.complete { mset.complete = true n := tmset.Len() for i := 0; i < n; i++ { prog.addMethod(mset, tmset.At(i)) } } } // Recursion over signatures of each method. for i := 0; i < tmset.Len(); i++ { sig := tmset.At(i).Type().(*types.Signature) prog.needMethods(sig.Params(), false) prog.needMethods(sig.Results(), false) } switch t := T.(type) { case *types.Basic: // nop case *types.Interface: // nop---handled by recursion over method set. case *types.Pointer: prog.needMethods(t.Elem(), false) case *types.Slice: prog.needMethods(t.Elem(), false) case *types.Chan: prog.needMethods(t.Elem(), false) case *types.Map: prog.needMethods(t.Key(), false) prog.needMethods(t.Elem(), false) case *types.Signature: if t.Recv() != nil { panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) } prog.needMethods(t.Params(), false) prog.needMethods(t.Results(), false) case *types.Named: // A pointer-to-named type can be derived from a named // type via reflection. It may have methods too. prog.needMethods(types.NewPointer(T), false) // Consider 'type T struct{S}' where S has methods. // Reflection provides no way to get from T to struct{S}, // only to S, so the method set of struct{S} is unwanted, // so set 'skip' flag during recursion. prog.needMethods(t.Underlying(), true) case *types.Array: prog.needMethods(t.Elem(), false) case *types.Struct: for i, n := 0, t.NumFields(); i < n; i++ { prog.needMethods(t.Field(i).Type(), false) } case *types.Tuple: for i, n := 0, t.Len(); i < n; i++ { prog.needMethods(t.At(i).Type(), false) } default: panic(T) } }
func ext۰reflect۰New(fr *frame, args []value) value { // Signature: func (t reflect.Type) reflect.Value t := args[0].(iface).v.(rtype).t alloc := zero(t) return makeReflectValue(types.NewPointer(t), &alloc) }
// addRuntimeType is called for each concrete type that can be the // dynamic type of some interface or reflect.Value. // Adapted from needMethods in go/ssa/builder.go // func (r *rta) addRuntimeType(T types.Type, skip bool) { if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok { if skip && !prev { r.result.RuntimeTypes.Set(T, skip) } return } r.result.RuntimeTypes.Set(T, skip) mset := r.prog.MethodSets.MethodSet(T) if _, ok := T.Underlying().(*types.Interface); !ok { // T is a new concrete type. for i, n := 0, mset.Len(); i < n; i++ { sel := mset.At(i) m := sel.Obj() if m.Exported() { // Exported methods are always potentially callable via reflection. r.addReachable(r.prog.Method(sel), true) } } // Add callgraph edge for each existing dynamic // "invoke"-mode call via that interface. for _, I := range r.interfaces(T) { sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction) for _, site := range sites { r.addInvokeEdge(site, T) } } } // Precondition: T is not a method signature (*Signature with Recv()!=nil). // Recursive case: skip => don't call makeMethods(T). // Each package maintains its own set of types it has visited. var n *types.Named switch T := T.(type) { case *types.Named: n = T case *types.Pointer: n, _ = T.Elem().(*types.Named) } if n != nil { owner := n.Obj().Pkg() if owner == nil { return // built-in error type } } // Recursion over signatures of each exported method. for i := 0; i < mset.Len(); i++ { if mset.At(i).Obj().Exported() { sig := mset.At(i).Type().(*types.Signature) r.addRuntimeType(sig.Params(), true) // skip the Tuple itself r.addRuntimeType(sig.Results(), true) // skip the Tuple itself } } switch t := T.(type) { case *types.Basic: // nop case *types.Interface: // nop---handled by recursion over method set. case *types.Pointer: r.addRuntimeType(t.Elem(), false) case *types.Slice: r.addRuntimeType(t.Elem(), false) case *types.Chan: r.addRuntimeType(t.Elem(), false) case *types.Map: r.addRuntimeType(t.Key(), false) r.addRuntimeType(t.Elem(), false) case *types.Signature: if t.Recv() != nil { panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) } r.addRuntimeType(t.Params(), true) // skip the Tuple itself r.addRuntimeType(t.Results(), true) // skip the Tuple itself case *types.Named: // A pointer-to-named type can be derived from a named // type via reflection. It may have methods too. r.addRuntimeType(types.NewPointer(T), false) // Consider 'type T struct{S}' where S has methods. // Reflection provides no way to get from T to struct{S}, // only to S, so the method set of struct{S} is unwanted, // so set 'skip' flag during recursion. r.addRuntimeType(t.Underlying(), true) case *types.Array: r.addRuntimeType(t.Elem(), false) case *types.Struct: for i, n := 0, t.NumFields(); i < n; i++ { r.addRuntimeType(t.Field(i).Type(), false) } case *types.Tuple: for i, n := 0, t.Len(); i < n; i++ { r.addRuntimeType(t.At(i).Type(), false) } default: panic(T) } }
// Analyze runs the pointer analysis with the scope and options // specified by config, and returns the (synthetic) root of the callgraph. // // Pointer analysis of a transitively closed well-typed program should // always succeed. An error can occur only due to an internal bug. // func Analyze(config *Config) (result *Result, err error) { if config.Mains == nil { return nil, fmt.Errorf("no main/test packages to analyze (check $GOROOT/$GOPATH)") } defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error in pointer analysis: %v (please report this bug)", p) fmt.Fprintln(os.Stderr, "Internal panic in pointer analysis:") debug.PrintStack() } }() a := &analysis{ config: config, log: config.Log, prog: config.prog(), globalval: make(map[ssa.Value]nodeid), globalobj: make(map[ssa.Value]nodeid), flattenMemo: make(map[types.Type][]*fieldInfo), trackTypes: make(map[types.Type]bool), atFuncs: make(map[*ssa.Function]bool), hasher: typeutil.MakeHasher(), intrinsics: make(map[*ssa.Function]intrinsic), result: &Result{ Queries: make(map[ssa.Value]Pointer), IndirectQueries: make(map[ssa.Value]Pointer), }, deltaSpace: make([]int, 0, 100), } if false { a.log = os.Stderr // for debugging crashes; extremely verbose } if a.log != nil { fmt.Fprintln(a.log, "==== Starting analysis") } // Pointer analysis requires a complete program for soundness. // Check to prevent accidental misconfiguration. for _, pkg := range a.prog.AllPackages() { // (This only checks that the package scope is complete, // not that func bodies exist, but it's a good signal.) if !pkg.Object.Complete() { return nil, fmt.Errorf(`pointer analysis requires a complete program yet package %q was incomplete (don't set loader.Config.ImportFromBinary during loading)`, pkg.Object.Path()) } } if reflect := a.prog.ImportedPackage("reflect"); reflect != nil { rV := reflect.Object.Scope().Lookup("Value") a.reflectValueObj = rV a.reflectValueCall = a.prog.LookupMethod(rV.Type(), nil, "Call") a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named) a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype") a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type()) // Override flattening of reflect.Value, treating it like a basic type. tReflectValue := a.reflectValueObj.Type() a.flattenMemo[tReflectValue] = []*fieldInfo{{typ: tReflectValue}} // Override shouldTrack of reflect.Value and *reflect.rtype. // Always track pointers of these types. a.trackTypes[tReflectValue] = true a.trackTypes[a.reflectRtypePtr] = true a.rtypes.SetHasher(a.hasher) a.reflectZeros.SetHasher(a.hasher) } if runtime := a.prog.ImportedPackage("runtime"); runtime != nil { a.runtimeSetFinalizer = runtime.Func("SetFinalizer") } a.computeTrackBits() a.generate() a.showCounts() if optRenumber { a.renumber() } N := len(a.nodes) // excludes solver-created nodes if optHVN { if debugHVNCrossCheck { // Cross-check: run the solver once without // optimization, once with, and compare the // solutions. savedConstraints := a.constraints a.solve() a.dumpSolution("A.pts", N) // Restore. a.constraints = savedConstraints for _, n := range a.nodes { n.solve = new(solverState) } a.nodes = a.nodes[:N] // rtypes is effectively part of the solver state. a.rtypes = typeutil.Map{} a.rtypes.SetHasher(a.hasher) } a.hvn() } if debugHVNCrossCheck { runtime.GC() runtime.GC() } a.solve() // Compare solutions. if optHVN && debugHVNCrossCheck { a.dumpSolution("B.pts", N) if !diff("A.pts", "B.pts") { return nil, fmt.Errorf("internal error: optimization changed solution") } } // Create callgraph.Nodes in deterministic order. if cg := a.result.CallGraph; cg != nil { for _, caller := range a.cgnodes { cg.CreateNode(caller.fn) } } // Add dynamic edges to call graph. var space [100]int for _, caller := range a.cgnodes { for _, site := range caller.sites { for _, callee := range a.nodes[site.targets].solve.pts.AppendTo(space[:0]) { a.callEdge(caller, site, nodeid(callee)) } } } return a.result, nil }
// computeImplements computes the "implements" relation over all pairs // of named types in allNamed. func computeImplements(cache *types.MethodSetCache, allNamed []*types.Named) map[*types.Named]implementsFacts { // Information about a single type's method set. type msetInfo struct { typ types.Type mset *types.MethodSet mask1, mask2 uint64 } initMsetInfo := func(info *msetInfo, typ types.Type) { info.typ = typ info.mset = cache.MethodSet(typ) for i := 0; i < info.mset.Len(); i++ { name := info.mset.At(i).Obj().Name() info.mask1 |= 1 << methodBit(name[0]) info.mask2 |= 1 << methodBit(name[len(name)-1]) } } // satisfies(T, U) reports whether type T satisfies type U. // U must be an interface. // // Since there are thousands of types (and thus millions of // pairs of types) and types.Assignable(T, U) is relatively // expensive, we compute assignability directly from the // method sets. (At least one of T and U must be an // interface.) // // We use a trick (thanks gri!) related to a Bloom filter to // quickly reject most tests, which are false. For each // method set, we precompute a mask, a set of bits, one per // distinct initial byte of each method name. Thus the mask // for io.ReadWriter would be {'R','W'}. AssignableTo(T, U) // cannot be true unless mask(T)&mask(U)==mask(U). // // As with a Bloom filter, we can improve precision by testing // additional hashes, e.g. using the last letter of each // method name, so long as the subset mask property holds. // // When analyzing the standard library, there are about 1e6 // calls to satisfies(), of which 0.6% return true. With a // 1-hash filter, 95% of calls avoid the expensive check; with // a 2-hash filter, this grows to 98.2%. satisfies := func(T, U *msetInfo) bool { return T.mask1&U.mask1 == U.mask1 && T.mask2&U.mask2 == U.mask2 && containsAllIdsOf(T.mset, U.mset) } // Information about a named type N, and perhaps also *N. type namedInfo struct { isInterface bool base msetInfo // N ptr msetInfo // *N, iff N !isInterface } var infos []namedInfo // Precompute the method sets and their masks. for _, N := range allNamed { var info namedInfo initMsetInfo(&info.base, N) _, info.isInterface = N.Underlying().(*types.Interface) if !info.isInterface { initMsetInfo(&info.ptr, types.NewPointer(N)) } if info.base.mask1|info.ptr.mask1 == 0 { continue // neither N nor *N has methods } infos = append(infos, info) } facts := make(map[*types.Named]implementsFacts) // Test all pairs of distinct named types (T, U). // TODO(adonovan): opt: compute (U, T) at the same time. for t := range infos { T := &infos[t] var to, from, fromPtr []types.Type for u := range infos { if t == u { continue } U := &infos[u] switch { case T.isInterface && U.isInterface: if satisfies(&U.base, &T.base) { to = append(to, U.base.typ) } if satisfies(&T.base, &U.base) { from = append(from, U.base.typ) } case T.isInterface: // U concrete if satisfies(&U.base, &T.base) { to = append(to, U.base.typ) } else if satisfies(&U.ptr, &T.base) { to = append(to, U.ptr.typ) } case U.isInterface: // T concrete if satisfies(&T.base, &U.base) { from = append(from, U.base.typ) } else if satisfies(&T.ptr, &U.base) { fromPtr = append(fromPtr, U.base.typ) } } } // Sort types (arbitrarily) to avoid nondeterminism. sort.Sort(typesByString(to)) sort.Sort(typesByString(from)) sort.Sort(typesByString(fromPtr)) facts[T.base.typ.(*types.Named)] = implementsFacts{to, from, fromPtr} } return facts }