diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml new file mode 100644 index 0000000000..d0ecea74fd --- /dev/null +++ b/.github/workflows/clang-tidy.yml @@ -0,0 +1,49 @@ +name: clang-tidy + +on: + pull_request: + branches: + - main + - rne-rewrite + paths: + - 'packages/react-native-executorch/cpp/**' + - 'packages/react-native-executorch/.clang-tidy' + - 'packages/react-native-executorch/compile_flags.txt' + - 'packages/react-native-executorch/scripts/clang-tidy.sh' + - '.github/workflows/clang-tidy.yml' + workflow_dispatch: + +jobs: + clang-tidy: + runs-on: ubuntu-latest + # Dormant until the on-demand header artifact is published (#1283): clang-tidy + # must parse the sources, which needs the ExecuTorch/torch headers that are not + # committed. Enable by setting the repo variable ENABLE_CLANG_TIDY=true once a + # release carrying headers.tar.gz exists for the package version. + if: ${{ vars.ENABLE_CLANG_TIDY == 'true' }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup + uses: ./.github/actions/setup + + - name: Install clang-tidy + run: | + sudo apt-get update + sudo apt-get install -y clang-tidy + clang-tidy --version + + - name: Provision ExecuTorch headers + # Reuse the on-demand download flow (#1283). clang-tidy only needs headers + # (it syntax-checks, no linking), so RNET_HEADERS_ONLY fetches just + # headers.tar.gz; RNET_TARGET avoids host platform auto-detection. + working-directory: packages/react-native-executorch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RNET_HEADERS_ONLY: '1' + RNET_TARGET: android-arm64-v8a + run: node scripts/download-libs.js + + - name: Run clang-tidy + run: yarn workspace react-native-executorch lint:cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f890f80fd7..097a37aa8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,6 +79,20 @@ don't conflict. clangd discovers `compile_flags.txt`/`.clangd` automatically fro open file's directory. If you produce a `compile_commands.json` from a native build, clangd will prefer it — it's gitignored and takes precedence over `compile_flags.txt`. +## clang-tidy + +`packages/react-native-executorch/.clang-tidy` adds a conservative, high-signal set of +static checks (bugprone / performance / clang-analyzer) on top of the same database. The +clangd extension surfaces these inline; you can also run them from the command line: + +``` +yarn workspace react-native-executorch lint:cpp # all C++ sources +CLANG_TIDY=$(brew --prefix llvm)/bin/clang-tidy yarn workspace react-native-executorch lint:cpp +``` + +It needs the same provisioned headers as clangd. Suppress an intentional, reviewed finding +with a commented `// NOLINTNEXTLINE(check-name)` rather than loosening the shared config. + # Creating a Pull Request Before writing any code reach out to us to make sure no one is currently working on it, you can always open an issue first. diff --git a/packages/react-native-executorch/.clang-tidy b/packages/react-native-executorch/.clang-tidy new file mode 100644 index 0000000000..fd8423aebd --- /dev/null +++ b/packages/react-native-executorch/.clang-tidy @@ -0,0 +1,74 @@ +# clang-tidy configuration for the react-native-executorch C/C++ sources. +# +# A high-signal set focused on correctness/performance bugs and modern C++ +# guidelines (issue #1271). Check groups are enabled one at a time and the +# findings fixed; it is expected to stay green on our own code. +# +# clang-tidy reads the include flags / standard / defines from compile_flags.txt +# (the same database clangd uses), so it needs the same prerequisites: a provisioned +# third-party/include and `yarn install`. The clangd extension also surfaces these +# checks inline in the editor. +# +# Excluded checks (incompatible with a native tensor/buffer library that interops +# with ExecuTorch over raw pointers, or purely opinionated): +# pro-bounds-pointer-arithmetic / -avoid-unchecked-container-access / +# -constant-array-index — raw buffer pointer math and hot-loop indexing are by design +# pro-type-reinterpret-cast — type-erased byte buffers cast to typed pointers +# avoid-magic-numbers / easily-swappable-parameters / enum-size — opinionated / noisy +# llvm-header-guard — the project uses #pragma once by convention +# llvm-prefer-static-over-anonymous-namespace — anonymous namespaces are a valid +# idiom here and also hold the helpers' shared types/constants +# misc-include-cleaner — ExecuTorch umbrella headers make it misreport includes +# misc-multiple-inheritance — the JSI HostObject + enable_shared_from_this pattern +# misc-non-private-member-variables-in-classes — HostObjects are deliberate public +# data carriers read across extensions +# misc-const-correctness — applied once as a cleanup, but too aggressive to gate on +# (it flags RAII lock guards as const-able); not enforced going forward +# modernize-use-trailing-return-type — the codebase uses traditional return types +# modernize-return-braced-init-list — `return {…}` hides the (JSI) return type +# readability-identifier-length — short names (i, rt, h, w, …) are idiomatic here +# readability-math-missing-parentheses — flags standard array-offset arithmetic +# readability-function-cognitive-complexity — the JSI get() dispatchers and the +# image/tensor kernels are inherently above the threshold +# readability-uppercase-literal-suffix — the codebase uses lowercase (1.0f) consistently +# readability-magic-numbers — opinionated (same as cppcoreguidelines-avoid-magic-numbers) +Checks: > + -*, + bugprone-*, + performance-*, + clang-analyzer-*, + cppcoreguidelines-*, + google-*, + llvm-*, + misc-*, + modernize-*, + readability-*, + -bugprone-easily-swappable-parameters, + -performance-enum-size, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-avoid-magic-numbers, + -llvm-header-guard, + -llvm-prefer-static-over-anonymous-namespace, + -misc-include-cleaner, + -misc-multiple-inheritance, + -misc-non-private-member-variables-in-classes, + -misc-const-correctness, + -modernize-use-trailing-return-type, + -modernize-return-braced-init-list, + -readability-identifier-length, + -readability-math-missing-parentheses, + -readability-function-cognitive-complexity, + -readability-uppercase-literal-suffix, + -readability-magic-numbers + +# Restrict analysis to this package's own cpp/ tree. Anchored on the full package +# path so it can never match an unrelated directory that merely contains "cpp" +# (e.g. third-party/.../abseil-cpp/, or another package's cpp/). Third-party headers +# are also -isystem in compile_flags.txt, so clang-tidy does not analyze them anyway. +HeaderFilterRegex: 'packages/react-native-executorch/cpp/.*\.(h|hpp)$' + +# Treat findings as warnings (CI decides failure via --warnings-as-errors). +WarningsAsErrors: '' diff --git a/packages/react-native-executorch/cpp/RnExecutorch.cpp b/packages/react-native-executorch/cpp/RnExecutorch.cpp index 3b71b61775..3a47fbeb65 100644 --- a/packages/react-native-executorch/cpp/RnExecutorch.cpp +++ b/packages/react-native-executorch/cpp/RnExecutorch.cpp @@ -5,7 +5,7 @@ #include "extensions/math/install.h" #include "extensions/nlp/install.h" -using namespace facebook; +namespace jsi = facebook::jsi; namespace rnexecutorch { void install(jsi::Runtime &jsiRuntime) { diff --git a/packages/react-native-executorch/cpp/core/dtype.cpp b/packages/react-native-executorch/cpp/core/dtype.cpp index 114ac474c7..e744ea4db7 100644 --- a/packages/react-native-executorch/cpp/core/dtype.cpp +++ b/packages/react-native-executorch/cpp/core/dtype.cpp @@ -54,6 +54,8 @@ size_t elementSize(DType dtype) { switch (dtype) { case DType::uint8: return 1; + // int32 and float32 are both 4 bytes; the identical branches are intentional. + // NOLINTNEXTLINE(bugprone-branch-clone) case DType::int32: return 4; case DType::float32: diff --git a/packages/react-native-executorch/cpp/core/dtype.h b/packages/react-native-executorch/cpp/core/dtype.h index b859ae2f83..7259b6b18f 100644 --- a/packages/react-native-executorch/cpp/core/dtype.h +++ b/packages/react-native-executorch/cpp/core/dtype.h @@ -5,7 +5,7 @@ #include namespace rnexecutorch::core::types { -enum DType { +enum class DType { uint8, int32, float32 diff --git a/packages/react-native-executorch/cpp/core/model.cpp b/packages/react-native-executorch/cpp/core/model.cpp index fec9478e58..ba50d9d12b 100644 --- a/packages/react-native-executorch/cpp/core/model.cpp +++ b/packages/react-native-executorch/cpp/core/model.cpp @@ -43,7 +43,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto methodNames = self->etModule_->method_names(); if (!methodNames.ok()) { - std::string errorMsg = executorch::runtime::to_string(methodNames.error()); + const std::string errorMsg = executorch::runtime::to_string(methodNames.error()); throw jsi::JSError(rt, "getMethodNames: Failed to get method names: " + errorMsg); } @@ -82,7 +82,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto methodName = args[0].asString(rt).utf8(rt); auto methodMeta = self->etModule_->method_meta(methodName); if (!methodMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(methodMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(methodMeta.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get method meta: " + errorMsg); } @@ -90,7 +90,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { for (size_t i = 0; i < methodMeta->num_inputs(); ++i) { auto tag = methodMeta->input_tag(i); if (!tag.ok()) { - std::string errorMsg = executorch::runtime::to_string(tag.error()); + const std::string errorMsg = executorch::runtime::to_string(tag.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get input tag for input " + std::to_string(i) + ": " + errorMsg); } inputTagsArray.setValueAtIndex(rt, i, jsi::String::createFromUtf8(rt, executorch::runtime::tag_to_string(tag.get()))); @@ -100,7 +100,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { for (size_t i = 0; i < methodMeta->num_outputs(); ++i) { auto tag = methodMeta->output_tag(i); if (!tag.ok()) { - std::string errorMsg = executorch::runtime::to_string(tag.error()); + const std::string errorMsg = executorch::runtime::to_string(tag.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get output tag for output " + std::to_string(i) + ": " + errorMsg); } outputTagsArray.setValueAtIndex(rt, i, jsi::String::createFromUtf8(rt, executorch::runtime::tag_to_string(tag.get()))); @@ -110,7 +110,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { for (size_t i = 0; i < methodMeta->num_backends(); ++i) { auto backendName = methodMeta->get_backend_name(i); if (!backendName.ok()) { - std::string errorMsg = executorch::runtime::to_string(backendName.error()); + const std::string errorMsg = executorch::runtime::to_string(backendName.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get backend name for backend " + std::to_string(i) + ": " + errorMsg); } usesBackendMap.setProperty(rt, backendName.get(), methodMeta->uses_backend(backendName.get())); @@ -123,7 +123,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { jsTensorMeta.setProperty(rt, "nbytes", static_cast(tensorMeta.nbytes())); try { - std::string dtypeStr = rnexecutorch::core::types::toString(rnexecutorch::core::types::fromScalarType(tensorMeta.scalar_type())); + const std::string dtypeStr = rnexecutorch::core::types::toString(rnexecutorch::core::types::fromScalarType(tensorMeta.scalar_type())); jsTensorMeta.setProperty(rt, "dtype", jsi::String::createFromUtf8(rt, dtypeStr)); } catch (const std::exception &) { jsTensorMeta.setProperty(rt, "dtype", jsi::String::createFromUtf8(rt, "not supported")); @@ -142,7 +142,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { for (size_t i = 0; i < methodMeta->num_inputs(); ++i) { auto tensorMeta = methodMeta->input_tensor_meta(i); if (!tensorMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get tensor meta for input " + std::to_string(i) + ": " + errorMsg); } inputTensorMetaArray.setValueAtIndex(rt, i, tensorMetaToJS(rt, tensorMeta.get())); @@ -152,7 +152,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { for (size_t i = 0; i < methodMeta->num_outputs(); ++i) { auto tensorMeta = methodMeta->output_tensor_meta(i); if (!tensorMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); throw jsi::JSError(rt, "getMethodMeta: Failed to get tensor meta for output " + std::to_string(i) + ": " + errorMsg); } outputTensorMetaArray.setValueAtIndex(rt, i, tensorMetaToJS(rt, tensorMeta.get())); @@ -207,14 +207,14 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto inputsArray = args[1].asObject(rt).asArray(rt); if (!methodMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(methodMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(methodMeta.error()); throw jsi::JSError(rt, "execute: Failed to get method meta for '" + methodName + "': " + errorMsg); } if (inputsArray.size(rt) != methodMeta->num_inputs()) { - std::string errorMsg = "execute: Incorrect size for inputs: got " + - std::to_string(inputsArray.size(rt)) + - ", expected " + std::to_string(methodMeta->num_inputs()); + const std::string errorMsg = "execute: Incorrect size for inputs: got " + + std::to_string(inputsArray.size(rt)) + + ", expected " + std::to_string(methodMeta->num_inputs()); throw jsi::JSError(rt, errorMsg); } @@ -252,7 +252,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto val = inputsArray.getValueAtIndex(rt, i); if (!tag.ok()) { - std::string errorMsg = executorch::runtime::to_string(tag.error()); + const std::string errorMsg = executorch::runtime::to_string(tag.error()); throw jsi::JSError(rt, "execute: Failed to get input tag for inputs[" + std::to_string(i) + "]: " + errorMsg); } @@ -290,7 +290,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto tensorMeta = methodMeta->input_tensor_meta(i); if (!tensorMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); throw jsi::JSError(rt, "execute: Failed to get tensor meta for inputs[" + std::to_string(i) + "]: " + errorMsg); } @@ -343,7 +343,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { #endif if (!result.ok()) { - std::string errorMsg = executorch::runtime::to_string(result.error()); + const std::string errorMsg = executorch::runtime::to_string(result.error()); throw jsi::JSError(rt, "execute: Method '" + methodName + "' execution failed: " + errorMsg + ". This may be due to missing required backends - use getMethodMeta()" + " to check required backends and getExecuTorchRegisteredBackends()" + @@ -392,7 +392,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto tensorMeta = methodMeta->output_tensor_meta(index); if (!tensorMeta.ok()) { - std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); + const std::string errorMsg = executorch::runtime::to_string(tensorMeta.error()); throw jsi::JSError(rt, "execute: Failed to get tensor meta for output at index " + std::to_string(index) + ": " + errorMsg); } @@ -467,7 +467,7 @@ std::vector ModelHostObject::getPropertyNames(jsi::Ru } void install_loadModel(jsi::Runtime &rt, jsi::Object &module) { - auto name = "loadModel"; + const auto *name = "loadModel"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 1) { throw jsi::JSError(rt, "loadModel: Usage: loadModel(arg0)"); @@ -482,7 +482,7 @@ void install_loadModel(jsi::Runtime &rt, jsi::Object &module) { auto error = modelInstance->etModule_->load(); if (!modelInstance->etModule_->is_loaded()) { - std::string errorMsg = executorch::runtime::to_string(error); + const std::string errorMsg = executorch::runtime::to_string(error); throw jsi::JSError(rt, "loadModel: Failed to load model: " + errorMsg); } diff --git a/packages/react-native-executorch/cpp/core/model.h b/packages/react-native-executorch/cpp/core/model.h index abb780309f..4cc8bd0647 100644 --- a/packages/react-native-executorch/cpp/core/model.h +++ b/packages/react-native-executorch/cpp/core/model.h @@ -24,7 +24,7 @@ class ModelHostObject : public facebook::jsi::HostObject, public std::enable_sha std::unique_ptr etModule_; std::mutex mutex_; - ModelHostObject(const std::string &modelPath); + explicit ModelHostObject(const std::string &modelPath); facebook::jsi::Value get(facebook::jsi::Runtime &rt, const facebook::jsi::PropNameID &name) override; std::vector getPropertyNames(facebook::jsi::Runtime &rt) override; diff --git a/packages/react-native-executorch/cpp/core/tensor.cpp b/packages/react-native-executorch/cpp/core/tensor.cpp index 83bec4afaa..22950844ef 100644 --- a/packages/react-native-executorch/cpp/core/tensor.cpp +++ b/packages/react-native-executorch/cpp/core/tensor.cpp @@ -5,14 +5,14 @@ namespace rnexecutorch::core::tensor { namespace jsi = facebook::jsi; -TensorHostObject::TensorHostObject(const std::vector &shape, rnexecutorch::core::types::DType dtype) { +TensorHostObject::TensorHostObject(const std::vector &shape, rnexecutorch::core::types::DType dtype) + : dtype_(dtype), + shape_(shape), + numel_(std::accumulate(shape.begin(), shape.end(), static_cast(1), std::multiplies<>())) { const auto elemSize = rnexecutorch::core::types::elementSize(dtype); - dtype_ = dtype; - shape_ = shape; - numel_ = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); - size_ = numel_ * elemSize; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) — owning runtime-sized byte buffer data_ = std::make_unique(size_); tensor_ = executorch::extension::from_blob(data_.get(), shape_, rnexecutorch::core::types::toScalarType(dtype)); } @@ -93,12 +93,12 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, "setData: Expected array to be an object (TypedArray)"); } - jsi::Object dataObj = args[0].asObject(rt); + const jsi::Object dataObj = args[0].asObject(rt); if (!dataObj.hasProperty(rt, "buffer")) { throw jsi::JSError(rt, "setData: Expected a TypedArray with a 'buffer' property"); } - jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); + const jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); size_t byteOffset = 0; size_t byteLength = buffer.size(rt); @@ -128,9 +128,9 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) } if (byteLength != self->size_) { - std::string errorMsg = "setData: Data size mismatch: TypedArray is " + std::to_string(byteLength) + - " bytes, but Tensor requires " + std::to_string(self->size_) + - " bytes."; + const std::string errorMsg = "setData: Data size mismatch: TypedArray is " + std::to_string(byteLength) + + " bytes, but Tensor requires " + std::to_string(self->size_) + + " bytes."; throw jsi::JSError(rt, errorMsg); } @@ -152,12 +152,12 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, "getData: Expected array to be an object (TypedArray)"); } - jsi::Object dataObj = args[0].asObject(rt); + const jsi::Object dataObj = args[0].asObject(rt); if (!dataObj.hasProperty(rt, "buffer")) { throw jsi::JSError(rt, "getData: Expected a TypedArray with a 'buffer' property"); } - jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); + const jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); size_t byteOffset = 0; size_t byteLength = buffer.size(rt); @@ -187,9 +187,9 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) } if (byteLength != self->size_) { - std::string errorMsg = "getData: Data size mismatch: TypedArray is " + std::to_string(byteLength) + - " bytes, but Tensor requires " + std::to_string(self->size_) + - " bytes."; + const std::string errorMsg = "getData: Data size mismatch: TypedArray is " + std::to_string(byteLength) + + " bytes, but Tensor requires " + std::to_string(self->size_) + + " bytes."; throw jsi::JSError(rt, errorMsg); } @@ -215,9 +215,9 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) std::vector fnArgs; fnArgs.reserve(count); - fnArgs.push_back(jsi::Value(rt, thisVal)); + fnArgs.emplace_back(rt, thisVal); for (size_t i = 1; i < count; ++i) { - fnArgs.push_back(jsi::Value(rt, args[i])); + fnArgs.emplace_back(rt, args[i]); } return fn.call(rt, static_cast(fnArgs.data()), fnArgs.size()); @@ -233,7 +233,7 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, "throughIf: Usage: throughIf(pred, fn, ...args)"); } - bool pred = args[0].asBool(); + const bool pred = args[0].asBool(); if (!pred) { return jsi::Value(rt, thisVal); } @@ -246,9 +246,9 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) std::vector fnArgs; fnArgs.reserve(count - 1); - fnArgs.push_back(jsi::Value(rt, thisVal)); + fnArgs.emplace_back(rt, thisVal); for (size_t i = 2; i < count; ++i) { - fnArgs.push_back(jsi::Value(rt, args[i])); + fnArgs.emplace_back(rt, args[i]); } return fn.call(rt, static_cast(fnArgs.data()), fnArgs.size()); @@ -296,7 +296,7 @@ std::vector TensorHostObject::getPropertyNames(jsi::R } void install_createTensor(jsi::Runtime &rt, jsi::Object &module) { - auto name = "createTensor"; + const auto *name = "createTensor"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 2) { throw jsi::JSError(rt, "createTensor: Usage: createTensor(shape, dtype)"); diff --git a/packages/react-native-executorch/cpp/core/tensor.h b/packages/react-native-executorch/cpp/core/tensor.h index a20dfc54ec..68f1033ef1 100644 --- a/packages/react-native-executorch/cpp/core/tensor.h +++ b/packages/react-native-executorch/cpp/core/tensor.h @@ -28,7 +28,7 @@ class TensorHostObject : public facebook::jsi::HostObject, public std::enable_sh size_t numel_; size_t size_; - std::unique_ptr data_; + std::unique_ptr data_; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) executorch::extension::TensorPtr tensor_; std::shared_mutex mutex_; diff --git a/packages/react-native-executorch/cpp/core/utils.cpp b/packages/react-native-executorch/cpp/core/utils.cpp index 1e64763bd8..512e374934 100644 --- a/packages/react-native-executorch/cpp/core/utils.cpp +++ b/packages/react-native-executorch/cpp/core/utils.cpp @@ -9,7 +9,7 @@ namespace rnexecutorch::core::utils { namespace jsi = facebook::jsi; void install_getExecuTorchRegisteredBackends(jsi::Runtime &rt, jsi::Object &module) { - auto name = "getExecuTorchRegisteredBackends"; + const auto *name = "getExecuTorchRegisteredBackends"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value * /*args*/, size_t count) -> jsi::Value { if (count != 0) { throw jsi::JSError(rt, "Usage: getExecuTorchRegisteredBackends()"); @@ -20,7 +20,7 @@ void install_getExecuTorchRegisteredBackends(jsi::Runtime &rt, jsi::Object &modu for (size_t i = 0; i < registeredCount; ++i) { auto backendName = executorch::runtime::get_backend_name(i); if (!backendName.ok()) { - std::string errorMsg = executorch::runtime::to_string(backendName.error()); + const std::string errorMsg = executorch::runtime::to_string(backendName.error()); throw jsi::JSError(rt, "Failed to get backend name: " + errorMsg); } jsArray.setValueAtIndex(rt, i, jsi::String::createFromUtf8(rt, backendName.get())); diff --git a/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp b/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp index 5d42fc5097..c5c87e8253 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp @@ -66,7 +66,7 @@ std::array decodeToXyxy( } // namespace void install_nms(jsi::Runtime &rt, jsi::Object &module) { - auto name = "nms"; + const auto *name = "nms"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count < 3) { throw jsi::JSError(rt, "Usage: nms(boxes, scores, options)"); @@ -96,14 +96,14 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "nms: options must specify iouThreshold, boxFormat, confidenceThreshold, and nmsType"); } - float iouThreshold = static_cast(opts.getProperty(rt, "iouThreshold").asNumber()); - float confidenceThreshold = static_cast(opts.getProperty(rt, "confidenceThreshold").asNumber()); + const float iouThreshold = static_cast(opts.getProperty(rt, "iouThreshold").asNumber()); + const float confidenceThreshold = static_cast(opts.getProperty(rt, "confidenceThreshold").asNumber()); - std::string nmsTypeStr = opts.getProperty(rt, "nmsType").asString(rt).utf8(rt); - std::string boxFormatStr = opts.getProperty(rt, "boxFormat").asString(rt).utf8(rt); + const std::string nmsTypeStr = opts.getProperty(rt, "nmsType").asString(rt).utf8(rt); + const std::string boxFormatStr = opts.getProperty(rt, "boxFormat").asString(rt).utf8(rt); - NmsType nmsType; - BoxFormat boxFormat; + NmsType nmsType{}; + BoxFormat boxFormat{}; try { nmsType = parseNmsType(nmsTypeStr); boxFormat = parseBoxFormat(boxFormatStr); @@ -139,8 +139,8 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "nms: boxes and scores must have dtype float32"); } - const float *boxesPtr = reinterpret_cast(boxes->data_.get()); - const float *scoresPtr = reinterpret_cast(scores->data_.get()); + const auto *boxesPtr = reinterpret_cast(boxes->data_.get()); + const auto *scoresPtr = reinterpret_cast(scores->data_.get()); std::vector> candidates; candidates.reserve(static_cast(numAnchors)); @@ -176,7 +176,7 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { boxesPtr[idxI * 4 + 3], boxFormat); - float areaA = (xmaxA - xminA) * (ymaxA - yminA); + const float areaA = (xmaxA - xminA) * (ymaxA - yminA); std::vector overlapping = {idxI}; @@ -194,19 +194,19 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { boxesPtr[idxJ * 4 + 3], boxFormat); - float areaB = (xmaxB - xminB) * (ymaxB - yminB); + const float areaB = (xmaxB - xminB) * (ymaxB - yminB); - float interYMin = std::max(yminA, yminB); - float interXMin = std::max(xminA, xminB); - float interYMax = std::min(ymaxA, ymaxB); - float interXMax = std::min(xmaxA, xmaxB); + const float interYMin = std::max(yminA, yminB); + const float interXMin = std::max(xminA, xminB); + const float interYMax = std::min(ymaxA, ymaxB); + const float interXMax = std::min(xmaxA, xmaxB); - float interH = std::max(0.0f, interYMax - interYMin); - float interW = std::max(0.0f, interXMax - interXMin); - float intersection = interH * interW; + const float interH = std::max(0.0f, interYMax - interYMin); + const float interW = std::max(0.0f, interXMax - interXMin); + const float intersection = interH * interW; - float unionArea = areaA + areaB - intersection; - float iou = (unionArea > 0.0f) ? (intersection / unionArea) : 0.0f; + const float unionArea = areaA + areaB - intersection; + const float iou = (unionArea > 0.0f) ? (intersection / unionArea) : 0.0f; if (iou > iouThreshold) { if (nmsType == NmsType::Weighted) { @@ -230,7 +230,7 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { case NmsType::Weighted: { jsi::Array resultGroups = jsi::Array(rt, groups.size()); for (size_t i = 0; i < groups.size(); ++i) { - jsi::Array singleGroup = jsi::Array(rt, groups[i].size()); + const jsi::Array singleGroup = jsi::Array(rt, groups[i].size()); for (size_t j = 0; j < groups[i].size(); ++j) { singleGroup.setValueAtIndex(rt, j, jsi::Value(static_cast(groups[i][j]))); } diff --git a/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp b/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp index 2511bb69e1..e27a44de83 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp @@ -52,19 +52,19 @@ struct FitBox { }; FitBox computeFit(int32_t srcW, int32_t srcH, int32_t dstW, int32_t dstH, bool inner) { - double scaleW = static_cast(dstW) / srcW; - double scaleH = static_cast(dstH) / srcH; - double scale = inner ? std::min(scaleW, scaleH) : std::max(scaleW, scaleH); - - int32_t w = static_cast(std::round(srcW * scale)); - int32_t h = static_cast(std::round(srcH * scale)); - int32_t sign = inner ? 1 : -1; // letterbox centers padding, crop centers the crop - return {w, h, sign * (dstW - w) / 2, sign * (dstH - h) / 2}; + const double scaleW = static_cast(dstW) / srcW; + const double scaleH = static_cast(dstH) / srcH; + const double scale = inner ? std::min(scaleW, scaleH) : std::max(scaleW, scaleH); + + const auto w = static_cast(std::round(srcW * scale)); + const auto h = static_cast(std::round(srcH * scale)); + const int32_t sign = inner ? 1 : -1; // letterbox centers padding, crop centers the crop + return {.w = w, .h = h, .offX = sign * (dstW - w) / 2, .offY = sign * (dstH - h) / 2}; } } // namespace void install_resize(jsi::Runtime &rt, jsi::Object &module) { - auto name = "resize"; + const auto *name = "resize"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: resize(src, dst, options)"); @@ -104,7 +104,7 @@ void install_resize(jsi::Runtime &rt, jsi::Object &module) { auto mode = opts.getProperty(rt, "mode").asString(rt).utf8(rt); auto interp = opts.getProperty(rt, "interpolation").asString(rt).utf8(rt); - double padValue = opts.getProperty(rt, "padValue").asNumber(); + const double padValue = opts.getProperty(rt, "padValue").asNumber(); if (src->shape_.size() != 3) { throw jsi::JSError(rt, "resize: src must be [H, W, C]"); @@ -142,13 +142,14 @@ void install_resize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "resize: dst tensor has been disposed"); } - int32_t srcH = src->shape_[0]; - int32_t srcW = src->shape_[1]; - int32_t channels = src->shape_[2]; - int32_t dstH = dst->shape_[0]; - int32_t dstW = dst->shape_[1]; + const int32_t srcH = src->shape_[0]; + const int32_t srcW = src->shape_[1]; + const int32_t channels = src->shape_[2]; + const int32_t dstH = dst->shape_[0]; + const int32_t dstW = dst->shape_[1]; - int cvType, interpFlag; + int cvType{}; + int interpFlag{}; try { cvType = CV_MAKETYPE(dtypeToCvDepth(src->dtype_), channels); interpFlag = interpToFlag(interp); @@ -156,19 +157,19 @@ void install_resize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "resize: " + std::string(e.what())); } - ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); ::cv::Mat dstMat(dstH, dstW, cvType, dst->data_.get()); if (mode == "stretch") { ::cv::resize(srcMat, dstMat, dstMat.size(), 0, 0, interpFlag); } else if (mode == "letterbox") { - FitBox fit = computeFit(srcW, srcH, dstW, dstH, /*inner=*/true); + const FitBox fit = computeFit(srcW, srcH, dstW, dstH, /*inner=*/true); dstMat.setTo(::cv::Scalar::all(padValue)); ::cv::Mat roi = dstMat(::cv::Rect(fit.offX, fit.offY, fit.w, fit.h)); ::cv::resize(srcMat, roi, roi.size(), 0, 0, interpFlag); } else if (mode == "crop") { - FitBox fit = computeFit(srcW, srcH, dstW, dstH, /*inner=*/false); + const FitBox fit = computeFit(srcW, srcH, dstW, dstH, /*inner=*/false); ::cv::Mat scaled; ::cv::resize(srcMat, scaled, ::cv::Size(fit.w, fit.h), 0, 0, interpFlag); @@ -232,7 +233,7 @@ int codeToColorConversionFlag(const std::string &code) { } // namespace void install_cvtColor(jsi::Runtime &rt, jsi::Object &module) { - auto name = "cvtColor"; + const auto *name = "cvtColor"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: cvtColor(src, dst, code)"); @@ -292,18 +293,20 @@ void install_cvtColor(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "cvtColor: dst tensor has been disposed"); } - int32_t srcH = src->shape_[0]; - int32_t srcW = src->shape_[1]; - int32_t srcC = src->shape_[2]; - int32_t dstC = dst->shape_[2]; + const int32_t srcH = src->shape_[0]; + const int32_t srcW = src->shape_[1]; + const int32_t srcC = src->shape_[2]; + const int32_t dstC = dst->shape_[2]; - int cvSrcType, cvDstType, flag; + int cvSrcType{}; + int cvDstType{}; + int flag{}; try { cvSrcType = CV_MAKETYPE(dtypeToCvDepth(src->dtype_), srcC); cvDstType = CV_MAKETYPE(dtypeToCvDepth(dst->dtype_), dstC); flag = codeToColorConversionFlag(code); - ::cv::Mat srcMat(srcH, srcW, cvSrcType, src->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvSrcType, src->data_.get()); ::cv::Mat dstMat(srcH, srcW, cvDstType, dst->data_.get()); ::cv::cvtColor(srcMat, dstMat, flag); @@ -318,7 +321,7 @@ void install_cvtColor(jsi::Runtime &rt, jsi::Object &module) { } void install_toChannelsFirst(jsi::Runtime &rt, jsi::Object &module) { - auto name = "toChannelsFirst"; + const auto *name = "toChannelsFirst"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 2) { throw jsi::JSError(rt, "Usage: toChannelsFirst(src, dst)"); @@ -347,16 +350,16 @@ void install_toChannelsFirst(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "toChannelsFirst: src and dst must have the same dtype"); } - int32_t srcH = src->shape_[0]; - int32_t srcW = src->shape_[1]; - int32_t srcC = src->shape_[2]; + const int32_t srcH = src->shape_[0]; + const int32_t srcW = src->shape_[1]; + const int32_t srcC = src->shape_[2]; if (dst->shape_.size() != 3) { throw jsi::JSError(rt, "toChannelsFirst: dst must be a 3D tensor [C, H, W]"); } - int32_t dstC = dst->shape_[0]; - int32_t dstH = dst->shape_[1]; - int32_t dstW = dst->shape_[2]; + const int32_t dstC = dst->shape_[0]; + const int32_t dstH = dst->shape_[1]; + const int32_t dstW = dst->shape_[2]; if (srcH != dstH || srcW != dstW || srcC != dstC) { throw jsi::JSError(rt, "toChannelsFirst: src and dst spatial dimensions and channel counts must match"); @@ -380,19 +383,19 @@ void install_toChannelsFirst(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "toChannelsFirst: dst tensor has been disposed"); } - int cvType; + int cvType{}; try { cvType = CV_MAKETYPE(dtypeToCvDepth(src->dtype_), srcC); } catch (const std::invalid_argument &e) { throw jsi::JSError(rt, "toChannelsFirst: " + std::string(e.what())); } - ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); std::vector<::cv::Mat> channels; ::cv::split(srcMat, channels); - size_t hw = static_cast(srcH) * static_cast(srcW); - size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); + const size_t hw = static_cast(srcH) * static_cast(srcW); + const size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); uint8_t *dstPtr = dst->data_.get(); for (size_t i = 0; std::cmp_less(i, srcC); ++i) { @@ -406,7 +409,7 @@ void install_toChannelsFirst(jsi::Runtime &rt, jsi::Object &module) { } void install_toChannelsLast(jsi::Runtime &rt, jsi::Object &module) { - auto name = "toChannelsLast"; + const auto *name = "toChannelsLast"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 2) { throw jsi::JSError(rt, "Usage: toChannelsLast(src, dst)"); @@ -435,16 +438,16 @@ void install_toChannelsLast(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "toChannelsLast: src and dst must have the same dtype"); } - int32_t srcC = src->shape_[0]; - int32_t srcH = src->shape_[1]; - int32_t srcW = src->shape_[2]; + const int32_t srcC = src->shape_[0]; + const int32_t srcH = src->shape_[1]; + const int32_t srcW = src->shape_[2]; if (dst->shape_.size() != 3) { throw jsi::JSError(rt, "toChannelsLast: dst must be a 3D tensor [H, W, C]"); } - int32_t dstH = dst->shape_[0]; - int32_t dstW = dst->shape_[1]; - int32_t dstC = dst->shape_[2]; + const int32_t dstH = dst->shape_[0]; + const int32_t dstW = dst->shape_[1]; + const int32_t dstC = dst->shape_[2]; if (srcH != dstH || srcW != dstW || srcC != dstC) { throw jsi::JSError(rt, "toChannelsLast: src and dst spatial dimensions and channel counts must match"); @@ -468,20 +471,20 @@ void install_toChannelsLast(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "toChannelsLast: dst tensor has been disposed"); } - int cvDepth; + int cvDepth{}; try { cvDepth = dtypeToCvDepth(src->dtype_); } catch (const std::invalid_argument &e) { throw jsi::JSError(rt, "toChannelsLast: " + std::string(e.what())); } - size_t hw = static_cast(srcH) * static_cast(srcW); - size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); + const size_t hw = static_cast(srcH) * static_cast(srcW); + const size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); uint8_t *srcPtr = src->data_.get(); std::vector<::cv::Mat> channels; for (size_t i = 0; std::cmp_less(i, srcC); ++i) { - channels.push_back(::cv::Mat(srcH, srcW, cvDepth, srcPtr + i * hw * elemSize)); + channels.emplace_back(srcH, srcW, cvDepth, srcPtr + i * hw * elemSize); } ::cv::Mat dstMat(dstH, dstW, CV_MAKETYPE(cvDepth, dstC), dst->data_.get()); @@ -494,7 +497,7 @@ void install_toChannelsLast(jsi::Runtime &rt, jsi::Object &module) { } void install_normalize(jsi::Runtime &rt, jsi::Object &module) { - auto name = "normalize"; + const auto *name = "normalize"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: normalize(src, dst, options)"); @@ -525,8 +528,8 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { } int32_t c = src->shape_[0]; - int32_t h = src->shape_[1]; - int32_t w = src->shape_[2]; + const int32_t h = src->shape_[1]; + const int32_t w = src->shape_[2]; if (dst->shape_.size() != 3 || dst->shape_[0] != c || @@ -588,8 +591,8 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "normalize: dst tensor has been disposed"); } - int srcDepthType; - int dstDepthType; + int srcDepthType{}; + int dstDepthType{}; try { srcDepthType = dtypeToCvDepth(src->dtype_); dstDepthType = dtypeToCvDepth(dst->dtype_); @@ -597,14 +600,14 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "normalize: " + std::string(e.what())); } - size_t srcElemSize = rnexecutorch::core::types::elementSize(src->dtype_); - size_t dstElemSize = rnexecutorch::core::types::elementSize(dst->dtype_); + const size_t srcElemSize = rnexecutorch::core::types::elementSize(src->dtype_); + const size_t dstElemSize = rnexecutorch::core::types::elementSize(dst->dtype_); uint8_t *srcPtr = src->data_.get(); uint8_t *dstPtr = dst->data_.get(); const size_t plane = static_cast(h) * static_cast(w); for (size_t ch = 0; std::cmp_less(ch, c); ++ch) { - ::cv::Mat srcChannel(h, w, srcDepthType, srcPtr + ch * plane * srcElemSize); + const ::cv::Mat srcChannel(h, w, srcDepthType, srcPtr + ch * plane * srcElemSize); ::cv::Mat dstChannel(h, w, dstDepthType, dstPtr + ch * plane * dstElemSize); srcChannel.convertTo(dstChannel, dstDepthType, alpha[ch], beta[ch]); @@ -617,7 +620,7 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { } void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { - auto name = "applyColormap"; + const auto *name = "applyColormap"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: applyColormap(src, dst, colormap)"); @@ -649,7 +652,7 @@ void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { } auto colormapArray = args[2].asObject(rt).asArray(rt); - size_t numColors = colormapArray.size(rt); + const size_t numColors = colormapArray.size(rt); std::vector> lut(numColors); for (size_t i = 0; i < numColors; ++i) { auto colorVal = colormapArray.getValueAtIndex(rt, i); @@ -665,7 +668,7 @@ void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { if (!channelVal.isNumber()) { throw jsi::JSError(rt, "applyColormap: colormap channel value must be a number"); } - double val = channelVal.asNumber(); + const double val = channelVal.asNumber(); if (std::isnan(val) || val < 0.0 || val > 255.0) { throw jsi::JSError(rt, "applyColormap: colormap channel value must be between 0 and 255"); } @@ -683,14 +686,14 @@ void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "applyColormap: tensor has been disposed"); } - size_t pixels = src->numel_; + const size_t pixels = src->numel_; - const int32_t *srcData = reinterpret_cast(src->data_.get()); + const auto *srcData = reinterpret_cast(src->data_.get()); uint8_t *dstData = dst->data_.get(); for (size_t i = 0; i < pixels; ++i) { - int32_t idx = srcData[i]; - if (idx < 0 || static_cast(idx) >= numColors) { + const int32_t idx = srcData[i]; + if (idx < 0 || std::cmp_greater_equal(idx, numColors)) { throw jsi::JSError(rt, "applyColormap: tensor contains class index (" + std::to_string(idx) + ") that exceeds provided colormap size (" + std::to_string(numColors) + ")"); diff --git a/packages/react-native-executorch/cpp/extensions/math/operations.cpp b/packages/react-native-executorch/cpp/extensions/math/operations.cpp index 4d6fdde5ff..9f72704a08 100644 --- a/packages/react-native-executorch/cpp/extensions/math/operations.cpp +++ b/packages/react-native-executorch/cpp/extensions/math/operations.cpp @@ -12,7 +12,7 @@ namespace jsi = facebook::jsi; using TensorHostObject = rnexecutorch::core::tensor::TensorHostObject; void install_sigmoid(jsi::Runtime &rt, jsi::Object &module) { - auto name = "sigmoid"; + const auto *name = "sigmoid"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 2) { throw jsi::JSError(rt, "Usage: sigmoid(src, dst)"); @@ -78,7 +78,7 @@ void install_sigmoid(jsi::Runtime &rt, jsi::Object &module) { } void install_softmax(jsi::Runtime &rt, jsi::Object &module) { - auto name = "softmax"; + const auto *name = "softmax"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: softmax(src, dst, axis)"); @@ -120,7 +120,7 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { } int axis = static_cast(args[2].asNumber()); - int rank = static_cast(src->shape_.size()); + const int rank = static_cast(src->shape_.size()); // Support negative axis indices like numpy (e.g., axis=-1 means last // axis, -2 means second to last, etc.) @@ -130,7 +130,7 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { if (axis < 0 || axis >= rank) { throw jsi::JSError(rt, "softmax: axis is out of range"); } - const size_t axisIdx = static_cast(axis); + const auto axisIdx = static_cast(axis); std::shared_lock srcLock(src->mutex_, std::try_to_lock); if (!srcLock.owns_lock()) { @@ -153,7 +153,7 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { const auto *srcData = reinterpret_cast(src->data_.get()); auto *dstData = reinterpret_cast(dst->data_.get()); - const size_t axisDim = static_cast(src->shape_[axisIdx]); + const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { throw jsi::JSError(rt, "softmax: axis dimension must be greater than zero"); } @@ -197,7 +197,7 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { } void install_argmax(jsi::Runtime &rt, jsi::Object &module) { - auto name = "argmax"; + const auto *name = "argmax"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 3) { throw jsi::JSError(rt, "Usage: argmax(src, dst, axis)"); @@ -231,7 +231,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { } int axis = static_cast(args[2].asNumber()); - int rank = static_cast(src->shape_.size()); + const int rank = static_cast(src->shape_.size()); // Support negative axis indices like numpy (e.g., axis=-1 means last // axis, -2 means second to last, etc.) @@ -241,7 +241,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { if (axis < 0 || axis >= rank) { throw jsi::JSError(rt, "argmax: axis is out of range"); } - const size_t axisIdx = static_cast(axis); + const auto axisIdx = static_cast(axis); auto dstExpectedShape = src->shape_; dstExpectedShape[axisIdx] = 1; @@ -263,14 +263,15 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "argmax: dst tensor has been disposed"); } - const float *srcData = reinterpret_cast(src->data_.get()); + const auto *srcData = reinterpret_cast(src->data_.get()); - const size_t axisDim = static_cast(src->shape_[axisIdx]); + const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { throw jsi::JSError(rt, "argmax: axis dimension must be greater than zero"); } - size_t outer = 1, inner = 1; + size_t outer = 1; + size_t inner = 1; for (size_t i = 0; std::cmp_less(i, axis); ++i) { outer *= static_cast(src->shape_[i]); } @@ -278,7 +279,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { inner *= static_cast(src->shape_[i]); } - int32_t *dstData = reinterpret_cast(dst->data_.get()); + auto *dstData = reinterpret_cast(dst->data_.get()); // DO NOT swap loop order. This structure intentionally prioritizes the // most common case (axis = -1, inner = 1) for sequential access. diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp index 4afd634de3..4e55a78434 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -44,8 +45,8 @@ std::string toString(tokenizers::Error error) { } } // namespace -TokenizerHostObject::TokenizerHostObject(const std::string &tokenizerPath) - : tokenizerPath_(tokenizerPath), +TokenizerHostObject::TokenizerHostObject(std::string tokenizerPath) + : tokenizerPath_(std::move(tokenizerPath)), tokenizer_(std::make_unique()) { auto error = tokenizer_->load(tokenizerPath_); if (error != tokenizers::Error::Ok) { @@ -276,7 +277,7 @@ std::vector TokenizerHostObject::getPropertyNames(jsi } void install_loadTokenizer(jsi::Runtime &rt, jsi::Object &module) { - auto name = "loadTokenizer"; + const auto *name = "loadTokenizer"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 1) { throw jsi::JSError(rt, "loadTokenizer: Usage: loadTokenizer(arg0)"); diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h index c85a804bb0..aac5aa2c0c 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h @@ -14,7 +14,7 @@ class TokenizerHostObject : public facebook::jsi::HostObject, public std::enable_shared_from_this { public: // Loads the tokenizer from `tokenizerPath`; throws std::runtime_error on failure. - explicit TokenizerHostObject(const std::string &tokenizerPath); + explicit TokenizerHostObject(std::string tokenizerPath); facebook::jsi::Value get(facebook::jsi::Runtime &rt, const facebook::jsi::PropNameID &name) override; std::vector getPropertyNames(facebook::jsi::Runtime &rt) override; diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json index d29d74882e..cae559925a 100644 --- a/packages/react-native-executorch/package.json +++ b/packages/react-native-executorch/package.json @@ -34,6 +34,7 @@ "prepare": "bob build", "typecheck": "tsc --noEmit", "lint": "eslint \"**/*.{js,ts,tsx}\"", + "lint:cpp": "./scripts/clang-tidy.sh", "prepack": "cp ../../README.md ./README.md", "postpack": "rm ./README.md", "postinstall": "yarn run -T patch-package --patch-dir ../../patches" diff --git a/packages/react-native-executorch/scripts/clang-tidy.sh b/packages/react-native-executorch/scripts/clang-tidy.sh new file mode 100755 index 0000000000..0c9eae9c14 --- /dev/null +++ b/packages/react-native-executorch/scripts/clang-tidy.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# +# Runs clang-tidy over the package's own C/C++ sources, using the checks in +# .clang-tidy and the include flags from compile_flags.txt (the same database +# clangd uses). Any finding fails the run (--warnings-as-errors). +# +# Usage: +# scripts/clang-tidy.sh [file ...] # defaults to every *.cpp under cpp/ +# +# Prerequisites (same as a native build): a provisioned third-party/include and +# `yarn install` at the repo root, so the third-party / JSI includes resolve. +# Override the binary with CLANG_TIDY=/path/to/clang-tidy (e.g. Homebrew LLVM). +set -euo pipefail + +cd "$(dirname "$0")/.." + +CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" + +if ! command -v "$CLANG_TIDY" >/dev/null 2>&1; then + echo "error: '$CLANG_TIDY' not found. Install LLVM (e.g. 'brew install llvm' or" \ + "'apt-get install clang-tidy') or set CLANG_TIDY to its path." >&2 + exit 127 +fi + +if [ "$#" -gt 0 ]; then + files=("$@") +else + files=() + while IFS= read -r f; do files+=("$f"); done < <(find cpp -name '*.cpp' | sort) +fi + +if [ "${#files[@]}" -eq 0 ]; then + echo "No C++ sources to check." + exit 0 +fi + +echo "Running clang-tidy on ${#files[@]} file(s)…" +exec "$CLANG_TIDY" --warnings-as-errors='*' "${files[@]}"