Revision c773da9f5b81c15a00fec29ca0499e58441671de authored by Rene Brun on 09 October 2006, 06:31:09 UTC, committed by Rene Brun on 09 October 2006, 06:31:09 UTC
ixes in GetStats() for taking into account underflow/overflow when TH1::StatOverflows is set and the modifications in TH2::ProjectionX and TH2::ProjectionY to use TH1::SetBinContent
instead of TH1::Fill in oder to have correct statistics in the projected histogram in case of weights. This fixes the bug 19628.
The number of entries in the projected histogram is set now to the number of effective entries.


git-svn-id: http://root.cern.ch/svn/root/trunk@16477 27541ba8-7e3a-0410-8455-c3a389f83636
1 parent 73d8d11
Raw File
MethodHolder.cxx
// @(#)root/pyroot:$Name:  $:$Id: MethodHolder.cxx,v 1.49 2006/07/01 21:19:55 brun Exp $
// Author: Wim Lavrijsen, Apr 2004

// Bindings
#include "PyROOT.h"
#include "MethodHolder.h"
#include "Converters.h"
#include "Executors.h"
#include "ObjectProxy.h"
#include "RootWrapper.h"
#include "TPyException.h"
#include "Utility.h"

// ROOT
#include "TROOT.h"
#include "TClass.h"
#include "TString.h"
#include "TFunction.h"
#include "TMethodArg.h"
#include "TClassEdit.h"
#include "TVirtualMutex.h"
#include "TException.h"

// CINT
#include "Api.h"
#include "TCint.h"

// Standard
#include <assert.h>
#include <string.h>
#include <exception>
#include <string>


//- data and local helpers ---------------------------------------------------
namespace {

// CINT temp level guard
   struct TempLevelGuard_t {
      TempLevelGuard_t() { G__settemplevel( 1 ); }
      ~TempLevelGuard_t() { G__settemplevel( -1 ); }
   };

   G__ClassInfo* GetGlobalNamespaceInfo() {
      static G__ClassInfo gcl;
      return &gcl;
   }

} // unnamed namespace


//- private helpers ----------------------------------------------------------
inline void PyROOT::TMethodHolder::Copy_( const TMethodHolder& other )
{
// yes, these pointer copy semantics are proper
   fClass  = other.fClass;
   fMethod = other.fMethod;

// do not copy caches
   fMethodCall = 0;
   fExecutor   = 0;

   fArgsRequired = -1;
   fOffset       =  0;

// being uninitialized will trigger setting up caches as appropriate
   fIsInitialized  = kFALSE;
}

//____________________________________________________________________________
inline void PyROOT::TMethodHolder::Destroy_() const
{
// no deletion of fMethod (ROOT responsibility)
   delete fMethodCall;

// destroy executor and argument converters
   delete fExecutor;

   for ( int i = 0; i < (int)fConverters.size(); ++i )
      delete fConverters[ i ];
}

//____________________________________________________________________________
inline PyObject* PyROOT::TMethodHolder::CallFast( void* self )
{
// helper code to prevent some duplication; this is called from CallSafe() as well
// as directly from TMethodHolder::Execute in fast mode

   PyObject* result = 0;

   try {       // C++ try block
      result = fExecutor->Execute( fMethodCall, (void*)((Long_t)self + fOffset) );
   } catch ( TPyException& ) {
      result = (PyObject*)TPyExceptionMagic;
   } catch ( std::exception& e ) {
      PyErr_Format( PyExc_Exception, "%s (C++ exception)", e.what() );
      result = 0;
   } catch ( ... ) {
      PyErr_SetString( PyExc_Exception, "unhandled, unknown C++ exception" );
      result = 0;
   }

   return result;
}

