go.tools/pointer: minor API simplifications.

Details:
- Warnings are reported as values in Result, not a callback in Config.
- remove TODO to eliminate Print callback.  It's better than the alternative.
- remove unused Config.root field.
- hang Result off analysis object (impl. detail)
- reword TODO.

R=crawshaw
CC=golang-dev
https://golang.org/cl/14128043
This commit is contained in:
Alan Donovan 2013-09-30 12:39:54 -04:00
parent 0730d79f0f
commit 06c4192423
6 changed files with 39 additions and 71 deletions

View File

@ -99,12 +99,6 @@ type queryResult interface {
display(printf printfFunc) display(printf printfFunc)
} }
type warning struct {
pos token.Pos
format string
args []interface{}
}
// A QueryPos represents the position provided as input to a query: // A QueryPos represents the position provided as input to a query:
// a textual extent in the program's source code, the AST node it // a textual extent in the program's source code, the AST node it
// corresponds to, and the package to which it belongs. // corresponds to, and the package to which it belongs.
@ -121,9 +115,9 @@ type Result struct {
fset *token.FileSet fset *token.FileSet
// fprintf is a closure over the oracle's fileset and start/end position. // fprintf is a closure over the oracle's fileset and start/end position.
fprintf func(w io.Writer, pos interface{}, format string, args ...interface{}) fprintf func(w io.Writer, pos interface{}, format string, args ...interface{})
q queryResult // the query-specific result q queryResult // the query-specific result
mode string // query mode mode string // query mode
warnings []warning // pointer analysis warnings warnings []pointer.Warning // pointer analysis warnings
} }
// Serial returns an instance of serial.Result, which implements the // Serial returns an instance of serial.Result, which implements the
@ -135,8 +129,8 @@ func (res *Result) Serial() *serial.Result {
res.q.toSerial(resj, res.fset) res.q.toSerial(resj, res.fset)
for _, w := range res.warnings { for _, w := range res.warnings {
resj.Warnings = append(resj.Warnings, serial.PTAWarning{ resj.Warnings = append(resj.Warnings, serial.PTAWarning{
Pos: res.fset.Position(w.pos).String(), Pos: res.fset.Position(w.Pos).String(),
Message: fmt.Sprintf(w.format, w.args...), Message: w.Message,
}) })
} }
return resj return resj
@ -302,9 +296,6 @@ func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
fset: o.prog.Fset, fset: o.prog.Fset,
fprintf: o.fprintf, // captures o.prog, o.{start,end}Pos for later printing fprintf: o.fprintf, // captures o.prog, o.{start,end}Pos for later printing
} }
o.config.Warn = func(pos token.Pos, format string, args ...interface{}) {
res.warnings = append(res.warnings, warning{pos, format, args})
}
var err error var err error
res.q, err = minfo.impl(o, qpos) res.q, err = minfo.impl(o, qpos)
if err != nil { if err != nil {
@ -342,7 +333,7 @@ func (res *Result) WriteTo(out io.Writer) {
if res.warnings != nil { if res.warnings != nil {
fmt.Fprintln(out, "\nPointer analysis warnings:") fmt.Fprintln(out, "\nPointer analysis warnings:")
for _, w := range res.warnings { for _, w := range res.warnings {
printf(w.pos, "warning: "+w.format, w.args...) printf(w.Pos, "warning: "+w.Message)
} }
} }
} }

View File

@ -16,13 +16,11 @@ CONSTRAINT GENERATION:
A downside is that we can't keep the identity field of struct A downside is that we can't keep the identity field of struct
allocations that identifies the object. allocations that identifies the object.
PRESOLVER OPTIMISATIONS OPTIMISATIONS
- use HVN, HRU, LE, PE, HCD, LCD. - pre-solver: PE and LE via HVN/HRU.
- solver: HCD, LCD.
SOLVER: - use sparse bitvectors for ptsets
- use BDDs and/or sparse bitvectors for ptsets
- use sparse bitvectors for graph edges - use sparse bitvectors for graph edges
- use BDD-based relational composition for e.g. offset computations.
- experiment with different worklist algorithms: - experiment with different worklist algorithms:
priority queue (solver visit-time order) priority queue (solver visit-time order)
red-black tree (node id order) red-black tree (node id order)
@ -34,9 +32,8 @@ SOLVER:
API: API:
- Some optimisations (e.g. LE, PE) may change the API. - Some optimisations (e.g. LE, PE) may change the API.
Think about them sooner rather than later. Think about them sooner rather than later.
- Eliminate Print probe now that we can query specific ssa.Values.
Misc: MISC:
- os.Args should point to something; currently they don't. - os.Args should point to something; currently they don't.
- Test on all platforms. - Test on all platforms.
Currently we assume these go/build tags: linux, amd64, !cgo. Currently we assume these go/build tags: linux, amd64, !cgo.

View File

@ -183,7 +183,7 @@ type analysis struct {
localval map[ssa.Value]nodeid // node for each local ssa.Value localval map[ssa.Value]nodeid // node for each local ssa.Value
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
work worklist // solver's worklist work worklist // solver's worklist
queries map[ssa.Value][]Pointer // same as Results.Queries result *Result // results of the analysis
// Reflection: // Reflection:
hasher typemap.Hasher // cache of type hashes hasher typemap.Hasher // cache of type hashes
@ -221,13 +221,7 @@ func (a *analysis) labelFor(id nodeid) *Label {
} }
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) { func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
if Warn := a.config.Warn; Warn != nil { a.result.Warnings = append(a.result.Warnings, Warning{pos, fmt.Sprintf(format, args...)})
Warn(pos, format, args...)
} else {
fmt.Fprintf(os.Stderr, "%s: warning: ", a.prog.Fset.Position(pos))
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
}
} }
// Analyze runs the pointer analysis with the scope and options // Analyze runs the pointer analysis with the scope and options
@ -245,7 +239,9 @@ func Analyze(config *Config) *Result {
intrinsics: make(map[*ssa.Function]intrinsic), intrinsics: make(map[*ssa.Function]intrinsic),
probes: make(map[*ssa.CallCommon]nodeid), probes: make(map[*ssa.CallCommon]nodeid),
work: makeMapWorklist(), work: makeMapWorklist(),
queries: make(map[ssa.Value][]Pointer), result: &Result{
Queries: make(map[ssa.Value][]Pointer),
},
} }
if false { if false {
@ -318,13 +314,9 @@ func Analyze(config *Config) *Result {
} }
} }
var callgraph *cgraph
if a.config.BuildCallGraph { if a.config.BuildCallGraph {
callgraph = &cgraph{root, a.cgnodes} a.result.CallGraph = &cgraph{root, a.cgnodes}
} }
return &Result{ return a.result
CallGraph: callgraph,
Queries: a.queries,
}
} }

