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:
parent
05f8c7dc66
commit
5d70784aca
|
@ -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),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
@ -113,10 +107,11 @@ 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 call.Graph // discovered call graph
|
CallGraph call.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
|
||||||
|
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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
// First time? Create the canonical probe node.
|
ptr, ok := a.result.PrintCalls[call]
|
||||||
if probe == 0 {
|
if !ok {
|
||||||
probe = a.addNodes(t, "print")
|
// First time? Create the canonical probe node.
|
||||||
a.probes[call] = probe
|
ptr = Pointer{a, nil, a.addNodes(t, "print")}
|
||||||
Print(call, Pointer{a, nil, probe}) // notify client
|
a.result.PrintCalls[call] = ptr
|
||||||
}
|
}
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
Mains: []*ssa.Package{ptrmain},
|
QueryPrintCalls: true,
|
||||||
Log: &log,
|
Mains: []*ssa.Package{ptrmain},
|
||||||
Print: func(site *ssa.CallCommon, p pointer.Pointer) {
|
Log: &log,
|
||||||
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 {
|
||||||
|
|
Loading…
Reference in New Issue