You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
8.6 KiB

  1. // Copyright 2014 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package main
  5. import (
  6. "fmt"
  7. "go/ast"
  8. "go/token"
  9. "go/types"
  10. "sort"
  11. "golang.org/x/tools/cmd/guru/serial"
  12. "golang.org/x/tools/go/ast/astutil"
  13. "golang.org/x/tools/go/loader"
  14. "golang.org/x/tools/go/pointer"
  15. "golang.org/x/tools/go/ssa"
  16. "golang.org/x/tools/go/ssa/ssautil"
  17. )
  18. var builtinErrorType = types.Universe.Lookup("error").Type()
  19. // whicherrs takes an position to an error and tries to find all types, constants
  20. // and global value which a given error can point to and which can be checked from the
  21. // scope where the error lives.
  22. // In short, it returns a list of things that can be checked against in order to handle
  23. // an error properly.
  24. //
  25. // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
  26. // can be queried recursively somehow.
  27. func whicherrs(q *Query) error {
  28. lconf := loader.Config{Build: q.Build}
  29. if err := setPTAScope(&lconf, q.Scope); err != nil {
  30. return err
  31. }
  32. // Load/parse/type-check the program.
  33. lprog, err := loadWithSoftErrors(&lconf)
  34. if err != nil {
  35. return err
  36. }
  37. qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
  38. if err != nil {
  39. return err
  40. }
  41. prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
  42. ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
  43. if err != nil {
  44. return err
  45. }
  46. path, action := findInterestingNode(qpos.info, qpos.path)
  47. if action != actionExpr {
  48. return fmt.Errorf("whicherrs wants an expression; got %s",
  49. astutil.NodeDescription(qpos.path[0]))
  50. }
  51. var expr ast.Expr
  52. var obj types.Object
  53. switch n := path[0].(type) {
  54. case *ast.ValueSpec:
  55. // ambiguous ValueSpec containing multiple names
  56. return fmt.Errorf("multiple value specification")
  57. case *ast.Ident:
  58. obj = qpos.info.ObjectOf(n)
  59. expr = n
  60. case ast.Expr:
  61. expr = n
  62. default:
  63. return fmt.Errorf("unexpected AST for expr: %T", n)
  64. }
  65. typ := qpos.info.TypeOf(expr)
  66. if !types.Identical(typ, builtinErrorType) {
  67. return fmt.Errorf("selection is not an expression of type 'error'")
  68. }
  69. // Determine the ssa.Value for the expression.
  70. var value ssa.Value
  71. if obj != nil {
  72. // def/ref of func/var object
  73. value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
  74. } else {
  75. value, _, err = ssaValueForExpr(prog, qpos.info, path)
  76. }
  77. if err != nil {
  78. return err // e.g. trivially dead code
  79. }
  80. // Defer SSA construction till after errors are reported.
  81. prog.Build()
  82. globals := findVisibleErrs(prog, qpos)
  83. constants := findVisibleConsts(prog, qpos)
  84. res := &whicherrsResult{
  85. qpos: qpos,
  86. errpos: expr.Pos(),
  87. }
  88. // TODO(adonovan): the following code is heavily duplicated
  89. // w.r.t. "pointsto". Refactor?
  90. // Find the instruction which initialized the
  91. // global error. If more than one instruction has stored to the global
  92. // remove the global from the set of values that we want to query.
  93. allFuncs := ssautil.AllFunctions(prog)
  94. for fn := range allFuncs {
  95. for _, b := range fn.Blocks {
  96. for _, instr := range b.Instrs {
  97. store, ok := instr.(*ssa.Store)
  98. if !ok {
  99. continue
  100. }
  101. gval, ok := store.Addr.(*ssa.Global)
  102. if !ok {
  103. continue
  104. }
  105. gbl, ok := globals[gval]
  106. if !ok {
  107. continue
  108. }
  109. // we already found a store to this global
  110. // The normal error define is just one store in the init
  111. // so we just remove this global from the set we want to query
  112. if gbl != nil {
  113. delete(globals, gval)
  114. }
  115. globals[gval] = store.Val
  116. }
  117. }
  118. }
  119. ptaConfig.AddQuery(value)
  120. for _, v := range globals {
  121. ptaConfig.AddQuery(v)
  122. }
  123. ptares := ptrAnalysis(ptaConfig)
  124. valueptr := ptares.Queries[value]
  125. if valueptr == (pointer.Pointer{}) {
  126. return fmt.Errorf("pointer analysis did not find expression (dead code?)")
  127. }
  128. for g, v := range globals {
  129. ptr, ok := ptares.Queries[v]
  130. if !ok {
  131. continue
  132. }
  133. if !ptr.MayAlias(valueptr) {
  134. continue
  135. }
  136. res.globals = append(res.globals, g)
  137. }
  138. pts := valueptr.PointsTo()
  139. dedup := make(map[*ssa.NamedConst]bool)
  140. for _, label := range pts.Labels() {
  141. // These values are either MakeInterfaces or reflect
  142. // generated interfaces. For the purposes of this
  143. // analysis, we don't care about reflect generated ones
  144. makeiface, ok := label.Value().(*ssa.MakeInterface)
  145. if !ok {
  146. continue
  147. }
  148. constval, ok := makeiface.X.(*ssa.Const)
  149. if !ok {
  150. continue
  151. }
  152. c := constants[*constval]
  153. if c != nil && !dedup[c] {
  154. dedup[c] = true
  155. res.consts = append(res.consts, c)
  156. }
  157. }
  158. concs := pts.DynamicTypes()
  159. concs.Iterate(func(conc types.Type, _ interface{}) {
  160. // go/types is a bit annoying here.
  161. // We want to find all the types that we can
  162. // typeswitch or assert to. This means finding out
  163. // if the type pointed to can be seen by us.
  164. //
  165. // For the purposes of this analysis, we care only about
  166. // TypeNames of Named or pointer-to-Named types.
  167. // We ignore other types (e.g. structs) that implement error.
  168. var name *types.TypeName
  169. switch t := conc.(type) {
  170. case *types.Pointer:
  171. named, ok := t.Elem().(*types.Named)
  172. if !ok {
  173. return
  174. }
  175. name = named.Obj()
  176. case *types.Named:
  177. name = t.Obj()
  178. default:
  179. return
  180. }
  181. if !isAccessibleFrom(name, qpos.info.Pkg) {
  182. return
  183. }
  184. res.types = append(res.types, &errorType{conc, name})
  185. })
  186. sort.Sort(membersByPosAndString(res.globals))
  187. sort.Sort(membersByPosAndString(res.consts))
  188. sort.Sort(sorterrorType(res.types))
  189. q.Output(lprog.Fset, res)
  190. return nil
  191. }
  192. // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
  193. func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
  194. globals := make(map[*ssa.Global]ssa.Value)
  195. for _, pkg := range prog.AllPackages() {
  196. for _, mem := range pkg.Members {
  197. gbl, ok := mem.(*ssa.Global)
  198. if !ok {
  199. continue
  200. }
  201. gbltype := gbl.Type()
  202. // globals are always pointers
  203. if !types.Identical(deref(gbltype), builtinErrorType) {
  204. continue
  205. }
  206. if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) {
  207. continue
  208. }
  209. globals[gbl] = nil
  210. }
  211. }
  212. return globals
  213. }
  214. // findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
  215. func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
  216. constants := make(map[ssa.Const]*ssa.NamedConst)
  217. for _, pkg := range prog.AllPackages() {
  218. for _, mem := range pkg.Members {
  219. obj, ok := mem.(*ssa.NamedConst)
  220. if !ok {
  221. continue
  222. }
  223. consttype := obj.Type()
  224. if !types.AssignableTo(consttype, builtinErrorType) {
  225. continue
  226. }
  227. if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) {
  228. continue
  229. }
  230. constants[*obj.Value] = obj
  231. }
  232. }
  233. return constants
  234. }
  235. type membersByPosAndString []ssa.Member
  236. func (a membersByPosAndString) Len() int { return len(a) }
  237. func (a membersByPosAndString) Less(i, j int) bool {
  238. cmp := a[i].Pos() - a[j].Pos()
  239. return cmp < 0 || cmp == 0 && a[i].String() < a[j].String()
  240. }
  241. func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  242. type sorterrorType []*errorType
  243. func (a sorterrorType) Len() int { return len(a) }
  244. func (a sorterrorType) Less(i, j int) bool {
  245. cmp := a[i].obj.Pos() - a[j].obj.Pos()
  246. return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String()
  247. }
  248. func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  249. type errorType struct {
  250. typ types.Type // concrete type N or *N that implements error
  251. obj *types.TypeName // the named type N
  252. }
  253. type whicherrsResult struct {
  254. qpos *queryPos
  255. errpos token.Pos
  256. globals []ssa.Member
  257. consts []ssa.Member
  258. types []*errorType
  259. }
  260. func (r *whicherrsResult) PrintPlain(printf printfFunc) {
  261. if len(r.globals) > 0 {
  262. printf(r.qpos, "this error may point to these globals:")
  263. for _, g := range r.globals {
  264. printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg))
  265. }
  266. }
  267. if len(r.consts) > 0 {
  268. printf(r.qpos, "this error may contain these constants:")
  269. for _, c := range r.consts {
  270. printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg))
  271. }
  272. }
  273. if len(r.types) > 0 {
  274. printf(r.qpos, "this error may contain these dynamic types:")
  275. for _, t := range r.types {
  276. printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
  277. }
  278. }
  279. }
  280. func (r *whicherrsResult) JSON(fset *token.FileSet) []byte {
  281. we := &serial.WhichErrs{}
  282. we.ErrPos = fset.Position(r.errpos).String()
  283. for _, g := range r.globals {
  284. we.Globals = append(we.Globals, fset.Position(g.Pos()).String())
  285. }
  286. for _, c := range r.consts {
  287. we.Constants = append(we.Constants, fset.Position(c.Pos()).String())
  288. }
  289. for _, t := range r.types {
  290. var et serial.WhichErrsType
  291. et.Type = r.qpos.typeString(t.typ)
  292. et.Position = fset.Position(t.obj.Pos()).String()
  293. we.Types = append(we.Types, et)
  294. }
  295. return toJSON(we)
  296. }