Skip to content

Add tryTo evalue accessors#19039

Open
lucylq wants to merge 3 commits intomainfrom
gh/lucylq/151/head
Open

Add tryTo evalue accessors#19039
lucylq wants to merge 3 commits intomainfrom
gh/lucylq/151/head

Conversation

@lucylq
Copy link
Copy Markdown
Contributor

@lucylq lucylq commented Apr 22, 2026

Add tryTo accessors for each value. Previously, toTensor etc. abort with ET_CHECK_MSG when the type mismatches.

API additions:

  • Per-type: tryToInt, tryToDouble, tryToBool, tryToScalar, tryToString,
    tryToTensor (already present, kept), tryToIntList, tryToBoolList,
    tryToDoubleList, tryToTensorList, tryToListOptionalTensor,
    tryToScalarType, tryToMemoryFormat, tryToLayout, tryToDevice.
    Tag mismatch returns Error::InvalidType; null list/string payload
    returns Error::InvalidState.
  • Templated tryTo() dispatcher mirroring to(), via a new
    EVALUE_DEFINE_TRY_TO macro kept adjacent to EVALUE_DEFINE_TO so drift
    between the two surfaces is visible at review time.
  • tryToOptional() widened from Tensor-only to generic, delegating
    to tryTo() so it works for any supported payload type.

Tests cover success + mismatch paths for each new accessor, plus the
widened tryToOptional() path.

Authored-with: Claude

[ghstack-poisoned]
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 22, 2026

Copilot AI review requested due to automatic review settings April 22, 2026 00:15
@lucylq lucylq requested a review from JacobSzwejbka as a code owner April 22, 2026 00:15
@pytorch-bot
Copy link
Copy Markdown

pytorch-bot Bot commented Apr 22, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19039

Note: Links to docs will display an error until the docs builds have been completed.

❌ 4 New Failures, 32 Cancelled Jobs, 2 Unrelated Failures

As of commit 3bbed22 with merge base 54b0148 (image):

NEW FAILURES - The following jobs have failed:

CANCELLED JOBS - The following jobs were cancelled. Please retry:

FLAKY - The following jobs failed but were likely due to flakiness present on trunk:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@github-actions
Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a non-fatal, Result-returning “tryTo” API surface to executorch::runtime::EValue so callers can safely handle tag mismatches (and some invalid internal states) without ET_CHECK_MSG aborting—intended for paths that may process untrusted .pte inputs.

Changes:

  • Introduce tryToX() accessors for most EValue payloads (primitives, Scalar, Tensor, string, lists, and int-backed enums) plus a templated tryTo<T>() dispatcher.
  • Generalize tryToOptional<T>() to work for any supported T by delegating to tryTo<T>().
  • Add unit tests exercising a subset of the new APIs (mainly primitives/Tensor/Scalar/ScalarType and some mismatch paths).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
runtime/core/evalue.h Adds Result-returning tryTo* accessors + templated dispatcher + generic tryToOptional<T>().
runtime/core/test/evalue_test.cpp Adds new tests for several tryTo* accessors and the generalized tryToOptional<T>().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

TRY_TO_PRIMITIVE_TEST(Bool, bool, true, static_cast<int64_t>(42))

#undef TRY_TO_PRIMITIVE_TEST

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite adding tryToString/tryToIntList/tryToBoolList/tryToDoubleList, this file currently has no direct success+mismatch coverage for those accessors (only primitives/Tensor/Scalar/ScalarType are exercised). If the PR goal is “for all EValue payload types”, add at least one representative test per accessor to ensure tag checks and null-pointer state handling behave as intended.

Suggested change
TEST_F(EValueTest, TryToStringSuccess) {
executorch::aten::string_view value("hello");
EValue e(value);
auto result = e.tryToString();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.get(), value);
}
TEST_F(EValueTest, TryToStringTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToString();
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToStringEmptyState) {
executorch::aten::string_view value;
EValue e(value);
auto result = e.tryToString();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.get().data(), nullptr);
EXPECT_EQ(result.get().size(), 0);
}
TEST_F(EValueTest, TryToIntListSuccess) {
int64_t values[] = {1, 2, 3};
executorch::aten::ArrayRef<int64_t> value(values, 3);
EValue e(value);
auto result = e.tryToIntList();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.get().size(), 3);
EXPECT_EQ(result.get()[0], 1);
EXPECT_EQ(result.get()[1], 2);
EXPECT_EQ(result.get()[2], 3);
}
TEST_F(EValueTest, TryToIntListTypeMismatch) {
EValue e(true);
auto result = e.tryToIntList();
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToIntListEmptyState) {
executorch::aten::ArrayRef<int64_t> value;
EValue e(value);
auto result = e.tryToIntList();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.get().data(), nullptr);
EXPECT_EQ(result.get().size(), 0);
}
TEST_F(EValueTest, TryToBoolListSuccess) {
bool values[] = {true, false, true};
executorch::aten::ArrayRef<bool> value(values, 3);
EValue e(value);
auto result = e.tryToBoolList();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.get().size(), 3);
EXPECT_EQ(result.get()[0], true);
EXPECT_EQ(result.get()[1], false);
EXPECT_EQ(result.get()[2], true);
}
TEST_F(EValueTest, TryToBoolListTypeMismatch) {
EValue e(3.14);
auto result = e.tryToBoolList();
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToBoolListEmptyState) {
executorch::aten::ArrayRef<bool> value;
EValue e(value);
auto result = e.tryToBoolList();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.get().data(), nullptr);
EXPECT_EQ(result.get().size(), 0);
}
TEST_F(EValueTest, TryToDoubleListSuccess) {
double values[] = {1.5, 2.5, 3.5};
executorch::aten::ArrayRef<double> value(values, 3);
EValue e(value);
auto result = e.tryToDoubleList();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.get().size(), 3);
EXPECT_DOUBLE_EQ(result.get()[0], 1.5);
EXPECT_DOUBLE_EQ(result.get()[1], 2.5);
EXPECT_DOUBLE_EQ(result.get()[2], 3.5);
}
TEST_F(EValueTest, TryToDoubleListTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToDoubleList();
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToDoubleListEmptyState) {
executorch::aten::ArrayRef<double> value;
EValue e(value);
auto result = e.tryToDoubleList();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.get().data(), nullptr);
EXPECT_EQ(result.get().size(), 0);
}

Copilot uses AI. Check for mistakes.
Comment thread runtime/core/evalue.h Outdated
if (payload.copyable_union.as_tensor_list_ptr == nullptr) {
return Error::InvalidState;
}
return payload.copyable_union.as_tensor_list_ptr->get();
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tryToTensorList()/tryToIntList()/tryToList currently delegate to BoxedEvalueList::get(), which unwraps elements via wrapped_vals_[i]->to() and can still ET_CHECK-abort on element tag mismatches. This means the new tryToList APIs are not actually non-fatal on malformed/untrusted .pte data. Consider adding a Result-returning BoxedEvalueList::tryGet() (using tryTo() / tryToOptional per element) and have the tryTo*List accessors call that instead.

Suggested change
return payload.copyable_union.as_tensor_list_ptr->get();
return payload.copyable_union.as_tensor_list_ptr->tryGet();

Copilot uses AI. Check for mistakes.
Comment on lines +543 to +560
// 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<int64_t>(42));
auto result = e.tryToTensorList();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}

TEST_F(EValueTest, TryToListOptionalTensorTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToListOptionalTensor();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tryTo* APIs for pointer-backed payloads return Error::InvalidState when the tag matches but the stored pointer is null (e.g., tryToString/tryToIntList/tryToTensorList/tryToListOptionalTensor). There are existing death tests for the aborting to* variants, but this PR doesn't add non-death coverage asserting the new InvalidState behavior; adding those tests would prevent regressions and validate the DoS-hardening intent.

Suggested change
// 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<int64_t>(42));
auto result = e.tryToTensorList();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToListOptionalTensorTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToListOptionalTensor();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
// Pointer-backed tryTo* accessors must distinguish type mismatches from
// malformed tagged state. Cover both `Error::InvalidType` for mismatched tags
// and `Error::InvalidState` when the tag matches but the stored pointer is
// null.
TEST_F(EValueTest, TryToStringNullPayloadInvalidState) {
EValue e(Tag::String, nullptr);
auto result = e.tryToString();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidState);
}
TEST_F(EValueTest, TryToIntListNullPayloadInvalidState) {
EValue e(Tag::IntList, nullptr);
auto result = e.tryToIntList();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidState);
}
TEST_F(EValueTest, TryToTensorListTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToTensorList();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToTensorListNullPayloadInvalidState) {
EValue e(Tag::TensorList, nullptr);
auto result = e.tryToTensorList();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidState);
}
TEST_F(EValueTest, TryToListOptionalTensorTypeMismatch) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryToListOptionalTensor();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
TEST_F(EValueTest, TryToListOptionalTensorNullPayloadInvalidState) {
EValue e(Tag::ListOptionalTensor, nullptr);
auto result = e.tryToListOptionalTensor();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidState);
}

