Ships: Fix crash when doing expedition between adjacent harbor places#1895
Ships: Fix crash when doing expedition between adjacent harbor places#1895Noseey wants to merge 11 commits intoReturn-To-The-Roots:masterfrom
Conversation
…Route to avoid crashes for lost and stuck ships.
.. assuming we have already arrived in case of DriveToHarbourPlace and ContinueExpedition. Consider a route check to be false in case it is empty.
Flamefire
left a comment
There was a problem hiding this comment.
Will look into adding a test, generally I think this works but might miss the notification, see comment. It might look odd though: Player clicks "next" and immediately receives a notification about a waiting ship.
But I guess that's ok and rate enough. Better than missing it and having a ship idle.
Or what do you think?
About the style: Compare those 3:
if (foo)
return x;
else
bar():
return y;
if (foo)
return x;
else {
bar():
return y;
}
if (foo)
return x;
bar():
return y;
I find the last one the clearest.
libs/s25main/nodeObjs/noShip.cpp
Outdated
There was a problem hiding this comment.
This check exists already at the top of the function, which got me confused.
I think the only situation would be a route recalculation by one of the 2 FindShipPathToHarbor
So either put a route_.empty() check there after curRouteIdx = 0; or something like:
| // Already arrived? (e. g. adjacent harbor) | |
| if(curRouteIdx == route_.size()) | |
| return Result::GoalReached; | |
| else | |
| StartDriving(route_[curRouteIdx++]); | |
| return Result::Driving; | |
| // FindShipPathToHarbor above found we arrived already? | |
| if(route_.empty()) | |
| return Result::GoalReached; | |
| RTTR_Assert(curRouteIdx < route_.size(); | |
| StartDriving(route_[curRouteIdx++]); | |
| return Result::Driving; |
(the else was also misleading as it can be omitted after return and looked like return Result::Driving; would always be executed)
There was a problem hiding this comment.
This check exists already at the top of the function, which got me confused.
Yes, in the end it is just another "are we there yet?" check , just after the route recalculations. I found this to be more in line with the rest of the code and since curRouteIdx is set to 0 in these cases, it would also work for empty routes in my opinion. But I can change it back to an check against it being empty.
About the else, I agree, that was not necessary.
There was a problem hiding this comment.
My issue as I was reading the function was: This check already exists, why do it again? And what is it about adjacent harbor? DriveToHarbourPlace isn't only called from a harbor so without the background knowledge of this issue it might be confusing.
Only after checking all uses of route_ I remembered that FindShipPathToHarbor changes it's parameter and wanted to keep that knowledge.
Maybe:
| // Already arrived? (e. g. adjacent harbor) | |
| if(curRouteIdx == route_.size()) | |
| return Result::GoalReached; | |
| else | |
| StartDriving(route_[curRouteIdx++]); | |
| return Result::Driving; | |
| // Already arrived after possibly recalculating route (FindShipPathToHarbor) | |
| if(curRouteIdx == route_.size()) | |
| return Result::GoalReached; | |
| RTTR_Assert(curRouteIdx < route_.size(); | |
| StartDriving(route_[curRouteIdx++]); | |
| return Result::Driving; |
The other idea was to have the check local to where it matters: Above
-curRouteIdx = 0;
+curRouteIdx = 0;
+if(route_.empty())
+ return Result::GoalReached;And then have only RTTR_Assert(curRouteIdx < route_.size(); here
There was a problem hiding this comment.
I probably haven't taken into account the history of the example map when adding this additional check. For the already stuck ships (due to them driving regardless the access to routes_ was OOB), the curRouteIdx was 1.
With a proper check in place in noShip::ContinueExpedition, this should no longer be possible. I would say, just to be save, we change the check on the top of this function to >= if(curRouteIdx >= route_.size()) and remove the additional check at the bottom. What do you say?
There was a problem hiding this comment.
just to be save
When curRouteIdx > route_.size() we already had an OOB and things are likely damaged already. So that situation cannot happen in a state we can recover from anyways.
It most I'd add an RTTR_Assert(curRouteIdx < route_size()) when == failed, i.e. not returned
So while I appreciate being careful, accounting for a situation that already caused fatal issues is a bit to much for me.
libs/s25main/nodeObjs/noShip.cpp
Outdated
There was a problem hiding this comment.
Misleading indentation, and check can be against "empty"
And we don't need to set the state (see check at top)
| if(curRouteIdx == route_.size()) | |
| { | |
| state = State::ExpeditionWaiting; | |
| return; | |
| } else | |
| state = State::ExpeditionDriving; | |
| StartDriving(route_[curRouteIdx++]); | |
| if(!route_.empty()) | |
| { | |
| state = State::ExpeditionDriving; | |
| StartDriving(route_[curRouteIdx++]); | |
| } |
There was a problem hiding this comment.
I also think we need to handle the "arrived" as an event to e.g. notify the player. Maybe simply:
| if(curRouteIdx == route_.size()) | |
| { | |
| state = State::ExpeditionWaiting; | |
| return; | |
| } else | |
| state = State::ExpeditionDriving; | |
| StartDriving(route_[curRouteIdx++]); | |
| state = State::ExpeditionDriving; | |
| HandleState_ExpeditionDriving(); |
There was a problem hiding this comment.
I think you misunderstood: Simply replace the original StartDriving by HandleState_ExpeditionDriving without checking the route-index/size
This is already handled by HandleState_ExpeditionDriving so we can avoid the work here which is also very clean: Advance the state and call the associated state handler instead of duplicating some of its code
There was a problem hiding this comment.
Ah, I see, so that the check is not needed here either.
There was a problem hiding this comment.
Not sure where else this is used but I'd say an empty route is valid: It just doesn't change the current point, i.e. *dest = pos
Or would that cause issues anywhere?
There was a problem hiding this comment.
Apart from the DriveToHarbourPlace, it seems to be also used in two functions for checking the Trade Route, being TradeRoute::GetNextDir (which checks the path route size before calling the function) and TradePathCache::pathExists, which I need check further.
There was a problem hiding this comment.
Just checked loosely and found:
s25client/libs/s25main/world/TradeRoute.cpp
Lines 38 to 39 in 6e12273
So when this returns true we expect route[pos] to be valid, which would not be the case when route is empty.
However RTTR_Assert(pos < route.size()); already assumes we have a valid index, as a precondition. And indeed we cannot have pos >= size and hence empty is never true, see the other discussion about handling situations after OOB access.
Hence we can remove this and ensure our assumption/assertion holds.
|
Amazing how after all the changes to the changes and discussion the fix is a single line 😄 I'll try to write a test in the next couple days to cover this. Did you try the original save/replay? |
Sounds good!
|
Related Issue:
Closes #1784
Changes Summary
This fixes a crash when expeditions are done between harbor places that are directly adjacent to another (e. g. on autogenerated maps), so the ship doesn't need to move at all for reaching them and thus its
route_is empty. For more details, see related issue.Note:
@Flamefire: Like discussed, could you add also a test for this if possible?