For C++98/C++03 (__cplusplus == 199711L), that block is skipped, so the compiler implicitly generates a public copy constructor and copy assignment operator. Because ConflictSet owns the opaque Impl *impl pointer, a copy shares the same Impl. When both objects are destroyed, internal_destroy is called twice, resulting in a double-free.
Impact: Any C++98/C++03 consumer who copies a ConflictSet (for example ConflictSet b = a;) will crash with a double-free. The C++ API is intended to be move-only, but that contract is not enforced for pre-C++11 compilation units.
Reproduction:
#include"ConflictSet.h"intmain(){weaselab::ConflictSeta(0);weaselab::ConflictSetb=a;// compiles in C++98, double-free at exit
return0;}
Compile with -std=c++98 and link against libconflict-set.so.0. In C++11 mode the same program correctly fails to compile because the copy constructor is deleted.
Expected behavior:ConflictSet(const ConflictSet &) and operator=(const ConflictSet &) should be inaccessible in C++98/C++03 (the usual pre-C++11 idiom is to declare them private and leave them undefined). Actual behavior: Copying is allowed and causes a double-free.
`include/ConflictSet.h` only deletes the copy constructor and copy assignment for C++11 and later:
```cpp
#if __cplusplus > 199711L
ConflictSet(ConflictSet &&) noexcept;
ConflictSet &operator=(ConflictSet &&) noexcept;
ConflictSet(const ConflictSet &) = delete;
ConflictSet &operator=(const ConflictSet &) = delete;
#endif
```
(`include/ConflictSet.h:123-128`)
For C++98/C++03 (`__cplusplus == 199711L`), that block is skipped, so the compiler implicitly generates a public copy constructor and copy assignment operator. Because `ConflictSet` owns the opaque `Impl *impl` pointer, a copy shares the same `Impl`. When both objects are destroyed, `internal_destroy` is called twice, resulting in a double-free.
**Impact:** Any C++98/C++03 consumer who copies a `ConflictSet` (for example `ConflictSet b = a;`) will crash with a double-free. The C++ API is intended to be move-only, but that contract is not enforced for pre-C++11 compilation units.
**Reproduction:**
```cpp
#include "ConflictSet.h"
int main() {
weaselab::ConflictSet a(0);
weaselab::ConflictSet b = a; // compiles in C++98, double-free at exit
return 0;
}
```
Compile with `-std=c++98` and link against `libconflict-set.so.0`. In C++11 mode the same program correctly fails to compile because the copy constructor is deleted.
**Expected behavior:** `ConflictSet(const ConflictSet &)` and `operator=(const ConflictSet &)` should be inaccessible in C++98/C++03 (the usual pre-C++11 idiom is to declare them private and leave them undefined).
**Actual behavior:** Copying is allowed and causes a double-free.
weaselbot
was assigned by andrew2026-06-21 13:10:38 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
include/ConflictSet.honly deletes the copy constructor and copy assignment for C++11 and later:(
include/ConflictSet.h:123-128)For C++98/C++03 (
__cplusplus == 199711L), that block is skipped, so the compiler implicitly generates a public copy constructor and copy assignment operator. BecauseConflictSetowns the opaqueImpl *implpointer, a copy shares the sameImpl. When both objects are destroyed,internal_destroyis called twice, resulting in a double-free.Impact: Any C++98/C++03 consumer who copies a
ConflictSet(for exampleConflictSet b = a;) will crash with a double-free. The C++ API is intended to be move-only, but that contract is not enforced for pre-C++11 compilation units.Reproduction:
Compile with
-std=c++98and link againstlibconflict-set.so.0. In C++11 mode the same program correctly fails to compile because the copy constructor is deleted.Expected behavior:
ConflictSet(const ConflictSet &)andoperator=(const ConflictSet &)should be inaccessible in C++98/C++03 (the usual pre-C++11 idiom is to declare them private and leave them undefined).Actual behavior: Copying is allowed and causes a double-free.