Merge lp:~widelands-dev/widelands/bug-1388028 into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 7240
Proposed branch: lp:~widelands-dev/widelands/bug-1388028
Merge into: lp:widelands
Diff against target: 62 lines (+24/-12)
1 file modified
src/ai/defaultai.cc (+24/-12)
To merge this branch: bzr merge lp:~widelands-dev/widelands/bug-1388028
Reviewer Review Type Date Requested Status
TiborB Pending
Review via email: mp+240357@code.launchpad.net

Description of the change

Fixed division by zero in AI that can come up when loading a savegame.

@Tibor: could you please make sure that this is still intended behaviour for the AI and remove the NOCOM comments when you're done?

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

GunChleoc - this is not about zero division but about out-of-range - I think.

Do you think that problem is that genstats is not populated some very short time after loading and AI tries to access members that are not there yet? So that genstats.size()==0?

If this is the case I would use other aproach then try-catch...

Also see comments in diff, default decision for AI if it can not get data on military strength of enemy should be 'do not attack'

Revision history for this message
GunChleoc (gunchleoc) wrote :

I added the try/catch because I was suspecting holes in the genstats
array, but the hole is in miltary_strength. So, the important point here
is to check for military_strength.empty() - if miltary_strength.back()
gives us nothing, we do get a division by 0. The try/catch isn't that
important really, so if you think it's not necessary, we can revert to
using genstats[j - 1] rather than player_attackable.at(j - 1). I just
thought it can't hurt to leave it in.

