Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
204f399
Add sanitizer and fault injection test support
May 23, 2026
de81725
Add disposal-array clone fault regression
May 23, 2026
486a2aa
Recover clone state on disposal-array failure
May 23, 2026
6e13017
Add dispose-notice allocation fault regression
May 23, 2026
a507282
Dispose actor when dispose-notice allocation fails
May 23, 2026
a9f65dd
Add IntSet allocation fault regression
May 23, 2026
000f249
Release IntSet object on range allocation failure
May 23, 2026
b5c341a
Add MiniModel default-node allocation fault regression
May 23, 2026
dba74f0
Release partial MiniModel arithmetic expressions on failure
May 23, 2026
dbbf433
Add BoolExpr misc allocation fault regression
May 24, 2026
33c55bb
Release BoolExpr misc object on node allocation failure
May 24, 2026
70e60a8
Recover unfinished clone destruction
May 24, 2026
bbd65f5
Avoid typed casts for marked advisor links
May 24, 2026
bfef1fc
Add LinIntExpr default API regression
May 24, 2026
a97a34d
Restore default LinIntExpr node invariant
May 24, 2026
3507e84
Recover clone disposal failures for any exception
May 24, 2026
3944e19
Add AP_DISPOSE registration failure regression
May 24, 2026
84c5910
Avoid disposing unregistered AP_DISPOSE actors
May 24, 2026
70f0956
Serialize fault injection tests
May 24, 2026
46e3647
Keep clone recovery non-virtual
May 24, 2026
c1a30de
Wire heap fault phase coverage
May 24, 2026
63ad86e
Expand heap fault coverage
May 25, 2026
9624ab1
Cover BoolExpr heap failure ownership
May 25, 2026
c7086f2
Fix clone and MiniModel fault-injection gaps
Jun 28, 2026
1a996d3
Fix BAB recomputation UBSan issue
Jun 28, 2026
fc1a6d2
Fix parallel search sanitizer races
Jun 28, 2026
f05e7f2
Fix parallel PBS slave lifetime
Jun 28, 2026
3bd9ea9
Fix large sanitizer findings
Jun 28, 2026
58cb2c6
Document stability changelog and attributions
Jul 6, 2026
abfd500
Use Gecode-style clone recovery names
Jul 6, 2026
a0dbbd4
Make fault-injection counters thread-safe
Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
76 changes: 76 additions & 0 deletions changelog.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion gecode/int.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,6 +49,10 @@
#include <climits>
#include <cfloat>

#ifdef GECODE_HAS_FAULT_INJECTION
#include <atomic>
#endif

