From 3b5de067a120fcc5aa9b6986ac205654ec0b62b7 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 16 Sep 2013 09:49:10 -0400 Subject: [PATCH] go.tools/pointer: reflection, part 1: maps, and some core features. Core: reflect.TypeOf reflect.ValueOf reflect.Zero reflect.Value.Interface Maps: (reflect.Value).MapIndex (reflect.Value).MapKeys (reflect.Value).SetMapIndex (*reflect.rtype).Elem (*reflect.rtype).Key + tests: pointer/testdata/mapreflect.go. oracle/testdata/src/main/reflection.go. Interface objects (T, V...) have been renamed "tagged objects". Abstraction: we model reflect.Value similar to interface{}---as a pointer that points only to tagged objects---but a reflect.Value may also point to an "indirect tagged object", one in which the payload V is of type *T not T. These are required because reflect.Values can hold lvalues, e.g. when derived via Field() or Elem(), though we won't use them till we get to structs and pointers. Solving: each reflection intrinsic defines a new constraint and resolution rule. Because of the nature of reflection, generalizing across types, the resolution rules dynamically create additional complex constraints during solving, where previously only simple (copy) constraints were created. This requires some solver changes: The work done before the main solver loop (to attach new constraints to the graph) is now done before each iteration, in processNewConstraints. Its loop over constraints is broken into two passes: the first handles base (addr-of) constraints, the second handles simple and complex constraints. constraint.init() has been inlined. The only behaviour that varies across constraints is ptr() Sadly this will pessimize presolver optimisations, when we get there; such is the price of reflection. Objects: reflection intrinsics create objects (i.e. cause memory allocations) with no SSA operation. We will represent them as the cgnode of the instrinsic (e.g. reflect.New), so we extend Labels and node.data to represent objects as a product (not sum) of ssa.Value and cgnode and pull this out into its own type, struct object. This simplifies a number of invariants and saves space. The ntObject flag is now represented by obj!=nil; the other flags are moved into object. cgnodes are now always recorded in objects/Labels for which it is appropriate (all but those for globals, constants and the shared contours for functions). Also: - Prepopulate the flattenMemo cache to consider reflect.Value a fake pointer, not a struct. - Improve accessors and documentation on type Label. - @conctypes assertions renamed @types (since dyn. types needn't be concrete). - add oracle 'describe' test on an interface (missing, an oversight). R=crawshaw CC=golang-dev https://golang.org/cl/13418048 --- oracle/describe.go | 19 +- oracle/json/json.go | 3 +- oracle/oracle_test.go | 1 + oracle/testdata/src/main/describe.go | 6 + oracle/testdata/src/main/describe.golden | 21 + oracle/testdata/src/main/reflection.go | 30 ++ oracle/testdata/src/main/reflection.golden | 40 ++ pointer/TODO | 1 + pointer/analysis.go | 170 +++++--- pointer/api.go | 65 ++- pointer/doc.go | 134 ++++-- pointer/gen.go | 152 ++++--- pointer/intrinsics.go | 194 +++++---- pointer/labels.go | 95 ++++- pointer/pointer_test.go | 67 +-- pointer/reflect.go | 470 +++++++++++++++++++++ pointer/solve.go | 213 +++++++--- pointer/testdata/another.go | 2 +- pointer/testdata/arrayreflect.go | 24 +- pointer/testdata/arrays.go | 4 +- pointer/testdata/chanreflect.go | 14 +- pointer/testdata/chanreflect1.go | 4 +- pointer/testdata/flow.go | 8 +- pointer/testdata/fmtexcerpt.go | 4 +- pointer/testdata/funcreflect.go | 2 +- pointer/testdata/interfaces.go | 22 +- pointer/testdata/mapreflect.go | 46 +- pointer/testdata/panic.go | 2 +- pointer/testdata/reflect.go | 36 +- pointer/testdata/structreflect.go | 2 +- pointer/testdata/structs.go | 2 +- pointer/util.go | 31 ++ 32 files changed, 1405 insertions(+), 479 deletions(-) create mode 100644 oracle/testdata/src/main/reflection.go create mode 100644 oracle/testdata/src/main/reflection.golden create mode 100644 pointer/reflect.go diff --git a/oracle/describe.go b/oracle/describe.go index 258e43af..143c7e4c 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -28,7 +28,8 @@ import ( // - the location of the definition of its referent (for identifiers) // - its type and method set (for an expression or type expression) // - its points-to set (for a pointer-like expression) -// - its concrete types (for an interface expression) and their points-to sets. +// - its dynamic types (for an interface, reflect.Value, or +// reflect.Type expression) and their points-to sets. // // All printed sets are sorted to ensure determinism. // @@ -427,9 +428,9 @@ func describePointer(o *oracle, v ssa.Value, indirect bool) (ptrs []pointerResul } pts := pointer.PointsToCombined(pointers) - if _, ok := v.Type().Underlying().(*types.Interface); ok { - // Show concrete types for interface expression. - if concs := pts.ConcreteTypes(); concs.Len() > 0 { + if pointer.CanHaveDynamicTypes(v.Type()) { + // Show concrete types for interface/reflect.Value expression. + if concs := pts.DynamicTypes(); concs.Len() > 0 { concs.Iterate(func(conc types.Type, pta interface{}) { combined := pointer.PointsToCombined(pta.([]pointer.Pointer)) labels := combined.Labels() @@ -518,10 +519,12 @@ func (r *describeValueResult) display(printf printfFunc) { } // Display the results of pointer analysis. - if _, ok := r.typ.Underlying().(*types.Interface); ok { - // Show concrete types for interface expression. + if pointer.CanHaveDynamicTypes(r.typ) { + // Show concrete types for interface, reflect.Type or + // reflect.Value expression. + if len(r.ptrs) > 0 { - printf(false, "interface may contain these concrete types:") + printf(false, "this %s may contain these dynamic types:", r.typ) for _, ptr := range r.ptrs { var obj types.Object if nt, ok := deref(ptr.typ).(*types.Named); ok { @@ -535,7 +538,7 @@ func (r *describeValueResult) display(printf printfFunc) { } } } else { - printf(false, "interface cannot contain any concrete values.") + printf(false, "this %s cannot contain any dynamic types.", r.typ) } } else { // Show labels for other expressions. diff --git a/oracle/json/json.go b/oracle/json/json.go index da6888d7..be0d9963 100644 --- a/oracle/json/json.go +++ b/oracle/json/json.go @@ -135,7 +135,8 @@ type DescribePointer struct { // If the described value is an interface, it will have one PTS entry // describing each concrete type that it may contain. For each // concrete type that is a pointer, the PTS entry describes the labels -// it may point to. +// it may point to. The same is true for reflect.Values, except the +// dynamic types needn't be concrete. // type DescribeValue struct { Type string `json:"type"` // type of the expression diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index 07c5d9cc..12b9c687 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -207,6 +207,7 @@ func TestOracle(t *testing.T) { "testdata/src/main/implements.go", "testdata/src/main/imports.go", "testdata/src/main/peers.go", + "testdata/src/main/reflection.go", // JSON: "testdata/src/main/callgraph-json.go", "testdata/src/main/calls-json.go", diff --git a/oracle/testdata/src/main/describe.go b/oracle/testdata/src/main/describe.go index 91d2c168..7d3c407f 100644 --- a/oracle/testdata/src/main/describe.go +++ b/oracle/testdata/src/main/describe.go @@ -39,6 +39,12 @@ func main() { // @describe func-def-main "main" x = &b // @describe var-def-x-2 "x" _ = x // @describe var-ref-x-2 "x" + i = new(C) // @describe var-ref-i-C "i" + if i != nil { + i = D{} // @describe var-ref-i-D "i" + } + _ = i // @describe var-ref-i "i" + // const objects const localpi = 3.141 // @describe const-local-pi "localpi" const localpie = cake(pi) // @describe const-local-pie "localpie" diff --git a/oracle/testdata/src/main/describe.golden b/oracle/testdata/src/main/describe.golden index 5d0e781b..23efffd3 100644 --- a/oracle/testdata/src/main/describe.golden +++ b/oracle/testdata/src/main/describe.golden @@ -104,6 +104,27 @@ defined here value may point to these labels: b +-------- @describe var-ref-i-C -------- +reference to var i describe.I +defined here +this describe.I may contain these dynamic types: + *describe.C, may point to: + new + +-------- @describe var-ref-i-D -------- +reference to var i describe.I +defined here +this describe.I may contain these dynamic types: + describe.D + +-------- @describe var-ref-i -------- +reference to var i describe.I +defined here +this describe.I may contain these dynamic types: + *describe.C, may point to: + new + describe.D + -------- @describe const-local-pi -------- definition of const localpi untyped float diff --git a/oracle/testdata/src/main/reflection.go b/oracle/testdata/src/main/reflection.go new file mode 100644 index 00000000..fc11c2f2 --- /dev/null +++ b/oracle/testdata/src/main/reflection.go @@ -0,0 +1,30 @@ +package reflection + +// This is a test of 'describe', but we split it into a separate file +// so that describe.go doesn't have to import "reflect" each time. + +import "reflect" + +var a int +var b bool + +func main() { + m := make(map[*int]*bool) + m[&a] = &b + + mrv := reflect.ValueOf(m) + if a > 0 { + mrv = reflect.ValueOf(&b) + } + if a > 0 { + mrv = reflect.ValueOf(&a) + } + + _ = mrv // @describe mrv "mrv" + p1 := mrv.Interface() // @describe p1 "p1" + p2 := mrv.MapKeys() // @describe p2 "p2" + p3 := p2[0] // @describe p3 "p3" + p4 := reflect.TypeOf(p1) // @describe p4 "p4" + + _, _, _, _ = p1, p2, p3, p4 +} diff --git a/oracle/testdata/src/main/reflection.golden b/oracle/testdata/src/main/reflection.golden new file mode 100644 index 00000000..86dd7a68 --- /dev/null +++ b/oracle/testdata/src/main/reflection.golden @@ -0,0 +1,40 @@ +-------- @describe mrv -------- +reference to var mrv reflect.Value +defined here +this reflect.Value may contain these dynamic types: + *bool, may point to: + reflection.b + *int, may point to: + reflection.a + map[*int]*bool, may point to: + makemap + +-------- @describe p1 -------- +definition of var p1 interface{} +this interface{} may contain these dynamic types: + *bool, may point to: + reflection.b + *int, may point to: + reflection.a + map[*int]*bool, may point to: + makemap + +-------- @describe p2 -------- +definition of var p2 []reflect.Value +value may point to these labels: + + +-------- @describe p3 -------- +definition of var p3 reflect.Value +this reflect.Value may contain these dynamic types: + *int, may point to: + reflection.a + +-------- @describe p4 -------- +definition of var p4 reflect.Type +this reflect.Type may contain these dynamic types: + *reflect.rtype, may point to: + *bool + *int + map[*int]*bool + diff --git a/pointer/TODO b/pointer/TODO index d5f12bc7..b7976f65 100644 --- a/pointer/TODO +++ b/pointer/TODO @@ -18,6 +18,7 @@ CONSTRAINT GENERATION: PRESOLVER OPTIMISATIONS - use HVN, HRU, LE, PE, HCD, LCD. + But: LE would lose the precise detail we currently enjoy in each label. SOLVER: - use BDDs and/or sparse bitvectors for ptsets diff --git a/pointer/analysis.go b/pointer/analysis.go index 36654f24..319d976c 100644 --- a/pointer/analysis.go +++ b/pointer/analysis.go @@ -4,7 +4,7 @@ package pointer -// This file defines the entry points into the pointer analysis. +// This file defines the main datatypes and Analyze function of the pointer analysis. import ( "fmt" @@ -13,9 +13,44 @@ import ( "os" "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/go/types/typemap" "code.google.com/p/go.tools/ssa" ) +// object.flags bitmask values. +const ( + otTagged = 1 << iota // type-tagged object + otIndirect // type-tagged object with indirect payload + otFunction // function object +) + +// An object represents a contiguous block of memory to which some +// (generalized) pointer may point. +// +// (Note: most variables called 'obj' are not *objects but nodeids +// such that a.nodes[obj].obj != nil.) +// +type object struct { + // flags is a bitset of the node type (ot*) flags defined above. + flags uint32 + + // Number of following nodes belonging to the same "object" + // allocation. Zero for all other nodes. + size uint32 + + // The SSA operation that caused this object to be allocated. + // May be nil for (e.g.) intrinsic allocations. + val ssa.Value + + // The call-graph node (=context) in which this object was allocated. + // May be nil for global objects: Global, Const, some Functions. + cgn *cgnode + + // If this is an rtype instance object, or a *rtype-tagged + // object, this is its type. + rtype types.Type +} + // nodeid denotes a node. // It is an index within analysis.nodes. // We use small integers, not *node pointers, for many reasons: @@ -24,42 +59,24 @@ import ( // - order matters; a field offset can be computed by simple addition. type nodeid uint32 -// node.flags bitmask values. -const ( - ntObject = 1 << iota // start of an object (addressable memory location) - ntInterface // conctype node of interface object (=> ntObject) - ntFunction // identity node of function object (=> ntObject) -) - // A node is an equivalence class of memory locations. // Nodes may be pointers, pointed-to locations, neither, or both. +// +// Nodes that are pointed-to locations ("labels") have an enclosing +// object (see analysis.enclosingObject). +// type node struct { - // flags is a bitset of the node type (nt*) flags defined above. - flags uint32 - - // Number of following words belonging to the same "object" allocation. - // (Set by endObject.) Zero for all other nodes. - size uint32 + // If non-nil, this node is the start of an object + // (addressable memory location). + // The following obj.size words implicitly belong to the object; + // they locate their object by scanning back. + obj *object // The type of the field denoted by this node. Non-aggregate, - // unless this is an iface.conctype node (i.e. the thing + // unless this is an tagged.T node (i.e. the thing // pointed to by an interface) in which case typ is that type. typ types.Type - // data holds additional attributes of this node, depending on - // its flags. - // - // If ntObject is set, data is the ssa.Value of the - // instruction that allocated this memory, or nil if it was - // implicit. - // - // Special cases: - // - If ntInterface is also set, data will be a *ssa.MakeInterface. - // - If ntFunction is also set, this node is the first word of a - // function block, and data is a *cgnode (not an ssa.Value) - // representing this function. - data interface{} - // subelement indicates which directly embedded subelement of // an object of aggregate type (struct, tuple, array) this is. subelement *fieldInfo // e.g. ".a.b[*].c" @@ -83,10 +100,9 @@ type node struct { type constraint interface { String() string - // Called by solver to prepare a constraint, e.g. to - // - initialize a points-to set (addrConstraint). - // - attach it to a pointer node (complex constraints). - init(a *analysis) + // For a complex constraint, returns the nodeid of the pointer + // to which it is attached. + ptr() nodeid // solve is called for complex constraints when the pts for // the node to which they are attached has changed. @@ -97,7 +113,7 @@ type constraint interface { // pts(dst) ⊇ {src} // A base constraint used to initialize the solver's pt sets type addrConstraint struct { - dst nodeid + dst nodeid // (ptr) src nodeid } @@ -105,7 +121,7 @@ type addrConstraint struct { // A simple constraint represented directly as a copyTo graph edge. type copyConstraint struct { dst nodeid - src nodeid + src nodeid // (ptr) } // dst = src[offset] @@ -113,14 +129,14 @@ type copyConstraint struct { type loadConstraint struct { offset uint32 dst nodeid - src nodeid + src nodeid // (ptr) } // dst[offset] = src // A complex constraint attached to dst (the pointer) type storeConstraint struct { offset uint32 - dst nodeid + dst nodeid // (ptr) src nodeid } @@ -129,7 +145,7 @@ type storeConstraint struct { type offsetAddrConstraint struct { offset uint32 dst nodeid - src nodeid + src nodeid // (ptr) } // dst = src.(typ) @@ -137,36 +153,66 @@ type offsetAddrConstraint struct { type typeAssertConstraint struct { typ types.Type dst nodeid - src nodeid + src nodeid // (ptr) } // src.method(params...) // A complex constraint attached to iface. type invokeConstraint struct { method *types.Func // the abstract method - iface nodeid // the interface + iface nodeid // (ptr) the interface params nodeid // the first parameter in the params/results block } // An analysis instance holds the state of a single pointer analysis problem. type analysis struct { - config *Config // the client's control/observer interface - prog *ssa.Program // the program being analyzed - log io.Writer // log stream; nil to disable - panicNode nodeid // sink for panic, source for recover - nodes []*node // indexed by nodeid - flattenMemo map[types.Type][]*fieldInfo // memoization of flatten() - constraints []constraint // set of constraints - callsites []*callsite // all callsites - genq []*cgnode // queue of functions to generate constraints for - intrinsics map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns - reflectValueObj types.Object // type symbol for reflect.Value (if present) - reflectRtypeObj types.Object // type symbol for reflect.rtype (if present) - reflectRtype *types.Pointer // *reflect.rtype - 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 - work worklist // solver's worklist + config *Config // the client's control/observer interface + prog *ssa.Program // the program being analyzed + log io.Writer // log stream; nil to disable + panicNode nodeid // sink for panic, source for recover + nodes []*node // indexed by nodeid + flattenMemo map[types.Type][]*fieldInfo // memoization of flatten() + constraints []constraint // set of constraints + callsites []*callsite // all callsites + 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 + work worklist // solver's worklist + + // Reflection: + hasher typemap.Hasher // cache of type hashes + reflectValueObj types.Object // type symbol for reflect.Value (if present) + reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present) + reflectRtype *types.Pointer // *reflect.rtype + rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T + reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value +} + +// enclosingObj returns the object (addressible memory object) that encloses node id. +// Panic ensues if that node does not belong to any object. +func (a *analysis) enclosingObj(id nodeid) *object { + // Find previous node with obj != nil. + for i := id; i >= 0; i-- { + n := a.nodes[i] + if obj := n.obj; obj != nil { + if i+nodeid(obj.size) <= id { + break // out of bounds + } + return obj + } + } + panic("node has no enclosing object") +} + +// labelFor returns the Label for node id. +// Panic ensues if that node is not addressable. +func (a *analysis) labelFor(id nodeid) *Label { + return &Label{ + obj: a.enclosingObj(id), + subelement: a.nodes[id].subelement, + } } func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) { @@ -189,6 +235,7 @@ func Analyze(config *Config) CallGraphNode { prog: config.prog(), valNode: 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), @@ -199,6 +246,13 @@ func Analyze(config *Config) CallGraphNode { a.reflectValueObj = reflect.Object.Scope().Lookup("Value") a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype") a.reflectRtype = 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}} + + a.rtypes.SetHasher(a.hasher) + a.reflectZeros.SetHasher(a.hasher) } if false { @@ -238,7 +292,7 @@ func Analyze(config *Config) CallGraphNode { Call := a.config.Call for _, site := range a.callsites { for nid := range a.nodes[site.targets].pts { - cgn := a.nodes[nid].data.(*cgnode) + cgn := a.nodes[nid].obj.cgn // Notify the client of the call graph, if // they're interested. diff --git a/pointer/api.go b/pointer/api.go index 4a8b9264..c11b908a 100644 --- a/pointer/api.go +++ b/pointer/api.go @@ -132,16 +132,20 @@ type PointsToSet interface { // argument points-to set contain common members. Intersects(PointsToSet) bool - // If this PointsToSet came from a Pointer of interface kind, - // ConcreteTypes returns the set of concrete types the - // interface may contain. + // If this PointsToSet came from a Pointer of interface kind + // or a reflect.Value, DynamicTypes returns the set of dynamic + // types that it may contain. (For an interface, they will + // always be concrete types.) // - // The result is a mapping whose keys are the concrete types - // to which this interface may point. For each pointer-like - // key type, the corresponding map value is a set of pointer - // abstractions of that concrete type, represented as a - // []Pointer slice. Use PointsToCombined to merge them. - ConcreteTypes() *typemap.M + // The result is a mapping whose keys are the dynamic types to + // which it may point. For each pointer-like key type, the + // corresponding map value is a set of pointer abstractions of + // that dynamic type, represented as a []Pointer slice. Use + // PointsToCombined to merge them. + // + // The result is empty unless CanHaveDynamicTypes(T). + // + DynamicTypes() *typemap.M } // Union returns the set containing all the elements of each set in sets. @@ -175,39 +179,24 @@ type ptset struct { func (s ptset) Labels() []*Label { var labels []*Label for l := range s.pts { - // Scan back to the previous object start. - for i := l; i >= 0; i-- { - n := s.a.nodes[i] - if n.flags&ntObject != 0 { - // TODO(adonovan): do bounds-check against n.size. - var v ssa.Value - if n.flags&ntFunction != 0 { - v = n.data.(*cgnode).fn - } else { - v = n.data.(ssa.Value) - // TODO(adonovan): what if v is nil? - } - labels = append(labels, &Label{ - Value: v, - subelement: s.a.nodes[l].subelement, - }) - break - } - } + labels = append(labels, s.a.labelFor(l)) } return labels } -func (s ptset) ConcreteTypes() *typemap.M { - var tmap typemap.M // default hasher // TODO(adonovan): opt: memoize per analysis +func (s ptset) DynamicTypes() *typemap.M { + var tmap typemap.M + tmap.SetHasher(s.a.hasher) for ifaceObjId := range s.pts { - if s.a.nodes[ifaceObjId].flags&ntInterface == 0 { - // ConcreteTypes called on non-interface PT set. - continue // shouldn't happen + tDyn, v, indirect := s.a.taggedValue(ifaceObjId) + if tDyn == nil { + continue // !CanHaveDynamicTypes(tDyn) } - v, tconc := s.a.interfaceValue(ifaceObjId) - prev, _ := tmap.At(tconc).([]Pointer) - tmap.Set(tconc, append(prev, ptr{s.a, v})) + if indirect { + panic("indirect tagged object") // implement later + } + prev, _ := tmap.At(tDyn).([]Pointer) + tmap.Set(tDyn, append(prev, ptr{s.a, v})) } return &tmap } @@ -242,6 +231,6 @@ func (p ptr) MayAlias(q Pointer) bool { return p.PointsTo().Intersects(q.PointsTo()) } -func (p ptr) ConcreteTypes() *typemap.M { - return p.PointsTo().ConcreteTypes() +func (p ptr) DynamicTypes() *typemap.M { + return p.PointsTo().DynamicTypes() } diff --git a/pointer/doc.go b/pointer/doc.go index 3d778442..a497cbad 100644 --- a/pointer/doc.go +++ b/pointer/doc.go @@ -61,10 +61,10 @@ during the solving phase. OBJECTS -An "object" is a contiguous sequence of nodes denoting an addressable -location: something that a pointer can point to. The first node of an -object has the ntObject flag, and its size indicates the extent of the -object. +Conceptually, an "object" is a contiguous sequence of nodes denoting +an addressable location: something that a pointer can point to. The +first node of an object has a non-nil obj field containing information +about the allocation: its size, context, and ssa.Value. Objects include: - functions and globals; @@ -78,7 +78,7 @@ Objects include: Many objects have no Go types. For example, the func, map and chan type kinds in Go are all varieties of pointers, but the respective objects are actual functions, maps, and channels. Given the way we -model interfaces, they too are pointers to interface objects with no +model interfaces, they too are pointers to tagged objects with no Go type. And an *ssa.Global denotes the address of a global variable, but the object for a Global is the actual data. So, types of objects are usually "off by one indirection". @@ -93,6 +93,27 @@ to have at least one node so that there is something to point to. nodes; check.) +TAGGED OBJECTS + +An tagged object has the following layout: + + T -- obj.flags ⊇ {otTagged} + v + ... + +The T node's typ field is the dynamic type of the "payload", the value +v which follows, flattened out. The T node's obj has the otTagged +flag. + +Tagged objects are needed when generalizing across types: interfaces, +reflect.Values, reflect.Types. Each of these three types is modelled +as a pointer that exclusively points to tagged objects. + +Tagged objects may be indirect (obj.flags ⊇ {otIndirect}) meaning that +the value v is not of type T but *T; this is used only for +reflect.Values that represent lvalues. + + ANALYSIS ABSTRACTION OF EACH TYPE Variables of the following "scalar" types may be represented by a @@ -161,7 +182,7 @@ Functions A function object has the following layout: - identity -- typ:*types.Signature; flags ⊇ {ntFunction}; data:*cgnode + identity -- typ:*types.Signature; obj.flags ⊇ {otFunction} params_0 -- (the receiver, if a method) ... params_n-1 @@ -172,8 +193,7 @@ Functions There may be multiple function objects for the same *ssa.Function due to context-sensitive treatment of some functions. - The first node is the function's identity node; its .data is the - callgraph node (*cgnode) this object represents. + The first node is the function's identity node. Associated with every callsite is a special "targets" variable, whose pts(·) contains the identity node of each function to which the call may dispatch. Identity words are not otherwise used. @@ -191,39 +211,33 @@ Functions for an *ssa.Function returns a singleton for that function. Interfaces - An expression of type 'interface{...}' is a kind of pointer that - points exclusively to interface objects. + An expression of type 'interface{...}' is a kind of pointer that + points exclusively to tagged objects. All tagged objects pointed to + by an interface are direct (the otIndirect flag is clear) and + concrete (the tag type T is not itself an interface type). The + associated ssa.Value for an interface's tagged objects may be an + *ssa.MakeInterface instruction, or nil if the tagged object was + created by an instrinsic (e.g. reflection). - An interface object has the following layout: + Constructing an interface value causes generation of constraints for + all of the concrete type's methods; we can't tell a priori which + ones may be called. - conctype -- flags ⊇ {ntInterface}; data:*ssa.MakeInterface? - value - ... + TypeAssert y = x.(T) is implemented by a dynamic filter triggered by + each tagged object E added to pts(x). If T is an interface that E.T + implements, E is added to pts(y). If T is a concrete type then edge + E.v -> pts(y) is added. - The conctype node's typ field is the concrete type of the interface - value which follows, flattened out. It has the ntInterface flag. - Its associated data is the originating MakeInterface instruction, if - any. + ChangeInterface is a simple copy because the representation of + tagged objects is independent of the interface type (in contrast + to the "method tables" approach used by the gc runtime). - Constructing an interface value causes generation of constraints for - all of the concrete type's methods; we can't tell a priori which ones - may be called. + y := Invoke x.m(...) is implemented by allocating a contiguous P/R + block for the callsite and adding a dynamic rule triggered by each + tagged object E added to pts(x). The rule adds param/results copy + edges to/from each discovered concrete method. - TypeAssert y = x.(T) is implemented by a dynamic filter triggered by - each interface object E added to pts(x). If T is an interface that - E.conctype implements, pts(y) gets E. If T is a concrete type then - edge pts(y) <- E.value is added. - - ChangeInterface is a simple copy because the representation of - interface objects is independent of the interface type (in contrast - to the "method tables" approach used by the gc runtime). - - y := Invoke x.m(...) is implemented by allocating a contiguous P/R - block for the callsite and adding a dynamic rule triggered by each - interface object E added to pts(x). The rule adds param/results copy - edges to/from each discovered concrete method. - - (Q. Why do we model an interface as a pointer to a pair of type and + (Q. Why do we model an interface as a pointer to a pair of type and value, rather than as a pair of a pointer to type and a pointer to value? A. Control-flow joins would merge interfaces ({T1}, {V1}) and ({T2}, @@ -231,6 +245,52 @@ Interfaces type-unsafe combination (T1,V2). Treating the value and its concrete type as inseparable makes the analysis type-safe.) +reflect.Value + A reflect.Value is modelled very similar to an interface{}, i.e. as + a pointer exclusively to tagged objects, but with two + generalizations. + + 1) a reflect.Value that represents an lvalue points to an indirect + (obj.flags ⊇ {otIndirect}) tagged object, which has a similar + layout to an tagged object except that the value is a pointer to + the dynamic type. Indirect tagged objects preserve the correct + aliasing so that mutations made by (reflect.Value).Set can be + observed. + + Indirect objects only arise when an lvalue is derived from an + rvalue by indirection, e.g. the following code: + + type S struct { X T } + var s S + var i interface{} = &s // i points to a *S-tagged object (from MakeInterface) + v1 := reflect.ValueOf(i) // v1 points to same *S-tagged object as i + v2 := v1.Elem() // v2 points to an indirect S-tagged object, pointing to s + v3 := v2.FieldByName("X") // v3 points to an indirect int-tagged object, pointing to s.X + v3.Set(y) // pts(s.X) ⊇ pts(y) + + Whether indirect or not, the concrete type of the tagged value + corresponds to the user-visible dynamic type, and the existence + of a pointer is an implementation detail. + + 2) The dynamic type tag of a tagged object pointed to by a + reflect.Value may be an interface type; it need not be concrete. + +reflect.Type + Just as in the real "reflect" library, we represent a reflect.Type + as an interface whose sole implementation is the concrete type, + *reflect.rtype. (This choice is forced on us by go/types: clients + cannot fabricate types with arbitrary method sets.) + + rtype instances are canonical: there is at most one per dynamic + type. (rtypes are in fact large structs but since identity is all + that matters, we represent them by a single node.) + + The payload of each *rtype-tagged object is an *rtype pointer that + points to exactly one such canonical rtype object. We exploit this + by setting the node.typ of the payload to the dynamic type, not + '*rtype'. This saves us an indirection in each resolution rule. As + an optimisation, *rtype-tagged objects are canonicalized too. + Aggregate types: @@ -333,7 +393,7 @@ FUNCTION CALLS For invoke-mode calls, we create a params/results block for the callsite and attach a dynamic closure rule to the interface. For - each new interface object that flows to the interface, we look up + each new tagged object that flows to the interface, we look up the concrete method, find its function object, and connect its P/R block to the callsite's P/R block. diff --git a/pointer/gen.go b/pointer/gen.go index 624252ad..0c448fa2 100644 --- a/pointer/gen.go +++ b/pointer/gen.go @@ -47,7 +47,7 @@ func (a *analysis) addNodes(typ types.Type, comment string) nodeid { // addOneNode creates a single node with type typ, and returns its id. // -// typ should generally be scalar (except for interface.conctype nodes +// typ should generally be scalar (except for tagged.T nodes // and struct/array identity nodes). Use addNodes for non-scalar types. // // comment explains the origin of the nodes, as a debugging aid. @@ -93,7 +93,7 @@ func (a *analysis) setValueNode(v ssa.Value, id nodeid) { // 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) { +func (a *analysis) endObject(obj nodeid, cgn *cgnode, val ssa.Value) *object { // Ensure object is non-empty by padding; // the pad will be the object node. size := uint32(a.nextNode() - obj) @@ -101,15 +101,17 @@ func (a *analysis) endObject(obj nodeid, data ssa.Value) { 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) - } + o := &object{ + size: size, // excludes padding + cgn: cgn, + val: val, } + objNode.obj = o + if val != nil && a.log != nil { + fmt.Fprintf(a.log, "\tobj[%s] = n%d\n", val, obj) + } + + return o } // makeFunctionObject creates and returns a new function object for @@ -123,6 +125,7 @@ func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid { // obj is the function object (identity, params, results). obj := a.nextNode() + cgn := &cgnode{fn: fn, obj: obj} sig := fn.Signature a.addOneNode(sig, "func.cgnode", nil) // (scalar with Signature type) if recv := sig.Recv(); recv != nil { @@ -130,16 +133,12 @@ func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid { } a.addNodes(sig.Params(), "func.params") a.addNodes(sig.Results(), "func.results") - a.endObject(obj, fn) + a.endObject(obj, cgn, fn).flags |= otFunction 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) @@ -181,7 +180,7 @@ func (a *analysis) makeGlobal(g *ssa.Global) nodeid { // The nodes representing the object itself. obj := a.nextNode() a.addNodes(mustDeref(g.Type()), "global") - a.endObject(obj, g) + a.endObject(obj, nil, g) if a.log != nil { fmt.Fprintf(a.log, "\t----\n") @@ -207,7 +206,7 @@ func (a *analysis) makeConstant(l *ssa.Const) nodeid { // 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.endObject(obj, nil, l) a.addressOf(id, obj) } @@ -215,6 +214,36 @@ func (a *analysis) makeConstant(l *ssa.Const) nodeid { 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! + a.addNodes(typ, "tagged.v") + a.endObject(obj, cgn, val).flags |= otTagged + return obj +} + +// makeRtype returns the canonical tagged object of type *rtype whose +// payload points to the sole rtype object for T. +func (a *analysis) makeRtype(T types.Type) nodeid { + if v := a.rtypes.At(T); v != nil { + return v.(nodeid) + } + + // Create the object for the reflect.rtype itself, which is + // ordinarily a large struct but here a single node will do. + obj := a.nextNode() + a.addOneNode(T, "reflect.rtype", nil) + a.endObject(obj, nil, nil).rtype = T + + id := a.makeTagged(a.reflectRtype, nil, nil) + a.nodes[id].obj.rtype = T + a.nodes[id+1].typ = T // trick (each *rtype tagged object is a singleton) + a.addressOf(id+1, obj) + + a.rtypes.Set(T, id) + 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. @@ -262,34 +291,36 @@ func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid { 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. +// taggedValue returns the dynamic type tag, the (first node of the) +// payload, and the indirect flag of the tagged object starting at id. +// It returns tDyn==nil if obj is not a tagged object. // -func (a *analysis) interfaceValue(id nodeid) (nodeid, types.Type) { +func (a *analysis) taggedValue(id nodeid) (tDyn types.Type, v nodeid, indirect bool) { n := a.nodes[id] - if n.flags&ntInterface == 0 { - panic(fmt.Sprintf("interfaceValue(n%d): not an interface object; typ=%s", id, n.typ)) + flags := n.obj.flags + if flags&otTagged != 0 { + return n.typ, id + 1, flags&otIndirect != 0 } - return id + 1, n.typ + return } // funcParams returns the first node of the params block of the -// function whose object node (flags&ntFunction) is id. +// function whose object node (obj.flags&otFunction) is id. // func (a *analysis) funcParams(id nodeid) nodeid { - if a.nodes[id].flags&ntFunction == 0 { + n := a.nodes[id] + if n.obj == nil || n.obj.flags&otFunction == 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. +// function whose object node (obj.flags&otFunction) is id. // func (a *analysis) funcResults(id nodeid) nodeid { n := a.nodes[id] - if n.flags&ntFunction == 0 { + if n.obj == nil || n.obj.flags&otFunction == 0 { panic(fmt.Sprintf("funcResults(n%d): not a function object block", id)) } sig := n.typ.(*types.Signature) @@ -423,7 +454,7 @@ func (a *analysis) copyElems(typ types.Type, dst, src nodeid) { // ---------- Constraint generation ---------- // genConv generates constraints for the conversion operation conv. -func (a *analysis) genConv(conv *ssa.Convert) { +func (a *analysis) genConv(conv *ssa.Convert, cgn *cgnode) { res := a.valueNode(conv) if res == 0 { return // result is non-pointerlike @@ -464,7 +495,7 @@ func (a *analysis) genConv(conv *ssa.Convert) { // 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.endObject(obj, cgn, conv) a.addressOf(res, obj) return } @@ -473,7 +504,7 @@ func (a *analysis) genConv(conv *ssa.Convert) { // string -> []byte/[]rune (or named aliases)? if utSrc.Info()&types.IsString != 0 { obj := a.addNodes(sliceToArray(tDst), "convert") - a.endObject(obj, conv) + a.endObject(obj, cgn, conv) a.addressOf(res, obj) return } @@ -505,7 +536,7 @@ func (a *analysis) genConv(conv *ssa.Convert) { } // genAppend generates constraints for a call to append. -func (a *analysis) genAppend(instr *ssa.Call) { +func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { // 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: @@ -530,19 +561,19 @@ func (a *analysis) genAppend(instr *ssa.Call) { var w nodeid w = a.nextNode() a.addNodes(tArray, "append") - a.endObject(w, instr) + a.endObject(w, cgn, 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) { +func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) { 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)) + a.genAppend(instr.(*ssa.Call), cgn) case "copy": tElem := call.Args[0].Type().Underlying().(*types.Slice).Elem() @@ -691,6 +722,12 @@ func (a *analysis) genDynamicCall(call *ssa.CallCommon, result nodeid) nodeid { func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid { sig := call.Signature() + // TODO(adonovan): optimise this into a static call when there + // can be at most one type that implements the interface (due + // to unexported methods). This is particularly important for + // methods of interface reflect.Type (sole impl: + // *reflect.rtype), so we can realize context sensitivity. + // Allocate a contiguous targets/params/results block for this call. block := a.nextNode() targets := a.addOneNode(sig, "invoke.targets", nil) @@ -722,7 +759,7 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { // Intrinsic implementations of built-in functions. if _, ok := call.Value.(*ssa.Builtin); ok { - a.genBuiltinCall(instr) + a.genBuiltinCall(instr, caller) return } @@ -793,7 +830,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.Convert: - a.genConv(instr) + a.genConv(instr, cgn) case *ssa.Extract: a.copy(a.valueNode(instr), @@ -846,19 +883,19 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { case *ssa.Alloc: obj := a.nextNode() a.addNodes(mustDeref(instr.Type()), "alloc") - a.endObject(obj, instr) + 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, instr) + 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, instr) + a.endObject(obj, cgn, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeMap: @@ -866,7 +903,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { tmap := instr.Type().Underlying().(*types.Map) a.addNodes(tmap.Key(), "makemap.key") a.addNodes(tmap.Elem(), "makemap.value") - a.endObject(obj, instr) + a.endObject(obj, cgn, instr) a.addressOf(a.valueNode(instr), obj) case *ssa.MakeInterface: @@ -877,13 +914,12 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { 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 + + obj := a.makeTagged(tConc, cgn, instr) + // Copy the value into it, if nontrivial. if x := a.valueNode(instr.X); x != 0 { - a.copy(vnode, x, a.sizeof(tConc)) + a.copy(obj+1, x, a.sizeof(tConc)) } a.addressOf(a.valueNode(instr), obj) @@ -995,13 +1031,25 @@ func (a *analysis) genRootCalls() *cgnode { // genFunc generates constraints for function fn. func (a *analysis) genFunc(cgn *cgnode) { fn := cgn.fn + + impl := a.findIntrinsic(fn) + if a.log != nil { fmt.Fprintln(a.log) fmt.Fprintln(a.log) - cgn.fn.DumpTo(a.log) + + // Hack: don't display body if intrinsic. + if impl != nil { + fn2 := *cgn.fn // copy + fn2.Locals = nil + fn2.Blocks = nil + fn2.DumpTo(a.log) + } else { + cgn.fn.DumpTo(a.log) + } } - if impl := a.findIntrinsic(fn); impl != nil { + if impl != nil { impl(a, cgn) return } @@ -1068,6 +1116,14 @@ func (a *analysis) generate() *cgnode { // Create the global node for panic values. a.panicNode = a.addNodes(tEface, "panic") + // Create nodes and constraints for all methods of reflect.rtype. + if rtype := a.reflectRtype; rtype != nil { + mset := rtype.MethodSet() + for i, n := 0, mset.Len(); i < n; i++ { + a.valueNode(a.prog.Method(mset.At(i))) + } + } + root := a.genRootCalls() // Generate constraints for entire program. diff --git a/pointer/intrinsics.go b/pointer/intrinsics.go index f5f84b50..b59d3303 100644 --- a/pointer/intrinsics.go +++ b/pointer/intrinsics.go @@ -33,36 +33,36 @@ func init() { // categories [Nd]. intrinsicsByName = map[string]intrinsic{ // reflect.Value methods. - // "(reflect.Value).Addr": ext۰reflect۰Value۰Addr, - "(reflect.Value).Bool": ext۰NoEffect, - // "(reflect.Value).Bytes": ext۰reflect۰Value۰Bytes, - // "(reflect.Value).Call": ext۰reflect۰Value۰Call, - // "(reflect.Value).CallSlice": ext۰reflect۰Value۰CallSlice, - "(reflect.Value).CanAddr": ext۰NoEffect, - "(reflect.Value).CanInterface": ext۰NoEffect, - "(reflect.Value).CanSet": ext۰NoEffect, - "(reflect.Value).Cap": ext۰NoEffect, - "(reflect.Value).Close": ext۰NoEffect, - "(reflect.Value).Complex": ext۰NoEffect, - // "(reflect.Value).Convert": ext۰reflect۰Value۰Convert, - // "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, - // "(reflect.Value).Field": ext۰reflect۰Value۰Field, - // "(reflect.Value).FieldByIndex": ext۰reflect۰Value۰FieldByIndex, - // "(reflect.Value).FieldByName": ext۰reflect۰Value۰FieldByName, - // "(reflect.Value).FieldByNameFunc": ext۰reflect۰Value۰FieldByNameFunc, - "(reflect.Value).Float": ext۰NoEffect, - // "(reflect.Value).Index": ext۰reflect۰Value۰Index, - "(reflect.Value).Int": ext۰NoEffect, - // "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, - "(reflect.Value).InterfaceData": ext۰NoEffect, - "(reflect.Value).IsNil": ext۰NoEffect, - "(reflect.Value).IsValid": ext۰NoEffect, - "(reflect.Value).Kind": ext۰NoEffect, - "(reflect.Value).Len": ext۰NoEffect, - // "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, - // "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, - // "(reflect.Value).Method": ext۰reflect۰Value۰Method, - // "(reflect.Value).MethodByName": ext۰reflect۰Value۰MethodByName, + "(reflect.Value).Addr": ext۰reflect۰Value۰Addr, + "(reflect.Value).Bool": ext۰NoEffect, + "(reflect.Value).Bytes": ext۰reflect۰Value۰Bytes, + "(reflect.Value).Call": ext۰reflect۰Value۰Call, + "(reflect.Value).CallSlice": ext۰reflect۰Value۰CallSlice, + "(reflect.Value).CanAddr": ext۰NoEffect, + "(reflect.Value).CanInterface": ext۰NoEffect, + "(reflect.Value).CanSet": ext۰NoEffect, + "(reflect.Value).Cap": ext۰NoEffect, + "(reflect.Value).Close": ext۰NoEffect, + "(reflect.Value).Complex": ext۰NoEffect, + "(reflect.Value).Convert": ext۰reflect۰Value۰Convert, + "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, + "(reflect.Value).Field": ext۰reflect۰Value۰Field, + "(reflect.Value).FieldByIndex": ext۰reflect۰Value۰FieldByIndex, + "(reflect.Value).FieldByName": ext۰reflect۰Value۰FieldByName, + "(reflect.Value).FieldByNameFunc": ext۰reflect۰Value۰FieldByNameFunc, + "(reflect.Value).Float": ext۰NoEffect, + "(reflect.Value).Index": ext۰reflect۰Value۰Index, + "(reflect.Value).Int": ext۰NoEffect, + "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, + "(reflect.Value).InterfaceData": ext۰NoEffect, + "(reflect.Value).IsNil": ext۰NoEffect, + "(reflect.Value).IsValid": ext۰NoEffect, + "(reflect.Value).Kind": ext۰NoEffect, + "(reflect.Value).Len": ext۰NoEffect, + "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, + "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, + "(reflect.Value).Method": ext۰reflect۰Value۰Method, + "(reflect.Value).MethodByName": ext۰reflect۰Value۰MethodByName, "(reflect.Value).NumField": ext۰NoEffect, "(reflect.Value).NumMethod": ext۰NoEffect, "(reflect.Value).OverflowComplex": ext۰NoEffect, @@ -70,74 +70,74 @@ func init() { "(reflect.Value).OverflowInt": ext۰NoEffect, "(reflect.Value).OverflowUint": ext۰NoEffect, "(reflect.Value).Pointer": ext۰NoEffect, - // "(reflect.Value).Set": ext۰reflect۰Value۰Set, - "(reflect.Value).SetBool": ext۰NoEffect, - // "(reflect.Value).SetBytes": ext۰reflect۰Value۰SetBytes, - "(reflect.Value).SetComplex": ext۰NoEffect, - "(reflect.Value).SetFloat": ext۰NoEffect, - "(reflect.Value).SetInt": ext۰NoEffect, - "(reflect.Value).SetLen": ext۰NoEffect, - // "(reflect.Value).SetMapIndex": ext۰reflect۰Value۰SetMapIndex, - // "(reflect.Value).SetPointer": ext۰reflect۰Value۰SetPointer, - "(reflect.Value).SetString": ext۰NoEffect, - "(reflect.Value).SetUint": ext۰NoEffect, - // "(reflect.Value).Slice": ext۰reflect۰Value۰Slice, - "(reflect.Value).String": ext۰NoEffect, - "(reflect.Value).Type": ext۰NoEffect, - "(reflect.Value).Uint": ext۰NoEffect, - "(reflect.Value).UnsafeAddr": ext۰NoEffect, + "(reflect.Value).Set": ext۰reflect۰Value۰Set, + "(reflect.Value).SetBool": ext۰NoEffect, + "(reflect.Value).SetBytes": ext۰reflect۰Value۰SetBytes, + "(reflect.Value).SetComplex": ext۰NoEffect, + "(reflect.Value).SetFloat": ext۰NoEffect, + "(reflect.Value).SetInt": ext۰NoEffect, + "(reflect.Value).SetLen": ext۰NoEffect, + "(reflect.Value).SetMapIndex": ext۰reflect۰Value۰SetMapIndex, + "(reflect.Value).SetPointer": ext۰reflect۰Value۰SetPointer, + "(reflect.Value).SetString": ext۰NoEffect, + "(reflect.Value).SetUint": ext۰NoEffect, + "(reflect.Value).Slice": ext۰reflect۰Value۰Slice, + "(reflect.Value).String": ext۰NoEffect, + "(reflect.Value).Type": ext۰NoEffect, + "(reflect.Value).Uint": ext۰NoEffect, + "(reflect.Value).UnsafeAddr": ext۰NoEffect, // Standalone reflect.* functions. - "reflect.Append": ext۰NotYetImplemented, - "reflect.AppendSlice": ext۰NotYetImplemented, - "reflect.Copy": ext۰NotYetImplemented, - // "reflect.ChanOf": ext۰reflect۰ChanOf, - "reflect.DeepEqual": ext۰NoEffect, - // "reflect.Indirect": ext۰reflect۰Indirect, - // "reflect.MakeChan": ext۰reflect۰MakeChan, - "reflect.MakeFunc": ext۰NotYetImplemented, - "reflect.MakeMap": ext۰NotYetImplemented, - "reflect.MakeSlice": ext۰NotYetImplemented, - "reflect.MapOf": ext۰NotYetImplemented, - // "reflect.New": ext۰reflect۰New, - // "reflect.NewAt": ext۰reflect۰NewAt, - "reflect.PtrTo": ext۰NotYetImplemented, - "reflect.Select": ext۰NotYetImplemented, - "reflect.SliceOf": ext۰NotYetImplemented, - // "reflect.TypeOf": ext۰reflect۰TypeOf, - // "reflect.ValueOf": ext۰reflect۰ValueOf, - // "reflect.Zero": ext۰reflect۰Zero, - "reflect.init": ext۰NoEffect, + "reflect.Append": ext۰reflect۰Append, + "reflect.AppendSlice": ext۰reflect۰AppendSlice, + "reflect.Copy": ext۰reflect۰Copy, + "reflect.ChanOf": ext۰reflect۰ChanOf, + "reflect.DeepEqual": ext۰NoEffect, + "reflect.Indirect": ext۰reflect۰Indirect, + "reflect.MakeChan": ext۰reflect۰MakeChan, + "reflect.MakeFunc": ext۰reflect۰MakeFunc, + "reflect.MakeMap": ext۰reflect۰MakeMap, + "reflect.MakeSlice": ext۰reflect۰MakeSlice, + "reflect.MapOf": ext۰reflect۰MapOf, + "reflect.New": ext۰reflect۰New, + "reflect.NewAt": ext۰reflect۰NewAt, + "reflect.PtrTo": ext۰reflect۰PtrTo, + "reflect.Select": ext۰reflect۰Select, + "reflect.SliceOf": ext۰reflect۰SliceOf, + "reflect.TypeOf": ext۰reflect۰TypeOf, + "reflect.ValueOf": ext۰reflect۰ValueOf, + "reflect.Zero": ext۰reflect۰Zero, + "reflect.init": ext۰NoEffect, // *reflect.rtype methods - "(*reflect.rtype).Align": ext۰NoEffect, - "(*reflect.rtype).AssignableTo": ext۰NoEffect, - "(*reflect.rtype).Bits": ext۰NoEffect, - "(*reflect.rtype).ChanDir": ext۰NoEffect, - "(*reflect.rtype).ConvertibleTo": ext۰NoEffect, - // "(*reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, - "(*reflect.rtype).Field": ext۰NotYetImplemented, - "(*reflect.rtype).FieldAlign": ext۰NoEffect, - // "(*reflect.rtype).FieldByIndex": ext۰reflect۰rtype۰FieldByIndex, - // "(*reflect.rtype).FieldByName": ext۰reflect۰rtype۰FieldByName, - // "(*reflect.rtype).FieldByNameFunc": ext۰reflect۰rtype۰FieldByNameFunc, - "(*reflect.rtype).Implements": ext۰NoEffect, - // "(*reflect.rtype).In": ext۰reflect۰rtype۰In, - "(*reflect.rtype).IsVariadic": ext۰NoEffect, - // "(*reflect.rtype).Key": ext۰reflect۰rtype۰Key, - "(*reflect.rtype).Kind": ext۰NoEffect, - "(*reflect.rtype).Len": ext۰NoEffect, - "(*reflect.rtype).Method": ext۰NotYetImplemented, - "(*reflect.rtype).MethodByName": ext۰NotYetImplemented, - "(*reflect.rtype).Name": ext۰NoEffect, - "(*reflect.rtype).NumField": ext۰NoEffect, - "(*reflect.rtype).NumIn": ext۰NoEffect, - "(*reflect.rtype).NumMethod": ext۰NoEffect, - "(*reflect.rtype).NumOut": ext۰NoEffect, - // "(*reflect.rtype).Out": ext۰reflect۰rtype۰Out, - "(*reflect.rtype).PkgPath": ext۰NoEffect, - "(*reflect.rtype).Size": ext۰NoEffect, - "(*reflect.rtype).String": ext۰NoEffect, + "(*reflect.rtype).Align": ext۰NoEffect, + "(*reflect.rtype).AssignableTo": ext۰NoEffect, + "(*reflect.rtype).Bits": ext۰NoEffect, + "(*reflect.rtype).ChanDir": ext۰NoEffect, + "(*reflect.rtype).ConvertibleTo": ext۰NoEffect, + "(*reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, + "(*reflect.rtype).Field": ext۰reflect۰rtype۰Field, + "(*reflect.rtype).FieldAlign": ext۰NoEffect, + "(*reflect.rtype).FieldByIndex": ext۰reflect۰rtype۰FieldByIndex, + "(*reflect.rtype).FieldByName": ext۰reflect۰rtype۰FieldByName, + "(*reflect.rtype).FieldByNameFunc": ext۰reflect۰rtype۰FieldByNameFunc, + "(*reflect.rtype).Implements": ext۰NoEffect, + "(*reflect.rtype).In": ext۰reflect۰rtype۰In, + "(*reflect.rtype).IsVariadic": ext۰NoEffect, + "(*reflect.rtype).Key": ext۰reflect۰rtype۰Key, + "(*reflect.rtype).Kind": ext۰NoEffect, + "(*reflect.rtype).Len": ext۰NoEffect, + "(*reflect.rtype).Method": ext۰reflect۰rtype۰Method, + "(*reflect.rtype).MethodByName": ext۰reflect۰rtype۰MethodByName, + "(*reflect.rtype).Name": ext۰NoEffect, + "(*reflect.rtype).NumField": ext۰NoEffect, + "(*reflect.rtype).NumIn": ext۰NoEffect, + "(*reflect.rtype).NumMethod": ext۰NoEffect, + "(*reflect.rtype).NumOut": ext۰NoEffect, + "(*reflect.rtype).Out": ext۰reflect۰rtype۰Out, + "(*reflect.rtype).PkgPath": ext۰NoEffect, + "(*reflect.rtype).Size": ext۰NoEffect, + "(*reflect.rtype).String": ext۰NoEffect, // Other packages. "bytes.Equal": ext۰NoEffect, @@ -305,9 +305,3 @@ func ext۰NotYetImplemented(a *analysis, cgn *cgnode) { // enough that it's not unbearably annoying. // a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn) } - -// We'll model reflect.Value as an interface{} containing pointers. -// We must use pointers since some reflect.Values (those derived by -// Field, Elem, etc) are abstractions of lvalues, not rvalues, and -// mutations via Set are reflected in the underlying value. (We could -// represent it as a union of lvalue and rvalue but that's more work.) diff --git a/pointer/labels.go b/pointer/labels.go index fec7a039..6f33f1fa 100644 --- a/pointer/labels.go +++ b/pointer/labels.go @@ -9,32 +9,99 @@ import ( "go/token" "strings" + "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/ssa" ) -// A Label is an abstract location or an instruction that allocates memory. -// A points-to set is (conceptually) a set of labels. +// A Label is an entity that may be pointed to by a pointer, map, +// channel, 'func', slice or interface. Labels include: // -// (This is basically a pair of a Value that allocates an object and a -// subelement indicator within that object.) +// Labels include: +// - functions +// - globals +// - tagged objects, representing interfaces and reflect.Values +// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s)) +// - stack- and heap-allocated variables (including composite literals) +// - channels, maps and arrays created by make() +// - instrinsic or reflective operations that allocate (e.g. append, reflect.New) +// - and their subelements, e.g. "alloc.y[*].z" // -// TODO(adonovan): local labels should include their context (CallGraphNode). +// Labels are so varied that they defy good generalizations; +// some have no value, no callgraph node, or no position. +// Many objects have types that are inexpressible in Go: +// maps, channels, functions, tagged objects. // type Label struct { - Value ssa.Value - subelement *fieldInfo // e.g. ".a.b[*].c" + obj *object // the addressable memory location containing this label + subelement *fieldInfo // subelement path within obj, e.g. ".a.b[*].c" } -func (l *Label) Pos() token.Pos { - if l.Value != nil { - return l.Value.Pos() +// Value returns the ssa.Value that allocated this label's object, +// or nil if it was allocated by an intrinsic. +// +func (l Label) Value() ssa.Value { + return l.obj.val +} + +// Context returns the analytic context in which this label's object was allocated, +// or nil for global objects: global, const, and shared contours for functions. +// +func (l Label) Context() CallGraphNode { + return l.obj.cgn +} + +// Path returns the path to the subelement of the object containing +// this label. For example, ".x[*].y". +// +func (l Label) Path() string { + return l.subelement.path() +} + +// Pos returns the position of this label, if known, zero otherwise. +func (l Label) Pos() token.Pos { + if v := l.Value(); v != nil { + return v.Pos() + } + if l.obj.rtype != nil { + if nt, ok := deref(l.obj.rtype).(*types.Named); ok { + return nt.Obj().Pos() + } + } + if cgn := l.obj.cgn; cgn != nil { + return cgn.Func().Pos() } return token.NoPos } -func (l *Label) String() string { +// String returns the printed form of this label. +// +// Examples: Object type: +// (sync.Mutex).Lock (a function) +// "foo":[]byte (a slice constant) +// makemap (map allocated via make) +// makechan (channel allocated via make) +// makeinterface (tagged object allocated by makeinterface) +// (allocation in instrinsic) +// sync.Mutex (a reflect.rtype instance) +// +// Labels within compound objects have subelement paths: +// x.y[*].z (a struct variable, x) +// append.y[*].z (array allocated by append) +// makeslice.y[*].z (array allocated via make) +// +func (l Label) String() string { var s string - switch v := l.Value.(type) { + switch v := l.obj.val.(type) { + case nil: + if l.obj.rtype != nil { + return l.obj.rtype.String() + } + if l.obj.cgn != nil { + // allocation by intrinsic or reflective operation + return fmt.Sprintf("", l.obj.cgn.Func()) + } + return "" // should be unreachable + case *ssa.Function, *ssa.Global: s = v.String() @@ -59,12 +126,12 @@ func (l *Label) String() string { case *ssa.MakeInterface: // MakeInterface is usually implicit in Go source (so - // Pos()==0), and interfaces objects may be allocated + // Pos()==0), and tagged objects may be allocated // synthetically (so no *MakeInterface data). s = "makeinterface:" + v.X.Type().String() default: - panic(fmt.Sprintf("unhandled Label.Value type: %T", v)) + panic(fmt.Sprintf("unhandled Label.val type: %T", v)) } return s + l.subelement.path() diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go index 575f5456..6f062428 100644 --- a/pointer/pointer_test.go +++ b/pointer/pointer_test.go @@ -48,6 +48,7 @@ var inputs = []string{ "testdata/recur.go", "testdata/structs.go", "testdata/a_test.go", + "testdata/mapreflect.go", // TODO(adonovan): get these tests (of reflection) passing. // (The tests are mostly sound since they were used for a @@ -57,7 +58,6 @@ var inputs = []string{ // "testdata/chanreflect.go", // "testdata/finalizer.go", // "testdata/reflect.go", - // "testdata/mapreflect.go", // "testdata/structreflect.go", } @@ -86,21 +86,22 @@ var inputs = []string{ // // From a theoretical perspective, concrete types in interfaces are // labels too, but they are represented differently and so have a -// different expectation, @concrete, below. +// different expectation, @types, below. // -// @concrete t | u | v +// @types t | u | v // -// A 'concrete' expectation asserts that the set of possible dynamic +// A 'types' expectation asserts that the set of possible dynamic // types of its interface operand is exactly {t,u,v}, notated per // go/types.Type.String(). In other words, it asserts that the type // component of the interface may point to that set of concrete type -// literals. +// literals. It also works for reflect.Value, though the types +// needn't be concrete in that case. // -// A 'concrete' expectation must appear on the same line as a +// A 'types' expectation must appear on the same line as a // print(x) statement; the expectation's operand is x. // // If one of the strings is "...", the expectation asserts that the -// interface's type may point to at least the other concrete types. +// interface's type may point to at least the other types. // // We use '|' because type names may contain spaces. // @@ -119,11 +120,11 @@ var inputs = []string{ // (NB, anon functions still include line numbers.) // type expectation struct { - kind string // "pointsto" | "concrete" | "calls" | "warning" + kind string // "pointsto" | "types" | "calls" | "warning" filename string linenum int // source line number, 1-based args []string - types []types.Type // for concrete + types []types.Type // for types } func (e *expectation) String() string { @@ -137,7 +138,7 @@ func (e *expectation) errorf(format string, args ...interface{}) { } func (e *expectation) needsProbe() bool { - return e.kind == "pointsto" || e.kind == "concrete" + return e.kind == "pointsto" || e.kind == "types" } // A record of a call to the built-in print() function. Used for testing. @@ -228,7 +229,7 @@ func doOneInput(input, filename string) bool { case "pointsto": e.args = split(rest, "|") - case "concrete": + case "types": for _, typstr := range split(rest, "|") { var t types.Type = types.Typ[types.Invalid] // means "..." if typstr != "..." { @@ -239,10 +240,11 @@ func doOneInput(input, filename string) bool { e.errorf("'%s' is not a valid type", typstr) continue } - t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainpkg.Object.Scope()) + mainFileScope := mainpkg.Object.Scope().Child(0) + t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainFileScope) if err != nil { ok = false - // TODO Don't print err since its location is bad. + // Don't print err since its location is bad. e.errorf("'%s' is not a valid type: %s", typstr, err) continue } @@ -332,8 +334,8 @@ func doOneInput(input, filename string) bool { ok = false } - case "concrete": - if !checkConcreteExpectation(e, pr) { + case "types": + if !checkTypesExpectation(e, pr) { ok = false } @@ -357,16 +359,18 @@ func doOneInput(input, filename string) bool { } func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string { - // Functions and Globals need no pos suffix. - switch l.Value.(type) { - case *ssa.Function, *ssa.Global: + // Functions and Globals need no pos suffix, + // nor do allocations in intrinsic operations + // (for which we'll print the function name). + switch l.Value().(type) { + case nil, *ssa.Function, *ssa.Global: return l.String() } str := l.String() - if pos := l.Value.Pos(); pos != 0 { + if pos := l.Pos(); pos != token.NoPos { // Append the position, using a @line tag instead of a line number, if defined. - posn := prog.Fset.Position(l.Value.Pos()) + posn := prog.Fset.Position(pos) s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line) if tag, ok := lineMapping[s]; ok { return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column) @@ -421,7 +425,7 @@ func underlyingType(typ types.Type) types.Type { return typ } -func checkConcreteExpectation(e *expectation, pr *probe) bool { +func checkTypesExpectation(e *expectation, pr *probe) bool { var expected typemap.M var surplus typemap.M exact := true @@ -433,32 +437,29 @@ func checkConcreteExpectation(e *expectation, pr *probe) bool { expected.Set(g, struct{}{}) } - switch t := underlyingType(pr.instr.Args[0].Type()).(type) { - case *types.Interface: - // ok - default: - e.errorf("@concrete expectation requires an interface-typed operand, got %s", t) + if t := pr.instr.Args[0].Type(); !pointer.CanHaveDynamicTypes(t) { + e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", t) return false } - // Find the set of concrete types that the probe's + // Find the set of types that the probe's // argument (x in print(x)) may contain. - for _, conc := range pr.arg0.PointsTo().ConcreteTypes().Keys() { - if expected.At(conc) != nil { - expected.Delete(conc) + for _, T := range pr.arg0.PointsTo().DynamicTypes().Keys() { + if expected.At(T) != nil { + expected.Delete(T) } else if exact { - surplus.Set(conc, struct{}{}) + surplus.Set(T, struct{}{}) } } // Report set difference: ok := true if expected.Len() > 0 { ok = false - e.errorf("interface cannot contain these concrete types: %s", expected.KeysString()) + e.errorf("interface cannot contain these types: %s", expected.KeysString()) } if surplus.Len() > 0 { ok = false - e.errorf("interface may additionally contain these concrete types: %s", surplus.KeysString()) + e.errorf("interface may additionally contain these types: %s", surplus.KeysString()) } return ok return false diff --git a/pointer/reflect.go b/pointer/reflect.go new file mode 100644 index 00000000..59b51a3a --- /dev/null +++ b/pointer/reflect.go @@ -0,0 +1,470 @@ +package pointer + +// This file implements the generation and resolution rules for +// constraints arising from the use of reflection in the target +// program. See doc.go for explanation of the representation. +// +// TODO(adonovan): fix: most of the reflect API permits implicit +// conversions due to assignability, e.g. m.MapIndex(k) is ok if T(k) +// is assignable to T(M).key. It's not yet clear how best to model +// that. +// +// To avoid proliferation of equivalent labels, instrinsics should +// memoize as much as possible, like TypeOf and Zero do for their +// tagged objects. +// +// TODO(adonovan): all {} functions are TODO. + +import ( + "fmt" + + "code.google.com/p/go.tools/go/types" +) + +// -------------------- (reflect.Value) -------------------- + +func ext۰reflect۰Value۰Addr(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Bytes(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Call(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰CallSlice(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Convert(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Elem(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Field(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰FieldByIndex(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰FieldByName(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰FieldByNameFunc(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Index(a *analysis, cgn *cgnode) {} + +// ---------- func (Value).Interface() Value ---------- + +// result = rv.Interface() +type rVInterfaceConstraint struct { + rv nodeid // (ptr) + result nodeid +} + +func (c *rVInterfaceConstraint) String() string { + return fmt.Sprintf("n%d = reflect n%d.Interface()", c.result, c.rv) +} + +func (c *rVInterfaceConstraint) ptr() nodeid { + return c.rv +} + +func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) { + resultPts := &a.nodes[c.result].pts + changed := false + for obj := range delta { + tDyn, _, indirect := a.taggedValue(obj) + if tDyn == nil { + panic("not a tagged object") + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } + + if resultPts.add(obj) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰Value۰Interface(a *analysis, cgn *cgnode) { + a.addConstraint(&rVInterfaceConstraint{ + rv: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +// ---------- func (Value).MapIndex(Value) Value ---------- + +// result = rv.MapIndex(key) +type rVMapIndexConstraint struct { + cgn *cgnode + rv nodeid // (ptr) + result nodeid +} + +func (c *rVMapIndexConstraint) String() string { + return fmt.Sprintf("n%d = reflect n%d.MapIndex(_)", c.result, c.rv) +} + +func (c *rVMapIndexConstraint) ptr() nodeid { + return c.rv +} + +func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + tDyn, m, indirect := a.taggedValue(obj) + tMap, _ := tDyn.(*types.Map) + if tMap == nil { + continue // not a map + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } + + vObj := a.makeTagged(tMap.Elem(), c.cgn, nil) + a.loadOffset(vObj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem())) + if a.nodes[c.result].pts.add(vObj) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰Value۰MapIndex(a *analysis, cgn *cgnode) { + a.addConstraint(&rVMapIndexConstraint{ + cgn: cgn, + rv: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +// ---------- func (Value).MapKeys() []Value ---------- + +// result = rv.MapKeys() +type rVMapKeysConstraint struct { + cgn *cgnode + rv nodeid // (ptr) + result nodeid +} + +func (c *rVMapKeysConstraint) String() string { + return fmt.Sprintf("n%d = reflect n%d.MapKeys()", c.result, c.rv) +} + +func (c *rVMapKeysConstraint) ptr() nodeid { + return c.rv +} + +func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + tDyn, m, indirect := a.taggedValue(obj) + tMap, _ := tDyn.(*types.Map) + if tMap == nil { + continue // not a map + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } + + kObj := a.makeTagged(tMap.Key(), c.cgn, nil) + a.load(kObj+1, m, a.sizeof(tMap.Key())) + if a.nodes[c.result].pts.add(kObj) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) { + // Allocate an array for the result. + obj := a.nextNode() + a.addNodes(types.NewArray(a.reflectValueObj.Type(), 1), "reflect.MapKeys result") + a.endObject(obj, cgn, nil) + a.addressOf(a.funcResults(cgn.obj), obj) + + // resolution rule attached to rv + a.addConstraint(&rVMapKeysConstraint{ + cgn: cgn, + rv: a.funcParams(cgn.obj), + result: obj + 1, // result is stored in array elems + }) +} + +func ext۰reflect۰Value۰Method(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰MethodByName(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Set(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰SetBytes(a *analysis, cgn *cgnode) {} + +// ---------- func (Value).SetMapIndex(k Value, v Value) ---------- + +// rv.SetMapIndex(k, v) +type rVSetMapIndexConstraint struct { + cgn *cgnode + rv nodeid // (ptr) + k nodeid + v nodeid +} + +func (c *rVSetMapIndexConstraint) String() string { + return fmt.Sprintf("reflect n%d.SetMapIndex(n%d, n%d)", c.rv, c.k, c.v) +} + +func (c *rVSetMapIndexConstraint) ptr() nodeid { + return c.rv +} + +func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { + for obj := range delta { + tDyn, m, indirect := a.taggedValue(obj) + tMap, _ := tDyn.(*types.Map) + if tMap == nil { + continue // not a map + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } + + ksize := a.sizeof(tMap.Key()) + + // Extract k Value's payload to ktmp, then store to map key. + ktmp := a.addNodes(tMap.Key(), "SetMapIndex.ktmp") + a.addConstraint(&typeAssertConstraint{tMap.Key(), ktmp, c.k}) + a.store(m, ktmp, ksize) + + // Extract v Value's payload to vtmp, then store to map value. + vtmp := a.addNodes(tMap.Elem(), "SetMapIndex.vtmp") + a.addConstraint(&typeAssertConstraint{tMap.Elem(), vtmp, c.v}) + a.storeOffset(m, vtmp, ksize, a.sizeof(tMap.Elem())) + } +} + +func ext۰reflect۰Value۰SetMapIndex(a *analysis, cgn *cgnode) { + // resolution rule attached to rv + rv := a.funcParams(cgn.obj) + a.addConstraint(&rVSetMapIndexConstraint{ + cgn: cgn, + rv: rv, + k: rv + 1, + v: rv + 2, + }) +} + +func ext۰reflect۰Value۰SetPointer(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Value۰Slice(a *analysis, cgn *cgnode) {} + +// -------------------- Standalone reflect functions -------------------- + +func ext۰reflect۰Append(a *analysis, cgn *cgnode) {} +func ext۰reflect۰AppendSlice(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Copy(a *analysis, cgn *cgnode) {} +func ext۰reflect۰ChanOf(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Indirect(a *analysis, cgn *cgnode) {} +func ext۰reflect۰MakeChan(a *analysis, cgn *cgnode) {} +func ext۰reflect۰MakeFunc(a *analysis, cgn *cgnode) {} +func ext۰reflect۰MakeMap(a *analysis, cgn *cgnode) {} +func ext۰reflect۰MakeSlice(a *analysis, cgn *cgnode) {} +func ext۰reflect۰MapOf(a *analysis, cgn *cgnode) {} +func ext۰reflect۰New(a *analysis, cgn *cgnode) {} +func ext۰reflect۰NewAt(a *analysis, cgn *cgnode) {} +func ext۰reflect۰PtrTo(a *analysis, cgn *cgnode) {} +func ext۰reflect۰Select(a *analysis, cgn *cgnode) {} +func ext۰reflect۰SliceOf(a *analysis, cgn *cgnode) {} + +// ---------- func TypeOf(v Value) Type ---------- + +// result = TypeOf(v) +type reflectTypeOfConstraint struct { + cgn *cgnode + v nodeid // (ptr) + result nodeid +} + +func (c *reflectTypeOfConstraint) String() string { + return fmt.Sprintf("n%d = reflect.TypeOf(n%d)", c.result, c.v) +} + +func (c *reflectTypeOfConstraint) ptr() nodeid { + return c.v +} + +func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + tDyn, _, _ := a.taggedValue(obj) + if tDyn == nil { + panic("not a tagged value") + } + + if a.nodes[c.result].pts.add(a.makeRtype(tDyn)) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰TypeOf(a *analysis, cgn *cgnode) { + a.addConstraint(&reflectTypeOfConstraint{ + cgn: cgn, + v: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +// ---------- func ValueOf(interface{}) Value ---------- + +func ext۰reflect۰ValueOf(a *analysis, cgn *cgnode) { + // TODO(adonovan): when we start creating indirect tagged + // objects, we'll need to handle them specially here since + // they must never appear in the PTS of an interface{}. + a.copy(a.funcResults(cgn.obj), a.funcParams(cgn.obj), 1) +} + +// ---------- func Zero(Type) Value ---------- + +// result = Zero(t) +type reflectZeroConstraint struct { + cgn *cgnode + t nodeid // (ptr) + result nodeid +} + +func (c *reflectZeroConstraint) String() string { + return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.t) +} + +func (c *reflectZeroConstraint) ptr() nodeid { + return c.t +} + +func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + tDyn, v, _ := a.taggedValue(obj) + if tDyn != a.reflectRtype { + panic("not a *reflect.rtype-tagged value") + } + T := a.nodes[v].typ + + // memoize using a.reflectZeros[T] + var id nodeid + if z := a.reflectZeros.At(T); false && z != nil { + id = z.(nodeid) + } else { + id = a.makeTagged(T, c.cgn, nil) + a.reflectZeros.Set(T, id) + } + if a.nodes[c.result].pts.add(id) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰Zero(a *analysis, cgn *cgnode) { + a.addConstraint(&reflectZeroConstraint{ + cgn: cgn, + t: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +// -------------------- (*reflect.rtype) methods -------------------- + +// ---------- func (*rtype) Elem() Type ---------- + +// result = Elem(t) +type rtypeElemConstraint struct { + cgn *cgnode + t nodeid // (ptr) + result nodeid +} + +func (c *rtypeElemConstraint) String() string { + return fmt.Sprintf("n%d = (*reflect.rtype).Elem(n%d)", c.result, c.t) +} + +func (c *rtypeElemConstraint) ptr() nodeid { + return c.t +} + +func (c *rtypeElemConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + T := a.nodes[obj].typ // assume obj is an *rtype + + // Works for *types.{Map,Chan,Array,Slice,Pointer}. + if T, ok := T.Underlying().(interface { + Elem() types.Type + }); ok { + if a.nodes[c.result].pts.add(a.makeRtype(T.Elem())) { + changed = true + } + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰rtype۰Elem(a *analysis, cgn *cgnode) { + a.addConstraint(&rtypeElemConstraint{ + cgn: cgn, + t: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰FieldByIndex(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰FieldByName(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰FieldByNameFunc(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰In(a *analysis, cgn *cgnode) {} + +// ---------- func (*rtype) Key() Type ---------- + +// result = Key(t) +type rtypeKeyConstraint struct { + cgn *cgnode + t nodeid // (ptr) + result nodeid +} + +func (c *rtypeKeyConstraint) String() string { + return fmt.Sprintf("n%d = (*reflect.rtype).Key(n%d)", c.result, c.t) +} + +func (c *rtypeKeyConstraint) ptr() nodeid { + return c.t +} + +func (c *rtypeKeyConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for obj := range delta { + T := a.nodes[obj].typ // assume obj is an *rtype + + if tMap, ok := T.Underlying().(*types.Map); ok { + if a.nodes[c.result].pts.add(a.makeRtype(tMap.Key())) { + changed = true + } + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰rtype۰Key(a *analysis, cgn *cgnode) { + a.addConstraint(&rtypeKeyConstraint{ + cgn: cgn, + t: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +func ext۰reflect۰rtype۰Method(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰MethodByName(a *analysis, cgn *cgnode) {} +func ext۰reflect۰rtype۰Out(a *analysis, cgn *cgnode) {} diff --git a/pointer/solve.go b/pointer/solve.go index 1cb04e53..252dc68c 100644 --- a/pointer/solve.go +++ b/pointer/solve.go @@ -14,47 +14,33 @@ import ( ) func (a *analysis) solve() { - // Initialize points-to sets and complex constraint sets. - for _, c := range a.constraints { - c.init(a) - } - a.constraints = nil // aid GC - - work := a.work - - // Now we've initialized all constraints, we populate the - // worklist with nodes that point to something initially (due - // to addrConstraints) and have other constraints attached. - for id, n := range a.nodes { - if len(n.pts) > 0 && (n.copyTo != nil || n.complex != nil) { - if a.log != nil { - fmt.Fprintf(a.log, "Adding to worklist n%d\n", id) - } - a.addWork(nodeid(id)) - } - } - work.swap() + a.work.swap() // Solver main loop. for round := 1; ; round++ { - if work.swap() { + // Add new constraints to the graph: + // static constraints from SSA on round 1, + // dynamic constraints from reflection thereafter. + a.processNewConstraints() + + if a.work.swap() { if a.log != nil { fmt.Fprintf(a.log, "Solving, round %d\n", round) } // Next iteration. - if work.empty() { + if a.work.empty() { break // done } } - id := work.take() - n := a.nodes[id] - + id := a.work.take() if a.log != nil { fmt.Fprintf(a.log, "\tnode n%d\n", id) } + n := a.nodes[id] + // Difference propagation. delta := n.pts.diff(n.prevPts) if delta == nil { @@ -62,23 +48,8 @@ func (a *analysis) solve() { } n.prevPts = n.pts.clone() - // Process complex constraints dependent on n. - for c := range n.complex { - if a.log != nil { - fmt.Fprintf(a.log, "\t\tconstraint %s\n", c) - } - c.solve(a, n, delta) - } - - // Process copy constraints. - var copySeen nodeset - for mid := range n.copyTo { - if copySeen.add(mid) { - if a.nodes[mid].pts.addAll(delta) { - a.addWork(mid) - } - } - } + // Apply all resolution rules attached to n. + a.solveConstraints(n, delta) if a.log != nil { fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, n.pts) @@ -90,6 +61,97 @@ func (a *analysis) solve() { } } +// processNewConstraints takes the new constraints from a.constraints +// and adds them to the graph, ensuring +// that new constraints are applied to pre-existing labels and +// that pre-existing constraints are applied to new labels. +// +func (a *analysis) processNewConstraints() { + // Take the slice of new constraints. + // (May grow during call to solveConstraints.) + constraints := a.constraints + a.constraints = nil + + // Initialize points-to sets from addr-of (base) constraints. + for _, c := range constraints { + if c, ok := c.(*addrConstraint); ok { + dst := a.nodes[c.dst] + dst.pts.add(c.src) + + // Populate the worklist with nodes that point to + // something initially (due to addrConstraints) and + // have other constraints attached. + // (A no-op in round 1.) + if dst.copyTo != nil || dst.complex != nil { + a.addWork(c.dst) + } + } + } + + // Attach simple (copy) and complex constraints to nodes. + var stale nodeset + for _, c := range constraints { + var id nodeid + switch c := c.(type) { + case *addrConstraint: + // base constraints handled in previous loop + continue + case *copyConstraint: + // simple (copy) constraint + id = c.src + a.nodes[id].copyTo.add(c.dst) + default: + // complex constraint + id = c.ptr() + a.nodes[id].complex.add(c) + } + + if n := a.nodes[id]; len(n.pts) > 0 { + if len(n.prevPts) > 0 { + stale.add(id) + } + if a.log != nil { + fmt.Fprintf(a.log, "Adding to worklist n%d\n", id) + } + a.addWork(id) + } + } + // Apply new constraints to pre-existing PTS labels. + for id := range stale { + n := a.nodes[id] + a.solveConstraints(n, n.prevPts) + } +} + +// solveConstraints applies each resolution rule attached to node n to +// the set of labels delta. It may generate new constraints in +// a.constraints. +// +func (a *analysis) solveConstraints(n *node, delta nodeset) { + if delta == nil { + return + } + + // Process complex constraints dependent on n. + for c := range n.complex { + if a.log != nil { + fmt.Fprintf(a.log, "\t\tconstraint %s\n", c) + } + // TODO(adonovan): parameter n is never used. Remove? + c.solve(a, n, delta) + } + + // Process copy constraints. + var copySeen nodeset + for mid := range n.copyTo { + if copySeen.add(mid) { + if a.nodes[mid].pts.addAll(delta) { + a.addWork(mid) + } + } + } +} + func (a *analysis) addWork(id nodeid) { a.work.add(id) if a.log != nil { @@ -97,29 +159,29 @@ func (a *analysis) addWork(id nodeid) { } } -func (c *addrConstraint) init(a *analysis) { - a.nodes[c.dst].pts.add(c.src) +func (c *addrConstraint) ptr() nodeid { + panic("addrConstraint: not a complex constraint") } -func (c *copyConstraint) init(a *analysis) { - a.nodes[c.src].copyTo.add(c.dst) +func (c *copyConstraint) ptr() nodeid { + panic("addrConstraint: not a complex constraint") } // Complex constraints attach themselves to the relevant pointer node. -func (c *storeConstraint) init(a *analysis) { - a.nodes[c.dst].complex.add(c) +func (c *storeConstraint) ptr() nodeid { + return c.dst } -func (c *loadConstraint) init(a *analysis) { - a.nodes[c.src].complex.add(c) +func (c *loadConstraint) ptr() nodeid { + return c.src } -func (c *offsetAddrConstraint) init(a *analysis) { - a.nodes[c.src].complex.add(c) +func (c *offsetAddrConstraint) ptr() nodeid { + return c.src } -func (c *typeAssertConstraint) init(a *analysis) { - a.nodes[c.src].complex.add(c) +func (c *typeAssertConstraint) ptr() nodeid { + return c.src } -func (c *invokeConstraint) init(a *analysis) { - a.nodes[c.iface].complex.add(c) +func (c *invokeConstraint) ptr() nodeid { + return c.iface } // onlineCopy adds a copy edge. It is called online, i.e. during @@ -189,16 +251,24 @@ func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) { tIface, _ := c.typ.Underlying().(*types.Interface) for ifaceObj := range delta { - ifaceValue, tConc := a.interfaceValue(ifaceObj) + tDyn, v, indirect := a.taggedValue(ifaceObj) + if tDyn == nil { + panic("not a tagged value") + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } if tIface != nil { - if types.IsAssignableTo(tConc, tIface) { + if types.IsAssignableTo(tDyn, tIface) { if a.nodes[c.dst].pts.add(ifaceObj) { a.addWork(c.dst) } } } else { - if types.IsIdentical(tConc, c.typ) { + if types.IsIdentical(tDyn, c.typ) { // Copy entire payload to dst. // // TODO(adonovan): opt: if tConc is @@ -206,7 +276,7 @@ func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) { // entire constraint, perhaps. We // only care about pointers among the // fields. - a.onlineCopyN(c.dst, ifaceValue, a.sizeof(tConc)) + a.onlineCopyN(c.dst, v, a.sizeof(tDyn)) } } } @@ -214,13 +284,22 @@ func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) { func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { for ifaceObj := range delta { - ifaceValue, tConc := a.interfaceValue(ifaceObj) + tDyn, v, indirect := a.taggedValue(ifaceObj) + if tDyn == nil { + panic("not a tagged value") + } + if indirect { + // TODO(adonovan): we may need to implement this if + // we ever apply invokeConstraints to reflect.Value PTSs, + // e.g. for (reflect.Value).Call. + panic("indirect tagged object") + } // Look up the concrete method. - meth := tConc.MethodSet().Lookup(c.method.Pkg(), c.method.Name()) + meth := tDyn.MethodSet().Lookup(c.method.Pkg(), c.method.Name()) if meth == nil { panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)", - c.iface, tConc, c.method, ifaceObj)) + c.iface, tDyn, c.method, ifaceObj)) } fn := a.prog.Method(meth) if fn == nil { @@ -228,7 +307,11 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { } sig := fn.Signature - fnObj := a.funcObj[fn] + fnObj := a.funcObj[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)) + } // Make callsite's fn variable point to identity of // concrete method. (There's no need to add it to @@ -239,7 +322,7 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { // Copy payload to method's receiver param (arg0). arg0 := a.funcParams(fnObj) recvSize := a.sizeof(sig.Recv().Type()) - a.onlineCopyN(arg0, ifaceValue, recvSize) + a.onlineCopyN(arg0, v, recvSize) // Copy iface object payload to method receiver. src := c.params + 1 // skip past identity diff --git a/pointer/testdata/another.go b/pointer/testdata/another.go index 90d4cd2a..f871f673 100644 --- a/pointer/testdata/another.go +++ b/pointer/testdata/another.go @@ -25,7 +25,7 @@ func main() { if unknown { i = incr } - print(i) // @concrete int | S | func(int, int) | func(int) int + print(i) // @types int | S | func(int, int) | func(int) int // NB, an interface may never directly alias any global // labels, even though it may contain pointers that do. diff --git a/pointer/testdata/arrayreflect.go b/pointer/testdata/arrayreflect.go index 06849d92..ce742962 100644 --- a/pointer/testdata/arrayreflect.go +++ b/pointer/testdata/arrayreflect.go @@ -13,7 +13,7 @@ func arrayreflect1() { sl[0] = &a srv := reflect.ValueOf(sl).Slice(0, 0) - print(srv.Interface()) // @concrete []*int + print(srv.Interface()) // @types []*int print(srv.Interface().([]*int)) // @pointsto makeslice@ar1make:12 print(srv.Interface().([]*int)[42]) // @pointsto main.a } @@ -24,18 +24,18 @@ func arrayreflect2() { sl[0] = &a srv := reflect.ValueOf(sl).Slice(0, 0) - print(srv.Interface()) // @concrete []*int + print(srv.Interface()) // @types []*int print(srv.Interface().([]*int)) // pointsto TODO print(srv.Interface().([]*int)[42]) // @pointsto main.a } func arrayreflect3() { srv := reflect.ValueOf("hi").Slice(0, 0) - print(srv.Interface()) // @concrete string + print(srv.Interface()) // @types string type S string srv2 := reflect.ValueOf(S("hi")).Slice(0, 0) - print(srv2.Interface()) // @concrete main.S + print(srv2.Interface()) // @types main.S } func arrayreflect4() { @@ -51,8 +51,8 @@ func arrayreflect4() { // Under Das's algorithm, the extra indirection results in // (undirected) unification not (directed) flow edges. // TODO(adonovan): precision: lift aggregates. - print(rv1.Interface()) // @concrete string | int - print(rv2.Interface()) // @concrete string | int + print(rv1.Interface()) // @types string | int + print(rv2.Interface()) // @types string | int } func arrayreflect5() { @@ -61,13 +61,13 @@ func arrayreflect5() { srv := reflect.ValueOf(sl1) - print(srv.Interface()) // @concrete []byte + print(srv.Interface()) // @types []byte print(srv.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:62:13 print(srv.Bytes()) // @pointsto makeslice@testdata/arrayreflect.go:62:13 srv2 := reflect.ValueOf(123) srv2.SetBytes(sl2) - print(srv2.Interface()) // @concrete []byte | int + print(srv2.Interface()) // @types []byte | int print(srv2.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:63:13 print(srv2.Bytes()) // @pointsto makeslice@testdata/arrayreflect.go:63:13 } @@ -77,22 +77,22 @@ func arrayreflect6() { sl2 := []*int{&a} srv1 := reflect.ValueOf(sl1) - print(srv1.Index(42).Interface()) // @concrete *bool + print(srv1.Index(42).Interface()) // @types *bool print(srv1.Index(42).Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20 srv2 := reflect.ValueOf(sl2) - print(srv2.Index(42).Interface()) // @concrete *int + print(srv2.Index(42).Interface()) // @types *int print(srv2.Index(42).Interface().(*int)) // @pointsto main.a p1 := &sl1[0] p2 := &sl2[0] prv1 := reflect.ValueOf(p1) - print(prv1.Elem().Interface()) // @concrete *bool + print(prv1.Elem().Interface()) // @types *bool print(prv1.Elem().Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20 prv2 := reflect.ValueOf(p2) - print(prv2.Elem().Interface()) // @concrete *int + print(prv2.Elem().Interface()) // @types *int print(prv2.Elem().Interface().(*int)) // @pointsto main.a } diff --git a/pointer/testdata/arrays.go b/pointer/testdata/arrays.go index ae0ae39d..e57a15b4 100644 --- a/pointer/testdata/arrays.go +++ b/pointer/testdata/arrays.go @@ -36,8 +36,8 @@ func array2() { func array3() { a := []interface{}{"", 1} b := []interface{}{true, func() {}} - print(a[0]) // @concrete string | int - print(b[0]) // @concrete bool | func() + print(a[0]) // @types string | int + print(b[0]) // @types bool | func() } // Test of append, copy, slice. diff --git a/pointer/testdata/chanreflect.go b/pointer/testdata/chanreflect.go index 8e662cd0..bfbcc26e 100644 --- a/pointer/testdata/chanreflect.go +++ b/pointer/testdata/chanreflect.go @@ -15,7 +15,7 @@ func chanreflect1() { ch := make(chan *int, 0) crv := reflect.ValueOf(ch) crv.Send(reflect.ValueOf(&a)) - print(crv.Interface()) // @concrete chan *int + print(crv.Interface()) // @types chan *int print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12 print(<-ch) // @pointsto main.a } @@ -25,27 +25,27 @@ func chanreflect2() { ch <- &b crv := reflect.ValueOf(ch) r, _ := crv.Recv() - print(r.Interface()) // @concrete *int + print(r.Interface()) // @types *int print(r.Interface().(*int)) // @pointsto main.b } func chanOfRecv() { // MakeChan(<-chan) is a no-op. t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a)) - print(reflect.Zero(t).Interface()) // @concrete <-chan *int + print(reflect.Zero(t).Interface()) // @types <-chan *int print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto } func chanOfSend() { // MakeChan(chan<-) is a no-op. t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a)) - print(reflect.Zero(t).Interface()) // @concrete chan<- *int + print(reflect.Zero(t).Interface()) // @types chan<- *int print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto } func chanOfBoth() { t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a)) - print(reflect.Zero(t).Interface()) // @concrete chan *int + print(reflect.Zero(t).Interface()) // @types chan *int ch := reflect.MakeChan(t, 0) print(ch.Interface().(chan *int)) // @pointsto reflectMakechan@testdata/chanreflect.go:49:24 ch.Send(reflect.ValueOf(&b)) @@ -61,8 +61,8 @@ func chanOfUnknown() { // Unknown channel direction: assume all three. // MakeChan only works on the bi-di channel type. t := reflect.ChanOf(unknownDir, reflect.TypeOf(&a)) - print(reflect.Zero(t).Interface()) // @concrete <-chan *int | chan<- *int | chan *int - print(reflect.MakeChan(t, 0).Interface()) // @concrete chan *int + print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int + print(reflect.MakeChan(t, 0).Interface()) // @types chan *int } func main() { diff --git a/pointer/testdata/chanreflect1.go b/pointer/testdata/chanreflect1.go index 4be85244..c5e25874 100644 --- a/pointer/testdata/chanreflect1.go +++ b/pointer/testdata/chanreflect1.go @@ -15,7 +15,7 @@ func chanreflect1() { ch := make(chan *int, 0) crv := reflect.ValueOf(ch) crv.Send(reflect.ValueOf(&a)) - print(crv.Interface()) // @concrete chan *int + print(crv.Interface()) // @types chan *int print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12 print(<-ch) // @pointsto main.a } @@ -25,7 +25,7 @@ func chanreflect2() { ch <- &b crv := reflect.ValueOf(ch) r, _ := crv.Recv() - print(r.Interface()) // @concrete *int + print(r.Interface()) // @types *int print(r.Interface().(*int)) // @pointsto main.b } diff --git a/pointer/testdata/flow.go b/pointer/testdata/flow.go index 06faff57..6fb599e8 100644 --- a/pointer/testdata/flow.go +++ b/pointer/testdata/flow.go @@ -33,10 +33,10 @@ func flow2() { if somepred { r = s } - print(s) // @concrete int - print(p) // @concrete string - print(q) // @concrete string - print(r) // @concrete int | string + print(s) // @types int + print(p) // @types string + print(q) // @types string + print(r) // @types int | string } var g1, g2 int diff --git a/pointer/testdata/fmtexcerpt.go b/pointer/testdata/fmtexcerpt.go index d738ffb0..ee2a0e76 100644 --- a/pointer/testdata/fmtexcerpt.go +++ b/pointer/testdata/fmtexcerpt.go @@ -22,13 +22,13 @@ func Println(a ...interface{}) { } func (p *pp) doPrint(a []interface{}, addspace, addnewline bool) { - print(a[0]) // @concrete S | string + print(a[0]) // @types S | string stringer := a[0].(interface { String() string }) stringer.String() - print(stringer) // @concrete S + print(stringer) // @types S } type S int diff --git a/pointer/testdata/funcreflect.go b/pointer/testdata/funcreflect.go index e7a8c451..d9275dcb 100644 --- a/pointer/testdata/funcreflect.go +++ b/pointer/testdata/funcreflect.go @@ -19,7 +19,7 @@ func g(p *bool) { func funcreflect1() { rvf := reflect.ValueOf(f) res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)}) - print(res[0].Interface()) // @concrete + print(res[0].Interface()) // @types print(res[0].Interface().(*int)) // @pointsto } diff --git a/pointer/testdata/interfaces.go b/pointer/testdata/interfaces.go index ed08a169..ef24c67b 100644 --- a/pointer/testdata/interfaces.go +++ b/pointer/testdata/interfaces.go @@ -30,9 +30,9 @@ func interface1() { k = i } - print(i) // @concrete *int - print(j) // @concrete D - print(k) // @concrete *int | D + print(i) // @types *int + print(j) // @types D + print(k) // @types *int | D print(i.(*int)) // @pointsto main.a print(j.(*int)) // @pointsto @@ -51,9 +51,9 @@ func interface2() { k = i } - print(i) // @concrete *C - print(j) // @concrete D - print(k) // @concrete *C | D + print(i) // @types *C + print(j) // @types D + print(k) // @types *C | D print(k) // @pointsto makeinterface:main.D | makeinterface:*main.C k.f() @@ -77,7 +77,7 @@ func interface2() { func interface3() { // There should be no backflow of concrete types from the type-switch to x. var x interface{} = 0 - print(x) // @concrete int + print(x) // @types int switch y := x.(type) { case int: case string: @@ -90,18 +90,18 @@ func interface4() { i = 123 } - print(i) // @concrete int | D + print(i) // @types int | D j := i.(I) // interface narrowing type-assertion - print(j) // @concrete D + print(j) // @types D print(j.(D).ptr) // @pointsto main.a var l interface{} = j // interface widening assignment. - print(l) // @concrete D + print(l) // @types D print(l.(D).ptr) // @pointsto main.a m := j.(interface{}) // interface widening type-assertion. - print(m) // @concrete D + print(m) // @types D print(m.(D).ptr) // @pointsto main.a } diff --git a/pointer/testdata/mapreflect.go b/pointer/testdata/mapreflect.go index 5662badd..5305c201 100644 --- a/pointer/testdata/mapreflect.go +++ b/pointer/testdata/mapreflect.go @@ -2,30 +2,33 @@ package main -import "reflect" - -// -// This test is very sensitive to line-number perturbations! - // Test of maps with reflection. +import "reflect" + var a int var b bool func mapreflect1() { - m := make(map[*int]*bool) + m := make(map[*int]*bool) // @line mr1make m[&a] = &b mrv := reflect.ValueOf(m) - print(mrv.Interface()) // @concrete map[*int]*bool - print(mrv.Interface().(map[*int]*bool)) // @pointsto makemap@testdata/mapreflect.go:16:11 + print(mrv.Interface()) // @types map[*int]*bool + print(mrv.Interface().(map[*int]*bool)) // @pointsto makemap@mr1make:11 + print(mrv) // @pointsto makeinterface:map[*int]*bool + print(mrv) // @types map[*int]*bool - for _, k := range mrv.MapKeys() { - print(k.Interface()) // @concrete *int + keys := mrv.MapKeys() + print(keys) // @pointsto + for _, k := range keys { + print(k) // @pointsto + print(k) // @types *int + print(k.Interface()) // @types *int print(k.Interface().(*int)) // @pointsto main.a v := mrv.MapIndex(k) - print(v.Interface()) // @concrete *bool + print(v.Interface()) // @types *bool print(v.Interface().(*bool)) // @pointsto main.b } } @@ -38,12 +41,27 @@ func mapreflect2() { print(m[nil]) // @pointsto main.b for _, k := range mrv.MapKeys() { - print(k.Interface()) // @concrete *int + print(k.Interface()) // @types *int print(k.Interface().(*int)) // @pointsto main.a } - print(reflect.Zero(reflect.TypeOf(m).Key()).Interface()) // @concrete *int - print(reflect.Zero(reflect.TypeOf(m).Elem()).Interface()) // @concrete *bool + tmap := reflect.TypeOf(m) + // types.EvalNode won't let us refer to non-exported types: + // print(tmap) // #@types *reflect.rtype + print(tmap) // @pointsto map[*int]*bool + + zmap := reflect.Zero(tmap) + print(zmap) // @pointsto + print(zmap.Interface()) // @pointsto + + print(tmap.Key()) // @pointsto *int + print(tmap.Elem()) // @pointsto *bool + print(reflect.Zero(tmap.Key())) // @pointsto + print(reflect.Zero(tmap.Key()).Interface()) // @pointsto + print(reflect.Zero(tmap.Key()).Interface()) // @types *int + print(reflect.Zero(tmap.Elem())) // @pointsto + print(reflect.Zero(tmap.Elem()).Interface()) // @pointsto + print(reflect.Zero(tmap.Elem()).Interface()) // @types *bool } func main() { diff --git a/pointer/testdata/panic.go b/pointer/testdata/panic.go index dd0bc6c4..ee8a7668 100644 --- a/pointer/testdata/panic.go +++ b/pointer/testdata/panic.go @@ -30,7 +30,7 @@ func main() { panic(g) } ex := recover() - print(ex) // @concrete myPanic | string | func(int) | func() string + print(ex) // @types myPanic | string | func(int) | func() string print(ex.(func(int))) // @pointsto main.f print(ex.(func() string)) // @pointsto main.g } diff --git a/pointer/testdata/reflect.go b/pointer/testdata/reflect.go index 986a649d..6aa83fb6 100644 --- a/pointer/testdata/reflect.go +++ b/pointer/testdata/reflect.go @@ -17,7 +17,7 @@ func reflectIndirect() { func reflectNewAt() { var x [8]byte - print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @concrete *int + print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @types *int } // @warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call" @@ -27,34 +27,34 @@ func reflectTypeOf() { if unknown { t = reflect.TypeOf("foo") } - print(t) // @concrete *reflect.rtype - print(reflect.Zero(t).Interface()) // @concrete int | string + print(t) // @types *reflect.rtype + print(reflect.Zero(t).Interface()) // @types int | string newint := reflect.New(t).Interface() // @line rtonew - print(newint) // @concrete *int | *string + print(newint) // @types *int | *string print(newint.(*int)) // @pointsto reflectAlloc@rtonew:23 print(newint.(*string)) // @pointsto reflectAlloc@rtonew:23 } func reflectTypeElem() { - print(reflect.Zero(reflect.TypeOf(&a).Elem()).Interface()) // @concrete int - print(reflect.Zero(reflect.TypeOf([]string{}).Elem()).Interface()) // @concrete string - print(reflect.Zero(reflect.TypeOf(make(chan bool)).Elem()).Interface()) // @concrete bool - print(reflect.Zero(reflect.TypeOf(make(map[string]float64)).Elem()).Interface()) // @concrete float64 - print(reflect.Zero(reflect.TypeOf([3]complex64{}).Elem()).Interface()) // @concrete complex64 - print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface()) // @concrete + print(reflect.Zero(reflect.TypeOf(&a).Elem()).Interface()) // @types int + print(reflect.Zero(reflect.TypeOf([]string{}).Elem()).Interface()) // @types string + print(reflect.Zero(reflect.TypeOf(make(chan bool)).Elem()).Interface()) // @types bool + print(reflect.Zero(reflect.TypeOf(make(map[string]float64)).Elem()).Interface()) // @types float64 + print(reflect.Zero(reflect.TypeOf([3]complex64{}).Elem()).Interface()) // @types complex64 + print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface()) // @types } func reflectTypeInOut() { var f func(float64, bool) (string, int) - print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @concrete float64 - print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @concrete bool - print(reflect.Zero(reflect.TypeOf(f).In(-1)).Interface()) // @concrete float64 | bool - print(reflect.Zero(reflect.TypeOf(f).In(zero)).Interface()) // @concrete float64 | bool + print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @types float64 + print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @types bool + print(reflect.Zero(reflect.TypeOf(f).In(-1)).Interface()) // @types float64 | bool + print(reflect.Zero(reflect.TypeOf(f).In(zero)).Interface()) // @types float64 | bool - print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @concrete string - print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @concrete int - print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @concrete string | int - print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @concrete + print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @types string + print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @types int + print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @types string | int + print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @types } func main() { diff --git a/pointer/testdata/structreflect.go b/pointer/testdata/structreflect.go index 510ff091..980ce777 100644 --- a/pointer/testdata/structreflect.go +++ b/pointer/testdata/structreflect.go @@ -16,7 +16,7 @@ func structReflect1() { var a A fld, _ := reflect.TypeOf(a).FieldByName("f") // "f" is ignored // TODO(adonovan): what does interface{} even mean here? - print(reflect.Zero(fld.Type).Interface()) // @concrete *int | bool | interface{} + print(reflect.Zero(fld.Type).Interface()) // @types *int | bool | interface{} // TODO(adonovan): test promotion/embedding. } diff --git a/pointer/testdata/structs.go b/pointer/testdata/structs.go index c9fbc2ab..a10760c7 100644 --- a/pointer/testdata/structs.go +++ b/pointer/testdata/structs.go @@ -34,7 +34,7 @@ func structs1() { print(b.h) // @pointsto main.q print(b.f) // @pointsto main.p - print(b.g) // @concrete *B + print(b.g) // @types *B ptr := &b.f print(*ptr) // @pointsto main.p diff --git a/pointer/util.go b/pointer/util.go index 184250da..64d8a161 100644 --- a/pointer/util.go +++ b/pointer/util.go @@ -16,6 +16,9 @@ import ( func CanPoint(T types.Type) bool { switch T := T.(type) { case *types.Named: + if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" { + return true // treat reflect.Value like interface{} + } return CanPoint(T.Underlying()) case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature, *types.Slice: @@ -25,12 +28,36 @@ func CanPoint(T types.Type) bool { return false // array struct tuple builtin basic } +// CanHaveDynamicTypes reports whether the type T can "hold" dynamic types, +// i.e. is an interface (incl. reflect.Type) or a reflect.Value. +// +func CanHaveDynamicTypes(T types.Type) bool { + switch T := T.(type) { + case *types.Named: + if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" { + return true // reflect.Value + } + return CanHaveDynamicTypes(T.Underlying()) + case *types.Interface: + return true + } + return false +} + // mustDeref returns the element type of its argument, which must be a // pointer; panic ensues otherwise. func mustDeref(typ types.Type) types.Type { return typ.Underlying().(*types.Pointer).Elem() } +// deref returns a pointer's element type; otherwise it returns typ. +func deref(typ types.Type) types.Type { + if p, ok := typ.Underlying().(*types.Pointer); ok { + return p.Elem() + } + return typ +} + // A fieldInfo describes one subelement (node) of the flattening-out // of a type T: the subelement's type and its path from the root of T. // @@ -76,6 +103,8 @@ func (fi *fieldInfo) path() string { // scalars (basic types or pointerlike types), except for struct/array // "identity" nodes, whose type is that of the aggregate. // +// reflect.Value is considered pointerlike, similar to interface{}. +// // Callers must not mutate the result. // func (a *analysis) flatten(t types.Type) []*fieldInfo { @@ -172,6 +201,8 @@ func sliceToArray(slice types.Type) *types.Array { // Node set ------------------------------------------------------------------- +// NB, mutator methods are attached to *nodeset. +// nodeset may be a reference, but its address matters! type nodeset map[nodeid]struct{} // ---- Accessors ----