go.tools/pointer: reflect, part 2: channels.

(reflect.Value).Send
        (reflect.Value).TrySend
        (reflect.Value).Recv
        (reflect.Value).TryRecv
        (reflect.Type).ChanOf
        (reflect.Type).In
        (reflect.Type).Out
        reflect.Indirect
        reflect.MakeChan

Also:
- specialize genInvoke when the receiver is a reflect.Type under the
  assumption that there's only one possible concrete type.  This
  makes all reflect.Type operations context-sensitive since the calls
  are no longer dynamic.
- Rename all variables to match the actual parameter names used in
  the reflect API.
- Add pointer.Config.Reflection flag
  (exposed in oracle as --reflect, default false) to enable reflection.
  It currently adds about 20% running time.  I'll make it true after
  the presolver is implemented.
- Simplified worklist datatype and solver main loop slightly
  (~10% speed improvement).
- Use addLabel() utility to add a label to a PTS.

(Working on my 3 yr old 2x2GHz+4GB Mac vs 8x4GHz+24GB workstation,
one really notices the cost of pointer analysis.
Note to self: time to implement presolver.)

R=crawshaw
CC=golang-dev
https://golang.org/cl/13242062
This commit is contained in:
Alan Donovan 2013-09-23 16:13:01 -04:00
parent 25a0cc4bfd
commit 3371b79a96
17 changed files with 752 additions and 253 deletions

View File

@ -41,6 +41,9 @@ var ptalogFlag = flag.String("ptalog", "",
var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.") var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.")
// TODO(adonovan): eliminate or flip this flag after PTA presolver is implemented.
var reflectFlag = flag.Bool("reflect", true, "Analyze reflection soundly (slow).")
const useHelp = "Run 'oracle -help' for more information.\n" const useHelp = "Run 'oracle -help' for more information.\n"
const helpMessage = `Go source code oracle. const helpMessage = `Go source code oracle.
@ -68,8 +71,8 @@ The user manual is available here: http://golang.org/s/oracle-user-manual
Examples: Examples:
Describe the syntax at offset 670 in this file (an import spec): Describe the syntax at offset 530 in this file (an import spec):
% oracle -mode=describe -pos=src/code.google.com/p/go.tools/cmd/oracle/main.go:#670 \ % oracle -mode=describe -pos=src/code.google.com/p/go.tools/cmd/oracle/main.go:#530 \
code.google.com/p/go.tools/cmd/oracle code.google.com/p/go.tools/cmd/oracle
Print the callgraph of the trivial web-server in JSON format: Print the callgraph of the trivial web-server in JSON format:
@ -149,7 +152,7 @@ func main() {
} }
// Ask the oracle. // Ask the oracle.
res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, &build.Default) res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, &build.Default, *reflectFlag)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s\n"+useHelp, err) fmt.Fprintf(os.Stderr, "%s\n"+useHelp, err)
os.Exit(1) os.Exit(1)

View File

