diff --git a/pointer/analysis.go b/pointer/analysis.go index c7d666ae..75933b88 100644 --- a/pointer/analysis.go +++ b/pointer/analysis.go @@ -11,6 +11,7 @@ import ( "go/token" "io" "os" + "reflect" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types/typemap" @@ -176,9 +177,11 @@ type analysis struct { cgnodes []*cgnode // all cgnodes genq []*cgnode // queue of functions to generate constraints for intrinsics map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns - funcObj map[*ssa.Function]nodeid // default function object for each func probes map[*ssa.CallCommon]nodeid // maps call to print() to argument variable - valNode map[ssa.Value]nodeid // node for each ssa.Value + globalval map[ssa.Value]nodeid // node for each global ssa.Value + globalobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton + localval map[ssa.Value]nodeid // node for each local ssa.Value + localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton work worklist // solver's worklist queries map[ssa.Value][]Pointer // same as Results.Queries @@ -235,16 +238,24 @@ func Analyze(config *Config) *Result { config: config, log: config.Log, prog: config.prog(), - valNode: make(map[ssa.Value]nodeid), + globalval: make(map[ssa.Value]nodeid), + globalobj: make(map[ssa.Value]nodeid), flattenMemo: make(map[types.Type][]*fieldInfo), hasher: typemap.MakeHasher(), intrinsics: make(map[*ssa.Function]intrinsic), - funcObj: make(map[*ssa.Function]nodeid), probes: make(map[*ssa.CallCommon]nodeid), work: makeMapWorklist(), queries: make(map[ssa.Value][]Pointer), } + if false { + a.log = os.Stderr // for debugging crashes; extremely verbose + } + + if a.log != nil { + fmt.Fprintln(a.log, "======== NEW ANALYSIS ========") + } + if reflect := a.prog.ImportedPackage("reflect"); reflect != nil { a.reflectValueObj = reflect.Object.Scope().Lookup("Value") a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named) @@ -259,16 +270,21 @@ func Analyze(config *Config) *Result { a.reflectZeros.SetHasher(a.hasher) } - if false { - a.log = os.Stderr // for debugging crashes; extremely verbose - } + root := a.generate() if a.log != nil { - fmt.Fprintln(a.log, "======== NEW ANALYSIS ========") + // Show size of constraint system. + counts := make(map[reflect.Type]int) + for _, c := range a.constraints { + counts[reflect.TypeOf(c)]++ + } + fmt.Fprintf(a.log, "# constraints:\t%d\n", len(a.constraints)) + for t, n := range counts { + fmt.Fprintf(a.log, "\t%s:\t%d\n", t, n) + } + fmt.Fprintf(a.log, "# nodes:\t%d\n", len(a.nodes)) } - root := a.generate() - //a.optimize() a.solve() diff --git a/pointer/api.go b/pointer/api.go index 3867b2a4..06fb6146 100644 --- a/pointer/api.go +++ b/pointer/api.go @@ -99,11 +99,6 @@ type Result struct { } // A Pointer is an equivalence class of pointerlike values. -// -// TODO(adonovan): add a method -// Context() call.GraphNode -// for pointers corresponding to local variables, -// type Pointer interface { // PointsTo returns the points-to set of this pointer. PointsTo() PointsToSet @@ -112,6 +107,10 @@ type Pointer interface { // the argument pointer. MayAlias(Pointer) bool + // Context returns the context of this pointer, + // if it corresponds to a local variable. + Context() call.GraphNode + String() string } @@ -190,7 +189,7 @@ func (s ptset) DynamicTypes() *typemap.M { panic("indirect tagged object") // implement later } prev, _ := tmap.At(tDyn).([]Pointer) - tmap.Set(tDyn, append(prev, ptr{s.a, v})) + tmap.Set(tDyn, append(prev, ptr{s.a, nil, v})) } return &tmap } @@ -209,14 +208,19 @@ func (x ptset) Intersects(y_ PointsToSet) bool { // ptr adapts a node to the Pointer interface. type ptr struct { - a *analysis - n nodeid // non-zero + a *analysis + cgn *cgnode + n nodeid // non-zero } func (p ptr) String() string { return fmt.Sprintf("n%d", p.n) } +func (p ptr) Context() call.GraphNode { + return p.cgn +} + func (p ptr) PointsTo() PointsToSet { return ptset{p.a, p.a.nodes[p.n].pts} } diff --git a/pointer/gen.go b/pointer/gen.go index cebeff5a..f62cd265 100644 --- a/pointer/gen.go +++ b/pointer/gen.go @@ -6,6 +6,10 @@ package pointer // This file defines the constraint generation phase. +// TODO(adonovan): move the constraint definitions and the store() etc +// functions which add them (and are also used by the solver) into a +// new file, constraints.go. + import ( "fmt" "go/ast" @@ -64,9 +68,14 @@ func (a *analysis) addOneNode(typ types.Type, comment string, subelement *fieldI } // 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 +// cgn identifies the context iff v is a local variable. +// +func (a *analysis) setValueNode(v ssa.Value, id nodeid, cgn *cgnode) { + if cgn != nil { + a.localval[v] = id + } else { + a.globalval[v] = id + } if a.log != nil { fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v) } @@ -75,10 +84,10 @@ func (a *analysis) setValueNode(v ssa.Value, id nodeid) { if indirect, ok := a.config.Queries[v]; ok { if indirect { tmp := a.addNodes(v.Type(), "query.indirect") - a.load(tmp, id, a.sizeof(v.Type())) + a.genLoad(cgn, tmp, v, 0, a.sizeof(v.Type())) id = tmp } - a.queries[v] = append(a.queries[v], ptr{a, id}) + a.queries[v] = append(a.queries[v], ptr{a, cgn, id}) } } @@ -140,75 +149,6 @@ func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid { 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, nil, 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, nil, l) - - a.addressOf(id, obj) - } - } - return id -} - // makeTagged creates a tagged object of type typ. func (a *analysis) makeTagged(typ types.Type, cgn *cgnode, val ssa.Value) nodeid { obj := a.addOneNode(typ, "tagged.T", nil) // NB: type may be non-scalar! @@ -252,34 +192,24 @@ func (a *analysis) rtypeTaggedValue(obj nodeid) types.Type { // 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] + // Value nodes for locals are created en masse by genFunc. + if id, ok := a.localval[v]; ok { + return id + } + + // Value nodes for globals are created on demand. + id, ok := a.globalval[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) + var comment string + if a.log != nil { + comment = v.String() } - a.setValueNode(v, id) + id = a.addOneNode(v.Type(), comment, nil) + if obj := a.objectNode(nil, v); obj != 0 { + a.addressOf(id, obj) + } + a.setValueNode(v, id, nil) } return id } @@ -365,18 +295,11 @@ func (a *analysis) addressOf(id, obj nodeid) { 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]. +// load 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) { +func (a *analysis) load(dst, src nodeid, offset, sizeof uint32) { if dst == 0 { return // load of non-pointerlike value } @@ -393,18 +316,11 @@ func (a *analysis) loadOffset(dst, src nodeid, offset uint32, sizeof uint32) { } } -// 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. +// store 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) { +func (a *analysis) store(dst, src nodeid, offset uint32, sizeof uint32) { if src == 0 { return // store of non-pointerlike value } @@ -451,13 +367,12 @@ func (a *analysis) addConstraint(c constraint) { // 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) { +func (a *analysis) copyElems(cgn *cgnode, typ types.Type, dst, src ssa.Value) { tmp := a.addNodes(typ, "copy") sz := a.sizeof(typ) - a.loadOffset(tmp, src, 1, sz) - a.storeOffset(dst, tmp, 1, sz) + a.genLoad(cgn, tmp, src, 1, sz) + a.genStore(cgn, dst, tmp, 1, sz) } // ---------- Constraint generation ---------- @@ -553,10 +468,10 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { // z = &w // *z = *y - x := a.valueNode(instr.Call.Args[0]) + x := instr.Call.Args[0] - z := a.valueNode(instr) - a.copy(z, x, 1) // z = x + z := instr + a.copy(a.valueNode(z), a.valueNode(x), 1) // z = x if len(instr.Call.Args) == 1 { return // no allocation for z = append(x) or _ = append(x). @@ -564,7 +479,7 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { // TODO(adonovan): test append([]byte, ...string) []byte. - y := a.valueNode(instr.Call.Args[1]) + y := instr.Call.Args[1] tArray := sliceToArray(instr.Call.Args[0].Type()) var w nodeid @@ -572,8 +487,8 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { a.addNodes(tArray, "append") a.endObject(w, cgn, instr) - a.copyElems(tArray.Elem(), z, y) // *z = *y - a.addressOf(z, w) // z = &w + a.copyElems(cgn, tArray.Elem(), z, y) // *z = *y + a.addressOf(a.valueNode(z), w) // z = &w } // genBuiltinCall generates contraints for a call to a built-in. @@ -586,7 +501,7 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) { case "copy": tElem := call.Args[0].Type().Underlying().(*types.Slice).Elem() - a.copyElems(tElem, a.valueNode(call.Args[0]), a.valueNode(call.Args[1])) + a.copyElems(cgn, tElem, call.Args[0], call.Args[1]) case "panic": a.copy(a.panicNode, a.valueNode(call.Args[0]), 1) @@ -611,7 +526,7 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) { if probe == 0 { probe = a.addNodes(t, "print") a.probes[call] = probe - Print(call, ptr{a, probe}) // notify client + Print(call, ptr{a, nil, probe}) // notify client } a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t)) @@ -668,8 +583,7 @@ func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid { 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. + obj = a.objectNode(nil, fn) // ordinary (shared) contour } sig := call.Signature() @@ -705,7 +619,7 @@ func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid { // 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 { +func (a *analysis) genDynamicCall(caller *cgnode, call *ssa.CallCommon, result nodeid) nodeid { fn := a.valueNode(call.Value) sig := call.Signature() @@ -716,11 +630,11 @@ func (a *analysis) genDynamicCall(call *ssa.CallCommon, result nodeid) nodeid { 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) + a.genStore(caller, call.Value, a.valueNode(arg), offset, sz) offset += sz } if result != 0 { - a.loadOffset(result, fn, offset, a.sizeof(sig.Results())) + a.genLoad(caller, result, call.Value, offset, a.sizeof(sig.Results())) } return fn } @@ -840,7 +754,7 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { case call.IsInvoke(): targets = a.genInvoke(call, result) default: - targets = a.genDynamicCall(call, result) + targets = a.genDynamicCall(caller, call, result) } site := &callsite{ @@ -853,6 +767,150 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { } } +// objectNode returns the object to which v points, if known. +// In other words, if the points-to set of v is a singleton, it +// returns the sole label, zero otherwise. +// +// We exploit this information to make the generated constraints less +// dynamic. For example, a complex load constraint can be replaced by +// a simple copy constraint when the sole destination is known a priori. +// +// Some SSA instructions always have singletons points-to sets: +// Alloc, Function, Global, MakeChan, MakeClosure, MakeInterface, MakeMap, MakeSlice. +// Others may be singletons depending on their operands: +// Capture, Const, Convert, FieldAddr, IndexAddr, Slice. +// +// Idempotent. Objects are created as needed, possibly via recursion +// down the SSA value graph, e.g IndexAddr(FieldAddr(Alloc))). +// +func (a *analysis) objectNode(cgn *cgnode, v ssa.Value) nodeid { + if cgn == nil { + // Global object. + obj, ok := a.globalobj[v] + if !ok { + switch v := v.(type) { + case *ssa.Global: + obj = a.nextNode() + a.addNodes(mustDeref(v.Type()), "global") + a.endObject(obj, nil, v) + + case *ssa.Function: + obj = a.makeFunctionObject(v) + + case *ssa.Const: + if t, ok := v.Type().Underlying().(*types.Slice); ok && !v.IsNil() { + // Non-nil []byte or []rune constant. + obj = a.nextNode() + a.addNodes(sliceToArray(t), "array in slice constant") + a.endObject(obj, nil, v) + } + + case *ssa.Capture: + // For now, Captures have the same cardinality as globals. + // TODO(adonovan): treat captures context-sensitively. + } + a.globalobj[v] = obj + } + return obj + } + + // Local object. + obj, ok := a.localobj[v] + if !ok { + switch v := v.(type) { + case *ssa.Alloc: + obj = a.nextNode() + a.addNodes(mustDeref(v.Type()), "alloc") + a.endObject(obj, cgn, v) + + case *ssa.MakeSlice: + obj = a.nextNode() + a.addNodes(sliceToArray(v.Type()), "makeslice") + a.endObject(obj, cgn, v) + + case *ssa.MakeChan: + obj = a.nextNode() + a.addNodes(v.Type().Underlying().(*types.Chan).Elem(), "makechan") + a.endObject(obj, cgn, v) + + case *ssa.MakeMap: + obj = a.nextNode() + tmap := v.Type().Underlying().(*types.Map) + a.addNodes(tmap.Key(), "makemap.key") + a.addNodes(tmap.Elem(), "makemap.value") + a.endObject(obj, cgn, v) + + case *ssa.MakeInterface: + tConc := v.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.makeTagged(tConc, cgn, v) + + // Copy the value into it, if nontrivial. + if x := a.valueNode(v.X); x != 0 { + a.copy(obj+1, x, a.sizeof(tConc)) + } + + case *ssa.FieldAddr: + if xobj := a.objectNode(cgn, v.X); xobj != 0 { + obj = xobj + nodeid(a.offsetOf(mustDeref(v.X.Type()), v.Field)) + } + + case *ssa.IndexAddr: + if xobj := a.objectNode(cgn, v.X); xobj != 0 { + obj = xobj + 1 + } + + case *ssa.Slice: + obj = a.objectNode(cgn, v.X) + + case *ssa.Convert: + // TODO(adonovan): opt: handle these cases too: + // - unsafe.Pointer->*T conversion acts like Alloc + // - string->[]byte/[]rune conversion acts like MakeSlice + } + a.localobj[v] = obj + } + return obj +} + +// genLoad generates constraints for result = *(ptr + val). +func (a *analysis) genLoad(cgn *cgnode, result nodeid, ptr ssa.Value, offset, sizeof uint32) { + if obj := a.objectNode(cgn, ptr); obj != 0 { + // Pre-apply loadConstraint.solve(). + a.copy(result, obj+nodeid(offset), sizeof) + } else { + a.load(result, a.valueNode(ptr), offset, sizeof) + } +} + +// genOffsetAddr generates constraints for a 'v=ptr.field' (FieldAddr) +// or 'v=ptr[*]' (IndexAddr) instruction v. +func (a *analysis) genOffsetAddr(cgn *cgnode, v ssa.Value, ptr nodeid, offset uint32) { + dst := a.valueNode(v) + if obj := a.objectNode(cgn, v); obj != 0 { + // Pre-apply offsetAddrConstraint.solve(). + a.addressOf(dst, obj) + } else { + a.offsetAddr(dst, ptr, offset) + } +} + +// genStore generates constraints for *(ptr + offset) = val. +func (a *analysis) genStore(cgn *cgnode, ptr ssa.Value, val nodeid, offset, sizeof uint32) { + if obj := a.objectNode(cgn, ptr); obj != 0 { + // Pre-apply storeConstraint.solve(). + a.copy(obj+nodeid(offset), val, sizeof) + } else { + a.store(a.valueNode(ptr), val, offset, sizeof) + } +} + // genInstr generates contraints for instruction instr in context cgn. func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { if a.log != nil { @@ -871,11 +929,11 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { 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())) + // altering is always at zero offset relative to instr + a.genLoad(cgn, a.valueNode(instr), instr.X, 0, a.sizeof(instr.Type())) case token.MUL: // *x - a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type())) + a.genLoad(cgn, a.valueNode(instr), instr.X, 0, a.sizeof(instr.Type())) default: // NOT, SUB, XOR: no-op. @@ -899,11 +957,11 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { a.sizeof(instr.Type())) case *ssa.FieldAddr: - a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X), + a.genOffsetAddr(cgn, 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) + a.genOffsetAddr(cgn, instr, a.valueNode(instr.X), 1) case *ssa.Field: a.copy(a.valueNode(instr), @@ -919,11 +977,11 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { elemSize := a.sizeof(st.Chan.Type().Underlying().(*types.Chan).Elem()) switch st.Dir { case ast.RECV: - a.load(recv, a.valueNode(st.Chan), elemSize) + a.genLoad(cgn, recv, st.Chan, 0, elemSize) recv++ case ast.SEND: - a.store(a.valueNode(st.Chan), a.valueNode(st.Send), elemSize) + a.genStore(cgn, st.Chan, a.valueNode(st.Send), 0, elemSize) } } @@ -936,53 +994,14 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { } case *ssa.Send: - a.store(a.valueNode(instr.Chan), a.valueNode(instr.X), a.sizeof(instr.X.Type())) + a.genStore(cgn, instr.Chan, a.valueNode(instr.X), 0, a.sizeof(instr.X.Type())) case *ssa.Store: - a.store(a.valueNode(instr.Addr), a.valueNode(instr.Val), a.sizeof(instr.Val.Type())) + a.genStore(cgn, instr.Addr, a.valueNode(instr.Val), 0, a.sizeof(instr.Val.Type())) - case *ssa.Alloc: - obj := a.nextNode() - a.addNodes(mustDeref(instr.Type()), "alloc") - a.endObject(obj, cgn, instr) - a.addressOf(a.valueNode(instr), obj) - - case *ssa.MakeSlice: - obj := a.nextNode() - a.addNodes(sliceToArray(instr.Type()), "makeslice") - a.endObject(obj, cgn, 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, cgn, 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, cgn, 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.makeTagged(tConc, cgn, instr) - - // Copy the value into it, if nontrivial. - if x := a.valueNode(instr.X); x != 0 { - a.copy(obj+1, x, a.sizeof(tConc)) - } - a.addressOf(a.valueNode(instr), obj) + case *ssa.Alloc, *ssa.MakeSlice, *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeInterface: + v := instr.(ssa.Value) + a.addressOf(a.valueNode(v), a.objectNode(cgn, v)) case *ssa.ChangeInterface: a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) @@ -1026,7 +1045,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { 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) + a.genLoad(cgn, a.valueNode(instr)+1, theMap, 0, ksize+vsize) } case *ssa.Lookup: @@ -1034,16 +1053,15 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { // 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) + a.genLoad(cgn, a.valueNode(instr), 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) + a.genStore(cgn, instr.Map, a.valueNode(instr.Key), 0, ksize) + a.genStore(cgn, instr.Map, a.valueNode(instr.Value), ksize, vsize) case *ssa.Panic: a.copy(a.panicNode, a.valueNode(instr.X), 1) @@ -1123,11 +1141,17 @@ func (a *analysis) genFunc(cgn *cgnode) { return } + if a.log != nil { + fmt.Fprintln(a.log, "; Creating nodes for local values") + } + + a.localval = make(map[ssa.Value]nodeid) + a.localobj = make(map[ssa.Value]nodeid) + // 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) + a.setValueNode(p, params, cgn) params += nodeid(a.sizeof(p.Type())) } @@ -1135,16 +1159,14 @@ func (a *analysis) genFunc(cgn *cgnode) { // 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") - } + // Create value nodes for all value instructions + // since SSA may contain forward references. for _, b := range fn.Blocks { for _, instr := range b.Instrs { switch instr := instr.(type) { case *ssa.Range: - // do nothing: it has a funky type. + // do nothing: it has a funky type, + // and *ssa.Next does all the work. case ssa.Value: var comment string @@ -1152,8 +1174,7 @@ func (a *analysis) genFunc(cgn *cgnode) { comment = instr.Name() } id := a.addNodes(instr.Type(), comment) - // TODO(adonovan): record the context (cgn) too. - a.setValueNode(instr, id) + a.setValueNode(instr, id, cgn) } } } @@ -1165,7 +1186,8 @@ func (a *analysis) genFunc(cgn *cgnode) { } } - // (Instruction Values will hang around in the environment.) + a.localval = nil + a.localobj = nil } // generate generates offline constraints for the entire program. diff --git a/pointer/reflect.go b/pointer/reflect.go index d55d0bb0..5b72fe29 100644 --- a/pointer/reflect.go +++ b/pointer/reflect.go @@ -118,7 +118,7 @@ func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { } obj := a.makeTagged(tMap.Elem(), c.cgn, nil) - a.loadOffset(obj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem())) + a.load(obj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem())) if a.addLabel(c.result, obj) { changed = true } @@ -168,7 +168,7 @@ func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) { } kObj := a.makeTagged(tMap.Key(), c.cgn, nil) - a.load(kObj+1, m, a.sizeof(tMap.Key())) + a.load(kObj+1, m, 0, a.sizeof(tMap.Key())) if a.addLabel(c.result, kObj) { changed = true } @@ -228,7 +228,7 @@ func (c *rVRecvConstraint) solve(a *analysis, _ *node, delta nodeset) { tElem := tChan.Elem() elemObj := a.makeTagged(tElem, c.cgn, nil) - a.load(elemObj+1, ch, a.sizeof(tElem)) + a.load(elemObj+1, ch, 0, a.sizeof(tElem)) if a.addLabel(c.result, elemObj) { changed = true } @@ -280,7 +280,7 @@ func (c *rVSendConstraint) solve(a *analysis, _ *node, delta nodeset) { tElem := tChan.Elem() xtmp := a.addNodes(tElem, "Send.xtmp") a.typeAssert(tElem, xtmp, c.x) - a.store(ch, xtmp, a.sizeof(tElem)) + a.store(ch, xtmp, 0, a.sizeof(tElem)) } } @@ -332,12 +332,12 @@ func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { // Extract key's payload to keytmp, then store to map key. keytmp := a.addNodes(tMap.Key(), "SetMapIndex.keytmp") a.typeAssert(tMap.Key(), keytmp, c.key) - a.store(m, keytmp, keysize) + a.store(m, keytmp, 0, keysize) // Extract val's payload to vtmp, then store to map value. valtmp := a.addNodes(tMap.Elem(), "SetMapIndex.valtmp") a.typeAssert(tMap.Elem(), valtmp, c.val) - a.storeOffset(m, valtmp, keysize, a.sizeof(tMap.Elem())) + a.store(m, valtmp, keysize, a.sizeof(tMap.Elem())) } } @@ -433,7 +433,7 @@ func (c *reflectIndirectConstraint) solve(a *analysis, _ *node, delta nodeset) { // load the payload of the pointer's tagged object // into a new tagged object res = a.makeTagged(tPtr.Elem(), c.cgn, nil) - a.load(res+1, vObj+1, a.sizeof(tPtr.Elem())) + a.load(res+1, vObj+1, 0, a.sizeof(tPtr.Elem())) } else { res = vObj } diff --git a/pointer/solve.go b/pointer/solve.go index b9d5f680..907a6b86 100644 --- a/pointer/solve.go +++ b/pointer/solve.go @@ -307,10 +307,10 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { } sig := fn.Signature - fnObj := a.funcObj[fn] // dynamic calls use shared contour + fnObj := a.globalobj[fn] // dynamic calls use shared contour if fnObj == 0 { - // a.valueNode(fn) was not called during gen phase. - panic(fmt.Sprintf("a.funcObj(%s)==nil", fn)) + // a.objectNode(fn) was not called during gen phase. + panic(fmt.Sprintf("a.globalobj[%s]==nil", fn)) } // Make callsite's fn variable point to identity of