Skip to content

fix: restore input buffer size validation lost in the Nitro migration - #201

Merged
mrousavy merged 1 commit into
margelo:mainfrom
giaBaoJS:fix/input-buffer-size-validation
Aug 14, 2026
Merged

fix: restore input buffer size validation lost in the Nitro migration#201
mrousavy merged 1 commit into
margelo:mainfrom
giaBaoJS:fix/input-buffer-size-validation

Conversation

@giaBaoJS

Copy link
Copy Markdown
Contributor

Summary

copyInputBuffers discards the TfLiteStatus returned by TfLiteTensorCopyFromBuffer. That function is documented as REQUIRES: input_data_size == TfLiteTensorByteSize(tensor) and, when the size does not match, it returns kTfLiteError and leaves the tensor untouched.

https://github.com/mrousavy/react-native-fast-tflite/blob/691f4b8/cpp/HybridTfliteModel.cpp#L98

So passing a wrong-sized input ArrayBuffer today does not throw. The copy is dropped, TfLiteInterpreterInvoke runs anyway, and you get results computed from whatever was left in the input tensor from the previous run/runSync (or the tensor's initial contents on the first call). The only surviving guard is the input tensor count check a few lines above.

The user-visible symptom is "my outputs never change" rather than an error — which is what #64 describes. #120 quotes the error message that used to be raised for this exact case.

This is a regression

The check existed from 0313eec ("feat: Check for input size mismatch", #17) and was removed by 6f86153 ("feat: Migrate to Nitro Modules (for V3)", #172), which deleted cpp/TensorHelpers.cpp (‑293 lines) and did not carry the validation over into the new cpp/HybridTfliteModel.cpp.

$ git log -S "does not match the Input Tensor" --oneline -- cpp/
6f86153 feat: Migrate to Nitro Modules (for V3) (#172)
d91daa3 fix: Fix GitHub Actions (#20)

$ git log -S "Make sure to resize the input values accordingly" --oneline -- cpp/
6f86153 feat: Migrate to Nitro Modules (for V3) (#172)
0313eec feat: Check for input size mismatch  (#17)

$ git show --stat 6f86153 -- cpp/TensorHelpers.cpp cpp/TensorHelpers.h
 cpp/TensorHelpers.cpp | 293 --------------------------------------------------
 cpp/TensorHelpers.h   |  47 --------

What v2 raised (cpp/TensorHelpers.cpp:259-270 at 6f86153^):

#if DEBUG
  // Validate size
  int inputBufferSize = buffer.size(runtime);
  int tensorSize = getTensorTotalLength(tensor) * getTFLTensorDataTypeSize(tensor->type);
  if (tensorSize != inputBufferSize) {
    [[unlikely]];
    throw std::runtime_error("TFLite: Input Buffer size (" + std::to_string(inputBufferSize) +
                             ") does not match the Input Tensor's expected size (" +
                             std::to_string(tensorSize) +
                             ")! Make sure to resize the input values accordingly.");
  }
#endif

Worth noting: that v2 check was inside #if DEBUG, so release builds of v2 already had the silent behaviour. The check here is unconditional — the cost is one comparison of an already-returned status code, and silently inferring on stale data is not something a release build should do either.

The fix

Capture the status and throw with the actual and expected byte sizes, in the style of the surrounding errors. Requires no extra state and no extra TFLite calls on the success path.

The message produced for a 224x224x3 uint8 model given a buffer one byte short (copied verbatim from a harness run):

TfliteModel.runSync(...): TFLite: Input buffer 0 size (150527) does not match input tensor "input" expected size (150528)! Status: error

Testing

Three cases added to example/__tests__/tflite.harness.ts against the already-bundled google-quant.tflite. The third is the important one: it does not merely assert "a status code is checked", it reproduces the user-visible symptom — prime the model with one image, then pass a one-byte-short buffer holding a different image, and check the result is not byte-identical to the primed run. It then runs the same data at the correct size and asserts inference still succeeds and produces a different result, so the fix cannot pass by throwing on everything.

iOS — iPhone 17 Pro Max simulator (iOS 26.3), Debug build, yarn test:harness --harnessRunner ios:

result
with the fix Tests: 19 passed, 19 total
fix reverted (counterfactual) Tests: 3 failed, 16 passed, 19 total

Android — arm64 emulator (API 36), Debug build, yarn test:harness --harnessRunner android:

result
with the fix Tests: 19 passed, 19 total
fix reverted (counterfactual) Tests: 3 failed, 16 passed, 19 total

The counterfactual failure is identical on both platforms and shows the silent-stale-data behaviour directly:

✕ runSync rejects an input buffer that is too small
✕ runSync rejects an input buffer that is too large
✕ does not silently run inference on the previous input when the buffer size is wrong

● … › runSync rejects an input buffer that is too small
  expected [Function] to throw an error

● … › does not silently run inference on the previous input when the buffer size is wrong
  expected true to be false // Object.is equality
  > 452 |       expect(reusedStaleInput).toBe(false);

reusedStaleInput being true is the bug: the run with the wrong-sized buffer returned the previous input's scores, byte for byte.

All 16 pre-existing harness tests pass unchanged in every run above. scripts/clang-format.sh is clean, and the Android -Wall -Wextra build produces no new warnings (the one -Wsign-compare warning at HybridTfliteModel.cpp:95 is pre-existing and untouched).

Interaction with the other open C++ PRs

Trial-merged locally against #196, #197 and #198 — all three merge cleanly with this branch, and copyInputBuffers keeps the new check after merging #198 (which relocates the call site behind a lifecycle lock but does not change the function body).

TfLiteTensorCopyFromBuffer returns kTfLiteError and leaves the tensor
untouched when the supplied byte size does not match the tensor's
expected size. copyInputBuffers discarded that status, so a wrong-sized
input ArrayBuffer was silently dropped and inference ran on whatever the
tensor held from the previous call, returning plausible-looking but stale
results instead of throwing.

v2 threw "Input Buffer size (N) does not match the Input Tensor's expected
size (M)!" from cpp/TensorHelpers.cpp; that check was removed when
cpp/TensorHelpers.cpp was deleted in 6f86153 (margelo#172, the Nitro migration).

Check the status and throw with the actual and expected byte sizes, and
add harness coverage asserting a wrong-sized buffer throws instead of
reusing the previous input.
@mrousavy

Copy link
Copy Markdown
Member

Thanks!

@mrousavy
mrousavy merged commit 4197efc into margelo:main Aug 14, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants