Skip to content

grt: fix cugr memory crash on out-of-bounds grid access#9739

Open
Divinesoumyadip wants to merge 2 commits intoThe-OpenROAD-Project:masterfrom
Divinesoumyadip:fix-grt-only
Open

grt: fix cugr memory crash on out-of-bounds grid access#9739
Divinesoumyadip wants to merge 2 commits intoThe-OpenROAD-Project:masterfrom
Divinesoumyadip:fix-grt-only

Conversation

@Divinesoumyadip
Copy link
Contributor

This change resolves a critical Signal 6 (SIGABRT) crash in the CUGR global routing engine caused by a mismatch between physical design coordinates and the internally constructed virtual routing grid. In certain inconsistent designs, coordinates of pins, ports, or obstacles could fall outside the precomputed grid boundaries, leading to an out-of-bounds access in the routing grid data structures. The update introduces geometric boundary clamping to ensure safe projection of coordinates onto the grid and prevent memory access violations.

During initialization, the CUGR router constructs a discrete 3D routing structure called GridGraph, derived from the routing layers and track definitions specified in LEF files. Throughout routing, physical coordinates are projected onto this grid using the GridGraph::rangeSearchRows function.
In inconsistent configurations for example, when a design using a 13-layer metal stack is routed on a 6-layer grid, or when IO pins are located outside the defined core region,the coordinate-to-grid index calculation may generate indices larger than the size of the internal gridlines_ vector.

Because the implementation previously accessed the vector directly (gridlines_[dimension][index]) without validating the computed index, the C++ standard library detected the invalid access and triggered the assertion __n < this->size(). This resulted in an immediate SIGABRT and core dump, preventing the router from reporting which design element caused the failure.

Solution

This change introduces a defensive boundary-clamping mechanism inside GridGraph.cpp. Instead of using the raw projection indices, the logic now determines the valid bounds of the gridlines_ container and clamps the requested lookup indices into that range before performing any vector access.

This approach ensures that coordinate projections are always mapped to a valid grid boundary, eliminating the possibility of out-of-range memory access while allowing the router to continue execution and report the underlying design inconsistency.

Implementation

The implementation computes the maximum legal index (N-1) of the gridline vector and constrains the computed low and high indices using std::min and std::max. This guarantees that accesses to gridlines_ remain within valid memory boundaries.

// Safety-hardened logic to prevent SIGABRT
const int max_idx = std::max(0, (int)gridlines_[dimension].size() - 1);
const int l = std::min(lineRange.low(), max_idx);
const int h = std::min(lineRange.high(), max_idx);

// Safe access is now physically guaranteed by the clamp
return {gridlines_[dimension][l] == loc_interval.low() ? l : std::max(l - 1, 0),
        gridlines_[dimension][h] == loc_interval.high() ? h - 1 : std::min(h, getSize(dimension) - 1)};

By enforcing this boundary clamping, invalid coordinate projections are safely mapped to the nearest valid grid cell. Instead of causing a memory crash, the router can now continue execution and emit a handled diagnostic such as GRT-0286 (Failed to determine parent layer), which identifies the specific net responsible for the geometry mismatch.

The fix was validated using a stress-test design containing a 13-layer metal stack routed using a 6-layer routing grid.

Prior to this change, invoking global_route caused an immediate crash with Aborted (core dumped) during grid projection. After applying the fix, the router successfully completes grid initialization, proceeds into stage 1 pattern routing, and terminates with a clear diagnostic message identifying the problematic net.

The added std::min and std::max checks are constant-time (O(1)) operations and introduce no measurable overhead to the routing runtime.
This update hardens the GridGraph coordinate projection logic against out-of-range memory access. By replacing a fatal crash with safe boundary handling and actionable diagnostics, the change improves the robustness and reliability of the OpenROAD routing pipeline and ensures predictable behavior even when encountering inconsistent design data.

Signed-off-by: Divinesoumyadip <soumyacode7@gmail.com>
@github-actions
Copy link
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a critical crash caused by out-of-bounds memory access in the routing grid logic. The approach of clamping indices to valid boundaries is correct. However, I've identified a critical scenario where the new code can still crash if the gridlines_ vector is empty. Additionally, there's a high-severity issue where the logic for the upper bound of the returned interval can produce a negative index, which is inconsistent with the lower bound calculation. I've provided specific comments and code suggestions to address both of these issues to make the fix more robust.

Comment on lines 274 to +280
const IntervalT& loc_interval) const
{
if (dimension < 0 || dimension > 1) {
return {0, 0};
}
const auto& lineRange = rangeSearchGridlines(dimension, loc_interval);
return {gridlines_[dimension][lineRange.low()] == loc_interval.low()
? lineRange.low()
: std::max(lineRange.low() - 1, 0),
gridlines_[dimension][lineRange.high()] == loc_interval.high()
? lineRange.high() - 1
: std::min(lineRange.high(), getSize(dimension) - 1)};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

While the clamping logic prevents out-of-bounds access for non-empty gridlines_, it doesn't handle the case where gridlines_[dimension] is empty. In this scenario, rangeSearchGridlines returns {0, 0}, max_idx becomes 0, and l and h are set to 0. The subsequent access gridlines_[dimension][l] will then cause a crash, which this pull request aims to prevent.

An early exit for an empty gridline vector is needed. With this check, the std::max in the max_idx calculation also becomes redundant and can be simplified.

  if (dimension < 0 || dimension > 1) return {0, 0};
  if (gridlines_[dimension].empty()) {
    return {0, 0};
  }
  const auto& lineRange = rangeSearchGridlines(dimension, loc_interval);
  
  // BOUNDARY SHIELD: Safely clamp to array boundaries
  const int max_idx = (int)gridlines_[dimension].size() - 1;
  const int l = std::min(lineRange.low(), max_idx);
  const int h = std::min(lineRange.high(), max_idx);

const int l = std::min(lineRange.low(), max_idx);
const int h = std::min(lineRange.high(), max_idx);

return {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The calculation for the upper bound of the returned interval can result in a negative index. If h is 0 and gridlines_[dimension][h] == loc_interval.high(), the expression evaluates to h - 1, which is -1. This is inconsistent with the lower bound calculation, which uses std::max(..., 0) to prevent negative indices. A similar protection should be applied here to ensure a non-negative index is always returned.

              ? std::max(0, h - 1)

@github-actions
Copy link
Contributor

clang-tidy review says "All clean, LGTM! 👍"

1 similar comment
@github-actions
Copy link
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Signed-off-by: Divinesoumyadip <soumyacode7@gmail.com>
@github-actions
Copy link
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@maliberty maliberty requested a review from eder-matheus March 12, 2026 13:56
@Divinesoumyadip
Copy link
Contributor Author

Hi @eder-matheus ,I have addressed the edge cases mentioned by the code assist bot (added empty-vector checks and non-negative index clamping). This fix is ready for review and will prevent the SIGABRT crash in the CUGR engine for inconsistent designs. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant