From a9018eb55ed7a8c692f0173192c39acbbe246122 Mon Sep 17 00:00:00 2001
From: Danilo Piparo <danilo.piparo@cern.ch>
Date: Wed, 23 Mar 2016 16:27:06 +0100
Subject: [PATCH] Implement IO for unique_ptr.

this commit implements the functionality necessary to perform I/O operations,
both column- and row-wise with std::unique_ptr<T> instances as if they were
simple, good old pointers.
Only unique_ptrs with a default second argument can be treated.
The implementation assumes that the layout in memory of a unique_ptr<T> is the
same of the one of a T*.

Some details:
- TStreamer infor construction, both starting from the type system and from files
(i.e. TStreamerInfo Build and BuildOld methods) have been adapted to accommodate
the I/O of unique_ptrs.
- Dictionary generation stops if a persistent data member is a unique_ptr with a
non default delete argument.
---
 core/metautils/inc/TClassEdit.h    |  9 ++++
 core/utils/src/rootclingTCling.cxx | 32 ++++++++++++
 io/io/src/TStreamerInfo.cxx        | 99 +++++++++++++++++++++++++++++++++-----
 3 files changed, 127 insertions(+), 13 deletions(-)

diff --git a/core/metautils/inc/TClassEdit.h b/core/metautils/inc/TClassEdit.h
index 02721b0..467e621 100644
--- a/core/metautils/inc/TClassEdit.h
+++ b/core/metautils/inc/TClassEdit.h
@@ -175,6 +175,15 @@ namespace TClassEdit {
    std::string ShortType (const char *typeDesc, int mode);
    std::string InsertStd(const char *tname);
    const char* GetUnqualifiedName(const char*name);
+   inline bool IsUniquePtr(std::string_view name) {return 0 == name.find("unique_ptr<");};
+   inline std::string GetUniquePtrType(std::string_view name)
+   {
+      // Find the first template parameter
+      std::vector<std::string> v;
+      int i;
+      GetSplit(name.data(), v, i);
+      return v[1];
+   };
    inline char* DemangleName(const char* mangled_name, int& errorCode)
    {
    // Demangle in a portable way the name.
diff --git a/core/utils/src/rootclingTCling.cxx b/core/utils/src/rootclingTCling.cxx
index fc47b9b..88f7b24 100644
--- a/core/utils/src/rootclingTCling.cxx
+++ b/core/utils/src/rootclingTCling.cxx
@@ -21,6 +21,7 @@
 #include "TProtoClass.h"
 #include "TROOT.h"
 #include "TStreamerInfo.h"
+#include "TClassEdit.h"
 #include <iostream>
 #include <unordered_set>
 
@@ -106,6 +107,37 @@ bool CloseStreamerInfoROOTFile(bool writeEmptyRootPCM)
                    << normName << '\n';
          return false;
       }
+
+      // Check if a datamember is a unique_ptr and if yes that it has a default
+      // deleter.
+      auto dms = cl->GetListOfDataMembers();
+      if (!dms) {
+         std::cerr << "ERROR in CloseStreamerInfoROOTFile(): cannot find data members for  "
+         << normName << '\n';
+         return false;
+      }
+
+      for (auto dmObj : *dms) {
+         auto dm = (TDataMember*) dmObj;
+         if (!dm->IsPersistent()) continue;
+         auto dmTypeName = dm->GetTypeName();
+         if (TClassEdit::IsUniquePtr(dmTypeName)) {
+            std::vector<std::string> out;
+            int i;
+            TClassEdit::GetSplit(dmTypeName, out, i);
+            std::string_view deleterTypeName(out[2].c_str());
+            // We check if the unique_ptr has a default deleter
+            if (0 != deleterTypeName.find("default_delete<")) {
+               std::cerr << "ERROR in CloseStreamerInfoROOTFile(): I/O is supported only for unique_ptrs with a default deleter. "
+                         << normName << "::" << dm->GetName() << " appears to have a custom one, "
+                         << deleterTypeName.data() << ".\n";
+               return false;
+            }
+         }
+
+      }
+
+
       // Never store a proto class for a class which rootcling already has
       // an 'official' TClass (i.e. the dictionary is in libCore or libRIO).
       if (cl->IsLoaded()) continue;
diff --git a/io/io/src/TStreamerInfo.cxx b/io/io/src/TStreamerInfo.cxx
index 4b96264..189b8d3 100644
--- a/io/io/src/TStreamerInfo.cxx
+++ b/io/io/src/TStreamerInfo.cxx
@@ -18,7 +18,7 @@ Describes a persistent version of a class.
 A ROOT file contains the list of TStreamerInfo objects for all the
 class versions written to this file.
 When reading a file, all the TStreamerInfo objects are read back in
-memory and registered to the TClass list of TStreamerInfo.                                                             
+memory and registered to the TClass list of TStreamerInfo.
 One can see the list and contents of the TStreamerInfo on a file
 with, e.g.,
 ~~~{.cpp}
@@ -128,6 +128,13 @@ static void R__TObjArray_InsertBefore(TObjArray *arr, TObject *newobj, TObject *
    R__TObjArray_InsertAt(arr, newobj, at);
 }
 
+enum class EUniquePtrOffset : char
+   {
+      kNA = 0,
+      kZero = 1,
+      kNonZero = 2
+   };
+
 ////////////////////////////////////////////////////////////////////////////////
 /// Default ctor.
 
@@ -366,6 +373,8 @@ void TStreamerInfo::Build()
 
    Int_t dsize;
    TDataMember* dm = 0;
+   std::string uniquePtrTypeNameBuf;
+   std::string uniquePtrTrueTypeNameBuf;
    TIter nextd(fClass->GetListOfDataMembers());
    while ((dm = (TDataMember*) nextd())) {
       if (fClass->GetClassVersion() == 0) {
@@ -381,11 +390,61 @@ void TStreamerInfo::Build()
       }
       TStreamerElement* element = 0;
       dsize = 0;
-      const char* dmName = dm->GetName();
+
+      // Let's treat the unique_ptr case
+      const char* dmName  = dm->GetName();
       const char* dmTitle = dm->GetTitle();
-      const char* dmType = dm->GetTypeName();
-      const char* dmFull = dm->GetTrueTypeName(); // Used to be GetFullTypeName ...
+      const char* dmType  = dm->GetTypeName();
+      const char* dmFull  = dm->GetTrueTypeName(); // Used to be GetFullTypeName ...
       Bool_t dmIsPtr = dm->IsaPointer();
+
+      static EUniquePtrOffset uniquePtrOffset = EUniquePtrOffset::kNA;
+      bool isUniquePtr = TClassEdit::IsUniquePtr(dmType);
+      if (isUniquePtr) {
+         // Now check if the implementation of the unique_ptr allows to stream it
+         // as a normal pointer
+         uniquePtrTypeNameBuf = TClassEdit::GetUniquePtrType(dmType);
+         dmType = uniquePtrTypeNameBuf.c_str();
+         uniquePtrTrueTypeNameBuf = uniquePtrTypeNameBuf + "*";
+         dmFull = uniquePtrTrueTypeNameBuf.c_str();
+         dmIsPtr = true;
+         dmTitle = "->";
+         if (uniquePtrOffset != EUniquePtrOffset::kZero) {
+
+            if (uniquePtrOffset == EUniquePtrOffset::kNA) {
+               auto clm = TClass::GetClass(dmType);
+               if (!clm) {
+                  Error("Build", "%s, unique_ptr templated with an unknown class: %s %s\n", GetName(), dmFull, dmName);
+                  continue;
+               }
+               auto upDms = clm->GetListOfDataMembers();
+               if (!upDms) {
+                  Error("Build", "%s, cannot determine unique_ptr data members: %s %s\n", GetName(), dmFull, dmName);
+                  continue;
+               }
+               auto uniquePtrFirstDm = (TDataMember *) upDms->At(0);
+               if (!uniquePtrFirstDm) {
+                  Error("Build", "%s, unique_ptr has zero data members: %s %s\n", GetName(), dmFull, dmName);
+                  continue;
+               }
+               if (0 != uniquePtrFirstDm->GetOffset()) {
+                  Error("Build", "%s, unique_ptr and pointee have a different offset: %s %s\n", GetName(), dmFull, dmName);
+                  continue;
+               }
+               if (clm != uniquePtrFirstDm->GetClass()) {
+                  Error("Build", "%s, unique_ptr first template parameter and first data member have a different type (%s and %s respectively): %s %s\n",
+                     GetName(), clm->GetName(),  uniquePtrFirstDm->GetClass()->GetName(), dmFull, dmName);
+                  continue;
+               }
+
+               uniquePtrOffset = EUniquePtrOffset::kZero;
+            } else {
+               Error("Build", "%s, unique_ptr and pointee have a different offset: %s %s\n", GetName(), dmFull, dmName);
+               continue;
+            }
+         }
+      }
+
       TDataMember* dmCounter = 0;
       if (dmIsPtr) {
          //
@@ -1693,6 +1752,8 @@ void TStreamerInfo::BuildOld()
          continue;
       };
 
+      //printf ("[%s] Dealing with element %s of type %s\n", fClass->GetName(), element->GetName(), element->GetTypeName());
+
       element->SetNewType(element->GetType());
       if (element->IsBase()) {
          //---------------------------------------------------------------------
@@ -1990,9 +2051,20 @@ void TStreamerInfo::BuildOld()
       Int_t newType = -1;
       TClassRef newClass;
 
+      std::string uniquePtrTypeNameBuf;
+      const char* dmType = dm->GetTypeName();
+      Bool_t dmIsPtr = dm->IsaPointer();
+
+      bool isUniquePtr = TClassEdit::IsUniquePtr(dmType);
+      if (isUniquePtr) {
+         dmIsPtr = true;
+         uniquePtrTypeNameBuf = TClassEdit::GetUniquePtrType(dmType);
+         dmType = uniquePtrTypeNameBuf.c_str();
+      }
+
+
       if (dm && dm->IsPersistent()) {
          if (dm->GetDataType()) {
-            Bool_t isPointer = dm->IsaPointer();
             Bool_t isArray = element->GetArrayLength() >= 1;
             Bool_t hasCount = element->HasCounter();
             // data member is a basic type
@@ -2003,16 +2075,16 @@ void TStreamerInfo::BuildOld()
                // All the values of EDataType have the same semantic in EReadWrite
                newType = (EReadWrite)dm->GetDataType()->GetType();
             }
-            if ((newType == ::kChar_t) && isPointer && !isArray && !hasCount) {
+            if ((newType == ::kChar_t) && dmIsPtr && !isArray && !hasCount) {
                newType = ::kCharStar;
-            } else if (isPointer) {
+            } else if (dmIsPtr) {
                newType += kOffsetP;
             } else if (isArray) {
                newType += kOffsetL;
             }
          }
          if (newType == -1) {
-            newClass = TClass::GetClass(dm->GetTypeName());
+            newClass = TClass::GetClass(dmType);
          }
       } else {
          // Either the class is not loaded or the data member is gone
@@ -2060,7 +2132,7 @@ void TStreamerInfo::BuildOld()
          newClass.Reset();
          TClass* oldClass = TClass::GetClass(TClassEdit::ShortType(element->GetTypeName(), TClassEdit::kDropTrailStar).c_str());
          if (oldClass == newClass.GetClass()) {
-            // Nothing to do :)
+            // Nothing to do, also in the unique_ptr case :)
          } else if (ClassWasMovedToNamespace(oldClass, newClass.GetClass())) {
             Int_t oldv;
             if (0 != (oldv = ImportStreamerInfo(oldClass, newClass.GetClass()))) {
@@ -2178,8 +2250,8 @@ void TStreamerInfo::BuildOld()
          Bool_t cannotConvert = kFALSE;
          if (element->GetNewType() != -2) {
             if (dm) {
-               if (dm->IsaPointer()) {
-                  if (strncmp(dm->GetTitle(),"->",2)==0) {
+               if (dmIsPtr) {
+                  if (isUniquePtr || strncmp(dm->GetTitle(),"->",2)==0) {
                      // We are fine, nothing to do.
                      if (newClass->IsTObject()) {
                         newType = kObjectp;
@@ -2212,7 +2284,7 @@ void TStreamerInfo::BuildOld()
                      newType = kAny;
                   }
                }
-               if ((!dm->IsaPointer() || newType==kSTLp) && dm->GetArrayDim() > 0) {
+               if ((!dmIsPtr || newType==kSTLp) && dm->GetArrayDim() > 0) {
                   newType += kOffsetL;
                }
             } else if (!fClass->IsLoaded()) {
@@ -2263,6 +2335,7 @@ void TStreamerInfo::BuildOld()
                   }
                } else {
                   // We have no clue
+                  printf("%s We have no clue\n", dm->GetName());
                   cannotConvert = kTRUE;
                }
             } else if (element->GetType() == kObjectP || element->GetType() == kAnyP) {
@@ -3923,7 +3996,7 @@ Int_t TStreamerInfo::GetSizeElements() const
 ////////////////////////////////////////////////////////////////////////////////
 /// Return the StreamerElement of "datamember" inside our
 /// class or any of its base classes.
-/// 
+///
 /// The offset information
 /// contained in the StreamerElement is related to its immediately
 /// containing class, so we return in 'offset' the offset inside
-- 
1.9.1

