+++ /dev/null
-// 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)
-}