Make java UTF-8 decode more lenient (#26886)

* Start asking for replace in the java UTF logic

* Fix exception reporting: make sure exception is cleared, print out a nice stack trace

* Restyle

* Fix for compilation

* Fix signature for repolace action field

* Restyled by clang-format

---------

Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp
index 66c16eb..de61a98 100644
--- a/src/lib/support/JniReferences.cpp
+++ b/src/lib/support/JniReferences.cpp
@@ -402,6 +402,29 @@
     jmethodID newDocoderMethod = env->GetMethodID(charSetClass, "newDecoder", "()Ljava/nio/charset/CharsetDecoder;");
     jobject decoderObject      = env->CallObjectMethod(charsetObject, newDocoderMethod);
 
+    // Even though spec requires UTF-8 strings, we have seen instances in the field of certified devices sending
+    // invalid strings like "startup?" (0x73 0x74 0x61 0x72 0x74 0x75 0x70 <0x91>) and we want to actually
+    // be lenient on those rather than failing an entire decode (which may fail an entire report for one invalid string,
+    // like in a very common 'subscribe *')
+    //
+    // As a result call:
+    //   onMalformedInput(CodingErrorAction.REPLACE)
+    //   onUnmappableCharacter(CodingErrorAction.REPLACE)
+    jclass codingErrorActionClass = env->FindClass("java/nio/charset/CodingErrorAction");
+    jobject replaceAction         = env->GetStaticObjectField(
+        codingErrorActionClass, env->GetStaticFieldID(codingErrorActionClass, "REPLACE", "Ljava/nio/charset/CodingErrorAction;"));
+    {
+        jmethodID onMalformedInput = env->GetMethodID(charSetDocoderClass, "onMalformedInput",
+                                                      "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;");
+        decoderObject              = env->CallObjectMethod(decoderObject, onMalformedInput, replaceAction);
+    }
+    {
+        jmethodID onUnmappableCharacter =
+            env->GetMethodID(charSetDocoderClass, "onUnmappableCharacter",
+                             "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;");
+        decoderObject = env->CallObjectMethod(decoderObject, onUnmappableCharacter, replaceAction);
+    }
+
     jmethodID charSetDecodeMethod = env->GetMethodID(charSetDocoderClass, "decode", "(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;");
     jobject decodeObject          = env->CallObjectMethod(decoderObject, charSetDecodeMethod, jbyteBuffer);
     env->DeleteLocalRef(jbyteBuffer);
@@ -409,7 +432,15 @@
     // If decode exception occur, outStr will be set null.
     outStr = nullptr;
 
-    VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN);
+    if (env->ExceptionCheck())
+    {
+        // If there is an exception, decode will not fail. Instead just
+        // an error will be reported.
+        ChipLogError(Support, "Exception encountered trying to decode a UTF string.");
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return CHIP_JNI_ERROR_EXCEPTION_THROWN;
+    }
 
     jclass charBufferClass       = env->FindClass("java/nio/CharBuffer");
     jmethodID charBufferToString = env->GetMethodID(charBufferClass, "toString", "()Ljava/lang/String;");