@ -43,7 +43,7 @@ import (
type Oracle struct { type Oracle struct {
out io.Writer // standard output out io.Writer // standard output
prog *ssa.Program // the SSA program [only populated if need&SSA] prog *ssa.Program // the SSA program [only populated if need&SSA]
config pointer.Config // pointer analysis configuration config pointer.Config // pointer analysis configuration [TODO rename ptaConfig]
// need&AllTypeInfo // need&AllTypeInfo
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program
@ -149,6 +149,7 @@ func (res *Result) MarshalJSON() ([]byte, error) {
// mode is the query mode ("callers", etc). // mode is the query mode ("callers", etc).
// ptalog is the (optional) pointer-analysis log file. // ptalog is the (optional) pointer-analysis log file.
// buildContext is the go/build configuration for locating packages. // buildContext is the go/build configuration for locating packages.
// reflection determines whether to model reflection soundly (currently slow).
// //
// Clients that intend to perform multiple queries against the same // Clients that intend to perform multiple queries against the same
// analysis scope should use this pattern instead: // analysis scope should use this pattern instead:
@ -169,14 +170,14 @@ func (res *Result) MarshalJSON() ([]byte, error) {
// TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos // TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos
// depends on the query mode; how should we expose this? // depends on the query mode; how should we expose this?
// //
func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context) (*Result, error) { func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) {
minfo := findMode(mode) minfo := findMode(mode)
if minfo == nil { if minfo == nil {
return nil, fmt.Errorf("invalid mode type: %q", mode) return nil, fmt.Errorf("invalid mode type: %q", mode)
} }
imp := importer.New(&importer.Config{Build: buildContext}) imp := importer.New(&importer.Config{Build: buildContext})
o, err := New(imp, args, ptalog) o, err := New(imp, args, ptalog, reflection)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -216,17 +217,19 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
// args specify the main package in importer.CreatePackageFromArgs syntax. // args specify the main package in importer.CreatePackageFromArgs syntax.
// //
// ptalog is the (optional) pointer-analysis log file. // ptalog is the (optional) pointer-analysis log file.
// reflection determines whether to model reflection soundly (currently slow).
// //
func New(imp *importer.Importer, args []string, ptalog io.Writer) (*Oracle, error) { func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection bool) (*Oracle, error) {
return newOracle(imp, args, ptalog, needAll) return newOracle(imp, args, ptalog, needAll, reflection)
} }
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int) (*Oracle, error) { func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{ o := &Oracle{
prog: ssa.NewProgram(imp.Fset, 0), prog: ssa.NewProgram(imp.Fset, 0),
timers: make(map[string]time.Duration), timers: make(map[string]time.Duration),
} }
o.config.Log = ptalog o.config.Log = ptalog
o.config.Reflection = reflection
// Load/parse/type-check program from args. // Load/parse/type-check program from args.
start := time.Now() start := time.Now()

View File

@ -165,7 +165,9 @@ func doQuery(out io.Writer, q *query, useJson bool) {
res, err := oracle.Query([]string{q.filename}, res, err := oracle.Query([]string{q.filename},
q.verb, q.verb,
fmt.Sprintf("%s:#%d,#%d", q.filename, q.start, q.end), fmt.Sprintf("%s:#%d,#%d", q.filename, q.start, q.end),
/*PTA-log=*/ nil, &buildContext) nil, // ptalog,
&buildContext,
true) // reflection
if err != nil { if err != nil {
fmt.Fprintf(out, "\nError: %s\n", stripLocation(err.Error())) fmt.Fprintf(out, "\nError: %s\n", stripLocation(err.Error()))
return return
@ -259,7 +261,7 @@ func TestMultipleQueries(t *testing.T) {
// Oracle // Oracle
filename := "testdata/src/main/multi.go" filename := "testdata/src/main/multi.go"
o, err := oracle.New(imp, []string{filename}, nil) o, err := oracle.New(imp, []string{filename}, nil, true)
if err != nil { if err != nil {
t.Fatalf("oracle.New failed: %s", err) t.Fatalf("oracle.New failed: %s", err)
} }

View File

@ -18,7 +18,6 @@ CONSTRAINT GENERATION:
PRESOLVER OPTIMISATIONS PRESOLVER OPTIMISATIONS
- use HVN, HRU, LE, PE, HCD, LCD. - use HVN, HRU, LE, PE, HCD, LCD.
But: LE would lose the precise detail we currently enjoy in each label.
SOLVER: SOLVER:
- use BDDs and/or sparse bitvectors for ptsets - use BDDs and/or sparse bitvectors for ptsets

View File

@ -185,7 +185,8 @@ type analysis struct {
hasher typemap.Hasher // cache of type hashes hasher typemap.Hasher // cache of type hashes
reflectValueObj types.Object // type symbol for reflect.Value (if present) reflectValueObj types.Object // type symbol for reflect.Value (if present)
reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present) reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present)
reflectRtype *types.Pointer // *reflect.rtype reflectRtypePtr *types.Pointer // *reflect.rtype
reflectType *types.Named // reflect.Type
rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T
reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value
} }
@ -244,8 +245,9 @@ func Analyze(config *Config) CallGraphNode {
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil { if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
a.reflectValueObj = reflect.Object.Scope().Lookup("Value") a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype") a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
a.reflectRtype = types.NewPointer(a.reflectRtypeObj.Type()) a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
// Override flattening of reflect.Value, treating it like a basic type. // Override flattening of reflect.Value, treating it like a basic type.
tReflectValue := a.reflectValueObj.Type() tReflectValue := a.reflectValueObj.Type()
@ -265,11 +267,7 @@ func Analyze(config *Config) CallGraphNode {
root := a.generate() root := a.generate()
// ---------- Presolver ---------- //a.optimize()
// TODO(adonovan): opt: presolver optimisations.
// ---------- Solver ----------
a.solve() a.solve()

View File

@ -20,6 +20,13 @@ type Config struct {
Mains []*ssa.Package // set of 'main' packages to analyze Mains []*ssa.Package // set of 'main' packages to analyze
root *ssa.Function // synthetic analysis root root *ssa.Function // synthetic analysis root
// Reflection determines whether to handle reflection
// operators soundly, which is currently rather slow since it
// causes constraint to be generated during solving
// proportional to the number of constraint variables, which
// has not yet been reduced by presolver optimisation.
Reflection bool
// -------- Optional callbacks invoked by the analysis -------- // -------- Optional callbacks invoked by the analysis --------
// Call is invoked for each discovered call-graph edge. The // Call is invoked for each discovered call-graph edge. The

View File

@ -10,11 +10,13 @@ pointer analysis algorithm first described in (Andersen, 1994).
The implementation is similar to that described in (Pearce et al, The implementation is similar to that described in (Pearce et al,
PASTE'04). Unlike many algorithms which interleave constraint PASTE'04). Unlike many algorithms which interleave constraint
generation and solving, constructing the callgraph as they go, this generation and solving, constructing the callgraph as they go, this
implementation has a strict phase ordering: generation before solving. implementation for the most part observes a phase ordering (generation
Only simple (copy) constraints may be generated during solving. This before solving), with only simple (copy) constraints being generated
improves the traction of presolver optimisations, but imposes certain during solving. (The exception is reflection, which creates various
restrictions, e.g. potential context sensitivity is limited since all constraints during solving as new types flow to reflect.Value
variants must be created a priori. operations.) This improves the traction of presolver optimisations,
but imposes certain restrictions, e.g. potential context sensitivity
is limited since all variants must be created a priori.
We intend to add various presolving optimisations such as Pointer and We intend to add various presolving optimisations such as Pointer and
Location Equivalence from (Hardekopf & Lin, SAS '07) and solver Location Equivalence from (Hardekopf & Lin, SAS '07) and solver

View File

@ -235,7 +235,7 @@ func (a *analysis) makeRtype(T types.Type) nodeid {
a.addOneNode(T, "reflect.rtype", nil) a.addOneNode(T, "reflect.rtype", nil)
a.endObject(obj, nil, nil).rtype = T a.endObject(obj, nil, nil).rtype = T
id := a.makeTagged(a.reflectRtype, nil, nil) id := a.makeTagged(a.reflectRtypePtr, nil, nil)
a.nodes[id].obj.rtype = T a.nodes[id].obj.rtype = T
a.nodes[id+1].typ = T // trick (each *rtype tagged object is a singleton) a.nodes[id+1].typ = T // trick (each *rtype tagged object is a singleton)
a.addressOf(id+1, obj) a.addressOf(id+1, obj)
@ -244,6 +244,15 @@ func (a *analysis) makeRtype(T types.Type) nodeid {
return id return id
} }
// rtypeValue returns the type of the *reflect.rtype-tagged object obj.
func (a *analysis) rtypeTaggedValue(obj nodeid) types.Type {
tDyn, t, _ := a.taggedValue(obj)
if tDyn != a.reflectRtypePtr {
panic(fmt.Sprintf("not a *reflect.rtype-tagged value: obj=n%d tag=%v payload=n%d", obj, tDyn, t))
}
return a.nodes[t].typ
}
// valueNode returns the id of the value node for v, creating it (and // valueNode returns the id of the value node for v, creating it (and
// the association) as needed. It may return zero for uninteresting // the association) as needed. It may return zero for uninteresting
// values containing no pointers. // values containing no pointers.
@ -432,6 +441,11 @@ func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) {
} }
} }
// typeAssert creates a typeAssert constraint of the form dst = src.(T).
func (a *analysis) typeAssert(T types.Type, dst, src nodeid) {
a.addConstraint(&typeAssertConstraint{T, dst, src})
}
// addConstraint adds c to the constraint set. // addConstraint adds c to the constraint set.
func (a *analysis) addConstraint(c constraint) { func (a *analysis) addConstraint(c constraint) {
a.constraints = append(a.constraints, c) a.constraints = append(a.constraints, c)
@ -720,13 +734,11 @@ func (a *analysis) genDynamicCall(call *ssa.CallCommon, result nodeid) nodeid {
// It returns a node whose pts() will be the set of possible call targets. // It returns a node whose pts() will be the set of possible call targets.
// //
func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid { func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
sig := call.Signature() if call.Value.Type() == a.reflectType {
return a.genInvokeReflectType(call, result)
}
// TODO(adonovan): optimise this into a static call when there sig := call.Signature()
// 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. // Allocate a contiguous targets/params/results block for this call.
block := a.nextNode() block := a.nextNode()
@ -753,6 +765,63 @@ func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
return targets return targets
} }
// genInvokeReflectType is a specialization of genInvoke where the
// receiver type is a reflect.Type, under the assumption that there
// can be at most one implementation of this interface, *reflect.rtype.
//
// (Though this may appear to be an instance of a pattern---method
// calls on interfaces known to have exactly one implementation---in
// practice it occurs rarely, so we special case for reflect.Type.)
//
// In effect we treat this:
// var rt reflect.Type = ...
// rt.F()
// as this:
// rt.(*reflect.rtype).F()
//
// It returns a node whose pts() will be the (singleton) set of
// possible call targets.
//
func (a *analysis) genInvokeReflectType(call *ssa.CallCommon, result nodeid) nodeid {
// Unpack receiver into rtype
rtype := a.addOneNode(a.reflectRtypePtr, "rtype.recv", nil)
recv := a.valueNode(call.Value)
a.typeAssert(a.reflectRtypePtr, rtype, recv)
// Look up the concrete method.
meth := a.reflectRtypePtr.MethodSet().Lookup(call.Method.Pkg(), call.Method.Name())
fn := a.prog.Method(meth)
obj := a.makeFunctionObject(fn) // new contour for this call
// From now on, it's essentially a static call, but little is
// gained by factoring together the code for both cases.
sig := fn.Signature // concrete method
targets := a.addOneNode(sig, "call.targets", nil)
a.addressOf(targets, obj) // (a singleton)
// Copy receiver.
params := a.funcParams(obj)
a.copy(params, rtype, 1)
params++
// Copy actual parameters into formal params block.
// Must loop, since the actuals aren't contiguous.
for i, arg := range call.Args {
sz := a.sizeof(sig.Params().At(i).Type())
a.copy(params, a.valueNode(arg), sz)
params += nodeid(sz)
}
// Copy formal results block to actual result.
if result != 0 {
a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
}
return obj
}
// genCall generates contraints for call instruction instr. // genCall generates contraints for call instruction instr.
func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) {
call := instr.Common() call := instr.Common()
@ -927,8 +996,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
case *ssa.TypeAssert: case *ssa.TypeAssert:
dst, src := a.valueNode(instr), a.valueNode(instr.X) a.typeAssert(instr.AssertedType, a.valueNode(instr), a.valueNode(instr.X))
a.addConstraint(&typeAssertConstraint{instr.AssertedType, dst, src})
case *ssa.Slice: case *ssa.Slice:
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
@ -1117,7 +1185,9 @@ func (a *analysis) generate() *cgnode {
a.panicNode = a.addNodes(tEface, "panic") a.panicNode = a.addNodes(tEface, "panic")
// Create nodes and constraints for all methods of reflect.rtype. // Create nodes and constraints for all methods of reflect.rtype.
if rtype := a.reflectRtype; rtype != nil { // (Shared contours are used by dynamic calls to reflect.Type
// methods---typically just String().)
if rtype := a.reflectRtypePtr; rtype != nil {
mset := rtype.MethodSet() mset := rtype.MethodSet()
for i, n := 0, mset.Len(); i < n; i++ { for i, n := 0, mset.Len(); i < n; i++ {
a.valueNode(a.prog.Method(mset.At(i))) a.valueNode(a.prog.Method(mset.At(i)))
@ -1135,9 +1205,5 @@ func (a *analysis) generate() *cgnode {
a.genFunc(cgn) a.genFunc(cgn)
} }
// Create a dummy node to avoid out-of-range indexing in case
// the last allocated type was of zero length.
a.addNodes(tInvalid, "(max)")
return root return root
} }

View File

@ -70,6 +70,8 @@ func init() {
"(reflect.Value).OverflowInt": ext۰NoEffect, "(reflect.Value).OverflowInt": ext۰NoEffect,
"(reflect.Value).OverflowUint": ext۰NoEffect, "(reflect.Value).OverflowUint": ext۰NoEffect,
"(reflect.Value).Pointer": ext۰NoEffect, "(reflect.Value).Pointer": ext۰NoEffect,
"(reflect.Value).Recv": ext۰reflect۰Value۰Recv,
"(reflect.Value).Send": ext۰reflect۰Value۰Send,
"(reflect.Value).Set": ext۰reflect۰Value۰Set, "(reflect.Value).Set": ext۰reflect۰Value۰Set,
"(reflect.Value).SetBool": ext۰NoEffect, "(reflect.Value).SetBool": ext۰NoEffect,
"(reflect.Value).SetBytes": ext۰reflect۰Value۰SetBytes, "(reflect.Value).SetBytes": ext۰reflect۰Value۰SetBytes,
@ -83,6 +85,8 @@ func init() {
"(reflect.Value).SetUint": ext۰NoEffect, "(reflect.Value).SetUint": ext۰NoEffect,
"(reflect.Value).Slice": ext۰reflect۰Value۰Slice, "(reflect.Value).Slice": ext۰reflect۰Value۰Slice,
"(reflect.Value).String": ext۰NoEffect, "(reflect.Value).String": ext۰NoEffect,
"(reflect.Value).TryRecv": ext۰reflect۰Value۰Recv,
"(reflect.Value).TrySend": ext۰reflect۰Value۰Send,
"(reflect.Value).Type": ext۰NoEffect, "(reflect.Value).Type": ext۰NoEffect,
"(reflect.Value).Uint": ext۰NoEffect, "(reflect.Value).Uint": ext۰NoEffect,
"(reflect.Value).UnsafeAddr": ext۰NoEffect, "(reflect.Value).UnsafeAddr": ext۰NoEffect,
@ -275,9 +279,13 @@ func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
if !ok { if !ok {
impl = intrinsicsByName[fn.String()] // may be nil impl = intrinsicsByName[fn.String()] // may be nil
// Ensure all "reflect" code is treated intrinsically. if fn.Pkg != nil && a.reflectValueObj != nil && a.reflectValueObj.Pkg() == fn.Pkg.Object {
if impl == nil && fn.Pkg != nil && a.reflectValueObj != nil && a.reflectValueObj.Pkg() == fn.Pkg.Object { if !a.config.Reflection {
impl = ext۰NotYetImplemented impl = ext۰NoEffect // reflection disabled
} else if impl == nil {
// Ensure all "reflect" code is treated intrinsically.
impl = ext۰NotYetImplemented
}
} }
a.intrinsics[fn] = impl a.intrinsics[fn] = impl

View File

@ -33,9 +33,11 @@ var inputs = []string{
// "testdata/tmp.go", // "testdata/tmp.go",
// Working: // Working:
"testdata/a_test.go",
"testdata/another.go", "testdata/another.go",
"testdata/arrays.go", "testdata/arrays.go",
"testdata/channels.go", "testdata/channels.go",
"testdata/chanreflect.go",
"testdata/context.go", "testdata/context.go",
"testdata/conv.go", "testdata/conv.go",
"testdata/flow.go", "testdata/flow.go",
@ -43,21 +45,19 @@ var inputs = []string{
"testdata/func.go", "testdata/func.go",
"testdata/hello.go", "testdata/hello.go",
"testdata/interfaces.go", "testdata/interfaces.go",
"testdata/funcreflect.go",
"testdata/mapreflect.go",
"testdata/maps.go", "testdata/maps.go",
"testdata/panic.go", "testdata/panic.go",
"testdata/recur.go", "testdata/recur.go",
"testdata/reflect.go",
"testdata/structs.go", "testdata/structs.go",
"testdata/a_test.go",
"testdata/mapreflect.go",
// TODO(adonovan): get these tests (of reflection) passing. // TODO(adonovan): get these tests (of reflection) passing.
// (The tests are mostly sound since they were used for a // (The tests are mostly sound since they were used for a
// previous implementation.) // previous implementation.)
// "testdata/funcreflect.go",
// "testdata/arrayreflect.go", // "testdata/arrayreflect.go",
// "testdata/chanreflect.go",
// "testdata/finalizer.go", // "testdata/finalizer.go",
// "testdata/reflect.go",
// "testdata/structreflect.go", // "testdata/structreflect.go",
} }
@ -290,8 +290,9 @@ func doOneInput(input, filename string) bool {
// Run the analysis. // Run the analysis.
config := &pointer.Config{ config := &pointer.Config{
Mains: []*ssa.Package{ptrmain}, Reflection: true,
Log: &log, Mains: []*ssa.Package{ptrmain},
Log: &log,
Print: func(site *ssa.CallCommon, p pointer.Pointer) { Print: func(site *ssa.CallCommon, p pointer.Pointer) {
probes = append(probes, probe{site, p}) probes = append(probes, probe{site, p})
}, },

View File

@ -4,10 +4,14 @@ package pointer
// constraints arising from the use of reflection in the target // constraints arising from the use of reflection in the target
// program. See doc.go for explanation of the representation. // program. See doc.go for explanation of the representation.
// //
// For consistency, the names of all parameters match those of the
// actual functions in the "reflect" package.
//
// TODO(adonovan): fix: most of the reflect API permits implicit // TODO(adonovan): fix: most of the reflect API permits implicit
// conversions due to assignability, e.g. m.MapIndex(k) is ok if T(k) // 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 // is assignable to T(M).key. It's not yet clear how best to model
// that. // that; perhaps a more lenient version of typeAssertConstraint is
// needed.
// //
// To avoid proliferation of equivalent labels, instrinsics should // To avoid proliferation of equivalent labels, instrinsics should
// memoize as much as possible, like TypeOf and Zero do for their // memoize as much as possible, like TypeOf and Zero do for their
@ -17,6 +21,7 @@ package pointer
import ( import (
"fmt" "fmt"
"go/ast"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
@ -37,25 +42,25 @@ func ext۰reflect۰Value۰Index(a *analysis, cgn *cgnode) {}
// ---------- func (Value).Interface() Value ---------- // ---------- func (Value).Interface() Value ----------
// result = rv.Interface() // result = v.Interface()
type rVInterfaceConstraint struct { type rVInterfaceConstraint struct {
rv nodeid // (ptr) v nodeid // (ptr)
result nodeid result nodeid
} }
func (c *rVInterfaceConstraint) String() string { func (c *rVInterfaceConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Interface()", c.result, c.rv) return fmt.Sprintf("n%d = reflect n%d.Interface()", c.result, c.v)
} }
func (c *rVInterfaceConstraint) ptr() nodeid { func (c *rVInterfaceConstraint) ptr() nodeid {
return c.rv return c.v
} }
func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) {
resultPts := &a.nodes[c.result].pts resultPts := &a.nodes[c.result].pts
changed := false changed := false
for obj := range delta { for vObj := range delta {
tDyn, _, indirect := a.taggedValue(obj) tDyn, _, indirect := a.taggedValue(vObj)
if tDyn == nil { if tDyn == nil {
panic("not a tagged object") panic("not a tagged object")
} }
@ -65,7 +70,7 @@ func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) {
panic("indirect tagged object") panic("indirect tagged object")
} }
if resultPts.add(obj) { if resultPts.add(vObj) {
changed = true changed = true
} }
} }
@ -76,33 +81,33 @@ func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) {
func ext۰reflect۰Value۰Interface(a *analysis, cgn *cgnode) { func ext۰reflect۰Value۰Interface(a *analysis, cgn *cgnode) {
a.addConstraint(&rVInterfaceConstraint{ a.addConstraint(&rVInterfaceConstraint{
rv: a.funcParams(cgn.obj), v: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj), result: a.funcResults(cgn.obj),
}) })
} }
// ---------- func (Value).MapIndex(Value) Value ---------- // ---------- func (Value).MapIndex(Value) Value ----------
// result = rv.MapIndex(key) // result = v.MapIndex(_)
type rVMapIndexConstraint struct { type rVMapIndexConstraint struct {
cgn *cgnode cgn *cgnode
rv nodeid // (ptr) v nodeid // (ptr)
result nodeid result nodeid
} }
func (c *rVMapIndexConstraint) String() string { func (c *rVMapIndexConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapIndex(_)", c.result, c.rv) return fmt.Sprintf("n%d = reflect n%d.MapIndex(_)", c.result, c.v)
} }
func (c *rVMapIndexConstraint) ptr() nodeid { func (c *rVMapIndexConstraint) ptr() nodeid {
return c.rv return c.v
} }
func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false changed := false
for obj := range delta { for vObj := range delta {
tDyn, m, indirect := a.taggedValue(obj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { if tMap == nil {
continue // not a map continue // not a map
} }
@ -112,9 +117,9 @@ func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
panic("indirect tagged object") panic("indirect tagged object")
} }
vObj := a.makeTagged(tMap.Elem(), c.cgn, nil) obj := a.makeTagged(tMap.Elem(), c.cgn, nil)
a.loadOffset(vObj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem())) a.loadOffset(obj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem()))
if a.nodes[c.result].pts.add(vObj) { if a.addLabel(c.result, obj) {
changed = true changed = true
} }
} }
@ -126,33 +131,33 @@ func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
func ext۰reflect۰Value۰MapIndex(a *analysis, cgn *cgnode) { func ext۰reflect۰Value۰MapIndex(a *analysis, cgn *cgnode) {
a.addConstraint(&rVMapIndexConstraint{ a.addConstraint(&rVMapIndexConstraint{
cgn: cgn, cgn: cgn,
rv: a.funcParams(cgn.obj), v: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj), result: a.funcResults(cgn.obj),
}) })
} }
// ---------- func (Value).MapKeys() []Value ---------- // ---------- func (Value).MapKeys() []Value ----------
// result = rv.MapKeys() // result = v.MapKeys()
type rVMapKeysConstraint struct { type rVMapKeysConstraint struct {
cgn *cgnode cgn *cgnode
rv nodeid // (ptr) v nodeid // (ptr)
result nodeid result nodeid
} }
func (c *rVMapKeysConstraint) String() string { func (c *rVMapKeysConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapKeys()", c.result, c.rv) return fmt.Sprintf("n%d = reflect n%d.MapKeys()", c.result, c.v)
} }
func (c *rVMapKeysConstraint) ptr() nodeid { func (c *rVMapKeysConstraint) ptr() nodeid {
return c.rv return c.v
} }
func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false changed := false
for obj := range delta { for vObj := range delta {
tDyn, m, indirect := a.taggedValue(obj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { if tMap == nil {
continue // not a map continue // not a map
} }
@ -164,7 +169,7 @@ func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) {
kObj := a.makeTagged(tMap.Key(), c.cgn, nil) kObj := a.makeTagged(tMap.Key(), c.cgn, nil)
a.load(kObj+1, m, a.sizeof(tMap.Key())) a.load(kObj+1, m, a.sizeof(tMap.Key()))
if a.nodes[c.result].pts.add(kObj) { if a.addLabel(c.result, kObj) {
changed = true changed = true
} }
} }
@ -180,41 +185,139 @@ func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) {
a.endObject(obj, cgn, nil) a.endObject(obj, cgn, nil)
a.addressOf(a.funcResults(cgn.obj), obj) a.addressOf(a.funcResults(cgn.obj), obj)
// resolution rule attached to rv
a.addConstraint(&rVMapKeysConstraint{ a.addConstraint(&rVMapKeysConstraint{
cgn: cgn, cgn: cgn,
rv: a.funcParams(cgn.obj), v: a.funcParams(cgn.obj),
result: obj + 1, // result is stored in array elems result: obj + 1, // result is stored in array elems
}) })
} }
func ext۰reflect۰Value۰Method(a *analysis, cgn *cgnode) {} func ext۰reflect۰Value۰Method(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰MethodByName(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).Recv(Value) ----------
// result, _ = v.Recv()
type rVRecvConstraint struct {
cgn *cgnode
v nodeid // (ptr)
result nodeid
}
func (c *rVRecvConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Recv()", c.result, c.v)
}
func (c *rVRecvConstraint) ptr() nodeid {
return c.v
}
func (c *rVRecvConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for vObj := range delta {
tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil {
continue // not a channel
}
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.
panic("indirect tagged object")
}
tElem := tChan.Elem()
elemObj := a.makeTagged(tElem, c.cgn, nil)
a.load(elemObj+1, ch, a.sizeof(tElem))
if a.addLabel(c.result, elemObj) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Value۰Recv(a *analysis, cgn *cgnode) {
a.addConstraint(&rVRecvConstraint{
cgn: cgn,
v: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// ---------- func (Value).Send(Value) ----------
// v.Send(x)
type rVSendConstraint struct {
cgn *cgnode
v nodeid // (ptr)
x nodeid
}
func (c *rVSendConstraint) String() string {
return fmt.Sprintf("reflect n%d.Send(n%d)", c.v, c.x)
}
func (c *rVSendConstraint) ptr() nodeid {
return c.v
}
func (c *rVSendConstraint) solve(a *analysis, _ *node, delta nodeset) {
for vObj := range delta {
tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil {
continue // not a channel
}
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.
panic("indirect tagged object")
}
// Extract x's payload to xtmp, then store to channel.
tElem := tChan.Elem()
xtmp := a.addNodes(tElem, "Send.xtmp")
a.typeAssert(tElem, xtmp, c.x)
a.store(ch, xtmp, a.sizeof(tElem))
}
}
func ext۰reflect۰Value۰Send(a *analysis, cgn *cgnode) {
params := a.funcParams(cgn.obj)
a.addConstraint(&rVSendConstraint{
cgn: cgn,
v: params,
x: params + 1,
})
}
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) ---------- // ---------- func (Value).SetMapIndex(k Value, v Value) ----------
// rv.SetMapIndex(k, v) // v.SetMapIndex(key, val)
type rVSetMapIndexConstraint struct { type rVSetMapIndexConstraint struct {
cgn *cgnode cgn *cgnode
rv nodeid // (ptr) v nodeid // (ptr)
k nodeid key nodeid
v nodeid val nodeid
} }
func (c *rVSetMapIndexConstraint) String() string { func (c *rVSetMapIndexConstraint) String() string {
return fmt.Sprintf("reflect n%d.SetMapIndex(n%d, n%d)", c.rv, c.k, c.v) return fmt.Sprintf("reflect n%d.SetMapIndex(n%d, n%d)", c.v, c.key, c.val)
} }
func (c *rVSetMapIndexConstraint) ptr() nodeid { func (c *rVSetMapIndexConstraint) ptr() nodeid {
return c.rv return c.v
} }
func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
for obj := range delta { for vObj := range delta {
tDyn, m, indirect := a.taggedValue(obj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { if tMap == nil {
continue // not a map continue // not a map
} }
@ -224,28 +327,27 @@ func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
panic("indirect tagged object") panic("indirect tagged object")
} }
ksize := a.sizeof(tMap.Key()) keysize := a.sizeof(tMap.Key())
// Extract k Value's payload to ktmp, then store to map key. // Extract key's payload to keytmp, then store to map key.
ktmp := a.addNodes(tMap.Key(), "SetMapIndex.ktmp") keytmp := a.addNodes(tMap.Key(), "SetMapIndex.keytmp")
a.addConstraint(&typeAssertConstraint{tMap.Key(), ktmp, c.k}) a.typeAssert(tMap.Key(), keytmp, c.key)
a.store(m, ktmp, ksize) a.store(m, keytmp, keysize)
// Extract v Value's payload to vtmp, then store to map value. // Extract val's payload to vtmp, then store to map value.
vtmp := a.addNodes(tMap.Elem(), "SetMapIndex.vtmp") valtmp := a.addNodes(tMap.Elem(), "SetMapIndex.valtmp")
a.addConstraint(&typeAssertConstraint{tMap.Elem(), vtmp, c.v}) a.typeAssert(tMap.Elem(), valtmp, c.val)
a.storeOffset(m, vtmp, ksize, a.sizeof(tMap.Elem())) a.storeOffset(m, valtmp, keysize, a.sizeof(tMap.Elem()))
} }
} }
func ext۰reflect۰Value۰SetMapIndex(a *analysis, cgn *cgnode) { func ext۰reflect۰Value۰SetMapIndex(a *analysis, cgn *cgnode) {
// resolution rule attached to rv params := a.funcParams(cgn.obj)
rv := a.funcParams(cgn.obj)
a.addConstraint(&rVSetMapIndexConstraint{ a.addConstraint(&rVSetMapIndexConstraint{
cgn: cgn, cgn: cgn,
rv: rv, v: params,
k: rv + 1, key: params + 1,
v: rv + 2, val: params + 2,
}) })
} }
@ -257,45 +359,300 @@ func ext۰reflect۰Value۰Slice(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Append(a *analysis, cgn *cgnode) {} func ext۰reflect۰Append(a *analysis, cgn *cgnode) {}
func ext۰reflect۰AppendSlice(a *analysis, cgn *cgnode) {} func ext۰reflect۰AppendSlice(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Copy(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 ---------- // ---------- func ChanOf(ChanDir, Type) Type ----------
// result = TypeOf(v) // result = ChanOf(_, t)
type reflectTypeOfConstraint struct { type reflectChanOfConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
}
func (c *reflectChanOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.ChanOf(n%d)", c.result, c.t)
}
func (c *reflectChanOfConstraint) ptr() nodeid {
return c.t
}
func (c *reflectChanOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for tObj := range delta {
T := a.rtypeTaggedValue(tObj)
// TODO(adonovan): use only the channel direction
// provided at the callsite, if constant.
for _, dir := range []ast.ChanDir{1, 2, 3} {
if a.addLabel(c.result, a.makeRtype(types.NewChan(dir, T))) {
changed = true
}
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰ChanOf(a *analysis, cgn *cgnode) {
params := a.funcParams(cgn.obj)
a.addConstraint(&reflectChanOfConstraint{
cgn: cgn,
t: params + 1,
result: a.funcResults(cgn.obj),
})
}
// ---------- func Indirect(v Value) Value ----------
// result = Indirect(v)
type reflectIndirectConstraint struct {
cgn *cgnode cgn *cgnode
v nodeid // (ptr) v nodeid // (ptr)
result nodeid result nodeid
} }
func (c *reflectTypeOfConstraint) String() string { func (c *reflectIndirectConstraint) String() string {
return fmt.Sprintf("n%d = reflect.TypeOf(n%d)", c.result, c.v) return fmt.Sprintf("n%d = reflect.Indirect(n%d)", c.result, c.v)
} }
func (c *reflectTypeOfConstraint) ptr() nodeid { func (c *reflectIndirectConstraint) ptr() nodeid {
return c.v return c.v
} }
func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *reflectIndirectConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false changed := false
for obj := range delta { for vObj := range delta {
tDyn, _, _ := a.taggedValue(obj) tDyn, _, _ := a.taggedValue(vObj)
if tDyn == nil { if tDyn == nil {
panic("not a tagged value") panic("not a tagged value")
} }
if a.nodes[c.result].pts.add(a.makeRtype(tDyn)) { var res nodeid
if tPtr, ok := tDyn.Underlying().(*types.Pointer); ok {
// load the payload of the pointer's tagged object
// into a new tagged object
res = a.makeTagged(tPtr.Elem(), c.cgn, nil)
a.load(res+1, vObj+1, a.sizeof(tPtr.Elem()))
} else {
res = vObj
}
if a.addLabel(c.result, res) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Indirect(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectIndirectConstraint{
cgn: cgn,
v: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// ---------- func MakeChan(Type) Value ----------
// result = MakeChan(typ)
type reflectMakeChanConstraint struct {
cgn *cgnode
typ nodeid // (ptr)
result nodeid
}
func (c *reflectMakeChanConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeChan(n%d)", c.result, c.typ)
}
func (c *reflectMakeChanConstraint) ptr() nodeid {
return c.typ
}
func (c *reflectMakeChanConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for typObj := range delta {
T := a.rtypeTaggedValue(typObj)
tChan, ok := T.Underlying().(*types.Chan)
if !ok || tChan.Dir() != ast.SEND|ast.RECV {
continue // not a bidirectional channel type
}
obj := a.nextNode()
a.addNodes(tChan.Elem(), "reflect.MakeChan.value")
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۰MakeChan(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectMakeChanConstraint{
cgn: cgn,
typ: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
func ext۰reflect۰MakeFunc(a *analysis, cgn *cgnode) {}
// ---------- func MakeMap(Type) Value ----------
// result = MakeMap(typ)
type reflectMakeMapConstraint struct {
cgn *cgnode
typ nodeid // (ptr)
result nodeid
}
func (c *reflectMakeMapConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeMap(n%d)", c.result, c.typ)
}
func (c *reflectMakeMapConstraint) ptr() nodeid {
return c.typ
}
func (c *reflectMakeMapConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for typObj := range delta {
T := a.rtypeTaggedValue(typObj)
tMap, ok := T.Underlying().(*types.Map)
if !ok {
continue // not a map type
}
mapObj := a.nextNode()
a.addNodes(tMap.Key(), "reflect.MakeMap.key")
a.addNodes(tMap.Elem(), "reflect.MakeMap.value")
a.endObject(mapObj, c.cgn, nil)
// put its address in a new T-tagged object
id := a.makeTagged(T, c.cgn, nil)
a.addLabel(id+1, mapObj)
// 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۰MakeMap(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectMakeMapConstraint{
cgn: cgn,
typ: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
func ext۰reflect۰MakeSlice(a *analysis, cgn *cgnode) {}
func ext۰reflect۰MapOf(a *analysis, cgn *cgnode) {}
// ---------- func New(Type) Value ----------
// result = New(typ)
type reflectNewConstraint struct {
cgn *cgnode
typ nodeid // (ptr)
result nodeid
}
func (c *reflectNewConstraint) String() string {
return fmt.Sprintf("n%d = reflect.New(n%d)", c.result, c.typ)
}
func (c *reflectNewConstraint) ptr() nodeid {
return c.typ
}
func (c *reflectNewConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for typObj := range delta {
T := a.rtypeTaggedValue(typObj)
// allocate new T object
newObj := a.nextNode()
a.addNodes(T, "reflect.New")
a.endObject(newObj, c.cgn, nil)
// put its address in a new *T-tagged object
id := a.makeTagged(types.NewPointer(T), c.cgn, nil)
a.addLabel(id+1, newObj)
// flow the pointer to the result
if a.addLabel(c.result, id) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰New(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectNewConstraint{
cgn: cgn,
typ: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
func ext۰reflect۰NewAt(a *analysis, cgn *cgnode) {
ext۰reflect۰New(a, cgn)
// TODO(adonovan): make it easier to report errors of this form,
// which includes the callsite:
// a.warnf("unsound: main.reflectNewAt contains a reflect.NewAt() call")
a.warnf(cgn.Func().Pos(), "unsound: reflect.NewAt() call")
}
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(i)
type reflectTypeOfConstraint struct {
cgn *cgnode
i nodeid // (ptr)
result nodeid
}
func (c *reflectTypeOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.TypeOf(n%d)", c.result, c.i)
}
func (c *reflectTypeOfConstraint) ptr() nodeid {
return c.i
}
func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for iObj := range delta {
tDyn, _, _ := a.taggedValue(iObj)
if tDyn == nil {
panic("not a tagged value")
}
if a.addLabel(c.result, a.makeRtype(tDyn)) {
changed = true changed = true
} }
} }
@ -307,7 +664,7 @@ func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
func ext۰reflect۰TypeOf(a *analysis, cgn *cgnode) { func ext۰reflect۰TypeOf(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectTypeOfConstraint{ a.addConstraint(&reflectTypeOfConstraint{
cgn: cgn, cgn: cgn,
v: a.funcParams(cgn.obj), i: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj), result: a.funcResults(cgn.obj),
}) })
} }
@ -323,29 +680,25 @@ func ext۰reflect۰ValueOf(a *analysis, cgn *cgnode) {
// ---------- func Zero(Type) Value ---------- // ---------- func Zero(Type) Value ----------
// result = Zero(t) // result = Zero(typ)
type reflectZeroConstraint struct { type reflectZeroConstraint struct {
cgn *cgnode cgn *cgnode
t nodeid // (ptr) typ nodeid // (ptr)
result nodeid result nodeid
} }
func (c *reflectZeroConstraint) String() string { func (c *reflectZeroConstraint) String() string {
return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.t) return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.typ)
} }
func (c *reflectZeroConstraint) ptr() nodeid { func (c *reflectZeroConstraint) ptr() nodeid {
return c.t return c.typ
} }
func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false changed := false
for obj := range delta { for typObj := range delta {
tDyn, v, _ := a.taggedValue(obj) T := a.rtypeTaggedValue(typObj)
if tDyn != a.reflectRtype {
panic("not a *reflect.rtype-tagged value")
}
T := a.nodes[v].typ
// memoize using a.reflectZeros[T] // memoize using a.reflectZeros[T]
var id nodeid var id nodeid
@ -355,7 +708,7 @@ func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
id = a.makeTagged(T, c.cgn, nil) id = a.makeTagged(T, c.cgn, nil)
a.reflectZeros.Set(T, id) a.reflectZeros.Set(T, id)
} }
if a.nodes[c.result].pts.add(id) { if a.addLabel(c.result, id) {
changed = true changed = true
} }
} }
@ -367,7 +720,7 @@ func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
func ext۰reflect۰Zero(a *analysis, cgn *cgnode) { func ext۰reflect۰Zero(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectZeroConstraint{ a.addConstraint(&reflectZeroConstraint{
cgn: cgn, cgn: cgn,
t: a.funcParams(cgn.obj), typ: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj), result: a.funcResults(cgn.obj),
}) })
} }
@ -392,15 +745,15 @@ func (c *rtypeElemConstraint) ptr() nodeid {
} }
func (c *rtypeElemConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rtypeElemConstraint) solve(a *analysis, _ *node, delta nodeset) {
// Implemented by *types.{Map,Chan,Array,Slice,Pointer}.
type hasElem interface {
Elem() types.Type
}
changed := false changed := false
for obj := range delta { for tObj := range delta {
T := a.nodes[obj].typ // assume obj is an *rtype T := a.nodes[tObj].obj.rtype
if tHasElem, ok := T.Underlying().(hasElem); ok {
// Works for *types.{Map,Chan,Array,Slice,Pointer}. if a.addLabel(c.result, a.makeRtype(tHasElem.Elem())) {
if T, ok := T.Underlying().(interface {
Elem() types.Type
}); ok {
if a.nodes[c.result].pts.add(a.makeRtype(T.Elem())) {
changed = true changed = true
} }
} }
@ -422,7 +775,71 @@ func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰FieldByIndex(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۰FieldByName(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰FieldByNameFunc(a *analysis, cgn *cgnode) {} func ext۰reflect۰rtype۰FieldByNameFunc(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰In(a *analysis, cgn *cgnode) {}
// ---------- func (*rtype) In/Out() Type ----------
// result = In/Out(t)
type rtypeInOutConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
out bool
}
func (c *rtypeInOutConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).InOut(n%d)", c.result, c.t)
}
func (c *rtypeInOutConstraint) ptr() nodeid {
return c.t
}
func (c *rtypeInOutConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for tObj := range delta {
T := a.nodes[tObj].obj.rtype
sig, ok := T.Underlying().(*types.Signature)
if !ok {
continue // not a func type
}
tuple := sig.Params()
if c.out {
tuple = sig.Results()
}
// TODO(adonovan): when a function is analyzed
// context-sensitively, we should be able to see its
// caller's actual parameter's ssa.Values. Refactor
// the intrinsic mechanism to allow this. Then if the
// value is an int const K, skip the loop and use
// tuple.At(K).
for i, n := 0, tuple.Len(); i < n; i++ {
if a.addLabel(c.result, a.makeRtype(tuple.At(i).Type())) {
changed = true
}
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰rtype۰InOut(a *analysis, cgn *cgnode, out bool) {
a.addConstraint(&rtypeInOutConstraint{
cgn: cgn,
t: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
out: out,
})
}
func ext۰reflect۰rtype۰In(a *analysis, cgn *cgnode) {
ext۰reflect۰rtype۰InOut(a, cgn, false)
}
func ext۰reflect۰rtype۰Out(a *analysis, cgn *cgnode) {
ext۰reflect۰rtype۰InOut(a, cgn, true)
}
// ---------- func (*rtype) Key() Type ---------- // ---------- func (*rtype) Key() Type ----------
@ -443,11 +860,10 @@ func (c *rtypeKeyConstraint) ptr() nodeid {
func (c *rtypeKeyConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rtypeKeyConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false changed := false
for obj := range delta { for tObj := range delta {
T := a.nodes[obj].typ // assume obj is an *rtype T := a.nodes[tObj].obj.rtype
if tMap, ok := T.Underlying().(*types.Map); ok { if tMap, ok := T.Underlying().(*types.Map); ok {
if a.nodes[c.result].pts.add(a.makeRtype(tMap.Key())) { if a.addLabel(c.result, a.makeRtype(tMap.Key())) {
changed = true changed = true
} }
} }
@ -467,4 +883,3 @@ func ext۰reflect۰rtype۰Key(a *analysis, cgn *cgnode) {
func ext۰reflect۰rtype۰Method(a *analysis, cgn *cgnode) {} func ext۰reflect۰rtype۰Method(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰MethodByName(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,27 +14,21 @@ import (
) )
func (a *analysis) solve() { func (a *analysis) solve() {
a.work.swap()
// Solver main loop. // Solver main loop.
for round := 1; ; round++ { for round := 1; ; round++ {
if a.log != nil {
fmt.Fprintf(a.log, "Solving, round %d\n", round)
}
// Add new constraints to the graph: // Add new constraints to the graph:
// static constraints from SSA on round 1, // static constraints from SSA on round 1,
// dynamic constraints from reflection thereafter. // dynamic constraints from reflection thereafter.
a.processNewConstraints() a.processNewConstraints()
if a.work.swap() {
if a.log != nil {
fmt.Fprintf(a.log, "Solving, round %d\n", round)
}
// Next iteration.
if a.work.empty() {
break // done
}
}
id := a.work.take() id := a.work.take()
if id == empty {
break
}
if a.log != nil { if a.log != nil {
fmt.Fprintf(a.log, "\tnode n%d\n", id) fmt.Fprintf(a.log, "\tnode n%d\n", id)
} }
@ -110,9 +104,6 @@ func (a *analysis) processNewConstraints() {
if len(n.prevPts) > 0 { if len(n.prevPts) > 0 {
stale.add(id) stale.add(id)
} }
if a.log != nil {
fmt.Fprintf(a.log, "Adding to worklist n%d\n", id)
}
a.addWork(id) a.addWork(id)
} }
} }
@ -152,6 +143,11 @@ func (a *analysis) solveConstraints(n *node, delta nodeset) {
} }
} }
// addLabel adds label to the points-to set of ptr and reports whether the set grew.
func (a *analysis) addLabel(ptr, label nodeid) bool {
return a.nodes[ptr].pts.add(label)
}
func (a *analysis) addWork(id nodeid) { func (a *analysis) addWork(id nodeid) {
a.work.add(id) a.work.add(id)
if a.log != nil { if a.log != nil {
@ -205,6 +201,10 @@ func (a *analysis) onlineCopy(dst, src nodeid) bool {
// Returns sizeof. // Returns sizeof.
// Implicitly adds nodes to worklist. // Implicitly adds nodes to worklist.
//
// TODO(adonovan): now that we support a.copy() during solving, we
// could eliminate onlineCopyN, but it's much slower. Investigate.
//
func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 { func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
for i := uint32(0); i < sizeof; i++ { for i := uint32(0); i < sizeof; i++ {
if a.onlineCopy(dst, src) { if a.onlineCopy(dst, src) {
@ -263,7 +263,7 @@ func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) {
if tIface != nil { if tIface != nil {
if types.IsAssignableTo(tDyn, tIface) { if types.IsAssignableTo(tDyn, tIface) {
if a.nodes[c.dst].pts.add(ifaceObj) { if a.addLabel(c.dst, ifaceObj) {
a.addWork(c.dst) a.addWork(c.dst)
} }
} }
@ -316,7 +316,7 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
// Make callsite's fn variable point to identity of // Make callsite's fn variable point to identity of
// concrete method. (There's no need to add it to // concrete method. (There's no need to add it to
// worklist since it never has attached constraints.) // worklist since it never has attached constraints.)
a.nodes[c.params].pts.add(fnObj) a.addLabel(c.params, fnObj)
// Extract value and connect to method's receiver. // Extract value and connect to method's receiver.
// Copy payload to method's receiver param (arg0). // Copy payload to method's receiver param (arg0).
@ -324,7 +324,6 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
recvSize := a.sizeof(sig.Recv().Type()) recvSize := a.sizeof(sig.Recv().Type())
a.onlineCopyN(arg0, v, recvSize) a.onlineCopyN(arg0, v, recvSize)
// Copy iface object payload to method receiver.
src := c.params + 1 // skip past identity src := c.params + 1 // skip past identity
dst := arg0 + nodeid(recvSize) dst := arg0 + nodeid(recvSize)

View File

@ -4,19 +4,16 @@ package main
import "reflect" import "reflect"
//
// This test is very sensitive to line-number perturbations!
// Test of channels with reflection. // Test of channels with reflection.
var a, b int var a, b int
func chanreflect1() { func chanreflect1() {
ch := make(chan *int, 0) ch := make(chan *int, 0) // @line cr1make
crv := reflect.ValueOf(ch) crv := reflect.ValueOf(ch)
crv.Send(reflect.ValueOf(&a)) crv.Send(reflect.ValueOf(&a))
print(crv.Interface()) // @types chan *int print(crv.Interface()) // @types chan *int
print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12 print(crv.Interface().(chan *int)) // @pointsto makechan@cr1make:12
print(<-ch) // @pointsto main.a print(<-ch) // @pointsto main.a
} }
@ -29,25 +26,31 @@ func chanreflect2() {
print(r.Interface().(*int)) // @pointsto main.b print(r.Interface().(*int)) // @pointsto main.b
} }
// TODO(adonovan): the analysis can't yet take advantage of the
// ChanOf(dir) parameter so the results are less precise than they
// should be: all three directions are returned.
func chanOfRecv() { func chanOfRecv() {
// MakeChan(<-chan) is a no-op. // MakeChan(<-chan) is a no-op.
t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a)) t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types <-chan *int print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
} }
func chanOfSend() { func chanOfSend() {
// MakeChan(chan<-) is a no-op. // MakeChan(chan<-) is a no-op.
t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a)) t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types chan<- *int print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
} }
func chanOfBoth() { func chanOfBoth() {
t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a)) t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types chan *int print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
ch := reflect.MakeChan(t, 0) ch := reflect.MakeChan(t, 0)
print(ch.Interface().(chan *int)) // @pointsto reflectMakechan@testdata/chanreflect.go:49:24 print(ch.Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
ch.Send(reflect.ValueOf(&b)) ch.Send(reflect.ValueOf(&b))
ch.Interface().(chan *int) <- &a ch.Interface().(chan *int) <- &a
r, _ := ch.Recv() r, _ := ch.Recv()

View File

@ -2,29 +2,43 @@
package main package main
//
import "reflect" import "reflect"
var a, b int var zero, a, b int
func f(p *int) *int { // func f(p *int) *int {
print(p) // @pointsto // print(p) // #@pointsto
return &b // return &b
// }
// func g(p *bool) {
// }
// func reflectValueCall() {
// rvf := reflect.ValueOf(f)
// res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)})
// print(res[0].Interface()) // #@types
// print(res[0].Interface().(*int)) // #@pointsto
// }
// #@calls main.reflectValueCall -> main.f
func reflectTypeInOut() {
var f func(float64, bool) (string, int)
// TODO(adonovan): when the In/Out argument is a valid index constant,
// only include a single type in the result. Needs some work.
print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @types float64 | bool
print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @types float64 | 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()) // @types string | int
print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @types string | int
print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @types string | int
print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @types
} }
func g(p *bool) {
}
func funcreflect1() {
rvf := reflect.ValueOf(f)
res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)})
print(res[0].Interface()) // @types
print(res[0].Interface().(*int)) // @pointsto
}
// @calls main.funcreflect1 -> main.f
func main() { func main() {
funcreflect1() //reflectValueCall()
reflectTypeInOut()
} }

View File

@ -9,7 +9,7 @@ import "reflect"
var a int var a int
var b bool var b bool
func mapreflect1() { func reflectMapKeysIndex() {
m := make(map[*int]*bool) // @line mr1make m := make(map[*int]*bool) // @line mr1make
m[&a] = &b m[&a] = &b
@ -33,7 +33,7 @@ func mapreflect1() {
} }
} }
func mapreflect2() { func reflectSetMapIndex() {
m := make(map[*int]*bool) m := make(map[*int]*bool)
mrv := reflect.ValueOf(m) mrv := reflect.ValueOf(m)
mrv.SetMapIndex(reflect.ValueOf(&a), reflect.ValueOf(&b)) mrv.SetMapIndex(reflect.ValueOf(&a), reflect.ValueOf(&b))
@ -64,7 +64,16 @@ func mapreflect2() {
print(reflect.Zero(tmap.Elem()).Interface()) // @types *bool print(reflect.Zero(tmap.Elem()).Interface()) // @types *bool
} }
func main() { func reflectMakeMap() {
mapreflect1() t := reflect.TypeOf(map[*int]*bool(nil))
mapreflect2() v := reflect.MakeMap(t)
print(v) // @types map[*int]*bool
print(v) // @pointsto <alloc in reflect.MakeMap>
}
func main() {
reflectMapKeysIndex()
reflectSetMapIndex()
reflectMakeMap()
// TODO(adonovan): reflect.MapOf(Type)
} }

View File

@ -6,6 +6,7 @@ import "reflect"
import "unsafe" import "unsafe"
var a, b int var a, b int
var unknown bool
func reflectIndirect() { func reflectIndirect() {
ptr := &a ptr := &a
@ -20,19 +21,22 @@ func reflectNewAt() {
print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @types *int print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @types *int
} }
// @warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call" // TODO(adonovan): report the location of the caller, not NewAt.
// #warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call"
// @warning "unsound: reflect.NewAt.. call"
func reflectTypeOf() { func reflectTypeOf() {
t := reflect.TypeOf(3) t := reflect.TypeOf(3)
if unknown { if unknown {
t = reflect.TypeOf("foo") t = reflect.TypeOf("foo")
} }
print(t) // @types *reflect.rtype // TODO(adonovan): make types.Eval let us refer to unexported types.
print(t) // #@types *reflect.rtype
print(reflect.Zero(t).Interface()) // @types int | string print(reflect.Zero(t).Interface()) // @types int | string
newint := reflect.New(t).Interface() // @line rtonew newint := reflect.New(t).Interface() // @line rtonew
print(newint) // @types *int | *string print(newint) // @types *int | *string
print(newint.(*int)) // @pointsto reflectAlloc@rtonew:23 print(newint.(*int)) // @pointsto <alloc in reflect.New>
print(newint.(*string)) // @pointsto reflectAlloc@rtonew:23 print(newint.(*string)) // @pointsto <alloc in reflect.New>
} }
func reflectTypeElem() { func reflectTypeElem() {
@ -44,26 +48,9 @@ func reflectTypeElem() {
print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface()) // @types 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()) // @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()) // @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() { func main() {
reflectIndirect() reflectIndirect()
reflectNewAt() reflectNewAt()
reflectTypeOf() reflectTypeOf()
reflectTypeElem() reflectTypeElem()
reflectTypeInOut()
} }
var unknown bool
var zero int

View File

@ -278,47 +278,30 @@ func (cs *constraintset) add(c constraint) bool {
// Worklist ------------------------------------------------------------------- // Worklist -------------------------------------------------------------------
// TODO(adonovan): interface may not be general enough for certain const empty nodeid = 1<<32 - 1
// implementations, e.g. priority queue
//
// Uses double-buffering so nodes can be added during iteration.
type worklist interface { type worklist interface {
empty() bool // Reports whether active buffer is empty. add(nodeid) // Adds a node to the set
swap() bool // Switches to the shadow buffer if empty(). take() nodeid // Takes a node from the set and returns it, or empty
add(nodeid) // Adds a node to the shadow buffer.
take() nodeid // Takes a node from the active buffer. Precondition: !empty().
} }
// Horribly naive (and nondeterministic) worklist // Simple nondeterministic worklist based on a built-in map.
// based on two hash-sets.
type mapWorklist struct { type mapWorklist struct {
active, shadow nodeset set nodeset
}
func (w *mapWorklist) empty() bool {
return len(w.active) == 0
}
func (w *mapWorklist) swap() bool {
if w.empty() {
w.shadow, w.active = w.active, w.shadow
return true
}
return false
} }
func (w *mapWorklist) add(n nodeid) { func (w *mapWorklist) add(n nodeid) {
w.shadow[n] = struct{}{} w.set[n] = struct{}{}
} }
func (w *mapWorklist) take() nodeid { func (w *mapWorklist) take() nodeid {
for k := range w.active { for k := range w.set {
delete(w.active, k) delete(w.set, k)
return k return k
} }
panic("worklist.take(): empty active buffer") return empty
} }
func makeMapWorklist() worklist { func makeMapWorklist() worklist {
return &mapWorklist{make(nodeset), make(nodeset)} return &mapWorklist{make(nodeset)}
} }