diff --git a/Control/RootUtils/RootUtils/PyROOTTypePatch.h b/Control/RootUtils/RootUtils/PyROOTTypePatch.h new file mode 100644 index 0000000000000000000000000000000000000000..11b76b602c6834f6b9f14cd7d76e408e0b445cfd --- /dev/null +++ b/Control/RootUtils/RootUtils/PyROOTTypePatch.h @@ -0,0 +1,64 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file RootUtils/PyROOTTypePatch.h + * @author scott snyder + * @date Mar 2019 + * @brief Work around pyroot problem with __pair_base. + */ + +#ifndef ROOTUTILS_PYROOTTYPEPATCH_H +#define ROOTUTILS_PYROOTTYPEPATCH_H + + +#include "CxxUtils/checker_macros.h" + + +class TClass; + + +namespace RootUtils { + + +/** + * @brief Work around pyroot problem with __pair_base. + * + * In gcc8, std::pair derives from the (empty) base class std::__pair_base. + * When ROOT makes dictionaries, it likes to strip std:: qualifiers. + * When it then tries to look up cling information for a TClass, + * if the lookup fails, it tries again with the std:: reinserted + * for all STL classes (with TClassEdit::InsertStd()). + * However, that doesn't work for __pair_base, because it's not a class + * that ROOT knows about. The upshot is that we see errors like: + * + * Error in <TClass::LoadClassInfo>: no interpreter information for class __pair_base<unsigned int,string> is available even though it has a TClass initialization routine. + * + * We work around this by hooking into ROOT's TClass creation. + * If we see a std::pair class, we erase the base class information + * from its TClass. + */ +class ATLAS_NOT_THREAD_SAFE PyROOTTypePatch +{ +public: + /** + * @brief Initialize the workaround. + * Scan all known classes, and also hook into class creation + * so that we'll scan all future classes. + */ + static void initialize(); + + + /** + * @brief Scan a single class. + * @param cls The class to scan. + */ + static void scan_one (TClass* cls); +}; + + +} // namespace RootUtils + + +#endif // not ROOTUTILS_PYROOTTYPEPATCH_H diff --git a/Control/RootUtils/RootUtils/RootUtilsPyROOTDict.h b/Control/RootUtils/RootUtils/RootUtilsPyROOTDict.h index 8d2b5d4e0289cfa29d6521a0270a75de9d26f098..7cb56d351511104384a8d4972d6d55dd9503c63b 100644 --- a/Control/RootUtils/RootUtils/RootUtilsPyROOTDict.h +++ b/Control/RootUtils/RootUtils/RootUtilsPyROOTDict.h @@ -1,7 +1,7 @@ // This file's extension implies that it's C, but it's really -*- C++ -*-. /* - Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration */ // $Id: RootUtilsPyROOTDict.h,v 1.5 2008-04-23 19:48:34 ssnyder Exp $ @@ -19,6 +19,8 @@ #include "RootUtils/PyROOTPickle.h" #include "RootUtils/PyROOTTFilePythonize.h" #include "RootUtils/PyROOTInspector.h" +#include "RootUtils/PyROOTTypePatch.h" + // Work around a problem sometimes seen with cling in which `struct timespec' diff --git a/Control/RootUtils/RootUtils/selection_PyROOT.xml b/Control/RootUtils/RootUtils/selection_PyROOT.xml index cf549f822a6ad6d99f210a4a4c0ff58091b304a8..f1aa32cf6b7e205619c99542fc7ae401b90b0fa4 100644 --- a/Control/RootUtils/RootUtils/selection_PyROOT.xml +++ b/Control/RootUtils/RootUtils/selection_PyROOT.xml @@ -13,6 +13,7 @@ <class name="RootUtils::PyLogger" /> <class name="RootUtils::PyBytes" /> <class name="RootUtils::PyROOTInspector" /> + <class name="RootUtils::PyROOTTypePatch" /> <function name="RootUtils::_pythonize_tell_root_file" /> <function name="RootUtils::_pythonize_read_root_file" /> </lcgdict> diff --git a/Control/RootUtils/python/PyROOTFixes.py b/Control/RootUtils/python/PyROOTFixes.py index 57ce198b10a83bb8593e53f06c33a3674c15c124..5582826a8104db059509bf6a9f8bf519fd91af23 100644 --- a/Control/RootUtils/python/PyROOTFixes.py +++ b/Control/RootUtils/python/PyROOTFixes.py @@ -1,22 +1,12 @@ -# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration """Functions to work around PyROOT bugs for accessing DataVector. :author: scott snyder :contact: snyder@bnl.gov -PyROOT (as of root 5.26.00) has a bug that bites us when accessing -DataVector classes. - - - Attempts to use element access via [] on a class deriving from DataVector - will cause an infinite recursion. - -Importing this module will automatically apply this workarounds -for all DataVector classes (and classes deriving from them). - -This module will also add comparison operators for iterator classes T -that have a RootUtils::PyROOTIteratorFuncs<T> specialization -generated. +Importing this module will automatically apply workarounds +for known PyROOT issues. There are also some fixes for performance problems with the pythonization of TTree. These may be enabled by calling enable_tree_speedups(). @@ -29,11 +19,6 @@ import ROOT import cppyy ROOT.TClass.GetClass("TClass") -def fix_dv_container (clname): - # Now only a stub. - return - - def fix_method (clname, methname): # Now only a stub. return @@ -64,38 +49,6 @@ def _getClassIfDictionaryExists (cname): if cl.HasDictionary(): return cl return None -# -# This function will be called for every new pyroot class created. -# -# We use this to fix up iterator comparison operators. -# If the new class appears to be an iterator and the class -# RootUtils::PyROOTIteratorFuncs<CLS> exists, then we install -# the comparison functions from that template class in the new class. -# -def _pyroot_class_hook (cls): - ll = cls.__name__.split ('<') - ll[0] = ll[0].split('.')[-1] - if ll[0].find ('iterator') >= 0: - name = '<'.join (ll) - ifuncsname = 'RootUtils::PyROOTIteratorFuncs<' + name - if ifuncsname[-1] == '>': - ifuncsname = ifuncsname + ' ' - ifuncsname = ifuncsname + '>' - if _getClassIfDictionaryExists (ifuncsname): - ifuncs = getattr (ROOT, ifuncsname, None) - if hasattr (ifuncs, 'eq'): - # Note: To prevent Pythonize.cxx from changing these out from - # under us, __cpp_eq__/__cpp_ne__ must be pyroot MethodProxy's, - # and __eq__/__ne__ must _not_ be MethodProxy's. - cls.__eq__ = lambda a,b: ifuncs.eq(a,b) - cls.__ne__ = lambda a,b: ifuncs.ne(a,b) - cls.__cpp_eq__ = ifuncs.eq - cls.__cpp_ne__ = ifuncs.ne - if hasattr (ifuncs, 'lt'): - cls.__lt__ = lambda a,b: not not ifuncs.lt(a,b) - cls.__gt__ = lambda a,b: not not ifuncs.lt(b,a) - cls.__ge__ = lambda a,b: not ifuncs.lt(a,b) - cls.__le__ = lambda a,b: not ifuncs.lt(b,a) - return - +ROOT.RootUtils.PyLogger +ROOT.RootUtils.PyROOTTypePatch.initialize() diff --git a/Control/RootUtils/src/pyroot/PyROOTTypePatch.cxx b/Control/RootUtils/src/pyroot/PyROOTTypePatch.cxx new file mode 100644 index 0000000000000000000000000000000000000000..de4445ee3440d497413ee3426ddd71c6bd8aba13 --- /dev/null +++ b/Control/RootUtils/src/pyroot/PyROOTTypePatch.cxx @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file RootUtils/src/pyroot/PyROOTTypePatch.cxx + * @author scott snyder + * @date Mar 2019 + * @brief Work around pyroot problem with __pair_base. + */ + +#include "CxxUtils/checker_macros.h" +ATLAS_NO_CHECK_FILE_THREAD_SAFETY; + +#include "RootUtils/PyROOTTypePatch.h" +#include "Rtypes.h" +#include "TGenericClassInfo.h" +#include "TClass.h" +#include "TROOT.h" +#include "TError.h" +#include "TClassTable.h" +#include <ctype.h> +#include <cassert> + + +using ROOT::Internal::TInitBehavior; +using ROOT::Internal::TDefaultInitBehavior; +using ROOT::Internal::DefineBehavior; + + +//**************************************************************************** +// This is the class creation hook. +// + + +namespace { + + +/** + * @brief Class creation hook. + */ +class TypePatchInitBehavior + : public TDefaultInitBehavior +{ +public: + virtual TClass *CreateClass(const char *cname, Version_t id, + const std::type_info &info, TVirtualIsAProxy *isa, + const char *dfil, const char *ifil, + Int_t dl, Int_t il) const; + + static TDefaultInitBehavior oldBehavior; + static TDefaultInitBehavior* oldBehaviorPtr; +}; +TDefaultInitBehavior TypePatchInitBehavior::oldBehavior; +TDefaultInitBehavior* TypePatchInitBehavior::oldBehaviorPtr = 0; + + +/** + * @brief Class creation hook. + * We arrange for this to be called by root when a new class is created. + */ +TClass* TypePatchInitBehavior::CreateClass(const char *cname, + Version_t id, + const std::type_info &info, + TVirtualIsAProxy *isa, + const char *dfil, + const char *ifil, + Int_t dl, + Int_t il) const +{ + // Do the default root behavior. + TClass* cl = oldBehaviorPtr->CreateClass(cname, id, info, isa, + dfil, ifil, dl, il); + + // Scan the new class. + RootUtils::PyROOTTypePatch::scan_one (cl); + + // Return it. + return cl; +} + + +} // anonymous namespace + + +//**************************************************************************** +// Local helper functions. +// + + +namespace { + + +/** + * @brief Test to see if a class name is that of a std::pair. + * @param clsname The name to test. + */ +bool name_is_pair (const char* clsname) +{ + return (strncmp (clsname, "pair<", 5) == 0 || + strncmp (clsname, "std::pair<", 10) == 0); +} + + +/** + * @brief Scan all known classes, and fix up any pairs. + */ +void scan_for_pair() +{ + TClassTable::Init(); + while (const char* clsname = TClassTable::Next()) { + if (name_is_pair (clsname)) { + TClass* cls = gROOT->GetClass (clsname, kFALSE); + if (cls) + cls->GetListOfBases()->Clear(); + } + } +} + + +} // anonymous namespace + + +namespace RootUtils { + + +/** + * @brief Initialize the workaround. + * Scan all known classes, and also hook into class creation + * so that we'll scan all future classes. + */ +void PyROOTTypePatch::initialize() +{ + // Return if we've already initialized. + static bool initialized = false; + if (initialized) return; + initialized = true; + + // Scan all classes. + scan_for_pair(); + + // Register ourselves as a hook, + // to be called in the future on class creation. + TypePatchInitBehavior::oldBehaviorPtr = &TypePatchInitBehavior::oldBehavior; + TInitBehavior* ib = + const_cast<TInitBehavior*>(DefineBehavior(0,0)); + assert (sizeof(TDefaultInitBehavior) == sizeof (TypePatchInitBehavior)); + memcpy ((char*)&TypePatchInitBehavior::oldBehavior, (char*)ib, + sizeof(TDefaultInitBehavior)); + static TypePatchInitBehavior sfaib; + memcpy ((char*)ib, (char*)&sfaib, sizeof(TDefaultInitBehavior)); + + // Make sure the TClass's for these are built. + // Otherwise, CreateClass can get called while global dtors are running. + gROOT->GetClass("TObjString"); + gROOT->GetClass("TTreeCache"); +} + + +/** + * @brief Scan a single class. + * @param cls The class to scan. + */ +void PyROOTTypePatch::scan_one (TClass* cls) +{ + if (name_is_pair (cls->GetName())) { + cls->GetListOfBases()->Clear(); + } +} + + +} // namespace RootUtils