[Lldb-commits] [lldb] ee7f9bc - [lldb] Fix invalid UTF-8 in JSON log message (#216185)
via lldb-commits
lldb-commits at lists.llvm.org
Sun Aug 16 03:25:35 PDT 2026
Author: Yao Qi
Date: 2026-08-16T11:25:30+01:00
New Revision: ee7f9bcf43f0f1adc7d98f23fe56e4331b508454
URL: https://github.com/llvm/llvm-project/commit/ee7f9bcf43f0f1adc7d98f23fe56e4331b508454
DIFF: https://github.com/llvm/llvm-project/commit/ee7f9bcf43f0f1adc7d98f23fe56e4331b508454.diff
LOG: [lldb] Fix invalid UTF-8 in JSON log message (#216185)
Enabling the JSON packet log part way through a session aborts an
assertions build:
```
(lldb) b f
(lldb) run
(lldb) log enable -j -f /tmp/pk.json gdb-remote packets
(lldb) next
Assertion failed: (false && "Invalid UTF-8 in value used as JSON"), function Value, file JSON.h, line 333.
```
`Log::EmitJSONMessage` passed the message straight to
`llvm::json::Value`,
which asserts on ill-formed UTF-8 and only then falls back to `fixUTF8`.
So a release build repairs the message while an assertions build dies.
The bytes come from the saved packets. Once logging is turned on,
`GDBRemoteCommunicationHistory::Dump` replays them verbatim, and the
reply
to an `x` (binary memory read) is binary:
```
history[80] tid=0x5c0f < 18> send packet: $x16fdfea00,200#f3
history[81] tid=0x5c0f < 516> read packet: $<binary>
```
The normal live packet path escapes binary before logging it, so this is
usually only seen when logging is enabled mid-session.
Added:
Modified:
lldb/source/Utility/Log.cpp
lldb/unittests/Utility/LogTest.cpp
Removed:
################################################################################
diff --git a/lldb/source/Utility/Log.cpp b/lldb/source/Utility/Log.cpp
index dd813efb90f90..62693dc314758 100644
--- a/lldb/source/Utility/Log.cpp
+++ b/lldb/source/Utility/Log.cpp
@@ -438,7 +438,11 @@ void Log::EmitJSONMessage(llvm::StringRef file, llvm::StringRef function,
llvm::StringRef message) {
llvm::json::Object obj;
WriteJSONHeader(obj, file, function);
- obj["message"] = message;
+ // Log messages can carry arbitrary bytes; JSON strings must be valid UTF-8.
+ if (llvm::json::isUTF8(message))
+ obj["message"] = message;
+ else
+ obj["message"] = llvm::json::fixUTF8(message);
std::string out;
llvm::raw_string_ostream os(out);
os << llvm::json::Value(std::move(obj)) << "\n";
diff --git a/lldb/unittests/Utility/LogTest.cpp b/lldb/unittests/Utility/LogTest.cpp
index 6eaa318dfe34c..8cfcef6d504c0 100644
--- a/lldb/unittests/Utility/LogTest.cpp
+++ b/lldb/unittests/Utility/LogTest.cpp
@@ -387,6 +387,35 @@ TEST_F(LogChannelEnabledTest, JSONLOutput) {
EXPECT_EQ(Obj->getString("function").value_or(""), "logAndTakeOutput");
}
+TEST_F(LogChannelEnabledTest, JSONLOutputInvalidUTF8) {
+ // Arbitrary bytes must not abort the JSON writer.
+ EXPECT_THAT_ERROR(Log::EnableLogChannel(getLogHandler(),
+ /*log_options=*/LLDB_LOG_OPTION_JSON,
+ "chan", {}),
+ llvm::Succeeded());
+
+ auto CheckMessage = [](llvm::StringRef Msg) {
+ llvm::Expected<llvm::json::Value> Parsed = llvm::json::parse(Msg);
+ ASSERT_TRUE(static_cast<bool>(Parsed))
+ << llvm::toString(Parsed.takeError());
+ llvm::json::Object *Obj = Parsed->getAsObject();
+ ASSERT_NE(Obj, nullptr);
+ std::optional<llvm::StringRef> Message = Obj->getString("message");
+ ASSERT_TRUE(Message.has_value());
+ EXPECT_TRUE(llvm::json::isUTF8(*Message));
+ EXPECT_TRUE(Message->contains("before"));
+ EXPECT_TRUE(Message->contains("after"));
+ };
+
+ // Check both entry points: Format and PutString.
+ CheckMessage(logAndTakeOutput("before\xff\xfe"
+ "after"));
+
+ getLog()->PutString("before\xff\xfe"
+ "after");
+ CheckMessage(takeOutput());
+}
+
TEST_F(LogChannelEnabledTest, LLDB_LOG_ERROR) {
LLDB_LOG_ERROR(getLog(), llvm::Error::success(), "Foo failed: {0}");
ASSERT_EQ("", takeOutput());
More information about the lldb-commits
mailing list