Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmake/onnxruntime_unittests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
endif()
list(REMOVE_ITEM all_tests
"${TEST_SRC_DIR}/providers/cpu/reduction/reduction_ops_test.cc"
"${TEST_SRC_DIR}/providers/cpu/tensor/grid_sample_test.cc")
"${TEST_SRC_DIR}/providers/cpu/tensor/grid_sample_test.cc"
"${TEST_SRC_DIR}/providers/cpu/tensor/grid_sample_test_custom.cc")
Comment thread
yuslepukhin marked this conversation as resolved.
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR IOS)
Expand Down
73 changes: 69 additions & 4 deletions onnxruntime/core/providers/cpu/tensor/grid_sample.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include <algorithm>
#include <cmath>
#include <vector>

#include "core/common/safeint.h"
Expand Down Expand Up @@ -63,9 +65,16 @@ T GsReflect(T x, T x_min, T x_max) {
T dx = {};
T fx = static_cast<T>(x);
T range = x_max - x_min;
// Guard against NaN, Inf, or non-positive range (e.g. dim==1 with align_corners=true)
// which would otherwise produce wild indices via division by zero or UB float->int casts.
if (!std::isfinite(fx) || !(range > T{0})) {
return x_min;
}
if (fx < x_min) {
dx = x_min - fx;
int n = static_cast<int>(dx / range);
// Use int64_t rather than int: for extreme (but finite) inputs, dx / range can exceed
// INT_MAX, making a float->int cast undefined behavior.
int64_t n = static_cast<int64_t>(dx / range);
T r = dx - n * range;
if (n % 2 == 0) {
fx = x_min + r;
Expand All @@ -74,7 +83,7 @@ T GsReflect(T x, T x_min, T x_max) {
}
} else if (fx > x_max) {
dx = fx - x_max;
int n = static_cast<int>(dx / range);
int64_t n = static_cast<int64_t>(dx / range);
T r = dx - n * range;
if (n % 2 == 0) {
fx = x_max - r;
Expand All @@ -86,6 +95,15 @@ T GsReflect(T x, T x_min, T x_max) {
return static_cast<T>(fx);
}

// Returns true when v is finite and its magnitude is small enough that converting it to
// int64_t via std::floor / std::nearbyint is well-defined. 2^62 is far below INT64_MAX
// (~9.22e18) and leaves ample margin for any realistic image dimension.
template <typename T>
inline bool IsSafeForInt64Conversion(T v) {
constexpr T kSafeBound = static_cast<T>(int64_t{1} << 62);
return std::isfinite(v) && v >= -kSafeBound && v <= kSafeBound;
}

// Calculate cubic convolution interpolation coefficients
// ROBERT G. KEYS https://ieeexplore.ieee.org/document/1163711
template <typename T>
Expand Down Expand Up @@ -124,6 +142,11 @@ T GridSample<T>::PixelAtGrid(const T* image, int64_t r, int64_t c, int64_t H, in
} else { // (padding_mode_ == Reflection)
c = static_cast<int64_t>(GsReflect(static_cast<T>(c), border[0], border[2]));
r = static_cast<int64_t>(GsReflect(static_cast<T>(r), border[1], border[3]));
// Safety clamp: GsReflect is computed in floating point and casts back to int64_t.
// Extreme grid coordinates can overflow that cast, so clamp the resulting indices
// back into the image range before indexing.
c = std::clamp<int64_t>(c, 0, W - 1);
r = std::clamp<int64_t>(r, 0, H - 1);
pixel = image[r * W + c];
}
return pixel;
Expand All @@ -145,6 +168,12 @@ T GridSample<T>::PixelAtGrid3D(const T* image, int64_t d, int64_t h, int64_t w,
w = static_cast<int64_t>(GsReflect(static_cast<T>(w), border[0], border[3]));
h = static_cast<int64_t>(GsReflect(static_cast<T>(h), border[1], border[4]));
d = static_cast<int64_t>(GsReflect(static_cast<T>(d), border[2], border[5]));
// Safety clamp: GsReflect is computed in floating point and casts back to int64_t.
// Extreme grid coordinates can overflow that cast, so clamp the resulting indices
// back into the image range before indexing.
w = std::clamp<int64_t>(w, 0, W - 1);
h = std::clamp<int64_t>(h, 0, H - 1);
d = std::clamp<int64_t>(d, 0, D - 1);
pixel = image[d * H * W + h * W + w];
}
return pixel;
Expand Down Expand Up @@ -186,8 +215,19 @@ void PrecomputeBilinearSamplePlan2D(const T* grid_data,
auto& plan = plans[idx];
const T nx = grid_data[idx * 2];
const T ny = grid_data[idx * 2 + 1];
const T x = GsDenormalize<T>(nx, W_in, false);
const T y = GsDenormalize<T>(ny, H_in, false);
T x = GsDenormalize<T>(nx, W_in, false);
T y = GsDenormalize<T>(ny, H_in, false);

// Sanitize coordinates that are non-finite or whose magnitude is too large
// for a safe float->int64 conversion via std::floor. The fast path is only used
// for zeros padding without align_corners, so substituting the lower border (-0.5)
// produces an out-of-bounds floor index that the mask logic correctly rejects.
if (!IsSafeForInt64Conversion(x)) {
x = static_cast<T>(-0.5f);
}
if (!IsSafeForInt64Conversion(y)) {
y = static_cast<T>(-0.5f);
}

const int64_t x1 = static_cast<int64_t>(std::floor(x));
const int64_t y1 = static_cast<int64_t>(std::floor(y));
Expand Down Expand Up @@ -398,6 +438,17 @@ Status GridSample<T>::Compute(OpKernelContext* context) const {
auto x = GsDenormalize<T>(nx, W_in, align_corners_); // actual location
auto y = GsDenormalize<T>(ny, H_in, align_corners_);

// Sanitize coordinates that are non-finite or whose magnitude is too large
// for a safe float->int64 conversion via std::floor / std::nearbyint.
// Substituting the in-range border value keeps the subsequent casts
// well-defined while still producing a defined output for each padding mode.
if (!IsSafeForInt64Conversion(x)) {
x = x_min;
}
if (!IsSafeForInt64Conversion(y)) {
y = y_min;
}

if (mode_ == Nearest) {
x = static_cast<T>(std::nearbyint(static_cast<T>(x)));
y = static_cast<T>(std::nearbyint(static_cast<T>(y)));
Expand Down Expand Up @@ -496,6 +547,20 @@ Status GridSample<T>::Compute(OpKernelContext* context) const {
auto y = GsDenormalize<T>(ny, H_in, align_corners_);
auto z = GsDenormalize<T>(nz, D_in, align_corners_);

// Sanitize coordinates that are non-finite or whose magnitude is too large
// for a safe float->int64 conversion via std::floor / std::nearbyint.
// Substituting the in-range border value keeps the subsequent casts
// well-defined while still producing a defined output for each padding mode.
if (!IsSafeForInt64Conversion(x)) {
x = x_min;
}
if (!IsSafeForInt64Conversion(y)) {
y = y_min;
}
if (!IsSafeForInt64Conversion(z)) {
z = z_min;
}

if (mode_ == Nearest) {
x = static_cast<T>(std::nearbyint(static_cast<T>(x)));
y = static_cast<T>(std::nearbyint(static_cast<T>(y)));
Expand Down
4 changes: 2 additions & 2 deletions onnxruntime/test/providers/cpu/tensor/grid_sample_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ void RunTests(T& test, std::vector<std::unique_ptr<IExecutionProvider>>&& execut
execution_providers.clear();
}

// Custom tests not generated by grid_sample_test_gen.py.
#include "test/providers/cpu/tensor/grid_sample_test_custom.inc"
// Custom tests not generated by grid_sample_test_gen.py live in
// grid_sample_test_custom.cc.

// DO NOT edit following tests. They are generated by:
// onnxruntime/test/providers/cpu/tensor/grid_sample_test_gen.py
Expand Down
Loading
Loading