Internal change

PiperOrigin-RevId: 496537672
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 6dbe207..038cfee 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -345,6 +345,7 @@
     srcs = ["internal/serialization.cc"],
     hdrs = ["internal/serialization.h"],
     deps = [
+        ":logging",
         ":meta",
         "@com_google_absl//absl/numeric:int128",
         "@com_google_absl//absl/strings",
diff --git a/fuzztest/internal/serialization.cc b/fuzztest/internal/serialization.cc
index d276d9c..73dd439 100644
--- a/fuzztest/internal/serialization.cc
+++ b/fuzztest/internal/serialization.cc
@@ -18,7 +18,6 @@
 #include <cstddef>
 #include <cstdint>
 #include <limits>
-#include <optional>
 #include <string>
 #include <vector>
 
@@ -72,36 +71,12 @@
   }
 };
 
-constexpr std::string_view kHeader = "FUZZTESTv1";
-
-absl::string_view AsAbsl(std::string_view str) {
-  return {str.data(), str.size()};
-}
-
-std::string_view ReadToken(std::string_view& in) {
-  while (!in.empty() && std::isspace(in[0])) in.remove_prefix(1);
-  if (in.empty()) return in;
-  size_t end = 1;
-  const auto is_literal = [](char c) {
-    return std::isalnum(c) != 0 || c == '+' || c == '-' || c == '.';
-  };
-  if (is_literal(in[0])) {
-    while (end < in.size() && is_literal(in[end])) ++end;
-  } else if (in[0] == '"') {
-    while (end < in.size() && in[end] != '"') ++end;
-    if (end < in.size()) ++end;
-  }
-  std::string_view res = in.substr(0, end);
-  in.remove_prefix(end);
-  return res;
-}
-
 bool ReadScalar(uint64_t& out, std::string_view value) {
-  return absl::SimpleAtoi(AsAbsl(value), &out);
+  return absl::SimpleAtoi(IRObject{}.AsAbsl(value), &out);
 }
 
 bool ReadScalar(double& out, std::string_view value) {
-  return absl::SimpleAtod(AsAbsl(value), &out);
+  return absl::SimpleAtod(IRObject{}.AsAbsl(value), &out);
 }
 
 bool ReadScalar(std::string& out, std::string_view value) {
@@ -134,7 +109,31 @@
   return true;
 }
 
-bool ParseImpl(IRObject& obj, std::string_view& str) {
+}  // namespace
+
+void IRObject::Visit(std::string& out) const {
+  absl::visit(OutputVisitor{value.index(), 0, out}, value);
+}
+
+std::string_view IRObject::ReadToken(std::string_view& in) const {
+  while (!in.empty() && std::isspace(in[0])) in.remove_prefix(1);
+  if (in.empty()) return in;
+  size_t end = 1;
+  const auto is_literal = [](char c) {
+    return std::isalnum(c) != 0 || c == '+' || c == '-' || c == '.';
+  };
+  if (is_literal(in[0])) {
+    while (end < in.size() && is_literal(in[end])) ++end;
+  } else if (in[0] == '"') {
+    while (end < in.size() && in[end] != '"') ++end;
+    if (end < in.size()) ++end;
+  }
+  std::string_view res = in.substr(0, end);
+  in.remove_prefix(end);
+  return res;
+}
+
+bool IRObject::ParseImpl(IRObject& obj, std::string_view& str) {
   std::string_view key = ReadToken(str);
   if (key.empty() || key == "}") {
     // The object is empty. Put the token back and return.
@@ -171,19 +170,4 @@
   }
 }
 
-}  // namespace
-
-std::string IRObject::ToString() const {
-  std::string out = absl::StrCat(AsAbsl(kHeader), "\n");
-  absl::visit(OutputVisitor{value.index(), 0, out}, value);
-  return out;
-}
-
-std::optional<IRObject> IRObject::FromString(std::string_view str) {
-  IRObject object;
-  if (ReadToken(str) != kHeader) return std::nullopt;
-  if (!ParseImpl(object, str) || !ReadToken(str).empty()) return std::nullopt;
-  return object;
-}
-
 }  // namespace fuzztest::internal
diff --git a/fuzztest/internal/serialization.h b/fuzztest/internal/serialization.h
index 2ce6092..82c2d65 100644
--- a/fuzztest/internal/serialization.h
+++ b/fuzztest/internal/serialization.h
@@ -23,11 +23,15 @@
 #include <tuple>
 #include <type_traits>
 #include <utility>
+#include <variant>
 #include <vector>
 
 #include "absl/numeric/int128.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "absl/types/variant.h"
+#include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 
 namespace fuzztest::internal {
@@ -260,10 +264,45 @@
     }
   }
 
+  static constexpr std::string_view kHeader = "FUZZTESTv1";
+
+  absl::string_view AsAbsl(std::string_view str) const {
+    return {str.data(), str.size()};
+  }
+
   // Serialize the object as a string. This is used to persist the object on
   // files for reproducing bugs later.
