From c86a94e73ddf2d61fc5ab9de779aba73b86c38d6 Mon Sep 17 00:00:00 2001 From: Github Executorch Date: Tue, 21 Apr 2026 17:15:30 -0700 Subject: [PATCH 1/2] Update [ghstack-poisoned] --- runtime/core/evalue.h | 196 +++++++++++++++++++++++++++++- runtime/core/test/evalue_test.cpp | 185 ++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+), 2 deletions(-) diff --git a/runtime/core/evalue.h b/runtime/core/evalue.h index 8d75b1ace97..47d44698faf 100644 --- a/runtime/core/evalue.h +++ b/runtime/core/evalue.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -193,6 +194,13 @@ struct EValue { return payload.copyable_union.as_int; } + Result tryToInt() const { + if (!isInt()) { + return Error::InvalidType; + } + return payload.copyable_union.as_int; + } + /****** Double Type ******/ /*implicit*/ EValue(double d) : tag(Tag::Double) { payload.copyable_union.as_double = d; @@ -207,6 +215,13 @@ struct EValue { return payload.copyable_union.as_double; } + Result tryToDouble() const { + if (!isDouble()) { + return Error::InvalidType; + } + return payload.copyable_union.as_double; + } + /****** Bool Type ******/ /*implicit*/ EValue(bool b) : tag(Tag::Bool) { payload.copyable_union.as_bool = b; @@ -221,6 +236,13 @@ struct EValue { return payload.copyable_union.as_bool; } + Result tryToBool() const { + if (!isBool()) { + return Error::InvalidType; + } + return payload.copyable_union.as_bool; + } + /****** Scalar Type ******/ /// Construct an EValue using the implicit value of a Scalar. /*implicit*/ EValue(executorch::aten::Scalar s) { @@ -256,6 +278,19 @@ struct EValue { } } + Result tryToScalar() const { + if (isDouble()) { + return executorch::aten::Scalar(payload.copyable_union.as_double); + } + if (isInt()) { + return executorch::aten::Scalar(payload.copyable_union.as_int); + } + if (isBool()) { + return executorch::aten::Scalar(payload.copyable_union.as_bool); + } + return Error::InvalidType; + } + /****** Tensor Type ******/ /*implicit*/ EValue(executorch::aten::Tensor t) : tag(Tag::Tensor) { // When built in aten mode, at::Tensor has a non trivial constructor @@ -305,6 +340,13 @@ struct EValue { return payload.as_tensor; } + Result tryToTensor() const { + if (!isTensor()) { + return Error::InvalidType; + } + return payload.as_tensor; + } + /****** String Type ******/ /*implicit*/ EValue(executorch::aten::ArrayRef* s) : tag(Tag::String) { ET_CHECK_MSG(s != nullptr, "ArrayRef pointer cannot be null"); @@ -325,6 +367,18 @@ struct EValue { payload.copyable_union.as_string_ptr->size()); } + Result tryToString() const { + if (!isString()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_string_ptr == nullptr) { + return Error::InvalidState; + } + return std::string_view( + payload.copyable_union.as_string_ptr->data(), + payload.copyable_union.as_string_ptr->size()); + } + /****** Int List Type ******/ /*implicit*/ EValue(BoxedEvalueList* i) : tag(Tag::ListInt) { ET_CHECK_MSG( @@ -344,6 +398,16 @@ struct EValue { return (payload.copyable_union.as_int_list_ptr)->get(); } + Result> tryToIntList() const { + if (!isIntList()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_int_list_ptr == nullptr) { + return Error::InvalidState; + } + return (payload.copyable_union.as_int_list_ptr)->get(); + } + /****** Bool List Type ******/ /*implicit*/ EValue(executorch::aten::ArrayRef* b) : tag(Tag::ListBool) { @@ -363,6 +427,16 @@ struct EValue { return *(payload.copyable_union.as_bool_list_ptr); } + Result> tryToBoolList() const { + if (!isBoolList()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_bool_list_ptr == nullptr) { + return Error::InvalidState; + } + return *(payload.copyable_union.as_bool_list_ptr); + } + /****** Double List Type ******/ /*implicit*/ EValue(executorch::aten::ArrayRef* d) : tag(Tag::ListDouble) { @@ -382,6 +456,16 @@ struct EValue { return *(payload.copyable_union.as_double_list_ptr); } + Result> tryToDoubleList() const { + if (!isDoubleList()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_double_list_ptr == nullptr) { + return Error::InvalidState; + } + return *(payload.copyable_union.as_double_list_ptr); + } + /****** Tensor List Type ******/ /*implicit*/ EValue(BoxedEvalueList* t) : tag(Tag::ListTensor) { @@ -402,6 +486,17 @@ struct EValue { return payload.copyable_union.as_tensor_list_ptr->get(); } + Result> tryToTensorList() + const { + if (!isTensorList()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_tensor_list_ptr == nullptr) { + return Error::InvalidState; + } + return payload.copyable_union.as_tensor_list_ptr->get(); + } + /****** List Optional Tensor Type ******/ /*implicit*/ EValue( BoxedEvalueList>* t) @@ -426,6 +521,17 @@ struct EValue { return payload.copyable_union.as_list_optional_tensor_ptr->get(); } + Result>> + tryToListOptionalTensor() const { + if (!isListOptionalTensor()) { + return Error::InvalidType; + } + if (payload.copyable_union.as_list_optional_tensor_ptr == nullptr) { + return Error::InvalidState; + } + return payload.copyable_union.as_list_optional_tensor_ptr->get(); + } + /****** ScalarType Type ******/ executorch::aten::ScalarType toScalarType() const { ET_CHECK_MSG(isInt(), "EValue is not a ScalarType."); @@ -433,6 +539,14 @@ struct EValue { payload.copyable_union.as_int); } + Result tryToScalarType() const { + if (!isInt()) { + return Error::InvalidType; + } + return static_cast( + payload.copyable_union.as_int); + } + /****** MemoryFormat Type ******/ executorch::aten::MemoryFormat toMemoryFormat() const { ET_CHECK_MSG(isInt(), "EValue is not a MemoryFormat."); @@ -440,12 +554,27 @@ struct EValue { payload.copyable_union.as_int); } + Result tryToMemoryFormat() const { + if (!isInt()) { + return Error::InvalidType; + } + return static_cast( + payload.copyable_union.as_int); + } + /****** Layout Type ******/ executorch::aten::Layout toLayout() const { ET_CHECK_MSG(isInt(), "EValue is not a Layout."); return static_cast(payload.copyable_union.as_int); } + Result tryToLayout() const { + if (!isInt()) { + return Error::InvalidType; + } + return static_cast(payload.copyable_union.as_int); + } + /****** Device Type ******/ executorch::aten::Device toDevice() const { ET_CHECK_MSG(isInt(), "EValue is not a Device."); @@ -455,6 +584,16 @@ struct EValue { -1); } + Result tryToDevice() const { + if (!isInt()) { + return Error::InvalidType; + } + return executorch::aten::Device( + static_cast( + payload.copyable_union.as_int), + -1); + } + template T to() &&; template @@ -462,6 +601,15 @@ struct EValue { template typename internal::evalue_to_ref_overload_return::type to() &; + /** + * Result-returning equivalent of `to()`. Returns `Error::InvalidType` on + * tag mismatch instead of aborting, so callers processing untrusted EValues + * (e.g., from a `.pte`) can surface the error rather than terminate. + * Specializations are defined below via `EVALUE_DEFINE_TRY_TO`. + */ + template + Result tryTo() const; + /** * Converts the EValue to an optional object that can represent both T and * an uninitialized state. @@ -474,6 +622,23 @@ struct EValue { return this->to(); } + /** + * Result-returning equivalent of `toOptional()`. None maps to an empty + * optional; any other tag that doesn't match T propagates `tryTo()`'s + * error (`Error::InvalidType`). + */ + template + inline Result> tryToOptional() const { + if (this->isNone()) { + return std::optional(executorch::aten::nullopt); + } + auto r = this->tryTo(); + if (!r.ok()) { + return r.error(); + } + return std::optional(std::move(r.get())); + } + private: // Pre cond: the payload value has had its destructor called void clearToNone() noexcept { @@ -524,7 +689,7 @@ struct EValue { #define EVALUE_DEFINE_TO(T, method_name) \ template <> \ - inline T EValue::to()&& { \ + inline T EValue::to() && { \ return static_cast(std::move(*this).method_name()); \ } \ template <> \ @@ -538,7 +703,7 @@ struct EValue { template <> \ inline ::executorch::runtime::internal::evalue_to_ref_overload_return< \ T>::type \ - EValue::to()& { \ + EValue::to() & { \ typedef ::executorch::runtime::internal::evalue_to_ref_overload_return< \ T>::type return_type; \ return static_cast(this->method_name()); \ @@ -591,6 +756,33 @@ EVALUE_DEFINE_TO( toListOptionalTensor) #undef EVALUE_DEFINE_TO +#define EVALUE_DEFINE_TRY_TO(T, method_name) \ + template <> \ + inline Result EValue::tryTo() const { \ + return this->method_name(); \ + } + +EVALUE_DEFINE_TRY_TO(executorch::aten::Scalar, tryToScalar) +EVALUE_DEFINE_TRY_TO(int64_t, tryToInt) +EVALUE_DEFINE_TRY_TO(bool, tryToBool) +EVALUE_DEFINE_TRY_TO(double, tryToDouble) +EVALUE_DEFINE_TRY_TO(std::string_view, tryToString) +EVALUE_DEFINE_TRY_TO(executorch::aten::ScalarType, tryToScalarType) +EVALUE_DEFINE_TRY_TO(executorch::aten::MemoryFormat, tryToMemoryFormat) +EVALUE_DEFINE_TRY_TO(executorch::aten::Layout, tryToLayout) +EVALUE_DEFINE_TRY_TO(executorch::aten::Device, tryToDevice) +EVALUE_DEFINE_TRY_TO(executorch::aten::Tensor, tryToTensor) +EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToIntList) +EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToDoubleList) +EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToBoolList) +EVALUE_DEFINE_TRY_TO( + executorch::aten::ArrayRef, + tryToTensorList) +EVALUE_DEFINE_TRY_TO( + executorch::aten::ArrayRef>, + tryToListOptionalTensor) +#undef EVALUE_DEFINE_TRY_TO + template executorch::aten::ArrayRef BoxedEvalueList::get() const { for (typename executorch::aten::ArrayRef::size_type i = 0; diff --git a/runtime/core/test/evalue_test.cpp b/runtime/core/test/evalue_test.cpp index edf6a1b12c1..2c1c4415c59 100644 --- a/runtime/core/test/evalue_test.cpp +++ b/runtime/core/test/evalue_test.cpp @@ -417,3 +417,188 @@ TEST_F(EValueTest, toListOptionalTensorNullPointerCheck) { EXPECT_TRUE(e.isListOptionalTensor()); ET_EXPECT_DEATH({ e.toListOptionalTensor(); }, "pointer is null"); } + +TEST_F(EValueTest, TryToTensorSuccess) { + TensorFactory tf; + EValue e(tf.ones({3, 2})); + auto result = e.tryToTensor(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->dim(), 2); + EXPECT_EQ(result->numel(), 6); +} + +TEST_F(EValueTest, TryToTensorTypeMismatch) { + EValue e(static_cast(42)); + auto result = e.tryToTensor(); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToOptionalTensorSuccess) { + TensorFactory tf; + EValue e(tf.ones({3, 2})); + auto result = e.tryToOptional(); + EXPECT_TRUE(result.ok()); + EXPECT_TRUE(result->has_value()); + EXPECT_EQ(result->value().dim(), 2); +} + +TEST_F(EValueTest, TryToOptionalTensorNone) { + EValue e; + auto result = e.tryToOptional(); + EXPECT_TRUE(result.ok()); + EXPECT_FALSE(result->has_value()); +} + +TEST_F(EValueTest, TryToOptionalTensorTypeMismatch) { + EValue e(static_cast(42)); + auto result = e.tryToOptional(); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +// Scalar/primitive tryTo* coverage. The Success+TypeMismatch pair is +// identical per-type modulo the method name, so the macro below generates +// both tests. `match_value` should satisfy the isX() tag check; `mismatch_ev` +// must construct an EValue whose tag differs. +#define TRY_TO_PRIMITIVE_TEST(Name, Type, match_value, mismatch_ev) \ + TEST_F(EValueTest, TryTo##Name##Success) { \ + EValue e(static_cast(match_value)); \ + auto result = e.tryTo##Name(); \ + EXPECT_TRUE(result.ok()); \ + EXPECT_EQ(result.get(), static_cast(match_value)); \ + } \ + TEST_F(EValueTest, TryTo##Name##TypeMismatch) { \ + EValue e(mismatch_ev); \ + auto result = e.tryTo##Name(); \ + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); \ + } + +TRY_TO_PRIMITIVE_TEST(Int, int64_t, 42, 3.14) +TRY_TO_PRIMITIVE_TEST(Double, double, 3.14, static_cast(42)) +TRY_TO_PRIMITIVE_TEST(Bool, bool, true, static_cast(42)) + +#undef TRY_TO_PRIMITIVE_TEST + +TEST_F(EValueTest, TryToScalarFromInt) { + EValue e(static_cast(7)); + auto result = e.tryToScalar(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->to(), 7); +} + +TEST_F(EValueTest, TryToScalarFromDouble) { + EValue e(2.5); + auto result = e.tryToScalar(); + EXPECT_TRUE(result.ok()); + EXPECT_DOUBLE_EQ(result->to(), 2.5); +} + +TEST_F(EValueTest, TryToScalarFromBool) { + EValue e(true); + auto result = e.tryToScalar(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->to(), true); +} + +TEST_F(EValueTest, TryToScalarNoneTag) { + // None is neither Int/Double/Bool, so tryToScalar must reject it. + EValue e; + auto result = e.tryToScalar(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToScalarTypeTagReturnsScalarType) { + // ScalarType/MemoryFormat/Layout/Device share the Int tag; exercise each. + EValue e(static_cast(static_cast(ScalarType::Float))); + auto st = e.tryToScalarType(); + EXPECT_TRUE(st.ok()); + EXPECT_EQ(st.get(), ScalarType::Float); +} + +TEST_F(EValueTest, TryToScalarTypeTypeMismatch) { + EValue e(3.14); + auto result = e.tryToScalarType(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToMemoryFormatTypeMismatch) { + EValue e(3.14); + auto result = e.tryToMemoryFormat(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToLayoutTypeMismatch) { + EValue e(3.14); + auto result = e.tryToLayout(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToDeviceTypeMismatch) { + EValue e(3.14); + auto result = e.tryToDevice(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +// List tryTo* — only cover TensorList and ListOptionalTensor. The other +// list/string variants share the same `if (!isX()) return Error::InvalidType` +// pattern exercised by the primitive mismatch tests above. Tensor-family +// lists are the highest-risk attack surface (pointer-holding), so keep +// explicit coverage. + +TEST_F(EValueTest, TryToTensorListTypeMismatch) { + EValue e(static_cast(42)); + auto result = e.tryToTensorList(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToListOptionalTensorTypeMismatch) { + EValue e(static_cast(42)); + auto result = e.tryToListOptionalTensor(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +// Templated tryTo() dispatcher. Matches and mismatches should behave +// identically to the named tryToX methods. + +TEST_F(EValueTest, TryToTemplateIntSuccess) { + EValue e(static_cast(42)); + auto result = e.tryTo(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.get(), 42); +} + +TEST_F(EValueTest, TryToTemplateIntMismatch) { + EValue e(3.14); + auto result = e.tryTo(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, TryToTemplateTensorSuccess) { + TensorFactory tf; + EValue e(tf.ones({3, 2})); + auto result = e.tryTo(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->numel(), 6); +} + +TEST_F(EValueTest, TryToOptionalIntSuccess) { + EValue e(static_cast(42)); + auto result = e.tryToOptional(); + EXPECT_TRUE(result.ok()); + EXPECT_TRUE(result->has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST_F(EValueTest, TryToOptionalIntNone) { + EValue e; + auto result = e.tryToOptional(); + EXPECT_TRUE(result.ok()); + EXPECT_FALSE(result->has_value()); +} + +TEST_F(EValueTest, TryToOptionalIntTypeMismatch) { + EValue e(3.14); + auto result = e.tryToOptional(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} From 3bbed22936fa0f541a462346bf2b43f6e15073be Mon Sep 17 00:00:00 2001 From: Github Executorch Date: Tue, 21 Apr 2026 18:52:40 -0700 Subject: [PATCH 2/2] Update [ghstack-poisoned] --- runtime/core/evalue.cpp | 25 ++++++++++++ runtime/core/evalue.h | 65 +++++++++++++++++++++++++++-- runtime/core/test/evalue_test.cpp | 68 +++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/runtime/core/evalue.cpp b/runtime/core/evalue.cpp index 121a9a29fa2..ecb698a613e 100644 --- a/runtime/core/evalue.cpp +++ b/runtime/core/evalue.cpp @@ -27,5 +27,30 @@ BoxedEvalueList>::get() const { return executorch::aten::ArrayRef>{ unwrapped_vals_, wrapped_vals_.size()}; } + +// Specialization note: unlike the generic tryGet, a null wrapped_vals_[i] +// here is a valid encoding of None (matching the get() specialization above, +// which mirrors parseListOptionalType's "absent index" convention). Only an +// element whose tag is neither None nor Tensor is treated as an error. +template <> +Result>> +BoxedEvalueList>::tryGet() const { + for (typename executorch::aten::ArrayRef< + std::optional>::size_type i = 0; + i < wrapped_vals_.size(); + i++) { + if (wrapped_vals_[i] == nullptr) { + unwrapped_vals_[i] = std::nullopt; + continue; + } + auto r = wrapped_vals_[i]->tryToOptional(); + if (!r.ok()) { + return r.error(); + } + unwrapped_vals_[i] = std::move(r.get()); + } + return executorch::aten::ArrayRef>{ + unwrapped_vals_, wrapped_vals_.size()}; +} } // namespace runtime } // namespace executorch diff --git a/runtime/core/evalue.h b/runtime/core/evalue.h index 47d44698faf..8a0ef44c6ee 100644 --- a/runtime/core/evalue.h +++ b/runtime/core/evalue.h @@ -72,6 +72,16 @@ class BoxedEvalueList { */ executorch::aten::ArrayRef get() const; + /** + * Result-returning counterpart of get(). Validates each wrapped EValue's + * tag before materializing; returns Error::InvalidType if any element's + * tag does not match T and Error::InvalidState if any element pointer is + * null. Use this when materializing lists from untrusted .pte data so that + * a malformed program cannot force a process abort inside to() / + * ET_CHECK. + */ + Result> tryGet() const; + /** * Destroys the unwrapped elements without re-dereferencing wrapped_vals_. * This is safe to call during EValue destruction because it does not @@ -108,6 +118,10 @@ template <> executorch::aten::ArrayRef> BoxedEvalueList>::get() const; +template <> +Result>> +BoxedEvalueList>::tryGet() const; + // Aggregate typing system similar to IValue only slimmed down with less // functionality, no dependencies on atomic, and fewer supported types to better // suit embedded systems (ie no intrusive ptr) @@ -405,7 +419,7 @@ struct EValue { if (payload.copyable_union.as_int_list_ptr == nullptr) { return Error::InvalidState; } - return (payload.copyable_union.as_int_list_ptr)->get(); + return (payload.copyable_union.as_int_list_ptr)->tryGet(); } /****** Bool List Type ******/ @@ -494,7 +508,7 @@ struct EValue { if (payload.copyable_union.as_tensor_list_ptr == nullptr) { return Error::InvalidState; } - return payload.copyable_union.as_tensor_list_ptr->get(); + return payload.copyable_union.as_tensor_list_ptr->tryGet(); } /****** List Optional Tensor Type ******/ @@ -529,7 +543,7 @@ struct EValue { if (payload.copyable_union.as_list_optional_tensor_ptr == nullptr) { return Error::InvalidState; } - return payload.copyable_union.as_list_optional_tensor_ptr->get(); + return payload.copyable_union.as_list_optional_tensor_ptr->tryGet(); } /****** ScalarType Type ******/ @@ -630,7 +644,7 @@ struct EValue { template inline Result> tryToOptional() const { if (this->isNone()) { - return std::optional(executorch::aten::nullopt); + return std::optional(std::nullopt); } auto r = this->tryTo(); if (!r.ok()) { @@ -771,13 +785,39 @@ EVALUE_DEFINE_TRY_TO(executorch::aten::ScalarType, tryToScalarType) EVALUE_DEFINE_TRY_TO(executorch::aten::MemoryFormat, tryToMemoryFormat) EVALUE_DEFINE_TRY_TO(executorch::aten::Layout, tryToLayout) EVALUE_DEFINE_TRY_TO(executorch::aten::Device, tryToDevice) +// Tensor and Optional Tensor EVALUE_DEFINE_TRY_TO(executorch::aten::Tensor, tryToTensor) +EVALUE_DEFINE_TRY_TO( + std::optional, + tryToOptional) + +// IntList and Optional IntList EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToIntList) +EVALUE_DEFINE_TRY_TO( + std::optional>, + tryToOptional>) + +// DoubleList and Optional DoubleList EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToDoubleList) +EVALUE_DEFINE_TRY_TO( + std::optional>, + tryToOptional>) + +// BoolList and Optional BoolList EVALUE_DEFINE_TRY_TO(executorch::aten::ArrayRef, tryToBoolList) +EVALUE_DEFINE_TRY_TO( + std::optional>, + tryToOptional>) + +// TensorList and Optional TensorList EVALUE_DEFINE_TRY_TO( executorch::aten::ArrayRef, tryToTensorList) +EVALUE_DEFINE_TRY_TO( + std::optional>, + tryToOptional>) + +// List of Optional Tensor EVALUE_DEFINE_TRY_TO( executorch::aten::ArrayRef>, tryToListOptionalTensor) @@ -794,6 +834,23 @@ executorch::aten::ArrayRef BoxedEvalueList::get() const { return executorch::aten::ArrayRef{unwrapped_vals_, wrapped_vals_.size()}; } +template +Result> BoxedEvalueList::tryGet() const { + for (typename executorch::aten::ArrayRef::size_type i = 0; + i < wrapped_vals_.size(); + i++) { + if (wrapped_vals_[i] == nullptr) { + return Error::InvalidState; + } + auto r = wrapped_vals_[i]->template tryTo(); + if (!r.ok()) { + return r.error(); + } + unwrapped_vals_[i] = std::move(r.get()); + } + return executorch::aten::ArrayRef{unwrapped_vals_, wrapped_vals_.size()}; +} + } // namespace runtime } // namespace executorch diff --git a/runtime/core/test/evalue_test.cpp b/runtime/core/test/evalue_test.cpp index 2c1c4415c59..a3e1352cec8 100644 --- a/runtime/core/test/evalue_test.cpp +++ b/runtime/core/test/evalue_test.cpp @@ -214,6 +214,57 @@ TEST_F(EValueTest, BoxedEvalueList) { EXPECT_EQ(unwrapped[2], 3); } +TEST_F(EValueTest, BoxedEvalueListTryGetSuccess) { + EValue values[3] = { + EValue((int64_t)1), EValue((int64_t)2), EValue((int64_t)3)}; + EValue* values_p[3] = {&values[0], &values[1], &values[2]}; + int64_t storage[3] = {0, 0, 0}; + BoxedEvalueList x{values_p, storage, 3}; + auto result = x.tryGet(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->size(), 3); + EXPECT_EQ((*result)[0], 1); + EXPECT_EQ((*result)[2], 3); +} + +TEST_F(EValueTest, BoxedEvalueListTryGetWrongElementTag) { + // Second element is a Double, not an Int; tryGet should reject it rather + // than abort inside to(). + EValue values[3] = {EValue((int64_t)1), EValue(3.14), EValue((int64_t)3)}; + EValue* values_p[3] = {&values[0], &values[1], &values[2]}; + int64_t storage[3] = {0, 0, 0}; + BoxedEvalueList x{values_p, storage, 3}; + auto result = x.tryGet(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); +} + +TEST_F(EValueTest, BoxedEvalueListTryGetNullElement) { + // A null wrapped pointer is a malformed program for non-optional lists; + // tryGet reports InvalidState rather than aborting inside ET_CHECK. + EValue a((int64_t)1); + EValue c((int64_t)3); + EValue* values_p[3] = {&a, nullptr, &c}; + int64_t storage[3] = {0, 0, 0}; + BoxedEvalueList x{values_p, storage, 3}; + auto result = x.tryGet(); + EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidState); +} + +TEST_F(EValueTest, BoxedEvalueListTryGetOptionalTensorNullIsNone) { + // For the optional specialization, a null wrapped pointer is a + // valid None encoding (matches parseListOptionalType), not an error. + EValue a; + EValue* values_p[2] = {&a, nullptr}; + std::optional storage[2]; + BoxedEvalueList> x{ + values_p, storage, 2}; + auto result = x.tryGet(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result->size(), 2); + EXPECT_FALSE((*result)[0].has_value()); + EXPECT_FALSE((*result)[1].has_value()); +} + TEST_F(EValueTest, toOptionalTensorList) { // create list, empty evalue ctor gets tag::None EValue values[2] = {EValue(), EValue()}; @@ -602,3 +653,20 @@ TEST_F(EValueTest, TryToOptionalIntTypeMismatch) { auto result = e.tryToOptional(); EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType); } + +// Verify tryTo>() specializations match tryToOptional() +// semantics, mirroring the to>() specializations of to(). +TEST_F(EValueTest, TryToTemplateOptionalIntSuccess) { + EValue e(static_cast(42)); + auto result = e.tryTo>(); + EXPECT_TRUE(result.ok()); + EXPECT_TRUE(result->has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST_F(EValueTest, TryToTemplateOptionalTensorNone) { + EValue e; + auto result = e.tryTo>(); + EXPECT_TRUE(result.ok()); + EXPECT_FALSE(result->has_value()); +}