// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pointer // This file defines the constraint generation phase. import ( "fmt" "go/ast" "go/token" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/ssa" ) var ( tEface = types.NewInterface(nil) tInvalid = types.Typ[types.Invalid] tUnsafePtr = types.Typ[types.UnsafePointer] ) // ---------- Node creation ---------- // nextNode returns the index of the next unused node. func (a *analysis) nextNode() nodeid { return nodeid(len(a.nodes)) } // addNodes creates nodes for all scalar elements in type typ, and // returns the id of the first one, or zero if the type was // analytically uninteresting. // // comment explains the origin of the nodes, as a debugging aid. // func (a *analysis) addNodes(typ types.Type, comment string) nodeid { id := a.nextNode() for _, fi := range a.flatten(typ) { a.addOneNode(fi.typ, comment, fi) } if id == a.nextNode() { return 0 // type contained no pointers } return id } // addOneNode creates a single node with type typ, and returns its id. // // typ should generally be scalar (except for interface.conctype nodes // and struct/array identity nodes). Use addNodes for non-scalar types. // // comment explains the origin of the nodes, as a debugging aid. // subelement indicates the subelement, e.g. ".a.b[*].c". // func (a *analysis) addOneNode(typ types.Type, comment string, subelement *fieldInfo) nodeid { id := a.nextNode() a.nodes = append(a.nodes, &node{typ: typ, subelement: subelement}) if a.log != nil { fmt.Fprintf(a.log, "\tcreate n%d %s for %s%s\n", id, typ, comment, subelement.path()) } return id } // setValueNode associates node id with the value v. // TODO(adonovan): disambiguate v by its CallGraphNode, if it's a local. func (a *analysis) setValueNode(v ssa.Value, id nodeid) { a.valNode[v] = id if a.log != nil { fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v) } // Record the (v, id) relation if the client has queried v. qv := a.config.QueryValues if ptrs, ok := qv[v]; ok { qv[v] = append(ptrs, ptr{a, id}) } } // endObject marks the end of a sequence of calls to addNodes denoting // a single object allocation. // // obj is the start node of the object, from a prior call to nextNode. // Its size, flags and (optionally) data will be updated. // func (a *analysis) endObject(obj nodeid, data ssa.Value) { // Ensure object is non-empty by padding; // the pad will be the object node. size := uint32(a.nextNode() - obj) if size == 0 { a.addOneNode(tInvalid, "padding", nil) } objNode := a.nodes[obj] objNode.size = size // excludes padding objNode.flags = ntObject if data != nil { objNode.data = data if a.log != nil { fmt.Fprintf(a.log, "\tobj[%s] = n%d\n", data, obj) } } } // makeFunctionObject creates and returns a new function object for // fn, and returns the id of its first node. It also enqueues fn for // subsequent constraint generation. // func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid { if a.log != nil { fmt.Fprintf(a.log, "\t---- makeFunctionObject %s\n", fn) } // obj is the function object (identity, params, results). obj := a.nextNode() sig := fn.Signature a.addOneNode(sig, "func.cgnode", nil) // (scalar with Signature type) if recv := sig.Recv(); recv != nil { a.addNodes(recv.Type(), "func.recv") } a.addNodes(sig.Params(), "func.params") a.addNodes(sig.Results(), "func.results") a.endObject(obj, fn) if a.log != nil { fmt.Fprintf(a.log, "\t----\n") } cgn := &cgnode{fn: fn, obj: obj} a.nodes[obj].flags |= ntFunction a.nodes[obj].data = cgn // Queue it up for constraint processing. a.genq = append(a.genq, cgn) return obj } // makeFunction creates the shared function object (aka contour) for // function fn and returns a 'func' value node that points to it. // func (a *analysis) makeFunction(fn *ssa.Function) nodeid { obj := a.makeFunctionObject(fn) a.funcObj[fn] = obj var comment string if a.log != nil { comment = fn.String() } id := a.addOneNode(fn.Type(), comment, nil) a.addressOf(id, obj) return id } // makeGlobal creates the value node and object node for global g, // and returns the value node. // // The value node represents the address of the global variable, and // points to the object (and nothing else). // // The object consists of the global variable itself (conceptually, // the BSS address). // func (a *analysis) makeGlobal(g *ssa.Global) nodeid { var comment string if a.log != nil { fmt.Fprintf(a.log, "\t---- makeGlobal %s\n", g) comment = g.FullName() } // The nodes representing the object itself. obj := a.nextNode() a.addNodes(mustDeref(g.Type()), "global") a.endObject(obj, g) if a.log != nil { fmt.Fprintf(a.log, "\t----\n") } // The node representing the address of the global. id := a.addOneNode(g.Type(), comment, nil) a.addressOf(id, obj) return id } // makeConstant creates the value node and object node (if needed) for // constant c, and returns the value node. // An object node is created only for []byte or []rune constants. // The value node points to the object node, iff present. // func (a *analysis) makeConstant(l *ssa.Const) nodeid { id := a.addNodes(l.Type(), "const") if !l.IsNil() { // []byte or []rune? if t, ok := l.Type().Underlying().(*types.Slice); ok { // Treat []T like *[1]T, 'make []T' like new([1]T). obj := a.nextNode() a.addNodes(sliceToArray(t), "array in slice constant") a.endObject(obj, l) a.addressOf(id, obj) } } return id } // valueNode returns the id of the value node for v, creating it (and // the association) as needed. It may return zero for uninteresting // values containing no pointers. // // Nodes for locals are created en masse during genFunc and are // implicitly contextualized by the function currently being analyzed // (i.e. parameter to genFunc). // func (a *analysis) valueNode(v ssa.Value) nodeid { id, ok := a.valNode[v] if !ok { switch v := v.(type) { case *ssa.Function: id = a.makeFunction(v) case *ssa.Global: id = a.makeGlobal(v) case *ssa.Const: id = a.makeConstant(v) case *ssa.Capture: // TODO(adonovan): treat captures context-sensitively. id = a.addNodes(v.Type(), "capture") default: // *ssa.Parameters and ssa.Instruction values // are created by genFunc. // *Builtins are not true values. panic(v) } a.setValueNode(v, id) } return id } // valueOffsetNode ascertains the node for tuple/struct value v, // then returns the node for its subfield #index. // func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid { id := a.valueNode(v) if id == 0 { panic(fmt.Sprintf("cannot offset within n0: %s = %s", v.Name(), v)) } return id + nodeid(a.offsetOf(v.Type(), index)) } // interfaceValue returns the (first node of) the value, and the // concrete type, of the interface object (flags&ntInterface) starting // at id. // func (a *analysis) interfaceValue(id nodeid) (nodeid, types.Type) { n := a.nodes[id] if n.flags&ntInterface == 0 { panic(fmt.Sprintf("interfaceValue(n%d): not an interface object; typ=%s", id, n.typ)) } return id + 1, n.typ } // funcParams returns the first node of the params block of the // function whose object node (flags&ntFunction) is id. // func (a *analysis) funcParams(id nodeid) nodeid { if a.nodes[id].flags&ntFunction == 0 { panic(fmt.Sprintf("funcParams(n%d): not a function object block", id)) } return id + 1 } // funcResults returns the first node of the results block of the // function whose object node (flags&ntFunction) is id. // func (a *analysis) funcResults(id nodeid) nodeid { n := a.nodes[id] if n.flags&ntFunction == 0 { panic(fmt.Sprintf("funcResults(n%d): not a function object block", id)) } sig := n.typ.(*types.Signature) id += 1 + nodeid(a.sizeof(sig.Params())) if sig.Recv() != nil { id += nodeid(a.sizeof(sig.Recv().Type())) } return id } // ---------- Constraint creation ---------- // copy creates a constraint of the form dst = src. // sizeof is the width (in logical fields) of the copied type. // func (a *analysis) copy(dst, src nodeid, sizeof uint32) { if src == dst || sizeof == 0 { return // trivial } if src == 0 || dst == 0 { panic(fmt.Sprintf("ill-typed copy dst=n%d src=n%d", dst, src)) } for i := uint32(0); i < sizeof; i++ { a.addConstraint(©Constraint{dst, src}) src++ dst++ } } // addressOf creates a constraint of the form id = &obj. func (a *analysis) addressOf(id, obj nodeid) { if id == 0 { panic("addressOf: zero id") } if obj == 0 { panic("addressOf: zero obj") } a.addConstraint(&addrConstraint{id, obj}) } // load creates a load constraint of the form dst = *src. // sizeof is the width (in logical fields) of the loaded type. // func (a *analysis) load(dst, src nodeid, sizeof uint32) { a.loadOffset(dst, src, 0, sizeof) } // loadOffset creates a load constraint of the form dst = src[offset]. // offset is the pointer offset in logical fields. // sizeof is the width (in logical fields) of the loaded type. // func (a *analysis) loadOffset(dst, src nodeid, offset uint32, sizeof uint32) { if dst == 0 { return // load of non-pointerlike value } if src == 0 && dst == 0 { return // non-pointerlike operation } if src == 0 || dst == 0 { panic(fmt.Sprintf("ill-typed load dst=n%d src=n%d", dst, src)) } for i := uint32(0); i < sizeof; i++ { a.addConstraint(&loadConstraint{offset, dst, src}) offset++ dst++ } } // store creates a store constraint of the form *dst = src. // sizeof is the width (in logical fields) of the stored type. // func (a *analysis) store(dst, src nodeid, sizeof uint32) { a.storeOffset(dst, src, 0, sizeof) } // storeOffset creates a store constraint of the form dst[offset] = src. // offset is the pointer offset in logical fields. // sizeof is the width (in logical fields) of the stored type. // func (a *analysis) storeOffset(dst, src nodeid, offset uint32, sizeof uint32) { if src == 0 { return // store of non-pointerlike value } if src == 0 && dst == 0 { return // non-pointerlike operation } if src == 0 || dst == 0 { panic(fmt.Sprintf("ill-typed store dst=n%d src=n%d", dst, src)) } for i := uint32(0); i < sizeof; i++ { a.addConstraint(&storeConstraint{offset, dst, src}) offset++ src++ } } // offsetAddr creates an offsetAddr constraint of the form dst = &src.#offset. // offset is the field offset in logical fields. // func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) { if offset == 0 { // Simplify dst = &src->f0 // to dst = src // (NB: this optimisation is defeated by the identity // field prepended to struct and array objects.) a.copy(dst, src, 1) } else { a.addConstraint(&offsetAddrConstraint{offset, dst, src}) } } // addConstraint adds c to the constraint set. func (a *analysis) addConstraint(c constraint) { a.constraints = append(a.constraints, c) if a.log != nil { fmt.Fprintf(a.log, "\t%s\n", c) } } // copyElems generates load/store constraints for *dst = *src, // where src and dst are slices or *arrays. // (If pts(·) of either is a known singleton, this is suboptimal.) // func (a *analysis) copyElems(typ types.Type, dst, src nodeid) { tmp := a.addNodes(typ, "copy") sz := a.sizeof(typ) a.loadOffset(tmp, src, 1, sz) a.storeOffset(dst, tmp, 1, sz) } // ---------- Constraint generation ---------- // genConv generates constraints for the conversion operation conv. func (a *analysis) genConv(conv *ssa.Convert) { res := a.valueNode(conv) if res == 0 { return // result is non-pointerlike } tSrc := conv.X.Type() tDst := conv.Type() switch utSrc := tSrc.Underlying().(type) { case *types.Slice: // []byte/[]rune -> string? return case *types.Pointer: // *T -> unsafe.Pointer? if tDst == tUnsafePtr { // ignore for now // a.copy(res, a.valueNode(conv.X), 1) return } case *types.Basic: switch utDst := tDst.Underlying().(type) { case *types.Pointer: // unsafe.Pointer -> *T? (currently unsound) if utSrc == tUnsafePtr { a.warnf(conv.Pos(), "unsound: %s contains an unsafe.Pointer conversion (to %s)", conv.Parent(), tDst) // For now, we treat unsafe.Pointer->*T // conversion like new(T) and create an // unaliased object. In future we may handle // unsafe conversions soundly; see TODO file. obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion") a.endObject(obj, conv) a.addressOf(res, obj) return } case *types.Slice: // string -> []byte/[]rune (or named aliases)? if utSrc.Info()&types.IsString != 0 { obj := a.addNodes(sliceToArray(tDst), "convert") a.endObject(obj, conv) a.addressOf(res, obj) return } case *types.Basic: // TODO(adonovan): // unsafe.Pointer -> uintptr? // uintptr -> unsafe.Pointer // // The language doesn't adequately specify the // behaviour of these operations, but almost // all uses of these conversions (even in the // spec) seem to imply a non-moving garbage // collection strategy, or implicit "pinning" // semantics for unsafe.Pointer conversions. // TODO(adonovan): we need more work before we can handle // cryptopointers well. if utSrc == tUnsafePtr || utDst == tUnsafePtr { // Ignore for now. See TODO file for ideas. return } return // ignore all other basic type conversions } } panic(fmt.Sprintf("illegal *ssa.Convert %s -> %s: %s", tSrc, tDst, conv.Parent())) } // genAppend generates constraints for a call to append. func (a *analysis) genAppend(instr *ssa.Call) { // Consider z = append(x, y). y is optional. // This may allocate a new [1]T array; call its object w. // We get the following constraints: // z = x // z = &w // *z = *y x := a.valueNode(instr.Call.Args[0]) z := a.valueNode(instr) a.copy(z, x, 1) // z = x if len(instr.Call.Args) == 1 { return // no allocation for z = append(x) or _ = append(x). } // TODO(adonovan): test append([]byte, ...string) []byte. y := a.valueNode(instr.Call.Args[1]) tArray := sliceToArray(instr.Call.Args[0].Type()) var w nodeid w = a.nextNode() a.addNodes(tArray, "append") a.endObject(w, instr) a.copyElems(tArray.Elem(), z, y) // *z = *y a.addressOf(z, w) // z = &w } // genBuiltinCall generates contraints for a call to a built-in. func (a *analysis) genBuiltinCall(instr ssa.CallInstruction) { call := instr.Common() switch call.Value.(*ssa.Builtin).Object().Name() { case "append": // Safe cast: append cannot appear in a go or defer statement. a.genAppend(instr.(*ssa.Call)) case "copy": tElem := call.Args[0].Type().Underlying().(*types.Slice).Elem() a.copyElems(tElem, a.valueNode(call.Args[0]), a.valueNode(call.Args[1])) case "panic": a.copy(a.panicNode, a.valueNode(call.Args[0]), 1) case "recover": if v := instr.Value(); v != nil { a.copy(a.valueNode(v), a.panicNode, 1) } case "print": // Analytically print is a no-op, but it's a convenient hook // for testing the pts of an expression, so we notify the client. // Existing uses in Go core libraries are few and harmless. if Print := a.config.Print; Print != nil { // Due to context-sensitivity, we may encounter // the same print() call in many contexts, so // we merge them to a canonical node. probe := a.probes[call] t := call.Args[0].Type() // First time? Create the canonical probe node. if probe == 0 { probe = a.addNodes(t, "print") a.probes[call] = probe Print(call, ptr{a, probe}) // notify client } a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t)) } default: // No-ops: close len cap real imag complex println delete. } } // shouldUseContext defines the context-sensitivity policy. It // returns true if we should analyse all static calls to fn anew. // // Obviously this interface rather limits how much freedom we have to // choose a policy. The current policy, rather arbitrarily, is true // for intrinsics and accessor methods (actually: short, single-block, // call-free functions). This is just a starting point. // func (a *analysis) shouldUseContext(fn *ssa.Function) bool { if a.findIntrinsic(fn) != nil { return true // treat intrinsics context-sensitively } if len(fn.Blocks) != 1 { return false // too expensive } blk := fn.Blocks[0] if len(blk.Instrs) > 10 { return false // too expensive } if fn.Synthetic != "" && (fn.Pkg == nil || fn != fn.Pkg.Func("init")) { return true // treat synthetic wrappers context-sensitively } for _, instr := range blk.Instrs { switch instr := instr.(type) { case ssa.CallInstruction: // Disallow function calls (except to built-ins) // because of the danger of unbounded recursion. if _, ok := instr.Common().Value.(*ssa.Builtin); !ok { return false } } } return true } // genStaticCall generates constraints for a statically dispatched // function call. It returns a node whose pts() will be the set of // possible call targets (in this case, a singleton). // func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid { // Ascertain the context (contour/CGNode) for a particular call. var obj nodeid fn := call.StaticCallee() if a.shouldUseContext(fn) { obj = a.makeFunctionObject(fn) // new contour for this call } else { a.valueNode(fn) // ensure shared contour was created obj = a.funcObj[fn] // ordinary (shared) contour. } sig := call.Signature() targets := a.addOneNode(sig, "call.targets", nil) a.addressOf(targets, obj) // (a singleton) // Copy receiver, if any. params := a.funcParams(obj) args := call.Args if sig.Recv() != nil { sz := a.sizeof(sig.Recv().Type()) a.copy(params, a.valueNode(args[0]), sz) params += nodeid(sz) args = args[1:] } // Copy actual parameters into formal params block. // Must loop, since the actuals aren't contiguous. for i, arg := range args { sz := a.sizeof(sig.Params().At(i).Type()) a.copy(params, a.valueNode(arg), sz) params += nodeid(sz) } // Copy formal results block to actual result. if result != 0 { a.copy(result, a.funcResults(obj), a.sizeof(sig.Results())) } return targets } // genDynamicCall generates constraints for a dynamic function call. // It returns a node whose pts() will be the set of possible call targets. // func (a *analysis) genDynamicCall(call *ssa.CallCommon, result nodeid) nodeid { fn := a.valueNode(call.Value) sig := call.Signature() // We add dynamic closure rules that store the arguments into, // and load the results from, the P/R block of each function // discovered in pts(fn). var offset uint32 = 1 // P/R block starts at offset 1 for i, arg := range call.Args { sz := a.sizeof(sig.Params().At(i).Type()) a.storeOffset(fn, a.valueNode(arg), offset, sz) offset += sz } if result != 0 { a.loadOffset(result, fn, offset, a.sizeof(sig.Results())) } return fn } // genInvoke generates constraints for a dynamic method invocation. // It returns a node whose pts() will be the set of possible call targets. // func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid { sig := call.Signature() // Allocate a contiguous targets/params/results block for this call. block := a.nextNode() targets := a.addOneNode(sig, "invoke.targets", nil) p := a.addNodes(sig.Params(), "invoke.params") r := a.addNodes(sig.Results(), "invoke.results") // Copy the actual parameters into the call's params block. for i, n := 0, sig.Params().Len(); i < n; i++ { sz := a.sizeof(sig.Params().At(i).Type()) a.copy(p, a.valueNode(call.Args[i]), sz) p += nodeid(sz) } // Copy the call's results block to the actual results. if result != 0 { a.copy(result, r, a.sizeof(sig.Results())) } // We add a dynamic invoke constraint that will add // edges from the caller's P/R block to the callee's // P/R block for each discovered call target. a.addConstraint(&invokeConstraint{call.Method, a.valueNode(call.Value), block}) return targets } // genCall generates contraints for call instruction instr. func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { call := instr.Common() // Intrinsic implementations of built-in functions. if _, ok := call.Value.(*ssa.Builtin); ok { a.genBuiltinCall(instr) return } var result nodeid if v := instr.Value(); v != nil { result = a.valueNode(v) } // The node whose pts(·) will contain all targets of the call. var targets nodeid switch { case call.StaticCallee() != nil: targets = a.genStaticCall(call, result) case call.IsInvoke(): targets = a.genInvoke(call, result) default: targets = a.genDynamicCall(call, result) } site := &callsite{ caller: caller, targets: targets, instr: instr, pos: instr.Pos(), } a.callsites = append(a.callsites, site) if a.log != nil { fmt.Fprintf(a.log, "\t%s to targets %s from %s\n", site.Description(), site.targets, site.caller) } } // genInstr generates contraints for instruction instr in context cgn. func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { if a.log != nil { var prefix string if val, ok := instr.(ssa.Value); ok { prefix = val.Name() + " = " } fmt.Fprintf(a.log, "; %s%s\n", prefix, instr) } switch instr := instr.(type) { case *ssa.DebugRef: // no-op. case *ssa.UnOp: switch instr.Op { case token.ARROW: // <-x // We can ignore instr.CommaOk because the node we're // altering is always at zero offset relative to instr. a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type())) case token.MUL: // *x a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type())) default: // NOT, SUB, XOR: no-op. } case *ssa.BinOp: // All no-ops. case ssa.CallInstruction: // *ssa.Call, *ssa.Go, *ssa.Defer a.genCall(cgn, instr) case *ssa.ChangeType: a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.Convert: a.genConv(instr) case *ssa.Extract: a.copy(a.valueNode(instr), a.valueOffsetNode(instr.Tuple, instr.Index), a.sizeof(instr.Type())) case *ssa.FieldAddr: a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X), a.offsetOf(mustDeref(instr.X.Type()), instr.Field)) case *ssa.IndexAddr: a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.Field: a.copy(a.valueNode(instr), a.valueOffsetNode(instr.X, instr.Field), a.sizeof(instr.Type())) case *ssa.Index: a.copy(a.valueNode(instr), 1+a.valueNode(instr.X), a.sizeof(instr.Type())) case *ssa.Select: recv := a.valueOffsetNode(instr, 2) // instr : (index, recvOk, recv0, ... recv_n-1) for _, st := range instr.States { elemSize := a.sizeof(st.Chan.Type().Underlying().(*types.Chan).Elem()) switch st.Dir { case ast.RECV: a.load(recv, a.valueNode(st.Chan), elemSize) recv++ case ast.SEND: a.store(a.valueNode(st.Chan), a.valueNode(st.Send), elemSize) } } case *ssa.Ret: results := a.funcResults(cgn.obj) for _, r := range instr.Results { sz := a.sizeof(r.Type()) a.copy(results, a.valueNode(r), sz) results += nodeid(sz) } case *ssa.Send: a.store(a.valueNode(instr.Chan), a.valueNode(instr.X), a.sizeof(instr.X.Type())) case *ssa.Store: a.store(a.valueNode(instr.Addr), a.valueNode(instr.Val), a.sizeof(instr.Val.Type())) case *ssa.Alloc: obj := a.nextNode() a.addNodes(mustDeref(instr.Type()), "alloc") a.endObject(obj, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeSlice: obj := a.nextNode() a.addNodes(sliceToArray(instr.Type()), "makeslice") a.endObject(obj, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeChan: obj := a.nextNode() a.addNodes(instr.Type().Underlying().(*types.Chan).Elem(), "makechan") a.endObject(obj, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeMap: obj := a.nextNode() tmap := instr.Type().Underlying().(*types.Map) a.addNodes(tmap.Key(), "makemap.key") a.addNodes(tmap.Elem(), "makemap.value") a.endObject(obj, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeInterface: tConc := instr.X.Type() // Create nodes and constraints for all methods of the type. // Ascertaining which will be needed is undecidable in general. mset := tConc.MethodSet() for i, n := 0, mset.Len(); i < n; i++ { a.valueNode(a.prog.Method(mset.At(i))) } obj := a.addOneNode(tConc, "iface.conctype", nil) // NB: type may be non-scalar! vnode := a.addNodes(tConc, "iface.value") a.endObject(obj, instr) a.nodes[obj].flags |= ntInterface // Copy the value into it, if nontrivial. if x := a.valueNode(instr.X); x != 0 { a.copy(vnode, x, a.sizeof(tConc)) } a.addressOf(a.valueNode(instr), obj) case *ssa.ChangeInterface: a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.TypeAssert: dst, src := a.valueNode(instr), a.valueNode(instr.X) a.addConstraint(&typeAssertConstraint{instr.AssertedType, dst, src}) case *ssa.Slice: a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.If, *ssa.Jump: // no-op. case *ssa.Phi: sz := a.sizeof(instr.Type()) for _, e := range instr.Edges { a.copy(a.valueNode(instr), a.valueNode(e), sz) } case *ssa.MakeClosure: fn := instr.Fn.(*ssa.Function) a.copy(a.valueNode(instr), a.valueNode(fn), 1) // Free variables are treated like global variables. for i, b := range instr.Bindings { a.copy(a.valueNode(fn.FreeVars[i]), a.valueNode(b), a.sizeof(b.Type())) } case *ssa.RunDefers: // The analysis is flow insensitive, so we just "call" // defers as we encounter them. case *ssa.Range: // Do nothing. Next{Iter: *ssa.Range} handles this case. case *ssa.Next: if !instr.IsString { // map // Assumes that Next is always directly applied to a Range result. theMap := instr.Iter.(*ssa.Range).X tMap := theMap.Type().Underlying().(*types.Map) ksize := a.sizeof(tMap.Key()) vsize := a.sizeof(tMap.Elem()) // Load from the map's (k,v) into the tuple's (ok, k, v). a.load(a.valueNode(instr)+1, a.valueNode(theMap), ksize+vsize) } case *ssa.Lookup: if tMap, ok := instr.X.Type().Underlying().(*types.Map); ok { // CommaOk can be ignored: field 0 is a no-op. ksize := a.sizeof(tMap.Key()) vsize := a.sizeof(tMap.Elem()) a.loadOffset(a.valueNode(instr), a.valueNode(instr.X), ksize, vsize) } case *ssa.MapUpdate: tmap := instr.Map.Type().Underlying().(*types.Map) ksize := a.sizeof(tmap.Key()) vsize := a.sizeof(tmap.Elem()) m := a.valueNode(instr.Map) a.store(m, a.valueNode(instr.Key), ksize) a.storeOffset(m, a.valueNode(instr.Value), ksize, vsize) case *ssa.Panic: a.copy(a.panicNode, a.valueNode(instr.X), 1) default: panic(fmt.Sprintf("unimplemented: %T", instr)) } } // genRootCalls generates the synthetic root of the callgraph and the // initial calls from it to the analysis scope, such as main, a test // or a library. // func (a *analysis) genRootCalls() *cgnode { r := ssa.NewFunction("", new(types.Signature), "root of callgraph") r.Prog = a.prog // hack. r.Enclosing = r // hack, so Function.String() doesn't crash r.String() // (asserts that it doesn't crash) root := &cgnode{fn: r} // For each main package, call main.init(), main.main(). for _, mainPkg := range a.config.Mains { main := mainPkg.Func("main") if main == nil { panic(fmt.Sprintf("%s has no main function", mainPkg)) } targets := a.addOneNode(main.Signature, "root.targets", nil) site := &callsite{ caller: root, targets: targets, } a.callsites = append(a.callsites, site) for _, fn := range [2]*ssa.Function{mainPkg.Func("init"), main} { if a.log != nil { fmt.Fprintf(a.log, "\troot call to %s:\n", fn) } a.copy(targets, a.valueNode(fn), 1) } } return root } // genFunc generates constraints for function fn. func (a *analysis) genFunc(cgn *cgnode) { fn := cgn.fn if a.log != nil { fmt.Fprintln(a.log) fmt.Fprintln(a.log) cgn.fn.DumpTo(a.log) } if impl := a.findIntrinsic(fn); impl != nil { impl(a, cgn) return } if fn.Blocks == nil { // External function with no intrinsic treatment. // We'll warn about calls to such functions at the end. return } // The value nodes for the params are in the func object block. params := a.funcParams(cgn.obj) for _, p := range fn.Params { // TODO(adonovan): record the context (cgn) too. a.setValueNode(p, params) params += nodeid(a.sizeof(p.Type())) } // Free variables are treated like global variables: // the outer function sets them with MakeClosure; // the inner function accesses them with Capture. // Create value nodes for all value instructions. // (Clobbers any previous nodes from same fn in different context.) if a.log != nil { fmt.Fprintln(a.log, "; Creating instruction values") } for _, b := range fn.Blocks { for _, instr := range b.Instrs { switch instr := instr.(type) { case *ssa.Range: // do nothing: it has a funky type. case ssa.Value: var comment string if a.log != nil { comment = instr.Name() } id := a.addNodes(instr.Type(), comment) // TODO(adonovan): record the context (cgn) too. a.setValueNode(instr, id) } } } // Generate constraints for instructions. for _, b := range fn.Blocks { for _, instr := range b.Instrs { a.genInstr(cgn, instr) } } // (Instruction Values will hang around in the environment.) } // generate generates offline constraints for the entire program. // It returns the synthetic root of the callgraph. // func (a *analysis) generate() *cgnode { // Create a dummy node since we use the nodeid 0 for // non-pointerlike variables. a.addNodes(tInvalid, "(zero)") // Create the global node for panic values. a.panicNode = a.addNodes(tEface, "panic") root := a.genRootCalls() // Generate constraints for entire program. // (Actually just the RTA-reachable portion of the program. // See Bacon & Sweeney, OOPSLA'96). for len(a.genq) > 0 { cgn := a.genq[0] a.genq = a.genq[1:] a.genFunc(cgn) } // Create a dummy node to avoid out-of-range indexing in case // the last allocated type was of zero length. a.addNodes(tInvalid, "(max)") return root }