1
0
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:
Alan Donovan 2013-10-17 09:26:44 -04:00
parent ac0a1222cb
commit 8bb20b8231
10 changed files with 423 additions and 142 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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