[llvm] [Remarks] Escape control characters in multi-line YAML remark arguments (PR #227654)
Henrich Lauko via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 03:46:45 PDT 2026
https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/227654
Argument values with more than one newline are written as YAML literal block scalars. A block scalar has no escapes, so a value that also contains a control character (other than tab and newline) was written with the raw byte. For example, `"abc\ndef\n\x01ghi"` produced a document that PyYAML rejects with `unacceptable character #x0001`. `llvm-remarkutil` still accepts it.
Such values now use the double-quoted form, which escapes them. Multi-line values without control characters still use the block form. A carriage return also falls back to the quoted form, since a block scalar would turn it into a line break.
Note: the YAML remark parser does not unescape double-quoted values. `YAMLRemarkParser::parseStr` returns the raw scalar and only strips single quotes, so `"abc\ndef\n\x01ghi"` is read back with its quotes and escapes. This is not new: it already happens for every value the serializer double-quotes, for example one with a single newline. I left it for a separate change.
Also not covered here:
- `yaml::escape` does not escape DEL (0x7F). It is written raw even in double-quoted scalars, on any YAML output path.
- The serializer still does not check non-ASCII bytes. UTF-8 C1 controls and invalid UTF-8 still take the block path, as before.
>From 00b72f10da9012ea5c8adfa7b8da89bb4e87939f 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] Escape control characters in multi-line YAML remark
arguments
Argument values with more than one newline are written as YAML literal
block scalars. A block scalar has no escapes, so a value that also
contains a control character (other than tab and newline) was written
with the raw byte, which strict YAML readers reject. Use the
double-quoted form for such values instead.
Assisted-by: Claude
---
llvm/lib/Remarks/YAMLRemarkSerializer.cpp | 12 +++++++-
.../Remarks/YAMLRemarksSerializerTest.cpp | 29 +++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
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/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