Copilot uses AI. Check for mistakes.
Comment on lines +525 to +541
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);
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests for tryToMemoryFormat/tryToLayout/tryToDevice only cover the type-mismatch path; there is no success-path coverage to ensure the int->enum/device conversions are wired correctly and stay consistent with the aborting to* accessors. Consider adding simple success tests for each of these new accessors.

Copilot uses AI. Check for mistakes.
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 22, 2026

@claude review

@lucylq lucylq changed the title Add EValue::tryTo<T>() for all EValue payload types Add tryTo evalue accessors Apr 22, 2026
Github Executorch added 2 commits April 21, 2026 18:52
[ghstack-poisoned]
[ghstack-poisoned]
Copilot AI review requested due to automatic review settings April 22, 2026 02:23
@pytorch pytorch deleted a comment from claude Bot Apr 22, 2026
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 22, 2026

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented Apr 22, 2026

Claude finished @lucylq's task in 1m 34s —— View job


Review of PR #19039: Add tryTo EValue accessors

  • Read changed files and understand the diff
  • Review evalue.h changes
  • Review evalue.cpp changes
  • Review test coverage in evalue_test.cpp
  • Post review feedback

Overall this is a well-structured addition. The approach of mirroring each toX() with a tryToX() returning Result<T> is clean, and having EVALUE_DEFINE_TRY_TO adjacent to EVALUE_DEFINE_TO makes drift visible. A few observations:

Design

BoxedEvalueList::tryGet() — good catch on element-level safety. The generic tryGet() template (evalue.h:841-855) correctly validates each element via tryTo<T>() instead of to<T>(), avoiding process aborts on malformed .pte data. The optional<Tensor> specialization in evalue.cpp:37-54 correctly treats null wrapped_vals_[i] as None (matching the existing get() semantics from parseListOptionalType), while the generic template treats null elements as Error::InvalidState.

tryToOptional<T>() generalization is correct. The widening from Tensor-only to generic tryToOptional<T>() (evalue.h:648-657) delegates properly to tryTo<T>(), and the EVALUE_DEFINE_TRY_TO specializations for std::optional<...> types route through tryToOptional<...> — all consistent.

Issues

  1. tryToScalar() returns by value, constructing a new Scalar — but toScalar() uses the aborting toDouble()/toInt()/toBool() internally. tryToScalar() (evalue.h:295-306) avoids this by reading the payload directly — good. However, there's a subtle inconsistency: toScalar() returns via implicit Scalar constructors from the primitive getters (which could theoretically route differently from explicit Scalar(payload...) construction). This isn't a real bug today, but worth noting the implementation divergence.

  2. tryToTensor() returns by copy, not by reference (evalue.h:360-365). The comment acknowledges this, and it's inherent to Result<T> wrapping by value. In lean mode this is free (TensorImpl* copy), but in ATen mode it's a refcount bump. Users migrating from toTensor() (which returns const& or &) to tryToTensor() need to be aware of this semantic difference. Consider documenting this in the method comment or in the PR description for downstream consumers.

  3. Minor: two whitespace fixes in EVALUE_DEFINE_TO got bundled in (evalue.h:709, evalue.h:723to<T>()&&to<T>() && and to<T>()&to<T>() &). These are correct formatting fixes but are unrelated to the tryTo feature. Not blocking, just noting they increase noise in the diff.

Test coverage gaps