//____________________________________________________________________________
inline PyObject* PyROOT::TMethodHolder::CallSafe( void* self )
{
// helper code to prevent some code duplication; this code embeds a ROOT "try/catch"
// block that saves the stack for restoration in case of an otherwise fatal signal

   PyObject* result = 0;

   TRY {       // ROOT "try block"
      result = CallFast( self );
   } CATCH( excode ) {
      PyErr_SetString( PyExc_SystemError, "problem in C++; program state has been reset" );
      result = 0;
      Throw( excode );
   } ENDTRY;

   return result;
}

//____________________________________________________________________________
Bool_t PyROOT::TMethodHolder::InitCallFunc_( std::string& callString )
{
// buffers for argument dispatching
   const int nArgs = fMethod ? fMethod->GetNargs() : 0;
   if ( nArgs == 0 )
      return kTRUE;

   fConverters.resize( nArgs );    // id.

// setup the dispatch cache
   int iarg = 0;
   TIter nextarg( fMethod->GetListOfMethodArgs() );
   while ( TMethodArg* arg = (TMethodArg*)nextarg() ) {
      std::string fullType = arg->GetFullTypeName();
      fConverters[ iarg ] = CreateConverter( fullType );

      if ( ! fConverters[ iarg ] ) {
         PyErr_Format( PyExc_TypeError, "argument type %s not handled", fullType.c_str() );
         return kFALSE;
      }

   // setup call string
      if ( callString.length() == 0 )
         callString = fullType;
      else
         callString += "," + fullType;

   // advance argument counter
      iarg += 1;
   }

   return kTRUE;
}

//____________________________________________________________________________
Bool_t PyROOT::TMethodHolder::InitExecutor_( TExecutor*& executor )
{
// install executor conform to the return type
   executor = CreateExecutor( fMethod ? fMethod->GetReturnTypeName() : fClass->GetName() );
   if ( ! executor )
      return kFALSE;

   return kTRUE;
}

//____________________________________________________________________________
void PyROOT::TMethodHolder::SetPyError_( PyObject* msg )
{
// helper to report errors in a consistent format (derefs msg)
   PyObject *etype, *evalue, *etrace;
   PyErr_Fetch( &etype, &evalue, &etrace );

   std::string details = "";
   if ( evalue ) {
      PyObject* s = PyObject_Str( evalue );
      details = PyString_AS_STRING( s );
      Py_DECREF( s );
   }

   Py_XDECREF( etype ); Py_XDECREF( evalue ); Py_XDECREF( etrace );

   PyObject* doc = GetDocString();

   if ( details != "" ) {
      PyErr_Format( PyExc_TypeError, "%s =>\n    %s (%s)",
          PyString_AS_STRING( doc ), PyString_AS_STRING( msg ), details.c_str() );
   } else {
      PyErr_Format( PyExc_TypeError, "%s =>\n    %s",
          PyString_AS_STRING( doc ), PyString_AS_STRING( msg ) );
   }

   Py_DECREF( doc );
   Py_DECREF( msg );
}

//- constructors and destructor ----------------------------------------------
PyROOT::TMethodHolder::TMethodHolder( TClass* klass, TFunction* method ) :
      fClass( klass ), fMethod( method )
{
// constructor; initialization is deferred
   fMethodCall    =  0;
   fExecutor      =  0;
   fArgsRequired  = -1;
   fOffset        =  0;

   fIsInitialized = kFALSE;
}

//____________________________________________________________________________
PyROOT::TMethodHolder::TMethodHolder( const TMethodHolder& other ) : PyCallable( other )
{
// copy constructor
   Copy_( other );
}

//____________________________________________________________________________
PyROOT::TMethodHolder& PyROOT::TMethodHolder::operator=( const TMethodHolder& other )
{
// assignment operator
   if ( this != &other ) {
      Destroy_();
      Copy_( other );
   }

   return *this;
}

//____________________________________________________________________________
PyROOT::TMethodHolder::~TMethodHolder()
{
// destructor
   Destroy_();
}


//- public members -----------------------------------------------------------
PyObject* PyROOT::TMethodHolder::GetSignature()
{
// construct python string from the method's signature
   return PyString_FromFormat( "%s", fMethod->GetSignature() );
}

//____________________________________________________________________________
PyObject* PyROOT::TMethodHolder::GetPrototype()
{
// construct python string from the method's prototype
   return PyString_FromFormat( "%s%s",
      ( fMethod->Property() & G__BIT_ISSTATIC ) ? "static " : "", fMethod->GetPrototype() );
}

//____________________________________________________________________________
Int_t PyROOT::TMethodHolder::GetPriority()
{
// Method priorities exist (in lieu of true overloading) there to prevent
// void* or <unknown>* from usurping otherwise valid calls. TODO: extend this
// to favour classes that are not bases.

   Int_t priority = 0;

   TIter nextarg( fMethod->GetListOfMethodArgs() );
   while ( TMethodArg* arg = (TMethodArg*)nextarg() ) {
      G__TypeInfo ti( arg->GetFullTypeName() );

   // the following numbers are made up and may cause problems in specific
   // situations: use <obj>.<meth>.disp() for choice of exact dispatch
      if ( ! ti.IsValid() )
         priority -= 10000;   // class is gibberish
      else if ( (ti.Property() & (kIsClass|kIsStruct)) && ! ti.IsLoaded() )
         priority -= 1000;    // class is known, but no dictionary available
      else if ( TClassEdit::CleanType( ti.TrueName(), 1 ) == "void*" )
         priority -= 100;     // void* shouldn't be too greedy
      else if ( TClassEdit::CleanType( ti.TrueName(), 1 ) == "float" )
         priority -= 30;      // double preferred over float (no float in python)
      else if ( TClassEdit::CleanType( ti.TrueName(), 1 ) == "double" )
         priority -= 10;      // char, int, long preferred over double
   }

   return priority;
}

//____________________________________________________________________________
Bool_t PyROOT::TMethodHolder::Initialize()
{
// done if cache is already setup
   if ( fIsInitialized == kTRUE )
      return kTRUE;

   std::string callString = "";
   if ( ! InitCallFunc_( callString ) )
      return kFALSE;

   if ( ! InitExecutor_( fExecutor ) )
      return kFALSE;

// setup call func
   assert( fMethodCall == 0 );

   fMethodCall = new G__CallFunc();
   fMethodCall->Init();

   G__ClassInfo* gcl = fClass->GetClassInfo();
   if ( ! gcl )
      gcl = GetGlobalNamespaceInfo();
   
   fMethodCall->SetFunc( gcl->GetMethod(
      fMethod ? fMethod->GetName() : fClass->GetName(), callString.c_str(), &fOffset ) );

// minimum number of arguments when calling
   fArgsRequired = fMethod ? fMethod->GetNargs() - fMethod->GetNargsOpt() : 0;

// init done
   fIsInitialized = kTRUE;

   return kTRUE;
}

//____________________________________________________________________________
PyObject* PyROOT::TMethodHolder::FilterArgs( ObjectProxy*& self, PyObject* args, PyObject* )
{
// verify existence of self, return if ok
   if ( self != 0 ) {
      Py_INCREF( args );
      return args;
   }

// otherwise, check for a suitable 'self' in args and update accordingly
   if ( PyTuple_GET_SIZE( args ) != 0 ) {
      ObjectProxy* pyobj = (ObjectProxy*)PyTuple_GET_ITEM( args, 0 );

   // demand PyROOT object, and an argument that may match down the road
      if ( ObjectProxy_Check( pyobj ) &&
           ( strlen( fClass->GetName() ) == 0 ||            // free global
           ( pyobj->ObjectIsA() == 0 ) ||                   // null pointer or ctor call
           ( pyobj->ObjectIsA()->GetBaseClass( fClass ) ) ) // matching types
         ) {
      // reset self (will live for the life time of args; i.e. call of function)
         self = pyobj;

      // offset args by 1 (new ref)
         return PyTuple_GetSlice( args, 1, PyTuple_GET_SIZE( args ) );
      }
   }

// no self, set error and lament
   SetPyError_( PyString_FromFormat(
      "unbound method %s::%s must be called with a %s instance as first argument",
      fClass->GetName(), fMethod->GetName(), fClass->GetName() ) );
   return 0;
}