-  std::string ToString() const;
-  static std::optional<IRObject> FromString(std::string_view str);
+  template <typename ValueType>
+  std::string ToString() const {
+    // Return single string-like or proto values as raw strings.
+    if constexpr (std::is_same_v<ValueType, std::string> ||
+                  is_protocol_buffer_v<ValueType>) {
+      FUZZTEST_INTERNAL_CHECK_PRECONDITION(
+          absl::holds_alternative<std::string>(value),
+          "String-like value should hold a string!");
+      return absl::get<std::string>(value);
+    }
+    std::string out = absl::StrCat(AsAbsl(kHeader), "\n");
+    // Construct out using IRObject format.
+    IRObject::Visit(out);
+    return out;
+  }
+
+  template <typename ValueType>
+  std::optional<IRObject> FromString(std::string_view str) {
+    IRObject object;
+    if constexpr (std::is_same_v<ValueType, std::string>) {
+      object.value.emplace<std::string>(str);
+      FUZZTEST_INTERNAL_CHECK(
+          absl::holds_alternative<std::string>(object.value),
+          "IRObject value should hold a string after deserializing from a "
+          "single string-like value!");
+      return object;
+    }
+    if (ReadToken(str) != kHeader) return std::nullopt;
+    if (!ParseImpl(object, str) || !ReadToken(str).empty()) return std::nullopt;
+    return object;
+  }
 
  private:
   template <typename T>
@@ -272,6 +311,10 @@
   template <typename K, typename V>
   static std::pair<std::remove_const_t<K>, std::remove_const_t<V>>
       RemoveConstFromPair(std::pair<K, V>);
+
+  void Visit(std::string& out) const;
+  std::string_view ReadToken(std::string_view& in) const;
+  bool ParseImpl(IRObject& obj, std::string_view& str);
 };
 
 }  // namespace fuzztest::internal
diff --git a/fuzztest/internal/serialization_test.cc b/fuzztest/internal/serialization_test.cc
index d0113ac..773228c 100644
--- a/fuzztest/internal/serialization_test.cc
+++ b/fuzztest/internal/serialization_test.cc
@@ -39,8 +39,8 @@
 
 using testing::_;
 using testing::ElementsAre;
-using testing::Eq;
 using testing::FieldsAre;
+using testing::HasSubstr;
 using testing::NanSensitiveDoubleEq;
 using testing::Not;
 using testing::Optional;
@@ -92,7 +92,10 @@
 
 void VerifyProtobufFormat(const IRObject& object) {
   IRObjectTestProto proto;
-  std::string s = object.ToString();
+  std::string s = object.ToString<IRObject>();
+  // TODO(cnie0017) This is not a IRObjectTestProto
+  // since it is already being parsed to String once. It is now of IRObject form
+
   // Chop the header.
   s.erase(0, strlen("FUZZTESTv1\n"));
 
@@ -104,14 +107,19 @@
 void RoundTripVerify(const T&... values) {
   IRObject object;
   object.value = std::vector{IRObject{values}...};
-  std::string s = object.ToString();
+  std::string s = object.ToString<IRObject>();
 
   SCOPED_TRACE(s);
 
   VerifyProtobufFormat(object);
 
-  EXPECT_THAT(IRObject::FromString(s),
+  // TODO(cnie0017): why can't use T?
+  EXPECT_THAT(IRObject{}.FromString<IRObject>(s),
               Optional(SubsAre(ValueIs<T>(values)...)));
+  // EXPECT_THAT(IRObject::FromString<T>(s),
+  //             Optional(SubsAre(ValueIs<T>(values)...)));
+  // // Initializer contains unexpanded parameter pack
+  // // 'T'clang(unexpanded_parameter_pack)
 }
 
 template <typename T>
@@ -134,13 +142,13 @@
                                                      IRObject{"child3.2.2"}}}}},
       IRObject{"child4"}}};
 
-  std::string s = root.ToString();
+  std::string s = root.ToString<IRObject>();
 
   SCOPED_TRACE(s);
 
   VerifyProtobufFormat(root);
 
-  std::optional<IRObject> obj = IRObject::FromString(s);
+  std::optional<IRObject> obj = IRObject{}.FromString<IRObject>(s);
   EXPECT_THAT(
       obj, Optional(SubsAre(
                ValueIs<std::string>("child1"), ValueIs<std::string>("child2"),
@@ -151,9 +159,10 @@
 }
 
 TEST(SerializerTest, EmptyObjectRoundTrips) {
-  std::string s = IRObject{}.ToString();
+  std::string s = IRObject{}.ToString<std::nullptr_t>();
   SCOPED_TRACE(s);
-  EXPECT_THAT(IRObject::FromString(s), Optional(ValueIs<std::monostate>({})));
+  EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>(s),
+              Optional(ValueIs<std::monostate>({})));
 }
 
 TEST(SerializerTest, IndentationIsCorrect) {
@@ -169,7 +178,7 @@
                                            IRObject{uint64_t{322}}}}}},
                   IRObject{uint64_t{4}}}};
 
-  std::string s = root.ToString();
+  std::string s = root.ToString<IRObject>();
 
   EXPECT_EQ(s, R"(FUZZTESTv1
 sub { i: 1 }
@@ -185,50 +194,67 @@
 )");
 }
 
