Skip to content

Commit c17afa3

Browse files
committed
Make bool(), int() and float() of an ExecuTorchResult raise TypeError
An ExecuTorchResult supports the buffer protocol but has no truth value or number conversion of its own. So Python falls back to defaults that can give wrong answers without an error: bool(result) is always True, and int() and float() parse the raw bytes as text, so a uint8 result that holds 55 (the character "7") gives 7. Now all three raise TypeError and say how to convert the result first: bool(result) # TypeError: ExecuTorchResult has no truth value. Convert it first, for # example with numpy.asarray(result) or torch.from_dlpack(result). Tested with the standalone no-ATen build: a new test checks that all three raise TypeError, and it fails without this change.
1 parent 656de46 commit c17afa3

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

‎extension/pybindings/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ from executorch.extension.pybindings import portable_lib
103103
```
104104

105105
`ExecuTorchResult` preserves shape, strides, dtype, and device. CPU results expose
106-
the Python buffer protocol so NumPy can read them without another copy. Without
106+
the Python buffer protocol so NumPy can read them without another copy. A result
107+
is not a number or a truth value: `bool()`, `int()`, and `float()` raise
108+
`TypeError`, so convert it first, for example with `np.asarray(output)`. Without
107109
the environment setting, the bindings fall back to `ExecuTorchResult` if
108110
PyTorch cannot be imported. Explicitly selecting `torch` requires it to import
109111
successfully. ATen-mode bindings continue to return native PyTorch tensors

‎extension/pybindings/pybindings.cpp‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5447,6 +5447,15 @@ py::dict get_option(const std::string& backend_name, const py::dict& options) {
54475447
return result;
54485448
}
54495449

5450+
auto refuse_scalar_conversion(const char* problem) {
5451+
return [problem](const PyExecuTorchResult&) -> py::object {
5452+
throw py::type_error(
5453+
std::string("ExecuTorchResult ") + problem +
5454+
". Convert it first, for example with numpy.asarray(result) or "
5455+
"torch.from_dlpack(result).");
5456+
};
5457+
}
5458+
54505459
} // namespace
54515460

54525461
PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
@@ -5579,6 +5588,12 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
55795588
"Copy a dense CPU buffer, or a CPU ExecuTorchResult, to a CUDA "
55805589
"device, for use as a method input. Requires bindings built with "
55815590
"CUDA support.")
5591+
// Python would otherwise treat a result as always true, and int() and
5592+
// float() would parse its bytes as text.
5593+
.def("__bool__", refuse_scalar_conversion("has no truth value"))
5594+
.def("__int__", refuse_scalar_conversion("cannot be converted to int"))
5595+
.def(
5596+
"__float__", refuse_scalar_conversion("cannot be converted to float"))
55825597
.def("__repr__", &PyExecuTorchResult::repr);
55835598
#ifndef USE_ATEN_LIB
55845599
py::class_<

‎extension/pybindings/test/test_pybindings_no_aten.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,21 @@ def test_executorch_result_to_host_and_repr(self) -> None:
211211
self.assertFalse(np.shares_memory(np.asarray(copy), np.asarray(output)))
212212
np.testing.assert_array_equal(np.asarray(copy), np.array([3.0]))
213213

214+
def test_executorch_result_is_not_a_bool_or_number(self) -> None:
215+
if runtime._tensor_output != "executorch":
216+
self.skipTest("ExecuTorchResult output was not selected")
217+
with open(os.environ["EXECUTORCH_PYBIND_TEST_PTE"], "rb") as program_file:
218+
program_data = program_file.read()
219+
module = runtime._load_for_executorch_from_buffer(program_data)
220+
output = module(
221+
(np.array([1.0], dtype=np.float32), np.array([2.0], dtype=np.float32))
222+
)[0]
223+
224+
for convert in (bool, int, float):
225+
with self.subTest(convert=convert.__name__):
226+
with self.assertRaisesRegex(TypeError, r"numpy\.asarray\(result\)"):
227+
convert(output)
228+
214229
def test_executorch_result_from_host_requires_cuda(self) -> None:
215230
# A build with CUDA support uploads instead; the CUDA suite covers it.
216231
if "CudaBackend" in runtime._get_registered_backend_names():

0 commit comments

Comments
 (0)