diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java index ccb10de08c3d9b..dd2652115500bf 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java @@ -10,6 +10,7 @@ import android.os.Handler; import android.os.Looper; import androidx.annotation.Nullable; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; import java.io.Closeable; @@ -21,7 +22,8 @@ import okhttp3.WebSocketListener; /** Java wrapper around a C++ InspectorPackagerConnection. */ -/* package */ class CxxInspectorPackagerConnection implements IInspectorPackagerConnection { +/* package */ @Nullsafe(Nullsafe.Mode.LOCAL) +class CxxInspectorPackagerConnection implements IInspectorPackagerConnection { static { DevSupportSoLoader.staticInit(); } @@ -39,7 +41,7 @@ private static native HybridData initHybrid( public native void closeQuietly(); - public native void sendEventToAllConnections(String event); + public native void sendEventToAllConnections(@Nullable String event); /** Java wrapper around a C++ IWebSocketDelegate, allowing us to call the interface from Java. */ @DoNotStrip diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java index 421377f7adaf24..9db6ef475fb916 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java @@ -18,6 +18,7 @@ import android.widget.FrameLayout; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; @@ -26,7 +27,8 @@ * Helper class for controlling overlay view with FPS and JS FPS info that gets added directly * to @{link WindowManager} instance. */ -/* package */ class DebugOverlayController { +/* package */ @Nullsafe(Nullsafe.Mode.LOCAL) +class DebugOverlayController { public static void requestPermission(Context context) { // Get permission to show debug overlay in dev builds. @@ -57,6 +59,7 @@ private static boolean hasPermission(Context context, String permission) { PackageInfo info = context .getPackageManager() + // NULLSAFE_FIXME[Nullable Dereference] .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (info.requestedPermissions != null) { for (String p : info.requestedPermissions) { @@ -73,7 +76,7 @@ private static boolean hasPermission(Context context, String permission) { private static boolean canHandleIntent(Context context, Intent intent) { PackageManager packageManager = context.getPackageManager(); - return intent.resolveActivity(packageManager) != null; + return packageManager != null && intent.resolveActivity(packageManager) != null; } private final WindowManager mWindowManager; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 155e45578b5b73..5411029777aaa2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -15,6 +15,7 @@ import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.ReactConstants; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; @@ -59,6 +60,7 @@ *
  • Genymotion emulator with default settings: 10.0.3.2 * */ +@Nullsafe(Nullsafe.Mode.LOCAL) public class DevServerHelper { private static final int HTTP_CONNECT_TIMEOUT_MS = 5000; @@ -205,10 +207,13 @@ public void openInspectorConnection() { protected Void doInBackground(Void... params) { Map metadata = AndroidInfoHelpers.getInspectorHostMetadata(mApplicationContext); - + String deviceName = metadata.get("deviceName"); + if (deviceName == null) { + FLog.w(ReactConstants.TAG, "Could not get device name from Inspector Host Metadata."); + return null; + } mInspectorPackagerConnection = - new CxxInspectorPackagerConnection( - getInspectorDeviceUrl(), metadata.get("deviceName"), mPackageName); + new CxxInspectorPackagerConnection(getInspectorDeviceUrl(), deviceName, mPackageName); mInspectorPackagerConnection.connect(); return null; } @@ -452,7 +457,7 @@ public String getSourceUrl(String mainModuleName) { final Request request = new Request.Builder().url(resourceURL).build(); try (Response response = mClient.newCall(request).execute()) { - if (!response.isSuccessful()) { + if (!response.isSuccessful() || response.body() == null) { return null; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSDebuggerWebSocketClient.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSDebuggerWebSocketClient.java index c5ee17c8441193..be02307e1b342d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSDebuggerWebSocketClient.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSDebuggerWebSocketClient.java @@ -13,6 +13,7 @@ import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.common.JavascriptException; import com.facebook.react.common.annotations.VisibleForTesting; import java.io.IOException; @@ -29,6 +30,7 @@ import okhttp3.WebSocketListener; /** A wrapper around WebSocketClient that recognizes RN debugging message format. */ +@Nullsafe(Nullsafe.Mode.LOCAL) class JSDebuggerWebSocketClient extends WebSocketListener { private static final String TAG = "JSDebuggerWebSocketClient"; @@ -210,7 +212,7 @@ public void onMessage(@Nullable WebSocket webSocket, String text) { } @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { + public void onFailure(@Nullable WebSocket webSocket, Throwable t, @Nullable Response response) { abort("Websocket exception", t); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java index 3f74eb60983d08..4c99f9bee75fbb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java @@ -7,7 +7,9 @@ package com.facebook.react.devsupport; +import com.facebook.infer.annotation.Nullsafe; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import okio.Buffer; @@ -15,6 +17,7 @@ import okio.ByteString; /** Utility class to parse the body of a response of type multipart/mixed. */ +@Nullsafe(Nullsafe.Mode.LOCAL) class MultipartStreamReader { // Standard line separator for HTTP. private static final String CRLF = "\r\n"; @@ -60,7 +63,7 @@ private void emitChunk(Buffer chunk, boolean done, ChunkListener listener) throw ByteString marker = ByteString.encodeUtf8(CRLF + CRLF); long indexOfMarker = chunk.indexOf(marker); if (indexOfMarker == -1) { - listener.onChunkComplete(null, chunk, done); + listener.onChunkComplete(Collections.emptyMap(), chunk, done); } else { Buffer headers = new Buffer(); Buffer body = new Buffer(); @@ -148,6 +151,7 @@ public boolean readAllParts(ChunkListener listener) throws IOException { Buffer chunk = new Buffer(); content.skip(chunkStart); content.read(chunk, length); + // NULLSAFE_FIXME[Parameter Not Nullable] emitProgress(currentHeaders, chunk.size() - currentHeadersLength, true, listener); emitChunk(chunk, isCloseDelimiter, listener); currentHeaders = null; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java index 9975ae6e7e6837..06084402c2b152 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -8,6 +8,8 @@ package com.facebook.react.devsupport; import androidx.annotation.Nullable; +import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; @@ -26,6 +28,7 @@ import org.json.JSONObject; /** Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects. */ +@Nullsafe(Nullsafe.Mode.LOCAL) public class StackTraceHelper { public static final String COLUMN_KEY = "column"; @@ -49,27 +52,29 @@ public class StackTraceHelper { /** Represents a generic entry in a stack trace, be it originally from JS or Java. */ public static class StackFrameImpl implements StackFrame { - private final String mFile; + @Nullable private final String mFile; private final String mMethod; private final int mLine; private final int mColumn; - private final String mFileName; + @Nullable private final String mFileName; private final boolean mIsCollapsed; - private StackFrameImpl(String file, String method, int line, int column, boolean isCollapsed) { + private StackFrameImpl( + @Nullable String file, String method, int line, int column, boolean isCollapsed) { mFile = file; mMethod = method; mLine = line; mColumn = column; - mFileName = file != null ? new File(file).getName() : ""; + mFileName = file != null ? new File(file).getName() : null; mIsCollapsed = isCollapsed; } - private StackFrameImpl(String file, String method, int line, int column) { + private StackFrameImpl(@Nullable String file, String method, int line, int column) { this(file, method, line, column, false); } - private StackFrameImpl(String file, String fileName, String method, int line, int column) { + private StackFrameImpl( + @Nullable String file, @Nullable String fileName, String method, int line, int column) { mFile = file; mFileName = fileName; mMethod = method; @@ -84,7 +89,8 @@ private StackFrameImpl(String file, String fileName, String method, int line, in *

    JS traces return the full path to the file here, while Java traces only return the file * name (the path is not known). */ - public String getFile() { + @Override + public @Nullable String getFile() { return mFile; } @@ -109,7 +115,8 @@ public int getColumn() { *

    For JS traces this is different from {@link #getFile()} in that it only returns the file * name, not the full path. For Java traces there is no difference. */ - public String getFileName() { + @Override + public @Nullable String getFileName() { return mFileName; } @@ -119,9 +126,10 @@ public boolean isCollapsed() { /** Convert the stack frame to a JSON representation. */ public JSONObject toJSON() { + String file = getFile(); return new JSONObject( MapBuilder.of( - "file", getFile(), + "file", (file == null) ? "" : file, "methodName", getMethod(), "lineNumber", getLine(), "column", getColumn(), @@ -136,25 +144,33 @@ public JSONObject toJSON() { public static StackFrame[] convertJsStackTrace(@Nullable ReadableArray stack) { int size = stack != null ? stack.size() : 0; StackFrame[] result = new StackFrame[size]; - for (int i = 0; i < size; i++) { - ReadableType type = stack.getType(i); - if (type == ReadableType.Map) { - ReadableMap frame = stack.getMap(i); - String methodName = frame.getString("methodName"); - String fileName = frame.getString("file"); - boolean collapse = - frame.hasKey("collapse") && !frame.isNull("collapse") && frame.getBoolean("collapse"); - int lineNumber = -1; - if (frame.hasKey(LINE_NUMBER_KEY) && !frame.isNull(LINE_NUMBER_KEY)) { - lineNumber = frame.getInt(LINE_NUMBER_KEY); - } - int columnNumber = -1; - if (frame.hasKey(COLUMN_KEY) && !frame.isNull(COLUMN_KEY)) { - columnNumber = frame.getInt(COLUMN_KEY); + if (stack != null) { + for (int i = 0; i < size; i++) { + ReadableType type = stack.getType(i); + if (type == ReadableType.Map) { + ReadableMap frame = stack.getMap(i); + Assertions.assertNotNull(frame); + String methodName = frame.getString("methodName"); + String fileName = frame.getString("file"); + Assertions.assertNotNull(fileName); + Assertions.assertNotNull(methodName); + boolean collapse = + frame.hasKey("collapse") && !frame.isNull("collapse") && frame.getBoolean("collapse"); + int lineNumber = -1; + if (frame.hasKey(LINE_NUMBER_KEY) && !frame.isNull(LINE_NUMBER_KEY)) { + lineNumber = frame.getInt(LINE_NUMBER_KEY); + } + int columnNumber = -1; + if (frame.hasKey(COLUMN_KEY) && !frame.isNull(COLUMN_KEY)) { + columnNumber = frame.getInt(COLUMN_KEY); + } + result[i] = new StackFrameImpl(fileName, methodName, lineNumber, columnNumber, collapse); + } else if (type == ReadableType.String) { + String stackFrame = stack.getString(i); + if (stackFrame != null) { + result[i] = new StackFrameImpl(null, stackFrame, -1, -1); + } } - result[i] = new StackFrameImpl(fileName, methodName, lineNumber, columnNumber, collapse); - } else if (type == ReadableType.String) { - result[i] = new StackFrameImpl(null, stack.getString(i), -1, -1); } } return result; @@ -203,15 +219,22 @@ public static StackFrame[] convertJsStackTrace(String stack) { } else if (matcher1.find()) { matcher = matcher1; } else { - result[i] = new StackFrameImpl(null, stackTrace[i], -1, -1); + String unmatchedStackFrame = stackTrace[i]; + if (unmatchedStackFrame != null) { + result[i] = new StackFrameImpl(null, unmatchedStackFrame, -1, -1); + } + continue; + } + String fileName = matcher.group(2); + String methodName = matcher.group(1) == null ? "(unknown)" : matcher.group(1); + String lineString = matcher.group(3); + String columnString = matcher.group(4); + if (methodName == null || fileName == null || lineString == null || columnString == null) { continue; } result[i] = new StackFrameImpl( - matcher.group(2), - matcher.group(1) == null ? "(unknown)" : matcher.group(1), - Integer.parseInt(matcher.group(3)), - Integer.parseInt(matcher.group(4))); + fileName, methodName, Integer.parseInt(lineString), Integer.parseInt(columnString)); } return result; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/StackFrame.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/StackFrame.kt index 47f32567879a0f..8f462ae6171bae 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/StackFrame.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/StackFrame.kt @@ -20,7 +20,7 @@ public interface StackFrame { public val file: String? /** Get the name of the method this frame points to. */ - public val method: String? + public val method: String /** Get the line number this frame points to in the file returned by [.getFile]. */ public val line: Int @@ -40,5 +40,5 @@ public interface StackFrame { public val isCollapsed: Boolean /** Convert the stack frame to a JSON representation. */ - public fun toJSON(): JSONObject? + public fun toJSON(): JSONObject } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.kt index fbef2c7dc9e1b7..bcec632eb6250e 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.kt @@ -41,7 +41,7 @@ class JSDebuggerWebSocketClientTest { val client = spy(JSDebuggerWebSocketClient()) val injectedObjects = mapOf("key1" to "value1", "key2" to "value2") client.loadBundle( - "http://localhost:8080/index.js", injectedObjects as HashMap?, cb) + "http://localhost:8080/index.js", injectedObjects as HashMap, cb) verify(client) .sendMessage( 0, diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt index b6e56307d3198c..2a693a8c1ee165 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt @@ -33,7 +33,7 @@ class MultipartStreamReaderTest { val callback: CallCountTrackingChunkCallback = object : CallCountTrackingChunkCallback() { - override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + override fun onChunkComplete(headers: Map, body: Buffer, done: Boolean) { super.onChunkComplete(headers, body, done) assertThat(done).isTrue @@ -68,7 +68,7 @@ class MultipartStreamReaderTest { val callback: CallCountTrackingChunkCallback = object : CallCountTrackingChunkCallback() { - override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + override fun onChunkComplete(headers: Map, body: Buffer, done: Boolean) { super.onChunkComplete(headers, body, done) assertThat(done).isEqualTo(callCount == 3) @@ -125,7 +125,7 @@ class MultipartStreamReaderTest { var callCount = 0 private set - override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + override fun onChunkComplete(headers: Map, body: Buffer, done: Boolean) { callCount++ } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt index 9476927d3eba4d..33dcc7e7ba3293 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt @@ -47,7 +47,7 @@ class StackTraceHelperTest { fun testParseStackFrameWithInvalidFrame() { val frame = StackTraceHelper.convertJsStackTrace("Test.bundle:ten:twenty").get(0) assertThat(frame.method).isEqualTo("Test.bundle:ten:twenty") - assertThat(frame.fileName).isEqualTo("") + assertThat(frame.fileName).isEqualTo(null) assertThat(frame.line).isEqualTo(-1) assertThat(frame.column).isEqualTo(-1) } @@ -56,7 +56,7 @@ class StackTraceHelperTest { fun testParseStackFrameWithNativeCodeFrame() { val frame = StackTraceHelper.convertJsStackTrace("forEach@[native code]").get(0) assertThat(frame.method).isEqualTo("forEach@[native code]") - assertThat(frame.fileName).isEqualTo("") + assertThat(frame.fileName).isEqualTo(null) assertThat(frame.line).isEqualTo(-1) assertThat(frame.column).isEqualTo(-1) }