Skip to content

Commit 89d36dd

Browse files
committed
Extra-strict iterator implementation for debugging
1 parent c7293cc commit 89d36dd

File tree

1 file changed

+93
-20
lines changed

1 file changed

+93
-20
lines changed

src/games/game.h

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -967,71 +967,122 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
967967
virtual void BuildComputedValues() const {}
968968
};
969969

970+
#include <assert.h>
971+
970972
class GameRep::Infosets {
971973
public:
972974
class iterator {
973975
public:
974-
using iterator_category = std::input_iterator_tag;
976+
using iterator_category = std::forward_iterator_tag; // diagnostic: proper category
975977
using value_type = GameInfoset;
976978
using difference_type = std::ptrdiff_t;
977-
using pointer = GameInfoset;
978-
using reference = GameInfoset;
979+
using pointer = value_type *;
980+
using reference = value_type &;
979981

980982
using outer_iter = std::vector<std::shared_ptr<GamePlayerRep>>::const_iterator;
981983
using inner_iter = std::vector<std::shared_ptr<GameInfosetRep>>::const_iterator;
982984

983-
iterator() = default;
985+
iterator() : owner_id_(0), players_(nullptr) {}
984986

985-
iterator(outer_iter outer, outer_iter outer_end) : outer_(outer), outer_end_(outer_end)
987+
iterator(outer_iter outer, outer_iter outer_end,
988+
const std::vector<std::shared_ptr<GamePlayerRep>> *players, std::size_t owner_id)
989+
: outer_(outer), outer_end_(outer_end), players_(players), owner_id_(owner_id)
986990
{
991+
// ------ DIAGNOSTICS ----------------------------------------------------
992+
993+
// Detect dangling players pointer
994+
assert(players_ != nullptr && "Infosets iterator constructed with null players pointer.");
995+
996+
// Detect begin()==end() but outer mismatch
997+
if (outer_ == outer_end_) {
998+
assert(inner_ == inner_end_ && "Inner iterators must be empty for end iterator.");
999+
}
1000+
9871001
if (outer_ != outer_end_) {
9881002
init_inner();
9891003
satisfy_invariant();
9901004
}
9911005
}
9921006

993-
GameInfoset operator*() const { return *inner_; }
994-
GameInfoset operator->() const { return *inner_; }
1007+
// -------------------------------------------------------------------------
1008+
// Dereference
1009+
// -------------------------------------------------------------------------
1010+
value_type operator*() const
1011+
{
1012+
assert(players_ != nullptr);
1013+
assert(!is_end() && "Dereferencing end iterator!");
1014+
1015+
// Check many possible UB causes
1016+
assert(inner_ != inner_end_ && "Inner iterator invalid during dereference.");
1017+
assert(valid_owner() &&
1018+
"Iterator used after view destruction or copying from different view.");
9951019

1020+
// Additional optional check: ensure GameInfoset is safe to construct
1021+
// You can instrument your GameInfoset(value_type) here to assert its invariants.
1022+
1023+
return *inner_;
1024+
}
1025+
1026+
value_type operator->() const { return operator*(); }
1027+
1028+
// -------------------------------------------------------------------------
1029+
// Increment
1030+
// -------------------------------------------------------------------------
9961031
iterator &operator++()
9971032
{
1033+
assert(players_ != nullptr);
1034+
assert(!is_end() && "Incrementing end iterator!");
1035+
9981036
++inner_;
9991037
satisfy_invariant();
10001038
return *this;
10011039
}
10021040

1003-
// -------- Windows/MSVC-safe equality ----------
1041+
// -------------------------------------------------------------------------
1042+
// Equality / Inequality
1043+
// -------------------------------------------------------------------------
10041044
bool operator==(const iterator &other) const
10051045
{
1006-
// Both are end() ⇒ equal
1046+
// Detect comparing iterators from different views
1047+
if (owner_id_ != other.owner_id_) {
1048+
assert(false && "Comparing iterators from different Infosets views!");
1049+
}
1050+
1051+
// Both end iterators must compare equal
10071052
if (is_end() && other.is_end()) {
10081053
return true;
10091054
}
10101055

1011-
// Otherwise, must match outer + inner (safe because both non-end)
1056+
// Detect illegal comparisons
1057+
assert(players_ == other.players_ &&
1058+
"Comparing iterators belonging to different container instances!");
1059+
10121060
return outer_ == other.outer_ && inner_ == other.inner_;
10131061
}
10141062

10151063
bool operator!=(const iterator &other) const { return !(*this == other); }
10161064

10171065
private:
1066+
// -------------------------------------------------------------------------
1067+
// Helpers
1068+
// -------------------------------------------------------------------------
10181069
bool is_end() const { return outer_ == outer_end_; }
10191070

1020-
// Safe initialization even when shared_ptr is null
1071+
bool valid_owner() const { return players_ != nullptr && owner_id_ != 0; }
1072+
1073+
// Initialize inner iter safely
10211074
void init_inner()
10221075
{
10231076
const auto &playerPtr = *outer_;
10241077

1025-
if (!playerPtr) {
1026-
// Null shared_ptr → no infosets
1027-
inner_ = inner_end_ = inner_iter{};
1028-
return;
1029-
}
1078+
// Check for null shared_ptr (VERY COMMON SOURCE OF UB)
1079+
assert(playerPtr && "Null shared_ptr<GamePlayerRep> inside m_players!");
10301080

10311081
inner_ = playerPtr->m_infosets.begin();
10321082
inner_end_ = playerPtr->m_infosets.end();
10331083
}
10341084

1085+
// Advance to next non-empty inner range
10351086
void satisfy_invariant()
10361087
{
10371088
while (!is_end() && inner_ == inner_end_) {
@@ -1042,23 +1093,45 @@ class GameRep::Infosets {
10421093
}
10431094
}
10441095

1096+
// -------------------------------------------------------------------------
1097+
// Data members
1098+
// -------------------------------------------------------------------------
10451099
outer_iter outer_{};
10461100
outer_iter outer_end_{};
10471101
inner_iter inner_{};
10481102
inner_iter inner_end_{};
1103+
1104+
const std::vector<std::shared_ptr<GamePlayerRep>> *players_ = nullptr;
1105+
1106+
// Unique stamp to detect view lifetime/dangling iterator
1107+
std::size_t owner_id_ = 0;
10491108
};
10501109

1110+
// -------------------------------------------------------------------------
10511111
// View interface
1052-
iterator begin() const { return iterator(players_->begin(), players_->end()); }
1053-
iterator end() const { return iterator(players_->end(), players_->end()); }
1112+
// -------------------------------------------------------------------------
1113+
iterator begin() const
1114+
{
1115+
return iterator(players_->begin(), players_->end(), players_, owner_id_);
1116+
}
1117+
1118+
iterator end() const { return iterator(players_->end(), players_->end(), players_, owner_id_); }
10541119

1055-
explicit Infosets(const GameRep *game)
1056-
: players_(&game->m_players) // ✔ store pointer, not reference
1120+
explicit Infosets(const GameRep *game) : players_(&game->m_players)
10571121
{
1122+
assert(game != nullptr);
1123+
owner_id_ = next_owner_id(); // unique ID for detecting illegal comparisons
10581124
}
10591125

10601126
private:
10611127
const std::vector<std::shared_ptr<GamePlayerRep>> *players_;
1128+
std::size_t owner_id_;
1129+
1130+
static std::size_t next_owner_id()
1131+
{
1132+
static std::size_t id = 1;
1133+
return id++;
1134+
}
10621135
};
10631136

10641137
inline GameRep::Infosets GameRep::GetInfosets() const { return Infosets(this); }

0 commit comments

Comments
 (0)