Merge lp:~widelands-dev/widelands/bug-1636966-segfault-in-battle into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 8367
Proposed branch: lp:~widelands-dev/widelands/bug-1636966-segfault-in-battle
Merge into: lp:widelands
Diff against target: 131 lines (+23/-19)
3 files modified
src/logic/map_objects/tribes/battle.cc (+10/-9)
src/logic/map_objects/tribes/battle.h (+4/-4)
src/logic/map_objects/tribes/soldier.cc (+9/-6)
To merge this branch: bzr merge lp:~widelands-dev/widelands/bug-1636966-segfault-in-battle
Reviewer Review Type Date Requested Status
Widelands Developers Pending
Review via email: mp+324365@code.launchpad.net

Commit message

Assume that we have no battle if the opponent is nullptr. Added some assertions. And a bit of refactoring.

Description of the change

After going through the code, I am making the following assumption: the opponent has died.

These are the commands sent by Battle when a soldier dies:

    soldier.start_task_die(game);
    molog("[battle] waking up winner %d\n", opponent(soldier)->serial());
    opponent(soldier)->send_signal(game, "wakeup");
    return schedule_destroy(game);

The wakeup signal then ends up leading to the part in the code that crashes. The wakeup is supposed to happen 10 ticks after the Battle is destroyed, but I guess that with very low frametime, this can get its wires crossed. This would also explain the rarity of the crash. I haven't dug really deeply into this part though.

There is already a comment in the code that running the command queue depends on frametime and that it's a bad idea.

To post a comment you must log in.
Revision history for this message
Notabilis (notabilis27) wrote :

I think your argumentation makes sense and it is quite likely that the game was lagging when I encountered the bug. I wasn't able to force the bug by fighting with a low framerate, though.
Diff looks good, too.

When I read the code right, the soldier dies / is deleted after 1 second gametime while the opponent receives the signal after 10 ticks. Seems kind of strange to use two "time"scales in the game, maybe someone should try to clean this up at some time.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2225. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/234398380.
Appveyor build 2060. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_1636966_segfault_in_battle-2060.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review :)

