diff --git a/tree/dataframe/inc/ROOT/RDF/RAction.hxx b/tree/dataframe/inc/ROOT/RDF/RAction.hxx
index c71b0dd38b889d793d39a3e730bb77575fa7b000..a4bc04cf7a828b6af33242a63f48d81464235537 100644
--- a/tree/dataframe/inc/ROOT/RDF/RAction.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/RAction.hxx
@@ -16,6 +16,7 @@
 #include "ROOT/RDF/NodesUtils.hxx" // InitRDFValues
 #include "ROOT/RDF/Utils.hxx"      // ColumnNames_t
 #include "ROOT/RDF/RColumnValue.hxx"
+#include "ROOT/RDF/RLoopManager.hxx"
 
 #include <cstddef> // std::size_t
 #include <memory>
@@ -117,6 +118,9 @@ public:
 
    RActionCRTP(const RActionCRTP &) = delete;
    RActionCRTP &operator=(const RActionCRTP &) = delete;
+   // must call Deregister here, before fPrevDataFrame is destroyed,
+   // otherwise if fPrevDataFrame is fLoopManager we get a use after delete
+   ~RActionCRTP() { fLoopManager->Deregister(this); }
 
    Helper &GetHelper() { return fHelper; }
 
diff --git a/tree/dataframe/inc/ROOT/RDF/RActionBase.hxx b/tree/dataframe/inc/ROOT/RDF/RActionBase.hxx
index b42c4370b8f35e1d472ce1e1989b6be4a7af9cbc..ed5217ef5a7ba1bd377e16f2cbfd4b976013123b 100644
--- a/tree/dataframe/inc/ROOT/RDF/RActionBase.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/RActionBase.hxx
@@ -42,11 +42,12 @@ bool CheckIfDefaultOrDSColumn(const std::string &name,
 } // namespace GraphDrawing
 
 class RActionBase {
-private:
+protected:
    /// A raw pointer to the RLoopManager at the root of this functional graph.
    /// Never null: children nodes have shared ownership of parent nodes in the graph.
    RLoopManager *fLoopManager;
 
+private:
    const unsigned int fNSlots; ///< Number of thread slots used by this node.
    bool fHasRun = false;
    const ColumnNames_t fColumnNames;
diff --git a/tree/dataframe/inc/ROOT/RDF/RFilter.hxx b/tree/dataframe/inc/ROOT/RDF/RFilter.hxx
index feb00e78a72433996381e6e9ee38bdd6f6c4b975..095461d899b4aab2e2cd16ecda1a44286bc1525a 100644
--- a/tree/dataframe/inc/ROOT/RDF/RFilter.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/RFilter.hxx
@@ -16,6 +16,7 @@
 #include "ROOT/RDF/NodesUtils.hxx"
 #include "ROOT/RDF/Utils.hxx"
 #include "ROOT/RDF/RFilterBase.hxx"
+#include "ROOT/RDF/RLoopManager.hxx"
 #include "ROOT/RIntegerSequence.hxx"
 #include "ROOT/TypeTraits.hxx"
 #include "RtypesCore.h"
@@ -69,6 +70,9 @@ public:
 
    RFilter(const RFilter &) = delete;
    RFilter &operator=(const RFilter &) = delete;
+   // must call Deregister here, before fPrevDataFrame is destroyed,
+   // otherwise if fPrevDataFrame is fLoopManager we get a use after delete
+   ~RFilter() { fLoopManager->Deregister(this); }
 
    bool CheckFilters(unsigned int slot, Long64_t entry) final
    {
diff --git a/tree/dataframe/inc/ROOT/RDF/RRange.hxx b/tree/dataframe/inc/ROOT/RDF/RRange.hxx
index f0e2c2ed49f07202b08853dee7e4c18bddad2bfc..e7a91417d17374bc61c681745c7ee5b04425ffa7 100644
--- a/tree/dataframe/inc/ROOT/RDF/RRange.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/RRange.hxx
@@ -11,6 +11,7 @@
 #ifndef ROOT_RDFRANGE
 #define ROOT_RDFRANGE
 
+#include "ROOT/RDF/RLoopManager.hxx"
 #include "ROOT/RDF/RRangeBase.hxx"
 #include "RtypesCore.h"
 
@@ -43,6 +44,9 @@ public:
 
    RRange(const RRange &) = delete;
    RRange &operator=(const RRange &) = delete;
+   // must call Deregister here, before fPrevDataFrame is destroyed,
+   // otherwise if fPrevDataFrame is fLoopManager we get a use after delete
+   ~RRange() { fLoopManager->Deregister(this); }
 
    /// Ranges act as filters when it comes to selecting entries that downstream nodes should process
    bool CheckFilters(unsigned int slot, Long64_t entry) final
diff --git a/tree/dataframe/src/RActionBase.cxx b/tree/dataframe/src/RActionBase.cxx
index d3e017a67dab9687895ac77439abff6b94254ea6..9af61f7e3c3119fa39d09b8c370921de9ab8a61c 100644
--- a/tree/dataframe/src/RActionBase.cxx
+++ b/tree/dataframe/src/RActionBase.cxx
@@ -16,7 +16,5 @@ using namespace ROOT::Internal::RDF;
 RActionBase::RActionBase(RLoopManager *lm, const ColumnNames_t &colNames, const RBookedCustomColumns &customColumns)
    : fLoopManager(lm), fNSlots(lm->GetNSlots()), fColumnNames(colNames), fCustomColumns(customColumns) { }
 
-RActionBase::~RActionBase()
-{
-   fLoopManager->Deregister(this);
-}
+// outlined to pin virtual table
+RActionBase::~RActionBase() {}
diff --git a/tree/dataframe/src/RFilterBase.cxx b/tree/dataframe/src/RFilterBase.cxx
index de9b0fc3e270cbfe6d9b2c3c0a94ba74bc440a56..89cc938ea547a980229cf7f8a41c82d4167fa580 100644
--- a/tree/dataframe/src/RFilterBase.cxx
+++ b/tree/dataframe/src/RFilterBase.cxx
@@ -9,8 +9,8 @@
  *************************************************************************/
 
 #include "ROOT/RDF/RCutFlowReport.hxx"
-#include "ROOT/RDF/RLoopManager.hxx"
 #include "ROOT/RDF/RFilterBase.hxx"
+#include <numeric> // std::accumulate
 
 using namespace ROOT::Detail::RDF;
 
@@ -19,10 +19,8 @@ RFilterBase::RFilterBase(RLoopManager *implPtr, std::string_view name, const uns
    : RNodeBase(implPtr), fLastResult(nSlots), fAccepted(nSlots), fRejected(nSlots), fName(name), fNSlots(nSlots),
      fCustomColumns(customColumns) {}
 
-RFilterBase::~RFilterBase()
-{
-   fLoopManager->Deregister(this);
-}
+// outlined to pin virtual table
+RFilterBase::~RFilterBase() {}
 
 bool RFilterBase::HasName() const
 {
diff --git a/tree/dataframe/src/RRangeBase.cxx b/tree/dataframe/src/RRangeBase.cxx
index 345b6939e742c105367886b2e358b55377df22c8..07ada8effc7f8c264991cf1ee664483f7783773f 100644
--- a/tree/dataframe/src/RRangeBase.cxx
+++ b/tree/dataframe/src/RRangeBase.cxx
@@ -8,7 +8,6 @@
  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
  *************************************************************************/
 
-#include "ROOT/RDF/RLoopManager.hxx"
 #include "ROOT/RDF/RRangeBase.hxx"
 
 using ROOT::Detail::RDF::RRangeBase;
@@ -25,7 +24,5 @@ void RRangeBase::ResetCounters()
    fHasStopped = false;
 }
 
-RRangeBase::~RRangeBase()
-{
-   fLoopManager->Deregister(this);
-}
+// outlined to pin virtual table
+RRangeBase::~RRangeBase() { }