// Oracle annotates `pkg` using go.tools/oracle interface implements detector. // It uses `scopes` as analysis scope. // If `scopes` is none of one of `scopes` is zero string, it uses unit tests as scope. func Oracle(pkg *ast.Package, scopes ...string) error { settings := build.Default settings.BuildTags = []string{} // TODO conf := loader.Config{Build: &settings, SourceImports: true} withTests := false if len(scopes) == 0 { withTests = true } for _, scope := range scopes { if scope == "" { withTests = true } else { conf.Import(scope) } } if withTests { conf.ImportWithTests(pkg.Name) } else { conf.Import(pkg.Name) } iprog, err := conf.Load() if err != nil { return fmt.Errorf("oracle annotator: conf load error: %+v", err) } o, err := oracle.New(iprog, nil, false) if err != nil { return fmt.Errorf("oracle annotator: create error: %+v", err) } for _, class := range pkg.Classes { qpos, err := oracle.ParseQueryPos(iprog, string(class.Pos), false) if err != nil { log.Printf("oracle annotator: parse query pos error: %+v, %+v", err, class.Pos) continue } res, err := o.Query("implements", qpos) if err != nil { return fmt.Errorf("oracle annotator: query error: %+v, %v", err, class.Pos) } impls := res.Serial().Implements for _, target := range impls.AssignableFromPtr { addImplements(class, target) } for _, target := range impls.AssignableFrom { addImplements(class, target) } } return nil }
func TestMultipleQueries(t *testing.T) { // Loader var buildContext = build.Default buildContext.GOPATH = "testdata" conf := loader.Config{Build: &buildContext, SourceImports: true} filename := "testdata/src/main/multi.go" conf.CreateFromFilenames("", filename) iprog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %s", err) } // Oracle o, err := oracle.New(iprog, nil, true) if err != nil { t.Fatalf("oracle.New failed: %s", err) } // QueryPos pos := filename + ":#54,#58" qpos, err := oracle.ParseQueryPos(iprog, pos, true) if err != nil { t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err) } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. iprog = nil // Run different query modes on same scope and selection. out := new(bytes.Buffer) for _, mode := range [...]string{"callers", "describe", "freevars"} { res, err := o.Query(mode, qpos) if err != nil { t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err) } WriteResult(out, res) } want := `multi.f is called from these 1 sites: static function call from multi.main function call (or conversion) of type () Free identifiers: var x int ` if got := out.String(); got != want { t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got) } }
func TestMultipleQueries(t *testing.T) { // Importer var buildContext = build.Default buildContext.GOPATH = "testdata" imp := importer.New(&importer.Config{Build: &buildContext}) // Oracle filename := "testdata/src/main/multi.go" o, err := oracle.New(imp, []string{filename}, nil, true) if err != nil { t.Fatalf("oracle.New failed: %s", err) } // QueryPos pos := filename + ":#54,#58" qpos, err := oracle.ParseQueryPos(imp, pos, true) if err != nil { t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err) } // SSA is built and we have the QueryPos. // Release the other ASTs and type info to the GC. imp = nil // Run different query moes on same scope and selection. out := new(bytes.Buffer) for _, mode := range [...]string{"callers", "describe", "freevars"} { res, err := o.Query(mode, qpos) if err != nil { t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err) } capture := new(bytes.Buffer) // capture standard output res.WriteTo(capture) for _, line := range strings.Split(capture.String(), "\n") { fmt.Fprintf(out, "%s\n", stripLocation(line)) } } want := `multi.f is called from these 1 sites: static function call from multi.main function call (or conversion) of type () Free identifiers: var x int ` if got := out.String(); got != want { t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got) } }
func (ctxt *context) callees(inst *ssa.Call) ([]*ssa.Function, error) { pos := ctxt.lprog.Fset.Position(inst.Pos()) if pos.Line <= 0 { return nil, fmt.Errorf("no position") } qpos, err := oracle.ParseQueryPos(ctxt.lprog, posStr(pos), true) if err != nil { return nil, fmt.Errorf("cannot parse query pos %q: %v", posStr(pos), err) } result, err := ctxt.oracle.Query("callees", qpos) if err != nil { return nil, fmt.Errorf("query error: %v", err) } return calleeFuncs(result), nil }
// serveQuery executes a query to the oracle and delivers the results // in the specified format. The request parameters are: // // mode: e.g. "describe", "callers", "freevars", ... // pos: file name with byte offset(s), e.g. "/path/to/file.go:#1457,#1462" // format: "json" or "plain", no "xml" at the moment // // If the application was launched in verbose mode, each query will be // logged like an invocation of the oracle command. func serveQuery(w http.ResponseWriter, req *http.Request) { mode := req.FormValue("mode") pos := req.FormValue("pos") format := req.FormValue("format") if *verbose { log.Println(req.RemoteAddr, cmdLine(mode, pos, format, args)) } qpos, err := oracle.ParseQueryPos(imp, pos, false) if err != nil { io.WriteString(w, err.Error()) return } res, err := queryOracle(mode, qpos) if err != nil { io.WriteString(w, err.Error()) return } writeResult(w, res, format) }