go.tools/go/pointer: remove context-sensitivity from API.
Previously, each {Indirect,}Query would return a set of Pointers, one per context; now it returns (at most) one Pointer combining information from all contexts.
The old API was more faithful to the implementation concepts, but the analysis is not sufficiently context-sensitive that it makes sense: all existing clients simply throw away the context information---so now we do that for them.
(I may remove the context-sensitivity from the callgraph too, but I'll benchmark that first to see if it reduces precision.)
LGTM=crawshaw
R=crawshaw
CC=golang-codereviews
https://golang.org/cl/66130044
This commit is contained in:
parent
804b9654fe
commit
28104d2c91
|
|
@ -298,8 +298,8 @@ func Analyze(config *Config) *Result {
|
||||||
intrinsics: make(map[*ssa.Function]intrinsic),
|
intrinsics: make(map[*ssa.Function]intrinsic),
|
||||||
work: makeMapWorklist(),
|
work: makeMapWorklist(),
|
||||||
result: &Result{
|
result: &Result{
|
||||||
Queries: make(map[ssa.Value][]Pointer),
|
Queries: make(map[ssa.Value]Pointer),
|
||||||
IndirectQueries: make(map[ssa.Value][]Pointer),
|
IndirectQueries: make(map[ssa.Value]Pointer),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,6 @@ type Config struct {
|
||||||
// variable for v or *v. Upon completion the client can
|
// variable for v or *v. Upon completion the client can
|
||||||
// inspect that map for the results.
|
// 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
|
// TODO(adonovan): this API doesn't scale well for batch tools
|
||||||
// that want to dump the entire solution. Perhaps optionally
|
// that want to dump the entire solution. Perhaps optionally
|
||||||
// populate a map[*ssa.DebugRef]Pointer in the Result, one
|
// 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.
|
// See Config for how to request the various Result components.
|
||||||
//
|
//
|
||||||
type Result struct {
|
type Result struct {
|
||||||
CallGraph callgraph.Graph // discovered call graph
|
CallGraph callgraph.Graph // discovered call graph
|
||||||
Queries map[ssa.Value][]Pointer // pts(v) for each v in Config.Queries.
|
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.
|
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||||
Warnings []Warning // warnings of unsoundness
|
Warnings []Warning // warnings of unsoundness
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Pointer is an equivalence class of pointer-like values.
|
// A Pointer is an equivalence class of pointer-like values.
|
||||||
|
|
@ -134,9 +127,8 @@ type Result struct {
|
||||||
// types may alias the same object.
|
// types may alias the same object.
|
||||||
//
|
//
|
||||||
type Pointer struct {
|
type Pointer struct {
|
||||||
a *analysis
|
a *analysis
|
||||||
cgn *cgnode
|
n nodeid // non-zero
|
||||||
n nodeid // non-zero
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A PointsToSet is a set of labels (locations or allocations).
|
// A PointsToSet is a set of labels (locations or allocations).
|
||||||
|
|
@ -145,26 +137,6 @@ type PointsToSet struct {
|
||||||
pts nodeset
|
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 {
|
func (s PointsToSet) String() string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
fmt.Fprintf(&buf, "[")
|
fmt.Fprintf(&buf, "[")
|
||||||
|
|
@ -192,11 +164,9 @@ func (s PointsToSet) Labels() []*Label {
|
||||||
// types that it may contain. (For an interface, they will
|
// types that it may contain. (For an interface, they will
|
||||||
// always be concrete types.)
|
// always be concrete types.)
|
||||||
//
|
//
|
||||||
// The result is a mapping whose keys are the dynamic types to
|
// The result is a mapping whose keys are the dynamic types to which
|
||||||
// which it may point. For each pointer-like key type, the
|
// it may point. For each pointer-like key type, the corresponding
|
||||||
// corresponding map value is a set of pointer abstractions of
|
// map value is the PointsToSet for pointers of that type.
|
||||||
// that dynamic type, represented as a []Pointer slice. Use
|
|
||||||
// PointsToCombined to merge them.
|
|
||||||
//
|
//
|
||||||
// The result is empty unless CanHaveDynamicTypes(T).
|
// The result is empty unless CanHaveDynamicTypes(T).
|
||||||
//
|
//
|
||||||
|
|
@ -211,8 +181,12 @@ func (s PointsToSet) DynamicTypes() *typeutil.Map {
|
||||||
if indirect {
|
if indirect {
|
||||||
panic("indirect tagged object") // implement later
|
panic("indirect tagged object") // implement later
|
||||||
}
|
}
|
||||||
prev, _ := tmap.At(tDyn).([]Pointer)
|
pts, ok := tmap.At(tDyn).(PointsToSet)
|
||||||
tmap.Set(tDyn, append(prev, Pointer{s.a, nil, v}))
|
if !ok {
|
||||||
|
pts = PointsToSet{s.a, make(nodeset)}
|
||||||
|
tmap.Set(tDyn, pts)
|
||||||
|
}
|
||||||
|
pts.pts.addAll(s.a.nodes[v].pts)
|
||||||
}
|
}
|
||||||
return &tmap
|
return &tmap
|
||||||
}
|
}
|
||||||
|
|
@ -232,12 +206,6 @@ func (p Pointer) String() string {
|
||||||
return fmt.Sprintf("n%d", p.n)
|
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.
|
// PointsTo returns the points-to set of this pointer.
|
||||||
func (p Pointer) PointsTo() PointsToSet {
|
func (p Pointer) PointsTo() PointsToSet {
|
||||||
return PointsToSet{p.a, p.a.nodes[p.n].pts}
|
return PointsToSet{p.a, p.a.nodes[p.n].pts}
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,8 @@ func main() {
|
||||||
|
|
||||||
// Print the labels of (C).f(m)'s points-to set.
|
// Print the labels of (C).f(m)'s points-to set.
|
||||||
fmt.Println("m may point to:")
|
fmt.Println("m may point to:")
|
||||||
ptset := pointer.PointsToCombined(result.Queries[Cfm])
|
|
||||||
var labels []string
|
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)
|
label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l)
|
||||||
labels = append(labels, label)
|
labels = append(labels, label)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(adonovan): due to context-sensitivity, we may
|
// Due to context-sensitivity, we may encounter the same Value
|
||||||
// encounter the same Value in many contexts. In a follow-up,
|
// in many contexts. We merge them to a canonical node, since
|
||||||
// let's merge them to a canonical node, since that's what all
|
// that's what all clients want.
|
||||||
// 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()))
|
|
||||||
|
|
||||||
// Record the (v, id) relation if the client has queried pts(v).
|
// Record the (v, id) relation if the client has queried pts(v).
|
||||||
if _, ok := a.config.Queries[v]; ok {
|
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).
|
// Record the (*v, id) relation if the client has queried pts(*v).
|
||||||
if _, ok := a.config.IndirectQueries[v]; ok {
|
if _, ok := a.config.IndirectQueries[v]; ok {
|
||||||
indirect := a.addNodes(v.Type(), "query.indirect")
|
t := v.Type()
|
||||||
a.genLoad(cgn, indirect, v, 0, a.sizeof(v.Type()))
|
ptr, ok := a.result.IndirectQueries[v]
|
||||||
a.result.IndirectQueries[v] = append(a.result.IndirectQueries[v], Pointer{a, cgn, indirect})
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,14 +137,14 @@ func (e *expectation) needsProbe() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find probe (call to print(x)) of same source file/line as expectation.
|
// 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 {
|
for call := range probes {
|
||||||
pos := prog.Fset.Position(call.Pos())
|
pos := prog.Fset.Position(call.Pos())
|
||||||
if pos.Line == e.linenum && pos.Filename == e.filename {
|
if pos.Line == e.linenum && pos.Filename == e.filename {
|
||||||
// TODO(adonovan): send this to test log (display only on failure).
|
// TODO(adonovan): send this to test log (display only on failure).
|
||||||
// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
|
// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
|
||||||
// e.filename, e.linenum, e, p.arg0) // debugging
|
// 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
|
return // e.g. analysis didn't reach this call
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// 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
|
// package containing the query to avoid doing more work than it needs
|
||||||
// (loading, parsing, type checking, SSA construction).
|
// (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
|
// running" tool. It calls New() and then loops, calling
|
||||||
// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
|
// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
|
||||||
// query. Since New cannot see which queries will follow, it must
|
// query. Since New cannot see which queries will follow, it must
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"sort"
|
"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"
|
||||||
"code.google.com/p/go.tools/go/ssa/ssautil"
|
"code.google.com/p/go.tools/go/ssa/ssautil"
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
|
@ -74,12 +73,12 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
// Run the pointer analysis.
|
// Run the pointer analysis.
|
||||||
ptares := ptrAnalysis(o)
|
ptares := ptrAnalysis(o)
|
||||||
|
|
||||||
// Combine the PT sets from all contexts.
|
// Find the points-to set.
|
||||||
queryChanPts := pointer.PointsToCombined(ptares.Queries[queryOp.ch])
|
queryChanPtr := ptares.Queries[queryOp.ch]
|
||||||
|
|
||||||
// Ascertain which make(chan) labels the query's channel can alias.
|
// Ascertain which make(chan) labels the query's channel can alias.
|
||||||
var makes []token.Pos
|
var makes []token.Pos
|
||||||
for _, label := range queryChanPts.Labels() {
|
for _, label := range queryChanPtr.PointsTo().Labels() {
|
||||||
makes = append(makes, label.Pos())
|
makes = append(makes, label.Pos())
|
||||||
}
|
}
|
||||||
sort.Sort(byPos(makes))
|
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.
|
// Ascertain which send/receive operations can alias the same make(chan) labels.
|
||||||
var sends, receives []token.Pos
|
var sends, receives []token.Pos
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
for _, ptr := range ptares.Queries[op.ch] {
|
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
|
||||||
if ptr.PointsTo().Intersects(queryChanPts) {
|
if op.dir == types.SendOnly {
|
||||||
if op.dir == types.SendOnly {
|
sends = append(sends, op.pos)
|
||||||
sends = append(sends, op.pos)
|
} else {
|
||||||
} else {
|
receives = append(receives, op.pos)
|
||||||
receives = append(receives, op.pos)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,24 +132,22 @@ func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err erro
|
||||||
}
|
}
|
||||||
ptares := ptrAnalysis(o)
|
ptares := ptrAnalysis(o)
|
||||||
|
|
||||||
// Combine the PT sets from all contexts.
|
var ptr pointer.Pointer
|
||||||
var pointers []pointer.Pointer
|
|
||||||
if isAddr {
|
if isAddr {
|
||||||
pointers = ptares.IndirectQueries[v]
|
ptr = ptares.IndirectQueries[v]
|
||||||
} else {
|
} 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?)")
|
return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
|
||||||
}
|
}
|
||||||
pts := pointer.PointsToCombined(pointers)
|
pts := ptr.PointsTo()
|
||||||
|
|
||||||
if pointer.CanHaveDynamicTypes(v.Type()) {
|
if pointer.CanHaveDynamicTypes(v.Type()) {
|
||||||
// Show concrete types for interface/reflect.Value expression.
|
// Show concrete types for interface/reflect.Value expression.
|
||||||
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
||||||
concs.Iterate(func(conc types.Type, pta interface{}) {
|
concs.Iterate(func(conc types.Type, pta interface{}) {
|
||||||
combined := pointer.PointsToCombined(pta.([]pointer.Pointer))
|
labels := pta.(pointer.PointsToSet).Labels()
|
||||||
labels := combined.Labels()
|
|
||||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
||||||
ptrs = append(ptrs, pointerResult{conc, labels})
|
ptrs = append(ptrs, pointerResult{conc, labels})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue