cycle.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Copyright (c) 2019 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package dig
  21. import (
  22. "bytes"
  23. "fmt"
  24. "go.uber.org/dig/internal/digreflect"
  25. )
  26. type cycleEntry struct {
  27. Key key
  28. Func *digreflect.Func
  29. }
  30. type errCycleDetected struct {
  31. Path []cycleEntry
  32. }
  33. func (e errCycleDetected) Error() string {
  34. // We get something like,
  35. //
  36. // foo provided by "path/to/package".NewFoo (path/to/file.go:42)
  37. // depends on bar provided by "another/package".NewBar (somefile.go:1)
  38. // depends on baz provided by "somepackage".NewBar (anotherfile.go:2)
  39. // depends on foo provided by "path/to/package".NewFoo (path/to/file.go:42)
  40. //
  41. b := new(bytes.Buffer)
  42. for i, entry := range e.Path {
  43. if i > 0 {
  44. b.WriteString("\n\tdepends on ")
  45. }
  46. fmt.Fprintf(b, "%v provided by %v", entry.Key, entry.Func)
  47. }
  48. return b.String()
  49. }
  50. // IsCycleDetected returns a boolean as to whether the provided error indicates
  51. // a cycle was detected in the container graph.
  52. func IsCycleDetected(err error) bool {
  53. _, ok := RootCause(err).(errCycleDetected)
  54. return ok
  55. }
  56. func verifyAcyclic(c containerStore, n provider, k key) error {
  57. visited := make(map[key]struct{})
  58. err := detectCycles(n, c, []cycleEntry{
  59. {Key: k, Func: n.Location()},
  60. }, visited)
  61. if err != nil {
  62. err = errf("this function introduces a cycle", err)
  63. }
  64. return err
  65. }
  66. func detectCycles(n provider, c containerStore, path []cycleEntry, visited map[key]struct{}) error {
  67. var err error
  68. walkParam(n.ParamList(), paramVisitorFunc(func(param param) bool {
  69. if err != nil {
  70. return false
  71. }
  72. var (
  73. k key
  74. providers []provider
  75. )
  76. switch p := param.(type) {
  77. case paramSingle:
  78. k = key{name: p.Name, t: p.Type}
  79. if _, ok := visited[k]; ok {
  80. // We've already checked the dependencies for this type.
  81. return false
  82. }
  83. providers = c.getValueProviders(p.Name, p.Type)
  84. case paramGroupedSlice:
  85. // NOTE: The key uses the element type, not the slice type.
  86. k = key{group: p.Group, t: p.Type.Elem()}
  87. if _, ok := visited[k]; ok {
  88. // We've already checked the dependencies for this type.
  89. return false
  90. }
  91. providers = c.getGroupProviders(p.Group, p.Type.Elem())
  92. default:
  93. // Recurse for non-edge params.
  94. return true
  95. }
  96. entry := cycleEntry{Func: n.Location(), Key: k}
  97. if len(path) > 0 {
  98. // Only mark a key as visited if path exists, i.e. this is not the
  99. // first iteration through the c.verifyAcyclic() check. Otherwise the
  100. // early exit from checking visited above will short circuit the
  101. // cycle check below.
  102. visited[k] = struct{}{}
  103. // If it exists, the first element of path is the new addition to the
  104. // graph, therefore it must be in any cycle that exists, assuming
  105. // verifyAcyclic has been run for every previous Provide.
  106. //
  107. // Alternatively, if deferAcyclicVerification was set and detectCycles
  108. // is only being called before the first Invoke, each node in the
  109. // graph will be tested as the first element of the path, so any
  110. // cycle that exists is guaranteed to trip the following condition.
  111. if path[0].Key == k {
  112. err = errCycleDetected{Path: append(path, entry)}
  113. return false
  114. }
  115. }
  116. for _, n := range providers {
  117. if e := detectCycles(n, c, append(path, entry), visited); e != nil {
  118. err = e
  119. return false
  120. }
  121. }
  122. return true
  123. }))
  124. return err
  125. }