Fix crash: replace malformed UTF-8 instead of throwing in PayloadDecoder - #1512
Conversation
There was a problem hiding this comment.
Pull request overview
This PR prevents PayloadDecoder’s JSON serialization from terminating host processes when decoded telemetry contains malformed UTF-8, by switching nlohmann::json::dump() to use error_handler_t::replace.
Changes:
- Update
DecodeRequestto calldump(..., json::error_handler_t::replace)and add rationale comments. - Update
DecodeRecordsimilarly to replace malformed UTF-8 instead of throwing.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Reword the DecodeRequest comment to state the local rationale for error_handler_t::replace instead of referencing the HAVE_MAT_AI-gated AIJsonSerializer, which is not part of the default OSS build. - Add PayloadDecoderTests covering the invalid-UTF-8 regression: a record whose string field carries a non-UTF-8 byte must be decoded without throwing type_error.316, and the bad byte must surface as U+FFFD. Also verifies valid UTF-8 is left untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6ab3a16b-98f7-4fa7-a933-684b34196436
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
tests/unittests/PayloadDecoderTests.cpp:62
out.find('\xFF')has the same signed/unsignedcharportability issue as the invalid-byte injection above and can also trigger-Werrorconstant-conversion warnings on some compilers. Use an explicitstatic_cast<char>(0xFF)when searching for the raw byte.
EXPECT_NE(out.find("\xEF\xBF\xBD"), std::string::npos)
<< "Malformed UTF-8 should be replaced with U+FFFD";
EXPECT_EQ(out.find('\xFF'), std::string::npos)
<< "Raw invalid byte must not survive in the output";
tests/unittests/PayloadDecoderTests.cpp:48
"Bad\xFFName"relies on implementation-defined conversion of 0xFF into acharwithin a string literal. With the repo’s-Werror(see top-level CMakeLists), some toolchains warn on out-of-range/constant conversion for such escapes, which can break CI. Build the invalid UTF-8 byte viapush_back(static_cast<char>(0xFF))instead (similar to AIJsonSerializerTests).
This issue also appears on line 59 of the same file.
CsProtocol::Record record = MakeMinimalRecord();
// 0xFF is never a valid UTF-8 byte.
record.name = std::string("Bad\xFFName");
tests/unittests/PayloadDecoderTests.cpp:43
- The behavior change in this PR is applied to both
DecodeRecordandDecodeRequest, but the new regression tests only exerciseDecodeRecord. There are call sites forDecodeRequestin functests, but they don’t assert the no-throw / U+FFFD replacement behavior. Adding aDecodeRequest_InvalidUtf8_DoesNotThrowtest (constructing a minimal request buffer containing a record with an invalid UTF-8 byte) would better lock in the crash fix on the primary entry point described in the PR.
// A telemetry event field can legitimately contain bytes that are not valid
// UTF-8. nlohmann::json::dump() defaults to error_handler_t::strict, which
// throws type_error.316 on such input. Because DecodeRecord/DecodeRequest run
// on the Diagnostic Data Viewer path inside the hosting process, an unhandled
// throw terminates that process (Watson crash). These tests lock in the
// error_handler_t::replace behavior: no throw, and the malformed byte is
// emitted as the U+FFFD replacement character (EF BF BD).
TEST(PayloadDecoderTests, DecodeRecord_InvalidUtf8_DoesNotThrow)
Address review feedback on the regression tests: - Inject the invalid UTF-8 byte via push_back(static_cast<char>(0xFF)) and search for it with static_cast<char>(0xFF) instead of a '\xFF' string/char literal, which relies on implementation-defined char conversion and can trip -Werror constant-conversion on some toolchains. - Drop the product-specific phrasing from the test comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6ab3a16b-98f7-4fa7-a933-684b34196436
|
Addressed the low-confidence review notes on
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
tests/unittests/PayloadDecoderTests.cpp:84
- Like the invalid-UTF8 case above, this test currently skips all assertions when
decodedis false, which means it won’t fail if the full decoder unexpectedly starts returningfalseon feature-enabled builds. Consider assertingdecoded/outbased onHAVE_MAT_ZLIB+HAVE_MAT_JSONHPPso the test reliably validates the real implementation when it is compiled in.
if (decoded)
{
EXPECT_NE(out.find("Valid.Event"), std::string::npos);
EXPECT_EQ(out.find("\xEF\xBF\xBD"), std::string::npos)
<< "Valid UTF-8 must not be altered";
tests/unittests/PayloadDecoderTests.cpp:68
- This test only asserts behavior when
decodedis true, so it can still pass even if the full decoder unexpectedly returnsfalse(e.g., due to a regression) on builds that actually have Zlib + json.hpp enabled. Since the implementation is stubbed specifically whenHAVE_MAT_ZLIB/HAVE_MAT_JSONHPPare missing, you can make the test deterministic by assertingdecoded(andout) based on those feature macros.
This issue also appears on line 80 of the same file.
// When the SDK is built with JSON + Zlib support the real decoder runs and
// must have replaced the bad byte. In a stubbed build DecodeRecord returns
// false with an empty string, in which case the no-throw guarantee above is
// what this test protects.
if (decoded)
Summary
PayloadDecoder::DecodeRequestandDecodeRecordserialize decoded telemetry to a human-readable string withnlohmann::json::dump()using the defaulterror_handler_t::strict. When a decoded event string contains bytes that are not valid UTF-8,dump()throwsnlohmann::json::type_error(id 316, "invalid UTF-8 byte") fromserializer::dump_escaped.Telemetry event properties can legitimately contain arbitrary / binary bytes, so this throw can fire in the field. Because the decode path can be reached from a host application's debug-event listener with no surrounding
try/catch, the unhandled C++ exception propagates out of the SDK and terminates the hosting process.Representative stack:
Fix
Use
error_handler_t::replaceso malformed UTF-8 is replaced with U+FFFD instead of throwing. Applied to both decoder entry points:DecodeRequest:j.dump(2)->j.dump(2, ' ', false, json::error_handler_t::replace)DecodeRecord:j.dump(4)->j.dump(4, ' ', false, json::error_handler_t::replace)The decoder still returns the (sanitized) JSON; it can no longer throw or crash the host on non-UTF-8 telemetry.
Tests
Adds
tests/unittests/PayloadDecoderTests.cpp(registered intests/unittests/CMakeLists.txt):DecodeRecord_InvalidUtf8_DoesNotThrow: builds a minimalCsProtocol::Record, injects an invalid UTF-8 byte (0xFF) into a string field, and assertsDecodeRecorddoes not throw and emits U+FFFD (EF BF BD) rather than the raw byte.DecodeRecord_ValidUtf8_IsPreserved: verifies well-formed UTF-8 is left untouched.Risk / compatibility
dump(); no public API change.