Skip to content

Commit

Permalink
Reduce use of assertions in prop parsing (#36164)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #36164

Changelog:
[General][Fixed] - Invalid prop values no longer trigger assertion failures in Fabric

## Context

Fabric has historically been very strict about prop parsing. It originally `abort()`ed on any prop value that failed to parse according to the expected type; this was replaced with dev-only assertions in D27540903 (cb37562). We've received feedback that C++ assertions (and other similar mechanisms in the legacy renderer) are still too aggressive and disruptive as a diagnostic for developers working on JS code.

We are changing React Native to behave more like a browser in this regard, reflecting a new principle that **bad style values are not runtime errors**. (See e.g. D43159284 (d6e9891).) The recommended way for developers to ensure they are passing correct style values is to use a typechecker (TypeScript or Flow) in conjunction with E2E tests and manual spot checks.

More broadly, values passed from JS product code should not be able to crash the app, which is why we're not strictly limiting this change to style props. From now on, if a JS developer can trigger an internal assertion in React Native simply by writing normal application code, that is a bug.

## This diff

This diff introduces a new macro called `react_native_expect` which serves as a drop-in replacement for `react_native_assert`, but logs (to glog / logcat / stdout) instead of asserting. This way we don't need to fully delete the existing call sites. This will be helpful if we decide that we want to repurpose these checks for a new, more visible diagnostic.

I'm *intentionally* opting for the simplest possible improvement here, which is to silence the assertions - not to print them to the JS console, not to convert them to LogBox warnings, etc. The hypothesis is that this is already strictly an improvement over the previous behaviour, will help us get to feature parity between renderers faster, and allow us to design improved diagnostics that are consistent and helpful.

## Next steps

1. There are still places where Fabric can hit an unguarded assertion in prop conversion code (e.g. unchecked casts from `RawValue` with no fallback code path). I will fix those in a separate diff.
2. Paper on iOS needs a similar treatment as it calls `RCTLogError` liberally during prop parsing (resulting in a native redbox experience that is nearly as bad as an outright crash). I will fix that in a separate diff.
3. I'll add some manual test cases to RNTester to cover these scenarios.
4. We will eventually need to take a clear stance on PropTypes, but since they provide reasonable, non-breaking diagnostics (recoverable JS LogBox + component stack) it is less urgent to do so.

Reviewed By: sammy-SC

Differential Revision: D43184380

fbshipit-source-id: 0c921efef297d935a2ae5acc57ff23171356014b
  • Loading branch information
motiz88 authored and facebook-github-bot committed Feb 15, 2023
1 parent 8232c47 commit d16c1a0
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 100 deletions.
6 changes: 6 additions & 0 deletions ReactCommon/react/debug/react_native_assert.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
// test before moving on. When all issues have been found, maybe we can use
// `UNDEBUG` flag to disable NDEBUG in debug builds on Android.

// Asserting is appropriate for conditions that:
// 1. May or may not be recoverable, and
// 2. imply there is a bug in React Native when violated.
// For recoverable conditions that can be violated by user mistake (e.g. JS
// code passes an unexpected prop value), consider react_native_expect instead.

#include "flags.h"

#undef react_native_assert
Expand Down
41 changes: 41 additions & 0 deletions ReactCommon/react/debug/react_native_expect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// No header guards since it is legitimately possible to include this file more
// than once with and without REACT_NATIVE_DEBUG.

// react_native_expect is a non-fatal counterpart of react_native_assert.
// In debug builds, when an expectation fails, we log and move on.
// In release builds, react_native_expect is a noop.

// react_native_expect is appropriate for recoverable conditions that can be
// violated by user mistake (e.g. JS code passes an unexpected prop value).
// To enforce invariants that are internal to React Native, consider
// react_native_assert (or a stronger mechanism).
// Calling react_native_expect does NOT, by itself, guarantee that the user
// will see a helpful diagnostic (beyond a low level log). That concern is the
// caller's responsibility.

#include "flags.h"

#undef react_native_expect

#ifndef REACT_NATIVE_DEBUG

#define react_native_expect(e) ((void)0)

#else // REACT_NATIVE_DEBUG

#include <glog/logging.h>
#include <cassert>

#define react_native_expect(cond) \
if (!(cond)) { \
LOG(ERROR) << "react_native_expect failure: " << #cond; \
}

#endif // REACT_NATIVE_DEBUG
Loading

0 comments on commit d16c1a0

Please sign in to comment.