Skip to content

Empty Viewpoint Set Causes Out-of-Bounds Access and Repeated Replanning #52

Description

@wong-772

1. Problem Description

RACER assumes that every grid selected in the grid tour contains at least one valid frontier and viewpoint.

However, a grid can still be considered explorable when it contains enough unknown cells, even if it contains no frontier:

return grid.unknown_num_ >= min_unknown_
    || !grid.contained_frontier_ids_.empty();

Therefore, a grid may be added to the grid tour while having:

contained_frontier_ids_ = empty

Without a frontier, no viewpoint can be generated. The candidate position and yaw arrays are consequently empty.

The planner does not check whether these arrays contain valid candidates before selecting the next viewpoint. This may result in an out-of-bounds access, invalid target coordinates, trajectory generation toward an unintended position, and repeated planning failures.

The problem is further amplified because a failed planning attempt does not change the FSM state, skip the invalid grid, introduce a delay, or limit the number of retries. The planner may therefore repeatedly attempt to plan toward the same invalid target.


2. Code Analysis

The viewpoint selection logic initializes the candidate index to -1:

int min_cost_id = -1;

for (int i = 0; i < ed_->points_.size(); ++i) {
  ...
  min_cost_id = i;
}

next_pos = ed_->points_[min_cost_id];
next_yaw = ed_->yaws_[min_cost_id];

When ed_->points_ is empty, the loop is never executed and min_cost_id remains -1.

The subsequent accesses are therefore equivalent to:

ed_->points_[-1];
ed_->yaws_[-1];

This is undefined behavior in C++. Instead of immediately crashing, the process may read residual memory values and interpret them as the next position and yaw.

If next_pos and next_yaw are only declared without explicit initialization, their values may also remain undefined when viewpoint selection fails.

The planning failure branch currently only records the failure:

if (res == FAIL) {
  fd_->static_state_ = true;
  ROS_WARN("Plan fail");
}

It does not:

  • switch to IDLE or another recovery state;
  • remove or skip the invalid grid;
  • wait before retrying;
  • limit consecutive retries.

As a result, the same invalid grid can trigger continuous replanning.


3. Log Analysis

In Round 156, the relevant runtime state was:

grid_ids = [29]
frontier_ids = []
ed_->points_ = []
min_cost_id = -1

Grid 29 was allocated even though it contained no frontier and produced no valid viewpoint.

The following log was repeatedly observed:

Allocated grid: 29
Find frontier tour, 0 involved
Next view: 4.77649e-308 7.17238e-308 3.87842e-321

The reported coordinates are close to the lower range of double-precision floating-point values and are not valid map coordinates. They are consistent with values obtained from invalid or uninitialized memory.

This invalid Next view message appeared 765 times.

Round 156 recorded 1,156 replanning attempts in total, of which UAV 4 triggered 879. This indicates that UAV 4 repeatedly retried planning for the same invalid grid after the initial failure.

The failure sequence was:

Grid 29 selected
→ no frontier found
→ no viewpoint generated
→ min_cost_id remains -1
→ invalid position and yaw are accessed
→ trajectory planning fails
→ FSM state remains unchanged
→ replanning is immediately triggered again

4. Suggested Fix

Before accessing the viewpoint arrays, verify that valid candidates exist and that both arrays have consistent sizes:

if (ed_->points_.empty() || ed_->yaws_.empty()) {
  ROS_WARN("No valid viewpoint is available for the allocated grid.");
  return FAIL;
}

if (ed_->points_.size() != ed_->yaws_.size()) {
  ROS_ERROR("Inconsistent viewpoint position and yaw arrays.");
  return FAIL;
}

The selected index should also be validated before use:

if (min_cost_id < 0 ||
    min_cost_id >= static_cast<int>(ed_->points_.size()) ||
    min_cost_id >= static_cast<int>(ed_->yaws_.size())) {
  ROS_ERROR("Invalid viewpoint index: %d", min_cost_id);
  return FAIL;
}

When a grid has no valid viewpoint, the planner should skip or temporarily disable that grid and trigger task reassignment or wait for a map update.

The planning failure branch should also include a recovery strategy, such as a retry limit, retry delay, or transition to IDLE, to prevent an infinite replanning loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions