Environment owns a heap-allocated Grid through a raw pointer — grid = new Grid(id, size) in the constructor (src/environment.cpp:10) and delete grid in the destructor (src/environment.cpp:14) — but declares no copy constructor or copy-assignment operator (src/header/environment.h:14-15 declare only the constructor and destructor). The compiler therefore generates a shallow copy: two Environment objects end up holding the same Grid*, and the second one to be destroyed deletes an already-freed grid.
This is the same class of defect as #27 (a new/free mismatch in the destructor, fixed in #30): the destructor is now correct, but the object is still unsafe to copy. Any of the following triggers a double delete at scope exit:
Environment a(0, "Earth", 4);
Environment b = a; // copy-construct: b.grid == a.grid
Environment a(0, "Earth", 4);
Environment b(1, "Mars", 4);
b = a; // copy-assign: b's original grid is leaked, then a.grid is deleted twice
Passing an Environment by value to a function, or storing one in a std::vector<Environment> that reallocates, does the same. Nothing in the current test suite copies an Environment, so the suite does not observe it, but run_tests_asan.sh would report it as a heap-use-after-free / double-free the moment such a test were added.
Suggested fix: delete the copy operations in the header, matching the existing Grid() = delete; style already used in src/header/grid.h:
Environment(const Environment&) = delete;
Environment& operator=(const Environment&) = delete;
This turns the runtime double-free into a compile error for any caller that copies. (Alternatively, Grid* grid could become std::unique_ptr<Grid>, which gives the same non-copyable semantics and removes the explicit delete; either approach is acceptable, the deleted-copy pair being the smaller change.) A characterization test cannot assert a compile error, so the acceptance criterion is a clean bash run_tests_asan.sh run plus the two deleted declarations being present in the header.
This issue body was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).
drafted by Claude on behalf of Daniel Stephenson
Environmentowns a heap-allocatedGridthrough a raw pointer —grid = new Grid(id, size)in the constructor (src/environment.cpp:10) anddelete gridin the destructor (src/environment.cpp:14) — but declares no copy constructor or copy-assignment operator (src/header/environment.h:14-15declare only the constructor and destructor). The compiler therefore generates a shallow copy: twoEnvironmentobjects end up holding the sameGrid*, and the second one to be destroyed deletes an already-freed grid.This is the same class of defect as #27 (a
new/freemismatch in the destructor, fixed in #30): the destructor is now correct, but the object is still unsafe to copy. Any of the following triggers a doubledeleteat scope exit:Passing an
Environmentby value to a function, or storing one in astd::vector<Environment>that reallocates, does the same. Nothing in the current test suite copies anEnvironment, so the suite does not observe it, butrun_tests_asan.shwould report it as a heap-use-after-free / double-free the moment such a test were added.Suggested fix: delete the copy operations in the header, matching the existing
Grid() = delete;style already used insrc/header/grid.h:This turns the runtime double-free into a compile error for any caller that copies. (Alternatively,
Grid* gridcould becomestd::unique_ptr<Grid>, which gives the same non-copyable semantics and removes the explicitdelete; either approach is acceptable, the deleted-copy pair being the smaller change.) A characterization test cannot assert a compile error, so the acceptance criterion is a cleanbash run_tests_asan.shrun plus the two deleted declarations being present in the header.This issue body was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).
drafted by Claude on behalf of Daniel Stephenson