@bunnybot merge

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2229. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/234503157.
Appveyor build 2064. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_1636966_segfault_in_battle-2064.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/logic/map_objects/tribes/battle.cc'
2--- src/logic/map_objects/tribes/battle.cc 2017-04-23 12:11:19 +0000
3+++ src/logic/map_objects/tribes/battle.cc 2017-05-21 09:25:30 +0000
4@@ -53,23 +53,23 @@
5 last_attack_hits_(false) {
6 }
7
8-Battle::Battle(Game& game, Soldier& First, Soldier& Second)
9+Battle::Battle(Game& game, Soldier* first_soldier, Soldier* second_soldier)
10 : MapObject(&g_battle_descr),
11- first_(&First),
12- second_(&Second),
13+ first_(first_soldier),
14+ second_(second_soldier),
15 readyflags_(0),
16 damage_(0),
17 first_strikes_(true) {
18- assert(First.get_owner() != Second.get_owner());
19+ assert(first_soldier->get_owner() != second_soldier->get_owner());
20 {
21 StreamWrite& ss = game.syncstream();
22 ss.unsigned_32(0x00e111ba); // appears as ba111e00 in a hexdump
23- ss.unsigned_32(First.serial());
24- ss.unsigned_32(Second.serial());
25+ ss.unsigned_32(first_soldier->serial());
26+ ss.unsigned_32(second_soldier->serial());
27 }
28
29- // Ensures only live soldiers eganges in a battle
30- assert(First.get_current_health() && Second.get_current_health());
31+ // Ensures only live soldiers engage in a battle
32+ assert(first_soldier->get_current_health() && second_soldier->get_current_health());
33
34 init(game);
35 }
36@@ -129,7 +129,8 @@
37 return first_->get_position() == second_->get_position();
38 }
39
40-Soldier* Battle::opponent(Soldier& soldier) {
41+Soldier* Battle::opponent(const Soldier& soldier) const {
42+ assert(&soldier != nullptr);
43 assert(first_ == &soldier || second_ == &soldier);
44 Soldier* other_soldier = first_ == &soldier ? second_ : first_;
45 return other_soldier;
46
47=== modified file 'src/logic/map_objects/tribes/battle.h'
48--- src/logic/map_objects/tribes/battle.h 2017-04-23 12:11:19 +0000
49+++ src/logic/map_objects/tribes/battle.h 2017-05-21 09:25:30 +0000
50@@ -49,7 +49,7 @@
51 const BattleDescr& descr() const;
52
53 Battle(); // for loading an existing battle from a savegame
54- Battle(Game&, Soldier&, Soldier&); // to create a new battle in the game
55+ Battle(Game&, Soldier*, Soldier*); // to create a new battle in the game
56
57 // Implements MapObject.
58 bool init(EditorGameBase&) override;
59@@ -75,9 +75,9 @@
60 }
61
62 // Returns the other soldier involved in this battle. CHECKs that the given
63- // soldier is participating in this battle. Can return nullptr, but I have
64- // no idea what that means.
65- Soldier* opponent(Soldier&);
66+ // soldier is participating in this battle. Can return nullptr, probably when the
67+ // opponent has died.
68+ Soldier* opponent(const Soldier&) const;
69
70 // Called by the battling soldiers once they've met on a common node and are
71 // idle.
72
73=== modified file 'src/logic/map_objects/tribes/soldier.cc'
74--- src/logic/map_objects/tribes/soldier.cc 2017-05-13 18:48:26 +0000
75+++ src/logic/map_objects/tribes/soldier.cc 2017-05-21 09:25:30 +0000
76@@ -1047,7 +1047,8 @@
77 for (Bob* temp_bob : soldiers) {
78 if (upcast(Soldier, temp_soldier, temp_bob)) {
79 if (temp_soldier->can_be_challenged()) {
80- new Battle(game, *this, *temp_soldier);
81+ assert(temp_soldier != nullptr);
82+ new Battle(game, this, temp_soldier);
83 return start_task_battle(game);
84 }
85 }
86@@ -1134,7 +1135,8 @@
87
88 if (target.dist <= 1) {
89 molog("[defense] starting battle with %u!\n", target.s->serial());
90- new Battle(game, *this, *(target.s));
91+ assert(target.s != nullptr);
92+ new Battle(game, this, target.s);
93 return start_task_battle(game);
94 }
95
96@@ -1257,7 +1259,8 @@
97 }
98 }
99
100- if (!battle_) {
101+ // The opponent might have died on us
102+ if (!battle_ || battle_->opponent(*this) == nullptr) {
103 if (combat_walking_ == CD_COMBAT_W) {
104 return start_task_move_in_battle(game, CD_RETURN_W);
105 }
106@@ -1287,7 +1290,7 @@
107 if (stay_home()) {
108 if (this == battle_->first()) {
109 molog("[battle] stay_home, so reverse roles\n");
110- new Battle(game, *battle_->second(), *battle_->first());
111+ new Battle(game, battle_->second(), battle_->first());
112 return skip_act(); // we will get a signal via set_battle()
113 } else {
114 if (combat_walking_ != CD_COMBAT_E) {
115@@ -1298,7 +1301,7 @@
116 } else {
117 if (opponent.stay_home() && (this == battle_->second())) {
118 // Wait until correct roles are assigned
119- new Battle(game, *battle_->second(), *battle_->first());
120+ new Battle(game, battle_->second(), battle_->first());
121 return schedule_act(game, 10);
122 }
123
124@@ -1488,7 +1491,7 @@
125 if (commit && !foundbattle && !multiplesoldiers) {
126 if (foundsoldier->owner().is_hostile(*get_owner()) && foundsoldier->can_be_challenged()) {
127 molog("[check_node_blocked] attacking a soldier (%u)\n", foundsoldier->serial());
128- new Battle(game, *this, *foundsoldier);
129+ new Battle(game, this, foundsoldier);
130 }
131 }
132

Subscribers

People subscribed via source and target branches

to status/vote changes: