88
99#include < executorch/extension/data_loader/file_data_loader.h>
1010
11+ #include < atomic>
1112#include < cstring>
13+ #include < new>
1214
1315#include < gtest/gtest.h>
1416
@@ -25,6 +27,59 @@ using executorch::runtime::Error;
2527using executorch::runtime::FreeableBuffer;
2628using executorch::runtime::Result;
2729
30+ namespace {
31+ // When set, the replacement nothrow aligned operator new below returns nullptr,
32+ // simulating an allocation failure without needing a real OOM.
33+ std::atomic<bool > g_fail_aligned_nothrow_alloc{false };
34+
35+ // RAII guard to ensure flag is reset even if test asserts early.
36+ struct FailAllocGuard {
37+ FailAllocGuard () {
38+ g_fail_aligned_nothrow_alloc.store (true , std::memory_order_relaxed);
39+ }
40+ ~FailAllocGuard () {
41+ g_fail_aligned_nothrow_alloc.store (false , std::memory_order_relaxed);
42+ }
43+ };
44+ } // namespace
45+
46+ // Detect ASAN to avoid multiple definition link error and to skip test when
47+ // ASAN runtime provides its own strong operator new.
48+ #if defined(__SANITIZE_ADDRESS__) || \
49+ (defined (__has_feature) && __has_feature(address_sanitizer))
50+ #define ET_TEST_ASAN_ENABLED 1
51+ #else
52+ #define ET_TEST_ASAN_ENABLED 0
53+ #endif
54+
55+ #if !ET_TEST_ASAN_ENABLED
56+ // Replaces the global nothrow aligned allocation function for this test binary
57+ // so FileDataLoader's segment allocation can be made to fail on demand. When
58+ // the toggle is off it forwards to the real aligned allocator. We call the
59+ // throwing aligned new inside a try/catch and convert exceptions to nullptr
60+ // to emulate nothrow semantics without recursing into this same nothrow
61+ // overload (calling ::operator new(size, alignment, std::nothrow) here would
62+ // infinite-loop). Memory allocated here is released through the default
63+ // operator delete, which is not replaced.
64+ // This is a strong (non-weak) replacement so it reliably overrides libc++'s
65+ // default on all platforms (a weak definition loses to libc++'s own weak
66+ // definition on Apple's linker, leaving the override silently unused). Under
67+ // ASAN this whole block is excluded so it can't clash with ASAN's allocator.
68+ void * operator new (
69+ std::size_t size,
70+ std::align_val_t alignment,
71+ const std::nothrow_t & /* tag */ ) noexcept {
72+ if (g_fail_aligned_nothrow_alloc.load (std::memory_order_relaxed)) {
73+ return nullptr ;
74+ }
75+ try {
76+ return ::operator new (size, alignment);
77+ } catch (...) {
78+ return nullptr ;
79+ }
80+ }
81+ #endif // !ET_TEST_ASAN_ENABLED
82+
2883class FileDataLoaderTest : public ::testing::TestWithParam<size_t > {
2984 protected:
3085 void SetUp () override {
@@ -147,6 +202,46 @@ TEST_P(FileDataLoaderTest, OutOfBoundsLoadFails) {
147202 }
148203}
149204
205+ #if !ET_TEST_ASAN_ENABLED
206+ TEST_P (FileDataLoaderTest, AllocationFailureDuringLoadReturnsError) {
207+ // Create a temp file; contents don't matter.
208+ uint8_t data[256 ] = {};
209+ TempFile tf (data, sizeof (data));
210+
211+ Result<FileDataLoader> fdl =
212+ FileDataLoader::from (tf.path ().c_str (), alignment ());
213+ ASSERT_EQ (fdl.error (), Error::Ok);
214+
215+ // Force the segment allocation inside load() to fail. The loader must surface
216+ // Error::MemoryAllocationFailed rather than letting std::bad_alloc escape,
217+ // which would abort the process in the exception-free runtime.
218+ FailAllocGuard fail_guard;
219+ Result<FreeableBuffer> fb = fdl->load (
220+ /* offset=*/ 0 ,
221+ /* size=*/ sizeof (data),
222+ DataLoader::SegmentInfo (DataLoader::SegmentInfo::Type::Program));
223+
224+ EXPECT_EQ (fb.error (), Error::MemoryAllocationFailed);
225+ }
226+ #endif // !ET_TEST_ASAN_ENABLED
227+
228+ #if !ET_TEST_ASAN_ENABLED
229+ TEST_P (FileDataLoaderTest, AllocationFailureDuringFromReturnsError) {
230+ // Create a temp file; contents don't matter.
231+ uint8_t data[256 ] = {};
232+ TempFile tf (data, sizeof (data));
233+
234+ // Force the filename allocation inside from() to fail. FileDataLoader::from
235+ // copies the filename using et_aligned_alloc and must return
236+ // Error::MemoryAllocationFailed on nullptr rather than throwing.
237+ FailAllocGuard fail_guard;
238+ Result<FileDataLoader> fdl =
239+ FileDataLoader::from (tf.path ().c_str (), alignment ());
240+
241+ EXPECT_EQ (fdl.error (), Error::MemoryAllocationFailed);
242+ }
243+ #endif // !ET_TEST_ASAN_ENABLED
244+
150245TEST_P (FileDataLoaderTest, FromMissingFileFails) {
151246 // Wrapping a file that doesn't exist should fail.
152247 Result<FileDataLoader> fdl = FileDataLoader::from (
0 commit comments