[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