| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| "sort" |
| |
| "golang.org/x/tools/cmd/guru/serial" |
| "golang.org/x/tools/go/ast/astutil" |
| "golang.org/x/tools/go/loader" |
| "golang.org/x/tools/go/pointer" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/go/ssa/ssautil" |
| ) |
| |
| var builtinErrorType = types.Universe.Lookup("error").Type() |
| |
| // whicherrs takes an position to an error and tries to find all types, constants |
| // and global value which a given error can point to and which can be checked from the |
| // scope where the error lives. |
| // In short, it returns a list of things that can be checked against in order to handle |
| // an error properly. |
| // |
| // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err |
| // can be queried recursively somehow. |
| func whicherrs(q *Query) error { |
| lconf := loader.Config{Build: q.Build} |
| |
| if err := setPTAScope(&lconf, q.Scope); err != nil { |
| return err |
| } |
| |
| // Load/parse/type-check the program. |
| lprog, err := loadWithSoftErrors(&lconf) |
| if err != nil { |
| return err |
| } |
| |
| qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos |
| if err != nil { |
| return err |
| } |
| |
| prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) |
| |
| ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) |
| if err != nil { |
| return err |
| } |
| |
| path, action := findInterestingNode(qpos.info, qpos.path) |
| if action != actionExpr { |
| return fmt.Errorf("whicherrs wants an expression; got %s", |
| astutil.NodeDescription(qpos.path[0])) |
| } |
| var expr ast.Expr |
| var obj types.Object |
| switch n := path[0].(type) { |
| case *ast.ValueSpec: |
| // ambiguous ValueSpec containing multiple names |
| return fmt.Errorf("multiple value specification") |
| case *ast.Ident: |
| obj = qpos.info.ObjectOf(n) |
| expr = n |
| case ast.Expr: |
| expr = n |
| default: |
| return fmt.Errorf("unexpected AST for expr: %T", n) |
| } |
| |
| typ := qpos.info.TypeOf(expr) |
| if !types.Identical(typ, builtinErrorType) { |
| return fmt.Errorf("selection is not an expression of type 'error'") |
| } |
| // Determine the ssa.Value for the expression. |
| var value ssa.Value |
| if obj != nil { |
| // def/ref of func/var object |
| value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) |
| } else { |
| value, _, err = ssaValueForExpr(prog, qpos.info, path) |
| } |
| if err != nil { |
| return err // e.g. trivially dead code |
| } |
| |
| // Defer SSA construction till after errors are reported. |
| prog.Build() |
| |
| globals := findVisibleErrs(prog, qpos) |
| constants := findVisibleConsts(prog, qpos) |
| |
| res := &whicherrsResult{ |
| qpos: qpos, |
| errpos: expr.Pos(), |
| } |
| |
| // TODO(adonovan): the following code is heavily duplicated |
| // w.r.t. "pointsto". Refactor? |
| |
| // Find the instruction which initialized the |
| // global error. If more than one instruction has stored to the global |
| // remove the global from the set of values that we want to query. |
| allFuncs := ssautil.AllFunctions(prog) |
| for fn := range allFuncs { |
| for _, b := range fn.Blocks { |
| for _, instr := range b.Instrs { |
| store, ok := instr.(*ssa.Store) |
| if !ok { |
| continue |
| } |
| gval, ok := store.Addr.(*ssa.Global) |
| if !ok { |
| continue |
| } |
| gbl, ok := globals[gval] |
| if !ok { |
| continue |
| } |
| // we already found a store to this global |
| // The normal error define is just one store in the init |
| // so we just remove this global from the set we want to query |
| if gbl != nil { |
| delete(globals, gval) |
| } |
| globals[gval] = store.Val |
| } |
| } |
| } |
| |
| ptaConfig.AddQuery(value) |
| for _, v := range globals { |
| ptaConfig.AddQuery(v) |
| } |
| |
| ptares := ptrAnalysis(ptaConfig) |
| valueptr := ptares.Queries[value] |
| if valueptr == (pointer.Pointer{}) { |
| return fmt.Errorf("pointer analysis did not find expression (dead code?)") |
| } |
| for g, v := range globals { |
| ptr, ok := ptares.Queries[v] |
| if !ok { |
| continue |
| } |
| if !ptr.MayAlias(valueptr) { |
| continue |
| } |
| res.globals = append(res.globals, g) |
| } |
| pts := valueptr.PointsTo() |
| dedup := make(map[*ssa.NamedConst]bool) |
| for _, label := range pts.Labels() { |
| // These values are either MakeInterfaces or reflect |
| // generated interfaces. For the purposes of this |
| // analysis, we don't care about reflect generated ones |
| makeiface, ok := label.Value().(*ssa.MakeInterface) |
| if !ok { |
| continue |
| } |
| constval, ok := makeiface.X.(*ssa.Const) |
| if !ok { |
| continue |
| } |
| c := constants[*constval] |
| if c != nil && !dedup[c] { |
| dedup[c] = true |
| res.consts = append(res.consts, c) |
| } |
| } |
| concs := pts.DynamicTypes() |
| concs.Iterate(func(conc types.Type, _ interface{}) { |
| // go/types is a bit annoying here. |
| // We want to find all the types that we can |
| // typeswitch or assert to. This means finding out |
| // if the type pointed to can be seen by us. |
| // |
| // For the purposes of this analysis, we care only about |
| // TypeNames of Named or pointer-to-Named types. |
| // We ignore other types (e.g. structs) that implement error. |
| var name *types.TypeName |
| switch t := conc.(type) { |
| case *types.Pointer: |
| named, ok := t.Elem().(*types.Named) |
| if !ok { |
| return |
| } |
| name = named.Obj() |
| case *types.Named: |
| name = t.Obj() |
| default: |
| return |
| } |
| if !isAccessibleFrom(name, qpos.info.Pkg) { |
| return |
| } |
| res.types = append(res.types, &errorType{conc, name}) |
| }) |
| sort.Sort(membersByPosAndString(res.globals)) |
| sort.Sort(membersByPosAndString(res.consts)) |
| sort.Sort(sorterrorType(res.types)) |
| |
| q.Output(lprog.Fset, res) |
| return nil |
| } |
| |
| // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. |
| func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value { |
| globals := make(map[*ssa.Global]ssa.Value) |
| for _, pkg := range prog.AllPackages() { |
| for _, mem := range pkg.Members { |
| gbl, ok := mem.(*ssa.Global) |
| if !ok { |
| continue |
| } |
| gbltype := gbl.Type() |
| // globals are always pointers |
| if !types.Identical(deref(gbltype), builtinErrorType) { |
| continue |
| } |
| if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) { |
| continue |
| } |
| globals[gbl] = nil |
| } |
| } |
| return globals |
| } |
| |
| // findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil. |
| func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst { |
| constants := make(map[ssa.Const]*ssa.NamedConst) |
| for _, pkg := range prog.AllPackages() { |
| for _, mem := range pkg.Members { |
| obj, ok := mem.(*ssa.NamedConst) |
| if !ok { |
| continue |
| } |
| consttype := obj.Type() |
| if !types.AssignableTo(consttype, builtinErrorType) { |
| continue |
| } |
| if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) { |
| continue |
| } |
| constants[*obj.Value] = obj |
| } |
| } |
| |
| return constants |
| } |
| |
| type membersByPosAndString []ssa.Member |
| |
| func (a membersByPosAndString) Len() int { return len(a) } |
| func (a membersByPosAndString) Less(i, j int) bool { |
| cmp := a[i].Pos() - a[j].Pos() |
| return cmp < 0 || cmp == 0 && a[i].String() < a[j].String() |
| } |
| func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| |
| type sorterrorType []*errorType |
| |
| func (a sorterrorType) Len() int { return len(a) } |
| func (a sorterrorType) Less(i, j int) bool { |
| cmp := a[i].obj.Pos() - a[j].obj.Pos() |
| return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String() |
| } |
| func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| |
| type errorType struct { |
| typ types.Type // concrete type N or *N that implements error |
| obj *types.TypeName // the named type N |
| } |
| |
| type whicherrsResult struct { |
| qpos *queryPos |
| errpos token.Pos |
| globals []ssa.Member |
| consts []ssa.Member |
| types []*errorType |
| } |
| |
| func (r *whicherrsResult) PrintPlain(printf printfFunc) { |
| if len(r.globals) > 0 { |
| printf(r.qpos, "this error may point to these globals:") |
| for _, g := range r.globals { |
| printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg)) |
| } |
| } |
| if len(r.consts) > 0 { |
| printf(r.qpos, "this error may contain these constants:") |
| for _, c := range r.consts { |
| printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg)) |
| } |
| } |
| if len(r.types) > 0 { |
| printf(r.qpos, "this error may contain these dynamic types:") |
| for _, t := range r.types { |
| printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ)) |
| } |
| } |
| } |
| |
| func (r *whicherrsResult) JSON(fset *token.FileSet) []byte { |
| we := &serial.WhichErrs{} |
| we.ErrPos = fset.Position(r.errpos).String() |
| for _, g := range r.globals { |
| we.Globals = append(we.Globals, fset.Position(g.Pos()).String()) |
| } |
| for _, c := range r.consts { |
| we.Constants = append(we.Constants, fset.Position(c.Pos()).String()) |
| } |
| for _, t := range r.types { |
| var et serial.WhichErrsType |
| et.Type = r.qpos.typeString(t.typ) |
| et.Position = fset.Position(t.obj.Pos()).String() |
| we.Types = append(we.Types, et) |
| } |
| return toJSON(we) |
| } |