View File

@ -14,12 +14,12 @@ import (
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
// A Config formulates a pointer analysis problem for Analyze().
type Config struct { type Config struct {
// -------- Scope of the analysis -------- // Mains contains the set of 'main' packages to analyze
// Clients must provide the analysis with at least one
// Clients must provide the analysis with at least one package defining a main() function. // package defining a main() function.
Mains []*ssa.Package // set of 'main' packages to analyze Mains []*ssa.Package
root *ssa.Function // synthetic analysis root
// Reflection determines whether to handle reflection // Reflection determines whether to handle reflection
// operators soundly, which is currently rather slow since it // operators soundly, which is currently rather slow since it
@ -32,25 +32,15 @@ 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
// -------- Optional callbacks invoked by the analysis --------
// Warn is invoked for each warning encountered by the analysis,
// e.g. unknown external function, unsound use of unsafe.Pointer.
// pos may be zero if the position is not known.
Warn func(pos token.Pos, format string, args ...interface{})
// Print is invoked during the analysis for each discovered // Print is invoked during the analysis for each discovered
// call to the built-in print(x). // 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 // Pointer p may be saved until the analysis is complete, at
// which point its methods provide access to the analysis // which point its methods provide access to the analysis
// (The result of callings its methods within the Print // (The result of callings its methods within the Print
// callback is undefined.) p is nil if x is non-pointerlike. // callback is undefined.) p is nil if x is non-pointerlike.
// //
// TODO(adonovan): this was a stop-gap measure for identifing
// arbitrary expressions of interest in the tests. Now that
// ssa.ValueForExpr exists, we should use that instead.
//
Print func(site *ssa.CallCommon, p Pointer) Print func(site *ssa.CallCommon, p Pointer)
// The client populates Queries[v] for each ssa.Value v of // The client populates Queries[v] for each ssa.Value v of
@ -73,9 +63,7 @@ type Config struct {
// //
Queries map[ssa.Value]Indirect Queries map[ssa.Value]Indirect
// -------- Other configuration options -------- // If Log is non-nil, log messages are written to it.
// If Log is non-nil, a log messages are written to it.
// Logging is extremely verbose. // Logging is extremely verbose.
Log io.Writer Log io.Writer
} }
@ -89,6 +77,11 @@ func (c *Config) prog() *ssa.Program {
panic("empty scope") panic("empty scope")
} }
type Warning struct {
Pos token.Pos
Message string
}
// A Result contains the results of a pointer analysis. // A Result contains the results of a pointer analysis.
// //
// See Config for how to request the various Result components. // See Config for how to request the various Result components.
@ -96,6 +89,7 @@ func (c *Config) prog() *ssa.Program {
type Result struct { type Result struct {
CallGraph call.Graph // discovered call graph CallGraph call.Graph // discovered call graph
Queries map[ssa.Value][]Pointer // points-to sets for queried ssa.Values Queries map[ssa.Value][]Pointer // points-to sets for queried ssa.Values
Warnings []Warning // warnings of unsoundness
} }
// A Pointer is an equivalence class of pointerlike values. // A Pointer is an equivalence class of pointerlike values.

View File

@ -87,7 +87,7 @@ func (a *analysis) setValueNode(v ssa.Value, id nodeid, cgn *cgnode) {
a.genLoad(cgn, tmp, v, 0, a.sizeof(v.Type())) a.genLoad(cgn, tmp, v, 0, a.sizeof(v.Type()))
id = tmp id = tmp
} }
a.queries[v] = append(a.queries[v], ptr{a, cgn, id}) a.result.Queries[v] = append(a.result.Queries[v], ptr{a, cgn, id})
} }
} }

View File

@ -285,7 +285,6 @@ func doOneInput(input, filename string) bool {
} }
var probes []probe var probes []probe
var warnings []string
var log bytes.Buffer var log bytes.Buffer
// Run the analysis. // Run the analysis.
@ -297,11 +296,6 @@ func doOneInput(input, filename string) bool {
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})
}, },
Warn: func(pos token.Pos, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s: warning: %s\n", prog.Fset.Position(pos), msg)
warnings = append(warnings, msg)
},
} }
result := pointer.Analyze(config) result := pointer.Analyze(config)
@ -346,7 +340,7 @@ func doOneInput(input, filename string) bool {
} }
case "warning": case "warning":
if !checkWarningExpectation(prog, e, warnings) { if !checkWarningExpectation(prog, e, result.Warnings) {
ok = false ok = false
} }
} }
@ -493,7 +487,7 @@ func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Gra
return false return false
} }
func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []string) bool { func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool {
// TODO(adonovan): check the position part of the warning too? // TODO(adonovan): check the position part of the warning too?
re, err := regexp.Compile(e.args[0]) re, err := regexp.Compile(e.args[0])
if err != nil { if err != nil {
@ -506,15 +500,15 @@ func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []strin
return false return false
} }
for _, warning := range warnings { for _, w := range warnings {
if re.MatchString(warning) { if re.MatchString(w.Message) {
return true return true
} }
} }
e.errorf("@warning %s expectation not satised; found these warnings though:", strconv.Quote(e.args[0])) e.errorf("@warning %s expectation not satised; found these warnings though:", strconv.Quote(e.args[0]))
for _, warning := range warnings { for _, w := range warnings {
fmt.Println("\t", warning) fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message)
} }
return false return false
} }