[llvm] [Remarks] Fix YAML remark round-trip for values that need escaping (PR #227654)
Henrich Lauko via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 04:03:11 PDT 2026
https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/227654
>From 629ca6556fa12dbeb69ada35d6a33ce6cff710df Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Wed, 30 Sep 2026 10:41:35 +0000
Subject: [PATCH] [Remarks] Fix YAML remark round-trip for values that need
escaping
The serializer writes argument values with more than one newline as
literal block scalars. A block scalar has no escapes, so a value that
also contained a control character other than tab or newline was
written with the raw byte, which strict YAML readers reject. Such
values now use the double-quoted form.
The parser took the raw scalar text and stripped only single quotes, so
double-quoted values came back with their quotes and escapes, and ''
inside single quotes was not unescaped. Decode scalars with
ScalarNode::getValue instead and report escape errors. Unescaped values
and block scalar values are copied into storage owned by the parser.
Block scalar values used to point into the YAML document, which next()
frees before returning the remark.
Assisted-by: Claude
---
llvm/lib/Remarks/YAMLRemarkParser.cpp | 18 ++++---
llvm/lib/Remarks/YAMLRemarkParser.h | 6 +++
llvm/lib/Remarks/YAMLRemarkSerializer.cpp | 12 ++++-
.../Remarks/YAMLRemarksParsingTest.cpp | 49 +++++++++++++++++++
.../Remarks/YAMLRemarksSerializerTest.cpp | 29 +++++++++++
5 files changed, 107 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Remarks/YAMLRemarkParser.cpp b/llvm/lib/Remarks/YAMLRemarkParser.cpp
index 33f659eaaa49b..30acbb459a3b1 100644
--- a/llvm/lib/Remarks/YAMLRemarkParser.cpp
+++ b/llvm/lib/Remarks/YAMLRemarkParser.cpp
@@ -273,12 +273,18 @@ Expected<StringRef> YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) {
ValueBlock = dyn_cast_if_present<yaml::BlockScalarNode>(Node.getValue());
if (!ValueBlock)
return error("expected a value of scalar type.", Node);
- Result = ValueBlock->getValue();
- } else
- Result = Value->getRawValue();
-
- Result.consume_front("\'");
- Result.consume_back("\'");
+ // The block value lives in the YAML document, which next() frees before
+ // returning the remark.
+ Result = Saver.save(ValueBlock->getValue());
+ } else {
+ SmallString<32> Storage;
+ Result = Value->getValue(Storage);
+ if (Error E = error())
+ return std::move(E);
+ // getValue only uses Storage when it had to unescape the value.
+ if (Result.data() == Storage.data())
+ Result = Saver.save(Result);
+ }
return Result;
}
diff --git a/llvm/lib/Remarks/YAMLRemarkParser.h b/llvm/lib/Remarks/YAMLRemarkParser.h
index 9a30e9e295cb2..470858a7e9954 100644
--- a/llvm/lib/Remarks/YAMLRemarkParser.h
+++ b/llvm/lib/Remarks/YAMLRemarkParser.h
@@ -15,9 +15,11 @@
#include "llvm/Remarks/Remark.h"
#include "llvm/Remarks/RemarkParser.h"
+#include "llvm/Support/Allocator.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/StringSaver.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
@@ -58,6 +60,10 @@ struct YAMLRemarkParser : public RemarkParser {
/// If we parse remark metadata in separate mode, we need to open a new file
/// and parse that.
std::unique_ptr<MemoryBuffer> SeparateBuf;
+ /// Storage for values that do not point into the input buffer: unescaped
+ /// scalars and block scalars. Remarks point into it.
+ BumpPtrAllocator Alloc;
+ StringSaver Saver{Alloc};
YAMLRemarkParser(StringRef Buf);
diff --git a/llvm/lib/Remarks/YAMLRemarkSerializer.cpp b/llvm/lib/Remarks/YAMLRemarkSerializer.cpp
index 22e297040575c..62bf9ed26d8e4 100644
--- a/llvm/lib/Remarks/YAMLRemarkSerializer.cpp
+++ b/llvm/lib/Remarks/YAMLRemarkSerializer.cpp
@@ -12,6 +12,8 @@
//===----------------------------------------------------------------------===//
#include "llvm/Remarks/YAMLRemarkSerializer.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Remarks/Remark.h"
#include "llvm/Support/FileSystem.h"
#include <optional>
@@ -109,6 +111,14 @@ template <typename T> struct SequenceTraits<ArrayRef<T>> {
}
};
+/// A literal block scalar has no escapes, so it cannot hold control characters
+/// other than tab and line feed. UTF-8 is written as-is.
+static bool canUseBlockScalar(StringRef S) {
+ return all_of(S, [](unsigned char C) {
+ return isPrint(C) || C == '\t' || C == '\n' || C >= 0x80;
+ });
+}
+
/// Implement this as a mapping for now to get proper quotation for the value.
template <> struct MappingTraits<Argument> {
static void mapping(IO &io, Argument &A) {
@@ -116,7 +126,7 @@ template <> struct MappingTraits<Argument> {
// NB: A.Key.data() is not necessarily null-terminated, as the StringRef may
// be a span into the middle of a string.
- if (StringRef(A.Val).count('\n') > 1) {
+ if (StringRef(A.Val).count('\n') > 1 && canUseBlockScalar(A.Val)) {
StringBlockVal S(A.Val);
io.mapRequired(A.Key, S);
} else {
diff --git a/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp b/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
index 824813aa5af7c..7c117ea897d97 100644
--- a/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
+++ b/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
@@ -475,6 +475,55 @@ TEST(YAMLRemarks, Contents) {
EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors.
}
+TEST(YAMLRemarks, ContentsQuoted) {
+ StringRef Buf = "--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - Single: 'it''s'\n"
+ " - Double: \"abc\\ndef\\n\\x01ghi\"\n"
+ " - Block: |\n"
+ " 'abc'\n"
+ " def\n"
+ "--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - Block: |\n"
+ " xxxxxxxxxx\n"
+ " xxxxxxxxxx\n"
+ "\n";
+
+ Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser =
+ remarks::createRemarkParser(remarks::Format::YAML, Buf);
+ EXPECT_FALSE(errorToBool(MaybeParser.takeError()));
+ EXPECT_TRUE(*MaybeParser != nullptr);
+
+ remarks::RemarkParser &Parser = **MaybeParser;
+ Expected<std::unique_ptr<remarks::Remark>> MaybeRemark = Parser.next();
+ EXPECT_FALSE(errorToBool(MaybeRemark.takeError()));
+ EXPECT_TRUE(*MaybeRemark != nullptr);
+ // The values must outlive the YAML document they were parsed from.
+ Expected<std::unique_ptr<remarks::Remark>> MaybeNext = Parser.next();
+ EXPECT_FALSE(errorToBool(MaybeNext.takeError()));
+
+ const remarks::Remark &Remark = **MaybeRemark;
+ ASSERT_EQ(Remark.Args.size(), 3U);
+ EXPECT_EQ(checkStr(Remark.Args[0].Val, 4), "it's");
+ EXPECT_EQ(checkStr(Remark.Args[1].Val, 12), "abc\ndef\n\x01ghi");
+ EXPECT_EQ(checkStr(Remark.Args[2].Val, 10), "'abc'\ndef\n");
+
+ EXPECT_TRUE(parseExpectError("--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - Double: \"a\\qb\"\n",
+ "Unrecognized escape code"));
+}
+
static inline StringRef checkStr(LLVMRemarkStringRef Str,
unsigned ExpectedLen) {
const char *StrData = LLVMRemarkStringGetData(Str);
diff --git a/llvm/unittests/Remarks/YAMLRemarksSerializerTest.cpp b/llvm/unittests/Remarks/YAMLRemarksSerializerTest.cpp
index 974356d9cf30a..859ec01c2c4ee 100644
--- a/llvm/unittests/Remarks/YAMLRemarksSerializerTest.cpp
+++ b/llvm/unittests/Remarks/YAMLRemarksSerializerTest.cpp
@@ -193,3 +193,32 @@ TEST(YAMLRemarks, SerializerRemarkStringRefOOBRead) {
" DebugLoc: { File: argpath, Line: 6, Column: 7 }\n"
"...\n");
}
+
+TEST(YAMLRemarks, SerializerRemarkMultiLineArg) {
+ remarks::Remark R;
+ R.RemarkType = remarks::Type::Missed;
+ R.PassName = "pass";
+ R.RemarkName = "name";
+ R.FunctionName = "func";
+ R.Args.emplace_back();
+ R.Args.back().Key = "block";
+ R.Args.back().Val = "abc\ndef\nghi";
+ // A literal block scalar cannot hold control characters, so this has to be
+ // escaped in a double-quoted scalar instead.
+ R.Args.emplace_back();
+ R.Args.back().Key = "control";
+ R.Args.back().Val = "abc\ndef\n\x01"
+ "ghi";
+ checkStandalone(remarks::Format::YAML, R,
+ "--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - block: |\n"
+ " abc\n"
+ " def\n"
+ " ghi\n"
+ " - control: \"abc\\ndef\\n\\x01ghi\"\n"
+ "...\n");
+}
More information about the llvm-commits
mailing list