01/11/2014 20:09, sgrìobh TiborB:
> GunChleoc - this is not about zero division but about out-of-range - I think.
>
> Do you think that problem is that genstats is not populated some very short time after loading and AI tries to access members that are not there yet? So that genstats.size()==0?
>
> If this is the case I would use other aproach then try-catch...
>
> Also see comments in diff, default decision for AI if it can not get data on military strength of enemy should be 'do not attack'
>
>
>
>
> Diff comments:
>
>> === modified file 'src/ai/defaultai.cc'
>> --- src/ai/defaultai.cc 2014-10-31 20:49:02 +0000
>> +++ src/ai/defaultai.cc 2014-11-01 14:10:33 +0000
>> @@ -698,7 +698,7 @@
>> }
>> }
>>
>> - // folowing is done allways (regardless of military or not)
>> + // the following is done always (regardless of military or not)
>>
>> // we get immovables with higher radius
>> immovables.clear();
>> @@ -710,6 +710,7 @@
>> field.military_presence_ = 0;
>>
>> for (uint32_t i = 0; i < immovables.size(); ++i) {
>> +
>> const BaseImmovable& base_immovable = *immovables.at(i).object;
>>
>> // testing if it is enemy-owned field
>> @@ -3089,20 +3090,27 @@
>> const Game::GeneralStatsVector& genstats = game().get_general_statistics();
>> for (uint8_t j = 1; j <= plr_in_game; ++j) {
>> if (pn == j) {
>> - player_attackable[j - 1] = false;
>> + player_attackable.at(j - 1) = false;
>> continue;
>> }
>>
>> - if (genstats[j - 1].miltary_strength.back() == 0) {
>> - // to avoid improbable zero division
>> - player_attackable[j - 1] = true;
>> - any_attackable = true;
>> - } else if ((genstats[pn - 1].miltary_strength.back() * 100 /
>> - genstats[j - 1].miltary_strength.back()) > treshold_ratio) {
>> - player_attackable[j - 1] = true;
>> - any_attackable = true;
>> - } else {
>> - player_attackable[j - 1] = false;
>> + try {
>> + // Avoid division by zero
>> + if (genstats.at(j - 1).miltary_strength.empty() ||
>> + genstats.at(j - 1).miltary_strength.back() == 0) {
>> + player_attackable.at(j - 1) = true;
>> + any_attackable = true;
>> + // Check threshold
>> + } else if ((genstats.at(pn - 1).miltary_strength.back() * 100 /
>> + genstats.at(j - 1).miltary_strength.back()) > treshold_ratio) {
>> + player_attackable.at(j - 1) = true;
>> + any_attackable = true;
>> + } else {
>> + player_attackable.at(j - 1) = false;
>> + }
>> + } catch (const std::out_of_range&) {
>> + player_attackable.at(j - 1) = true;
>
> change to false
>
>> + any_attackable = true;
>
> remove the line
>
>> }
>> }
>>
>>
>
>

Revision history for this message
TiborB (tiborb95) wrote :

from http://www.cplusplus.com/reference/vector/vector/back/

Calling this function on an empty container causes undefined behavior.

But what is more interesting is that if really the genstats or miltary_strength can be empty during a small period of time after loading.

Also, it can be empty during first 60 seconds of new game...

I somehow dont like that 'try' just because it can hide other problems

I will think about it and rework it...

Revision history for this message
TiborB (tiborb95) wrote :

What do you think?

Revision history for this message
TiborB (tiborb95) wrote :

Also, I tested the savegame, I got:

ComputerPlayer(5): initializing (2)
ComputerPlayer(5): miltary_strength empty
ComputerPlayer(5): miltary_strength empty
ComputerPlayer(5): miltary_strength empty
ComputerPlayer(6): initializing (2)
ComputerPlayer(6): miltary_strength empty
ComputerPlayer(6): miltary_strength empty
ComputerPlayer(6): miltary_strength empty
ComputerPlayer(7): initializing (2)

but the game keeps running :)

Revision history for this message
GunChleoc (gunchleoc) wrote :

Do we really need the

if (genstats.size()<j) {

As I said, the try/catch was just something I added while searching, and the real fix for this particular crash was the check for

genstats.at(j - 1).miltary_strength.empty())

I don't mind either way though. I expect that these cases will mostly come up when loading a savegame, so if the AI decided to attack a bit later because initialization isn't ready, no harm done.

Revision history for this message
TiborB (tiborb95) wrote :

I have no idea if a case genstats.size()==0 can ever happen, if samebody guarantees that it cannot happen ever, we can omit it. But this way we will be on a safe side.

Can AI know that saving or loading of a game is in progress?

Revision history for this message
GunChleoc (gunchleoc) wrote :

I don't know. Let's stay on the safe side and use the exception in combination with your log output and defaults.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/ai/defaultai.cc'
2--- src/ai/defaultai.cc 2014-10-31 20:49:02 +0000
3+++ src/ai/defaultai.cc 2014-11-02 09:15:33 +0000
4@@ -698,7 +698,7 @@
5 }
6 }
7
8- // folowing is done allways (regardless of military or not)
9+ // the following is done always (regardless of military or not)
10
11 // we get immovables with higher radius
12 immovables.clear();
13@@ -710,6 +710,7 @@
14 field.military_presence_ = 0;
15
16 for (uint32_t i = 0; i < immovables.size(); ++i) {
17+
18 const BaseImmovable& base_immovable = *immovables.at(i).object;
19
20 // testing if it is enemy-owned field
21@@ -3089,20 +3090,31 @@
22 const Game::GeneralStatsVector& genstats = game().get_general_statistics();
23 for (uint8_t j = 1; j <= plr_in_game; ++j) {
24 if (pn == j) {
25- player_attackable[j - 1] = false;
26+ player_attackable.at(j - 1) = false;
27 continue;
28 }
29
30- if (genstats[j - 1].miltary_strength.back() == 0) {
31- // to avoid improbable zero division
32- player_attackable[j - 1] = true;
33- any_attackable = true;
34- } else if ((genstats[pn - 1].miltary_strength.back() * 100 /
35- genstats[j - 1].miltary_strength.back()) > treshold_ratio) {
36- player_attackable[j - 1] = true;
37- any_attackable = true;
38- } else {
39- player_attackable[j - 1] = false;
40+ try {
41+ // It seems that under some circumstances genstats can be empty.
42+ // So, to avoid crash, the AI tests its content first.
43+ if (genstats.at(j - 1).miltary_strength.empty()) {
44+ log("ComputerPlayer(%d): miltary_strength is empty\n", player_number());
45+ player_attackable.at(j - 1) = false;
46+ // Avoid division by zero
47+ } else if ( genstats.at(j - 1).miltary_strength.back() == 0) {
48+ player_attackable.at(j - 1) = true;
49+ any_attackable = true;
50+ // Check threshold
51+ } else if ((genstats.at(pn - 1).miltary_strength.back() * 100 /
52+ genstats.at(j - 1).miltary_strength.back()) > treshold_ratio) {
53+ player_attackable.at(j - 1) = true;
54+ any_attackable = true;
55+ } else {
56+ player_attackable.at(j - 1) = false;
57+ }
58+ } catch (const std::out_of_range&) {
59+ log("ComputerPlayer(%d): genstats entry missing - size :%d\n", player_number(), genstats.size());
60+ player_attackable.at(j - 1) = false;
61 }
62 }
63

Subscribers

People subscribed via source and target branches

to status/vote changes: