Laszlo, I do not have any specific issues with the undefined behavior of these APIs, and I agree with the limited utility of checking for NULL. I just wanted to make sure the style difference between this lib class and other lib classes in the MdePkg was on purpose.
I did misunderstand the behavior of Delete(). Your explanation helped, and I agree no changes are required. Not sure I see the value in the Validate() API. I can see it could be useful as an internal API during the development of an OrderedCollection library instance. What is the use case from a consumer of the OrderedCollection lib class perspective? Thanks, Mike -----Original Message----- From: Laszlo Ersek [mailto:ler...@redhat.com] Sent: Tuesday, August 05, 2014 3:11 PM To: edk2-devel@lists.sourceforge.net Subject: Re: [edk2] [PATCH v2 1/3] MdePkg: introduce OrderedCollectionLib library class On 08/05/14 23:16, Kinney, Michael D wrote: > Lazlo, > > These updates look really good. Just a few minor comments on the lib class: > > 1) UserStruct()/IsEmpty()/Min()/Max()/Next()/Prev()/Validate(): What > is the required behavior if an invalid pointer is passed in? The behavior is undefined. This mimics the C standard, which says 4. Conformance 2 If a ''shall'' or ''shall not'' requirement that appears outside of a constraint is violated, the behavior is undefined. Undefined behavior is otherwise indicated in this International Standard by the words ''undefined behavior'' or by the omission of any explicit definition of behavior. There is no difference in emphasis among these three; they all describe ''behavior that is undefined''. Thus, undefined behavior by the omission of any explicit definition of behavior. > Many of > the other MdePkg lib APIs will ASSERT() for NULL, which is really > only useful for debug builds. The library instance in the second patch doesn't even ASSERT() for NULL, in general; and the library class makes no such requirement. There's no way to detect if a pointer is valid or invalid. If a pointer is invalid, just *evaluating* it, for example, in order to compare it against NULL (that is, not *dereferencing* it) is undefined behavior. void *p; p = malloc(10); if (p != NULL) { free(p); if (p != NULL) { // <------ undefined behavior /* ... */ } } If the caller doesn't track its pointers carefully, there's nothing the callee can do. Checking for NULL might catch a minuscule subset. > 2) UnInit(): Should this API return an error status if the collection > is not empty? I don't see why. The specification clearly states the caller's responsibility. If the caller can't bother to ensure that, why would the caller bother to check the error? > What is the required behavior if an invalid pointer is > passed in? It is undefined. > Many of the other MdePkg lib APIs will ASSERT() for NULL, > which is really only useful for debug builds. You can't catch pointer errors in a non-managed environment. > 3) Insert()/Delete(): Should Collection be an a CONST [in]? Absolutely not. > Does the > caller need to consider the case there the Collection structure is > re-allocated to a new buffer location or the contents are modified? Yes, the contents can be modified. In the red-black tree library instance, a pointer to the root node of the tree is stored in the Collection, and that can change during rebalancing on insert/delete. > 4) Delete(): Should this API have a return code. Maybe SUCCESS for > deleted, and NOT_FOUND of the entry is not in the collection. This is incorrect. The Delete() API does not take a key to look up, and then to delete the associated node. The API of the library is iterator-centric. The example / tester application in the third patch demonstrates this, for example in the CmdDelete() function. If the caller wants to unlink a user structure with a specific key, the caller has to find associated node first, with the Find() API. If that node exists, then the iterator (not the key!) needs to be passed to the Delete() API. (This emphasis on iterators makes the library more flexible. For example, you can use the iterator (ie. pointer-to-Entry) returned by the Find() API to start a forward iteration from that point on.) Given the nature of the Delete() API -- ie. it takes a valid iterator, that is, a valid pointer to an ORDERED_COLLECTION_ENTRY), rather than a key -- there is no way to check for the callee to see if the pointer (the iterator) is valid. Consider the example of trying to unlinking the same-keyed user structure twice in succession, from the same tree: - On the first Find() call, assume a user structure is found with the requested key. An iterator is returned. (== A pointer is returned to the Entry that links the user structure with the requested key.) - The iterator is passed to Delete(), and the pointed-to entry is unlinked and released. The Delete() documentation clearly states, Existing ORDERED_COLLECTION_ENTRY pointers (ie. iterators) *different* from Entry remain valid. (The address of the user structure originally linked by that entry is reported back.) - Now, if the caller at this point tries to look up the key *again*, with the Find() API, Find() will return NULL. Hence the correct caller will know that no entry exists that links a user structure with a matching key. - If, instead, the caller just passes in the *same* iterator to Delete() again, you get undefined behavior. This is precisely the same as calling FreePool() twice in succession on the same pointer. The same way FreePool() -- or free() in standard C -- can't tell you if your pointer is stale, Delete() can't tell you if your pointer-to-Entry is stale. I'd like to emphasize the iterator-centricity of the Delete() interface (and the rest of the API). For example, - looking up key "2", - taking its successor with OrderedCollectionNext(), - then passing *that* iterator to Delete() is completely valid (assuming of course that the original Find() and the subsequent Next() suceed). > 5) Validate(): Should this API have a return code. Maybe SUCCESS if > the ordered collection is consistent. Some other error code if it is > inconsistent. I disagree. If you have an inconsistent data structure, in particular a red-black tree, then you possibly have stale / chaotic pointers in it. There's nothing to do. The Validate() function works by simply not triggering undefined behavior before returning (if it returns at all). The red-black tree implementation uses ASSERT()s exclusively for this reason, for checking validity. > Do you intend this to be able to do repair operations if the > collection is not ordered correctly and return a status that it was > repaired? No. Consider the example of duplicated keys. How do you recover from conflicting keys (which appear as a traversal disorder in Validate())? Which user structure do you evict? How do you return it to the client code? You can't. If basic invariants are violated, the world has ended. Thanks, Laszlo ------------------------------------------------------------------------------ Infragistics Professional Build stunning WinForms apps today! Reboot your WinForms applications with our WinForms controls. Build a bridge from your legacy apps to the future. http://pubads.g.doubleclick.net/gampad/clk?id=153845071&iu=/4140/ostg.clktrk _______________________________________________ edk2-devel mailing list edk2-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/edk2-devel ------------------------------------------------------------------------------ Infragistics Professional Build stunning WinForms apps today! Reboot your WinForms applications with our WinForms controls. Build a bridge from your legacy apps to the future. http://pubads.g.doubleclick.net/gampad/clk?id=153845071&iu=/4140/ostg.clktrk _______________________________________________ edk2-devel mailing list edk2-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/edk2-devel