InitializerLists.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. ================
  2. Initializer List
  3. ================
  4. This discussion took place in https://reviews.llvm.org/D35216
  5. "Escape symbols when creating std::initializer_list".
  6. It touches problems of modelling C++ standard library constructs in general,
  7. including modelling implementation-defined fields within C++ standard library
  8. objects, in particular constructing objects into pointers held by such fields,
  9. and separation of responsibilities between analyzer's core and checkers.
  10. **Artem:**
  11. I've seen a few false positives that appear because we construct
  12. C++11 std::initializer_list objects with brace initializers, and such
  13. construction is not properly modeled. For instance, if a new object is
  14. constructed on the heap only to be put into a brace-initialized STL container,
  15. the object is reported to be leaked.
  16. Approach (0): This can be trivially fixed by this patch, which causes pointers
  17. passed into initializer list expressions to immediately escape.
  18. This fix is overly conservative though. So i did a bit of investigation as to
  19. how model std::initializer_list better.
  20. According to the standard, ``std::initializer_list<T>`` is an object that has
  21. methods ``begin(), end(), and size()``, where ``begin()`` returns a pointer to continuous
  22. array of ``size()`` objects of type T, and end() is equal to begin() plus size().
  23. The standard does hint that it should be possible to implement
  24. ``std::initializer_list<T>`` as a pair of pointers, or as a pointer and a size
  25. integer, however specific fields that the object would contain are an
  26. implementation detail.
  27. Ideally, we should be able to model the initializer list's methods precisely.
  28. Or, at least, it should be possible to explain to the analyzer that the list
  29. somehow "takes hold" of the values put into it. Initializer lists can also be
  30. copied, which is a separate story that i'm not trying to address here.
  31. The obvious approach to modeling ``std::initializer_list`` in a checker would be to
  32. construct a SymbolMetadata for the memory region of the initializer list object,
  33. which would be of type ``T*`` and represent ``begin()``, so we'd trivially model ``begin()``
  34. as a function that returns this symbol. The array pointed to by that symbol
  35. would be ``bindLoc()``ed to contain the list's contents (probably as a ``CompoundVal``
  36. to produce less bindings in the store). Extent of this array would represent
  37. ``size()`` and would be equal to the length of the list as written.
  38. So this sounds good, however apparently it does nothing to address our false
  39. positives: when the list escapes, our ``RegionStoreManager`` is not magically
  40. guessing that the metadata symbol attached to it, together with its contents,
  41. should also escape. In fact, it's impossible to trigger a pointer escape from
  42. within the checker.
  43. Approach (1): If only we enabled ``ProgramState::bindLoc(..., notifyChanges=true)``
  44. to cause pointer escapes (not only region changes) (which sounds like the right
  45. thing to do anyway) such checker would be able to solve the false positives by
  46. triggering escapes when binding list elements to the list. However, it'd be as
  47. conservative as the current patch's solution. Ideally, we do not want escapes to
  48. happen so early. Instead, we'd prefer them to be delayed until the list itself
  49. escapes.
  50. So i believe that escaping metadata symbols whenever their base regions escape
  51. would be the right thing to do. Currently we didn't think about that because we
  52. had neither pointer-type metadatas nor non-pointer escapes.
  53. Approach (2): We could teach the Store to scan itself for bindings to
  54. metadata-symbolic-based regions during scanReachableSymbols() whenever a region
  55. turns out to be reachable. This requires no work on checker side, but it sounds
  56. performance-heavy.
  57. Approach (3): We could let checkers maintain the set of active metadata symbols
  58. in the program state (ideally somewhere in the Store, which sounds weird but
  59. causes the smallest amount of layering violations), so that the core knew what
  60. to escape. This puts a stress on the checkers, but with a smart data map it
  61. wouldn't be a problem.
  62. Approach (4): We could allow checkers to trigger pointer escapes in arbitrary
  63. moments. If we allow doing this within ``checkPointerEscape`` callback itself, we
  64. would be able to express facts like "when this region escapes, that metadata
  65. symbol attached to it should also escape". This sounds like an ultimate freedom,
  66. with maximum stress on the checkers - still not too much stress when we have
  67. smart data maps.
  68. I'm personally liking the approach (2) - it should be possible to avoid
  69. performance overhead, and clarity seems nice.
  70. **Gabor:**
  71. At this point, I am a bit wondering about two questions.
  72. * When should something belong to a checker and when should something belong to the engine?
  73. Sometimes we model library aspects in the engine and model language constructs in checkers.
  74. * What is the checker programming model that we are aiming for? Maximum freedom or more easy checker development?
  75. I think if we aim for maximum freedom, we do not need to worry about the
  76. potential stress on checkers, and we can introduce abstractions to mitigate that
  77. later on.
  78. If we want to simplify the API, then maybe it makes more sense to move language
  79. construct modeling to the engine when the checker API is not sufficient instead
  80. of complicating the API.
  81. Right now I have no preference or objections between the alternatives but there
  82. are some random thoughts:
  83. * Maybe it would be great to have a guideline how to evolve the analyzer and
  84. follow it, so it can help us to decide in similar situations
  85. * I do care about performance in this case. The reason is that we have a
  86. limited performance budget. And I think we should not expect most of the checker
  87. writers to add modeling of language constructs. So, in my opinion, it is ok to
  88. have less nice/more verbose API for language modeling if we can have better
  89. performance this way, since it only needs to be done once, and is done by the
  90. framework developers.
  91. **Artem:** These are some great questions, i guess it'd be better to discuss
  92. them more openly. As a quick dump of my current mood:
  93. * To me it seems obvious that we need to aim for a checker API that is both
  94. simple and powerful. This can probably by keeping the API as powerful as
  95. necessary while providing a layer of simple ready-made solutions on top of it.
  96. Probably a few reusable components for assembling checkers. And this layer
  97. should ideally be pleasant enough to work with, so that people would prefer to
  98. extend it when something is lacking, instead of falling back to the complex
  99. omnipotent API. I'm thinking of AST matchers vs. AST visitors as a roughly
  100. similar situation: matchers are not omnipotent, but they're so nice.
  101. * Separation between core and checkers is usually quite strange. Once we have
  102. shared state traits, i generally wouldn't mind having region store or range
  103. constraint manager as checkers (though it's probably not worth it to transform
  104. them - just a mood). The main thing to avoid here would be the situation when
  105. the checker overwrites stuff written by the core because it thinks it has a
  106. better idea what's going on, so the core should provide a good default behavior.
  107. * Yeah, i totally care about performance as well, and if i try to implement
  108. approach, i'd make sure it's good.
  109. **Artem:**
  110. > Approach (2): We could teach the Store to scan itself for bindings to
  111. > metadata-symbolic-based regions during scanReachableSymbols() whenever
  112. > a region turns out to be reachable. This requires no work on checker side,
  113. > but it sounds performance-heavy.
  114. Nope, this approach is wrong. Metadata symbols may become out-of-date: when the
  115. object changes, metadata symbols attached to it aren't changing (because symbols
  116. simply don't change). The same metadata may have different symbols to denote its
  117. value in different moments of time, but at most one of them represents the
  118. actual metadata value. So we'd be escaping more stuff than necessary.
  119. If only we had "ghost fields"
  120. (https://lists.llvm.org/pipermail/cfe-dev/2016-May/049000.html), it would have
  121. been much easier, because the ghost field would only contain the actual
  122. metadata, and the Store would always know about it. This example adds to my
  123. belief that ghost fields are exactly what we need for most C++ checkers.
  124. **Devin:**
  125. In this case, I would be fine with some sort of
  126. AbstractStorageMemoryRegion that meant "here is a memory region and somewhere
  127. reachable from here exists another region of type T". Or even multiple regions
  128. with different identifiers. This wouldn't specify how the memory is reachable,
  129. but it would allow for transfer functions to get at those regions and it would
  130. allow for invalidation.
  131. For ``std::initializer_list`` this reachable region would the region for the backing
  132. array and the transfer functions for begin() and end() yield the beginning and
  133. end element regions for it.
  134. In my view this differs from ghost variables in that (1) this storage does
  135. actually exist (it is just a library implementation detail where that storage
  136. lives) and (2) it is perfectly valid for a pointer into that storage to be
  137. returned and for another part of the program to read or write from that storage.
  138. (Well, in this case just read since it is allowed to be read-only memory).
  139. What I'm not OK with is modeling abstract analysis state (for example, the count
  140. of a NSMutableArray or the typestate of a file handle) as a value stored in some
  141. ginned up region in the store. This takes an easy problem that the analyzer does
  142. well at (modeling typestate) and turns it into a hard one that the analyzer is
  143. bad at (reasoning about the contents of the heap).
  144. I think the key criterion here is: "is the region accessible from outside the
  145. library". That is, does the library expose the region as a pointer that can be
  146. read to or written from in the client program? If so, then it makes sense for
  147. this to be in the store: we are modeling reachable storage as storage. But if
  148. we're just modeling arbitrary analysis facts that need to be invalidated when a
  149. pointer escapes then we shouldn't try to gin up storage for them just to get
  150. invalidation for free.
  151. **Artem:**
  152. > In this case, I would be fine with some sort of ``AbstractStorageMemoryRegion``
  153. > that meant "here is a memory region and somewhere reachable from here exists
  154. > another region of type T". Or even multiple regions with different
  155. > identifiers. This wouldn't specify how the memory is reachable, but it would
  156. > allow for transfer functions to get at those regions and it would allow for
  157. > invalidation.
  158. Yeah, this is what we can easily implement now as a
  159. symbolic-region-based-on-a-metadata-symbol (though we can make a new region
  160. class for that if we eg. want it typed). The problem is that the relation
  161. between such storage region and its parent object region is essentially
  162. immaterial, similarly to the relation between ``SymbolRegionValue`` and its parent
  163. region. Region contents are mutable: today the abstract storage is reachable
  164. from its parent object, tomorrow it's not, and maybe something else becomes
  165. reachable, something that isn't even abstract. So the parent region for the
  166. abstract storage is most of the time at best a "nice to know" thing - we cannot
  167. rely on it to do any actual work. We'd anyway need to rely on the checker to do
  168. the job.
  169. > For std::initializer_list this reachable region would the region for the
  170. > backing array and the transfer functions for begin() and end() yield the
  171. > beginning and end element regions for it.
  172. So maybe in fact for std::initializer_list it may work fine because you cannot
  173. change the data after the object is constructed - so this region's contents are
  174. essentially immutable. For the future, i feel as if it is a dead end.
  175. I'd like to consider another funny example. Suppose we're trying to model
  176. .. code-block:: cpp
  177. std::unique_ptr. Consider::
  178. void bar(const std::unique_ptr<int> &x);
  179. void foo(std::unique_ptr<int> &x) {
  180. int *a = x.get(); // (a, 0, direct): &AbstractStorageRegion
  181. *a = 1; // (AbstractStorageRegion, 0, direct): 1 S32b
  182. int *b = new int;
  183. *b = 2; // (SymRegion{conj_$0<int *>}, 0 ,direct): 2 S32b
  184. x.reset(b); // Checker map: x -> SymRegion{conj_$0<int *>}
  185. bar(x); // 'a' doesn't escape (the pointer was unique), 'b' does.
  186. clang_analyzer_eval(*a == 1); // Making this true is up to the checker.
  187. clang_analyzer_eval(*b == 2); // Making this unknown is up to the checker.
  188. }
  189. The checker doesn't totally need to ensure that ``*a == 1`` passes - even though the
  190. pointer was unique, it could theoretically have ``.get()``-ed above and the code
  191. could of course break the uniqueness invariant (though we'd probably want it).
  192. The checker can say that "even if ``*a`` did escape, it was not because it was
  193. stuffed directly into bar()".
  194. The checker's direct responsibility, however, is to solve the ``*b == 2`` thing
  195. (which is in fact the problem we're dealing with in this patch - escaping the
  196. storage region of the object).
  197. So we're talking about one more operation over the program state (scanning
  198. reachable symbols and regions) that cannot work without checker support.
  199. We can probably add a new callback "checkReachableSymbols" to solve this. This
  200. is in fact also related to the dead symbols problem (we're scanning for live
  201. symbols in the store and in the checkers separately, but we need to do so
  202. simultaneously with a single worklist). Hmm, in fact this sounds like a good
  203. idea; we can replace checkLiveSymbols with checkReachableSymbols.
  204. Or we could just have ghost member variables, and no checker support required at
  205. all. For ghost member variables, the relation with their parent region (which
  206. would be their superregion) is actually useful, the mutability of their contents
  207. is expressed naturally, and the store automagically sees reachable symbols, live
  208. symbols, escapes, invalidations, whatever.
  209. > In my view this differs from ghost variables in that (1) this storage does
  210. > actually exist (it is just a library implementation detail where that storage
  211. > lives) and (2) it is perfectly valid for a pointer into that storage to be
  212. > returned and for another part of the program to read or write from that
  213. > storage. (Well, in this case just read since it is allowed to be read-only
  214. > memory).
  215. > What I'm not OK with is modeling abstract analysis state (for example, the
  216. > count of a NSMutableArray or the typestate of a file handle) as a value stored
  217. > in some ginned up region in the store.This takes an easy problem that the
  218. > analyzer does well at (modeling typestate) and turns it into a hard one that
  219. > the analyzer is bad at (reasoning about the contents of the heap).
  220. Yeah, i tend to agree on that. For simple typestates, this is probably an
  221. overkill, so let's definitely put aside the idea of "ghost symbolic regions"
  222. that i had earlier.
  223. But, to summarize a bit, in our current case, however, the typestate we're
  224. looking for is the contents of the heap. And when we try to model such
  225. typestates (complex in this specific manner, i.e. heap-like) in any checker, we
  226. have a choice between re-doing this modeling in every such checker (which is
  227. something analyzer is indeed good at, but at a price of making checkers heavy)
  228. or instead relying on the Store to do exactly what it's designed to do.
  229. > I think the key criterion here is: "is the region accessible from outside
  230. > the library". That is, does the library expose the region as a pointer that
  231. > can be read to or written from in the client program? If so, then it makes
  232. > sense for this to be in the store: we are modeling reachable storage as
  233. > storage. But if we're just modeling arbitrary analysis facts that need to be
  234. > invalidated when a pointer escapes then we shouldn't try to gin up storage
  235. > for them just to get invalidation for free.
  236. As a metaphor, i'd probably compare it to body farms - the difference between
  237. ghost member variables and metadata symbols seems to me like the difference
  238. between body farms and evalCall. Both are nice to have, and body farms are very
  239. pleasant to work with, even if not omnipotent. I think it's fine for a
  240. FunctionDecl's body in a body farm to have a local variable, even if such
  241. variable doesn't actually exist, even if it cannot be seen from outside the
  242. function call. I'm not seeing immediate practical difference between "it does
  243. actually exist" and "it doesn't actually exist, just a handy abstraction".
  244. Similarly, i think it's fine if we have a ``CXXRecordDecl`` with
  245. implementation-defined contents, and try to farm up a member variable as a handy
  246. abstraction (we don't even need to know its name or offset, only that it's there
  247. somewhere).
  248. **Artem:**
  249. We've discussed it in person with Devin, and he provided more points to think
  250. about:
  251. * If the initializer list consists of non-POD data, constructors of list's
  252. objects need to take the sub-region of the list's region as this-region In the
  253. current (v2) version of this patch, these objects are constructed elsewhere and
  254. then trivial-copied into the list's metadata pointer region, which may be
  255. incorrect. This is our overall problem with C++ constructors, which manifests in
  256. this case as well. Additionally, objects would need to be constructed in the
  257. analyzer's core, which would not be able to predict that it needs to take a
  258. checker-specific region as this-region, which makes it harder, though it might
  259. be mitigated by sharing the checker state traits.
  260. * Because "ghost variables" are not material to the user, we need to somehow
  261. make super sure that they don't make it into the diagnostic messages.
  262. So, because this needs further digging into overall C++ support and rises too
  263. many questions, i'm delaying a better approach to this problem and will fall
  264. back to the original trivial patch.