Skip to content

Commit 9d28e17

Browse files
authored
Correct null pointer dereference in setting a null outcome (#666)
This fixes a regression introduced when moving to using `std::shared_ptr` to handle reference-counting for game objects. Fixes #625 and #647.
1 parent 68d73e7 commit 9d28e17

6 files changed

Lines changed: 27 additions & 19 deletions

File tree

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
extensive form.
1010
- Fixed a regression in the GUI in which unique action labels were not being generated when
1111
adding a move via drag-and-drop of a player icon (#618)
12+
- Fixed a regression generating null pointer dereference errors when setting the outcome of
13+
a node to the null outcome (#625, #647)
1214

1315

1416
## [16.3.2] - unreleased

src/games/game.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,10 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
775775
throw UndefinedException();
776776
}
777777
virtual void DeleteAction(GameAction) { throw UndefinedException(); }
778-
virtual void SetOutcome(GameNode, const GameOutcome &p_outcome) { throw UndefinedException(); }
778+
virtual void SetOutcome(const GameNode &p_node, const GameOutcome &p_outcome)
779+
{
780+
throw UndefinedException();
781+
}
779782

780783
/// @name Dimensions of the game
781784
//@{

src/games/gameobject.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,10 @@ template <class T> class GameObjectPtr {
9696
/// game element held by this object and throws an exception if the object is the
9797
/// null object, or is no longer valid (has been removed from the game)
9898
///
99-
/// @exception NullException if the object holds a reference to a null element
10099
/// @exception InvalidObjectException if the element referred to has been deleted from its game
101100
std::shared_ptr<T> get_shared() const
102101
{
103-
if (!m_rep) {
104-
throw NullException();
105-
}
106-
if (!m_rep->IsValid()) {
102+
if (m_rep && !m_rep->IsValid()) {
107103
throw InvalidObjectException();
108104
}
109105
return m_rep;
@@ -129,16 +125,16 @@ template <class T> class GameObjectPtr {
129125
}
130126

131127
bool operator==(const GameObjectPtr<T> &r) const { return (m_rep == r.m_rep); }
132-
bool operator==(const std::shared_ptr<T> r) const { return (m_rep == r); }
133-
bool operator==(const std::shared_ptr<const T> r) const { return (m_rep == r); }
134-
bool operator==(const std::nullptr_t) const { return !bool(m_rep); }
128+
bool operator==(const std::shared_ptr<T> &r) const { return (m_rep == r); }
129+
bool operator==(const std::shared_ptr<const T> &r) const { return (m_rep == r); }
130+
bool operator==(const std::nullptr_t &) const { return m_rep == nullptr; }
135131
bool operator!=(const GameObjectPtr<T> &r) const { return (m_rep != r.m_rep); }
136-
bool operator!=(const std::shared_ptr<T> r) const { return (m_rep != r); }
137-
bool operator!=(const std::shared_ptr<const T> r) const { return (m_rep != r); }
138-
bool operator!=(const std::nullptr_t) const { return bool(m_rep); }
132+
bool operator!=(const std::shared_ptr<T> &r) const { return (m_rep != r); }
133+
bool operator!=(const std::shared_ptr<const T> &r) const { return (m_rep != r); }
134+
bool operator!=(const std::nullptr_t &) const { return m_rep != nullptr; }
139135
bool operator<(const GameObjectPtr<T> &r) const { return (m_rep < r.m_rep); }
140136

141-
operator bool() const noexcept { return bool(m_rep); }
137+
operator bool() const noexcept { return m_rep != nullptr; }
142138
operator std::shared_ptr<T>() const { return m_rep; }
143139
};
144140

src/games/gametree.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,17 +334,17 @@ void GameNodeRep::DeleteOutcome(GameOutcomeRep *outc)
334334
}
335335
}
336336

337-
void GameTreeRep::SetOutcome(GameNode p_node, const GameOutcome &p_outcome)
337+
void GameTreeRep::SetOutcome(const GameNode &p_node, const GameOutcome &p_outcome)
338338
{
339-
IncrementVersion();
340339
if (p_node->m_game != this) {
341340
throw MismatchException();
342341
}
343342
if (p_outcome && p_outcome->m_game != this) {
344343
throw MismatchException();
345344
}
346-
if (p_outcome.get() != p_node->m_outcome) {
347-
p_node->m_outcome = p_outcome.get();
345+
if (const auto newOutcome = p_outcome.get_shared().get(); newOutcome != p_node->m_outcome) {
346+
p_node->m_outcome = newOutcome;
347+
IncrementVersion();
348348
ClearComputedValues();
349349
}
350350
}

src/games/gametree.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class GameTreeRep : public GameExplicitRep {
137137
Game SetChanceProbs(const GameInfoset &, const Array<Number> &) override;
138138
GameAction InsertAction(GameInfoset, GameAction p_where = nullptr) override;
139139
void DeleteAction(GameAction) override;
140-
void SetOutcome(GameNode, const GameOutcome &p_outcome) override;
140+
void SetOutcome(const GameNode &p_node, const GameOutcome &p_outcome) override;
141141

142142
std::vector<GameNode> GetPlays(GameNode node) const override;
143143
std::vector<GameNode> GetPlays(GameInfoset infoset) const override;

tests/test_node.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ def test_get_infoset():
1919
def test_get_outcome():
2020
"""Test to ensure that we can retrieve an outcome for a given node"""
2121
game = games.read_from_file("basic_extensive_game.efg")
22-
assert game.root.children[0].children[1].children[0].outcome == game.outcomes[1]
22+
assert game.root.children[0].children[1].children[0].outcome == game.outcomes["Outcome 1"]
2323
assert game.root.outcome is None
2424

2525

26+
def test_set_outcome_null():
27+
"""Test to set an outcome to the null outcome."""
28+
game = games.read_from_file("basic_extensive_game.efg")
29+
game.set_outcome(game.root.children[0].children[0].children[0], None)
30+
assert game.root.children[0].children[0].children[0].outcome is None
31+
32+
2633
def test_get_player():
2734
"""Test to ensure that we can retrieve a player for a given node"""
2835
game = games.read_from_file("basic_extensive_game.efg")

0 commit comments

Comments
 (0)