mirror of
https://github.com/golang/go
synced 2024-10-01 03:28:32 -06:00
go.tools/pointer: more reflection.
Support for: (*reflect.rtype).Field (*reflect.rtype).FieldByName reflect.MakeSlice runtime.SetFinalizer Details: - analysis locates ssa.Functions for (reflect.Value).Call and runtime.SetFinalizer during startup to that it can special-case them during genCall. ('Call' is forthcoming.) - The callsite.targets mechanism is only used for dynamic calls now. For static calls we call callEdge during constraint generation; this is a minor optimisation. - Static calls to SetFinalizer are inlined so that the call appears to go direct to the finalizer. (We'll use the same trick for (reflect.Value).Call.) - runtime.FuncForPC: treat as a no-op. - Fixed pointer_test to properly deal with expectations that are multi-sets. - Inlined rtypeMethodByNameConstraint.addMethod. - More tests. R=crawshaw CC=golang-dev https://golang.org/cl/14682045
This commit is contained in:
parent
ac0a1222cb
commit
8bb20b8231
@ -205,14 +205,16 @@ type analysis struct {
|
||||
work worklist // solver's worklist
|
||||
result *Result // results of the analysis
|
||||
|
||||
// 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)
|
||||
reflectRtypePtr *types.Pointer // *reflect.rtype
|
||||
reflectType *types.Named // reflect.Type
|
||||
rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T
|
||||
reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value
|
||||
// Reflection & intrinsics:
|
||||
hasher typemap.Hasher // cache of type hashes
|
||||
reflectValueObj types.Object // type symbol for reflect.Value (if present)
|
||||
reflectValueCall *ssa.Function // (reflect.Value).Call
|
||||
reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present)
|
||||
reflectRtypePtr *types.Pointer // *reflect.rtype
|
||||
reflectType *types.Named // reflect.Type
|
||||
rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T
|
||||
reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value
|
||||
runtimeSetFinalizer *ssa.Function // runtime.SetFinalizer
|
||||
}
|
||||
|
||||
// enclosingObj returns the object (addressible memory object) that encloses node id.
|
||||
@ -273,7 +275,9 @@ func Analyze(config *Config) *Result {
|
||||
}
|
||||
|
||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||
a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
|
||||
rV := reflect.Object.Scope().Lookup("Value")
|
||||
a.reflectValueObj = rV
|
||||
a.reflectValueCall = a.prog.Method(rV.Type().MethodSet().Lookup(nil, "Call"))
|
||||
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
|
||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
@ -285,6 +289,9 @@ func Analyze(config *Config) *Result {
|
||||
a.rtypes.SetHasher(a.hasher)
|
||||
a.reflectZeros.SetHasher(a.hasher)
|
||||
}
|
||||
if runtime := a.prog.ImportedPackage("runtime"); runtime != nil {
|
||||
a.runtimeSetFinalizer = runtime.Func("SetFinalizer")
|
||||
}
|
||||
|
||||
root := a.generate()
|
||||
|
||||
@ -314,22 +321,11 @@ func Analyze(config *Config) *Result {
|
||||
}
|
||||
}
|
||||
|
||||
// Visit discovered call graph.
|
||||
// Add dynamic edges to call graph.
|
||||
for _, caller := range a.cgnodes {
|
||||
for _, site := range caller.sites {
|
||||
for nid := range a.nodes[site.targets].pts {
|
||||
callee := a.nodes[nid].obj.cgn
|
||||
|
||||
if a.config.BuildCallGraph {
|
||||
site.callees = append(site.callees, callee)
|
||||
}
|
||||
|
||||
// TODO(adonovan): de-dup these messages.
|
||||
// Warn about calls to non-intrinsic external functions.
|
||||
if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
|
||||
a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn)
|
||||
a.warnf(fn.Pos(), " (declared here)")
|
||||
}
|
||||
for callee := range a.nodes[site.targets].pts {
|
||||
a.callEdge(site, callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,3 +336,29 @@ func Analyze(config *Config) *Result {
|
||||
|
||||
return a.result
|
||||
}
|
||||
|
||||
// callEdge is called for each edge in the callgraph.
|
||||
// calleeid is the callee's object node (has otFunction flag).
|
||||
//
|
||||
func (a *analysis) callEdge(site *callsite, calleeid nodeid) {
|
||||
obj := a.nodes[calleeid].obj
|
||||
if obj.flags&otFunction == 0 {
|
||||
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
||||
}
|
||||
callee := obj.cgn
|
||||
|
||||
if a.config.BuildCallGraph {
|
||||
site.callees = append(site.callees, callee)
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tcall edge %s -> %s\n", site, callee)
|
||||
}
|
||||
|
||||
// Warn about calls to non-intrinsic external functions.
|
||||
// TODO(adonovan): de-dup these messages.
|
||||
if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
|
||||
a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn)
|
||||
a.warnf(fn.Pos(), " (declared here)")
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func (n *cgnode) String() string {
|
||||
// they are handled as intrinsics.
|
||||
//
|
||||
type callsite struct {
|
||||
targets nodeid // pts(targets) contains identities of all called functions.
|
||||
targets nodeid // pts(·) contains objects for dynamically called functions
|
||||
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
|
||||
callees []*cgnode // unordered set of callees of this site
|
||||
}
|
||||
|
@ -578,18 +578,31 @@ func (a *analysis) shouldUseContext(fn *ssa.Function) bool {
|
||||
|
||||
// genStaticCall generates constraints for a statically dispatched function call.
|
||||
func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
|
||||
// Ascertain the context (contour/CGNode) for a particular call.
|
||||
var obj nodeid
|
||||
fn := call.StaticCallee()
|
||||
|
||||
// Special cases for inlined intrinsics.
|
||||
switch fn {
|
||||
case a.runtimeSetFinalizer:
|
||||
// Inline SetFinalizer so the call appears direct.
|
||||
site.targets = a.addOneNode(tInvalid, "SetFinalizer.targets", nil)
|
||||
a.addConstraint(&runtimeSetFinalizerConstraint{
|
||||
targets: site.targets,
|
||||
x: a.valueNode(call.Args[0]),
|
||||
f: a.valueNode(call.Args[1]),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Ascertain the context (contour/cgnode) for a particular call.
|
||||
var obj nodeid
|
||||
if a.shouldUseContext(fn) {
|
||||
obj = a.makeFunctionObject(fn, site) // new contour
|
||||
} else {
|
||||
obj = a.objectNode(nil, fn) // shared contour
|
||||
}
|
||||
a.callEdge(site, obj)
|
||||
|
||||
sig := call.Signature()
|
||||
targets := a.addOneNode(sig, "call.targets", nil)
|
||||
a.addressOf(targets, obj) // (a singleton)
|
||||
|
||||
// Copy receiver, if any.
|
||||
params := a.funcParams(obj)
|
||||
@ -613,20 +626,18 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC
|
||||
if result != 0 {
|
||||
a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
|
||||
}
|
||||
|
||||
// pts(targets) will be the (singleton) set of possible call targets.
|
||||
site.targets = targets
|
||||
}
|
||||
|
||||
// genDynamicCall generates constraints for a dynamic function call.
|
||||
func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
|
||||
fn := a.valueNode(call.Value)
|
||||
sig := call.Signature()
|
||||
// pts(targets) will be the set of possible call targets.
|
||||
site.targets = a.valueNode(call.Value)
|
||||
|
||||
// We add dynamic closure rules that store the arguments into,
|
||||
// and load the results from, the P/R block of each function
|
||||
// discovered in pts(fn).
|
||||
// discovered in pts(targets).
|
||||
|
||||
sig := call.Signature()
|
||||
var offset uint32 = 1 // P/R block starts at offset 1
|
||||
for i, arg := range call.Args {
|
||||
sz := a.sizeof(sig.Params().At(i).Type())
|
||||
@ -636,9 +647,6 @@ func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.Call
|
||||
if result != 0 {
|
||||
a.genLoad(caller, result, call.Value, offset, a.sizeof(sig.Results()))
|
||||
}
|
||||
|
||||
// pts(targets) will be the (singleton) set of possible call targets.
|
||||
site.targets = fn
|
||||
}
|
||||
|
||||
// genInvoke generates constraints for a dynamic method invocation.
|
||||
@ -699,9 +707,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
||||
fn := a.prog.Method(meth)
|
||||
|
||||
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
||||
|
||||
// pts(targets) will be the (singleton) set of possible call targets.
|
||||
site.targets = obj
|
||||
a.callEdge(site, obj)
|
||||
|
||||
// From now on, it's essentially a static call, but little is
|
||||
// gained by factoring together the code for both cases.
|
||||
@ -745,13 +751,11 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) {
|
||||
}
|
||||
|
||||
site := &callsite{instr: instr}
|
||||
|
||||
switch {
|
||||
case call.StaticCallee() != nil:
|
||||
if call.StaticCallee() != nil {
|
||||
a.genStaticCall(caller, site, call, result)
|
||||
case call.IsInvoke():
|
||||
} else if call.IsInvoke() {
|
||||
a.genInvoke(caller, site, call, result)
|
||||
default:
|
||||
} else {
|
||||
a.genDynamicCall(caller, site, call, result)
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ package pointer
|
||||
// own C functions using a snippet of Go.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
@ -216,7 +219,7 @@ func init() {
|
||||
"runtime.Breakpoint": ext۰NoEffect,
|
||||
"runtime.CPUProfile": ext۰NotYetImplemented,
|
||||
"runtime.Caller": ext۰NoEffect,
|
||||
"runtime.FuncForPC": ext۰NotYetImplemented,
|
||||
"runtime.FuncForPC": ext۰NoEffect,
|
||||
"runtime.GC": ext۰NoEffect,
|
||||
"runtime.GOMAXPROCS": ext۰NoEffect,
|
||||
"runtime.Goexit": ext۰NoEffect,
|
||||
@ -228,7 +231,7 @@ func init() {
|
||||
"runtime.ReadMemStats": ext۰NoEffect,
|
||||
"runtime.SetBlockProfileRate": ext۰NoEffect,
|
||||
"runtime.SetCPUProfileRate": ext۰NoEffect,
|
||||
"runtime.SetFinalizer": ext۰NotYetImplemented,
|
||||
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
||||
"runtime.Stack": ext۰NoEffect,
|
||||
"runtime.ThreadCreateProfile": ext۰NoEffect,
|
||||
"runtime.funcentry_go": ext۰NoEffect,
|
||||
@ -315,3 +318,70 @@ 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)
|
||||
}
|
||||
|
||||
// ---------- func runtime.SetFinalizer(x, f interface{}) ----------
|
||||
|
||||
// runtime.SetFinalizer(x, f)
|
||||
type runtimeSetFinalizerConstraint struct {
|
||||
targets nodeid
|
||||
f nodeid // (ptr)
|
||||
x nodeid
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) String() string {
|
||||
return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f)
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) ptr() nodeid {
|
||||
return c.f
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, _ *node, delta nodeset) {
|
||||
for fObj := range delta {
|
||||
tDyn, f, indirect := a.taggedValue(fObj)
|
||||
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")
|
||||
}
|
||||
|
||||
tSig, ok := tDyn.Underlying().(*types.Signature)
|
||||
if !ok {
|
||||
continue // not a function
|
||||
}
|
||||
if tSig.Recv() != nil {
|
||||
panic(tSig)
|
||||
}
|
||||
if tSig.Params().Len() != 1 {
|
||||
continue // not a unary function
|
||||
}
|
||||
|
||||
// Extract x to tmp.
|
||||
tx := tSig.Params().At(0).Type()
|
||||
tmp := a.addNodes(tx, "SetFinalizer.tmp")
|
||||
a.untag(tx, tmp, c.x, false)
|
||||
|
||||
// Call f(tmp).
|
||||
a.store(f, tmp, 1, a.sizeof(tx))
|
||||
|
||||
// Add dynamic call target.
|
||||
if a.onlineCopy(c.targets, f) {
|
||||
a.addWork(c.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰runtime۰SetFinalizer(a *analysis, cgn *cgnode) {
|
||||
// This is the shared contour, used for dynamic calls.
|
||||
targets := a.addOneNode(tInvalid, "SetFinalizer.targets", nil)
|
||||
cgn.sites = append(cgn.sites, &callsite{targets: targets})
|
||||
params := a.funcParams(cgn.obj)
|
||||
a.addConstraint(&runtimeSetFinalizerConstraint{
|
||||
targets: targets,
|
||||
x: params,
|
||||
f: params + 1,
|
||||
})
|
||||
}
|
||||
|
@ -31,10 +31,6 @@ import (
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
// Currently debugging:
|
||||
// "testdata/tmp.go",
|
||||
|
||||
// Working:
|
||||
"testdata/a_test.go",
|
||||
"testdata/another.go",
|
||||
"testdata/arrayreflect.go",
|
||||
@ -43,6 +39,7 @@ var inputs = []string{
|
||||
"testdata/chanreflect.go",
|
||||
"testdata/context.go",
|
||||
"testdata/conv.go",
|
||||
"testdata/finalizer.go",
|
||||
"testdata/flow.go",
|
||||
"testdata/fmtexcerpt.go",
|
||||
"testdata/func.go",
|
||||
@ -54,13 +51,8 @@ var inputs = []string{
|
||||
"testdata/panic.go",
|
||||
"testdata/recur.go",
|
||||
"testdata/reflect.go",
|
||||
"testdata/structreflect.go",
|
||||
"testdata/structs.go",
|
||||
|
||||
// TODO(adonovan): get these tests (of reflection) passing.
|
||||
// (The tests are mostly sound since they were used for a
|
||||
// previous implementation.)
|
||||
// "testdata/finalizer.go",
|
||||
// "testdata/structreflect.go",
|
||||
}
|
||||
|
||||
// Expectation grammar:
|
||||
@ -376,35 +368,41 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog
|
||||
}
|
||||
|
||||
func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string]string, prog *ssa.Program) bool {
|
||||
expected := make(map[string]struct{})
|
||||
surplus := make(map[string]struct{})
|
||||
expected := make(map[string]int)
|
||||
surplus := make(map[string]int)
|
||||
exact := true
|
||||
for _, g := range e.args {
|
||||
if g == "..." {
|
||||
exact = false
|
||||
continue
|
||||
}
|
||||
expected[g] = struct{}{}
|
||||
expected[g]++
|
||||
}
|
||||
// Find the set of labels that the probe's
|
||||
// argument (x in print(x)) may point to.
|
||||
for _, label := range pr.arg0.PointsTo().Labels() {
|
||||
name := labelString(label, lineMapping, prog)
|
||||
if _, ok := expected[name]; ok {
|
||||
delete(expected, name)
|
||||
if expected[name] > 0 {
|
||||
expected[name]--
|
||||
} else if exact {
|
||||
surplus[name] = struct{}{}
|
||||
surplus[name]++
|
||||
}
|
||||
}
|
||||
// Report set difference:
|
||||
// Report multiset difference:
|
||||
ok := true
|
||||
if len(expected) > 0 {
|
||||
ok = false
|
||||
e.errorf("value does not alias these expected labels: %s", join(expected))
|
||||
for _, count := range expected {
|
||||
if count > 0 {
|
||||
ok = false
|
||||
e.errorf("value does not alias these expected labels: %s", join(expected))
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(surplus) > 0 {
|
||||
ok = false
|
||||
e.errorf("value may additionally alias these labels: %s", join(surplus))
|
||||
for _, count := range surplus {
|
||||
if count > 0 {
|
||||
ok = false
|
||||
e.errorf("value may additionally alias these labels: %s", join(surplus))
|
||||
break
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
@ -462,7 +460,7 @@ func checkTypesExpectation(e *expectation, pr *probe) bool {
|
||||
var errOK = errors.New("OK")
|
||||
|
||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Graph) bool {
|
||||
found := make(map[string]struct{})
|
||||
found := make(map[string]int)
|
||||
err := call.GraphVisitEdges(callgraph, func(edge call.Edge) error {
|
||||
// Name-based matching is inefficient but it allows us to
|
||||
// match functions whose names that would not appear in an
|
||||
@ -472,7 +470,7 @@ func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Gra
|
||||
if calleeStr == e.args[1] {
|
||||
return errOK // expectation satisified; stop the search
|
||||
}
|
||||
found[calleeStr] = struct{}{}
|
||||
found[calleeStr]++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -544,14 +542,16 @@ func TestInput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// join joins the elements of set with " | "s.
|
||||
func join(set map[string]struct{}) string {
|
||||
// join joins the elements of multiset with " | "s.
|
||||
func join(set map[string]int) string {
|
||||
var buf bytes.Buffer
|
||||
sep := ""
|
||||
for name := range set {
|
||||
buf.WriteString(sep)
|
||||
sep = " | "
|
||||
buf.WriteString(name)
|
||||
for name, count := range set {
|
||||
for i := 0; i < count; i++ {
|
||||
buf.WriteString(sep)
|
||||
sep = " | "
|
||||
buf.WriteString(name)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
@ -849,8 +849,58 @@ 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 MakeSlice(Type) Value ----------
|
||||
|
||||
// result = MakeSlice(typ)
|
||||
type reflectMakeSliceConstraint struct {
|
||||
cgn *cgnode
|
||||
typ nodeid // (ptr)
|
||||
result nodeid
|
||||
}
|
||||
|
||||
func (c *reflectMakeSliceConstraint) String() string {
|
||||
return fmt.Sprintf("n%d = reflect.MakeSlice(n%d)", c.result, c.typ)
|
||||
}
|
||||
|
||||
func (c *reflectMakeSliceConstraint) ptr() nodeid {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c *reflectMakeSliceConstraint) solve(a *analysis, _ *node, delta nodeset) {
|
||||
changed := false
|
||||
for typObj := range delta {
|
||||
T := a.rtypeTaggedValue(typObj)
|
||||
if _, ok := T.Underlying().(*types.Slice); !ok {
|
||||
continue // not a slice type
|
||||
}
|
||||
|
||||
obj := a.nextNode()
|
||||
a.addNodes(sliceToArray(T), "reflect.MakeSlice")
|
||||
a.endObject(obj, c.cgn, nil)
|
||||
|
||||
// put its address in a new T-tagged object
|
||||
id := a.makeTagged(T, c.cgn, nil)
|
||||
a.addLabel(id+1, obj)
|
||||
|
||||
// flow the T-tagged object to the result
|
||||
if a.addLabel(c.result, id) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
a.addWork(c.result)
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰MakeSlice(a *analysis, cgn *cgnode) {
|
||||
a.addConstraint(&reflectMakeSliceConstraint{
|
||||
cgn: cgn,
|
||||
typ: a.funcParams(cgn.obj),
|
||||
result: a.funcResults(cgn.obj),
|
||||
})
|
||||
}
|
||||
|
||||
func ext۰reflect۰MapOf(a *analysis, cgn *cgnode) {}
|
||||
|
||||
// ---------- func New(Type) Value ----------
|
||||
|
||||
@ -1063,6 +1113,13 @@ func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
|
||||
for typObj := range delta {
|
||||
T := a.rtypeTaggedValue(typObj)
|
||||
|
||||
// TODO(adonovan): if T is an interface type, we need
|
||||
// to create an indirect tagged object containing
|
||||
// new(T). To avoid updates of such shared values,
|
||||
// we'll need another flag on indirect tagged values
|
||||
// that marks whether they are addressable or
|
||||
// readonly, just like the reflect package does.
|
||||
|
||||
// memoize using a.reflectZeros[T]
|
||||
var id nodeid
|
||||
if z := a.reflectZeros.At(T); false && z != nil {
|
||||
@ -1134,9 +1191,90 @@ func ext۰reflect۰rtype۰Elem(a *analysis, cgn *cgnode) {
|
||||
})
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {}
|
||||
// ---------- func (*rtype) Field(int) StructField ----------
|
||||
// ---------- func (*rtype) FieldByName(string) (StructField, bool) ----------
|
||||
|
||||
// result = FieldByName(t, name)
|
||||
// result = Field(t, _)
|
||||
type rtypeFieldByNameConstraint struct {
|
||||
cgn *cgnode
|
||||
name string // name of field; "" for unknown
|
||||
t nodeid // (ptr)
|
||||
result nodeid
|
||||
}
|
||||
|
||||
func (c *rtypeFieldByNameConstraint) String() string {
|
||||
return fmt.Sprintf("n%d = (*reflect.rtype).FieldByName(n%d, %q)", c.result, c.t, c.name)
|
||||
}
|
||||
|
||||
func (c *rtypeFieldByNameConstraint) ptr() nodeid {
|
||||
return c.t
|
||||
}
|
||||
|
||||
func (c *rtypeFieldByNameConstraint) solve(a *analysis, _ *node, delta nodeset) {
|
||||
// type StructField struct {
|
||||
// 0 __identity__
|
||||
// 1 Name string
|
||||
// 2 PkgPath string
|
||||
// 3 Type Type
|
||||
// 4 Tag StructTag
|
||||
// 5 Offset uintptr
|
||||
// 6 Index []int
|
||||
// 7 Anonymous bool
|
||||
// }
|
||||
|
||||
for tObj := range delta {
|
||||
T := a.nodes[tObj].obj.data.(types.Type)
|
||||
tStruct, ok := T.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
continue // not a struct type
|
||||
}
|
||||
|
||||
n := tStruct.NumFields()
|
||||
for i := 0; i < n; i++ {
|
||||
f := tStruct.Field(i)
|
||||
if c.name == "" || c.name == f.Name() {
|
||||
|
||||
// a.offsetOf(Type) is 3.
|
||||
if id := c.result + 3; a.addLabel(id, a.makeRtype(f.Type())) {
|
||||
a.addWork(id)
|
||||
}
|
||||
// TODO(adonovan): StructField.Index should be non-nil.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰FieldByName(a *analysis, cgn *cgnode) {
|
||||
// If we have access to the callsite,
|
||||
// and the argument is a string constant,
|
||||
// return only that field.
|
||||
var name string
|
||||
if site := cgn.callersite; site != nil {
|
||||
if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok {
|
||||
name = exact.StringVal(c.Value)
|
||||
}
|
||||
}
|
||||
|
||||
a.addConstraint(&rtypeFieldByNameConstraint{
|
||||
cgn: cgn,
|
||||
name: name,
|
||||
t: a.funcParams(cgn.obj),
|
||||
result: a.funcResults(cgn.obj),
|
||||
})
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {
|
||||
// No-one ever calls Field with a constant argument,
|
||||
// so we don't specialize that case.
|
||||
a.addConstraint(&rtypeFieldByNameConstraint{
|
||||
cgn: cgn,
|
||||
t: a.funcParams(cgn.obj),
|
||||
result: a.funcResults(cgn.obj),
|
||||
})
|
||||
}
|
||||
|
||||
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 (*rtype) In/Out() Type ----------
|
||||
@ -1264,27 +1402,6 @@ func (c *rtypeMethodByNameConstraint) ptr() nodeid {
|
||||
return c.t
|
||||
}
|
||||
|
||||
func (c *rtypeMethodByNameConstraint) addMethod(a *analysis, meth *types.Selection) {
|
||||
// type Method struct {
|
||||
// 0 __identity__
|
||||
// 1 Name string
|
||||
// 2 PkgPath string
|
||||
// 3 Type Type
|
||||
// 4 Func Value
|
||||
// 5 Index int
|
||||
// }
|
||||
fn := a.prog.Method(meth)
|
||||
|
||||
// a.offsetOf(Type) is 3.
|
||||
if id := c.result + 3; a.addLabel(id, a.makeRtype(changeRecv(fn.Signature))) {
|
||||
a.addWork(id)
|
||||
}
|
||||
// a.offsetOf(Func) is 4.
|
||||
if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) {
|
||||
a.addWork(id)
|
||||
}
|
||||
}
|
||||
|
||||
// changeRecv returns sig with Recv prepended to Params().
|
||||
func changeRecv(sig *types.Signature) *types.Signature {
|
||||
params := sig.Params()
|
||||
@ -1307,7 +1424,24 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
sel := mset.At(i)
|
||||
if c.name == "" || c.name == sel.Obj().Name() {
|
||||
c.addMethod(a, sel)
|
||||
// type Method struct {
|
||||
// 0 __identity__
|
||||
// 1 Name string
|
||||
// 2 PkgPath string
|
||||
// 3 Type Type
|
||||
// 4 Func Value
|
||||
// 5 Index int
|
||||
// }
|
||||
fn := a.prog.Method(sel)
|
||||
|
||||
// a.offsetOf(Type) is 3.
|
||||
if id := c.result + 3; a.addLabel(id, a.makeRtype(changeRecv(fn.Signature))) {
|
||||
a.addWork(id)
|
||||
}
|
||||
// a.offsetOf(Func) is 4.
|
||||
if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) {
|
||||
a.addWork(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
pointer/testdata/arrayreflect.go
vendored
15
pointer/testdata/arrayreflect.go
vendored
@ -160,6 +160,20 @@ func reflectSliceOf() {
|
||||
print(reflect.Zero(tSliceInt)) // @types []int
|
||||
}
|
||||
|
||||
type T struct{ x int }
|
||||
|
||||
func reflectMakeSlice() {
|
||||
rt := []reflect.Type{
|
||||
reflect.TypeOf(3),
|
||||
reflect.TypeOf([]int{}),
|
||||
reflect.TypeOf([]T{}),
|
||||
}[0]
|
||||
sl := reflect.MakeSlice(rt, 0, 0)
|
||||
print(sl) // @types []int | []T
|
||||
print(sl) // @pointsto <alloc in reflect.MakeSlice> | <alloc in reflect.MakeSlice>
|
||||
print(&sl.Interface().([]T)[0].x) // @pointsto <alloc in reflect.MakeSlice>[*].x
|
||||
}
|
||||
|
||||
func main() {
|
||||
reflectValueSlice()
|
||||
reflectValueBytes()
|
||||
@ -168,4 +182,5 @@ func main() {
|
||||
reflectTypeElem()
|
||||
reflectPtrTo()
|
||||
reflectSliceOf()
|
||||
reflectMakeSlice()
|
||||
}
|
||||
|
69
pointer/testdata/finalizer.go
vendored
69
pointer/testdata/finalizer.go
vendored
@ -3,7 +3,7 @@ package main
|
||||
import "runtime"
|
||||
|
||||
func final1a(x *int) int {
|
||||
print(x) // @pointsto alloc@newint:10
|
||||
print(x) // @pointsto new@newint:10
|
||||
return *x
|
||||
}
|
||||
|
||||
@ -11,24 +11,24 @@ func final1b(x *bool) {
|
||||
print(x) // @pointsto
|
||||
}
|
||||
|
||||
func setfinalizer1() {
|
||||
func runtimeSetFinalizer1() {
|
||||
x := new(int) // @line newint
|
||||
runtime.SetFinalizer(x, final1a) // ok: final1a's result is ignored
|
||||
runtime.SetFinalizer(x, final1b) // param type mismatch: no effect
|
||||
}
|
||||
|
||||
// @calls runtime.SetFinalizer -> main.final1a
|
||||
// @calls main.setfinalizer1 -> runtime.SetFinalizer
|
||||
// @calls main.runtimeSetFinalizer1 -> main.final1a
|
||||
// @calls main.runtimeSetFinalizer1 -> main.final1b
|
||||
|
||||
func final2a(x *bool) {
|
||||
print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
|
||||
print(x) // @pointsto new@newbool1:10 | new@newbool2:10
|
||||
}
|
||||
|
||||
func final2b(x *bool) {
|
||||
print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
|
||||
print(x) // @pointsto new@newbool1:10 | new@newbool2:10
|
||||
}
|
||||
|
||||
func setfinalizer2() {
|
||||
func runtimeSetFinalizer2() {
|
||||
x := new(bool) // @line newbool1
|
||||
f := final2a
|
||||
if unknown {
|
||||
@ -38,33 +38,44 @@ func setfinalizer2() {
|
||||
runtime.SetFinalizer(x, f)
|
||||
}
|
||||
|
||||
// @calls runtime.SetFinalizer -> main.final2a
|
||||
// @calls runtime.SetFinalizer -> main.final2b
|
||||
// @calls main.setfinalizer2 -> runtime.SetFinalizer
|
||||
// @calls main.runtimeSetFinalizer2 -> main.final2a
|
||||
// @calls main.runtimeSetFinalizer2 -> main.final2b
|
||||
|
||||
// type T int
|
||||
type T int
|
||||
|
||||
// func (t *T) finalize() {
|
||||
// print(t) // #@pointsto x
|
||||
// }
|
||||
|
||||
// func setfinalizer3() {
|
||||
// x := new(T)
|
||||
// runtime.SetFinalizer(x, (*T).finalize) // go/types gives wrong type to f.
|
||||
// }
|
||||
|
||||
// #@calls runtime.SetFinalizer -> (*T) finalize
|
||||
|
||||
func funcForPC() {
|
||||
f := runtime.FuncForPC(0) // @line funcforpc
|
||||
print(f) // @pointsto reflectAlloc@funcforpc:25
|
||||
func (t *T) finalize() {
|
||||
print(t) // @pointsto new@final3:10
|
||||
}
|
||||
|
||||
func runtimeSetFinalizer3() {
|
||||
x := new(T) // @line final3
|
||||
runtime.SetFinalizer(x, (*T).finalize)
|
||||
}
|
||||
|
||||
// @calls main.runtimeSetFinalizer3 -> (*main.T).finalize
|
||||
|
||||
// I hope I never live to see this code in the wild.
|
||||
var setFinalizer = runtime.SetFinalizer
|
||||
|
||||
func final4(x *int) {
|
||||
print(x) // @pointsto new@finalIndirect:10
|
||||
}
|
||||
|
||||
func runtimeSetFinalizerIndirect() {
|
||||
// In an indirect call, the shared contour for SetFinalizer is
|
||||
// used, i.e. the call is not inlined and appears in the call graph.
|
||||
x := new(int) // @line finalIndirect
|
||||
setFinalizer(x, final4)
|
||||
}
|
||||
|
||||
// @calls main.runtimeSetFinalizerIndirect -> runtime.SetFinalizer
|
||||
// @calls runtime.SetFinalizer -> main.final4
|
||||
|
||||
func main() {
|
||||
setfinalizer1()
|
||||
setfinalizer2()
|
||||
// setfinalizer3()
|
||||
funcForPC()
|
||||
runtimeSetFinalizer1()
|
||||
runtimeSetFinalizer2()
|
||||
runtimeSetFinalizer3()
|
||||
runtimeSetFinalizerIndirect()
|
||||
}
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
1
pointer/testdata/reflect.go
vendored
1
pointer/testdata/reflect.go
vendored
@ -44,6 +44,7 @@ func reflectTypeElem() {
|
||||
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
|
||||
print(reflect.Zero(reflect.TypeOf(new(interface{})).Elem()).Interface()) // @types interface{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
42
pointer/testdata/structreflect.go
vendored
42
pointer/testdata/structreflect.go
vendored
@ -4,22 +4,46 @@ package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
var a, b int
|
||||
|
||||
type A struct {
|
||||
f *int
|
||||
g interface{}
|
||||
h bool
|
||||
}
|
||||
|
||||
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()) // @types *int | bool | interface{}
|
||||
// TODO(adonovan): test promotion/embedding.
|
||||
var dyn string
|
||||
|
||||
func reflectTypeFieldByName() {
|
||||
f, _ := reflect.TypeOf(A{}).FieldByName("f")
|
||||
print(f.Type) // @pointsto *int
|
||||
|
||||
g, _ := reflect.TypeOf(A{}).FieldByName("g")
|
||||
print(g.Type) // @pointsto interface{}
|
||||
print(reflect.Zero(g.Type)) // @pointsto <alloc in reflect.Zero>
|
||||
print(reflect.Zero(g.Type)) // @types interface{}
|
||||
|
||||
// TODO(adonovan): fix: the following should return a zero
|
||||
// value of the empty interface (i.e. pts is empty), but that
|
||||
// requires fixing the TODO comment in
|
||||
// reflectZeroConstraint.solve, which in turn requires that we
|
||||
// add a "settable" flag to tagged objects.
|
||||
print(reflect.Zero(g.Type).Interface()) // @types interface{}
|
||||
|
||||
h, _ := reflect.TypeOf(A{}).FieldByName("h")
|
||||
print(h.Type) // @pointsto bool
|
||||
|
||||
missing, _ := reflect.TypeOf(A{}).FieldByName("missing")
|
||||
print(missing.Type) // @pointsto
|
||||
|
||||
dyn, _ := reflect.TypeOf(A{}).FieldByName(dyn)
|
||||
print(dyn.Type) // @pointsto *int | bool | interface{}
|
||||
}
|
||||
|
||||
func reflectTypeField() {
|
||||
fld := reflect.TypeOf(A{}).Field(0)
|
||||
print(fld.Type) // @pointsto *int | bool | interface{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
structReflect1()
|
||||
reflectTypeFieldByName()
|
||||
reflectTypeField()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user