//____________________________________________________________________________
Bool_t PyROOT::TMethodHolder::SetMethodArgs( PyObject* args )
{
// clean slate
   fMethodCall->ResetArg();

   int argc = PyTuple_GET_SIZE( args );
   int argMax = fConverters.size();

// argc must be between min and max number of arguments
   if ( argc < fArgsRequired ) {
      SetPyError_( PyString_FromFormat(
         "takes at least %d arguments (%d given)", fArgsRequired, argc ) );
      return kFALSE;
   } else if ( argMax < argc ) {
      SetPyError_( PyString_FromFormat(
         "takes at most %d arguments (%d given)", argMax, argc ) );
      return kFALSE;
   }

// convert the arguments to the method call array
   for ( int i = 0; i < argc; i++ ) {
      if ( ! fConverters[ i ]->SetArg( PyTuple_GET_ITEM( args, i ), fMethodCall ) ) {
         SetPyError_( PyString_FromFormat( "could not convert argument %d", i+1 ) );
         return kFALSE;
      }
   }

   return kTRUE;
}

//____________________________________________________________________________
PyObject* PyROOT::TMethodHolder::Execute( void* self )
{
// call the interface method
   R__LOCKGUARD2( gCINTMutex );
   TempLevelGuard_t g;

   PyObject* result = 0;

#ifndef R__WIN32              // G__return isn't for API use on Windows
// TODO: get this into an API to cleanup/init on fresh call
   G__return = G__RETURN_NON;
#endif

   if ( Utility::gSignalPolicy == Utility::kFast ) {
   // bypasses ROOT try block (i.e. segfaults will abort)
      result = CallFast( self );
   } else {
   // at the cost of ~10% performance, don't abort the interpreter on any signal
      result = CallSafe( self );
   }

   if ( result && PyErr_Occurred() ) {
   // can happen in the case of a CINT error: trigger exception processing
      Py_DECREF( result );
      result = 0;
   }

   return result;
}

//____________________________________________________________________________
PyObject* PyROOT::TMethodHolder::operator()( ObjectProxy* self, PyObject* args, PyObject* kwds )
{
// setup as necessary
   if ( ! Initialize() )
      return 0;                              // important: 0, not Py_None

// fetch self, verify, and put the arguments in usable order
   if ( ! ( args = FilterArgs( self, args, kwds ) ) )
      return 0;

// translate the arguments
   Bool_t bConvertOk = SetMethodArgs( args );
   Py_DECREF( args );

   if ( bConvertOk == kFALSE )
      return 0;                              // important: 0, not Py_None

// get the ROOT object that this object proxy is a handle for
   void* object = self->GetObject();

// validity check that should not fail
   if ( ! object ) {
      PyErr_SetString( PyExc_ReferenceError, "attempt to access a null-pointer" );
      return 0;
   }

// get its class
   TClass* klass = self->ObjectIsA();

// reset this method's offset for the object as appropriate
   int objTag  = klass->GetClassInfo()  ? klass->GetClassInfo()->Tagnum()  : -1;   // derived
   int methTag = fClass->GetClassInfo() ? fClass->GetClassInfo()->Tagnum() : -1;   // base
   fOffset = objTag == methTag ? 0 : G__isanybase( methTag, objTag, (Long_t)object );

// actual call; recycle self instead of new object for same address objects
   ObjectProxy* pyobj = (ObjectProxy*)Execute( object );
   if ( ObjectProxy_Check( pyobj ) &&
        pyobj->GetObject() == object && pyobj->ObjectIsA() == klass ) {
      Py_INCREF( (PyObject*)self );
      Py_DECREF( pyobj );
      return (PyObject*)self;
   }

   return (PyObject*)pyobj;
}
back to top