diff --git a/go/pointer/analysis.go b/go/pointer/analysis.go index 4741caf8..44875e9f 100644 --- a/go/pointer/analysis.go +++ b/go/pointer/analysis.go @@ -298,8 +298,8 @@ func Analyze(config *Config) *Result { intrinsics: make(map[*ssa.Function]intrinsic), work: makeMapWorklist(), result: &Result{ - Queries: make(map[ssa.Value][]Pointer), - IndirectQueries: make(map[ssa.Value][]Pointer), + Queries: make(map[ssa.Value]Pointer), + IndirectQueries: make(map[ssa.Value]Pointer), }, } diff --git a/go/pointer/api.go b/go/pointer/api.go index 3925b630..89325812 100644 --- a/go/pointer/api.go +++ b/go/pointer/api.go @@ -47,13 +47,6 @@ type Config struct { // variable for v or *v. Upon completion the client can // inspect that map for the results. // - // If a Value belongs to a function that the analysis treats - // context-sensitively, the corresponding Result.{Indirect,}Queries - // slice may have multiple Pointers, one per distinct context. - // Use PointsToCombined to merge them. - // TODO(adonovan): need we distinguish contexts? Current - // clients always combine them. - // // TODO(adonovan): this API doesn't scale well for batch tools // that want to dump the entire solution. Perhaps optionally // populate a map[*ssa.DebugRef]Pointer in the Result, one @@ -122,10 +115,10 @@ type Warning struct { // See Config for how to request the various Result components. // type Result struct { - CallGraph callgraph.Graph // discovered call graph - Queries map[ssa.Value][]Pointer // pts(v) for each v in Config.Queries. - IndirectQueries map[ssa.Value][]Pointer // pts(*v) for each v in Config.IndirectQueries. - Warnings []Warning // warnings of unsoundness + CallGraph callgraph.Graph // discovered call graph + Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries. + IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries. + Warnings []Warning // warnings of unsoundness } // A Pointer is an equivalence class of pointer-like values. @@ -134,9 +127,8 @@ type Result struct { // types may alias the same object. // type Pointer struct { - a *analysis - cgn *cgnode - n nodeid // non-zero + a *analysis + n nodeid // non-zero } // A PointsToSet is a set of labels (locations or allocations). @@ -145,26 +137,6 @@ type PointsToSet struct { pts nodeset } -// Union returns the set containing all the elements of each set in sets. -func Union(sets ...PointsToSet) PointsToSet { - var union PointsToSet - for _, set := range sets { - union.a = set.a - union.pts.addAll(set.pts) - } - return union -} - -// PointsToCombined returns the combined points-to set of all the -// specified pointers. -func PointsToCombined(ptrs []Pointer) PointsToSet { - var ptsets []PointsToSet - for _, ptr := range ptrs { - ptsets = append(ptsets, ptr.PointsTo()) - } - return Union(ptsets...) -} - func (s PointsToSet) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "[") @@ -192,11 +164,9 @@ func (s PointsToSet) Labels() []*Label { // types that it may contain. (For an interface, they will // always be concrete types.) // -// The result is a mapping whose keys are the dynamic types to -// which it may point. For each pointer-like key type, the -// corresponding map value is a set of pointer abstractions of -// that dynamic type, represented as a []Pointer slice. Use -// PointsToCombined to merge them. +// The result is a mapping whose keys are the dynamic types to which +// it may point. For each pointer-like key type, the corresponding +// map value is the PointsToSet for pointers of that type. // // The result is empty unless CanHaveDynamicTypes(T). // @@ -211,8 +181,12 @@ func (s PointsToSet) DynamicTypes() *typeutil.Map { if indirect { panic("indirect tagged object") // implement later } - prev, _ := tmap.At(tDyn).([]Pointer) - tmap.Set(tDyn, append(prev, Pointer{s.a, nil, v})) + pts, ok := tmap.At(tDyn).(PointsToSet) + if !ok { + pts = PointsToSet{s.a, make(nodeset)} + tmap.Set(tDyn, pts) + } + pts.pts.addAll(s.a.nodes[v].pts) } return &tmap } @@ -232,12 +206,6 @@ func (p Pointer) String() string { return fmt.Sprintf("n%d", p.n) } -// Context returns the context of this pointer, -// if it corresponds to a local variable. -func (p Pointer) Context() callgraph.Node { - return p.cgn -} - // PointsTo returns the points-to set of this pointer. func (p Pointer) PointsTo() PointsToSet { return PointsToSet{p.a, p.a.nodes[p.n].pts} diff --git a/go/pointer/example_test.go b/go/pointer/example_test.go index 71210b4f..7f518afa 100644 --- a/go/pointer/example_test.go +++ b/go/pointer/example_test.go @@ -102,9 +102,8 @@ func main() { // Print the labels of (C).f(m)'s points-to set. fmt.Println("m may point to:") - ptset := pointer.PointsToCombined(result.Queries[Cfm]) var labels []string - for _, l := range ptset.Labels() { + for _, l := range result.Queries[Cfm].PointsTo().Labels() { label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l) labels = append(labels, label) } diff --git a/go/pointer/gen.go b/go/pointer/gen.go index dcaf76cb..1a2bfcf8 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -79,28 +79,33 @@ func (a *analysis) setValueNode(v ssa.Value, id nodeid, cgn *cgnode) { fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v) } - // TODO(adonovan): due to context-sensitivity, we may - // encounter the same Value in many contexts. In a follow-up, - // let's merge them to a canonical node, since that's what all - // clients want. - // ptr, ok := a.result.Queries[v] - // if !ok { - // // First time? Create the canonical probe node. - // ptr = Pointer{a, nil, a.addNodes(t, "query")} - // a.result.Queries[v] = ptr - // } - // a.copy(ptr.n, id, a.sizeof(v.Type())) + // Due to context-sensitivity, we may encounter the same Value + // in many contexts. We merge them to a canonical node, since + // that's what all clients want. // Record the (v, id) relation if the client has queried pts(v). if _, ok := a.config.Queries[v]; ok { - a.result.Queries[v] = append(a.result.Queries[v], Pointer{a, cgn, id}) + t := v.Type() + ptr, ok := a.result.Queries[v] + if !ok { + // First time? Create the canonical query node. + ptr = Pointer{a, a.addNodes(t, "query")} + a.result.Queries[v] = ptr + } + a.result.Queries[v] = ptr + a.copy(ptr.n, id, a.sizeof(t)) } // Record the (*v, id) relation if the client has queried pts(*v). if _, ok := a.config.IndirectQueries[v]; ok { - indirect := a.addNodes(v.Type(), "query.indirect") - a.genLoad(cgn, indirect, v, 0, a.sizeof(v.Type())) - a.result.IndirectQueries[v] = append(a.result.IndirectQueries[v], Pointer{a, cgn, indirect}) + t := v.Type() + ptr, ok := a.result.IndirectQueries[v] + if !ok { + // First time? Create the canonical indirect query node. + ptr = Pointer{a, a.addNodes(v.Type(), "query.indirect")} + a.result.IndirectQueries[v] = ptr + } + a.genLoad(cgn, ptr.n, v, 0, a.sizeof(t)) } } diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go index 9f76a220..f8bee71a 100644 --- a/go/pointer/pointer_test.go +++ b/go/pointer/pointer_test.go @@ -137,14 +137,14 @@ func (e *expectation) needsProbe() bool { } // Find probe (call to print(x)) of same source file/line as expectation. -func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value][]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) { +func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) { for call := range probes { pos := prog.Fset.Position(call.Pos()) if pos.Line == e.linenum && pos.Filename == e.filename { // TODO(adonovan): send this to test log (display only on failure). // fmt.Printf("%s:%d: info: found probe for %s: %s\n", // e.filename, e.linenum, e, p.arg0) // debugging - return call, pointer.PointsToCombined(queries[call.Args[0]]) + return call, queries[call.Args[0]].PointsTo() } } return // e.g. analysis didn't reach this call diff --git a/oracle/oracle.go b/oracle/oracle.go index 96253445..42409f96 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -1,4 +1,4 @@ -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -31,7 +31,7 @@ package oracle // package containing the query to avoid doing more work than it needs // (loading, parsing, type checking, SSA construction). // -// The Pythia tool (github.com/fzipp/pythia‎) is an example of a "long +// The Pythia tool (github.com/fzipp/pythia) is an example of a "long // running" tool. It calls New() and then loops, calling // ParseQueryPos and (*Oracle).Query to handle each incoming HTTP // query. Since New cannot see which queries will follow, it must diff --git a/oracle/peers.go b/oracle/peers.go index c7c24b98..fffc27a0 100644 --- a/oracle/peers.go +++ b/oracle/peers.go @@ -10,7 +10,6 @@ import ( "go/token" "sort" - "code.google.com/p/go.tools/go/pointer" "code.google.com/p/go.tools/go/ssa" "code.google.com/p/go.tools/go/ssa/ssautil" "code.google.com/p/go.tools/go/types" @@ -74,12 +73,12 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { // Run the pointer analysis. ptares := ptrAnalysis(o) - // Combine the PT sets from all contexts. - queryChanPts := pointer.PointsToCombined(ptares.Queries[queryOp.ch]) + // Find the points-to set. + queryChanPtr := ptares.Queries[queryOp.ch] // Ascertain which make(chan) labels the query's channel can alias. var makes []token.Pos - for _, label := range queryChanPts.Labels() { + for _, label := range queryChanPtr.PointsTo().Labels() { makes = append(makes, label.Pos()) } sort.Sort(byPos(makes)) @@ -87,13 +86,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { // Ascertain which send/receive operations can alias the same make(chan) labels. var sends, receives []token.Pos for _, op := range ops { - for _, ptr := range ptares.Queries[op.ch] { - if ptr.PointsTo().Intersects(queryChanPts) { - if op.dir == types.SendOnly { - sends = append(sends, op.pos) - } else { - receives = append(receives, op.pos) - } + if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { + if op.dir == types.SendOnly { + sends = append(sends, op.pos) + } else { + receives = append(receives, op.pos) } } } diff --git a/oracle/pointsto.go b/oracle/pointsto.go index 2bcd525b..76eb2aaf 100644 --- a/oracle/pointsto.go +++ b/oracle/pointsto.go @@ -132,24 +132,22 @@ func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err erro } ptares := ptrAnalysis(o) - // Combine the PT sets from all contexts. - var pointers []pointer.Pointer + var ptr pointer.Pointer if isAddr { - pointers = ptares.IndirectQueries[v] + ptr = ptares.IndirectQueries[v] } else { - pointers = ptares.Queries[v] + ptr = ptares.Queries[v] } - if pointers == nil { + if ptr == (pointer.Pointer{}) { return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)") } - pts := pointer.PointsToCombined(pointers) + pts := ptr.PointsTo() if pointer.CanHaveDynamicTypes(v.Type()) { // Show concrete types for interface/reflect.Value expression. if concs := pts.DynamicTypes(); concs.Len() > 0 { concs.Iterate(func(conc types.Type, pta interface{}) { - combined := pointer.PointsToCombined(pta.([]pointer.Pointer)) - labels := combined.Labels() + labels := pta.(pointer.PointsToSet).Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{conc, labels}) })