#include <iostream>
#include <vector>
#include <unordered_map>
Expand Down Expand Up @@ -188,6 +192,14 @@ namespace Gecode {
int n;
/// Array of ranges
Range* r;
#ifdef GECODE_HAS_FAULT_INJECTION
/// Number of live objects for fault-injection tests
static std::atomic<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
Expand All @@ -208,6 +220,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
Expand Down
14 changes: 10 additions & 4 deletions gecode/int/int-set-1.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
* Main authors:
* Christian Schulte <schulte@gecode.dev>
*
* Contributing authors:
* Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
*
* Copyright:
* Christian Schulte, 2003
* Mikael Zayenz Lagerkvist, 2026
*
* This file is part of Gecode, the generic constraint
* development environment:
Expand Down Expand Up @@ -164,7 +168,8 @@ namespace Gecode {
IntSet::width(int i) const {
assert(object() != nullptr);
IntSetObject* o = static_cast<IntSetObject*>(object());
return static_cast<unsigned int>(o->r[i].max-o->r[i].min)+1;
return static_cast<unsigned int>(o->r[i].max) -
static_cast<unsigned int>(o->r[i].min) + 1U;
}

forceinline int
Expand Down Expand Up @@ -203,7 +208,8 @@ namespace Gecode {
forceinline unsigned int
IntSet::width(void) const {
IntSetObject* o = static_cast<IntSetObject*>(object());
return (o == nullptr) ? 0U : static_cast<unsigned int>(max()-min()+1);
return (o == nullptr) ? 0U :
static_cast<unsigned int>(max()) - static_cast<unsigned int>(min()) + 1U;
}

forceinline bool
Expand Down Expand Up @@ -265,7 +271,8 @@ namespace Gecode {
}
forceinline unsigned int
IntSetRanges::width(void) const {
return static_cast<unsigned int>(i->max - i->min) + 1;
return static_cast<unsigned int>(i->max) -
static_cast<unsigned int>(i->min) + 1U;
}

/*
Expand Down Expand Up @@ -311,4 +318,3 @@ namespace Gecode {
}

// STATISTICS: int-var

55 changes: 50 additions & 5 deletions gecode/int/int-set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
* Main authors:
* Christian Schulte <schulte@gecode.dev>
*
* Contributing authors:
* Alexander Shepil <alexander.shepil@sap.com>
* Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
*
* Copyright:
* Christian Schulte, 2003
* Alexander Shepil, 2024
* Mikael Zayenz Lagerkvist, 2026
*
* This file is part of Gecode, the generic constraint
* development environment:
Expand Down Expand Up @@ -35,11 +41,49 @@

namespace Gecode {

#ifdef GECODE_HAS_FAULT_INJECTION
std::atomic<int> IntSet::IntSetObject::fault_live_objects{0};

void*
IntSet::IntSetObject::operator new(size_t s) {
void* p = ::operator new(s);
fault_live_objects.fetch_add(1, std::memory_order_relaxed);
return p;
}

void
IntSet::IntSetObject::operator delete(void* p) {
fault_live_objects.fetch_sub(1, std::memory_order_relaxed);
::operator delete(p);
}

void
IntSet::fault_reset_allocations(void) {
IntSetObject::fault_live_objects.store(0, std::memory_order_relaxed);
}

int
IntSet::fault_live_allocations(void) {
return IntSetObject::fault_live_objects.load(std::memory_order_relaxed);
}
#endif

IntSet::IntSetObject*
IntSet::IntSetObject::allocate(int n) {
IntSetObject* o = new IntSetObject;
o->size = 0U;
o->n = 0;
o->r = nullptr;
try {
#ifdef GECODE_HAS_FAULT_INJECTION
Support::FailPoint::check(Support::FailPoint::Phase::IntSet);
#endif
o->r = heap.alloc<Range>(n);
} catch (...) {
delete o;
throw;
}
o->n = n;
o->r = heap.alloc<Range>(n);
return o;
}

Expand Down Expand Up @@ -73,7 +117,8 @@ namespace Gecode {
}

IntSet::IntSetObject::~IntSetObject(void) {
heap.free<Range>(r,n);
if (r != nullptr)
heap.free<Range>(r,n);
}

/// Sort ranges according to increasing minimum
Expand Down Expand Up @@ -115,7 +160,8 @@ namespace Gecode {
IntSetObject* o = IntSetObject::allocate(n);
unsigned int s = 0;
for (int i=0; i<n; i++) {
s += static_cast<unsigned int>(r[i].max-r[i].min+1);
s += static_cast<unsigned int>(r[i].max) -
static_cast<unsigned int>(r[i].min) + 1U;
o->r[i]=r[i];
}
o->size = s;
Expand Down Expand Up @@ -178,7 +224,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<unsigned int>(m - n + 1);
o->size = static_cast<unsigned int>(m) - static_cast<unsigned int>(n) + 1U;
object(o);
}
}
Expand All @@ -188,4 +234,3 @@ namespace Gecode {
}

// STATISTICS: int-var

6 changes: 5 additions & 1 deletion gecode/int/var-imp/delta.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
* Main authors:
* Christian Schulte <schulte@gecode.dev>
*
* Contributing authors:
* Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
*
* Copyright:
* Christian Schulte, 2007
* Mikael Zayenz Lagerkvist, 2026
*
* This file is part of Gecode, the generic constraint
* development environment:
Expand Down Expand Up @@ -52,7 +56,7 @@ namespace Gecode { namespace Int {
}
forceinline unsigned int
IntDelta::width(void) const {
return static_cast<unsigned int>(_max - _min + 1);
return static_cast<unsigned int>(_max) - static_cast<unsigned int>(_min) + 1U;
}
forceinline bool
IntDelta::any(void) const {
Expand Down
Loading