From e7c6a4c038936ed54d153eeca7fa6fdf8cf1f503 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Fri, 21 Apr 2017 11:25:31 -0700 Subject: [PATCH] Fixed crash caused by NaN values in RCTTouchEvent Summary: React Native uses JSON to marshal the data across the bridge. And because of this we have to avoid using NaN and INF values in events and other pieces of data that suppose to be transfered to/from JS side. (We also don't want to introduce additional wrapping/escaping semantics for perfomance reasons.) So, we have to gate all particular cases where there is a possibility of NaN or INF values, and replace these value with something meaningful for each particular case. We are using `0` as NaN substitution here because: * NaN in touch event is super rare case; * Conversion to `0` is fast; * `0` is okay value for product code in most cases; * In all cases `0` is decent analog to "undefined position on screen" for touch event; * Nobody will explicitly handle NaN case in product code, just because it is super rare case and actually indicates that something else went wrong. Reviewed By: javache Differential Revision: D4918669 fbshipit-source-id: e95fa29e59dcdc40b57519e307b74c1f293da188 --- React/Base/RCTTouchHandler.m | 10 +++++----- React/Base/RCTUtils.h | 3 +++ React/Base/RCTUtils.m | 10 ++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index 19af0120d..481128efe 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -156,17 +156,17 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithTarget:(id)target action:(SEL)action CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView]; NSMutableDictionary *reactTouch = _reactTouches[touchIndex]; - reactTouch[@"pageX"] = @(rootViewLocation.x); - reactTouch[@"pageY"] = @(rootViewLocation.y); - reactTouch[@"locationX"] = @(touchViewLocation.x); - reactTouch[@"locationY"] = @(touchViewLocation.y); + reactTouch[@"pageX"] = @(RCTSanitizeNaNValue(rootViewLocation.x, @"touchEvent.pageX")); + reactTouch[@"pageY"] = @(RCTSanitizeNaNValue(rootViewLocation.y, @"touchEvent.pageY")); + reactTouch[@"locationX"] = @(RCTSanitizeNaNValue(touchViewLocation.x, @"touchEvent.locationX")); + reactTouch[@"locationY"] = @(RCTSanitizeNaNValue(touchViewLocation.y, @"touchEvent.locationY")); reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS // TODO: force for a 'normal' touch is usually 1.0; // should we expose a `normalTouchForce` constant somewhere (which would // have a value of `1.0 / nativeTouch.maximumPossibleForce`)? if (RCTForceTouchAvailable()) { - reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force / nativeTouch.maximumPossibleForce)); + reactTouch[@"force"] = @(RCTSanitizeNaNValue(nativeTouch.force / nativeTouch.maximumPossibleForce, @"touchEvent.force")); } } diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 8d6470260..d641f74d7 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -101,6 +101,9 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); // Convert NaN or infinite values to zero, as these aren't JSON-safe RCT_EXTERN double RCTZeroIfNaN(double value); +// Returns `0` and log special warning if value is NaN or INF. +RCT_EXTERN double RCTSanitizeNaNValue(double value, NSString *property); + // Convert data to a Base64-encoded data URL RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 4e2ed4623..c00bb283d 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -513,6 +513,16 @@ double RCTZeroIfNaN(double value) return isnan(value) || isinf(value) ? 0 : value; } +double RCTSanitizeNaNValue(double value, NSString *property) +{ + if (!isnan(value) && !isinf(value)) { + return value; + } + + RCTLogWarn(@"The value `%@` equals NaN or INF and will be replaced by `0`.", property); + return 0; +} + NSURL *RCTDataURL(NSString *mimeType, NSData *data) { return [NSURL URLWithString: