[code-health] Add ParamTraits for base::Value::{Dict,List}

* Use the new types internally, rather than the deprecated types.
* Add conversion unit tests for both deprecated and new types.
* Behavior change: a base::Value can no longer be read as a
  base::Value::List or base::Value::Dict or vice-versa. The type
  inferrence should be enough for the caller to use the right type.

Bug: 1301889
Bug: 1301890
Change-Id: I3279e30e586d7443603042b6b9a31f01e8ff41c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3558072
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: Ken Rockot <[email protected]>
Commit-Queue: Fabrice de Gans <[email protected]>
Cr-Commit-Position: refs/heads/main@{#988178}
diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc
index b80fe75..def0eee6 100644
--- a/ipc/ipc_message_utils.cc
+++ b/ipc/ipc_message_utils.cc
@@ -17,7 +17,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
-#include "base/values.h"
 #include "build/build_config.h"
 #include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_message.h"
@@ -80,125 +79,135 @@
 #endif
 }
 
-bool ReadValue(const base::Pickle* m,
-               base::PickleIterator* iter,
-               base::Value* value,
-               int recursion);
+void WriteValue(const base::Value& value, int recursion, base::Pickle* pickle);
 
-void WriteValue(base::Pickle* m, const base::Value* value, int recursion) {
+void WriteDictValue(const base::Value::Dict& value,
+                    int recursion,
+                    base::Pickle* pickle) {
+  WriteParam(pickle, base::checked_cast<int>(value.size()));
+  for (const auto entry : value) {
+    WriteParam(pickle, entry.first);
+    WriteValue(entry.second, recursion + 1, pickle);
+  }
+}
+
+void WriteListValue(const base::Value::List& value,
+                    int recursion,
+                    base::Pickle* pickle) {
+  WriteParam(pickle, base::checked_cast<int>(value.size()));
+  for (const auto& entry : value) {
+    WriteValue(entry, recursion + 1, pickle);
+  }
+}
+
+void WriteValue(const base::Value& value, int recursion, base::Pickle* pickle) {
   bool result;
   if (recursion > kMaxRecursionDepth) {
     LOG(ERROR) << "Max recursion depth hit in WriteValue.";
     return;
   }
 
-  m->WriteInt(static_cast<int>(value->type()));
+  pickle->WriteInt(static_cast<int>(value.type()));
 
-  switch (value->type()) {
+  switch (value.type()) {
     case base::Value::Type::NONE:
       break;
     case base::Value::Type::BOOLEAN: {
-      WriteParam(m, value->GetBool());
+      WriteParam(pickle, value.GetBool());
       break;
     }
     case base::Value::Type::INTEGER: {
-      DCHECK(value->is_int());
-      WriteParam(m, value->GetInt());
+      DCHECK(value.is_int());
+      WriteParam(pickle, value.GetInt());
       break;
     }
     case base::Value::Type::DOUBLE: {
-      DCHECK(value->is_int() || value->is_double());
-      WriteParam(m, value->GetDouble());
+      DCHECK(value.is_int() || value.is_double());
+      WriteParam(pickle, value.GetDouble());
       break;
     }
     case base::Value::Type::STRING: {
-      const std::string* val = value->GetIfString();
+      const std::string* val = value.GetIfString();
       result = !!val;
       DCHECK(result);
-      WriteParam(m, *val);
+      WriteParam(pickle, *val);
       break;
     }
     case base::Value::Type::BINARY: {
-      m->WriteData(reinterpret_cast<const char*>(value->GetBlob().data()),
-                   base::checked_cast<int>(value->GetBlob().size()));
+      pickle->WriteData(reinterpret_cast<const char*>(value.GetBlob().data()),
+                        base::checked_cast<int>(value.GetBlob().size()));
       break;
     }
-    case base::Value::Type::DICTIONARY: {
-      DCHECK(value->is_dict());
-      WriteParam(m, base::checked_cast<int>(value->DictSize()));
-
-      for (auto it : value->DictItems()) {
-        WriteParam(m, it.first);
-        WriteValue(m, &it.second, recursion + 1);
-      }
+    case base::Value::Type::DICT: {
+      DCHECK(value.is_dict());
+      WriteDictValue(value.GetDict(), recursion, pickle);
       break;
     }
     case base::Value::Type::LIST: {
-      const base::ListValue* list = static_cast<const base::ListValue*>(value);
-      WriteParam(m, base::checked_cast<int>(list->GetListDeprecated().size()));
-      for (const auto& entry : list->GetListDeprecated()) {
-        WriteValue(m, &entry, recursion + 1);
-      }
+      DCHECK(value.is_list());
+      WriteListValue(value.GetList(), recursion, pickle);
       break;
     }
   }
 }
 
-// Helper for ReadValue that reads a DictionaryValue into a pre-allocated
-// object.
-bool ReadDictionaryValue(const base::Pickle* m,
-                         base::PickleIterator* iter,
-                         base::DictionaryValue* value,
-                         int recursion) {
-  int size;
-  if (!ReadParam(m, iter, &size))
-    return false;
-
-  std::vector<base::Value::LegacyDictStorage::value_type> entries;
-  entries.resize(size);
-  for (auto& entry : entries) {
-    entry.second = std::make_unique<base::Value>();
-    if (!ReadParam(m, iter, &entry.first) ||
-        !ReadValue(m, iter, entry.second.get(), recursion + 1))
-      return false;
-  }
-
-  *value =
-      base::DictionaryValue(base::Value::LegacyDictStorage(std::move(entries)));
-  return true;
-}
-
-// Helper for ReadValue that reads a ReadListValue into a pre-allocated
-// object.
-bool ReadListValue(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   base::ListValue* value,
-                   int recursion) {
-  int size;
-  if (!ReadParam(m, iter, &size))
-    return false;
-
-  base::Value::ListStorage list_storage;
-  list_storage.resize(size);
-  for (base::Value& subval : list_storage) {
-    if (!ReadValue(m, iter, &subval, recursion + 1))
-      return false;
-  }
-  *value = base::ListValue(std::move(list_storage));
-  return true;
-}
-
-bool ReadValue(const base::Pickle* m,
+bool ReadValue(const base::Pickle* pickle,
                base::PickleIterator* iter,
-               base::Value* value,
-               int recursion) {
+               int recursion,
+               base::Value* value);
+
+// Helper for ReadValue that reads a Value::Dict into a pre-allocated object.
+bool ReadDictValue(const base::Pickle* pickle,
+                   base::PickleIterator* iter,
+                   int recursion,
+                   base::Value::Dict* value) {
+  int size;
+  if (!ReadParam(pickle, iter, &size))
+    return false;
+
+  for (int i = 0; i < size; i++) {
+    std::string key;
+    base::Value subvalue;
+    if (!ReadParam(pickle, iter, &key) ||
+        !ReadValue(pickle, iter, recursion + 1, &subvalue)) {
+      return false;
+    }
+    value->Set(key, std::move(subvalue));
+  }
+
+  return true;
+}
+
+// Helper for ReadValue that reads a Value::List into a pre-allocated object.
+bool ReadListValue(const base::Pickle* pickle,
+                   base::PickleIterator* iter,
+                   int recursion,
+                   base::Value::List* value) {
+  int size;
+  if (!ReadParam(pickle, iter, &size))
+    return false;
+
+  value->reserve(size);
+  for (int i = 0; i < size; i++) {
+    base::Value subval;
+    if (!ReadValue(pickle, iter, recursion + 1, &subval))
+      return false;
+    value->Append(std::move(subval));
+  }
+  return true;
+}
+
+bool ReadValue(const base::Pickle* pickle,
+               base::PickleIterator* iter,
+               int recursion,
+               base::Value* value) {
   if (recursion > kMaxRecursionDepth) {
     LOG(ERROR) << "Max recursion depth hit in ReadValue.";
     return false;
   }
 
   int type;
-  if (!ReadParam(m, iter, &type))
+  if (!ReadParam(pickle, iter, &type))
     return false;
 
   constexpr int kMinValueType = static_cast<int>(base::Value::Type::NONE);
@@ -212,28 +221,28 @@
       break;
     case base::Value::Type::BOOLEAN: {
       bool val;
-      if (!ReadParam(m, iter, &val))
+      if (!ReadParam(pickle, iter, &val))
         return false;
       *value = base::Value(val);
       break;
     }
     case base::Value::Type::INTEGER: {
       int val;
-      if (!ReadParam(m, iter, &val))
+      if (!ReadParam(pickle, iter, &val))
         return false;
       *value = base::Value(val);
       break;
     }
     case base::Value::Type::DOUBLE: {
       double val;
-      if (!ReadParam(m, iter, &val))
+      if (!ReadParam(pickle, iter, &val))
         return false;
       *value = base::Value(val);
       break;
     }
     case base::Value::Type::STRING: {
       std::string val;
-      if (!ReadParam(m, iter, &val))
+      if (!ReadParam(pickle, iter, &val))
         return false;
       *value = base::Value(std::move(val));
       break;
@@ -246,17 +255,17 @@
       break;
     }
     case base::Value::Type::DICTIONARY: {
-      base::DictionaryValue val;
-      if (!ReadDictionaryValue(m, iter, &val, recursion))
+      base::Value::Dict val;
+      if (!ReadDictValue(pickle, iter, recursion, &val))
         return false;
-      *value = std::move(val);
+      *value = base::Value(std::move(val));
       break;
     }
     case base::Value::Type::LIST: {
-      base::ListValue val;
-      if (!ReadListValue(m, iter, &val, recursion))
+      base::Value::List val;
+      if (!ReadListValue(pickle, iter, recursion, &val))
         return false;
-      *value = std::move(val);
+      *value = base::Value(std::move(val));
       break;
     }
     default:
@@ -510,18 +519,13 @@
 
 void ParamTraits<base::DictionaryValue>::Write(base::Pickle* m,
                                                const param_type& p) {
-  WriteValue(m, &p, 0);
+  WriteDictValue(p.GetDict(), 0, m);
 }
 
 bool ParamTraits<base::DictionaryValue>::Read(const base::Pickle* m,
                                               base::PickleIterator* iter,
                                               param_type* r) {
-  int type;
-  if (!ReadParam(m, iter, &type) ||
-      type != static_cast<int>(base::Value::Type::DICTIONARY))
-    return false;
-
-  return ReadDictionaryValue(m, iter, r, 0);
+  return ReadDictValue(m, iter, 0, &(r->GetDict()));
 }
 
 void ParamTraits<base::DictionaryValue>::Log(const param_type& p,
@@ -531,6 +535,23 @@
   l->append(json);
 }
 
+void ParamTraits<base::Value::Dict>::Write(base::Pickle* m,
+                                           const param_type& p) {
+  WriteDictValue(p, 0, m);
+}
+
+bool ParamTraits<base::Value::Dict>::Read(const base::Pickle* m,
+                                          base::PickleIterator* iter,
+                                          param_type* r) {
+  return ReadDictValue(m, iter, 0, r);
+}
+
+void ParamTraits<base::Value::Dict>::Log(const param_type& p, std::string* l) {
+  std::string json;
+  base::JSONWriter::Write(p, &json);
+  l->append(json);
+}
+
 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 void ParamTraits<base::FileDescriptor>::Write(base::Pickle* m,
                                               const param_type& p) {
@@ -1157,18 +1178,13 @@
 }
 
 void ParamTraits<base::ListValue>::Write(base::Pickle* m, const param_type& p) {
-  WriteValue(m, &p, 0);
+  WriteListValue(p.GetList(), 0, m);
 }
 
 bool ParamTraits<base::ListValue>::Read(const base::Pickle* m,
                                         base::PickleIterator* iter,
                                         param_type* r) {
-  int type;
-  if (!ReadParam(m, iter, &type) ||
-      type != static_cast<int>(base::Value::Type::LIST))
-    return false;
-
-  return ReadListValue(m, iter, r, 0);
+  return ReadListValue(m, iter, 0, &(r->GetList()));
 }
 
 void ParamTraits<base::ListValue>::Log(const param_type& p, std::string* l) {
@@ -1177,14 +1193,31 @@
   l->append(json);
 }
 
+void ParamTraits<base::Value::List>::Write(base::Pickle* m,
+                                           const param_type& p) {
+  WriteListValue(p, 0, m);
+}
+
+bool ParamTraits<base::Value::List>::Read(const base::Pickle* m,
+                                          base::PickleIterator* iter,
+                                          param_type* r) {
+  return ReadListValue(m, iter, 0, r);
+}
+
+void ParamTraits<base::Value::List>::Log(const param_type& p, std::string* l) {
+  std::string json;
+  base::JSONWriter::Write(p, &json);
+  l->append(json);
+}
+
 void ParamTraits<base::Value>::Write(base::Pickle* m, const param_type& p) {
-  WriteValue(m, &p, 0);
+  WriteValue(p, 0, m);
 }
 
 bool ParamTraits<base::Value>::Read(const base::Pickle* m,
                                     base::PickleIterator* iter,
                                     param_type* r) {
-  return ReadValue(m, iter, r, 0);
+  return ReadValue(m, iter, 0, r);
 }
 
 void ParamTraits<base::Value>::Log(const param_type& p, std::string* l) {
diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h
index baafeea..a2746359 100644
--- a/ipc/ipc_message_utils.h
+++ b/ipc/ipc_message_utils.h
@@ -30,6 +30,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/pickle.h"
 #include "base/types/id_type.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "ipc/ipc_buildflags.h"
 #include "ipc/ipc_param_traits.h"
@@ -53,16 +54,13 @@
 #endif
 
 namespace base {
-class DictionaryValue;
 class FilePath;
-class ListValue;
 class Time;
 class TimeDelta;
 class TimeTicks;
 class UnguessableToken;
-class Value;
 struct FileDescriptor;
-}
+}  // namespace base
 
 namespace IPC {
 
@@ -552,6 +550,16 @@
   static void Log(const param_type& p, std::string* l);
 };
 
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::Value::Dict> {
+  typedef base::Value::Dict param_type;
+  static void Write(base::Pickle* m, const param_type& p);
+  static bool Read(const base::Pickle* m,
+                   base::PickleIterator* iter,
+                   param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 // FileDescriptors may be serialised over IPC channels on POSIX. On the
 // receiving side, the FileDescriptor is a valid duplicate of the file
@@ -722,6 +730,16 @@
 };
 
 template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::Value::List> {
+  typedef base::Value::List param_type;
+  static void Write(base::Pickle* m, const param_type& p);
+  static bool Read(const base::Pickle* m,
+                   base::PickleIterator* iter,
+                   param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
 struct COMPONENT_EXPORT(IPC) ParamTraits<base::Value> {
   typedef base::Value param_type;
   static void Write(base::Pickle* m, const param_type& p);
diff --git a/ipc/ipc_message_utils_unittest.cc b/ipc/ipc_message_utils_unittest.cc
index 34e279e..83bf3db 100644
--- a/ipc/ipc_message_utils_unittest.cc
+++ b/ipc/ipc_message_utils_unittest.cc
@@ -233,6 +233,73 @@
   EXPECT_EQ(input, output);
 }
 
+TEST(IPCMessageUtilsTest, LegacyDictValueConversion) {
+  base::DictionaryValue dict_value;
+  dict_value.SetInteger("path1", 42);
+  dict_value.SetInteger("path2", 84);
+  base::ListValue subvalue;
+  subvalue.Append(1234);
+  subvalue.Append(5678);
+  dict_value.SetKey("path3", std::move(subvalue));
+
+  IPC::Message message;
+  ParamTraits<base::DictionaryValue>::Write(&message, dict_value);
+
+  base::PickleIterator iter(message);
+  base::DictionaryValue read_value;
+  ASSERT_TRUE(
+      ParamTraits<base::DictionaryValue>::Read(&message, &iter, &read_value));
+  EXPECT_EQ(dict_value, read_value);
+}
+
+TEST(IPCMessageUtilsTest, DictValueConversion) {
+  base::Value::Dict dict_value;
+  dict_value.Set("path1", 42);
+  dict_value.Set("path2", 84);
+  base::Value::List subvalue;
+  subvalue.Append(1234);
+  subvalue.Append(5678);
+  dict_value.Set("path3", std::move(subvalue));
+
+  IPC::Message message;
+  ParamTraits<base::Value::Dict>::Write(&message, dict_value);
+
+  base::PickleIterator iter(message);
+  base::Value::Dict read_value;
+  ASSERT_TRUE(
+      ParamTraits<base::Value::Dict>::Read(&message, &iter, &read_value));
+  EXPECT_EQ(dict_value, read_value);
+}
+
+TEST(IPCMessageUtilsTest, LegacyListValueConversion) {
+  base::ListValue list_value;
+  list_value.Append(42);
+  list_value.Append(84);
+
+  IPC::Message message;
+  ParamTraits<base::ListValue>::Write(&message, list_value);
+
+  base::PickleIterator iter(message);
+  base::ListValue read_value;
+  ASSERT_TRUE(ParamTraits<base::ListValue>::Read(&message, &iter, &read_value));
+  EXPECT_EQ(list_value, read_value);
+}
+
+TEST(IPCMessageUtilsTest, ListValueConversion) {
+  base::Value::List list_value;
+  list_value.Append(42);
+  list_value.Append(84);
+
+  IPC::Message message;
+  ParamTraits<base::Value::List>::Write(&message, list_value);
+
+  base::PickleIterator iter(message);
+  base::Value::List read_value;
+  ASSERT_TRUE(
+      ParamTraits<base::Value::List>::Read(&message, &iter, &read_value));
+  EXPECT_EQ(list_value, read_value);
+}
+
 #if BUILDFLAG(IS_WIN)
 TEST(IPCMessageUtilsTest, ScopedHandle) {
   HANDLE raw_dupe_handle;