diff --git a/hilti/runtime/include/types/bytes.h b/hilti/runtime/include/types/bytes.h index 53d1a0823..2a2b085b5 100644 --- a/hilti/runtime/include/types/bytes.h +++ b/hilti/runtime/include/types/bytes.h @@ -572,7 +572,7 @@ class Bytes : protected std::string { }; inline std::ostream& operator<<(std::ostream& out, const Bytes& x) { - out << escapeBytes(x.str()); + out << escapeBytes(x.str(), render_style::Bytes::NoEscapeBackslash); return out; } @@ -584,7 +584,7 @@ inline Bytes operator"" _b(const char* str, size_t size) { return Bytes(Bytes::B template<> inline std::string detail::to_string_for_print(const Bytes& x) { - return escapeBytes(x.str()); + return escapeBytes(x.str(), render_style::Bytes::NoEscapeBackslash); } namespace detail::adl { diff --git a/hilti/runtime/include/types/string.h b/hilti/runtime/include/types/string.h index 535cf1bc8..69e840a3b 100644 --- a/hilti/runtime/include/types/string.h +++ b/hilti/runtime/include/types/string.h @@ -108,12 +108,14 @@ inline std::string to_string(const CharT (&x)[N], adl::tag /*unused*/) { template<> inline std::string detail::to_string_for_print(const std::string& x) { - return escapeUTF8(x, render_style::UTF8::NoEscapeHex | render_style::UTF8::NoEscapeControl); + return escapeUTF8(x, render_style::UTF8::NoEscapeHex | render_style::UTF8::NoEscapeControl | + render_style::UTF8::NoEscapeBackslash); } template<> inline std::string detail::to_string_for_print(const std::string_view& x) { - return escapeUTF8(x, render_style::UTF8::NoEscapeHex | render_style::UTF8::NoEscapeControl); + return escapeUTF8(x, render_style::UTF8::NoEscapeHex | render_style::UTF8::NoEscapeControl | + render_style::UTF8::NoEscapeBackslash); } // Specialization for string literals. Since `to_string_for_print` is not diff --git a/hilti/runtime/include/util.h b/hilti/runtime/include/util.h index ca2b3843b..263ec46e2 100644 --- a/hilti/runtime/include/util.h +++ b/hilti/runtime/include/util.h @@ -297,9 +297,10 @@ namespace render_style { * the default style accordingly. */ enum class Bytes { - Default = 0, /**< name for unmodified default style */ - EscapeQuotes = (1U << 1U), /**< escape double quotes with backslashes */ - UseOctal = (1U << 2U), /**< escape non-printables with `\NNN` instead of `\xNN` */ + Default = 0, /**< name for unmodified default style */ + EscapeQuotes = (1U << 1U), /**< escape double quotes with backslashes */ + UseOctal = (1U << 2U), /**< escape non-printables with `\NNN` instead of `\xNN` */ + NoEscapeBackslash = (1U << 3U), /**< do not escape backslashes */ }; /** @@ -311,11 +312,12 @@ enum class Bytes { * through `expandUTF8Escapes()`. */ enum class UTF8 { - Default = 0, /**< name for unmodified default style */ - EscapeQuotes = (1U << 1U), /**< escape double quotes with backslashes */ - NoEscapeControl = (1U << 2U), /**< do not escape control characters and null bytes */ + Default = 0, /**< name for unmodified default style */ + EscapeQuotes = (1U << 1U), /**< escape double quotes with backslashes */ + NoEscapeBackslash = (1U << 2U), /**< do not escape backslashes; this may leave the result non-reversible */ + NoEscapeControl = (1U << 3U), /**< do not escape control characters and null bytes */ NoEscapeHex = - (1U << 3U), /**< do not escape already existing `\xNN` escape codes; this may leave the result non-reversible */ + (1U << 4U), /**< do not escape already existing `\xNN` escape codes; this may leave the result non-reversible */ }; } // namespace render_style diff --git a/hilti/runtime/src/tests/bytes.cc b/hilti/runtime/src/tests/bytes.cc index 37a15463d..f443a9455 100644 --- a/hilti/runtime/src/tests/bytes.cc +++ b/hilti/runtime/src/tests/bytes.cc @@ -653,4 +653,13 @@ TEST_CASE("issue 599") { CHECK_EQ(a->toInt(), 31); } +TEST_CASE("to_string") { + CHECK_EQ(to_string("abc"_b), "b\"abc\""); + CHECK_EQ(to_string("\"\\"_b), "b\"\\\"\\\\\""); +} + +TEST_CASE("to_string_for_print") { + CHECK_EQ(to_string_for_print("abc"_b), "abc"); + CHECK_EQ(to_string_for_print("\\\""_b), "\\\""); +} TEST_SUITE_END(); diff --git a/hilti/runtime/src/tests/deferred-expression.cc b/hilti/runtime/src/tests/deferred-expression.cc index 60c633133..f5cad06d3 100644 --- a/hilti/runtime/src/tests/deferred-expression.cc +++ b/hilti/runtime/src/tests/deferred-expression.cc @@ -89,8 +89,8 @@ TEST_CASE("to_string_for_print") { auto expr = make_deferred([&i]() { return Bytes(fmt("\\x0%d", ++i)); }); // Stringification evaluates the expression. - CHECK_EQ(to_string_for_print(expr), R"#(\\x01)#"); - CHECK_EQ(to_string_for_print(expr), R"#(\\x02)#"); + CHECK_EQ(to_string_for_print(expr), R"#(\x01)#"); + CHECK_EQ(to_string_for_print(expr), R"#(\x02)#"); } TEST_SUITE_END(); diff --git a/hilti/runtime/src/tests/string.cc b/hilti/runtime/src/tests/string.cc index a3394737d..71e56e2c2 100644 --- a/hilti/runtime/src/tests/string.cc +++ b/hilti/runtime/src/tests/string.cc @@ -66,12 +66,16 @@ TEST_CASE("to_string") { CHECK_EQ(to_string(std::string("abc")), "\"abc\""); CHECK_EQ(to_string(std::string_view("abc")), "\"abc\""); CHECK_EQ(to_string("abc"), "\"abc\""); + CHECK_EQ(to_string("\"\\"), "\"\\\"\\\\\""); } TEST_CASE("to_string_for_print") { CHECK_EQ(to_string_for_print(std::string("abc")), "abc"); CHECK_EQ(to_string_for_print(std::string_view("abc")), "abc"); CHECK_EQ(to_string_for_print("abc"), "abc"); + CHECK_EQ(to_string_for_print(std::string("\\\"")), "\\\""); + CHECK_EQ(to_string_for_print(std::string_view("\\\"")), "\\\""); + CHECK_EQ(to_string_for_print("\\\""), "\\\""); } TEST_CASE("split") { diff --git a/hilti/runtime/src/util.cc b/hilti/runtime/src/util.cc index c2d6b56c2..1f0ad0fee 100644 --- a/hilti/runtime/src/util.cc +++ b/hilti/runtime/src/util.cc @@ -294,8 +294,10 @@ std::string hilti::rt::escapeUTF8(std::string_view s, bitmask= 0.63. +\", \" +\", \" +5C22, 5C22 diff --git a/tests/spicy/rt/print-escaping.spicy b/tests/spicy/rt/print-escaping.spicy new file mode 100644 index 000000000..7d52487f4 --- /dev/null +++ b/tests/spicy/rt/print-escaping.spicy @@ -0,0 +1,17 @@ +# @TEST-EXEC: spicyc -j -d %INPUT >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Check that we escape certain special characters in strings correctly when printing them. + +module test; + +import spicy; + +global x = b"\\\""; +global y = "\\\""; + +print x, y; +print "%s, %s" % (x, y); + +# for comparison +print spicy::bytes_to_hexstring(x), spicy::bytes_to_hexstring(y.encode());