+// TODO(cnie0017): ensure the type passed in are correct
 // We manually write the serialized form to test the error handling of the
 // parser. The serializer would not generate these, so we can't use it.
 TEST(SerializerTest, WrongHeaderWontParse) {
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: 0"), Optional(_));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv2"), Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("FUZZtESTv1"), Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("-FUZZTESTv1"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 0"),
+              Optional(_));
+  EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZTESTv2"),
+              Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZtESTv1"),
+              Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("-FUZZTESTv1"),
+              Not(Optional(_)));
 }
 
 TEST(SerializerTest, HandlesUnterminatedString) {
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1\""), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZTESTv1\""),
+              Not(Optional(_)));
 }
 
 TEST(SerializerTest, BadScalarWontParse) {
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: 1"),
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 1"),
               Optional(ValueIs<uint64_t>(1)));
   // Out of bounds values
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: 123456789012345678901"),
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>(
+                  "FUZZTESTv1 i: 123456789012345678901"),
               Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: -1"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: -1"),
+              Not(Optional(_)));
   // Missing :
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i 1"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i 1"),
+              Not(Optional(_)));
   // Bad tag
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 x: 1"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 x: 1"),
+              Not(Optional(_)));
   // Wrong separator
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i; 1"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i; 1"),
+              Not(Optional(_)));
   // Extra close
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: 1}"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 1}"),
+              Not(Optional(_)));
 }
 
 TEST(SerializerTest, BadSubWontParse) {
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub { i: 0 }"),
+  EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { i: 0 }"),
               Optional(SubsAre(ValueIs<uint64_t>(0))));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub: { }"), Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub  }"), Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub { "), Not(Optional(_)));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub { } }"), Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub: { }"),
+              Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub  }"),
+              Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { "),
+              Not(Optional(_)));
+  EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { } }"),
+              Not(Optional(_)));
 }
 
 TEST(SerializerTest, ExtraWhitespaceIsFine) {
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 i: 0 \n "),
+  EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 0 \n "),
               Optional(ValueIs<uint64_t>(0)));
-  EXPECT_THAT(IRObject::FromString("FUZZTESTv1 sub {   \n i:   0 \n}  \n "),
-              Optional(SubsAre(ValueIs<uint64_t>(0))));
+  EXPECT_THAT(
+      IRObject{}.FromString<IRObject>("FUZZTESTv1 sub {   \n i:   0 \n}  \n "),
+      Optional(SubsAre(ValueIs<uint64_t>(0))));
 }
 
 template <typename T>
@@ -239,7 +265,7 @@
   obj.SetScalar(value);
   EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
 
-  auto roundtrip = IRObject::FromString(obj.ToString());
+  auto roundtrip = IRObject{}.FromString<IRObject>(obj.ToString<IRObject>());
   EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
 }
 
@@ -383,6 +409,51 @@
                    .ToCorpus<Tuple>());
 }
 
+TEST(SerializerTest, SingleStringValueIsSerializedAsRawString) {
+  std::string s("ABC");
+  IRObject obj;
+  obj.value = s;
+  std::string s_serialized = obj.ToString<std::string>();
+
+  // Should have no header.
+  EXPECT_THAT(s_serialized, Not(HasSubstr("FUZZTESTv1\n")));
+  EXPECT_EQ(s, s_serialized);
+}
+
+TEST(SerializerTest, SingleProtoValueIsSerializaedAsRawString) {
+  IRObjectTestProto proto;
+  IRObject obj = IRObject::FromCorpus(proto);
+  std::string proto_serialized = obj.ToString<IRObjectTestProto>();
+
+  // Should have no header.
+  EXPECT_THAT(proto_serialized, Not(HasSubstr("FUZZTESTv1\n")));
+  ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_serialized, &proto));
+  std::visit(VerifyVisitor{proto}, obj.value);
+}
+
+TEST(SerializerTest, SingleStringSerializationRoundTripVerify) {
+  std::string s("ABC");
+  IRObject obj;
+  obj.value = s;
+  std::string s_serialized = obj.ToString<std::string>();
+  std::optional<IRObject> obj_from_str =
+      IRObject{}.FromString<std::string>(s_serialized);
+
+  EXPECT_THAT(obj_from_str, Optional(ValueIs<std::string>(s)));
+}
+
+TEST(SerializerTest, SingleProtoSerializationRoundTripVerify) {
+  IRObjectTestProto proto;
+  std::string proto_str;
+  ASSERT_TRUE(google::protobuf::TextFormat::PrintToString(proto, &proto_str));
+  IRObject obj = IRObject::FromCorpus(proto);
+  std::string proto_serialized = obj.ToString<IRObjectTestProto>();
+  std::optional<IRObject> obj_from_str =
+      IRObject{}.FromString<std::string>(proto_serialized);
+
+  EXPECT_THAT(obj_from_str, Optional(ValueIs<std::string>(proto_str)));
+}
+
 // TODO(sbenzaquen): Add tests for failing conditions in the IR->Corpus conversion.
 
 }  // namespace