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
This commit is contained in:
Alan Donovan 2013-09-16 09:49:10 -04:00
parent 4c5148c4cd
commit 3b5de067a1
32 changed files with 1405 additions and 479 deletions

View File

@ -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.

View File

@ -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

View File

@ -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",

View File

@ -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"

View File

@ -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

30
oracle/testdata/src/main/reflection.go vendored Normal file
View File

@ -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
}

View File

@ -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:
<alloc in (reflect.Value).MapKeys>
-------- @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

View File

@ -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

View File

@ -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.

View File

@ -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()
}

View File

@ -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.

View File

@ -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.

View File

@ -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.)

View File

@ -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)
// <alloc in reflect.Zero> (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("<alloc in %s>", l.obj.cgn.Func())
}
return "<unknown>" // 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()

View File

@ -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

470
pointer/reflect.go Normal file
View File

@ -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) {}

View File

@ -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

View File

@ -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.

View File

@ -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
}

View File

@ -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.

View File

@ -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() {

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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 <alloc in (reflect.Value).MapKeys>
for _, k := range keys {
print(k) // @pointsto <alloc in (reflect.Value).MapKeys>
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 <alloc in reflect.Zero>
print(zmap.Interface()) // @pointsto <alloc in reflect.Zero>
print(tmap.Key()) // @pointsto *int
print(tmap.Elem()) // @pointsto *bool
print(reflect.Zero(tmap.Key())) // @pointsto <alloc in reflect.Zero>
print(reflect.Zero(tmap.Key()).Interface()) // @pointsto <alloc in reflect.Zero>
print(reflect.Zero(tmap.Key()).Interface()) // @types *int
print(reflect.Zero(tmap.Elem())) // @pointsto <alloc in reflect.Zero>
print(reflect.Zero(tmap.Elem()).Interface()) // @pointsto <alloc in reflect.Zero>
print(reflect.Zero(tmap.Elem()).Interface()) // @types *bool
}
func main() {

View File

@ -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
}

View File

@ -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() {

View File

@ -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.
}

View File

@ -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

View File

@ -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 ----