Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ Cppyy::TCppType_t CPyCppyy::CPPInstance::GetSmartIsA() const
return SMART_TYPE(this);
}

//----------------------------------------------------------------------------
Cppyy::TCppType_t CPyCppyy::CPPInstance::GetSmartUnderlyingType() const
{
// The declared underlying type of the embedded smart pointer (e.g. 'Base' for
// a std::unique_ptr<Base>). This is independent of any auto-down-cast applied
// to the dereferenced object, and so is what must be used to decide whether the
// smart pointer can be passed to a function expecting a particular smart type.
if (!IsSmart()) return (Cppyy::TCppType_t)0;
return SMART_CLS(this)->fUnderlyingType;
}

//----------------------------------------------------------------------------
CPyCppyy::CI_DatamemberCache_t& CPyCppyy::CPPInstance::GetDatamemberCache()
{
Expand Down
1 change: 1 addition & 0 deletions bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class CPPInstance {
void SetSmart(PyObject* smart_type);
void* GetSmartObject() { return GetObjectRaw(); }
Cppyy::TCppType_t GetSmartIsA() const;
Cppyy::TCppType_t GetSmartUnderlyingType() const;

// cross-inheritance dispatch
void SetDispatchPtr(void*);
Expand Down
7 changes: 6 additions & 1 deletion bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3066,7 +3066,12 @@ bool CPyCppyy::SmartPtrConverter::SetArg(
}

// final option, try mapping pointer types held (TODO: do not allow for non-const ref)
if (pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) {
// Note: this must be decided on the smart pointer's *declared* underlying type, not
// on the (possibly auto-down-cast) type of the dereferenced object. A
// std::unique_ptr<Base> holding a Derived must not be accepted where a
// std::unique_ptr<Derived> is expected: the held smart pointer is still a
// unique_ptr<Base> and does not convert to unique_ptr<Derived>.
if (pyobj->IsSmart() && Cppyy::IsSubtype(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) {
para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject();
para.fTypeCode = 'V';
return true;
Expand Down
26 changes: 25 additions & 1 deletion bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,31 @@ PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address,
PyObject* smart_type = (!(flags & CPPInstance::kNoWrapConv) && \
(((CPPClass*)pyclass)->fFlags & CPPScope::kIsSmart)) ? pyclass : nullptr;
if (smart_type) {
pyclass = CreateScopeProxy(((CPPSmartClass*)smart_type)->fUnderlyingType);
Cppyy::TCppType_t underlying = ((CPPSmartClass*)smart_type)->fUnderlyingType;

// Down-cast the underlying object to its actual (most derived) class, just
// as BindCppObject does for raw pointers. Two conditions must hold:
// * the reported actual class must really be a subtype of the declared one.
// Cppyy::GetActualClass() can return a *base* class (e.g. when the actual
// class has no dictionary of its own and inherits IsA() from a base with
// ClassDef, ROOT reports that base) -- such an up-cast must be rejected.
// * the cast must require no pointer adjustment, because the smart pointer's
// dereferencer always yields a pointer to the underlying (declared) class,
// so a non-zero offset can not be applied consistently on later member
// access (e.g. with multiple inheritance).
if (address && !isRef) {
void* deref = Cppyy::CallR(
((CPPSmartClass*)smart_type)->fDereferencer, address, 0, nullptr);
if (deref) {
Cppyy::TCppType_t clActual = Cppyy::GetActualClass(underlying, deref);
if (clActual && clActual != underlying &&
Cppyy::IsSubtype(clActual, underlying) &&
Cppyy::GetBaseOffset(clActual, underlying, deref, -1 /* down-cast */) == 0)
underlying = clActual;
}
}

pyclass = CreateScopeProxy(underlying);
if (!pyclass) {
// simply restore and expose as the actual smart pointer class
pyclass = smart_type;
Expand Down
20 changes: 20 additions & 0 deletions bindings/pyroot/cppyy/cppyy/test/cpp11features.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ TestSmartPtr create_TestSmartPtr_by_value() {
return TestSmartPtr{};
}

std::shared_ptr<TestSmartPtr> create_shared_ptr_to_derived() {
return std::shared_ptr<TestSmartPtr>(new PubDerivedTestSmartPtr);
}

std::unique_ptr<TestSmartPtr> create_unique_ptr_to_derived() {
return std::unique_ptr<TestSmartPtr>(new PubDerivedTestSmartPtr);
}

std::unique_ptr<TestSmartPtrIface> create_unique_ptr_to_offset_derived() {
return std::unique_ptr<TestSmartPtrIface>(new MultiDerivedTestSmartPtr);
}

int pass_unique_ptr_to_derived(std::unique_ptr<PubDerivedTestSmartPtr> p) {
return p->only_in_derived();
}

int pass_shared_ptr_to_derived(std::shared_ptr<PubDerivedTestSmartPtr> p) {
return p->only_in_derived();
}


// for move ctors etc.
int TestMoving1::s_move_counter = 0;
Expand Down
28 changes: 28 additions & 0 deletions bindings/pyroot/cppyy/cppyy/test/cpp11features.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ int move_unique_ptr_derived(std::unique_ptr<DerivedTestSmartPtr>&& p);

TestSmartPtr create_TestSmartPtr_by_value();

// for auto-downcast of objects returned through a smart pointer
class PubDerivedTestSmartPtr : public TestSmartPtr {
public:
int only_in_derived() { return 27; }
};

// second base so that the cross-cast to the most derived type needs a
// non-zero pointer adjustment, which the smart pointer's dereferencer can
// not apply consistently (so no down-cast should happen in that case)
class TestSmartPtrIface {
public:
virtual ~TestSmartPtrIface() {}
long m_pad = 0;
int only_in_iface() { return 37; }
};

class MultiDerivedTestSmartPtr : public PubDerivedTestSmartPtr, public TestSmartPtrIface {
};

std::shared_ptr<TestSmartPtr> create_shared_ptr_to_derived();
std::unique_ptr<TestSmartPtr> create_unique_ptr_to_derived();
std::unique_ptr<TestSmartPtrIface> create_unique_ptr_to_offset_derived();

// sinks expecting a smart pointer to the *derived* type; a base-class smart
// pointer (even when its object was auto-down-cast) must not be accepted here
int pass_unique_ptr_to_derived(std::unique_ptr<PubDerivedTestSmartPtr> p);
int pass_shared_ptr_to_derived(std::shared_ptr<PubDerivedTestSmartPtr> p);


//===========================================================================
class TestMoving1 { // for move ctors etc.
Expand Down
39 changes: 39 additions & 0 deletions bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sys, pytest, os

Check failure on line 1 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (F401)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:1:21: F401 `os` imported but unused help: Remove unused import: `os`

Check failure on line 1 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (E401)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:1:1: E401 Multiple imports on one line help: Split imports
from pytest import mark, raises
from support import setup_make, ispypy, IS_MAC_ARM, IS_WINDOWS

Check failure on line 3 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (F401)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:3:21: F401 `support.setup_make` imported but unused help: Remove unused import: `support.setup_make`

Check failure on line 3 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:1:1: I001 Import block is un-sorted or un-formatted help: Organize imports

Expand All @@ -15,9 +15,9 @@
def test01_smart_ptr(self):
"""Usage and access of std::shared/unique_ptr<>"""

from cppyy.gbl import TestSmartPtr
from cppyy.gbl import create_shared_ptr_instance, create_unique_ptr_instance
import gc

Check failure on line 20 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:18:9: I001 Import block is un-sorted or un-formatted help: Organize imports

for cf in [create_shared_ptr_instance, create_unique_ptr_instance]:
assert TestSmartPtr.s_counter == 0
Expand All @@ -43,8 +43,8 @@
def test02_smart_ptr_construction(self):
"""Shared/Unique pointer ctor is templated, requiring special care"""

from cppyy.gbl import std, TestSmartPtr
import gc

Check failure on line 47 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:46:9: I001 Import block is un-sorted or un-formatted help: Organize imports

class C(TestSmartPtr):
pass
Expand All @@ -70,8 +70,8 @@
def test03_smart_ptr_memory_handling(self):
"""Test shared/unique pointer memory ownership"""

from cppyy.gbl import std, TestSmartPtr
import gc

Check failure on line 74 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:73:9: I001 Import block is un-sorted or un-formatted help: Organize imports

class C(TestSmartPtr):
pass
Expand Down Expand Up @@ -100,9 +100,9 @@
def test04_shared_ptr_passing(self):
"""Ability to pass shared_ptr<Derived> through shared_ptr<Base>"""

from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr
from cppyy.gbl import pass_shared_ptr, move_shared_ptr, create_TestSmartPtr_by_value
import gc

Check failure on line 105 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:103:9: I001 Import block is un-sorted or un-formatted help: Organize imports

for ff, mv in [(pass_shared_ptr, lambda x: x), (move_shared_ptr, std.move)]:
assert TestSmartPtr.s_counter == 0
Expand Down Expand Up @@ -139,8 +139,8 @@
def test05_unique_ptr_passing(self):
"""Ability to pass unique_ptr<Derived> through unique_ptr<Base>"""

from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr
from cppyy.gbl import move_unique_ptr, move_unique_ptr_derived

Check failure on line 143 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (F401)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:143:31: F401 `cppyy.gbl.move_unique_ptr` imported but unused help: Remove unused import: `cppyy.gbl.move_unique_ptr`
from cppyy.gbl import create_TestSmartPtr_by_value
import gc

Check failure on line 145 in bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py:142:9: I001 Import block is un-sorted or un-formatted help: Organize imports

Expand Down Expand Up @@ -593,6 +593,45 @@

cppyy.gbl.std.tuple_element[1, ATuple].type

def test21_smart_ptr_downcast(self):
"""Object returned through a smart pointer is auto-downcast"""

import cppyy

from cppyy.gbl import TestSmartPtr, PubDerivedTestSmartPtr, TestSmartPtrIface
from cppyy.gbl import create_unique_ptr_instance, create_shared_ptr_to_derived
from cppyy.gbl import create_unique_ptr_to_derived, create_unique_ptr_to_offset_derived
from cppyy.gbl import pass_shared_ptr, pass_unique_ptr_to_derived, pass_shared_ptr_to_derived

# unique_ptr<Base> holding a Derived comes back as Derived, with the
# derived-only method callable, just like a raw pointer return
for cf in [create_unique_ptr_to_derived, create_shared_ptr_to_derived]:
obj = cf()
assert type(obj) == PubDerivedTestSmartPtr
assert obj.only_in_derived() == 27
assert obj.__smartptr__() # smart-pointer semantics preserved

# an object that really is of the declared type stays that type
obj = create_unique_ptr_instance()
assert type(obj) == TestSmartPtr

# the most derived type sits at a non-zero offset from the declared
# interface, which the dereferencer can not apply: stay the declared
# type and keep behaving correctly
obj = create_unique_ptr_to_offset_derived()
assert type(obj) == TestSmartPtrIface
assert obj.only_in_iface() == 37

# the auto-down-cast must not enable C++-invalid conversions: the proxy
# still embeds a smart pointer to the *base* type, which does not convert
# to a smart pointer to the derived type (no implicit down-conversion of
# smart pointers in C++), so passing it to such a sink must be rejected
raises(TypeError, pass_unique_ptr_to_derived, create_unique_ptr_to_derived())
raises(TypeError, pass_shared_ptr_to_derived, create_shared_ptr_to_derived())

# passing it where the matching base smart pointer is expected still works
assert pass_shared_ptr(create_shared_ptr_to_derived()) == 17


if __name__ == "__main__":
exit(pytest.main(args=['-sv', '-ra', __file__]))
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,9 @@ def learned_likelihood_ratio(*args):
nllr_learned.plotOn(frame1, LineColor="kP6Blue", ShiftToZero=True, Name="learned")


# Declare a helper function in ROOT to dereference unique_ptr
ROOT.gInterpreter.Declare(
"""
RooAbsArg &my_deref(std::unique_ptr<RooAbsArg> const& ptr) { return *ptr; }
"""
)

# Choose normalization set for lhr_calc to plot over
norm_set = ROOT.RooArgSet(x_vars)
lhr_calc_final_ptr = ROOT.RooFit.Detail.compileForNormSet(lhr_calc, norm_set)
lhr_calc_final = ROOT.my_deref(lhr_calc_final_ptr)
lhr_calc_final = ROOT.RooFit.Detail.compileForNormSet(lhr_calc, norm_set)
lhr_calc_final.recursiveRedirectServers(norm_set)

# Plot the likelihood ratio functions
Expand Down
Loading