Skip to content

Fix out-of-bounds write when reusing a cached xfunction shape - #2945

Open
gouarin wants to merge 1 commit into
xtensor-stack:masterfrom
gouarin:fix-xfunction-cache-rank
Open

gouarin wants to merge 1 commit into
xtensor-stack:masterfrom
gouarin:fix-xfunction-cache-rank

Conversation

@gouarin

@gouarin gouarin commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Checklist

  • The title and commit message(s) are descriptive.
  • Small commits made to fix your PR have been squashed to avoid history pollution.
  • Tests have been added for new features or bug fixes.
  • API of new functions and classes are documented (no public API changes).

Description

xfunction::broadcast_shape copied its cached shape into the output shape without checking the rank. Assigning a rank-2 expression to an xt::xtensor<T, 1> should throw, but once the shape of the xfunction has been cached, it writes past the std::array<std::size_t, 1>:

xt::xarray<double> a = {{1., 2., 3.}, {4., 5., 6.}};
auto f = a + 1.0;
f.shape();                  // fills the shape cache: {2, 3}
xt::xtensor<double, 1> res;
res = f;                    // copies {2, 3} into a 1-element std::array

Without the f.shape() call, the cache is empty and the same assignment throws broadcast_error. With a larger dynamic shape, the cached path returns {2, 3, MAX} where the uncached one returns {MAX, 2, 3}.

The cache is now used only when both ranks match. Other ranks go through the full broadcast, which already reports incompatible ranks.

xfunction.broadcast_shape_cached_other_rank covers both cases. On master, the higher-rank subcase gets a shape that differs from the uncached result, and the lower-rank subcase triggers a stack-buffer-overflow under AddressSanitizer. Both pass with the fix.

xfunction::broadcast_shape copied its cached shape into the output shape
without checking that both have the same rank. Assigning a rank-2
xfunction whose shape was already computed to an xt::xtensor<T, 1> wrote
past the std::array<std::size_t, 1> (stack-buffer-overflow under ASan).
With a larger dynamic output shape, the cached path returned a different
shape than the uncached one.

The cache is now used only when both ranks match. Otherwise the full
broadcast runs and reports incompatible ranks, as it already did when the
cache was empty.
@codspeed

codspeed Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 18.92%

2 warnings

⚠️ Unable to measure the execution of 1 benchmark

This benchmark measured no execution time, so its result is not comparable and counts as unchanged. This usually happens when the compiler removes the code under test.

Prevent compiler optimizations

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 254 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ xshape_access[xt\:\:svector<std\:\:size_t, 4>] 125 ns 154.2 ns -18.92%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing gouarin:fix-xfunction-cache-rank (45c6193) with master (57f3d07)

Open in CodSpeed

This branch has not been deployed

No deployments
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