From 204f399a0d581dd27f50410ff618c5e850883353 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 13:47:59 +0200 Subject: [PATCH 01/31] Add sanitizer and fault injection test support --- CMakeLists.txt | 44 ++++- gecode/support.hh | 3 + gecode/support/config.hpp.in | 3 + gecode/support/failpoint.cpp | 76 +++++++++ gecode/support/failpoint.hpp | 67 ++++++++ test/fault.cpp | 304 +++++++++++++++++++++++++++++++++++ 6 files changed, 495 insertions(+), 2 deletions(-) create mode 100644 gecode/support/failpoint.cpp create mode 100644 gecode/support/failpoint.hpp create mode 100644 test/fault.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f4f5c8c56..e560572729 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,12 +124,14 @@ option(GECODE_ENABLE_FLATZINC "Build FlatZinc module" ON) option(GECODE_ENABLE_MPFR "Enable MPFR support" ON) option(GECODE_ENABLE_ALLOCATOR "Enable default allocator" ON) option(GECODE_ENABLE_AUDIT "Enable audit code" OFF) +option(GECODE_ENABLE_FAULT_INJECTION "Enable deterministic test failpoints" OFF) option(GECODE_ENABLE_GCC_VISIBILITY "Enable GCC visibility attributes" ON) option(GECODE_ENABLE_OSX_UNFAIR_MUTEX "Enable macOS unfair mutexes" ON) option(GECODE_REGENERATE_VARIMP "Regenerate checked-in var-imp headers during build" OFF) option(GECODE_DOC_DOT "Enable Graphviz dot graphs in Doxygen documentation" ON) option(GECODE_DOC_SEARCH "Enable the Doxygen documentation search engine" OFF) option(GECODE_DOC_TAGFILE "Generate the Doxygen tag file" ON) +set(GECODE_SANITIZER "" CACHE STRING "Sanitizer instrumentation: empty, address, undefined, address-undefined, or thread") set(GECODE_FREELIST32_SIZE_MAX "" CACHE STRING "Max freelist size on 32-bit platforms") set(GECODE_FREELIST64_SIZE_MAX "" CACHE STRING "Max freelist size on 64-bit platforms") set(GECODE_WITH_VIS "" CACHE STRING "Additional .vis files (comma-separated)") @@ -199,6 +201,37 @@ function(gecode_force_option_on option reason) endif() endfunction() +string(TOLOWER "${GECODE_SANITIZER}" GECODE_SANITIZER_NORMALIZED) +set(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY ${GECODE_ENABLE_GCC_VISIBILITY}) +set(GECODE_SANITIZER_COMPILE_OPTIONS) +set(GECODE_SANITIZER_LINK_OPTIONS) +if(GECODE_SANITIZER_NORMALIZED) + if(GECODE_SANITIZER_NORMALIZED STREQUAL "address") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=address -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=address) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "undefined") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=undefined -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=undefined) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "address-undefined") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=address,undefined -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=address,undefined) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "thread") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=thread -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=thread) + else() + message(FATAL_ERROR "Unsupported GECODE_SANITIZER='${GECODE_SANITIZER}'. Expected empty, address, undefined, address-undefined, or thread.") + endif() + + add_compile_options(${GECODE_SANITIZER_COMPILE_OPTIONS}) + add_link_options(${GECODE_SANITIZER_LINK_OPTIONS}) + message(STATUS "Gecode sanitizer instrumentation: ${GECODE_SANITIZER_NORMALIZED}") + + if(GECODE_BUILD_SHARED AND GECODE_SANITIZER_NORMALIZED MATCHES "undefined") + set(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY OFF) + message(STATUS "Disabling hidden visibility for UBSan shared-library instrumentation") + endif() +endif() + # Keep dependency closure behavior similar to configure. if(GECODE_ENABLE_SET_VARS) gecode_force_option_on(GECODE_ENABLE_INT_VARS "Set variables require int variables") @@ -234,7 +267,7 @@ endif() include(CheckCXXCompilerFlag) set(GECODE_VISIBILITY_COMPILE_OPTION) -if(GECODE_ENABLE_GCC_VISIBILITY) +if(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY) check_cxx_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_FLAG) if(HAVE_VISIBILITY_HIDDEN_FLAG) set(GECODE_VISIBILITY_COMPILE_OPTION -fvisibility=hidden) @@ -477,6 +510,9 @@ endif() if(GECODE_ENABLE_AUDIT) set(GECODE_AUDIT "/**/") endif() +if(GECODE_ENABLE_FAULT_INJECTION) + set(GECODE_HAS_FAULT_INJECTION 1) +endif() if(GECODE_ENABLE_CPPROFILER) set(GECODE_HAS_CPPROFILER "/**/") endif() @@ -486,7 +522,7 @@ endif() if(GECODE_ENABLE_MPFR AND MPFR_FOUND AND GECODE_ENABLE_FLOAT_VARS) set(GECODE_HAS_MPFR "/**/") endif() -if(HAVE_VISIBILITY_HIDDEN_FLAG) +if(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY AND HAVE_VISIBILITY_HIDDEN_FLAG) set(GECODE_GCC_HAS_CLASS_VISIBILITY "/**/") endif() if(GECODE_ENABLE_THREAD) @@ -569,6 +605,10 @@ ${CONFIG_OUT}") # Native source inventory for CMake (decoupled from Makefile.in). # --------------------------------------------------------------------------- include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GecodeSources.cmake) +if(GECODE_ENABLE_FAULT_INJECTION) + list(APPEND GECODE_SUPPORT_SOURCES gecode/support/failpoint.cpp) + list(APPEND GECODE_TEST_SOURCES test/fault.cpp) +endif() # --------------------------------------------------------------------------- # Generated variable implementations (in-source, matching Makefile behavior) diff --git a/gecode/support.hh b/gecode/support.hh index d78117a33f..a4ce3a1730 100644 --- a/gecode/support.hh +++ b/gecode/support.hh @@ -87,6 +87,9 @@ #include #include +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif #include #include #include diff --git a/gecode/support/config.hpp.in b/gecode/support/config.hpp.in index bc78cb03c0..df8070abc3 100644 --- a/gecode/support/config.hpp.in +++ b/gecode/support/config.hpp.in @@ -108,6 +108,9 @@ /* Whether we are compiling with thread support */ #undef GECODE_HAS_THREADS +/* Whether deterministic test failpoints are enabled */ +#undef GECODE_HAS_FAULT_INJECTION + /* Whether to use unfair mutexes on macOS */ #undef GECODE_USE_OSX_UNFAIR_MUTEX diff --git a/gecode/support/failpoint.cpp b/gecode/support/failpoint.cpp new file mode 100644 index 0000000000..1b475fc1a8 --- /dev/null +++ b/gecode/support/failpoint.cpp @@ -0,0 +1,76 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Christian Schulte + * + * Copyright: + * Christian Schulte, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include + +#ifdef GECODE_HAS_FAULT_INJECTION + +namespace Gecode { namespace Support { namespace FailPoint { + + namespace { + std::atomic fail_after_count; + std::atomic seen_count; + std::atomic active_phase; + } + + void reset(void) { + active_phase.store(static_cast(Phase::Disabled), + std::memory_order_release); + fail_after_count.store(0, std::memory_order_release); + seen_count.store(0, std::memory_order_release); + } + + void fail_after(Phase p, unsigned long long n) { + seen_count.store(0, std::memory_order_release); + fail_after_count.store(n, std::memory_order_release); + active_phase.store(static_cast(p), std::memory_order_release); + } + + void check(Phase p) { + if (active_phase.load(std::memory_order_acquire) != static_cast(p)) + return; + unsigned long long seen = + seen_count.fetch_add(1, std::memory_order_acq_rel); + if (seen >= fail_after_count.load(std::memory_order_acquire)) + throw MemoryExhausted(); + } + + unsigned long long count(void) { + return seen_count.load(std::memory_order_acquire); + } + +}}} + +#endif + +// STATISTICS: support-any diff --git a/gecode/support/failpoint.hpp b/gecode/support/failpoint.hpp new file mode 100644 index 0000000000..139bd6ee8c --- /dev/null +++ b/gecode/support/failpoint.hpp @@ -0,0 +1,67 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Christian Schulte + * + * Copyright: + * Christian Schulte, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SUPPORT_FAILPOINT_HPP +#define GECODE_SUPPORT_FAILPOINT_HPP + +#include + +namespace Gecode { namespace Support { namespace FailPoint { + + /// Named phase for deterministic test failure injection + enum class Phase { + Disabled, + Heap, + PropagatorCopy, + BrancherCopy, + DerivedSpaceCopy, + LocalObjectCopy, + SpaceDisposalArray, + IntSet, + MiniModel + }; + + /// Reset all failpoint state + GECODE_SUPPORT_EXPORT void reset(void); + /// Fail after \a n successful checks for phase \a p + GECODE_SUPPORT_EXPORT void fail_after(Phase p, unsigned long long n); + /// Check failpoint for phase \a p + GECODE_SUPPORT_EXPORT void check(Phase p); + /// Number of checks observed for the configured phase + GECODE_SUPPORT_EXPORT unsigned long long count(void); + +}}} + +#endif + +// STATISTICS: support-any diff --git a/test/fault.cpp b/test/fault.cpp new file mode 100644 index 0000000000..e384bba622 --- /dev/null +++ b/test/fault.cpp @@ -0,0 +1,304 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Christian Schulte + * + * Copyright: + * Christian Schulte, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include + +#include "test/test.hh" + +namespace Test { namespace Fault { + + using namespace Gecode; + using Gecode::Support::FailPoint::Phase; + + class ThrowingPropagator : public Propagator { + protected: + ThrowingPropagator(Home home) : Propagator(home) {} + ThrowingPropagator(Space& home, ThrowingPropagator& p) + : Propagator(home,p) { + Support::FailPoint::check(Phase::PropagatorCopy); + } + public: + static void post(Home home) { + (void) new (home) ThrowingPropagator(home); + } + virtual Actor* copy(Space& home) { + return new (home) ThrowingPropagator(home,*this); + } + virtual PropCost cost(const Space&, const ModEventDelta&) const { + return PropCost::unary(PropCost::LO); + } + virtual void reschedule(Space&) {} + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + return home.ES_SUBSUMED(*this); + } + virtual size_t dispose(Space& home) { + (void) Propagator::dispose(home); + return sizeof(*this); + } + }; + + class ThrowingChoice : public Choice { + public: + ThrowingChoice(const Brancher& b) : Choice(b,1) {} + virtual void archive(Archive& e) const { + Choice::archive(e); + } + }; + + class ThrowingBrancher : public Brancher { + protected: + bool done; + ThrowingBrancher(Home home) : Brancher(home), done(false) {} + ThrowingBrancher(Space& home, ThrowingBrancher& b) + : Brancher(home,b), done(b.done) { + Support::FailPoint::check(Phase::BrancherCopy); + } + public: + static void post(Home home) { + (void) new (home) ThrowingBrancher(home); + } + virtual bool status(const Space&) const { + return !done; + } + virtual const Choice* choice(Space&) { + return new ThrowingChoice(*this); + } + virtual const Choice* choice(const Space&, Archive&) { + return new ThrowingChoice(*this); + } + virtual ExecStatus commit(Space&, const Choice&, unsigned int) { + done = true; + return ES_OK; + } + virtual void print(const Space&, const Choice&, + unsigned int, std::ostream&) const {} + virtual Actor* copy(Space& home) { + return new (home) ThrowingBrancher(home,*this); + } + virtual size_t dispose(Space&) { + return sizeof(*this); + } + }; + + class CloneCopySpace : public Space { + public: + CloneCopySpace(void) { + ThrowingPropagator::post(*this); + ThrowingBrancher::post(*this); + } + CloneCopySpace(CloneCopySpace& s) : Space(s) {} + virtual Space* copy(void) { + return new CloneCopySpace(*this); + } + }; + + class DerivedCopySpace : public Space { + public: + IntVarArray x; + DerivedCopySpace(void) : x(*this,2,0,1) { + ThrowingPropagator::post(*this); + ThrowingBrancher::post(*this); + } + DerivedCopySpace(DerivedCopySpace& s) : Space(s) { + Support::FailPoint::check(Phase::DerivedSpaceCopy); + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new DerivedCopySpace(*this); + } + }; + + class FaultLocalObject : public LocalObject { + public: + int value; + FaultLocalObject(Home home, int v) : LocalObject(home), value(v) {} + FaultLocalObject(Space& home, FaultLocalObject& o) + : LocalObject(home,o), value(o.value) { + Support::FailPoint::check(Phase::LocalObjectCopy); + } + virtual Actor* copy(Space& home) { + return new (home) FaultLocalObject(home,*this); + } + virtual size_t dispose(Space&) { + return sizeof(*this); + } + }; + + class FaultLocalHandle : public LocalHandle { + public: + FaultLocalHandle(void) {} + FaultLocalHandle(FaultLocalObject* o) : LocalHandle(o) {} + void update(Space& home, FaultLocalHandle& h) { + LocalHandle::update(home,h); + } + int value(void) const { + return static_cast(object())->value; + } + }; + + class LocalCopySpace : public Space { + public: + FaultLocalHandle h; + LocalCopySpace(void) + : h(new (*this) FaultLocalObject(*this,42)) {} + LocalCopySpace(LocalCopySpace& s) : Space(s) { + h.update(*this,s.h); + } + virtual Space* copy(void) { + return new LocalCopySpace(*this); + } + }; + + bool clone_after_failed_copy(Phase p) { + CloneCopySpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(p,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Space* c = s.clone(); + delete c; + const Choice* ch = s.choice(); + bool ok = ch != nullptr; + if (ch != nullptr) { + s.commit(*ch,0); + delete ch; + } + return ok && (s.status() != SS_FAILED); + } + + bool expect_memory_exhausted(Phase p, unsigned long long n) { + DerivedCopySpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(p,n); + try { + Space* c = s.clone(); + delete c; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + Space* c = s.clone(); + delete c; + const Choice* ch = s.choice(); + bool ok = ch != nullptr; + if (ch != nullptr) { + s.commit(*ch,0); + delete ch; + } + return ok && (s.status() != SS_FAILED); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Support::FailPoint::reset(); + return false; + } + + bool clone_after_failed_local_object_copy(void) { + LocalCopySpace s; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::LocalObjectCopy,0); + try { + Space* c = s.clone(); + delete c; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + if (s.h.value() != 42) + return false; + LocalCopySpace* c = static_cast(s.clone()); + bool ok = (c != nullptr) && (c->h.value() == 42); + delete c; + return ok; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Support::FailPoint::reset(); + return false; + } + + class ClonePropagatorCopy : public Base { + public: + ClonePropagatorCopy(void) + : Base("Fault::Clone::PropagatorCopy") {} + virtual bool run(void) { + return clone_after_failed_copy(Phase::PropagatorCopy); + } + }; + + class CloneBrancherCopy : public Base { + public: + CloneBrancherCopy(void) + : Base("Fault::Clone::BrancherCopy") {} + virtual bool run(void) { + return clone_after_failed_copy(Phase::BrancherCopy); + } + }; + + class CloneDerivedSpaceCopy : public Base { + public: + CloneDerivedSpaceCopy(void) + : Base("Fault::Clone::DerivedSpaceCopy") {} + virtual bool run(void) { + return expect_memory_exhausted(Phase::DerivedSpaceCopy,0); + } + }; + + class CloneLocalObjectCopy : public Base { + public: + CloneLocalObjectCopy(void) + : Base("Fault::Clone::LocalObjectCopy") {} + virtual bool run(void) { + return clone_after_failed_local_object_copy(); + } + }; + + ClonePropagatorCopy clone_propagator_copy; + CloneBrancherCopy clone_brancher_copy; + CloneDerivedSpaceCopy clone_derived_space_copy; + CloneLocalObjectCopy clone_local_object_copy; + +}} + +// STATISTICS: test-fault From de8172558b57ff87f253692de77ebb896a64513e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 14:11:55 +0200 Subject: [PATCH 02/31] Add disposal-array clone fault regression --- gecode/kernel/core.cpp | 3 +++ test/fault.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index c83d70a8a4..f563653a67 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -768,6 +768,9 @@ namespace Gecode { c->d_fst = c->d_cur = c->d_lst = nullptr; } else { // Leave one entry free +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposalArray); +#endif c->d_fst = c->alloc(n+1); c->d_cur = c->d_fst; c->d_lst = c->d_fst+n+1; diff --git a/test/fault.cpp b/test/fault.cpp index e384bba622..e85b332f9a 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -32,6 +32,7 @@ */ #include +#include #include "test/test.hh" @@ -122,6 +123,20 @@ namespace Test { namespace Fault { } }; + class DisposeSpace : public Space { + public: + IntVarArray x; + DisposeSpace(void) : x(*this,3,0,2) { + branch(*this,x,INT_VAR_ACTION_SIZE_MAX(),INT_VAL_MIN()); + } + DisposeSpace(DisposeSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new DisposeSpace(*this); + } + }; + class DerivedCopySpace : public Space { public: IntVarArray x; @@ -258,6 +273,42 @@ namespace Test { namespace Fault { return false; } + bool clone_after_failed_disposal_array(void) { + DisposeSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::SpaceDisposalArray,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Space* c = s.clone(); + delete c; + DisposeSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return ok; + } + + class CloneDisposalArray : public Base { + public: + CloneDisposalArray(void) + : Base("Fault::Clone::DisposalArray") {} + virtual bool run(void) { + return clone_after_failed_disposal_array(); + } + }; + class ClonePropagatorCopy : public Base { public: ClonePropagatorCopy(void) @@ -294,6 +345,7 @@ namespace Test { namespace Fault { } }; + CloneDisposalArray clone_disposal_array; ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From 486a2aafff5151ee764d00fe229742ca73d80b40 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 15:10:17 +0200 Subject: [PATCH 03/31] Recover clone state on disposal-array failure --- gecode/kernel/core.cpp | 31 +++++++++++----- gecode/kernel/core.hpp | 41 ++++++++++++++++++++- gecode/kernel/var-imp.hpp | 77 +++++++++++++++++++++++++++++++++++++++ misc/genvarimp.py | 74 +++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 12 deletions(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index f563653a67..9f6550a59f 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -166,6 +166,9 @@ namespace Gecode { void Space::ap_ignore_dispose(Actor* a, bool duplicate) { // Note that a might be a marked pointer! + if (inPrematureDestructionMode()) + return; + assert(d_fst != nullptr); Actor** f = d_fst; if (duplicate) { @@ -768,18 +771,26 @@ namespace Gecode { c->d_fst = c->d_cur = c->d_lst = nullptr; } else { // Leave one entry free + try { #ifdef GECODE_HAS_FAULT_INJECTION - Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposalArray); + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposalArray); #endif - c->d_fst = c->alloc(n+1); - c->d_cur = c->d_fst; - c->d_lst = c->d_fst+n+1; - for (Actor** d_fst_iter = d_fst; d_fst_iter != d_cur; d_fst_iter++) { - ptrdiff_t m; - Actor* a = static_cast(Support::ptrsplit(*d_fst_iter,m)); - if (a->prev()) - *(c->d_cur++) = Actor::cast(static_cast - (Support::ptrjoin(a->prev(),m))); + c->d_fst = c->alloc(n+1); + c->d_cur = c->d_fst; + c->d_lst = c->d_fst+n+1; + for (Actor** d_fst_iter = d_fst; d_fst_iter != d_cur; d_fst_iter++) { + ptrdiff_t m; + Actor* a = static_cast(Support::ptrsplit(*d_fst_iter,m)); + if (a->prev()) + *(c->d_cur++) = Actor::cast(static_cast + (Support::ptrjoin(a->prev(),m))); + } + } + catch (const std::exception&) { + c->recover(*this); + c->d_fst = c->d_cur = c->d_lst = nullptr; + delete c; + throw; } } } diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index affa266743..a4dc74d767 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -298,6 +298,9 @@ namespace Gecode { * to be stored. */ static void update(Space& home, ActorLink**& sub); + /// Recover copied variables after failed clone construction + static void revertHarmfulChangesOfUnfinishedClone(Space& home, + ActorLink**& sub); /// Enter propagator to subscription array void enter(Space& home, Propagator* p, PropCond pc); @@ -1892,6 +1895,15 @@ namespace Gecode { void update(ActorLink** sub); //@} + /// Update variables without indexing structure + void updateNoIdx(Space* space, bool recover); + + /// Recover after failed clone construction + virtual void recover(Space& source); + + /// Test whether clone construction has not reached a disposable state + bool inPrematureDestructionMode(void) const; + /// First actor for forced disposal Actor** d_fst; /// Current actor for forced disposal @@ -3030,6 +3042,11 @@ namespace Gecode { } #endif + forceinline bool + Space::inPrematureDestructionMode(void) const { + return d_fst == &Actor::sentinel; + } + // Space allocated entities: Actors, variable implementations, and advisors forceinline void Actor::operator delete(void*) {} @@ -4503,7 +4520,7 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Propagator& p, PropCond pc) { - if (b.base != nullptr) + if ((b.base != nullptr) && !home.inPrematureDestructionMode()) remove(home,&p,pc); } @@ -4533,7 +4550,7 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Advisor& a, bool fail) { - if (b.base != nullptr) { + if ((b.base != nullptr) && !home.inPrematureDestructionMode()) { Advisor* ma = static_cast(Support::ptrjoin(&a,fail ? 1 : 0)); remove(home,ma); } @@ -4723,6 +4740,26 @@ namespace Gecode { } } + template + forceinline void + VarImp::revertHarmfulChangesOfUnfinishedClone(Space& home, + ActorLink**&) { + VarImp* x = static_cast*>(home.pc.c.vars_u[idx_c]); + while (x != nullptr) { + if (x->copied()) { + VarImp* n = x->next(); + VarImp* copy = x->forward(); + x->b.base = copy->b.base; + x->u.idx[0] = copy->u.idx[0]; + if (pc_max > 0 && sizeof(ActorLink**) > sizeof(unsigned int)) + x->u.idx[1] = copy->u.idx[1]; + x = n; + } else { + break; + } + } + } + /* diff --git a/gecode/kernel/var-imp.hpp b/gecode/kernel/var-imp.hpp index a6cba54e1d..10479ba8a6 100644 --- a/gecode/kernel/var-imp.hpp +++ b/gecode/kernel/var-imp.hpp @@ -478,6 +478,19 @@ namespace Gecode { namespace Float { #endif namespace Gecode { + forceinline void + Space::updateNoIdx(Space* space, bool) { + VarImp* x = + static_cast*>(space->pc.c.vars_noidx); + while (x != nullptr) { + VarImp* n = x->next(); + x->b.base = nullptr; x->u.idx[0] = 0; + if (sizeof(ActorLink**) > sizeof(unsigned int)) + *(1+&x->u.idx[0]) = 0; + x = n; + } + } + forceinline void Space::update(ActorLink** sub) { #ifdef GECODE_HAS_INT_VARS @@ -491,6 +504,70 @@ namespace Gecode { #endif #ifdef GECODE_HAS_FLOAT_VARS Gecode::VarImp::update(*this,sub); +#endif + } + + forceinline void + Space::recover(Space& source) { + ActorLink* clone_lists[2] = {&pl, &bl}; + for (int i=0; i<2; i++) { + ActorLink* l = clone_lists[i]; + ActorLink* c = l->next(); + while (c != l) { + ActorLink* n = c->next(); + (void) Actor::cast(c)->dispose(*this); + c = n; + } + l->init(); + } + while (pc.c.local != nullptr) { + LocalObject* l = pc.c.local; + ActorLink* n = l->next(); + pc.c.local = (n == nullptr) ? nullptr : LocalObject::cast(n); + ActorLink* fwd = l->prev(); + if (fwd != nullptr) { + (void) Actor::cast(fwd)->dispose(*this); + l->prev(nullptr); + } + } + + ActorLink* lists[2] = {&source.pl, &source.bl}; + for (int i=0; i<2; i++) { + ActorLink* l = lists[i]; + ActorLink* p_a = l; + ActorLink* c_a = p_a->next(); + while (c_a != l) { + if (i == 0) { + Propagator* p = Propagator::cast(c_a); + if (p->u.advisors != nullptr) { + ActorLink* a = p->u.advisors; + p->u.advisors = nullptr; + do { + a->prev(p); a = a->next(); + } while (a != nullptr); + } + } + ActorLink* fwd = c_a->prev(); + if (fwd != p_a) { + c_a->prev(p_a); + } + p_a = c_a; c_a = c_a->next(); + } + } + + ActorLink** sub = static_cast(mm.subscriptions()); + Space::updateNoIdx(this, true); +#ifdef GECODE_HAS_INT_VARS + Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); +#endif +#ifdef GECODE_HAS_INT_VARS + Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); +#endif +#ifdef GECODE_HAS_SET_VARS + Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); +#endif +#ifdef GECODE_HAS_FLOAT_VARS + Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); #endif } } diff --git a/misc/genvarimp.py b/misc/genvarimp.py index e32c1ad5f9..5ca3be0d83 100644 --- a/misc/genvarimp.py +++ b/misc/genvarimp.py @@ -855,6 +855,19 @@ def generate_header(files: List[Dict], out: List[str]) -> None: out.append( """namespace Gecode { + forceinline void + Space::updateNoIdx(Space* space, bool) { + VarImp* x = + static_cast*>(space->pc.c.vars_noidx); + while (x != nullptr) { + VarImp* n = x->next(); + x->b.base = nullptr; x->u.idx[0] = 0; + if (sizeof(ActorLink**) > sizeof(unsigned int)) + *(1+&x->u.idx[0]) = 0; + x = n; + } + } + forceinline void Space::update(ActorLink** sub) { """ @@ -867,6 +880,67 @@ def generate_header(files: List[Dict], out: List[str]) -> None: out.append( """ } + + forceinline void + Space::recover(Space& source) { + ActorLink* clone_lists[2] = {&pl, &bl}; + for (int i=0; i<2; i++) { + ActorLink* l = clone_lists[i]; + ActorLink* c = l->next(); + while (c != l) { + ActorLink* n = c->next(); + (void) Actor::cast(c)->dispose(*this); + c = n; + } + l->init(); + } + while (pc.c.local != nullptr) { + LocalObject* l = pc.c.local; + ActorLink* n = l->next(); + pc.c.local = (n == nullptr) ? nullptr : LocalObject::cast(n); + ActorLink* fwd = l->prev(); + if (fwd != nullptr) { + (void) Actor::cast(fwd)->dispose(*this); + l->prev(nullptr); + } + } + + ActorLink* lists[2] = {&source.pl, &source.bl}; + for (int i=0; i<2; i++) { + ActorLink* l = lists[i]; + ActorLink* p_a = l; + ActorLink* c_a = p_a->next(); + while (c_a != l) { + if (i == 0) { + Propagator* p = Propagator::cast(c_a); + if (p->u.advisors != nullptr) { + ActorLink* a = p->u.advisors; + p->u.advisors = nullptr; + do { + a->prev(p); a = a->next(); + } while (a != nullptr); + } + } + ActorLink* fwd = c_a->prev(); + if (fwd != p_a) { + c_a->prev(p_a); + } + p_a = c_a; c_a = c_a->next(); + } + } + + ActorLink** sub = static_cast(mm.subscriptions()); + Space::updateNoIdx(this, true); +""" + ) + + for d in files: + out.append(d["ifdef"]) + out.append(f" {d['base']}::revertHarmfulChangesOfUnfinishedClone(*this,sub);\n") + out.append(d["endif"]) + + out.append( + """ } } """ ) From 6e130175ea04848a25440da5ff461596cd8ce8f6 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 15:13:02 +0200 Subject: [PATCH 04/31] Add dispose-notice allocation fault regression --- gecode/kernel/core.cpp | 6 ++++ gecode/support/failpoint.hpp | 1 + test/fault.cpp | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 9f6550a59f..d46244ce74 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -148,6 +148,9 @@ namespace Gecode { // Resize if (d_fst == nullptr) { // Create new array +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); +#endif d_fst = alloc(4); d_cur = d_fst; d_lst = d_fst+4; @@ -155,6 +158,9 @@ namespace Gecode { // Resize existing array unsigned int n = static_cast(d_lst - d_fst); assert(n != 0); +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); +#endif d_fst = realloc(d_fst,n,2*n); d_cur = d_fst+n; d_lst = d_fst+2*n; diff --git a/gecode/support/failpoint.hpp b/gecode/support/failpoint.hpp index 139bd6ee8c..ffa32dcee5 100644 --- a/gecode/support/failpoint.hpp +++ b/gecode/support/failpoint.hpp @@ -46,6 +46,7 @@ namespace Gecode { namespace Support { namespace FailPoint { BrancherCopy, DerivedSpaceCopy, LocalObjectCopy, + SpaceDisposeNoticeArray, SpaceDisposalArray, IntSet, MiniModel diff --git a/test/fault.cpp b/test/fault.cpp index e85b332f9a..6407f7ef4d 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -137,6 +137,38 @@ namespace Test { namespace Fault { } }; + class NoticeDisposeActor : public Actor { + public: + static int disposed; + + NoticeDisposeActor(Home home) : Actor() { + home.notice(*this,AP_DISPOSE); + } + static void post(Home home) { + (void) new (home) NoticeDisposeActor(home); + } + virtual Actor* copy(Space& home) { + return new (home) NoticeDisposeActor(home); + } + virtual size_t dispose(Space&) { + disposed++; + return sizeof(*this); + } + }; + + int NoticeDisposeActor::disposed = 0; + + class NoticeDisposeSpace : public Space { + public: + NoticeDisposeSpace(void) { + NoticeDisposeActor::post(*this); + } + NoticeDisposeSpace(NoticeDisposeSpace& s) : Space(s) {} + virtual Space* copy(void) { + return new NoticeDisposeSpace(*this); + } + }; + class DerivedCopySpace : public Space { public: IntVarArray x; @@ -300,6 +332,32 @@ namespace Test { namespace Fault { return ok; } + bool dispose_notice_failure_disposes_actor(void) { + NoticeDisposeActor::disposed = 0; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::SpaceDisposeNoticeArray,0); + try { + NoticeDisposeSpace s; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return NoticeDisposeActor::disposed == 1; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + class DisposeNoticeArray : public Base { + public: + DisposeNoticeArray(void) + : Base("Fault::Dispose::NoticeArray") {} + virtual bool run(void) { + return dispose_notice_failure_disposes_actor(); + } + }; + class CloneDisposalArray : public Base { public: CloneDisposalArray(void) @@ -346,6 +404,7 @@ namespace Test { namespace Fault { }; CloneDisposalArray clone_disposal_array; + DisposeNoticeArray dispose_notice_array; ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From a507282a487e10834ac9a2724612dd71bd47ba6c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 15:13:48 +0200 Subject: [PATCH 05/31] Dispose actor when dispose-notice allocation fails --- gecode/kernel/core.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index d46244ce74..5e296e2c14 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -148,20 +148,32 @@ namespace Gecode { // Resize if (d_fst == nullptr) { // Create new array + try { #ifdef GECODE_HAS_FAULT_INJECTION - Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); #endif - d_fst = alloc(4); + d_fst = alloc(4); + } catch (...) { + if ((a != nullptr) && !Support::marked(a)) + (void) a->dispose(*this); + throw; + } d_cur = d_fst; d_lst = d_fst+4; } else { // Resize existing array unsigned int n = static_cast(d_lst - d_fst); assert(n != 0); + try { #ifdef GECODE_HAS_FAULT_INJECTION - Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); #endif - d_fst = realloc(d_fst,n,2*n); + d_fst = realloc(d_fst,n,2*n); + } catch (...) { + if ((a != nullptr) && !Support::marked(a)) + (void) a->dispose(*this); + throw; + } d_cur = d_fst+n; d_lst = d_fst+2*n; } From a9f65dd2a4c4731abf177f16c3324085ce7f46d7 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 20:04:58 +0200 Subject: [PATCH 06/31] Add IntSet allocation fault regression --- gecode/int.hh | 14 ++++++++++++++ gecode/int/int-set.cpp | 30 +++++++++++++++++++++++++++++- test/fault.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/gecode/int.hh b/gecode/int.hh index 2c172305f1..b4dd6173a8 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -188,6 +188,14 @@ namespace Gecode { int n; /// Array of ranges Range* r; +#ifdef GECODE_HAS_FAULT_INJECTION + /// Number of live objects for fault-injection tests + static int fault_live_objects; + /// Memory management for fault-injection accounting + static void* operator new(size_t s); + /// Memory management for fault-injection accounting + static void operator delete(void* p); +#endif /// Allocate object with \a m elements GECODE_INT_EXPORT static IntSetObject* allocate(int m); /// Check whether \a n is included in the set @@ -208,6 +216,12 @@ namespace Gecode { /// Initialize with \a n ranges from array \a r GECODE_INT_EXPORT void init(const int r[][2], int n); public: +#ifdef GECODE_HAS_FAULT_INJECTION + /// Reset live object accounting for fault-injection tests + GECODE_INT_EXPORT static void fault_reset_allocations(void); + /// Return live object accounting for fault-injection tests + GECODE_INT_EXPORT static int fault_live_allocations(void); +#endif /// \name Constructors and initialization //@{ /// Initialize as empty set diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index 708e91035c..a4d0d98c8a 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -35,10 +35,39 @@ namespace Gecode { +#ifdef GECODE_HAS_FAULT_INJECTION + int IntSet::IntSetObject::fault_live_objects = 0; + + void* + IntSet::IntSetObject::operator new(size_t s) { + fault_live_objects++; + return ::operator new(s); + } + + void + IntSet::IntSetObject::operator delete(void* p) { + fault_live_objects--; + ::operator delete(p); + } + + void + IntSet::fault_reset_allocations(void) { + IntSetObject::fault_live_objects = 0; + } + + int + IntSet::fault_live_allocations(void) { + return IntSetObject::fault_live_objects; + } +#endif + IntSet::IntSetObject* IntSet::IntSetObject::allocate(int n) { IntSetObject* o = new IntSetObject; o->n = n; +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::IntSet); +#endif o->r = heap.alloc(n); return o; } @@ -188,4 +217,3 @@ namespace Gecode { } // STATISTICS: int-var - diff --git a/test/fault.cpp b/test/fault.cpp index 6407f7ef4d..ac7c017524 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -349,6 +349,33 @@ namespace Test { namespace Fault { } } + bool int_set_failure_releases_object(void) { + int ranges[2][2] = {{0, 0}, {2, 2}}; + IntSet::fault_reset_allocations(); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::IntSet,0); + try { + IntSet s(ranges,2); + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return IntSet::fault_live_allocations() == 0; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + class IntSetAllocation : public Base { + public: + IntSetAllocation(void) + : Base("Fault::IntSet::Allocation") {} + virtual bool run(void) { + return int_set_failure_releases_object(); + } + }; + class DisposeNoticeArray : public Base { public: DisposeNoticeArray(void) @@ -405,6 +432,7 @@ namespace Test { namespace Fault { CloneDisposalArray clone_disposal_array; DisposeNoticeArray dispose_notice_array; + IntSetAllocation int_set_allocation; ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From 000f249318ae413e9eef2878a7c0e6c7986b6eb2 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 20:21:15 +0200 Subject: [PATCH 07/31] Release IntSet object on range allocation failure --- gecode/int/int-set.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index a4d0d98c8a..321efecbb8 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -64,11 +64,19 @@ namespace Gecode { IntSet::IntSetObject* IntSet::IntSetObject::allocate(int n) { IntSetObject* o = new IntSetObject; - o->n = n; + o->size = 0U; + o->n = 0; + o->r = nullptr; + try { #ifdef GECODE_HAS_FAULT_INJECTION - Support::FailPoint::check(Support::FailPoint::Phase::IntSet); + Support::FailPoint::check(Support::FailPoint::Phase::IntSet); #endif - o->r = heap.alloc(n); + o->r = heap.alloc(n); + } catch (...) { + delete o; + throw; + } + o->n = n; return o; } @@ -102,7 +110,8 @@ namespace Gecode { } IntSet::IntSetObject::~IntSetObject(void) { - heap.free(r,n); + if (r != nullptr) + heap.free(r,n); } /// Sort ranges according to increasing minimum From b5c341a6cca036f63f259b8b7708338ebc0d9fcd Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 22:55:22 +0200 Subject: [PATCH 08/31] Add MiniModel default-node allocation fault regression --- gecode/minimodel.hh | 6 ++++++ gecode/minimodel/int-expr.cpp | 30 ++++++++++++++++++++++++++- test/fault.cpp | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 223aa53b9c..9dd9e72f70 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -272,6 +272,12 @@ namespace Gecode { /// Default constructor GECODE_MINIMODEL_EXPORT LinIntExpr(void); +#ifdef GECODE_HAS_FAULT_INJECTION + /// Reset live node accounting for fault-injection tests + GECODE_MINIMODEL_EXPORT static void fault_reset_allocations(void); + /// Return live node accounting for fault-injection tests + GECODE_MINIMODEL_EXPORT static int fault_live_allocations(void); +#endif /// Create expression for constant \a c GECODE_MINIMODEL_EXPORT LinIntExpr(int c); diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index b499d8d3d2..7c41352fe7 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -83,6 +83,10 @@ namespace Gecode { static void* operator new(size_t size); /// Memory management static void operator delete(void* p,size_t size); +#ifdef GECODE_HAS_FAULT_INJECTION + /// Number of live nodes for fault-injection tests + static int fault_live_nodes; +#endif }; /* @@ -93,6 +97,20 @@ namespace Gecode { LinIntExpr::Node::Node(void) : use(1) { } +#ifdef GECODE_HAS_FAULT_INJECTION + int LinIntExpr::Node::fault_live_nodes = 0; + + void + LinIntExpr::fault_reset_allocations(void) { + Node::fault_live_nodes = 0; + } + + int + LinIntExpr::fault_live_allocations(void) { + return Node::fault_live_nodes; + } +#endif + forceinline LinIntExpr::Node::~Node(void) { switch (t) { @@ -113,11 +131,21 @@ namespace Gecode { forceinline void* LinIntExpr::Node::operator new(size_t size) { - return heap.ralloc(size); +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::MiniModel); +#endif + void* p = heap.ralloc(size); +#ifdef GECODE_HAS_FAULT_INJECTION + fault_live_nodes++; +#endif + return p; } forceinline void LinIntExpr::Node::operator delete(void* p, size_t) { +#ifdef GECODE_HAS_FAULT_INJECTION + fault_live_nodes--; +#endif heap.rfree(p); } bool diff --git a/test/fault.cpp b/test/fault.cpp index ac7c017524..1a5eef0cf6 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -169,6 +169,15 @@ namespace Test { namespace Fault { } }; + class MiniModelSpace : public Space { + public: + MiniModelSpace(void) {} + MiniModelSpace(MiniModelSpace& s) : Space(s) {} + virtual Space* copy(void) { + return new MiniModelSpace(*this); + } + }; + class DerivedCopySpace : public Space { public: IntVarArray x; @@ -367,6 +376,35 @@ namespace Test { namespace Fault { } } + bool minimodel_failure_releases_default_nodes(void) { + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,2,0,1); + Support::FailPoint::fail_after(Phase::MiniModel,1); + LinIntExpr e = min(x); + (void) e; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return LinIntExpr::fault_live_allocations() == 0; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + class MiniModelDefaultNodes : public Base { + public: + MiniModelDefaultNodes(void) + : Base("Fault::MiniModel::DefaultNodes") {} + virtual bool run(void) { + return minimodel_failure_releases_default_nodes(); + } + }; + class IntSetAllocation : public Base { public: IntSetAllocation(void) @@ -433,6 +471,7 @@ namespace Test { namespace Fault { CloneDisposalArray clone_disposal_array; DisposeNoticeArray dispose_notice_array; IntSetAllocation int_set_allocation; + MiniModelDefaultNodes minimodel_default_nodes; ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From dba74f0361dad2c71b0dff3440235b9a701f4dce Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 23 May 2026 23:25:19 +0200 Subject: [PATCH 09/31] Release partial MiniModel arithmetic expressions on failure --- gecode/minimodel/int-arith.cpp | 88 ++++++++++++++++++++++++++++------ gecode/minimodel/int-expr.cpp | 20 ++++---- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/gecode/minimodel/int-arith.cpp b/gecode/minimodel/int-arith.cpp index 4251410b91..2b174b619a 100755 --- a/gecode/minimodel/int-arith.cpp +++ b/gecode/minimodel/int-arith.cpp @@ -292,6 +292,19 @@ namespace Gecode { namespace MiniModel { dynamic_cast(e.nle())->t == t; } + class ArithNonLinIntExprGuard { + private: + ArithNonLinIntExpr* e; + public: + ArithNonLinIntExprGuard(ArithNonLinIntExpr* e0) : e(e0) {} + ~ArithNonLinIntExprGuard(void) { + delete e; + } + void release(void) { + e = nullptr; + } + }; + }} namespace Gecode { @@ -303,8 +316,11 @@ namespace Gecode { return e; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ABS,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -321,6 +337,7 @@ namespace Gecode { n += 1; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MIN,n); + ArithNonLinIntExprGuard g(ae); int i=0; if (hasType(e0, ArithNonLinIntExpr::ANLE_MIN)) { ArithNonLinIntExpr* e0e = static_cast(e0.nle()); @@ -337,7 +354,9 @@ namespace Gecode { } else { ae->a[i++] = e1; } - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -354,6 +373,7 @@ namespace Gecode { n += 1; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MAX,n); + ArithNonLinIntExprGuard g(ae); int i=0; if (hasType(e0, ArithNonLinIntExpr::ANLE_MAX)) { ArithNonLinIntExpr* e0e = static_cast(e0.nle()); @@ -370,7 +390,9 @@ namespace Gecode { } else { ae->a[i++] = e1; } - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -378,9 +400,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MIN,x.size()); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -388,9 +413,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MAX,x.size()); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -398,9 +426,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MULT,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -408,8 +439,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQR,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -417,8 +451,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQRT,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -426,8 +463,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_POW,1,n); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -435,8 +475,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_NROOT,1,n); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -444,9 +487,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_DIV,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -454,9 +500,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MOD,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -464,10 +513,13 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ELMNT,x.size()+1); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -475,10 +527,13 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ELMNT,x.size()+1); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } LinIntExpr @@ -486,9 +541,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ITE,2,b); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + LinIntExpr r(ae); + g.release(); + return r; } } diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index 7c41352fe7..471fce0b3c 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -167,7 +167,8 @@ namespace Gecode { LinIntExpr::LinIntExpr(const LinIntExpr& e) : n(e.n) { - n->use++; + if (n != nullptr) + n->use++; } int @@ -385,16 +386,11 @@ namespace Gecode { NonLinIntExpr* LinIntExpr::nle(void) const { - return n->t == NT_NONLIN ? n->sum.ne : nullptr; + return ((n != nullptr) && (n->t == NT_NONLIN)) ? n->sum.ne : nullptr; } LinIntExpr::LinIntExpr(void) : - n(new Node) { - n->n_int = n->n_bool = 0; - n->t = NT_VAR_INT; - n->l = n->r = nullptr; - n->a = 0; - } + n(nullptr) {} LinIntExpr::LinIntExpr(int c) : n(new Node) { @@ -532,15 +528,17 @@ namespace Gecode { const LinIntExpr& LinIntExpr::operator =(const LinIntExpr& e) { if (this != &e) { - if (n->decrement()) + if ((n != nullptr) && n->decrement()) delete n; - n = e.n; n->use++; + n = e.n; + if (n != nullptr) + n->use++; } return *this; } LinIntExpr::~LinIntExpr(void) { - if (n->decrement()) + if ((n != nullptr) && n->decrement()) delete n; } From dbbf4337c9fb3f3ca6a125cee8eff394d6c24cc3 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 07:34:17 +0200 Subject: [PATCH 10/31] Add BoolExpr misc allocation fault regression --- gecode/minimodel/bool-expr.cpp | 3 +++ test/fault.cpp | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/gecode/minimodel/bool-expr.cpp b/gecode/minimodel/bool-expr.cpp index ce8e814c61..06880022b1 100755 --- a/gecode/minimodel/bool-expr.cpp +++ b/gecode/minimodel/bool-expr.cpp @@ -92,6 +92,9 @@ namespace Gecode { void* BoolExpr::Node::operator new(size_t size) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::MiniModel); +#endif return heap.ralloc(size); } void diff --git a/test/fault.cpp b/test/fault.cpp index 1a5eef0cf6..319b5712b4 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -396,6 +396,37 @@ namespace Test { namespace Fault { } } + class FaultBoolMisc : public BoolExpr::Misc { + public: + static int disposed; + + FaultBoolMisc(void) {} + virtual ~FaultBoolMisc(void) { + disposed++; + } + virtual void post(Home, BoolVar, bool, const IntPropLevels&) {} + }; + + int FaultBoolMisc::disposed = 0; + + bool minimodel_failure_releases_bool_misc(void) { + FaultBoolMisc::disposed = 0; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::MiniModel,0); + try { + BoolExpr b(new FaultBoolMisc); + (void) b; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return FaultBoolMisc::disposed == 1; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + class MiniModelDefaultNodes : public Base { public: MiniModelDefaultNodes(void) @@ -405,6 +436,15 @@ namespace Test { namespace Fault { } }; + class MiniModelBoolMisc : public Base { + public: + MiniModelBoolMisc(void) + : Base("Fault::MiniModel::BoolMisc") {} + virtual bool run(void) { + return minimodel_failure_releases_bool_misc(); + } + }; + class IntSetAllocation : public Base { public: IntSetAllocation(void) @@ -472,6 +512,7 @@ namespace Test { namespace Fault { DisposeNoticeArray dispose_notice_array; IntSetAllocation int_set_allocation; MiniModelDefaultNodes minimodel_default_nodes; + MiniModelBoolMisc minimodel_bool_misc; ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From 33c55bb04ce5647920a935a7a9a3930828ed5e76 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 07:35:13 +0200 Subject: [PATCH 11/31] Release BoolExpr misc object on node allocation failure --- gecode/minimodel/bool-expr.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gecode/minimodel/bool-expr.cpp b/gecode/minimodel/bool-expr.cpp index 06880022b1..a9432a72ef 100755 --- a/gecode/minimodel/bool-expr.cpp +++ b/gecode/minimodel/bool-expr.cpp @@ -197,7 +197,13 @@ namespace Gecode { #endif BoolExpr::BoolExpr(BoolExpr::Misc* m) - : n(new Node) { + : n(nullptr) { + try { + n = new Node; + } catch (...) { + delete m; + throw; + } n->same = 1; n->t = NT_MISC; n->l = nullptr; From 70e60a81f11e7506dba95c2553cbc2e70283262d Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 07:45:42 +0200 Subject: [PATCH 12/31] Recover unfinished clone destruction --- gecode/kernel/core.cpp | 100 ++++++++++++++++++++++++----------------- gecode/kernel/core.hpp | 2 + 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 5e296e2c14..d85378fefb 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -205,6 +205,13 @@ namespace Gecode { } Space::~Space(void) { + if (inPrematureDestructionMode()) { + if (pc.c.source != nullptr) { + recover(*pc.c.source); + pc.c.source = nullptr; + } + d_fst = d_cur = d_lst = nullptr; + } // Mark space as failed fail(); // Delete actors that must be deleted @@ -718,53 +725,64 @@ namespace Gecode { #ifdef GECODE_HAS_CBS var_id_counter(s.var_id_counter), #endif - d_fst(&Actor::sentinel) { + d_fst(&Actor::sentinel),d_cur(nullptr),d_lst(nullptr) { #ifdef GECODE_HAS_VAR_DISPOSE for (int i=0; inext(); a != e; a = a->next()) { - Actor* c = Actor::cast(a)->copy(*this); - // Link copied actor - p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); - // Note that forwarding is done in the constructors - p = c; + try { + for (int i=0; inext(); a != e; a = a->next()) { + Actor* c = Actor::cast(a)->copy(*this); + // Link copied actor + p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); + // Note that forwarding is done in the constructors + p = c; + } + // Link last actor + p->next(&pl); pl.prev(p); } - // Link last actor - p->next(&pl); pl.prev(p); - } - // Copy all branchers - { - ActorLink* p = &bl; - ActorLink* e = &s.bl; - for (ActorLink* a = e->next(); a != e; a = a->next()) { - Actor* c = Actor::cast(a)->copy(*this); - // Link copied actor - p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); - // Note that forwarding is done in the constructors - p = c; + // Copy all branchers + { + ActorLink* p = &bl; + ActorLink* e = &s.bl; + for (ActorLink* a = e->next(); a != e; a = a->next()) { + Actor* c = Actor::cast(a)->copy(*this); + // Link copied actor + p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); + // Note that forwarding is done in the constructors + p = c; + } + // Link last actor + p->next(&bl); bl.prev(p); } - // Link last actor - p->next(&bl); bl.prev(p); - } - // Setup brancher pointers - if (s.b_status == &s.bl) { - b_status = Brancher::cast(&bl); - } else { - b_status = Brancher::cast(s.b_status->prev()); - } - if (s.b_commit == &s.bl) { - b_commit = Brancher::cast(&bl); - } else { - b_commit = Brancher::cast(s.b_commit->prev()); + // Setup brancher pointers + if (s.b_status == &s.bl) { + b_status = Brancher::cast(&bl); + } else { + b_status = Brancher::cast(s.b_status->prev()); + } + if (s.b_commit == &s.bl) { + b_commit = Brancher::cast(&bl); + } else { + b_commit = Brancher::cast(s.b_commit->prev()); + } + } catch (...) { + recover(s); + pc.c.source = nullptr; + mm.release(ssd.data().sm); + throw; } } diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index a4dc74d767..ef4950d8d0 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -1873,6 +1873,8 @@ namespace Gecode { VarImpBase* vars_noidx; /// Linked list of local objects LocalObject* local; + /// Source space during clone construction + Space* source; } c; } pc; /// Put propagator \a p into right queue From bbd65f5387961001eca084d153a97f2eda606669 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 07:56:09 +0200 Subject: [PATCH 13/31] Avoid typed casts for marked advisor links --- gecode/kernel/core.hpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index ef4950d8d0..431b4d21c8 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -304,14 +304,14 @@ namespace Gecode { /// Enter propagator to subscription array void enter(Space& home, Propagator* p, PropCond pc); - /// Enter advisor to subscription array - void enter(Space& home, Advisor* a); + /// Enter possibly marked advisor to subscription array + void enter(Space& home, ActorLink* a); /// Resize subscription array void resize(Space& home); /// Remove propagator from subscription array void remove(Space& home, Propagator* p, PropCond pc); - /// Remove advisor from subscription array - void remove(Space& home, Advisor* a); + /// Remove possibly marked advisor from subscription array + void remove(Space& home, ActorLink* a); protected: @@ -1306,6 +1306,8 @@ namespace Gecode { static Advisor* cast(ActorLink* al); /// Static cast static const Advisor* cast(const ActorLink* al); + /// Cast to actor link + static ActorLink* link(Advisor& a); protected: /// Return the advisor's propagator Propagator& propagator(void) const; @@ -3921,6 +3923,11 @@ namespace Gecode { return static_cast(al); } + forceinline ActorLink* + Advisor::link(Advisor& a) { + return static_cast(&a); + } + forceinline Propagator& Advisor::propagator(void) const { assert(!disposed()); @@ -4440,7 +4447,7 @@ namespace Gecode { template forceinline void - VarImp::enter(Space& home, Advisor* a) { + VarImp::enter(Space& home, ActorLink* a) { // Note that a might be a marked pointer // Count one new subscription home.pc.p.n_sub += 1; @@ -4473,7 +4480,8 @@ namespace Gecode { forceinline void VarImp::subscribe(Space& home, Advisor& a, bool assigned, bool fail) { if (!assigned) { - Advisor* ma = static_cast(Support::ptrjoin(&a,fail ? 1 : 0)); + ActorLink* ma = static_cast + (Support::ptrjoin(Advisor::link(a),fail ? 1 : 0)); enter(home,ma); } } @@ -4528,7 +4536,7 @@ namespace Gecode { template void - VarImp::remove(Space& home, Advisor* a) { + VarImp::remove(Space& home, ActorLink* a) { // Note that a might be a marked pointer // Find actor in dependency array ActorLink** f = actorNonZero(pc_max+1); @@ -4553,7 +4561,8 @@ namespace Gecode { forceinline void VarImp::cancel(Space& home, Advisor& a, bool fail) { if ((b.base != nullptr) && !home.inPrematureDestructionMode()) { - Advisor* ma = static_cast(Support::ptrjoin(&a,fail ? 1 : 0)); + ActorLink* ma = static_cast + (Support::ptrjoin(Advisor::link(a),fail ? 1 : 0)); remove(home,ma); } } From bfef1fca8ff0f8de6d9351b9c530e14d3eacb967 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 11:30:25 +0200 Subject: [PATCH 14/31] Add LinIntExpr default API regression --- test/int/mm-lin.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/int/mm-lin.cpp b/test/int/mm-lin.cpp index 22dd0f050d..478d5171c9 100755 --- a/test/int/mm-lin.cpp +++ b/test/int/mm-lin.cpp @@ -90,6 +90,44 @@ namespace Test { namespace Int { * \ingroup TaskTestInt */ //@{ + /// %Test public default linear expression behavior + class LinExprDefaultSpace : public Gecode::Space { + public: + Gecode::IntVar x; + LinExprDefaultSpace(void) : x(*this,-5,5) { + using namespace Gecode; + LinIntExpr e; + LinIntExpr c(e); + LinIntExpr a = e + 1; + LinIntExpr b = 2 + c; + LinIntExpr m = 3 * e; + rel(*this, x == expr(*this, a + b + m)); + } + LinExprDefaultSpace(LinExprDefaultSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + } + virtual Gecode::Space* copy(void) { + return new LinExprDefaultSpace(*this); + } + }; + + /// %Test public default linear expression behavior + class LinExprDefault : public Test::Base { + public: + LinExprDefault(void) : Test::Base("MiniModel::LinExpr::Default") {} + virtual bool run(void) { + LinExprDefaultSpace* s = new LinExprDefaultSpace; + Gecode::DFS e(s); + delete s; + LinExprDefaultSpace* sol = e.next(); + if (sol == nullptr) + return false; + bool ok = sol->x.assigned() && (sol->x.val() == 3); + delete sol; + return ok && (e.next() == nullptr); + } + }; + /// %Test linear expressions over integer variables class LinExprInt : public Test { protected: @@ -2172,6 +2210,7 @@ namespace Test { namespace Int { } }; + LinExprDefault lin_expr_default; Create c; //@} } From a97a34d4b454fd625c667a11694266b04afb6c34 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 11:48:27 +0200 Subject: [PATCH 15/31] Restore default LinIntExpr node invariant --- gecode/kernel/core.hpp | 12 ++++++++---- gecode/minimodel.hh | 9 +++++++++ gecode/minimodel/int-arith.cpp | 14 +++++++++++--- gecode/minimodel/int-expr.cpp | 11 ++++++++++- test/int/mm-lin.cpp | 6 +++--- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 431b4d21c8..b6ec4be089 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -4377,10 +4377,14 @@ namespace Gecode { template forceinline void VarImp::schedule(Space& home, PropCond pc1, PropCond pc2, ModEvent me) { - ActorLink** b = actor(pc1); - ActorLink** p = actorNonZero(pc2+1); - while (p-- > b) - schedule(home,*Propagator::cast(*p),me); + if (b.base == nullptr) + return; + ActorLink** begin = actor(pc1); + ActorLink** end = actorNonZero(pc2+1); + while (end > begin) { + end--; + schedule(home,*Propagator::cast(*end),me); + } } template diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 9dd9e72f70..468ccd2c9d 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -241,9 +241,14 @@ namespace Gecode { namespace Gecode { + namespace MiniModel { + class ArithNonLinIntExpr; + } + /// Linear expressions over integer variables class LinIntExpr { friend class LinIntRel; + friend class MiniModel::ArithNonLinIntExpr; #ifdef GECODE_HAS_SET_VARS friend class SetExpr; #endif @@ -264,10 +269,14 @@ namespace Gecode { NT_MUL ///< Multiplication by coefficient }; private: + /// Tag for internal expression slots without a public zero node + struct NoNode {}; /// Nodes for linear expressions class Node; /// The actual node Node* n; + /// Create internal expression slot without a node + LinIntExpr(NoNode); public: /// Default constructor GECODE_MINIMODEL_EXPORT diff --git a/gecode/minimodel/int-arith.cpp b/gecode/minimodel/int-arith.cpp index 2b174b619a..08fa399f0f 100755 --- a/gecode/minimodel/int-arith.cpp +++ b/gecode/minimodel/int-arith.cpp @@ -61,15 +61,23 @@ namespace Gecode { namespace MiniModel { int aInt; /// Boolean expression argument (used in ite for example) BoolExpr b; + /// Allocate internal expression slots without public default nodes + static LinIntExpr* allocate(int n) { + LinIntExpr* a = static_cast + (heap.ralloc(sizeof(LinIntExpr)*n)); + for (int i=0; i(n0)), n(n0) {} + : t(t0), a(allocate(n0)), n(n0) {} /// Constructor ArithNonLinIntExpr(ArithNonLinIntExprType t0, int n0, int a0) - : t(t0), a(heap.alloc(n0)), n(n0), aInt(a0) {} + : t(t0), a(allocate(n0)), n(n0), aInt(a0) {} /// Constructor ArithNonLinIntExpr(ArithNonLinIntExprType t0, int n0, const BoolExpr& b0) - : t(t0), a(heap.alloc(n0)), n(n0), b(b0) {} + : t(t0), a(allocate(n0)), n(n0), b(b0) {} /// Destructor ~ArithNonLinIntExpr(void) { heap.free(a,n); diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index 471fce0b3c..8006365532 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -389,9 +389,18 @@ namespace Gecode { return ((n != nullptr) && (n->t == NT_NONLIN)) ? n->sum.ne : nullptr; } - LinIntExpr::LinIntExpr(void) : + LinIntExpr::LinIntExpr(NoNode) : n(nullptr) {} + LinIntExpr::LinIntExpr(void) : + n(new Node) { + n->n_int = n->n_bool = 0; + n->t = NT_CONST; + n->l = n->r = nullptr; + n->a = 0; + n->c = 0; + } + LinIntExpr::LinIntExpr(int c) : n(new Node) { n->n_int = n->n_bool = 0; diff --git a/test/int/mm-lin.cpp b/test/int/mm-lin.cpp index 478d5171c9..e5d7544b0a 100755 --- a/test/int/mm-lin.cpp +++ b/test/int/mm-lin.cpp @@ -98,10 +98,10 @@ namespace Test { namespace Int { using namespace Gecode; LinIntExpr e; LinIntExpr c(e); - LinIntExpr a = e + 1; + LinIntExpr a = e + x; LinIntExpr b = 2 + c; LinIntExpr m = 3 * e; - rel(*this, x == expr(*this, a + b + m)); + rel(*this, a + b + m == 3); } LinExprDefaultSpace(LinExprDefaultSpace& s) : Gecode::Space(s) { x.update(*this,s.x); @@ -122,7 +122,7 @@ namespace Test { namespace Int { LinExprDefaultSpace* sol = e.next(); if (sol == nullptr) return false; - bool ok = sol->x.assigned() && (sol->x.val() == 3); + bool ok = sol->x.assigned() && (sol->x.val() == 1); delete sol; return ok && (e.next() == nullptr); } From 3507e843e893995ee9b54d949f6f9bfd40f1617d Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 11:54:22 +0200 Subject: [PATCH 16/31] Recover clone disposal failures for any exception --- gecode/kernel/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index d85378fefb..32a3c01a00 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -822,7 +822,7 @@ namespace Gecode { (Support::ptrjoin(a->prev(),m))); } } - catch (const std::exception&) { + catch (...) { c->recover(*this); c->d_fst = c->d_cur = c->d_lst = nullptr; delete c; From 3944e195201a872bf796c7e636ea9c3f7983a637 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 21:35:04 +0200 Subject: [PATCH 17/31] Add AP_DISPOSE registration failure regression --- test/fault.cpp | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/test/fault.cpp b/test/fault.cpp index 319b5712b4..251abe98c2 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -150,7 +150,8 @@ namespace Test { namespace Fault { virtual Actor* copy(Space& home) { return new (home) NoticeDisposeActor(home); } - virtual size_t dispose(Space&) { + virtual size_t dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); disposed++; return sizeof(*this); } @@ -160,8 +161,15 @@ namespace Test { namespace Fault { class NoticeDisposeSpace : public Space { public: - NoticeDisposeSpace(void) { - NoticeDisposeActor::post(*this); + NoticeDisposeSpace(int registered = 0, bool fail_next = false) { + for (int i=0; i Date: Sun, 24 May 2026 21:36:05 +0200 Subject: [PATCH 18/31] Avoid disposing unregistered AP_DISPOSE actors --- gecode/kernel/core.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 32a3c01a00..f22b16cdec 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -154,8 +154,6 @@ namespace Gecode { #endif d_fst = alloc(4); } catch (...) { - if ((a != nullptr) && !Support::marked(a)) - (void) a->dispose(*this); throw; } d_cur = d_fst; @@ -170,8 +168,6 @@ namespace Gecode { #endif d_fst = realloc(d_fst,n,2*n); } catch (...) { - if ((a != nullptr) && !Support::marked(a)) - (void) a->dispose(*this); throw; } d_cur = d_fst+n; From 70f09566d5d56680a7eef1d7500e5e2974a4f824 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 21:37:53 +0200 Subject: [PATCH 19/31] Serialize fault injection tests --- test/fault.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/fault.cpp b/test/fault.cpp index 251abe98c2..d2dca14bd5 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -41,6 +41,20 @@ namespace Test { namespace Fault { using namespace Gecode; using Gecode::Support::FailPoint::Phase; + Support::Mutex fault_mutex; + + class FaultScope { + private: + Support::Lock lock; + public: + FaultScope(void) : lock(fault_mutex) { + Support::FailPoint::reset(); + } + ~FaultScope(void) { + Support::FailPoint::reset(); + } + }; + class ThrowingPropagator : public Propagator { protected: ThrowingPropagator(Home home) : Propagator(home) {} @@ -244,6 +258,7 @@ namespace Test { namespace Fault { }; bool clone_after_failed_copy(Phase p) { + FaultScope scope; CloneCopySpace s; if (s.status() == SS_FAILED) return false; @@ -272,6 +287,7 @@ namespace Test { namespace Fault { } bool expect_memory_exhausted(Phase p, unsigned long long n) { + FaultScope scope; DerivedCopySpace s; if (s.status() == SS_FAILED) return false; @@ -300,6 +316,7 @@ namespace Test { namespace Fault { } bool clone_after_failed_local_object_copy(void) { + FaultScope scope; LocalCopySpace s; Support::FailPoint::reset(); Support::FailPoint::fail_after(Phase::LocalObjectCopy,0); @@ -323,6 +340,7 @@ namespace Test { namespace Fault { } bool clone_after_failed_disposal_array(void) { + FaultScope scope; DisposeSpace s; if (s.status() == SS_FAILED) return false; @@ -350,6 +368,7 @@ namespace Test { namespace Fault { } bool dispose_notice_initial_failure_does_not_dispose_actor(void) { + FaultScope scope; NoticeDisposeActor::disposed = 0; Support::FailPoint::reset(); Support::FailPoint::fail_after(Phase::SpaceDisposeNoticeArray,0); @@ -367,6 +386,7 @@ namespace Test { namespace Fault { } bool dispose_notice_resize_failure_does_not_dispose_unregistered_actor(void) { + FaultScope scope; NoticeDisposeActor::disposed = 0; Support::FailPoint::reset(); try { @@ -383,6 +403,7 @@ namespace Test { namespace Fault { } bool int_set_failure_releases_object(void) { + FaultScope scope; int ranges[2][2] = {{0, 0}, {2, 2}}; IntSet::fault_reset_allocations(); Support::FailPoint::reset(); @@ -401,6 +422,7 @@ namespace Test { namespace Fault { } bool minimodel_failure_releases_default_nodes(void) { + FaultScope scope; LinIntExpr::fault_reset_allocations(); Support::FailPoint::reset(); try { @@ -434,6 +456,7 @@ namespace Test { namespace Fault { int FaultBoolMisc::disposed = 0; bool minimodel_failure_releases_bool_misc(void) { + FaultScope scope; FaultBoolMisc::disposed = 0; Support::FailPoint::reset(); Support::FailPoint::fail_after(Phase::MiniModel,0); From 46e3647ad13e88e27bbf607639f86e40a771c202 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 21:40:02 +0200 Subject: [PATCH 20/31] Keep clone recovery non-virtual --- gecode/kernel/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index b6ec4be089..b0800c2e7f 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -1903,7 +1903,7 @@ namespace Gecode { void updateNoIdx(Space* space, bool recover); /// Recover after failed clone construction - virtual void recover(Space& source); + void recover(Space& source); /// Test whether clone construction has not reached a disposable state bool inPrematureDestructionMode(void) const; From c1a30de908826b60f267a478be32d1f8bbd29331 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 24 May 2026 21:45:24 +0200 Subject: [PATCH 21/31] Wire heap fault phase coverage --- gecode/support/heap.hpp | 6 +++ test/fault.cpp | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/gecode/support/heap.hpp b/gecode/support/heap.hpp index b8a33c4b6a..c26205c0ce 100644 --- a/gecode/support/heap.hpp +++ b/gecode/support/heap.hpp @@ -355,6 +355,9 @@ namespace Gecode { */ forceinline void* Heap::ralloc(size_t s) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::Heap); +#endif void* p = Support::allocator.alloc(s); #ifdef GECODE_PEAKHEAP _m.acquire(); @@ -389,6 +392,9 @@ namespace Gecode { forceinline void* Heap::rrealloc(void* p, size_t s) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::Heap); +#endif #ifdef GECODE_PEAKHEAP _m.acquire(); _cur -= GECODE_MSIZE(p); diff --git a/test/fault.cpp b/test/fault.cpp index d2dca14bd5..1b4c3d2fc7 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -216,6 +216,22 @@ namespace Test { namespace Fault { } }; + class DerivedUpdateSpace : public Space { + public: + IntVarArray x; + DerivedUpdateSpace(void) : x(*this,2,0,1) { + rel(*this,x[0] != x[1]); + branch(*this,x,INT_VAR_SIZE_MIN(),INT_VAL_MIN()); + } + DerivedUpdateSpace(DerivedUpdateSpace& s) : Space(s) { + x.update(*this,s.x); + Support::FailPoint::check(Phase::DerivedSpaceCopy); + } + virtual Space* copy(void) { + return new DerivedUpdateSpace(*this); + } + }; + class FaultLocalObject : public LocalObject { public: int value; @@ -367,6 +383,52 @@ namespace Test { namespace Fault { return ok; } + bool clone_after_failed_derived_update(void) { + FaultScope scope; + DerivedUpdateSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::DerivedSpaceCopy,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Space* c = s.clone(); + delete c; + DerivedUpdateSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return ok; + } + + bool heap_allocation_failpoint_throws(void) { + FaultScope scope; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + int* x = heap.alloc(1); + heap.free(x,1); + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + bool dispose_notice_initial_failure_does_not_dispose_actor(void) { FaultScope scope; NoticeDisposeActor::disposed = 0; @@ -528,6 +590,24 @@ namespace Test { namespace Fault { } }; + class CloneDerivedSpaceUpdate : public Base { + public: + CloneDerivedSpaceUpdate(void) + : Base("Fault::Clone::DerivedSpaceUpdate") {} + virtual bool run(void) { + return clone_after_failed_derived_update(); + } + }; + + class HeapAllocation : public Base { + public: + HeapAllocation(void) + : Base("Fault::Heap::Allocation") {} + virtual bool run(void) { + return heap_allocation_failpoint_throws(); + } + }; + class ClonePropagatorCopy : public Base { public: ClonePropagatorCopy(void) @@ -565,8 +645,10 @@ namespace Test { namespace Fault { }; CloneDisposalArray clone_disposal_array; + CloneDerivedSpaceUpdate clone_derived_space_update; DisposeNoticeArray dispose_notice_array; DisposeNoticeArrayResize dispose_notice_array_resize; + HeapAllocation heap_allocation; IntSetAllocation int_set_allocation; MiniModelDefaultNodes minimodel_default_nodes; MiniModelBoolMisc minimodel_bool_misc; From 63ad86ea9248e04cc9252638f580805602d0e8e1 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 25 May 2026 06:16:49 +0200 Subject: [PATCH 22/31] Expand heap fault coverage --- test/fault.cpp | 340 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 332 insertions(+), 8 deletions(-) diff --git a/test/fault.cpp b/test/fault.cpp index 1b4c3d2fc7..aa7c00a8b6 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -273,6 +273,29 @@ namespace Test { namespace Fault { } }; + class BranchPostSpace : public Space { + public: + IntVarArray x; + BranchPostSpace(void) : x(*this,4,0,3) { + rel(*this,x[0] != x[1]); + } + BranchPostSpace(BranchPostSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new BranchPostSpace(*this); + } + void post_action_branch(void) { + branch(*this,x,INT_VAR_ACTION_SIZE_MAX(),INT_VAL_MIN()); + } + void post_chb_branch(void) { + branch(*this,x,INT_VAR_CHB_SIZE_MAX(),INT_VAL_MIN()); + } + void post_plain_branch(void) { + branch(*this,x,INT_VAR_SIZE_MIN(),INT_VAL_MIN()); + } + }; + bool clone_after_failed_copy(Phase p) { FaultScope scope; CloneCopySpace s; @@ -383,6 +406,17 @@ namespace Test { namespace Fault { return ok; } + bool derived_update_space_is_usable(DerivedUpdateSpace& s) { + Space* c = s.clone(); + delete c; + DerivedUpdateSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return ok; + } + bool clone_after_failed_derived_update(void) { FaultScope scope; DerivedUpdateSpace s; @@ -401,14 +435,7 @@ namespace Test { namespace Fault { Support::FailPoint::reset(); return false; } - Space* c = s.clone(); - delete c; - DerivedUpdateSpace* root = static_cast(s.clone()); - DFS e(root); - Space* sol = e.next(); - bool ok = (sol != nullptr); - delete sol; - return ok; + return derived_update_space_is_usable(s); } bool heap_allocation_failpoint_throws(void) { @@ -429,6 +456,215 @@ namespace Test { namespace Fault { } } + bool heap_reallocation_failpoint_throws(void) { + FaultScope scope; + int* x = heap.alloc(1); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + int* y = heap.realloc(x,1,2); + Support::FailPoint::reset(); + heap.free(y,2); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + heap.free(x,1); + return true; + } catch (...) { + Support::FailPoint::reset(); + heap.free(x,1); + return false; + } + } + + bool clone_heap_failures_recover_source(void) { + FaultScope scope; + DerivedUpdateSpace s; + if (s.status() == SS_FAILED) + return false; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + Space* c = s.clone(); + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + delete c; + return saw_failure && (checks > 0) && derived_update_space_is_usable(s); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (!derived_update_space_is_usable(s)) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool branch_post_heap_failures_are_recoverable(bool chb) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + BranchPostSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + if (chb) + s.post_chb_branch(); + else + s.post_action_branch(); + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + BranchPostSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return saw_failure && (checks > 0) && ok; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + s.post_plain_branch(); + BranchPostSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + if (!ok) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool int_set_heap_failures_release_object(void) { + FaultScope scope; + int ranges[3][2] = {{0, 0}, {2, 2}, {4, 4}}; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 8; budget++) { + IntSet::fault_reset_allocations(); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + unsigned long long checks; + { + IntSet s(ranges,3); + checks = Support::FailPoint::count(); + } + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (IntSet::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (IntSet::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool minimodel_heap_failures_release_nodes(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,4,0,3); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + LinIntExpr e = min(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (LinIntExpr::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (LinIntExpr::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + +#ifdef GECODE_HAS_FLOAT_VARS + bool minimodel_float_heap_failures_are_recoverable(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + try { + MiniModelSpace home; + FloatVarArgs x(home,4,0.0,3.0); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + LinFloatExpr e = min(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } +#endif + +#ifdef GECODE_HAS_SET_VARS + bool minimodel_set_heap_failures_are_recoverable(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + try { + MiniModelSpace home; + SetVarArgs x(home,4,IntSet::empty,1,1); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + SetExpr e = setunion(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } +#endif + bool dispose_notice_initial_failure_does_not_dispose_actor(void) { FaultScope scope; NoticeDisposeActor::disposed = 0; @@ -608,6 +844,82 @@ namespace Test { namespace Fault { } }; + class HeapReallocation : public Base { + public: + HeapReallocation(void) + : Base("Fault::Heap::Reallocation") {} + virtual bool run(void) { + return heap_reallocation_failpoint_throws(); + } + }; + + class CloneHeapFailures : public Base { + public: + CloneHeapFailures(void) + : Base("Fault::Clone::HeapFailures") {} + virtual bool run(void) { + return clone_heap_failures_recover_source(); + } + }; + + class BranchActionHeapFailures : public Base { + public: + BranchActionHeapFailures(void) + : Base("Fault::Branch::ActionHeapFailures") {} + virtual bool run(void) { + return branch_post_heap_failures_are_recoverable(false); + } + }; + + class BranchChbHeapFailures : public Base { + public: + BranchChbHeapFailures(void) + : Base("Fault::Branch::ChbHeapFailures") {} + virtual bool run(void) { + return branch_post_heap_failures_are_recoverable(true); + } + }; + + class IntSetHeapFailures : public Base { + public: + IntSetHeapFailures(void) + : Base("Fault::IntSet::HeapFailures") {} + virtual bool run(void) { + return int_set_heap_failures_release_object(); + } + }; + + class MiniModelHeapFailures : public Base { + public: + MiniModelHeapFailures(void) + : Base("Fault::MiniModel::HeapFailures") {} + virtual bool run(void) { + return minimodel_heap_failures_release_nodes(); + } + }; + +#ifdef GECODE_HAS_FLOAT_VARS + class MiniModelFloatHeapFailures : public Base { + public: + MiniModelFloatHeapFailures(void) + : Base("Fault::MiniModel::FloatHeapFailures") {} + virtual bool run(void) { + return minimodel_float_heap_failures_are_recoverable(); + } + }; +#endif + +#ifdef GECODE_HAS_SET_VARS + class MiniModelSetHeapFailures : public Base { + public: + MiniModelSetHeapFailures(void) + : Base("Fault::MiniModel::SetHeapFailures") {} + virtual bool run(void) { + return minimodel_set_heap_failures_are_recoverable(); + } + }; +#endif + class ClonePropagatorCopy : public Base { public: ClonePropagatorCopy(void) @@ -644,14 +956,26 @@ namespace Test { namespace Fault { } }; + BranchActionHeapFailures branch_action_heap_failures; + BranchChbHeapFailures branch_chb_heap_failures; CloneDisposalArray clone_disposal_array; CloneDerivedSpaceUpdate clone_derived_space_update; + CloneHeapFailures clone_heap_failures; DisposeNoticeArray dispose_notice_array; DisposeNoticeArrayResize dispose_notice_array_resize; HeapAllocation heap_allocation; + HeapReallocation heap_reallocation; IntSetAllocation int_set_allocation; + IntSetHeapFailures int_set_heap_failures; MiniModelDefaultNodes minimodel_default_nodes; MiniModelBoolMisc minimodel_bool_misc; + MiniModelHeapFailures minimodel_heap_failures; +#ifdef GECODE_HAS_FLOAT_VARS + MiniModelFloatHeapFailures minimodel_float_heap_failures; +#endif +#ifdef GECODE_HAS_SET_VARS + MiniModelSetHeapFailures minimodel_set_heap_failures; +#endif ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; CloneDerivedSpaceCopy clone_derived_space_copy; From 9624ab10fb770b3edd1ad3838cb5ace185644aac Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 25 May 2026 06:20:30 +0200 Subject: [PATCH 23/31] Cover BoolExpr heap failure ownership --- test/fault.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/fault.cpp b/test/fault.cpp index aa7c00a8b6..6e7c65ddc7 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -772,6 +772,26 @@ namespace Test { namespace Fault { } } + bool minimodel_heap_failure_releases_bool_misc(void) { + FaultScope scope; + FaultBoolMisc::disposed = 0; + FaultBoolMisc* m = new FaultBoolMisc; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + BoolExpr b(m); + (void) b; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return FaultBoolMisc::disposed == 1; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + class MiniModelDefaultNodes : public Base { public: MiniModelDefaultNodes(void) @@ -898,6 +918,15 @@ namespace Test { namespace Fault { } }; + class MiniModelBoolMiscHeapFailure : public Base { + public: + MiniModelBoolMiscHeapFailure(void) + : Base("Fault::MiniModel::BoolMiscHeapFailure") {} + virtual bool run(void) { + return minimodel_heap_failure_releases_bool_misc(); + } + }; + #ifdef GECODE_HAS_FLOAT_VARS class MiniModelFloatHeapFailures : public Base { public: @@ -969,6 +998,7 @@ namespace Test { namespace Fault { IntSetHeapFailures int_set_heap_failures; MiniModelDefaultNodes minimodel_default_nodes; MiniModelBoolMisc minimodel_bool_misc; + MiniModelBoolMiscHeapFailure minimodel_bool_misc_heap_failure; MiniModelHeapFailures minimodel_heap_failures; #ifdef GECODE_HAS_FLOAT_VARS MiniModelFloatHeapFailures minimodel_float_heap_failures; From c7086f288bcfc838818d1fedc30701e9d1dd1b19 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 28 Jun 2026 10:52:08 +0200 Subject: [PATCH 24/31] Fix clone and MiniModel fault-injection gaps Close follow-up gaps found while validating the local clone, IntSet, and MiniModel exception-safety invariants. In particular, recover advisor-copy state on failure, keep IntSet fault accounting consistent when allocation throws, and make MiniModel ownership cleanup robust under injected heap failures. Reference: Gecode/gecode#211 --- gecode/int/int-set.cpp | 3 +- gecode/kernel/core.hpp | 45 +++++--- gecode/minimodel/int-arith.cpp | 49 +++++---- gecode/minimodel/int-expr.cpp | 86 ++++++++++----- gecode/support/failpoint.hpp | 1 + test/fault.cpp | 187 +++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 68 deletions(-) diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index 321efecbb8..b2e5b17a33 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -40,8 +40,9 @@ namespace Gecode { void* IntSet::IntSetObject::operator new(size_t s) { + void* p = ::operator new(s); fault_live_objects++; - return ::operator new(s); + return p; } void diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index b0800c2e7f..1736bcedd4 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -4016,26 +4016,37 @@ namespace Gecode { ActorLink** a_f = &c.advisors; // Advisors in to-space A* a_t = nullptr; - while (*a_f != nullptr) { - if (static_cast(*a_f)->disposed()) { - *a_f = (*a_f)->next(); - } else { - // Run specific copying part - A* a = new (home) A(home,*static_cast(*a_f)); - // Set propagator pointer - a->prev(p_t); - // Set forwarding pointer - (*a_f)->prev(a); - // Link - a->next(a_t); - a_t = a; - a_f = (*a_f)->next_ref(); + // Enter advisor link early so failed clone recovery can restore links. + assert(p_f->u.advisors == nullptr); + p_f->u.advisors = c.advisors; + try { + while (*a_f != nullptr) { + if (static_cast(*a_f)->disposed()) { + *a_f = (*a_f)->next(); + } else { + // Run specific copying part + A* a = new (home) A(home,*static_cast(*a_f)); + // Set propagator pointer + a->prev(p_t); + // Set forwarding pointer + (*a_f)->prev(a); + // Link + a->next(a_t); + a_t = a; + advisors = a_t; + a_f = (*a_f)->next_ref(); + } } + } catch (...) { + dispose(home); + advisors = nullptr; + for (ActorLink* a = c.advisors; a != nullptr; a = a->next()) + if (!static_cast(a)->disposed()) + a->prev(p_f); + p_f->u.advisors = nullptr; + throw; } advisors = a_t; - // Enter advisor link for reset - assert(p_f->u.advisors == nullptr); - p_f->u.advisors = c.advisors; } else { advisors = nullptr; } diff --git a/gecode/minimodel/int-arith.cpp b/gecode/minimodel/int-arith.cpp index 08fa399f0f..507fd51b28 100755 --- a/gecode/minimodel/int-arith.cpp +++ b/gecode/minimodel/int-arith.cpp @@ -53,16 +53,18 @@ namespace Gecode { namespace MiniModel { ANLE_ELMNT, ///< Element expression ANLE_ITE ///< If-then-else expression } t; - /// Expressions - LinIntExpr* a; + /// Boolean expression argument (used in ite for example) + BoolExpr b; /// Size of variable array int n; /// Integer argument (used in nroot for example) int aInt; - /// Boolean expression argument (used in ite for example) - BoolExpr b; + /// Expressions + LinIntExpr* a; /// Allocate internal expression slots without public default nodes static LinIntExpr* allocate(int n) { + if (n == 0) + return nullptr; LinIntExpr* a = static_cast (heap.ralloc(sizeof(LinIntExpr)*n)); for (int i=0; i(a,n); + if (a != nullptr) + heap.free(a,n); } /// Post expression virtual IntVar post(Home home, IntVar* ret, @@ -326,8 +329,8 @@ namespace Gecode { new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ABS,1); ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -362,8 +365,8 @@ namespace Gecode { } else { ae->a[i++] = e1; } - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -398,8 +401,8 @@ namespace Gecode { } else { ae->a[i++] = e1; } - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -411,8 +414,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -424,8 +427,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -437,8 +440,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -449,8 +452,8 @@ namespace Gecode { new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQR,1); ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -461,8 +464,8 @@ namespace Gecode { new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQRT,1); ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -473,8 +476,8 @@ namespace Gecode { new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_POW,1,n); ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -485,8 +488,8 @@ namespace Gecode { new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_NROOT,1,n); ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -498,8 +501,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -511,8 +514,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -525,8 +528,8 @@ namespace Gecode { for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -539,8 +542,8 @@ namespace Gecode { for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } @@ -552,8 +555,8 @@ namespace Gecode { ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - LinIntExpr r(ae); g.release(); + LinIntExpr r(ae); return r; } diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index 8006365532..c91fb36444 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -433,65 +433,91 @@ namespace Gecode { LinIntExpr::LinIntExpr(const IntVarArgs& x) : n(new Node) { - n->n_int = x.size(); + n->n_int = 0; n->n_bool = 0; n->t = NT_SUM_INT; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.ti = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.ti[i].x = x[i]; - n->sum.ti[i].a = 1; + try { + if (x.size() > 0) { + n->sum.ti = heap.alloc >(x.size()); + n->n_int = x.size(); + for (int i=x.size(); i--; ) { + n->sum.ti[i].x = x[i]; + n->sum.ti[i].a = 1; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const IntArgs& a, const IntVarArgs& x) : - n(new Node) { + n(nullptr) { if (a.size() != x.size()) throw Int::ArgumentSizeMismatch("MiniModel::LinIntExpr"); - n->n_int = x.size(); + n = new Node; + n->n_int = 0; n->n_bool = 0; n->t = NT_SUM_INT; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.ti = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.ti[i].x = x[i]; - n->sum.ti[i].a = a[i]; + try { + if (x.size() > 0) { + n->sum.ti = heap.alloc >(x.size()); + n->n_int = x.size(); + for (int i=x.size(); i--; ) { + n->sum.ti[i].x = x[i]; + n->sum.ti[i].a = a[i]; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const BoolVarArgs& x) : n(new Node) { n->n_int = 0; - n->n_bool = x.size(); + n->n_bool = 0; n->t = NT_SUM_BOOL; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.tb = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.tb[i].x = x[i]; - n->sum.tb[i].a = 1; + try { + if (x.size() > 0) { + n->sum.tb = heap.alloc >(x.size()); + n->n_bool = x.size(); + for (int i=x.size(); i--; ) { + n->sum.tb[i].x = x[i]; + n->sum.tb[i].a = 1; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const IntArgs& a, const BoolVarArgs& x) : - n(new Node) { + n(nullptr) { if (a.size() != x.size()) throw Int::ArgumentSizeMismatch("MiniModel::LinIntExpr"); + n = new Node; n->n_int = 0; - n->n_bool = x.size(); + n->n_bool = 0; n->t = NT_SUM_BOOL; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.tb = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.tb[i].x = x[i]; - n->sum.tb[i].a = a[i]; + try { + if (x.size() > 0) { + n->sum.tb = heap.alloc >(x.size()); + n->n_bool = x.size(); + for (int i=x.size(); i--; ) { + n->sum.tb[i].x = x[i]; + n->sum.tb[i].a = a[i]; + } } + } catch (...) { + delete n; + throw; } } @@ -525,7 +551,13 @@ namespace Gecode { } LinIntExpr::LinIntExpr(NonLinIntExpr* e) : - n(new Node) { + n(nullptr) { + try { + n = new Node; + } catch (...) { + delete e; + throw; + } n->n_int = 1; n->n_bool = 0; n->t = NT_NONLIN; diff --git a/gecode/support/failpoint.hpp b/gecode/support/failpoint.hpp index ffa32dcee5..28cf888510 100644 --- a/gecode/support/failpoint.hpp +++ b/gecode/support/failpoint.hpp @@ -44,6 +44,7 @@ namespace Gecode { namespace Support { namespace FailPoint { Heap, PropagatorCopy, BrancherCopy, + AdvisorCopy, DerivedSpaceCopy, LocalObjectCopy, SpaceDisposeNoticeArray, diff --git a/test/fault.cpp b/test/fault.cpp index 6e7c65ddc7..e88058a524 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -82,6 +82,69 @@ namespace Test { namespace Fault { } }; + class AdvisorCopyPropagator; + + class ThrowingAdvisor : public Advisor { + protected: + int id; + public: + ThrowingAdvisor(Space& home, Propagator& p, + Council& c, int id0) + : Advisor(home,p,c), id(id0) {} + ThrowingAdvisor(Space& home, ThrowingAdvisor& a) + : Advisor(home,a), id(a.id) { + Support::FailPoint::check(Phase::AdvisorCopy); + } + Propagator* owner(void) const { + return &propagator(); + } + void dispose(Space& home, Council& c) { + Advisor::dispose(home,c); + } + }; + + class AdvisorCopyPropagator : public Propagator { + protected: + Council c; + AdvisorCopyPropagator(Home home) : Propagator(home), c(home) { + (void) new (home) ThrowingAdvisor(home,*this,c,0); + (void) new (home) ThrowingAdvisor(home,*this,c,1); + } + AdvisorCopyPropagator(Space& home, AdvisorCopyPropagator& p) + : Propagator(home,p) { + c.update(home,p.c); + } + public: + static AdvisorCopyPropagator* post(Home home) { + return new (home) AdvisorCopyPropagator(home); + } + bool advisors_point_to_self(void) const { + Advisors as(c); + int n = 0; + while (as()) { + if (as.advisor().owner() != this) + return false; + ++n; ++as; + } + return n == 2; + } + virtual Actor* copy(Space& home) { + return new (home) AdvisorCopyPropagator(home,*this); + } + virtual PropCost cost(const Space&, const ModEventDelta&) const { + return PropCost::unary(PropCost::LO); + } + virtual void reschedule(Space&) {} + virtual ExecStatus propagate(Space&, const ModEventDelta&) { + return ES_FIX; + } + virtual size_t dispose(Space& home) { + c.dispose(home); + (void) Propagator::dispose(home); + return sizeof(*this); + } + }; + class ThrowingChoice : public Choice { public: ThrowingChoice(const Brancher& b) : Choice(b,1) {} @@ -273,6 +336,20 @@ namespace Test { namespace Fault { } }; + class AdvisorCopySpace : public Space { + public: + AdvisorCopyPropagator* p; + AdvisorCopySpace(void) + : p(AdvisorCopyPropagator::post(*this)) {} + AdvisorCopySpace(AdvisorCopySpace& s) : Space(s), p(nullptr) {} + virtual Space* copy(void) { + return new AdvisorCopySpace(*this); + } + bool advisors_point_to_source(void) const { + return (p != nullptr) && p->advisors_point_to_self(); + } + }; + class BranchPostSpace : public Space { public: IntVarArray x; @@ -378,6 +455,32 @@ namespace Test { namespace Fault { return false; } + bool clone_after_failed_advisor_copy(void) { + FaultScope scope; + AdvisorCopySpace s; + if (!s.advisors_point_to_source()) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::AdvisorCopy,1); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + if (!s.advisors_point_to_source()) + return false; + AdvisorCopySpace* c = static_cast(s.clone()); + bool ok = (c != nullptr); + delete c; + return ok && s.advisors_point_to_source(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + bool clone_after_failed_disposal_array(void) { FaultScope scope; DisposeSpace s; @@ -607,6 +710,70 @@ namespace Test { namespace Fault { return false; } + bool linear_sum_constructor_heap_failures_release_nodes(int kind) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 8; budget++) { + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,4,0,3); + BoolVarArgs b(home,4,0,1); + IntArgs a({1,2,3,4}); + Support::FailPoint::fail_after(Phase::Heap,budget); + switch (kind) { + case 0: + { + LinIntExpr e(x); + (void) e; + } + break; + case 1: + { + LinIntExpr e(a,x); + (void) e; + } + break; + case 2: + { + LinIntExpr e(b); + (void) e; + } + break; + case 3: + { + LinIntExpr e(a,b); + (void) e; + } + break; + default: + return false; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (LinIntExpr::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (LinIntExpr::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool minimodel_linear_sum_heap_failures_release_nodes(void) { + for (int kind = 0; kind < 4; kind++) + if (!linear_sum_constructor_heap_failures_release_nodes(kind)) + return false; + return true; + } + #ifdef GECODE_HAS_FLOAT_VARS bool minimodel_float_heap_failures_are_recoverable(void) { FaultScope scope; @@ -918,6 +1085,15 @@ namespace Test { namespace Fault { } }; + class MiniModelLinearSumHeapFailures : public Base { + public: + MiniModelLinearSumHeapFailures(void) + : Base("Fault::MiniModel::LinearSumHeapFailures") {} + virtual bool run(void) { + return minimodel_linear_sum_heap_failures_release_nodes(); + } + }; + class MiniModelBoolMiscHeapFailure : public Base { public: MiniModelBoolMiscHeapFailure(void) @@ -967,6 +1143,15 @@ namespace Test { namespace Fault { } }; + class CloneAdvisorCopy : public Base { + public: + CloneAdvisorCopy(void) + : Base("Fault::Clone::AdvisorCopy") {} + virtual bool run(void) { + return clone_after_failed_advisor_copy(); + } + }; + class CloneDerivedSpaceCopy : public Base { public: CloneDerivedSpaceCopy(void) @@ -1008,8 +1193,10 @@ namespace Test { namespace Fault { #endif ClonePropagatorCopy clone_propagator_copy; CloneBrancherCopy clone_brancher_copy; + CloneAdvisorCopy clone_advisor_copy; CloneDerivedSpaceCopy clone_derived_space_copy; CloneLocalObjectCopy clone_local_object_copy; + MiniModelLinearSumHeapFailures minimodel_linear_sum_heap_failures; }} From 1a996d3118ee8b8d0c160cdf7d79e7e2f26ae94f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 28 Jun 2026 11:00:37 +0200 Subject: [PATCH 25/31] Fix BAB recomputation UBSan issue Pass the incumbent best solution as an optional pointer during BAB path recomputation. Recompute can run before an incumbent exists; the previous reference parameter forced callers to dereference a null best pointer and tripped UBSan. --- gecode/search/par/bab.hpp | 2 +- gecode/search/par/path.hh | 2 +- gecode/search/par/path.hpp | 10 +++++----- gecode/search/seq/bab.hpp | 2 +- gecode/search/seq/path.hh | 2 +- gecode/search/seq/path.hpp | 10 +++++----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gecode/search/par/bab.hpp b/gecode/search/par/bab.hpp index b3dd78430e..7246ea856d 100755 --- a/gecode/search/par/bab.hpp +++ b/gecode/search/par/bab.hpp @@ -331,7 +331,7 @@ namespace Gecode { namespace Search { namespace Par { } } } else if (!path.empty()) { - cur = path.recompute(d,engine().opt().a_d,*this,*best,mark,tracer); + cur = path.recompute(d,engine().opt().a_d,*this,best,mark,tracer); if (cur == nullptr) path.next(); m.release(); diff --git a/gecode/search/par/path.hh b/gecode/search/par/path.hh index c9017777f8..14759ed4f3 100644 --- a/gecode/search/par/path.hh +++ b/gecode/search/par/path.hh @@ -143,7 +143,7 @@ namespace Gecode { namespace Search { namespace Par { Tracer& t); /// Recompute space according to path Space* recompute(unsigned int& d, unsigned int a_d, Worker& s, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t); /// Return number of entries on stack int entries(void) const; diff --git a/gecode/search/par/path.hpp b/gecode/search/par/path.hpp index a9b321b46a..82922ec672 100644 --- a/gecode/search/par/path.hpp +++ b/gecode/search/par/path.hpp @@ -353,7 +353,7 @@ namespace Gecode { namespace Search { namespace Par { template forceinline Space* Path::recompute(unsigned int& d, unsigned int a_d, Worker& stat, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t) { assert(!ds.empty()); // Recompute space according to path @@ -364,9 +364,9 @@ namespace Gecode { namespace Search { namespace Par { Space* s = ds.top().space(); s->commit(*ds.top().choice(),ds.top().alt()); assert(ds.entries()-1 == lc()); - if (mark > ds.entries()-1) { + if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; - s->constrain(best); + s->constrain(*best); } ds.top().space(nullptr); // Mark as reusable @@ -383,9 +383,9 @@ namespace Gecode { namespace Search { namespace Par { Space* s = ds[l].space(); // Last clone - if (l < mark) { + if ((best != nullptr) && (l < mark)) { mark = l; - s->constrain(best); + s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. if (s->status(stat) == SS_FAILED) { diff --git a/gecode/search/seq/bab.hpp b/gecode/search/seq/bab.hpp index b744685024..97523ae07a 100644 --- a/gecode/search/seq/bab.hpp +++ b/gecode/search/seq/bab.hpp @@ -83,7 +83,7 @@ namespace Gecode { namespace Search { namespace Seq { while (cur == nullptr) { if (path.empty()) return nullptr; - cur = path.recompute(d,opt.a_d,*this,*best,mark,tracer); + cur = path.recompute(d,opt.a_d,*this,best,mark,tracer); if (cur != nullptr) break; path.next(); diff --git a/gecode/search/seq/path.hh b/gecode/search/seq/path.hh index fcc46088cf..36c43015f2 100644 --- a/gecode/search/seq/path.hh +++ b/gecode/search/seq/path.hh @@ -137,7 +137,7 @@ namespace Gecode { namespace Search { namespace Seq { Tracer& t); /// Recompute space according to path Space* recompute(unsigned int& d, unsigned int a_d, Worker& s, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t); /// Return number of entries on stack int entries(void) const; diff --git a/gecode/search/seq/path.hpp b/gecode/search/seq/path.hpp index efbc7a783b..f4445adc8c 100644 --- a/gecode/search/seq/path.hpp +++ b/gecode/search/seq/path.hpp @@ -289,7 +289,7 @@ namespace Gecode { namespace Search { namespace Seq { template forceinline Space* Path::recompute(unsigned int& d, unsigned int a_d, Worker& stat, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t) { assert(!ds.empty()); // Recompute space according to path @@ -300,9 +300,9 @@ namespace Gecode { namespace Search { namespace Seq { Space* s = ds.top().space(); s->commit(*ds.top().choice(),ds.top().alt()); assert(ds.entries()-1 == lc()); - if (mark > ds.entries()-1) { + if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; - s->constrain(best); + s->constrain(*best); } ds.top().space(nullptr); // Mark as reusable @@ -319,9 +319,9 @@ namespace Gecode { namespace Search { namespace Seq { Space* s = ds[l].space(); // Last clone - if (l < mark) { + if ((best != nullptr) && (l < mark)) { mark = l; - s->constrain(best); + s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. if (s->status(stat) == SS_FAILED) { From fc1a6d28b6fdf05993cc5b887ec789e1d304d46e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 28 Jun 2026 11:43:05 +0200 Subject: [PATCH 26/31] Fix parallel search sanitizer races Replace shared volatile search flags and stop limits with atomics, and close a racy path-steal check. TSan reported concurrent reads and writes in parallel engines and portfolio stop propagation; these fields are cross-thread coordination state. --- gecode/search.hh | 9 +++++---- gecode/search/par/engine.hh | 5 +++-- gecode/search/par/engine.hpp | 25 ++++++++++++------------- gecode/search/par/pbs.cpp | 3 ++- gecode/search/par/pbs.hh | 9 +++++---- gecode/search/par/pbs.hpp | 18 +++++++++--------- gecode/search/stop.cpp | 8 ++++---- gecode/search/stop.hpp | 16 ++++++++-------- 8 files changed, 48 insertions(+), 45 deletions(-) diff --git a/gecode/search.hh b/gecode/search.hh index efd5168fbd..fa97479249 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -43,6 +43,7 @@ #define GECODE_SEARCH_HH #include +#include #include @@ -832,7 +833,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT NodeStop : public Stop { protected: /// Node limit - unsigned long long int l; + std::atomic l; public: /// Stop if node limit \a l is exceeded NodeStop(unsigned long long int l); @@ -855,7 +856,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT FailStop : public Stop { protected: /// Failure limit - unsigned long long int l; + std::atomic l; public: /// Stop if failure limit \a l is exceeded FailStop(unsigned long long int l); @@ -876,7 +877,7 @@ namespace Gecode { namespace Search { /// Time when execution should stop Support::Timer t; /// Current limit in milliseconds - double l; + std::atomic l; public: /// Stop if search exceeds \a l milliseconds (from creation of this object) TimeStop(double l); @@ -897,7 +898,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT RestartStop : public Stop { protected: /// Restart limit - unsigned long long int l; + std::atomic l; public: /// Stop if restart limit \a l is exceeded RestartStop(unsigned long long int l); diff --git a/gecode/search/par/engine.hh b/gecode/search/par/engine.hh index 7a0bb608d0..6342de960d 100644 --- a/gecode/search/par/engine.hh +++ b/gecode/search/par/engine.hh @@ -38,6 +38,7 @@ #include #include #include +#include namespace Gecode { namespace Search { namespace Par { @@ -98,7 +99,7 @@ namespace Gecode { namespace Search { namespace Par { }; protected: /// The current command - volatile Cmd _cmd; + std::atomic _cmd; /// Mutex for forcing workers to wait Support::Mutex _m_wait; public: @@ -172,7 +173,7 @@ namespace Gecode { namespace Search { namespace Par { /// Number of busy workers volatile unsigned int n_busy; /// Whether a worker had been stopped - volatile bool has_stopped; + std::atomic has_stopped; /// Whether search state changed such that signal is needed bool signal(void) const; public: diff --git a/gecode/search/par/engine.hpp b/gecode/search/par/engine.hpp index 62d978f3f3..105ad8a94d 100644 --- a/gecode/search/par/engine.hpp +++ b/gecode/search/par/engine.hpp @@ -55,7 +55,7 @@ namespace Gecode { namespace Search { namespace Par { template forceinline bool Engine::stopped(void) const { - return has_stopped; + return has_stopped.load(std::memory_order_acquire); } @@ -66,18 +66,18 @@ namespace Gecode { namespace Search { namespace Par { template forceinline typename Engine::Cmd Engine::cmd(void) const { - return _cmd; + return _cmd.load(std::memory_order_acquire); } template forceinline void Engine::block(void) { - _cmd = C_WAIT; + _cmd.store(C_WAIT, std::memory_order_release); Support::Thread::acquireGlobalMutex(&_m_wait); } template forceinline void Engine::release(Cmd c) { - _cmd = c; + _cmd.store(c, std::memory_order_release); Support::Thread::releaseGlobalMutex(&_m_wait); } template @@ -114,13 +114,13 @@ namespace Gecode { namespace Search { namespace Par { template forceinline Engine::Engine(const Options& o) - : _opt(o), solutions(heap) { + : _opt(o), _cmd(C_WAIT), solutions(heap) { // Initialize termination information _n_term_not_ack = workers(); _n_not_terminated = workers(); // Initialize search information n_busy = workers(); - has_stopped = false; + has_stopped.store(false, std::memory_order_release); // Initialize reset information _n_reset_not_ack = workers(); } @@ -145,7 +145,8 @@ namespace Gecode { namespace Search { namespace Par { template forceinline bool Engine::signal(void) const { - return solutions.empty() && (n_busy > 0) && !has_stopped; + return solutions.empty() && (n_busy > 0) && + !has_stopped.load(std::memory_order_acquire); } template forceinline void @@ -172,7 +173,7 @@ namespace Gecode { namespace Search { namespace Par { Engine::stop(void) { m_search.acquire(); bool bs = signal(); - has_stopped = true; + has_stopped.store(true, std::memory_order_release); if (bs) e_search.signal(); m_search.release(); @@ -270,10 +271,8 @@ namespace Gecode { namespace Search { namespace Par { * If that is not true any longer, the worker will be asked * again eventually. */ - if (!path.steal()) - return nullptr; m.acquire(); - Space* s = path.steal(*this,d,myt,ot); + Space* s = path.steal() ? path.steal(*this,d,myt,ot) : nullptr; m.release(); // Tell that there will be one more busy worker if (s != nullptr) @@ -305,7 +304,7 @@ namespace Gecode { namespace Search { namespace Par { return s; } // We ignore stopped (it will be reported again if needed) - has_stopped = false; + has_stopped.store(false, std::memory_order_release); // No more solutions? if (n_busy == 0) { m_search.release(); @@ -333,7 +332,7 @@ namespace Gecode { namespace Search { namespace Par { return s; } // No more solutions or stopped? - if ((n_busy == 0) || has_stopped) { + if ((n_busy == 0) || has_stopped.load(std::memory_order_acquire)) { m_search.release(); // Make workers wait again block(); diff --git a/gecode/search/par/pbs.cpp b/gecode/search/par/pbs.cpp index 2c46677a39..ee86e3e899 100644 --- a/gecode/search/par/pbs.cpp +++ b/gecode/search/par/pbs.cpp @@ -37,7 +37,8 @@ namespace Gecode { namespace Search { namespace Par { bool PortfolioStop::stop(const Statistics& s, const Options& o) { - return *tostop || ((so != nullptr) && so->stop(s,o)); + return tostop->load(std::memory_order_acquire) || + ((so != nullptr) && so->stop(s,o)); } }}} diff --git a/gecode/search/par/pbs.hh b/gecode/search/par/pbs.hh index d8238513e5..5a2f6a36e9 100644 --- a/gecode/search/par/pbs.hh +++ b/gecode/search/par/pbs.hh @@ -35,6 +35,7 @@ #define GECODE_SEARCH_PAR_PBS_HH #include +#include namespace Gecode { namespace Search { namespace Par { @@ -44,12 +45,12 @@ namespace Gecode { namespace Search { namespace Par { /// The stop object for the slaves Stop* so; /// Whether search must be stopped - volatile bool* tostop; + std::atomic* tostop; public: /// Initialize PortfolioStop(Stop* so); /// Set pointer to shared \a tostop variable - void share(volatile bool* ts); + void share(std::atomic* ts); /// Return true if portfolio engine must be stopped virtual bool stop(const Statistics& s, const Options& o); /// Signal whether search must be stopped @@ -147,9 +148,9 @@ namespace Gecode { namespace Search { namespace Par { /// Number of active slave engines unsigned int n_active; /// Whether a slave has been stopped - bool slave_stop; + std::atomic slave_stop; /// Shared stop flag - volatile bool tostop; + std::atomic tostop; /// Collect solutions in this Collect solutions; /// Mutex for synchronization diff --git a/gecode/search/par/pbs.hpp b/gecode/search/par/pbs.hpp index 39e3628ec1..d45a3df96a 100755 --- a/gecode/search/par/pbs.hpp +++ b/gecode/search/par/pbs.hpp @@ -118,7 +118,7 @@ namespace Gecode { namespace Search { namespace Par { : so(so0), tostop(nullptr) {} forceinline void - PortfolioStop::share(volatile bool* ts) { + PortfolioStop::share(std::atomic* ts) { tostop = ts; } @@ -174,10 +174,10 @@ namespace Gecode { namespace Search { namespace Par { if (s != nullptr) { b = solutions.add(s,slave); if (b) - tostop = true; + tostop.store(true, std::memory_order_release); } else if (slave->stopped()) { - if (!tostop) - slave_stop = true; + if (!tostop.load(std::memory_order_acquire)) + slave_stop.store(true, std::memory_order_release); } else { // Move slave to inactive, as it has exhausted its engine unsigned int i=0; @@ -186,7 +186,7 @@ namespace Gecode { namespace Search { namespace Par { assert(i < n_active); assert(n_active > 0); std::swap(slaves[i],slaves[--n_active]); - tostop = true; + tostop.store(true, std::memory_order_release); } if (b) { if (--n_busy == 0) @@ -211,12 +211,12 @@ namespace Gecode { namespace Search { namespace Par { m.acquire(); if (solutions.empty()) { // Clear all - tostop = false; - slave_stop = false; + tostop.store(false, std::memory_order_release); + slave_stop.store(false, std::memory_order_release); // Invariant: all slaves are idle! assert(n_busy == 0); - assert(!tostop); + assert(!tostop.load(std::memory_order_acquire)); if (n_active > 0) { // Run all active slaves @@ -254,7 +254,7 @@ namespace Gecode { namespace Search { namespace Par { template bool PBS::stopped(void) const { - return slave_stop; + return slave_stop.load(std::memory_order_acquire); } template diff --git a/gecode/search/stop.cpp b/gecode/search/stop.cpp index d21cca77da..1b60c65b2f 100755 --- a/gecode/search/stop.cpp +++ b/gecode/search/stop.cpp @@ -63,7 +63,7 @@ namespace Gecode { namespace Search { */ bool NodeStop::stop(const Statistics& s, const Options&) { - return s.node > l; + return s.node > l.load(std::memory_order_acquire); } @@ -73,7 +73,7 @@ namespace Gecode { namespace Search { */ bool FailStop::stop(const Statistics& s, const Options&) { - return s.fail > l; + return s.fail > l.load(std::memory_order_acquire); } @@ -83,7 +83,7 @@ namespace Gecode { namespace Search { */ bool TimeStop::stop(const Statistics&, const Options&) { - return t.stop() > l; + return t.stop() > l.load(std::memory_order_acquire); } /* @@ -92,7 +92,7 @@ namespace Gecode { namespace Search { */ bool RestartStop::stop(const Statistics& s, const Options&) { - return s.restart > l; + return s.restart > l.load(std::memory_order_acquire); } }} diff --git a/gecode/search/stop.hpp b/gecode/search/stop.hpp index 3f62484ddc..742a78fda9 100755 --- a/gecode/search/stop.hpp +++ b/gecode/search/stop.hpp @@ -53,12 +53,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int NodeStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void NodeStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } @@ -72,12 +72,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int FailStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void FailStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } @@ -94,12 +94,12 @@ namespace Gecode { namespace Search { forceinline double TimeStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void TimeStop::limit(double l0) { - l=l0; + l.store(l0, std::memory_order_release); } forceinline void @@ -117,12 +117,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int RestartStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void RestartStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } }} From f05e7f218a0d9646361538c249e77fde1d50df4d Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 28 Jun 2026 12:03:49 +0200 Subject: [PATCH 27/31] Fix parallel PBS slave lifetime Delete portfolio slave engines in the PBS destructor before freeing the slave pointer array. This closes the lifetime leak exposed by sanitizer runs. --- gecode/search/par/pbs.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gecode/search/par/pbs.hpp b/gecode/search/par/pbs.hpp index d45a3df96a..7f4bec9d78 100755 --- a/gecode/search/par/pbs.hpp +++ b/gecode/search/par/pbs.hpp @@ -283,6 +283,8 @@ namespace Gecode { namespace Search { namespace Par { template PBS::~PBS(void) { assert(n_busy == 0); + for (unsigned int i=0U; i*>(slaves,n_slaves); } From 3bd9ea974a5c6e2fae2abe86b1529bc5ec626ed6 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 28 Jun 2026 15:57:59 +0200 Subject: [PATCH 28/31] Fix large sanitizer findings Address the remaining findings from the larger sanitizer matrix: harden integer-domain width arithmetic, avoid invalid marked-link casts, protect RBS stop state, and make thread-runner cleanup tolerate null terminators. --- gecode/int/int-set-1.hpp | 10 +++--- gecode/int/int-set.cpp | 5 +-- gecode/int/var-imp/delta.hpp | 2 +- gecode/int/var-imp/int.cpp | 18 ++++++---- gecode/int/var-imp/int.hpp | 47 ++++++++++++++++++--------- gecode/kernel/core.hpp | 56 ++++++++++++++++++++++++++------ gecode/search/seq/rbs.cpp | 20 +++++++----- gecode/search/seq/rbs.hh | 10 ++++++ gecode/search/seq/rbs.hpp | 28 ++++++++++++++++ gecode/support/thread/thread.cpp | 5 +-- test/int.cpp | 32 ++++++++++++------ 11 files changed, 173 insertions(+), 60 deletions(-) diff --git a/gecode/int/int-set-1.hpp b/gecode/int/int-set-1.hpp index 637253066c..9d5f4cf89e 100755 --- a/gecode/int/int-set-1.hpp +++ b/gecode/int/int-set-1.hpp @@ -164,7 +164,8 @@ namespace Gecode { IntSet::width(int i) const { assert(object() != nullptr); IntSetObject* o = static_cast(object()); - return static_cast(o->r[i].max-o->r[i].min)+1; + return static_cast(o->r[i].max) - + static_cast(o->r[i].min) + 1U; } forceinline int @@ -203,7 +204,8 @@ namespace Gecode { forceinline unsigned int IntSet::width(void) const { IntSetObject* o = static_cast(object()); - return (o == nullptr) ? 0U : static_cast(max()-min()+1); + return (o == nullptr) ? 0U : + static_cast(max()) - static_cast(min()) + 1U; } forceinline bool @@ -265,7 +267,8 @@ namespace Gecode { } forceinline unsigned int IntSetRanges::width(void) const { - return static_cast(i->max - i->min) + 1; + return static_cast(i->max) - + static_cast(i->min) + 1U; } /* @@ -311,4 +314,3 @@ namespace Gecode { } // STATISTICS: int-var - diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index b2e5b17a33..fdfa4f2369 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -154,7 +154,8 @@ namespace Gecode { IntSetObject* o = IntSetObject::allocate(n); unsigned int s = 0; for (int i=0; i(r[i].max-r[i].min+1); + s += static_cast(r[i].max) - + static_cast(r[i].min) + 1U; o->r[i]=r[i]; } o->size = s; @@ -217,7 +218,7 @@ namespace Gecode { if (n <= m) { IntSetObject* o = IntSetObject::allocate(1); o->r[0].min = n; o->r[0].max = m; - o->size = static_cast(m - n + 1); + o->size = static_cast(m) - static_cast(n) + 1U; object(o); } } diff --git a/gecode/int/var-imp/delta.hpp b/gecode/int/var-imp/delta.hpp index e0b41c8956..4d8fa3a2ee 100644 --- a/gecode/int/var-imp/delta.hpp +++ b/gecode/int/var-imp/delta.hpp @@ -52,7 +52,7 @@ namespace Gecode { namespace Int { } forceinline unsigned int IntDelta::width(void) const { - return static_cast(_max - _min + 1); + return static_cast(_max) - static_cast(_min) + 1U; } forceinline bool IntDelta::any(void) const { diff --git a/gecode/int/var-imp/int.cpp b/gecode/int/var-imp/int.cpp index a96e23ca4c..f6555323e6 100644 --- a/gecode/int/var-imp/int.cpp +++ b/gecode/int/var-imp/int.cpp @@ -37,8 +37,10 @@ namespace Gecode { namespace Int { forceinline bool IntVarImp::closer_min(int n) const { - unsigned int l = static_cast(n - dom.min()); - unsigned int r = static_cast(dom.max() - n); + unsigned int l = static_cast(n) - + static_cast(dom.min()); + unsigned int r = static_cast(dom.max()) - + static_cast(n); return l < r; } @@ -102,7 +104,8 @@ namespace Gecode { namespace Int { unsigned int h = 0; while (m < c->min()) { RangeList* p = c->prev(n); c->fix(n); - h += (c->min() - p->max() - 1); + h += static_cast(c->min()) - + static_cast(p->max()) - 1U; n=c; c=p; } holes -= h; @@ -136,7 +139,8 @@ namespace Gecode { namespace Int { unsigned int h = 0; while (m > c->max()) { RangeList* n = c->next(p); c->fix(n); - h += (n->min() - c->max() - 1); + h += static_cast(n->min()) - + static_cast(c->max()) - 1U; p=c; c=n; } holes -= h; @@ -212,7 +216,8 @@ namespace Gecode { namespace Int { } else { // Remains non-range f_next->prev(fst(),nullptr); fst()->dispose(home); fst(f_next); - holes -= dom.min() - f_min - 1; + holes -= static_cast(dom.min()) - + static_cast(f_min) - 1U; me = ME_INT_BND; } } else if (m == f_min) { @@ -243,7 +248,8 @@ namespace Gecode { namespace Int { } else { // Remains non-range l_prev->next(lst(),nullptr); lst()->dispose(home); lst(l_prev); - holes -= l_max - dom.max() - 1; + holes -= static_cast(l_max) - + static_cast(dom.max()) - 1U; me = ME_INT_BND; } } else if (m == l_max) { diff --git a/gecode/int/var-imp/int.hpp b/gecode/int/var-imp/int.hpp index 06f2cffe02..f71ac683fd 100644 --- a/gecode/int/var-imp/int.hpp +++ b/gecode/int/var-imp/int.hpp @@ -45,6 +45,12 @@ namespace Gecode { namespace Int { #define GECODE_INT_RL2PD(r) reinterpret_cast(r) #define GECODE_INT_PD2RL(p) reinterpret_cast(p) + forceinline unsigned int + range_width(int min, int max) { + return static_cast(max) - + static_cast(min) + 1U; + } + forceinline IntVarImp::RangeList::RangeList(void) {} @@ -104,7 +110,7 @@ namespace Gecode { namespace Int { } forceinline unsigned int IntVarImp::RangeList::width(void) const { - return static_cast(_max - _min) + 1; + return range_width(_min,_max); } @@ -196,7 +202,7 @@ namespace Gecode { namespace Int { assert(n >= 2); RangeList* r = home.alloc(n); fst(r); lst(r+n-1); - unsigned int h = static_cast(d.max()-d.min())+1; + unsigned int h = range_width(d.min(),d.max()); h -= d.width(0); r[0].min(d.min(0)); r[0].max(d.max(0)); r[0].prevnext(nullptr,&r[1]); @@ -259,7 +265,8 @@ namespace Gecode { namespace Int { if (fst() == nullptr) { return (dom.min() == dom.max()) ? 0U : 1U; } else if (dom.min() == fst()->max()) { - return static_cast(fst()->next(nullptr)->min()-dom.min()); + return static_cast(fst()->next(nullptr)->min()) - + static_cast(dom.min()); } else { return 1U; } @@ -269,7 +276,8 @@ namespace Gecode { namespace Int { if (fst() == nullptr) { return (dom.min() == dom.max()) ? 0U : 1U; } else if (dom.max() == lst()->min()) { - return static_cast(dom.max()-lst()->prev(nullptr)->max()); + return static_cast(dom.max()) - + static_cast(lst()->prev(nullptr)->max()); } else { return 1U; } @@ -531,7 +539,7 @@ namespace Gecode { namespace Int { // Construct new rangelist RangeList* f = new (home) RangeList(min0,max0,nullptr,nullptr); RangeList* l = f; - unsigned int s = static_cast(max0-min0+1); + unsigned int s = range_width(min0,max0); do { RangeList* n = new (home) RangeList(ri.min(),ri.max(),l,nullptr); l->next(nullptr,n); @@ -604,8 +612,9 @@ namespace Gecode { namespace Int { min0=ri.min(); max0=ri.max(); ++ri; if (max0 > end) break; - assert(h > static_cast(max0-min0+1)); - h -= max0-min0+1; + unsigned int w = range_width(min0,max0); + assert(h > w); + h -= w; RangeList* n = new (home) RangeList(min0,max0,p,r); p->next(r,n); r->prev(p,n); p=n; @@ -642,8 +651,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - unsigned int b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + unsigned int b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); @@ -747,7 +758,7 @@ namespace Gecode { namespace Int { break; } else if ((i_min > r->min()) && (i_max < r->max())) { // i is included in r: create new range before the current one - h += static_cast(i_max - i_min) + 1; + h += range_width(i_min,i_max); RangeList* n = new (home) RangeList(r->min(),i_min-1,p,r); r->min(i_max+1); p->next(r,n); r->prev(p,n); @@ -760,7 +771,7 @@ namespace Gecode { namespace Int { } else if (i_max < r->max()) { assert(i_min <= r->min()); // i ends before r: adjust minimum of r - h += i_max-r->min()+1; + h += range_width(r->min(),i_max); r->min(i_max+1); if (!i()) break; @@ -770,7 +781,7 @@ namespace Gecode { namespace Int { } else { assert((i_max >= r->max()) && (r->min() < i_min)); // r ends before i: adjust maximum of r - h += r->max()-i_min+1; + h += range_width(i_min,r->max()); r->max(i_min-1); RangeList* n=r->next(p); p=r; r=n; if (r == &l) @@ -807,8 +818,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); @@ -961,8 +974,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - unsigned int b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + unsigned int b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 1736bcedd4..0e462f92f8 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -44,6 +44,7 @@ * */ +#include #include namespace Gecode { @@ -1595,7 +1596,7 @@ namespace Gecode { class GECODE_VTABLE_EXPORT NoGoods { protected: /// Number of no-goods - unsigned long int n; + std::atomic n; public: /// Initialize NoGoods(void); @@ -1915,11 +1916,11 @@ namespace Gecode { /// Last actor for forced disposal Actor** d_lst; - /// Used for default argument + /// Legacy unused status statistics GECODE_KERNEL_EXPORT static StatusStatistics unused_status; - /// Used for default argument + /// Legacy unused clone statistics GECODE_KERNEL_EXPORT static CloneStatistics unused_clone; - /// Used for default argument + /// Legacy unused commit statistics GECODE_KERNEL_EXPORT static CommitStatistics unused_commit; /** @@ -2148,7 +2149,9 @@ namespace Gecode { * \ingroup TaskSearch */ GECODE_KERNEL_EXPORT - SpaceStatus status(StatusStatistics& stat=unused_status); + SpaceStatus status(StatusStatistics& stat); + /// Query space status without collecting statistics + SpaceStatus status(void); /** * \brief Create new choice for current brancher @@ -2211,7 +2214,10 @@ namespace Gecode { * * \ingroup TaskSearch */ - Space* clone(CloneStatistics& stat=unused_clone) const; + /// Clone space without collecting statistics + Space* clone(void) const; + /// Clone space + Space* clone(CloneStatistics& stat) const; /** * \brief Commit choice \a c for alternative \a a @@ -2247,8 +2253,11 @@ namespace Gecode { * * \ingroup TaskSearch */ + /// Commit choice \a c for alternative \a a without collecting statistics + void commit(const Choice& c, unsigned int a); + /// Commit choice \a c for alternative \a a void commit(const Choice& c, unsigned int a, - CommitStatistics& stat=unused_commit); + CommitStatistics& stat); /** * \brief If possible, commit choice \a c for alternative \a a * @@ -2281,8 +2290,11 @@ namespace Gecode { * * \ingroup TaskSearch */ + /// If possible, commit choice \a c for alternative \a a without collecting statistics + void trycommit(const Choice& c, unsigned int a); + /// If possible, commit choice \a c for alternative \a a void trycommit(const Choice& c, unsigned int a, - CommitStatistics& stat=unused_commit); + CommitStatistics& stat); /** * \brief Create no-good literal for choice \a c and alternative \a a * @@ -3103,11 +3115,11 @@ namespace Gecode { : n(0) {} forceinline unsigned long int NoGoods::ng(void) const { - return n; + return n.load(std::memory_order_acquire); } forceinline void NoGoods::ng(unsigned long int n0) { - n=n0; + n.store(n0, std::memory_order_release); } forceinline NoGoods::~NoGoods(void) {} @@ -3275,6 +3287,18 @@ namespace Gecode { s.notice(a,p,duplicate); } + forceinline SpaceStatus + Space::status(void) { + StatusStatistics stat; + return status(stat); + } + + forceinline Space* + Space::clone(void) const { + CloneStatistics stat; + return clone(stat); + } + forceinline Space* Space::clone(CloneStatistics&) const { // Clone is only const for search engines. During cloning, several data @@ -3283,11 +3307,23 @@ namespace Gecode { return const_cast(this)->_clone(); } + forceinline void + Space::commit(const Choice& c, unsigned int a) { + CommitStatistics stat; + commit(c,a,stat); + } + forceinline void Space::commit(const Choice& c, unsigned int a, CommitStatistics&) { _commit(c,a); } + forceinline void + Space::trycommit(const Choice& c, unsigned int a) { + CommitStatistics stat; + trycommit(c,a,stat); + } + forceinline void Space::trycommit(const Choice& c, unsigned int a, CommitStatistics&) { _trycommit(c,a); diff --git a/gecode/search/seq/rbs.cpp b/gecode/search/seq/rbs.cpp index 98007145db..7f01b82687 100755 --- a/gecode/search/seq/rbs.cpp +++ b/gecode/search/seq/rbs.cpp @@ -38,10 +38,12 @@ namespace Gecode { namespace Search { namespace Seq { bool RestartStop::stop(const Statistics& s, const Options& o) { + Support::Lock lock(m); // Stop if the fail limit for the engine says so if (s.fail > l) { + if (!e_stopped) + m_stat.restart++; e_stopped = true; - m_stat.restart++; return true; } // Stop if the stop object for the meta engine says so @@ -60,10 +62,10 @@ namespace Gecode { namespace Search { namespace Seq { NoGoods& ng = e->nogoods(); // Reset number of no-goods found ng.ng(0); - MetaInfo mi(stop->m_stat.restart,MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); + MetaInfo mi(stop->restarts(),MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); bool r = master->master(mi); - stop->m_stat.nogood += ng.ng(); - if (master->status(stop->m_stat) == SS_FAILED) { + stop->nogood(ng.ng()); + if (stop->status(master) == SS_FAILED) { stop->update(e->statistics()); delete master; master = nullptr; @@ -76,7 +78,7 @@ namespace Gecode { namespace Search { namespace Seq { complete = slave->slave(mi); e->reset(slave); sslr = 0; - stop->m_stat.restart++; + stop->restart(); } } while (true) { @@ -92,16 +94,16 @@ namespace Gecode { namespace Search { namespace Seq { // The engine must perform a true restart // The number of the restart has been incremented in the stop object if (!complete && !e->stopped()) - stop->m_stat.restart++; + stop->restart(); sslr = 0; NoGoods& ng = e->nogoods(); ng.ng(0); - MetaInfo mi(stop->m_stat.restart,e->stopped() ? MetaInfo::RR_LIM : MetaInfo::RR_CMPL,sslr,e->statistics().fail,last,ng); + MetaInfo mi(stop->restarts(),e->stopped() ? MetaInfo::RR_LIM : MetaInfo::RR_CMPL,sslr,e->statistics().fail,last,ng); (void) master->master(mi); - stop->m_stat.nogood += ng.ng(); + stop->nogood(ng.ng()); unsigned long long int nl = ++(*co); stop->limit(e->statistics(),nl); - if (master->status(stop->m_stat) == SS_FAILED) + if (stop->status(master) == SS_FAILED) return nullptr; Space* slave = master; master = master->clone(); diff --git a/gecode/search/seq/rbs.hh b/gecode/search/seq/rbs.hh index 1828c2f33f..311bf355e0 100755 --- a/gecode/search/seq/rbs.hh +++ b/gecode/search/seq/rbs.hh @@ -45,6 +45,8 @@ namespace Gecode { namespace Search { namespace Seq { templateclass> friend class ::Gecode::RBS; friend class ::Gecode::Search::Seq::RBS; private: + /// Mutex protecting restart stop state + mutable Support::Mutex m; /// The failure limit for the engine unsigned long long int l; /// The stop object for the meta engine @@ -58,6 +60,14 @@ namespace Gecode { namespace Search { namespace Seq { RestartStop(Stop* s); /// Return true if meta engine must be stopped virtual bool stop(const Statistics& s, const Options& o); + /// Return current restart count + unsigned long int restarts(void) const; + /// Increment current restart count + void restart(void); + /// Add no-goods to meta statistics + void nogood(unsigned long int n); + /// Test master status with meta statistics + SpaceStatus status(Space* s); /// Set current limit for the engine to \a l fails void limit(const Statistics& s, unsigned long long int l); /// Update statistics diff --git a/gecode/search/seq/rbs.hpp b/gecode/search/seq/rbs.hpp index 179a5c1f84..c1ce0ec4ec 100755 --- a/gecode/search/seq/rbs.hpp +++ b/gecode/search/seq/rbs.hpp @@ -38,8 +38,33 @@ namespace Gecode { namespace Search { namespace Seq { RestartStop::RestartStop(Stop* s) : l(0U), m_stop(s), e_stopped(false) {} + forceinline unsigned long int + RestartStop::restarts(void) const { + Support::Lock lock(m); + return m_stat.restart; + } + + forceinline void + RestartStop::restart(void) { + Support::Lock lock(m); + m_stat.restart++; + } + + forceinline void + RestartStop::nogood(unsigned long int n) { + Support::Lock lock(m); + m_stat.nogood += n; + } + + forceinline SpaceStatus + RestartStop::status(Space* s) { + Support::Lock lock(m); + return s->status(m_stat); + } + forceinline void RestartStop::limit(const Search::Statistics& s, unsigned long long int l0) { + Support::Lock lock(m); l = l0; m_stat += s; e_stopped = false; @@ -47,16 +72,19 @@ namespace Gecode { namespace Search { namespace Seq { forceinline void RestartStop::update(const Search::Statistics& s) { + Support::Lock lock(m); m_stat += s; } forceinline bool RestartStop::enginestopped(void) const { + Support::Lock lock(m); return e_stopped; } forceinline Statistics RestartStop::metastatistics(void) const { + Support::Lock lock(m); return m_stat; } diff --git a/gecode/support/thread/thread.cpp b/gecode/support/thread/thread.cpp index 7428a1b270..4b726671aa 100644 --- a/gecode/support/thread/thread.cpp +++ b/gecode/support/thread/thread.cpp @@ -54,9 +54,10 @@ namespace Gecode { namespace Support { GECODE_ASSUME(r != nullptr); Runnable* e = r.exchange(nullptr); assert(e != nullptr); + const bool delete_after_run = e->todelete(); + Terminator* t = delete_after_run ? e->terminator() : nullptr; e->run(); - if (e->todelete()) { - Terminator* t = e->terminator(); + if (delete_after_run) { delete e; if (t) t->terminated(); diff --git a/test/int.cpp b/test/int.cpp index af583e530d..82c57e4192 100755 --- a/test/int.cpp +++ b/test/int.cpp @@ -185,16 +185,20 @@ namespace Test { namespace Int { switch (rand(3)) { case 0: if (a[i] < x[i].max()) { - v=a[i]+1+ - static_cast(rand(static_cast(x[i].max()-a[i]))); + unsigned int n = + static_cast(x[i].max()) - static_cast(a[i]); + unsigned int offset = rand(n); + v = static_cast(static_cast(a[i]) + 1LL + offset); assert((v > a[i]) && (v <= x[i].max())); irt = IRT_LE; } break; case 1: if (a[i] > x[i].min()) { - v=x[i].min()+ - static_cast(rand(static_cast(a[i]-x[i].min()))); + unsigned int n = + static_cast(a[i]) - static_cast(x[i].min()); + unsigned int offset = rand(n); + v = static_cast(static_cast(x[i].min()) + offset); assert((v < a[i]) && (v >= x[i].min())); irt = IRT_GR; } @@ -205,7 +209,7 @@ namespace Test { namespace Int { unsigned int skip = rand(static_cast(x[i].size()-1)); while (true) { if (it.width() > skip) { - v = it.min() + static_cast(skip); + v = static_cast(static_cast(it.min()) + skip); if (v == a[i]) { if (it.width() == 1) { ++it; v = it.min(); @@ -278,14 +282,21 @@ namespace Test { namespace Int { // Prune values if (bounds_only) { if (rand(2) && !x[i].assigned()) { - int v=x[i].min()+1+ - static_cast(rand(static_cast(x[i].max()-x[i].min()))); + unsigned int n = + static_cast(x[i].max()) - + static_cast(x[i].min()); + unsigned int offset = rand(n); + int v = static_cast(static_cast(x[i].min()) + + 1LL + offset); assert((v > x[i].min()) && (v <= x[i].max())); rel(i, Gecode::IRT_LE, v); } if (rand(2) && !x[i].assigned()) { - int v=x[i].min()+ - static_cast(rand(static_cast(x[i].max()-x[i].min()))); + unsigned int n = + static_cast(x[i].max()) - + static_cast(x[i].min()); + unsigned int offset = rand(n); + int v = static_cast(static_cast(x[i].min()) + offset); assert((v < x[i].max()) && (v >= x[i].min())); rel(i, Gecode::IRT_GR, v); } @@ -297,7 +308,8 @@ namespace Test { namespace Int { unsigned int skip = rand(x[i].size()-1); while (true) { if (it.width() > skip) { - v = it.min() + static_cast(skip); break; + v = static_cast(static_cast(it.min()) + skip); + break; } skip -= it.width(); ++it; } From 58cb2c676eb196b975b587a934cbd5ac05dcfc09 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 09:33:46 +0200 Subject: [PATCH 29/31] Document stability changelog and attributions Add release-note entries and file-header attribution for the clone, IntSet, MiniModel, fault-injection, and sanitizer-stability changes. PR 211 is kept as release-note issue metadata and as a reference for original patch authorship where that context matters. Reference: Gecode/gecode#211 --- changelog.in | 76 ++++++++++++++++++++++++++++++++ gecode/int.hh | 2 +- gecode/int/int-set-1.hpp | 4 ++ gecode/int/int-set.cpp | 6 +++ gecode/int/var-imp/delta.hpp | 4 ++ gecode/int/var-imp/int.cpp | 4 ++ gecode/int/var-imp/int.hpp | 2 + gecode/kernel/core.cpp | 6 +++ gecode/kernel/core.hpp | 6 ++- gecode/kernel/var-imp.hpp | 8 ++++ gecode/kernel/var-type.hpp | 8 ++++ gecode/minimodel.hh | 6 ++- gecode/minimodel/bool-expr.cpp | 6 +++ gecode/minimodel/int-arith.cpp | 6 +++ gecode/minimodel/int-expr.cpp | 6 +++ gecode/search.hh | 2 + gecode/search/par/bab.hpp | 4 ++ gecode/search/par/engine.hh | 4 ++ gecode/search/par/engine.hpp | 4 ++ gecode/search/par/path.hh | 4 ++ gecode/search/par/path.hpp | 4 ++ gecode/search/par/pbs.cpp | 4 ++ gecode/search/par/pbs.hh | 4 ++ gecode/search/par/pbs.hpp | 4 ++ gecode/search/seq/bab.hpp | 2 + gecode/search/seq/path.hh | 4 ++ gecode/search/seq/path.hpp | 4 ++ gecode/search/seq/rbs.cpp | 4 ++ gecode/search/seq/rbs.hh | 4 ++ gecode/search/seq/rbs.hpp | 4 ++ gecode/search/stop.cpp | 4 ++ gecode/search/stop.hpp | 4 ++ gecode/support.hh | 4 ++ gecode/support/config.hpp.in | 4 ++ gecode/support/failpoint.cpp | 4 +- gecode/support/failpoint.hpp | 4 +- gecode/support/heap.hpp | 4 ++ gecode/support/thread/thread.cpp | 4 ++ misc/genvarimp.py | 8 ++++ test/fault.cpp | 4 +- test/int.cpp | 2 +- test/int/mm-lin.cpp | 4 ++ 42 files changed, 246 insertions(+), 10 deletions(-) diff --git a/changelog.in b/changelog.in index 3daea2feb3..654285e075 100755 --- a/changelog.in +++ b/changelog.in @@ -75,6 +75,82 @@ This release modernizes the Gecode build infrastructure, adds a first-class CMake package for downstream consumers, refreshes the autoconf build path, and updates CI coverage for current platforms. +[ENTRY] +Module: kernel +What: bug +Rank: major +Issue: 211 +Thanks: Kris Coester and Alexander Shepil +[DESCRIPTION] +Make failed space cloning transactional. If copying actors, advisors, +local objects, variable implementations, or clone disposal metadata +throws, Gecode now restores the source space and releases the partial +clone without leaving forwarding links, actor lists, or dispose notices +in an inconsistent state. + +[ENTRY] +Module: kernel +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Harden dispose-notice registration against allocation failure. Actors +are no longer disposed by the space when an AP_DISPOSE registration +fails before ownership has been transferred to the dispose-notice list. + +[ENTRY] +Module: int +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Fix exception safety in IntSet object allocation. Partially constructed +IntSet objects now initialize their cleanup state before allocating +range storage and release the object if range allocation fails. + +[ENTRY] +Module: int +What: bug +Rank: minor +[DESCRIPTION] +Avoid signed integer overflow when computing integer-domain widths and +holes. Range width calculations now use unsigned arithmetic after +conversion, including full-domain integer ranges. + +[ENTRY] +Module: minimodel +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Fix MiniModel expression ownership during allocation failure. Boolean +misc-expression nodes and non-linear integer expression nodes now clean +up partially transferred objects and internal expression arrays while +preserving the public default LinIntExpr zero-expression invariant. + +[ENTRY] +Module: search +What: bug +Rank: major +[DESCRIPTION] +Fix sanitizer-detected search issues, including branch-and-bound +recomputation undefined behavior, parallel search command and stop-state +races, PBS slave lifetime hazards, restart-stop state races, stop-object +limit races, and shared no-good accounting races. + +[ENTRY] +Module: test +What: new +Rank: minor +[DESCRIPTION] +Add opt-in deterministic fault injection and sanitizer build support. +The new fault-injection tests exercise clone, dispose-notice, IntSet, +MiniModel, and heap-allocation failure paths that are otherwise hard to +reproduce reliably. + [ENTRY] Module: other What: change diff --git a/gecode/int.hh b/gecode/int.hh index b4dd6173a8..2a4f7c1ac0 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -12,7 +12,7 @@ * * Copyright: * Stefano Gualandi, 2013 - * Mikael Zayenz Lagerkvist, 2006 + * Mikael Zayenz Lagerkvist, 2006, 2026 * David Rijsman, 2009 * Christian Schulte, 2002 * Guido Tack, 2004 diff --git a/gecode/int/int-set-1.hpp b/gecode/int/int-set-1.hpp index 9d5f4cf89e..5aac66ec8a 100755 --- a/gecode/int/int-set-1.hpp +++ b/gecode/int/int-set-1.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index fdfa4f2369..50794e4238 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/int/var-imp/delta.hpp b/gecode/int/var-imp/delta.hpp index 4d8fa3a2ee..980108b684 100644 --- a/gecode/int/var-imp/delta.hpp +++ b/gecode/int/var-imp/delta.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/int/var-imp/int.cpp b/gecode/int/var-imp/int.cpp index f6555323e6..bce2b3ad49 100644 --- a/gecode/int/var-imp/int.cpp +++ b/gecode/int/var-imp/int.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2002 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/int/var-imp/int.hpp b/gecode/int/var-imp/int.hpp index f71ac683fd..dcadd00d48 100644 --- a/gecode/int/var-imp/int.hpp +++ b/gecode/int/var-imp/int.hpp @@ -5,10 +5,12 @@ * * Contributing authors: * Guido Tack + * Mikael Zayenz Lagerkvist * * Copyright: * Christian Schulte, 2003 * Guido Tack, 2004 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index f22b16cdec..80e099edc1 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -4,10 +4,16 @@ * Christian Schulte * * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist * Samuel Gagnon * * Copyright: * Christian Schulte, 2002 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * Samuel Gagnon, 2018 * * This file is part of Gecode, the generic constraint diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 0e462f92f8..0d9253d7ca 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -7,12 +7,16 @@ * * Contributing authors: * Filip Konvicka + * Kris Coester + * Alexander Shepil * Samuel Gagnon * * Copyright: * Christian Schulte, 2002 * Guido Tack, 2003 - * Mikael Zayenz Lagerkvist, 2006 + * Mikael Zayenz Lagerkvist, 2006, 2026 + * Kris Coester, 2024 + * Alexander Shepil, 2024 * LOGIS, s.r.o., 2009 * Samuel Gagnon, 2018 * diff --git a/gecode/kernel/var-imp.hpp b/gecode/kernel/var-imp.hpp index 10479ba8a6..2627433193 100644 --- a/gecode/kernel/var-imp.hpp +++ b/gecode/kernel/var-imp.hpp @@ -13,8 +13,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: diff --git a/gecode/kernel/var-type.hpp b/gecode/kernel/var-type.hpp index 497095e0bb..5768ea5ea8 100644 --- a/gecode/kernel/var-type.hpp +++ b/gecode/kernel/var-type.hpp @@ -13,8 +13,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 468ccd2c9d..51f039f38c 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -7,11 +7,15 @@ * Mikael Zayenz Lagerkvist * Vincent Barichard * + * Contributing authors: + * Alexander Shepil + * * Copyright: * Christian Schulte, 2004 * Fraunhofer ITWM, 2017 * Guido Tack, 2004 - * Mikael Zayenz Lagerkvist, 2005 + * Mikael Zayenz Lagerkvist, 2005, 2026 + * Alexander Shepil, 2024 * Vincent Barichard, 2012 * * This file is part of Gecode, the generic constraint diff --git a/gecode/minimodel/bool-expr.cpp b/gecode/minimodel/bool-expr.cpp index a9432a72ef..9a6195910e 100755 --- a/gecode/minimodel/bool-expr.cpp +++ b/gecode/minimodel/bool-expr.cpp @@ -5,9 +5,15 @@ * Christian Schulte * Vincent Barichard * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2004 * Christian Schulte, 2004 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * Vincent Barichard, 2012 * * This file is part of Gecode, the generic constraint diff --git a/gecode/minimodel/int-arith.cpp b/gecode/minimodel/int-arith.cpp index 507fd51b28..989e4d4084 100755 --- a/gecode/minimodel/int-arith.cpp +++ b/gecode/minimodel/int-arith.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index c91fb36444..558d8dc22e 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2010 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search.hh b/gecode/search.hh index fa97479249..2602fde680 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -6,11 +6,13 @@ * * Contributing authors: * Kevin Leo + * Mikael Zayenz Lagerkvist * Maxim Shishmarev * * Copyright: * Kevin Leo, 2017 * Christian Schulte, 2002 + * Mikael Zayenz Lagerkvist, 2026 * Maxim Shishmarev, 2017 * Guido Tack, 2004 * diff --git a/gecode/search/par/bab.hpp b/gecode/search/par/bab.hpp index 7246ea856d..8e6d3f047d 100755 --- a/gecode/search/par/bab.hpp +++ b/gecode/search/par/bab.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/engine.hh b/gecode/search/par/engine.hh index 6342de960d..e3b59077be 100644 --- a/gecode/search/par/engine.hh +++ b/gecode/search/par/engine.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/engine.hpp b/gecode/search/par/engine.hpp index 105ad8a94d..8ff5ed66e9 100644 --- a/gecode/search/par/engine.hpp +++ b/gecode/search/par/engine.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/path.hh b/gecode/search/par/path.hh index 14759ed4f3..476825fd84 100644 --- a/gecode/search/par/path.hh +++ b/gecode/search/par/path.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/path.hpp b/gecode/search/par/path.hpp index 82922ec672..d027e2feb4 100644 --- a/gecode/search/par/path.hpp +++ b/gecode/search/par/path.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/pbs.cpp b/gecode/search/par/pbs.cpp index ee86e3e899..a05deeeda1 100644 --- a/gecode/search/par/pbs.cpp +++ b/gecode/search/par/pbs.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/pbs.hh b/gecode/search/par/pbs.hh index 5a2f6a36e9..561969bbe6 100644 --- a/gecode/search/par/pbs.hh +++ b/gecode/search/par/pbs.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/par/pbs.hpp b/gecode/search/par/pbs.hpp index 7f4bec9d78..39c18d44a2 100755 --- a/gecode/search/par/pbs.hpp +++ b/gecode/search/par/pbs.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/bab.hpp b/gecode/search/seq/bab.hpp index 97523ae07a..3138fd371e 100644 --- a/gecode/search/seq/bab.hpp +++ b/gecode/search/seq/bab.hpp @@ -5,10 +5,12 @@ * * Contributing authors: * Guido Tack + * Mikael Zayenz Lagerkvist * * Copyright: * Christian Schulte, 2004 * Guido Tack, 2004 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/path.hh b/gecode/search/seq/path.hh index 36c43015f2..18c21022ea 100644 --- a/gecode/search/seq/path.hh +++ b/gecode/search/seq/path.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/path.hpp b/gecode/search/seq/path.hpp index f4445adc8c..4a4cddd157 100644 --- a/gecode/search/seq/path.hpp +++ b/gecode/search/seq/path.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/rbs.cpp b/gecode/search/seq/rbs.cpp index 7f01b82687..033c525b1d 100755 --- a/gecode/search/seq/rbs.cpp +++ b/gecode/search/seq/rbs.cpp @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/rbs.hh b/gecode/search/seq/rbs.hh index 311bf355e0..6b506200de 100755 --- a/gecode/search/seq/rbs.hh +++ b/gecode/search/seq/rbs.hh @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/seq/rbs.hpp b/gecode/search/seq/rbs.hpp index c1ce0ec4ec..c55e4e28b5 100755 --- a/gecode/search/seq/rbs.hpp +++ b/gecode/search/seq/rbs.hpp @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/stop.cpp b/gecode/search/stop.cpp index 1b60c65b2f..859e62ae72 100755 --- a/gecode/search/stop.cpp +++ b/gecode/search/stop.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/search/stop.hpp b/gecode/search/stop.hpp index 742a78fda9..12c1170fa0 100755 --- a/gecode/search/stop.hpp +++ b/gecode/search/stop.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support.hh b/gecode/support.hh index a4ce3a1730..79f4894608 100644 --- a/gecode/support.hh +++ b/gecode/support.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support/config.hpp.in b/gecode/support/config.hpp.in index df8070abc3..1298f113f9 100644 --- a/gecode/support/config.hpp.in +++ b/gecode/support/config.hpp.in @@ -2,8 +2,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support/failpoint.cpp b/gecode/support/failpoint.cpp index 1b475fc1a8..2a5ac355c0 100644 --- a/gecode/support/failpoint.cpp +++ b/gecode/support/failpoint.cpp @@ -1,10 +1,10 @@ /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: - * Christian Schulte + * Mikael Zayenz Lagerkvist * * Copyright: - * Christian Schulte, 2026 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support/failpoint.hpp b/gecode/support/failpoint.hpp index 28cf888510..f4ab33bb54 100644 --- a/gecode/support/failpoint.hpp +++ b/gecode/support/failpoint.hpp @@ -1,10 +1,10 @@ /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: - * Christian Schulte + * Mikael Zayenz Lagerkvist * * Copyright: - * Christian Schulte, 2026 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support/heap.hpp b/gecode/support/heap.hpp index c26205c0ce..10721f19e4 100644 --- a/gecode/support/heap.hpp +++ b/gecode/support/heap.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/gecode/support/thread/thread.cpp b/gecode/support/thread/thread.cpp index 4b726671aa..2869aaa20e 100644 --- a/gecode/support/thread/thread.cpp +++ b/gecode/support/thread/thread.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/misc/genvarimp.py b/misc/genvarimp.py index 5ca3be0d83..f642d8ad25 100644 --- a/misc/genvarimp.py +++ b/misc/genvarimp.py @@ -23,8 +23,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: diff --git a/test/fault.cpp b/test/fault.cpp index e88058a524..6adad8f6b6 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -1,10 +1,10 @@ /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: - * Christian Schulte + * Mikael Zayenz Lagerkvist * * Copyright: - * Christian Schulte, 2026 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/test/int.cpp b/test/int.cpp index 82c57e4192..949652866d 100755 --- a/test/int.cpp +++ b/test/int.cpp @@ -6,7 +6,7 @@ * * Copyright: * Christian Schulte, 2005 - * Mikael Zayenz Lagerkvist, 2005 + * Mikael Zayenz Lagerkvist, 2005, 2026 * * This file is part of Gecode, the generic constraint * development environment: diff --git a/test/int/mm-lin.cpp b/test/int/mm-lin.cpp index e5d7544b0a..0ce04056b8 100755 --- a/test/int/mm-lin.cpp +++ b/test/int/mm-lin.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: From abfd50028f7a460c6e0ab74717e61bcc34b46f9a Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:58:25 +0200 Subject: [PATCH 30/31] Use Gecode-style clone recovery names --- gecode/kernel/core.cpp | 4 ++-- gecode/kernel/core.hpp | 18 ++++++++---------- gecode/kernel/var-imp.hpp | 14 +++++++------- misc/genvarimp.py | 8 ++++---- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 80e099edc1..c49471676f 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -186,7 +186,7 @@ namespace Gecode { void Space::ap_ignore_dispose(Actor* a, bool duplicate) { // Note that a might be a marked pointer! - if (inPrematureDestructionMode()) + if (is_partial_clone()) return; assert(d_fst != nullptr); @@ -207,7 +207,7 @@ namespace Gecode { } Space::~Space(void) { - if (inPrematureDestructionMode()) { + if (is_partial_clone()) { if (pc.c.source != nullptr) { recover(*pc.c.source); pc.c.source = nullptr; diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 0d9253d7ca..4e275489b5 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -304,8 +304,7 @@ namespace Gecode { */ static void update(Space& home, ActorLink**& sub); /// Recover copied variables after failed clone construction - static void revertHarmfulChangesOfUnfinishedClone(Space& home, - ActorLink**& sub); + static void recover(Space& home, ActorLink**& sub); /// Enter propagator to subscription array void enter(Space& home, Propagator* p, PropCond pc); @@ -1904,14 +1903,14 @@ namespace Gecode { void update(ActorLink** sub); //@} - /// Update variables without indexing structure - void updateNoIdx(Space* space, bool recover); + /// Recover variables without indexing structure + void recover_noidx(void); /// Recover after failed clone construction void recover(Space& source); /// Test whether clone construction has not reached a disposable state - bool inPrematureDestructionMode(void) const; + bool is_partial_clone(void) const; /// First actor for forced disposal Actor** d_fst; @@ -3063,7 +3062,7 @@ namespace Gecode { #endif forceinline bool - Space::inPrematureDestructionMode(void) const { + Space::is_partial_clone(void) const { return d_fst == &Actor::sentinel; } @@ -4585,7 +4584,7 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Propagator& p, PropCond pc) { - if ((b.base != nullptr) && !home.inPrematureDestructionMode()) + if ((b.base != nullptr) && !home.is_partial_clone()) remove(home,&p,pc); } @@ -4615,7 +4614,7 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Advisor& a, bool fail) { - if ((b.base != nullptr) && !home.inPrematureDestructionMode()) { + if ((b.base != nullptr) && !home.is_partial_clone()) { ActorLink* ma = static_cast (Support::ptrjoin(Advisor::link(a),fail ? 1 : 0)); remove(home,ma); @@ -4808,8 +4807,7 @@ namespace Gecode { template forceinline void - VarImp::revertHarmfulChangesOfUnfinishedClone(Space& home, - ActorLink**&) { + VarImp::recover(Space& home, ActorLink**&) { VarImp* x = static_cast*>(home.pc.c.vars_u[idx_c]); while (x != nullptr) { if (x->copied()) { diff --git a/gecode/kernel/var-imp.hpp b/gecode/kernel/var-imp.hpp index 2627433193..8f4e120524 100644 --- a/gecode/kernel/var-imp.hpp +++ b/gecode/kernel/var-imp.hpp @@ -487,9 +487,9 @@ namespace Gecode { namespace Float { namespace Gecode { forceinline void - Space::updateNoIdx(Space* space, bool) { + Space::recover_noidx(void) { VarImp* x = - static_cast*>(space->pc.c.vars_noidx); + static_cast*>(pc.c.vars_noidx); while (x != nullptr) { VarImp* n = x->next(); x->b.base = nullptr; x->u.idx[0] = 0; @@ -564,18 +564,18 @@ namespace Gecode { } ActorLink** sub = static_cast(mm.subscriptions()); - Space::updateNoIdx(this, true); + recover_noidx(); #ifdef GECODE_HAS_INT_VARS - Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); + Gecode::VarImp::recover(*this,sub); #endif #ifdef GECODE_HAS_INT_VARS - Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); + Gecode::VarImp::recover(*this,sub); #endif #ifdef GECODE_HAS_SET_VARS - Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); + Gecode::VarImp::recover(*this,sub); #endif #ifdef GECODE_HAS_FLOAT_VARS - Gecode::VarImp::revertHarmfulChangesOfUnfinishedClone(*this,sub); + Gecode::VarImp::recover(*this,sub); #endif } } diff --git a/misc/genvarimp.py b/misc/genvarimp.py index f642d8ad25..a26e607638 100644 --- a/misc/genvarimp.py +++ b/misc/genvarimp.py @@ -864,9 +864,9 @@ def generate_header(files: List[Dict], out: List[str]) -> None: """namespace Gecode { forceinline void - Space::updateNoIdx(Space* space, bool) { + Space::recover_noidx(void) { VarImp* x = - static_cast*>(space->pc.c.vars_noidx); + static_cast*>(pc.c.vars_noidx); while (x != nullptr) { VarImp* n = x->next(); x->b.base = nullptr; x->u.idx[0] = 0; @@ -938,13 +938,13 @@ def generate_header(files: List[Dict], out: List[str]) -> None: } ActorLink** sub = static_cast(mm.subscriptions()); - Space::updateNoIdx(this, true); + recover_noidx(); """ ) for d in files: out.append(d["ifdef"]) - out.append(f" {d['base']}::revertHarmfulChangesOfUnfinishedClone(*this,sub);\n") + out.append(f" {d['base']}::recover(*this,sub);\n") out.append(d["endif"]) out.append( From a0dbbd40991bed48993f2d34565801a5c66be36c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 16:54:23 +0200 Subject: [PATCH 31/31] Make fault-injection counters thread-safe Use relaxed atomics for the fault-injection live-allocation counters used by IntSet and MiniModel leak checks. These counters are diagnostics only, but fault-enabled TSan search runs can construct and destroy the instrumented objects concurrently. --- gecode/int.hh | 6 +++++- gecode/int/int-set.cpp | 10 +++++----- gecode/minimodel.hh | 4 ++++ gecode/minimodel/int-expr.cpp | 12 ++++++------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/gecode/int.hh b/gecode/int.hh index 2a4f7c1ac0..301eb0ddc4 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -49,6 +49,10 @@ #include #include +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif + #include #include #include @@ -190,7 +194,7 @@ namespace Gecode { Range* r; #ifdef GECODE_HAS_FAULT_INJECTION /// Number of live objects for fault-injection tests - static int fault_live_objects; + static std::atomic fault_live_objects; /// Memory management for fault-injection accounting static void* operator new(size_t s); /// Memory management for fault-injection accounting diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index 50794e4238..9dc4d92fef 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -42,29 +42,29 @@ namespace Gecode { #ifdef GECODE_HAS_FAULT_INJECTION - int IntSet::IntSetObject::fault_live_objects = 0; + std::atomic IntSet::IntSetObject::fault_live_objects{0}; void* IntSet::IntSetObject::operator new(size_t s) { void* p = ::operator new(s); - fault_live_objects++; + fault_live_objects.fetch_add(1, std::memory_order_relaxed); return p; } void IntSet::IntSetObject::operator delete(void* p) { - fault_live_objects--; + fault_live_objects.fetch_sub(1, std::memory_order_relaxed); ::operator delete(p); } void IntSet::fault_reset_allocations(void) { - IntSetObject::fault_live_objects = 0; + IntSetObject::fault_live_objects.store(0, std::memory_order_relaxed); } int IntSet::fault_live_allocations(void) { - return IntSetObject::fault_live_objects; + return IntSetObject::fault_live_objects.load(std::memory_order_relaxed); } #endif diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 51f039f38c..8c73825d7c 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -55,6 +55,10 @@ #include #endif +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif + #include /* diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index 558d8dc22e..9f5786887a 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -91,7 +91,7 @@ namespace Gecode { static void operator delete(void* p,size_t size); #ifdef GECODE_HAS_FAULT_INJECTION /// Number of live nodes for fault-injection tests - static int fault_live_nodes; + static std::atomic fault_live_nodes; #endif }; @@ -104,16 +104,16 @@ namespace Gecode { } #ifdef GECODE_HAS_FAULT_INJECTION - int LinIntExpr::Node::fault_live_nodes = 0; + std::atomic LinIntExpr::Node::fault_live_nodes{0}; void LinIntExpr::fault_reset_allocations(void) { - Node::fault_live_nodes = 0; + Node::fault_live_nodes.store(0, std::memory_order_relaxed); } int LinIntExpr::fault_live_allocations(void) { - return Node::fault_live_nodes; + return Node::fault_live_nodes.load(std::memory_order_relaxed); } #endif @@ -142,7 +142,7 @@ namespace Gecode { #endif void* p = heap.ralloc(size); #ifdef GECODE_HAS_FAULT_INJECTION - fault_live_nodes++; + fault_live_nodes.fetch_add(1, std::memory_order_relaxed); #endif return p; } @@ -150,7 +150,7 @@ namespace Gecode { forceinline void LinIntExpr::Node::operator delete(void* p, size_t) { #ifdef GECODE_HAS_FAULT_INJECTION - fault_live_nodes--; + fault_live_nodes.fetch_sub(1, std::memory_order_relaxed); #endif heap.rfree(p); }