isolation_check.nim 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Implementation of the check that `recover` needs, see
  10. ## https://github.com/nim-lang/RFCs/issues/244 for more details.
  11. import
  12. ast, types, renderer, intsets
  13. when defined(nimPreviewSlimSystem):
  14. import std/assertions
  15. proc canAlias(arg, ret: PType; marker: var IntSet): bool
  16. proc canAliasN(arg: PType; n: PNode; marker: var IntSet): bool =
  17. case n.kind
  18. of nkRecList:
  19. result = false
  20. for i in 0..<n.len:
  21. result = canAliasN(arg, n[i], marker)
  22. if result: return
  23. of nkRecCase:
  24. assert(n[0].kind == nkSym)
  25. result = canAliasN(arg, n[0], marker)
  26. if result: return
  27. for i in 1..<n.len:
  28. case n[i].kind
  29. of nkOfBranch, nkElse:
  30. result = canAliasN(arg, lastSon(n[i]), marker)
  31. if result: return
  32. else: discard
  33. of nkSym:
  34. result = canAlias(arg, n.sym.typ, marker)
  35. else: result = false
  36. proc canAlias(arg, ret: PType; marker: var IntSet): bool =
  37. if containsOrIncl(marker, ret.id):
  38. return false
  39. if ret.kind in {tyPtr, tyPointer}:
  40. # unsafe so we don't care:
  41. return false
  42. if compareTypes(arg, ret, dcEqIgnoreDistinct):
  43. return true
  44. case ret.kind
  45. of tyObject:
  46. if isFinal(ret):
  47. result = canAliasN(arg, ret.n, marker)
  48. if not result and ret.len > 0 and ret[0] != nil:
  49. result = canAlias(arg, ret[0], marker)
  50. else:
  51. result = true
  52. of tyTuple:
  53. result = false
  54. for i in 0..<ret.len:
  55. result = canAlias(arg, ret[i], marker)
  56. if result: break
  57. of tyArray, tySequence, tyDistinct, tyGenericInst,
  58. tyAlias, tyInferred, tySink, tyLent, tyOwned, tyRef:
  59. result = canAlias(arg, ret.lastSon, marker)
  60. of tyProc:
  61. result = ret.callConv == ccClosure
  62. else:
  63. result = false
  64. proc isValueOnlyType(t: PType): bool =
  65. # t doesn't contain pointers and references
  66. proc wrap(t: PType): bool {.nimcall.} = t.kind in {tyRef, tyPtr, tyVar, tyLent}
  67. result = not types.searchTypeFor(t, wrap)
  68. type
  69. SearchResult = enum
  70. NotFound, Abort, Found
  71. proc containsDangerousRefAux(t: PType; marker: var IntSet): SearchResult
  72. proc containsDangerousRefAux(n: PNode; marker: var IntSet): SearchResult =
  73. result = NotFound
  74. case n.kind
  75. of nkRecList:
  76. for i in 0..<n.len:
  77. result = containsDangerousRefAux(n[i], marker)
  78. if result == Found: return result
  79. of nkRecCase:
  80. assert(n[0].kind == nkSym)
  81. result = containsDangerousRefAux(n[0], marker)
  82. if result == Found: return result
  83. for i in 1..<n.len:
  84. case n[i].kind
  85. of nkOfBranch, nkElse:
  86. result = containsDangerousRefAux(lastSon(n[i]), marker)
  87. if result == Found: return result
  88. else: discard
  89. of nkSym:
  90. result = containsDangerousRefAux(n.sym.typ, marker)
  91. else: discard
  92. proc containsDangerousRefAux(t: PType; marker: var IntSet): SearchResult =
  93. result = NotFound
  94. if t == nil: return result
  95. if containsOrIncl(marker, t.id): return result
  96. if t.kind == tyRef or (t.kind == tyProc and t.callConv == ccClosure):
  97. result = Found
  98. elif tfSendable in t.flags:
  99. result = Abort
  100. else:
  101. # continue the type traversal:
  102. result = NotFound
  103. if result != NotFound: return result
  104. case t.kind
  105. of tyObject:
  106. if t[0] != nil:
  107. result = containsDangerousRefAux(t[0].skipTypes(skipPtrs), marker)
  108. if result == NotFound: result = containsDangerousRefAux(t.n, marker)
  109. of tyGenericInst, tyDistinct, tyAlias, tySink:
  110. result = containsDangerousRefAux(lastSon(t), marker)
  111. of tyArray, tySet, tyTuple, tySequence:
  112. for i in 0..<t.len:
  113. result = containsDangerousRefAux(t[i], marker)
  114. if result == Found: return result
  115. else:
  116. discard
  117. proc containsDangerousRef(t: PType): bool =
  118. # a `ref` type is "dangerous" if it occurs not within a type that is like `Isolated[T]`.
  119. # For example:
  120. # `ref int` # dangerous
  121. # `Isolated[ref int]` # not dangerous
  122. var marker = initIntSet()
  123. result = containsDangerousRefAux(t, marker) == Found
  124. proc canAlias*(arg, ret: PType): bool =
  125. if isValueOnlyType(arg):
  126. # can alias only with addr(arg.x) and we don't care if it is not safe
  127. result = false
  128. else:
  129. var marker = initIntSet()
  130. result = canAlias(arg, ret, marker)
  131. const
  132. SomeVar = {skForVar, skParam, skVar, skLet, skConst, skResult, skTemp}
  133. proc containsVariable(n: PNode): bool =
  134. case n.kind
  135. of nodesToIgnoreSet:
  136. result = false
  137. of nkSym:
  138. result = n.sym.kind in SomeVar
  139. else:
  140. for ch in n:
  141. if containsVariable(ch): return true
  142. result = false
  143. proc checkIsolate*(n: PNode): bool =
  144. if types.containsTyRef(n.typ):
  145. # XXX Maybe require that 'n.typ' is acyclic. This is not much
  146. # worse than the already exisiting inheritance and closure restrictions.
  147. case n.kind
  148. of nkCharLit..nkNilLit:
  149. result = true
  150. of nkCallKinds:
  151. # XXX: as long as we don't update the analysis while examining arguments
  152. # we can do an early check of the return type, otherwise this is a
  153. # bug and needs to be moved below
  154. if tfNoSideEffect notin n[0].typ.flags:
  155. return false
  156. for i in 1..<n.len:
  157. if checkIsolate(n[i]):
  158. discard "fine, it is isolated already"
  159. else:
  160. let argType = n[i].typ
  161. if argType != nil and not isCompileTimeOnly(argType) and containsDangerousRef(argType):
  162. if argType.canAlias(n.typ) or containsVariable(n[i]):
  163. # bug #19013: Alias information is not enough, we need to check for potential
  164. # "overlaps". I claim the problem can only happen by reading again from a location
  165. # that materialized which is only possible if a variable that contains a `ref`
  166. # is involved.
  167. return false
  168. result = true
  169. of nkIfStmt, nkIfExpr:
  170. result = false
  171. for it in n:
  172. result = checkIsolate(it.lastSon)
  173. if not result: break
  174. of nkCaseStmt:
  175. result = false
  176. for i in 1..<n.len:
  177. result = checkIsolate(n[i].lastSon)
  178. if not result: break
  179. of nkObjConstr:
  180. result = true
  181. for i in 1..<n.len:
  182. result = checkIsolate(n[i].lastSon)
  183. if not result: break
  184. of nkBracket, nkTupleConstr, nkPar:
  185. result = false
  186. for it in n:
  187. result = checkIsolate(it)
  188. if not result: break
  189. of nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
  190. result = checkIsolate(n[1])
  191. of nkObjUpConv, nkObjDownConv, nkDotExpr:
  192. result = checkIsolate(n[0])
  193. of nkStmtList, nkStmtListExpr:
  194. if n.len > 0:
  195. result = checkIsolate(n[^1])
  196. else:
  197. result = false
  198. of nkSym:
  199. result = true
  200. if n.sym.kind in SomeVar:
  201. let argType = n.typ
  202. if argType != nil and not isCompileTimeOnly(argType) and containsDangerousRef(argType):
  203. result = false
  204. else:
  205. # unanalysable expression:
  206. result = false
  207. else:
  208. # no ref, no cry:
  209. result = true