The test file has solid coverage for primitives, Scalar, Tensor, and the templated tryTo<T>()/tryToOptional<T>() dispatchers. However:

  • No success-path tests for tryToString, tryToIntList, tryToBoolList, tryToDoubleList, tryToTensorList, tryToListOptionalTensor. The mismatch paths are covered for TensorList and ListOptionalTensor only. Adding at least one success test per list/string accessor would verify the end-to-end path through the null-pointer check and tryGet().
    Add tests →

  • No success-path tests for tryToMemoryFormat, tryToLayout, tryToDevice. Only mismatch tests exist. These int-backed enum conversions should have a basic success test to confirm the cast is wired correctly.
    Add tests →

  • No InvalidState coverage for pointer-backed tryTo* methods. The death tests cover the aborting toString()/toIntList() etc. on null pointers, but there are no tests verifying that tryToString()/tryToIntList() etc. return Error::InvalidState for the same null-pointer scenario. Since these are the defensive paths this PR is designed to enable, they should be tested.
    Add tests →

Summary

The core implementation is correct and well-motivated. The BoxedEvalueList::tryGet() addition properly addresses the element-level abort concern. The main gap is test coverage — adding success-path and InvalidState tests for the pointer-backed accessors would make this robust. The rest looks good to land.


Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +657 to +666
// Verify tryTo<std::optional<T>>() specializations match tryToOptional<T>()
// semantics, mirroring the to<std::optional<T>>() specializations of to<T>().
TEST_F(EValueTest, TryToTemplateOptionalIntSuccess) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryTo<std::optional<int64_t>>();
EXPECT_TRUE(result.ok());
EXPECT_TRUE(result->has_value());
EXPECT_EQ(result->value(), 42);
}

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EValue::tryTo<T>() has no specialization for T = std::optional<int64_t> (or other optional primitives). With only a declaration of the primary template, e.tryTo<std::optional<int64_t>>() will not compile/link. Either add an EVALUE_DEFINE_TRY_TO(std::optional<int64_t>, tryToOptional<int64_t>) specialization (and any other optional primitives you intend to support) or remove/adjust this test to only cover optional types that are actually supported by tryTo<T>().

Suggested change
// Verify tryTo<std::optional<T>>() specializations match tryToOptional<T>()
// semantics, mirroring the to<std::optional<T>>() specializations of to<T>().
TEST_F(EValueTest, TryToTemplateOptionalIntSuccess) {
EValue e(static_cast<int64_t>(42));
auto result = e.tryTo<std::optional<int64_t>>();
EXPECT_TRUE(result.ok());
EXPECT_TRUE(result->has_value());
EXPECT_EQ(result->value(), 42);
}
// Verify supported tryTo<std::optional<T>>() specializations match
// tryToOptional<T>() semantics, mirroring the to<std::optional<T>>()
// specializations of to<T>().

Copilot uses AI. Check for mistakes.
Comment thread runtime/core/evalue.h
Comment on lines +782 to +796
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)
// Tensor and Optional Tensor
EVALUE_DEFINE_TRY_TO(executorch::aten::Tensor, tryToTensor)
EVALUE_DEFINE_TRY_TO(
std::optional<executorch::aten::Tensor>,
tryToOptional<executorch::aten::Tensor>)

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EValue::tryTo<T>() is only explicitly specialized for a subset of T (Tensor and list optionals), but the new tests call tryTo<std::optional<int64_t>>(). Since the primary tryTo<T>() template is only declared (not defined), any missing specialization is a hard compile error. Consider adding EVALUE_DEFINE_TRY_TO(std::optional<int64_t>, tryToOptional<int64_t>) (and other optional primitives if desired) or dropping support/tests for tryTo<std::optional<T>>() beyond the types already covered by to<T>().

Copilot uses AI. Check for mistakes.
Comment on lines +588 to +592
TEST_F(EValueTest, TryToDeviceTypeMismatch) {
EValue e(3.14);
auto result = e.tryToDevice();
EXPECT_EQ(result.error(), executorch::runtime::Error::InvalidType);
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says tests cover success+mismatch paths for each new tryTo* accessor, but this file only adds coverage for Tensor, Int/Double/Bool, Scalar, ScalarType (success) and several mismatch-only cases. There’s no coverage for tryToString() (including its new InvalidState-on-null behavior) and no success-path coverage for tryToMemoryFormat()/tryToLayout()/tryToDevice(). Either extend tests or adjust the PR description to match what’s actually covered.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants