-
Notifications
You must be signed in to change notification settings - Fork 84
[RNE Rewrite] feat: add input validation and JSI conversion utilities #1296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
a76d7e9
cpp/core: extract conversions and tensor_helpers modules
barhanc b70d91a
cpp: migrate callers to conversions and tensor_helpers
barhanc 84c703c
cpp/core: fix size_t/uint64_t duplicate specialization on Linux
barhanc cdfbbf0
style: add explicit `tensor::fromJs`
barhanc e60c5df
feat: Add RangeDim support to C++ SymbolicShape and refactor model ru…
barhanc 139eb27
fix: fix error message
barhanc 7305cad
refactor(cpp/core): clean up model execution and error context handling
barhanc f5511f1
refactor(cpp/core): clean up shape and tensor comparisons in tensor h…
barhanc 28adfc5
refactor(cpp): restore parameter validation and clean up redundant ty…
barhanc 086fc5c
refactor(cpp): implement strict range-checked template conversions fo…
barhanc 051c6ac
refactor(cpp): restore execution error messages and use getRequiredPr…
barhanc 0762abe
refactor(cpp): simplify shapeToString using range-based loop and pop_…
barhanc a01f7ec
refactor(cpp): preserve output tensor JS object identity and clean up…
barhanc fb4a7ed
refactor(cpp): restore execution profiling and original short execute…
barhanc db99e73
style: fix using declaration in model.cpp
barhanc 91c2864
refactor(cpp): replace manual JSI type checking with asType specializ…
barhanc 13e4bcd
refactor(cpp): use macro for template specializations and delete prim…
barhanc 1b0caa3
refactor(cpp): safe copy/move return for jsi::Value and const correct…
barhanc 05f298b
refactor(cpp): implement declarative shape validation in applyColorma…
barhanc e3edcc0
refactor(cpp): model and tokenizer path const correctness, conversion…
barhanc 463c4e8
refactor(cpp): add initializer_list overload to fromJs and simplify s…
barhanc 45f1097
refactor(cpp): remove const from modelPath_ and tokenizerPath_ privat…
barhanc 563365e
refactor(cpp): use auto for static_cast double conversions
barhanc 1ba3355
refactor(cpp): use std::format for string construction in conversions…
barhanc d6345d6
fix(core): add bounds checks for offset and length in tensor methods
barhanc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
119 changes: 119 additions & 0 deletions
119
packages/react-native-executorch/cpp/core/conversions.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| #include "conversions.h" | ||
| #include <cmath> | ||
| #include <limits> | ||
|
|
||
| namespace rnexecutorch::core::conversions { | ||
|
|
||
| constexpr auto kMaxInt64Double = static_cast<double>(std::numeric_limits<int64_t>::max()); | ||
| constexpr auto kMinInt64Double = static_cast<double>(std::numeric_limits<int64_t>::min()); | ||
| constexpr auto kMaxUint64Double = static_cast<double>(std::numeric_limits<uint64_t>::max()); | ||
|
|
||
| constexpr auto kMinInt32Double = static_cast<double>(std::numeric_limits<int32_t>::min()); | ||
| constexpr auto kMaxInt32Double = static_cast<double>(std::numeric_limits<int32_t>::max()); | ||
|
|
||
| constexpr auto kMinUint8Double = static_cast<double>(std::numeric_limits<uint8_t>::min()); | ||
| constexpr auto kMaxUint8Double = static_cast<double>(std::numeric_limits<uint8_t>::max()); | ||
|
|
||
| template <> | ||
| double asType<double>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isNumber()) { | ||
| throw jsi::JSError(rt, ctx + " must be a number"); | ||
| } | ||
| return val.asNumber(); | ||
| } | ||
|
|
||
| template <> | ||
| float asType<float>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| return static_cast<float>(asType<double>(rt, ctx, val)); | ||
| } | ||
|
|
||
| template <> | ||
| int32_t asType<int32_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| double v = asType<double>(rt, ctx, val); | ||
| if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinInt32Double || v > kMaxInt32Double) { | ||
| throw jsi::JSError(rt, ctx + " must be a 32-bit integer"); | ||
| } | ||
| return static_cast<int32_t>(v); | ||
| } | ||
|
|
||
| template <> | ||
| int64_t asType<int64_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| double v = asType<double>(rt, ctx, val); | ||
| if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinInt64Double || v >= kMaxInt64Double) { | ||
| throw jsi::JSError(rt, ctx + " must be a 64-bit integer"); | ||
| } | ||
| return static_cast<int64_t>(v); | ||
| } | ||
|
|
||
| template <> | ||
| uint64_t asType<uint64_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| double v = asType<double>(rt, ctx, val); | ||
| if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < 0.0 || v >= kMaxUint64Double) { | ||
| throw jsi::JSError(rt, ctx + " must be a non-negative integer"); | ||
| } | ||
| return static_cast<uint64_t>(v); | ||
| } | ||
|
|
||
| template <> | ||
| uint8_t asType<uint8_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| double v = asType<double>(rt, ctx, val); | ||
| if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinUint8Double || v > kMaxUint8Double) { | ||
| throw jsi::JSError(rt, ctx + " must be an integer between 0 and 255"); | ||
| } | ||
| return static_cast<uint8_t>(v); | ||
| } | ||
|
|
||
| template <> | ||
| bool asType<bool>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isBool()) { | ||
| throw jsi::JSError(rt, ctx + " must be a boolean"); | ||
| } | ||
| return val.asBool(); | ||
| } | ||
|
|
||
| template <> | ||
| std::string asType<std::string>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isString()) { | ||
| throw jsi::JSError(rt, ctx + " must be a string"); | ||
| } | ||
| return val.asString(rt).utf8(rt); | ||
| } | ||
|
|
||
| template <> | ||
| jsi::Value asType<jsi::Value>(jsi::Runtime &rt, const std::string & /*ctx*/, const jsi::Value &val) { | ||
| return jsi::Value(rt, val); | ||
| } | ||
|
|
||
| template <> | ||
| jsi::ArrayBuffer asType<jsi::ArrayBuffer>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isObject() || !val.asObject(rt).isArrayBuffer(rt)) { | ||
| throw jsi::JSError(rt, ctx + " must be an ArrayBuffer"); | ||
| } | ||
| return val.asObject(rt).getArrayBuffer(rt); | ||
| } | ||
|
|
||
| template <> | ||
| jsi::Object asType<jsi::Object>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isObject()) { | ||
| throw jsi::JSError(rt, ctx + " must be an object"); | ||
| } | ||
| return val.asObject(rt); | ||
| } | ||
|
|
||
| template <> | ||
| jsi::Array asType<jsi::Array>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isObject() || !val.asObject(rt).isArray(rt)) { | ||
| throw jsi::JSError(rt, ctx + " must be an Array"); | ||
| } | ||
| return val.asObject(rt).asArray(rt); | ||
| } | ||
|
|
||
| template <> | ||
| jsi::Function asType<jsi::Function>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| if (!val.isObject() || !val.asObject(rt).isFunction(rt)) { | ||
| throw jsi::JSError(rt, ctx + " must be a function"); | ||
| } | ||
| return val.asObject(rt).asFunction(rt); | ||
| } | ||
|
|
||
| } // namespace rnexecutorch::core::conversions | ||
139 changes: 139 additions & 0 deletions
139
packages/react-native-executorch/cpp/core/conversions.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| #pragma once | ||
|
|
||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <format> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <type_traits> | ||
| #include <vector> | ||
|
|
||
| #include <jsi/jsi.h> | ||
|
|
||
| namespace rnexecutorch::core::conversions { | ||
| namespace jsi = facebook::jsi; | ||
|
|
||
| /** | ||
| * Converts a facebook::jsi::Value to a specified target C++ or JSI type. Throws | ||
| * a facebook::jsi::JSError with contextual error info if the conversion fails. | ||
| * | ||
| * @tparam T The target type to convert to. | ||
| * @param rt The JSI runtime instance. | ||
| * @param ctx Context description used to generate helpful error messages. | ||
| * @param val The JSI value to convert. | ||
| * @return The converted value of type T. | ||
| */ | ||
| template <typename T> | ||
| T asType(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) = delete; | ||
|
|
||
| // NOLINTNEXTLINE(cppcoreguidelines-macro-usage): macro is used for succinct template specialization declarations | ||
| #define DECLARE_ASTYPE_SPECIALIZATION(Type) \ | ||
| template <> \ | ||
| Type asType<Type>(jsi::Runtime & rt, const std::string &ctx, const jsi::Value &val) | ||
| DECLARE_ASTYPE_SPECIALIZATION(double); | ||
| DECLARE_ASTYPE_SPECIALIZATION(float); | ||
| DECLARE_ASTYPE_SPECIALIZATION(int32_t); | ||
| DECLARE_ASTYPE_SPECIALIZATION(int64_t); | ||
| DECLARE_ASTYPE_SPECIALIZATION(uint64_t); | ||
| DECLARE_ASTYPE_SPECIALIZATION(uint8_t); | ||
| DECLARE_ASTYPE_SPECIALIZATION(bool); | ||
| DECLARE_ASTYPE_SPECIALIZATION(std::string); | ||
| DECLARE_ASTYPE_SPECIALIZATION(jsi::Value); | ||
| DECLARE_ASTYPE_SPECIALIZATION(jsi::Object); | ||
| DECLARE_ASTYPE_SPECIALIZATION(jsi::Array); | ||
| DECLARE_ASTYPE_SPECIALIZATION(jsi::Function); | ||
| DECLARE_ASTYPE_SPECIALIZATION(jsi::ArrayBuffer); | ||
| #undef DECLARE_ASTYPE_SPECIALIZATION | ||
|
|
||
| /** | ||
| * Retrieves a required property from a JSI object, converting it to the | ||
| * specified type. Throws a facebook::jsi::JSError if the property is missing or | ||
| * of an incorrect type. | ||
| * | ||
| * @tparam T The target type to convert the property to. | ||
| * @param rt The JSI runtime instance. | ||
| * @param ctx Context description used for error messages. | ||
| * @param obj The JSI object containing the property. | ||
| * @param propName The name of the property to retrieve. | ||
| * @return The converted property value. | ||
| */ | ||
| template <typename T> | ||
| T getRequiredProperty(jsi::Runtime &rt, const std::string &ctx, const jsi::Object &obj, const std::string &propName) { | ||
| if (!obj.hasProperty(rt, propName.c_str())) { | ||
| throw jsi::JSError(rt, std::format("{}: option '{}' is required", ctx, propName)); | ||
| } | ||
| return asType<T>(rt, std::format("{}: option '{}'", ctx, propName), obj.getProperty(rt, propName.c_str())); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves an optional property from a JSI object, returning std::nullopt if | ||
| * the property is missing, null, or undefined. Throws a facebook::jsi::JSError | ||
| * if the property exists but cannot be converted to the target type. | ||
| * | ||
| * @tparam T The target type to convert the property to if present. | ||
| * @param rt The JSI runtime instance. | ||
| * @param ctx Context description used for error messages. | ||
| * @param obj The JSI object containing the property. | ||
| * @param propName The name of the property to retrieve. | ||
| * @return An optional containing the converted property value, or std::nullopt. | ||
| */ | ||
| template <typename T> | ||
| std::optional<T> getOptionalProperty(jsi::Runtime &rt, const std::string &ctx, const jsi::Object &obj, const std::string &propName) { | ||
| if (!obj.hasProperty(rt, propName.c_str())) { | ||
| return std::nullopt; | ||
| } | ||
| auto val = obj.getProperty(rt, propName.c_str()); | ||
| if (val.isUndefined() || val.isNull()) { | ||
| return std::nullopt; | ||
| } | ||
| return asType<T>(rt, std::format("{}: option '{}'", ctx, propName), val); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a JSI Array to a std::vector of the specified type. Converts each | ||
| * element of the array individually and propagates index details in the error | ||
| * context. | ||
| * | ||
| * @tparam T The target element type. | ||
| * @param rt The JSI runtime instance. | ||
| * @param ctx Context description used for error messages. | ||
| * @param val The JSI value (must be an Array). | ||
| * @return A std::vector containing the converted elements. | ||
| */ | ||
| template <typename T> | ||
| std::vector<T> asVector(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) { | ||
| auto arr = asType<jsi::Array>(rt, ctx, val); | ||
| std::vector<T> vec; | ||
| const size_t len = arr.size(rt); | ||
| vec.reserve(len); | ||
| for (size_t i = 0; i < len; ++i) { | ||
| vec.push_back(asType<T>(rt, std::format("{}[{}]", ctx, i), arr.getValueAtIndex(rt, i))); | ||
| } | ||
| return vec; | ||
| } | ||
|
|
||
| /** | ||
| * Converts a std::vector of values to a new facebook::jsi::Array. | ||
| * Handles strings, booleans, and numeric types appropriately. | ||
| * | ||
| * @tparam T The element type in the vector. | ||
| * @param rt The JSI runtime instance. | ||
| * @param vec The source vector to convert. | ||
| * @return A new facebook::jsi::Array containing the elements from the vector. | ||
| */ | ||
| template <typename T> | ||
| jsi::Array toJsiArray(jsi::Runtime &rt, const std::vector<T> &vec) { | ||
| jsi::Array arr(rt, vec.size()); | ||
| for (size_t i = 0; i < vec.size(); ++i) { | ||
| if constexpr (std::is_same_v<T, std::string>) { | ||
| arr.setValueAtIndex(rt, i, jsi::String::createFromUtf8(rt, vec[i])); | ||
| } else if constexpr (std::is_same_v<T, bool>) { | ||
| arr.setValueAtIndex(rt, i, jsi::Value(vec[i])); | ||
| } else { | ||
| arr.setValueAtIndex(rt, i, jsi::Value(static_cast<double>(vec[i]))); | ||
| } | ||
| } | ||
| return arr; | ||
| } | ||
|
|
||
| } // namespace rnexecutorch::core::conversions |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.