Skip to content

Commit 2cf8c0a

Browse files
committed
Wrap NullPointerExceptions when thrown by native code called from JS for readability
1 parent e06d99b commit 2cf8c0a

3 files changed

Lines changed: 37 additions & 7 deletions

File tree

Libraries/ReactNative/PaperUIManager.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,23 @@ const UIManagerJS = {
105105
hasViewManagerConfig(viewManagerName: string): boolean {
106106
return getViewManagerConfig(viewManagerName) != null;
107107
},
108+
dispatchViewManagerCommand(
109+
reactTag: number,
110+
commandName: number | string,
111+
commandArgs: any[],
112+
) {
113+
if (typeof reactTag !== 'number') {
114+
let stringifiedArgs = '(failed to stringify)';
115+
try {
116+
stringifiedArgs = JSON.stringify(commandArgs);
117+
} catch (err) {
118+
// Do nothing. We have a default message
119+
}
120+
throw new Error(`dispatchViewManagerCommand: found null reactTag with args ${stringifiedArgs}`);
121+
}
122+
123+
return NativeUIManager.dispatchViewManagerCommand(reactTag, commandName, commandArgs);
124+
},
108125
};
109126

110127
// TODO (T45220498): Remove this.

ReactAndroid/src/main/java/com/facebook/react/bridge/JavaMethodWrapper.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import static com.facebook.infer.annotation.Assertions.assertNotNull;
1111
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
1212

13+
import android.text.Editable;
14+
import android.text.SpannableStringBuilder;
15+
1316
import androidx.annotation.Nullable;
1417
import com.facebook.debug.holder.PrinterHolder;
1518
import com.facebook.debug.tags.ReactDebugOverlayTags;
@@ -356,37 +359,44 @@ public void invoke(JSInstance jsInstance, ReadableArray parameters) {
356359
mArgumentExtractors[i].extractArgument(jsInstance, parameters, jsArgumentsConsumed);
357360
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
358361
}
359-
} catch (UnexpectedNativeTypeException e) {
362+
} catch (UnexpectedNativeTypeException | NullPointerException e) {
360363
throw new NativeArgumentsParseException(
361364
e.getMessage()
362365
+ " (constructing arguments for "
363366
+ traceName
364367
+ " at argument index "
365368
+ getAffectedRange(
366369
jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded())
367-
+ ")",
370+
+ ") with parameters "
371+
+ parameters.toArrayList(),
368372
e);
369373
}
370374

371375
try {
372376
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
373-
} catch (IllegalArgumentException ie) {
374-
throw new RuntimeException("Could not invoke " + traceName, ie);
375-
} catch (IllegalAccessException iae) {
376-
throw new RuntimeException("Could not invoke " + traceName, iae);
377+
} catch (IllegalArgumentException | IllegalAccessException e) {
378+
throw new RuntimeException(createInvokeExceptionMessage(traceName, parameters), e);
377379
} catch (InvocationTargetException ite) {
378380
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
379381
// which just make traces harder to read and bump out useful information
380382
if (ite.getCause() instanceof RuntimeException) {
381383
throw (RuntimeException) ite.getCause();
382384
}
383-
throw new RuntimeException("Could not invoke " + traceName, ite);
385+
throw new RuntimeException(createInvokeExceptionMessage(traceName, parameters), ite);
384386
}
385387
} finally {
386388
SystraceMessage.endSection(TRACE_TAG_REACT_JAVA_BRIDGE).flush();
387389
}
388390
}
389391

392+
/**
393+
* Makes it easier to determine the cause of an error invoking a native method from Javascript
394+
* code by adding the function and parameters.
395+
*/
396+
private static String createInvokeExceptionMessage(String traceName, ReadableArray parameters) {
397+
return "Could not invoke " + traceName + " with parameters " + parameters.toArrayList();
398+
}
399+
390400
/**
391401
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
392402
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC

WanderlogPatches.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ rebase on upstream's `main`
4848
- Prevent crash when runAnimationStep called with low frameTimeNanos
4949
- Summary: React Native was crashing on OnePlus and Oppo devices. This is a workaround
5050
- Pull request: https://github.com/facebook/react-native/pull/37487
51+
- Wrap NullPointerExceptions when thrown by native code called from JS for readability
52+
- Summary: These crashing exceptions were really hard to debug in Bugsnag since they don't print the method name. We wrap it and add that.
53+
- Pull request: https://github.com/facebook/react-native/pull/38060

0 commit comments

Comments
 (0)