diff --git a/pointer/analysis.go b/pointer/analysis.go index 4602ba58..e88ad58d 100644 --- a/pointer/analysis.go +++ b/pointer/analysis.go @@ -259,11 +259,11 @@ func Analyze(config *Config) *Result { flattenMemo: make(map[types.Type][]*fieldInfo), hasher: typemap.MakeHasher(), intrinsics: make(map[*ssa.Function]intrinsic), - probes: make(map[*ssa.CallCommon]nodeid), work: makeMapWorklist(), result: &Result{ Queries: make(map[ssa.Value][]Pointer), IndirectQueries: make(map[ssa.Value][]Pointer), + PrintCalls: make(map[*ssa.CallCommon]Pointer), }, } diff --git a/pointer/api.go b/pointer/api.go index 9e9c048b..a89fbd14 100644 --- a/pointer/api.go +++ b/pointer/api.go @@ -33,19 +33,13 @@ type Config struct { // If enabled, the graph will be available in Result.CallGraph. BuildCallGraph bool - // Print is invoked during the analysis for each discovered - // call to the built-in print(x), providing a convenient way - // to identify arbitrary expressions of interest in the tests. + // QueryPrintCalls causes the analysis to record (in + // Result.PrintCalls) the points-to set of the first operand + // 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 - // 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) + QueryPrintCalls bool // The client populates Queries[v] or IndirectQueries[v] // 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.) // // 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 // inspect that map for the results. // // 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. // Use PointsToCombined to merge them. // @@ -113,10 +107,11 @@ type Warning struct { // See Config for how to request the various Result components. // type Result struct { - CallGraph call.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 call.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 + PrintCalls map[*ssa.CallCommon]Pointer // pts(x) for each call to print(x) } // A Pointer is an equivalence class of pointerlike values. diff --git a/pointer/gen.go b/pointer/gen.go index 21520495..d603f53b 100644 --- a/pointer/gen.go +++ b/pointer/gen.go @@ -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 // for testing the pts of an expression, so we notify the client. // 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 // the same print() call in many contexts, so // we merge them to a canonical node. - probe := a.probes[call] + t := call.Args[0].Type() - // First time? Create the canonical probe node. - if probe == 0 { - probe = a.addNodes(t, "print") - a.probes[call] = probe - Print(call, Pointer{a, nil, probe}) // notify client + ptr, ok := a.result.PrintCalls[call] + if !ok { + // First time? Create the canonical probe node. + ptr = Pointer{a, nil, a.addNodes(t, "print")} + a.result.PrintCalls[call] = ptr } - + probe := ptr.n a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t)) } diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go index ca4abb96..cf543368 100644 --- a/pointer/pointer_test.go +++ b/pointer/pointer_test.go @@ -136,25 +136,19 @@ func (e *expectation) needsProbe() bool { 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 // file/line as expectation. -func findProbe(prog *ssa.Program, probes []probe, e *expectation) *probe { - for _, p := range probes { - pos := prog.Fset.Position(p.instr.Pos()) +func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e *expectation) (site *ssa.CallCommon, ptr pointer.Pointer) { + for call, ptr := 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 &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 { @@ -276,18 +270,15 @@ func doOneInput(input, filename string) bool { } } - var probes []probe var log bytes.Buffer // Run the analysis. config := &pointer.Config{ - Reflection: true, - BuildCallGraph: true, - Mains: []*ssa.Package{ptrmain}, - Log: &log, - Print: func(site *ssa.CallCommon, p pointer.Pointer) { - probes = append(probes, probe{site, p}) - }, + Reflection: true, + BuildCallGraph: true, + QueryPrintCalls: true, + Mains: []*ssa.Package{ptrmain}, + Log: &log, } // Print the log is there was an error or a panic. @@ -302,28 +293,31 @@ func doOneInput(input, filename string) bool { // Check the expectations. for _, e := range exps { - var pr *probe + var call *ssa.CallCommon + var ptr pointer.Pointer + var tProbe types.Type if e.needsProbe() { - if pr = findProbe(prog, probes, e); pr == nil { + if call, ptr = findProbe(prog, result.PrintCalls, e); call == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } - if tArg := pr.instr.Args[0].Type(); !pointer.CanPoint(tArg) { + tProbe = call.Args[0].Type() + if !pointer.CanPoint(tProbe) { ok = false - e.errorf("expectation on non-pointerlike operand: %s", tArg) + e.errorf("expectation on non-pointerlike operand: %s", tProbe) continue } } switch e.kind { case "pointsto": - if !checkPointsToExpectation(e, pr, lineMapping, prog) { + if !checkPointsToExpectation(e, ptr, lineMapping, prog) { ok = false } case "types": - if !checkTypesExpectation(e, pr) { + if !checkTypesExpectation(e, ptr, tProbe) { ok = false } @@ -368,7 +362,7 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog 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) surplus := make(map[string]int) exact := true @@ -381,7 +375,7 @@ func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string] } // Find the set of labels that the probe's // 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) if expected[name] > 0 { expected[name]-- @@ -419,7 +413,7 @@ func underlyingType(typ types.Type) types.Type { 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 surplus typemap.M exact := true @@ -431,14 +425,14 @@ func checkTypesExpectation(e *expectation, pr *probe) bool { expected.Set(g, struct{}{}) } - if t := pr.instr.Args[0].Type(); !pointer.CanHaveDynamicTypes(t) { - e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", t) + if !pointer.CanHaveDynamicTypes(typ) { + e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", typ) return false } // Find the set of types that the probe's // 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 { expected.Delete(T) } else if exact {