go.tools/pointer: replace Config.Print (callback) with a Result.PrintCalls (map).

This avoids leaking nodeids into client code before the
analysis has had a chance to run the (forthcoming) constraint
optimizer, which renumbers them.

R=crawshaw
CC=golang-dev
https://golang.org/cl/39410043
This commit is contained in:
Alan Donovan 2013-12-10 09:15:39 -05:00
parent 05f8c7dc66
commit 5d70784aca
4 changed files with 47 additions and 58 deletions

View File

@ -259,11 +259,11 @@ func Analyze(config *Config) *Result {
flattenMemo: make(map[types.Type][]*fieldInfo), flattenMemo: make(map[types.Type][]*fieldInfo),
hasher: typemap.MakeHasher(), hasher: typemap.MakeHasher(),
intrinsics: make(map[*ssa.Function]intrinsic), intrinsics: make(map[*ssa.Function]intrinsic),
probes: make(map[*ssa.CallCommon]nodeid),
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),
PrintCalls: make(map[*ssa.CallCommon]Pointer),
}, },
} }

View File

@ -33,19 +33,13 @@ type Config struct {
// If enabled, the graph will be available in Result.CallGraph. // If enabled, the graph will be available in Result.CallGraph.
BuildCallGraph bool BuildCallGraph bool
// Print is invoked during the analysis for each discovered // QueryPrintCalls causes the analysis to record (in
// call to the built-in print(x), providing a convenient way // Result.PrintCalls) the points-to set of the first operand
// to identify arbitrary expressions of interest in the tests. // of each discovered call to the built-in print(x), providing
// a convenient way to identify arbitrary expressions of
// interest in the tests.
// //
// Pointer p may be saved until the analysis is complete, at QueryPrintCalls bool
// which point its methods provide access to the analysis
// (The result of callings its methods within the Print
// callback is undefined.)
//
// CanPoint(site.Args[0].Type()) reports whether p is
// pointerlike.
//
Print func(site *ssa.CallCommon, p Pointer)
// The client populates Queries[v] or IndirectQueries[v] // The client populates Queries[v] or IndirectQueries[v]
// for each ssa.Value v of interest, to request that the // for each ssa.Value v of interest, to request that the
@ -57,12 +51,12 @@ type Config struct {
// to source-level lvalues, e.g. an *ssa.Global.) // to source-level lvalues, e.g. an *ssa.Global.)
// //
// The analysis populates the corresponding // The analysis populates the corresponding
// Results.{Indirect,}Queries map when it creates the pointer // Result.{Indirect,}Queries map when it creates the pointer
// 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 // If a Value belongs to a function that the analysis treats
// context-sensitively, the corresponding Results.{Indirect,}Queries // context-sensitively, the corresponding Result.{Indirect,}Queries
// slice may have multiple Pointers, one per distinct context. // slice may have multiple Pointers, one per distinct context.
// Use PointsToCombined to merge them. // Use PointsToCombined to merge them.
// //
@ -117,6 +111,7 @@ type Result struct {
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
PrintCalls map[*ssa.CallCommon]Pointer // pts(x) for each call to print(x)
} }
// A Pointer is an equivalence class of pointerlike values. // A Pointer is an equivalence class of pointerlike values.

View File

@ -528,20 +528,20 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
// Analytically print is a no-op, but it's a convenient hook // Analytically print is a no-op, but it's a convenient hook
// for testing the pts of an expression, so we notify the client. // for testing the pts of an expression, so we notify the client.
// Existing uses in Go core libraries are few and harmless. // Existing uses in Go core libraries are few and harmless.
if Print := a.config.Print; Print != nil { if a.config.QueryPrintCalls {
// Due to context-sensitivity, we may encounter // Due to context-sensitivity, we may encounter
// the same print() call in many contexts, so // the same print() call in many contexts, so
// we merge them to a canonical node. // we merge them to a canonical node.
probe := a.probes[call]
t := call.Args[0].Type() t := call.Args[0].Type()
ptr, ok := a.result.PrintCalls[call]
if !ok {
// First time? Create the canonical probe node. // First time? Create the canonical probe node.
if probe == 0 { ptr = Pointer{a, nil, a.addNodes(t, "print")}
probe = a.addNodes(t, "print") a.result.PrintCalls[call] = ptr
a.probes[call] = probe
Print(call, Pointer{a, nil, probe}) // notify client
} }
probe := ptr.n
a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t)) a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t))
} }

View File

@ -136,25 +136,19 @@ func (e *expectation) needsProbe() bool {
return e.kind == "pointsto" || e.kind == "types" return e.kind == "pointsto" || e.kind == "types"
} }
// A record of a call to the built-in print() function. Used for testing.
type probe struct {
instr *ssa.CallCommon
arg0 pointer.Pointer // first argument to print
}
// Find probe (call to print(x)) of same source // Find probe (call to print(x)) of same source
// file/line as expectation. // file/line as expectation.
func findProbe(prog *ssa.Program, probes []probe, e *expectation) *probe { func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e *expectation) (site *ssa.CallCommon, ptr pointer.Pointer) {
for _, p := range probes { for call, ptr := range probes {
pos := prog.Fset.Position(p.instr.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 &p return call, ptr
} }
} }
return nil // e.g. analysis didn't reach this call return // e.g. analysis didn't reach this call
} }
func doOneInput(input, filename string) bool { func doOneInput(input, filename string) bool {
@ -276,18 +270,15 @@ func doOneInput(input, filename string) bool {
} }
} }
var probes []probe
var log bytes.Buffer var log bytes.Buffer
// Run the analysis. // Run the analysis.
config := &pointer.Config{ config := &pointer.Config{
Reflection: true, Reflection: true,
BuildCallGraph: true, BuildCallGraph: true,
QueryPrintCalls: true,
Mains: []*ssa.Package{ptrmain}, Mains: []*ssa.Package{ptrmain},
Log: &log, Log: &log,
Print: func(site *ssa.CallCommon, p pointer.Pointer) {
probes = append(probes, probe{site, p})
},
} }
// Print the log is there was an error or a panic. // Print the log is there was an error or a panic.
@ -302,28 +293,31 @@ func doOneInput(input, filename string) bool {
// Check the expectations. // Check the expectations.
for _, e := range exps { for _, e := range exps {
var pr *probe var call *ssa.CallCommon
var ptr pointer.Pointer
var tProbe types.Type
if e.needsProbe() { if e.needsProbe() {
if pr = findProbe(prog, probes, e); pr == nil { if call, ptr = findProbe(prog, result.PrintCalls, e); call == nil {
ok = false ok = false
e.errorf("unreachable print() statement has expectation %s", e) e.errorf("unreachable print() statement has expectation %s", e)
continue continue
} }
if tArg := pr.instr.Args[0].Type(); !pointer.CanPoint(tArg) { tProbe = call.Args[0].Type()
if !pointer.CanPoint(tProbe) {
ok = false ok = false
e.errorf("expectation on non-pointerlike operand: %s", tArg) e.errorf("expectation on non-pointerlike operand: %s", tProbe)
continue continue
} }
} }
switch e.kind { switch e.kind {
case "pointsto": case "pointsto":
if !checkPointsToExpectation(e, pr, lineMapping, prog) { if !checkPointsToExpectation(e, ptr, lineMapping, prog) {
ok = false ok = false
} }
case "types": case "types":
if !checkTypesExpectation(e, pr) { if !checkTypesExpectation(e, ptr, tProbe) {
ok = false ok = false
} }
@ -368,7 +362,7 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog
return str return str
} }
func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string]string, prog *ssa.Program) bool { func checkPointsToExpectation(e *expectation, ptr pointer.Pointer, lineMapping map[string]string, prog *ssa.Program) bool {
expected := make(map[string]int) expected := make(map[string]int)
surplus := make(map[string]int) surplus := make(map[string]int)
exact := true exact := true
@ -381,7 +375,7 @@ func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string]
} }
// Find the set of labels that the probe's // Find the set of labels that the probe's
// argument (x in print(x)) may point to. // argument (x in print(x)) may point to.
for _, label := range pr.arg0.PointsTo().Labels() { for _, label := range ptr.PointsTo().Labels() {
name := labelString(label, lineMapping, prog) name := labelString(label, lineMapping, prog)
if expected[name] > 0 { if expected[name] > 0 {
expected[name]-- expected[name]--
@ -419,7 +413,7 @@ func underlyingType(typ types.Type) types.Type {
return typ return typ
} }
func checkTypesExpectation(e *expectation, pr *probe) bool { func checkTypesExpectation(e *expectation, ptr pointer.Pointer, typ types.Type) bool {
var expected typemap.M var expected typemap.M
var surplus typemap.M var surplus typemap.M
exact := true exact := true
@ -431,14 +425,14 @@ func checkTypesExpectation(e *expectation, pr *probe) bool {
expected.Set(g, struct{}{}) expected.Set(g, struct{}{})
} }
if t := pr.instr.Args[0].Type(); !pointer.CanHaveDynamicTypes(t) { if !pointer.CanHaveDynamicTypes(typ) {
e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", t) e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", typ)
return false return false
} }
// Find the set of types that the probe's // Find the set of types that the probe's
// argument (x in print(x)) may contain. // argument (x in print(x)) may contain.
for _, T := range pr.arg0.PointsTo().DynamicTypes().Keys() { for _, T := range ptr.PointsTo().DynamicTypes().Keys() {
if expected.At(T) != nil { if expected.At(T) != nil {
expected.Delete(T) expected.Delete(T)
} else if exact { } else if exact {