isolation_check.nim 6.9 KB

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