diff --git a/extension/aten_util/aten_bridge.cpp b/extension/aten_util/aten_bridge.cpp index 351919d810b..98f9bde0af7 100644 --- a/extension/aten_util/aten_bridge.cpp +++ b/extension/aten_util/aten_bridge.cpp @@ -18,11 +18,22 @@ namespace extension { namespace { void check_tensor_meta(const at::Tensor& a, const executorch::aten::Tensor& b) { - // Check sizes/strides pointers - ET_CHECK_MSG( - b.sizes().data() != nullptr, "ETensor must have valid sizes array"); - ET_CHECK_MSG( - b.strides().data() != nullptr, "ETensor must have valid strides array"); + // 0-dim (scalar) tensors legitimately have empty sizes/strides arrays; + // their `.data()` may return nullptr depending on the underlying container. + // Only require non-null sizes/strides/dim_order storage when the tensor + // actually has at least one dimension. The nullptr check is meant to catch + // malformed metadata for ranked tensors (where these arrays MUST be + // addressable because dim_order_to_stride_nocheck below indexes into them); + // it must not abort on valid 0-dim inputs. + if (b.dim() > 0) { + ET_CHECK_MSG( + b.sizes().data() != nullptr, "ETensor must have valid sizes array"); + ET_CHECK_MSG( + b.strides().data() != nullptr, "ETensor must have valid strides array"); + ET_CHECK_MSG( + b.dim_order().data() != nullptr, + "ETensor must have valid dim_order array"); + } // Check disabled because in ASR model we get 1 element tensor with different // rank. /* diff --git a/extension/aten_util/test/aten_bridge_test.cpp b/extension/aten_util/test/aten_bridge_test.cpp index f9be4c21155..0e695cdc307 100644 --- a/extension/aten_util/test/aten_bridge_test.cpp +++ b/extension/aten_util/test/aten_bridge_test.cpp @@ -155,6 +155,47 @@ TEST(ATenBridgeTest, AliasTensorPtrToATenTensor) { EXPECT_EQ(at_tensor.const_data_ptr(), et_tensor_ptr->const_data_ptr()); } +// 0-dim (scalar) tensors legitimately have empty sizes/strides arrays whose +// `.data()` may return nullptr. Regression test for T270603238: ensure +// check_tensor_meta does not abort on valid 0-dim tensors. We pass nullptr +// explicitly for sizes/dim_order/strides because std::vector::data() on an +// empty vector is implementation-defined (libstdc++/libc++ may return a +// non-null sentinel) — using nullptr makes the regression deterministic +// across STL implementations. +TEST(ATenBridgeTest, AliasETensorToATenTensorZeroDim) { + auto at_tensor = at::scalar_tensor(42.0f); + ASSERT_EQ(at_tensor.dim(), 0); + auto dtype = torchToExecuTorchScalarType(at_tensor.options().dtype()); + torch::executor::TensorImpl tensor_impl( + dtype, + /*dim=*/0, + /*sizes=*/nullptr, + /*data=*/nullptr, + /*dim_order=*/nullptr, + /*strides=*/nullptr); + torch::executor::Tensor etensor(&tensor_impl); + alias_etensor_to_attensor(at_tensor, etensor); + EXPECT_EQ(at_tensor.const_data_ptr(), etensor.const_data_ptr()); +} + +TEST(ATenBridgeTest, AliasATTensorToETensorZeroDim) { + auto at_tensor = at::scalar_tensor(7); + ASSERT_EQ(at_tensor.dim(), 0); + auto dtype = torchToExecuTorchScalarType(at_tensor.options().dtype()); + std::vector etensor_data(at_tensor.nbytes()); + torch::executor::TensorImpl tensor_impl( + dtype, + /*dim=*/0, + /*sizes=*/nullptr, + etensor_data.data(), + /*dim_order=*/nullptr, + /*strides=*/nullptr); + torch::executor::Tensor etensor(&tensor_impl); + auto aliased_at_tensor = alias_attensor_to_etensor(etensor); + EXPECT_EQ(aliased_at_tensor.dim(), 0); + EXPECT_EQ(aliased_at_tensor.const_data_ptr(), etensor_data.data()); +} + TEST(ATenBridgeTest, AliasATTensorToETensorChannelsLast) { auto at_tensor = at::randn({2, 3, 4, 5}).to(at::MemoryFormat::ChannelsLast); std::vector sizes(