3

Consider the following code:

#include <string>
#include <vector>

struct Foo;

std::vector<Foo> v;

struct Foo {
    std::string s = std::string(10000, 'x');
    Foo() {}
    Foo(int) { v.clear(); }
};

int main() {
    v.resize(1);  // required, otherwise no double free
    v.reserve(2);  // optional
    v.emplace_back(10);  // causes double free
}

Here I call v.emplace_back which calls Foo(int) which calls v.clear() before emplace_back is completed. Seemingly, std::vector is unprepared for that mess and my implementation (Ubuntu 22.04's libstdc++) results in double free: v[0] is first destroyed by clear(), and later by v's destructor.

What wording in any C++ standard makes the behavior of the program above undefined? I'm "simply" calling some operations on v. I don't even access any elements of v.

yeputons
  • 8,478
  • 34
  • 67
  • 8
    *I'm "simply" calling some operations on `v`* is accessing the elements of `v`. `clear` has to destroy all of the elements, including the one that you are currently in the constructor body of. So you destroy the object before the constructor finishes. – NathanOliver Apr 28 '23 at 13:10
  • You are deleting the object before it is (finally) constructed. I guess it fails on trying to delete the uninitialized string. But it may do anything. – m2j Apr 28 '23 at 13:15
  • @m2j The string is a data member, it has to be initialized before the constructor body starts. – François Andrieux Apr 28 '23 at 13:18
  • @François Andrieux that's right. my bad. – m2j Apr 28 '23 at 13:19
  • 1
    `clear` invalidates all pointers and references to elements on the vector. This includes any pointer or reference that `emplace_back` might hold onto the object being constructed, or the storage that will be used for that object. I suspect there is no obvious rule against this, and it is only forbidden through a collection of other rules. – François Andrieux Apr 28 '23 at 13:22
  • 2
    I think [\[basic.life\]p6](https://timsong-cpp.github.io/cppwp/basic.life#6) and [\[class.cdtor\]p2](https://timsong-cpp.github.io/cppwp/class.cdtor#2) are the likely paragraphs controlling the behavior. – NathanOliver Apr 28 '23 at 13:24
  • @NathanOliver That's not what happens in my implementation, but I digress. That explanation raises another question: is the object being `emplace`d into vector considered its content that has to be `clear`ed? My implementation believes it is not: `v.size()` in the constructor is 1, not 2. – yeputons Apr 28 '23 at 13:34
  • 1
    Weirdest code I ever saw – Paul Sanders Apr 28 '23 at 13:54
  • I don't know about the standard, but self deleting objects just result in UB: they will be destructed again - leading to double-free on all owned resources. There are other examples of such ill-formed code. Does double-free need to be re-emphasized as UB?! – Red.Wave Apr 28 '23 at 14:07

1 Answers1

3

There is an active LWG issue 2414 to clarify reentrancy when different member functions of library objects are called. The current wording in [reentrancy] only specifies that it is implementation-defined which library functions are reentrant with regards to themselves (unless overruled by more specific rules).

user17732522
  • 53,019
  • 2
  • 56
  • 105