[pim/messagelib] /: Clean up ownership semantics of body part plugins

Volker Krause <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 46f79e03086472d6676646698a29b7bc164a7fa1 by Volker Krause.
Committed on 17/08/2026 at 18:51.
Pushed by vkrause into branch 'master'.

Clean up ownership semantics of body part plugins

This addresses LSAN errors that show up in the render tests in
kdepim-addons.

M  +1    -1    CMakeLists.txt
M  +13   -18   messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
M  +1    -2    messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
M  +2    -3    messageviewer/src/messagepartthemes/default/messagepartrenderplugin.h
M  +27   -34   mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
M  +0    -10   mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/applicationpgpencrypted.h
M  +0    -10   mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/applicationpkcs7mime.h
M  +0    -17   mimetreeparser/src/bodyformatter/encrypted.cpp
M  +4    -1    mimetreeparser/src/bodyformatter/encrypted.h
M  +0    -10   mimetreeparser/src/bodyformatter/mailman.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/mailman.h
M  +0    -10   mimetreeparser/src/bodyformatter/multipartalternative.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/multipartalternative.h
M  +0    -10   mimetreeparser/src/bodyformatter/multipartencrypted.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/multipartencrypted.h
M  +0    -10   mimetreeparser/src/bodyformatter/multipartmixed.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/multipartmixed.h
M  +0    -10   mimetreeparser/src/bodyformatter/multipartsigned.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/multipartsigned.h
M  +0    -10   mimetreeparser/src/bodyformatter/texthtml.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/texthtml.h
M  +0    -10   mimetreeparser/src/bodyformatter/textplain.cpp
M  +0    -3    mimetreeparser/src/bodyformatter/textplain.h
M  +27   -64   mimetreeparser/src/bodypartformatter.cpp
M  +13   -21   mimetreeparser/src/bodypartformatterfactory.cpp
M  +1    -1    mimetreeparser/src/bodypartformatterfactory.h
M  +9    -4    mimetreeparser/src/bodypartformatterfactory_p.h
M  +2    -3    mimetreeparser/src/interfaces/bodypartformatter.h

https://invent.kde.org/pim/messagelib/-/commit/46f79e03086472d6676646698a29b7bc164a7fa1

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b7104dd76..1e3656e1e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: CC0-1.0
 # SPDX-FileCopyrightText: none
 cmake_minimum_required(VERSION 3.29)
-set(PIM_VERSION "6.8.40")
+set(PIM_VERSION "6.8.41")
 
 project(Messagelib VERSION ${PIM_VERSION})
 
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
index 8a91974c7..d7b4521ce 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
+++ b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
@@ -26,15 +26,7 @@
 using namespace Qt::Literals::StringLiterals;
 using namespace MessageViewer;
 
-MessagePartRendererFactoryPrivate::~MessagePartRendererFactoryPrivate()
-{
-    QHashIterator<QByteArray, std::vector<RendererInfo>> i(m_renderers);
-    while (i.hasNext()) {
-        i.next();
-        auto renderInfo = i.value();
-        renderInfo.erase(renderInfo.begin(), renderInfo.end());
-    }
-}
+MessagePartRendererFactoryPrivate::~MessagePartRendererFactoryPrivate() = default;
 
 void MessagePartRendererFactoryPrivate::setup()
 {
@@ -64,7 +56,7 @@ void MessagePartRendererFactoryPrivate::loadPlugins()
             continue;
         }
 
-        MessagePartRendererBase *renderer = nullptr;
+        std::unique_ptr<MessagePartRendererBase> renderer;
         for (int i = 0; (renderer = plugin->renderer(i)) && i < pluginData.size(); ++i) {
             const auto metaData = pluginData.at(i).toObject();
             const auto type = metaData.value(QLatin1StringView("type")).toString().toUtf8();
@@ -76,7 +68,7 @@ void MessagePartRendererFactoryPrivate::loadPlugins()
             // priority should always be higher than the built-in ones, otherwise what's the point?
             const auto priority = metaData.value(QLatin1StringView("priority")).toInt() + 100;
             qCDebug(MESSAGEVIEWER_LOG) << "renderer plugin for " << type << mimetype << priority;
-            insert(type, renderer, mimetype, priority);
+            insert(type, std::move(renderer), mimetype, priority);
         }
 
         const Interface::BodyPartURLHandler *handler = nullptr;
@@ -90,12 +82,15 @@ void MessagePartRendererFactoryPrivate::loadPlugins()
 
 void MessagePartRendererFactoryPrivate::initialize_builtin_renderers()
 {
-    insert("MimeTreeParser::MessagePart", new MessagePartRenderer());
-    insert("MimeTreeParser::TextMessagePart", new TextMessagePartRenderer());
-    insert("MimeTreeParser::AttachmentMessagePart", new AttachmentMessagePartRenderer());
+    insert("MimeTreeParser::MessagePart", std::make_unique<MessagePartRenderer>());
+    insert("MimeTreeParser::TextMessagePart", std::make_unique<TextMessagePartRenderer>());
+    insert("MimeTreeParser::AttachmentMessagePart", std::make_unique<AttachmentMessagePartRenderer>());
 }
 
-void MessagePartRendererFactoryPrivate::insert(const QByteArray &type, MessagePartRendererBase *renderer, const QString &mimeType, int priority)
+void MessagePartRendererFactoryPrivate::insert(const QByteArray &type,
+                                               std::unique_ptr<MessagePartRendererBase> &&renderer,
+                                               const QString &mimeType,
+                                               int priority)
 {
     if (type.isEmpty() || !renderer) {
         return;
@@ -105,12 +100,12 @@ void MessagePartRendererFactoryPrivate::insert(const QByteArray &type, MessagePa
     const auto mt = db.mimeTypeForName(mimeType);
 
     RendererInfo info;
-    info.renderer.reset(renderer);
+    info.renderer.reset(renderer.release());
     info.mimeType = mt.isValid() ? mt.name() : mimeType;
     info.priority = priority;
 
     auto &v = m_renderers[type];
-    v.push_back(info);
+    v.push_back(std::move(info));
 }
 
 MessagePartRendererFactory::MessagePartRendererFactory()
@@ -173,7 +168,7 @@ QList<MessagePartRendererBase *> MessagePartRendererFactory::renderersForPart(co
     QList<MessagePartRendererBase *> r;
     r.reserve(candidates.size());
     for (const auto &candidate : candidates) {
-        r.push_back(candidate.renderer.data());
+        r.push_back(candidate.renderer.get());
     }
     return r;
 }
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
index 03a30c27a..fe6e0ce87 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
+++ b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
@@ -7,7 +7,6 @@
 #pragma once
 
 #include <QByteArray>
-
 #include <QHash>
 #include <QString>
 
@@ -31,7 +30,7 @@ public:
     void setup();
     void loadPlugins();
     void initialize_builtin_renderers();
-    void insert(const QByteArray &type, MessagePartRendererBase *formatter, const QString &mimeType = QString(), int priority = 0);
+    void insert(const QByteArray &type, std::unique_ptr<MessagePartRendererBase> &&formatter, const QString &mimeType = QString(), int priority = 0);
 
     QHash<QByteArray, std::vector<RendererInfo>> m_renderers;
     QString m_pluginSubdir = u"pim6/messageviewer/bodypartformatter"_s;
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrenderplugin.h b/messageviewer/src/messagepartthemes/default/messagepartrenderplugin.h
index 942a2c01a..25d346d32 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrenderplugin.h
+++ b/messageviewer/src/messagepartthemes/default/messagepartrenderplugin.h
@@ -30,9 +30,8 @@ public:
     /*!
      */
     virtual ~MessagePartRenderPlugin();
-    /*!
-     */
-    virtual MessagePartRendererBase *renderer(int index) = 0;
+    /*! Return a renderer instance for the Nth entry in the plugin metadata. */
+    [[nodiscard]] virtual std::unique_ptr<MessagePartRendererBase> renderer(int index) = 0;
     /*!
      */
     virtual const Interface::BodyPartURLHandler *urlHandler(int idx) const;
diff --git a/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp b/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
index cc99847d3..01b0b45c7 100644
--- a/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
+++ b/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
@@ -26,11 +26,8 @@ class TestFactory : public BodyPartFormatterFactory
 public:
     void loadPlugins() override
     {
-        textCalFormatter = std::make_unique<DummyFormatter>();
-        insert(u"TEXT/CALENDAR"_s, textCalFormatter.get(), 100);
+        insert(u"TEXT/CALENDAR"_s, std::make_unique<DummyFormatter>(), 100);
     }
-
-    std::unique_ptr<DummyFormatter> textCalFormatter;
 };
 
 using namespace MimeTreeParser;
@@ -44,80 +41,76 @@ private Q_SLOTS:
         TestFactory fac;
         auto l = fac.formattersForType(u"application/octet-stream"_s);
         QCOMPARE(l.size(), 3);
-        const auto application_octet_stream_f = l.at(2);
-        QVERIFY(application_octet_stream_f);
+        const auto &application_octet_stream_f = typeid(*l.at(2));
 
         l = fac.formattersForType(u"application/pgp-encrypted"_s);
         // shared-mime-info < 2.4 has application/pgp-encrytped as a subtype of text/plain, 2.4+ doesn't
         // depending of that we get two additional entries for text/plain formatters here
         QVERIFY(l.size() == 4 || l.size() == 6);
-        QVERIFY(l.at(0) != application_octet_stream_f);
-        QCOMPARE(l.at(l.size() - 1), application_octet_stream_f);
+        QCOMPARE_NE(typeid(*l.at(0)), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(l.size() - 1)), application_octet_stream_f);
 
         l = fac.formattersForType(u"application/unknown"_s);
         QCOMPARE(l.size(), 3);
-        QCOMPARE(l.at(2), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(2)), application_octet_stream_f);
 
         l = fac.formattersForType(u"text/plain"_s);
         QCOMPARE(l.size(), 5);
-        const auto text_plain_f1 = l.at(0);
-        const auto text_plain_f2 = l.at(1);
-        QVERIFY(text_plain_f1);
-        QVERIFY(text_plain_f2);
+        const auto &text_plain_f1 = typeid(*l.at(0));
+        const auto &text_plain_f2 = typeid(*l.at(1));
         QVERIFY(text_plain_f1 != text_plain_f2);
-        QCOMPARE(l.at(4), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(4)), application_octet_stream_f);
 
         l = fac.formattersForType(u"text/calendar"_s);
         QCOMPARE(l.size(), 6);
-        QVERIFY(fac.textCalFormatter);
-        QCOMPARE(l.at(0), fac.textCalFormatter.get());
-        QCOMPARE(l.at(1), text_plain_f1);
-        QCOMPARE(l.at(2), text_plain_f2);
-        QCOMPARE(l.at(5), application_octet_stream_f);
+        QVERIFY(dynamic_cast<const DummyFormatter *>(l.at(0)));
+        QCOMPARE(typeid(*l.at(1)), text_plain_f1);
+        QCOMPARE(typeid(*l.at(2)), text_plain_f2);
+        QCOMPARE(typeid(*l.at(5)), application_octet_stream_f);
 
         l = fac.formattersForType(u"text/x-vcalendar"_s);
         QCOMPARE(l.size(), 6);
-        QCOMPARE(l.at(0), fac.textCalFormatter.get());
+        QVERIFY(dynamic_cast<const DummyFormatter *>(l.at(0)));
         l = fac.formattersForType(u"TEXT/X-VCALENDAR"_s);
         QCOMPARE(l.size(), 6);
-        QCOMPARE(l.at(0), fac.textCalFormatter.get());
+        QVERIFY(dynamic_cast<const DummyFormatter *>(l.at(0)));
 
         l = fac.formattersForType(u"text/html"_s);
         QCOMPARE(l.size(), 6);
-        QCOMPARE(l.at(1), text_plain_f1);
-        QCOMPARE(l.at(2), text_plain_f2);
-        QCOMPARE(l.at(5), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(1)), text_plain_f1);
+        QCOMPARE(typeid(*l.at(2)), text_plain_f2);
+        QCOMPARE(typeid(*l.at(5)), application_octet_stream_f);
 
         l = fac.formattersForType(u"text/rtf"_s);
         QCOMPARE(l.size(), 6);
-        QCOMPARE(l.at(0), application_octet_stream_f);
-        QCOMPARE(l.at(5), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(0)), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(5)), application_octet_stream_f);
 
         l = fac.formattersForType(u"multipart/mixed"_s);
         QCOMPARE(l.size(), 1);
-        const auto multipart_mixed_f = l.at(0);
-        QVERIFY(multipart_mixed_f);
+        const auto &multipart_mixed_f = typeid(*l.at(0));
+        // QVERIFY(multipart_mixed_f);
 
         l = fac.formattersForType(u"multipart/random"_s);
         QCOMPARE(l.size(), 1);
-        QCOMPARE(l.at(0), multipart_mixed_f);
+        QCOMPARE(typeid(*l.at(0)), multipart_mixed_f);
 
         l = fac.formattersForType(u"multipart/encrypted"_s);
         QCOMPARE(l.size(), 2);
-        QVERIFY(l.at(0) != multipart_mixed_f);
-        QCOMPARE(l.at(1), multipart_mixed_f);
+        QVERIFY(typeid(*l.at(0)) != multipart_mixed_f);
+        QCOMPARE(typeid(*l.at(1)), multipart_mixed_f);
 
         l = fac.formattersForType(u"image/png"_s);
         QCOMPARE(l.size(), 4);
-        QCOMPARE(l.at(3), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(3)), application_octet_stream_f);
 
         l = fac.formattersForType(u"vendor/random"_s);
         QCOMPARE(l.size(), 3);
-        QCOMPARE(l.at(2), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(2)), application_octet_stream_f);
 
         l = fac.formattersForType(u"message/rfc822"_s);
         QCOMPARE(l.size(), 6);
-        QCOMPARE(l.at(5), application_octet_stream_f);
+        QCOMPARE(typeid(*l.at(5)), application_octet_stream_f);
 
         l = fac.formattersForType(u"message/news"_s);
         QCOMPARE(l.size(), 5); // ### news does not inherit rfc822
diff --git a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp
index 2ae441177..7e3e69e7a 100644
--- a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp
+++ b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp
@@ -19,16 +19,6 @@
 
 using namespace MimeTreeParser;
 
-const ApplicationPGPEncryptedBodyPartFormatter *ApplicationPGPEncryptedBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *ApplicationPGPEncryptedBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new ApplicationPGPEncryptedBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr ApplicationPGPEncryptedBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node(part.content());
diff --git a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.h b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.h
index 208735a6d..944df1e2e 100644
--- a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.h
+++ b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class ApplicationPGPEncryptedBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const ApplicationPGPEncryptedBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
index 76173d1b8..f475934bd 100644
--- a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
+++ b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
@@ -17,16 +17,6 @@
 
 using namespace MimeTreeParser;
 
-const ApplicationPkcs7MimeBodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new ApplicationPkcs7MimeBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr ApplicationPkcs7MimeBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.h b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.h
index 869254822..021bbadb3 100644
--- a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.h
+++ b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class ApplicationPkcs7MimeBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const ApplicationPkcs7MimeBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/encrypted.cpp b/mimetreeparser/src/bodyformatter/encrypted.cpp
index 09c545859..709f71541 100644
--- a/mimetreeparser/src/bodyformatter/encrypted.cpp
+++ b/mimetreeparser/src/bodyformatter/encrypted.cpp
@@ -19,23 +19,6 @@
 
 using namespace MimeTreeParser;
 
-const Interface::BodyPartFormatter *EncryptedBodyPartFormatter::create(EncryptedBodyPartFormatter::EncryptionFlags flags)
-{
-    switch (flags) {
-    case AutoPGP: {
-        static EncryptedBodyPartFormatter self;
-        self.mFlags = flags;
-        return &self;
-    }
-    case ForcePGP: {
-        static EncryptedBodyPartFormatter self;
-        self.mFlags = flags;
-        return &self;
-    }
-    }
-    Q_UNREACHABLE();
-}
-
 MessagePart::Ptr EncryptedBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/encrypted.h b/mimetreeparser/src/bodyformatter/encrypted.h
index fedae2a41..7c0928ad7 100644
--- a/mimetreeparser/src/bodyformatter/encrypted.h
+++ b/mimetreeparser/src/bodyformatter/encrypted.h
@@ -19,9 +19,12 @@ public:
         ForcePGP = 0x1, ///< Always decode PGP data
     };
     Q_DECLARE_FLAGS(EncryptionFlags, EncryptionFlag)
+    explicit EncryptedBodyPartFormatter(EncryptionFlag flag)
+        : mFlags(flag)
+    {
+    }
 
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create(EncryptionFlags flags);
 
 private:
     EncryptionFlags mFlags;
diff --git a/mimetreeparser/src/bodyformatter/mailman.cpp b/mimetreeparser/src/bodyformatter/mailman.cpp
index 7a7043484..cb1c90f55 100644
--- a/mimetreeparser/src/bodyformatter/mailman.cpp
+++ b/mimetreeparser/src/bodyformatter/mailman.cpp
@@ -18,16 +18,6 @@
 using namespace Qt::Literals::StringLiterals;
 using namespace MimeTreeParser;
 
-const MailmanBodyPartFormatter *MailmanBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *MailmanBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MailmanBodyPartFormatter();
-    }
-    return self;
-}
-
 bool MailmanBodyPartFormatter::isMailmanMessage(KMime::Content *curNode) const
 {
     if (!curNode || curNode->head().isEmpty()) {
diff --git a/mimetreeparser/src/bodyformatter/mailman.h b/mimetreeparser/src/bodyformatter/mailman.h
index 244881ac6..a4057b848 100644
--- a/mimetreeparser/src/bodyformatter/mailman.h
+++ b/mimetreeparser/src/bodyformatter/mailman.h
@@ -13,11 +13,8 @@ namespace MimeTreeParser
 {
 class MailmanBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const MailmanBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 
 private:
     [[nodiscard]] bool isMailmanMessage(KMime::Content *curNode) const;
diff --git a/mimetreeparser/src/bodyformatter/multipartalternative.cpp b/mimetreeparser/src/bodyformatter/multipartalternative.cpp
index 9c1faa047..e1763bd24 100644
--- a/mimetreeparser/src/bodyformatter/multipartalternative.cpp
+++ b/mimetreeparser/src/bodyformatter/multipartalternative.cpp
@@ -13,16 +13,6 @@
 
 using namespace MimeTreeParser;
 
-const MultiPartAlternativeBodyPartFormatter *MultiPartAlternativeBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *MultiPartAlternativeBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MultiPartAlternativeBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr MultiPartAlternativeBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/multipartalternative.h b/mimetreeparser/src/bodyformatter/multipartalternative.h
index e0392f335..bcd3673ad 100644
--- a/mimetreeparser/src/bodyformatter/multipartalternative.h
+++ b/mimetreeparser/src/bodyformatter/multipartalternative.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class MultiPartAlternativeBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const MultiPartAlternativeBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/multipartencrypted.cpp b/mimetreeparser/src/bodyformatter/multipartencrypted.cpp
index 2fc37ce25..34383b7d3 100644
--- a/mimetreeparser/src/bodyformatter/multipartencrypted.cpp
+++ b/mimetreeparser/src/bodyformatter/multipartencrypted.cpp
@@ -19,16 +19,6 @@
 
 using namespace MimeTreeParser;
 
-const MultiPartEncryptedBodyPartFormatter *MultiPartEncryptedBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *MultiPartEncryptedBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MultiPartEncryptedBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr MultiPartEncryptedBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/multipartencrypted.h b/mimetreeparser/src/bodyformatter/multipartencrypted.h
index 93a21e5b0..b2c44afe1 100644
--- a/mimetreeparser/src/bodyformatter/multipartencrypted.h
+++ b/mimetreeparser/src/bodyformatter/multipartencrypted.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class MultiPartEncryptedBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const MultiPartEncryptedBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/multipartmixed.cpp b/mimetreeparser/src/bodyformatter/multipartmixed.cpp
index d4272287a..2dc163426 100644
--- a/mimetreeparser/src/bodyformatter/multipartmixed.cpp
+++ b/mimetreeparser/src/bodyformatter/multipartmixed.cpp
@@ -13,16 +13,6 @@
 
 using namespace MimeTreeParser;
 
-const MultiPartMixedBodyPartFormatter *MultiPartMixedBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *MultiPartMixedBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MultiPartMixedBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr MultiPartMixedBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     if (part.content()->contents().isEmpty()) {
diff --git a/mimetreeparser/src/bodyformatter/multipartmixed.h b/mimetreeparser/src/bodyformatter/multipartmixed.h
index 54e2d72dc..b61cace48 100644
--- a/mimetreeparser/src/bodyformatter/multipartmixed.h
+++ b/mimetreeparser/src/bodyformatter/multipartmixed.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class MultiPartMixedBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const MultiPartMixedBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/multipartsigned.cpp b/mimetreeparser/src/bodyformatter/multipartsigned.cpp
index 3d3bdc016..eaddc11d8 100644
--- a/mimetreeparser/src/bodyformatter/multipartsigned.cpp
+++ b/mimetreeparser/src/bodyformatter/multipartsigned.cpp
@@ -17,16 +17,6 @@
 
 using namespace MimeTreeParser;
 
-const MultiPartSignedBodyPartFormatter *MultiPartSignedBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *MultiPartSignedBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MultiPartSignedBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr MultiPartSignedBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/multipartsigned.h b/mimetreeparser/src/bodyformatter/multipartsigned.h
index 9bad2a326..0e5444bd1 100644
--- a/mimetreeparser/src/bodyformatter/multipartsigned.h
+++ b/mimetreeparser/src/bodyformatter/multipartsigned.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class MultiPartSignedBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const MultiPartSignedBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/texthtml.cpp b/mimetreeparser/src/bodyformatter/texthtml.cpp
index d3322886f..2502cf047 100644
--- a/mimetreeparser/src/bodyformatter/texthtml.cpp
+++ b/mimetreeparser/src/bodyformatter/texthtml.cpp
@@ -13,16 +13,6 @@
 
 using namespace MimeTreeParser;
 
-const TextHtmlBodyPartFormatter *TextHtmlBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *TextHtmlBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new TextHtmlBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr TextHtmlBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/texthtml.h b/mimetreeparser/src/bodyformatter/texthtml.h
index 942feb5bb..82c8712f9 100644
--- a/mimetreeparser/src/bodyformatter/texthtml.h
+++ b/mimetreeparser/src/bodyformatter/texthtml.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class TextHtmlBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const TextHtmlBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodyformatter/textplain.cpp b/mimetreeparser/src/bodyformatter/textplain.cpp
index 72485d1fc..52fbb9805 100644
--- a/mimetreeparser/src/bodyformatter/textplain.cpp
+++ b/mimetreeparser/src/bodyformatter/textplain.cpp
@@ -13,16 +13,6 @@
 
 using namespace MimeTreeParser;
 
-const TextPlainBodyPartFormatter *TextPlainBodyPartFormatter::self;
-
-const Interface::BodyPartFormatter *TextPlainBodyPartFormatter::create()
-{
-    if (!self) {
-        self = new TextPlainBodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr TextPlainBodyPartFormatter::process(Interface::BodyPart &part) const
 {
     KMime::Content *node = part.content();
diff --git a/mimetreeparser/src/bodyformatter/textplain.h b/mimetreeparser/src/bodyformatter/textplain.h
index 11aaf289c..304271d70 100644
--- a/mimetreeparser/src/bodyformatter/textplain.h
+++ b/mimetreeparser/src/bodyformatter/textplain.h
@@ -13,10 +13,7 @@ namespace MimeTreeParser
 {
 class TextPlainBodyPartFormatter : public Interface::BodyPartFormatter
 {
-    static const TextPlainBodyPartFormatter *self;
-
 public:
     [[nodiscard]] MessagePartPtr process(Interface::BodyPart &part) const override;
-    static const Interface::BodyPartFormatter *create();
 };
 }
diff --git a/mimetreeparser/src/bodypartformatter.cpp b/mimetreeparser/src/bodypartformatter.cpp
index f3b19941a..a5ca79f9e 100644
--- a/mimetreeparser/src/bodypartformatter.cpp
+++ b/mimetreeparser/src/bodypartformatter.cpp
@@ -36,8 +36,6 @@ namespace
 {
 class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
 {
-    static const AnyTypeBodyPartFormatter *self;
-
 public:
     MessagePart::Ptr process(Interface::BodyPart &part) const override
     {
@@ -50,31 +48,11 @@ public:
         mp->setIsImage(false);
         return mp;
     }
-
-    static const MimeTreeParser::Interface::BodyPartFormatter *create()
-    {
-        if (!self) {
-            self = new AnyTypeBodyPartFormatter();
-        }
-        return self;
-    }
 };
 
-const AnyTypeBodyPartFormatter *AnyTypeBodyPartFormatter::self = nullptr;
-
 class ImageTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
 {
-    static const ImageTypeBodyPartFormatter *self;
-
 public:
-    static const MimeTreeParser::Interface::BodyPartFormatter *create()
-    {
-        if (!self) {
-            self = new ImageTypeBodyPartFormatter();
-        }
-        return self;
-    }
-
     MessagePart::Ptr process(Interface::BodyPart &part) const override
     {
         KMime::Content *node = part.content();
@@ -95,27 +73,12 @@ public:
     }
 };
 
-const ImageTypeBodyPartFormatter *ImageTypeBodyPartFormatter::self = nullptr;
-
 class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
 {
-    static const MessageRfc822BodyPartFormatter *self;
-
 public:
     MessagePart::Ptr process(Interface::BodyPart &) const override;
-    static const MimeTreeParser::Interface::BodyPartFormatter *create();
 };
 
-const MessageRfc822BodyPartFormatter *MessageRfc822BodyPartFormatter::self;
-
-const MimeTreeParser::Interface::BodyPartFormatter *MessageRfc822BodyPartFormatter::create()
-{
-    if (!self) {
-        self = new MessageRfc822BodyPartFormatter();
-    }
-    return self;
-}
-
 MessagePart::Ptr MessageRfc822BodyPartFormatter::process(Interface::BodyPart &part) const
 {
     const std::shared_ptr<KMime::Message> message = part.content()->bodyAsMessage();
@@ -125,31 +88,31 @@ MessagePart::Ptr MessageRfc822BodyPartFormatter::process(Interface::BodyPart &pa
 
 void BodyPartFormatterFactoryPrivate::messageviewer_create_builtin_bodypart_formatters()
 {
-    insert(u"application/pkcs7-mime"_s, ApplicationPkcs7MimeBodyPartFormatter::create());
-    insert(u"application/x-pkcs7-mime"_s, ApplicationPkcs7MimeBodyPartFormatter::create());
-    insert(u"application/pgp-encrypted"_s, ApplicationPGPEncryptedBodyPartFormatter::create());
-
-    insert(u"application/octet-stream"_s, ApplicationPkcs7MimeBodyPartFormatter::create());
-    insert(u"application/octet-stream"_s, EncryptedBodyPartFormatter::create(EncryptedBodyPartFormatter::AutoPGP));
-    insert(u"application/octet-stream"_s, AnyTypeBodyPartFormatter::create());
-
-    insert(u"text/pgp"_s, EncryptedBodyPartFormatter::create(EncryptedBodyPartFormatter::ForcePGP));
-    insert(u"text/html"_s, TextHtmlBodyPartFormatter::create());
-    insert(u"text/rtf"_s, AnyTypeBodyPartFormatter::create());
-    insert(u"text/plain"_s, MailmanBodyPartFormatter::create());
-    insert(u"text/plain"_s, TextPlainBodyPartFormatter::create());
-
-    insert(u"image/png"_s, ImageTypeBodyPartFormatter::create());
-    insert(u"image/jpeg"_s, ImageTypeBodyPartFormatter::create());
-    insert(u"image/gif"_s, ImageTypeBodyPartFormatter::create());
-    insert(u"image/svg+xml"_s, ImageTypeBodyPartFormatter::create());
-    insert(u"image/bmp"_s, ImageTypeBodyPartFormatter::create());
-    insert(u"image/vnd.microsoft.icon"_s, ImageTypeBodyPartFormatter::create());
-
-    insert(u"message/rfc822"_s, MessageRfc822BodyPartFormatter::create());
-
-    insert(u"multipart/alternative"_s, MultiPartAlternativeBodyPartFormatter::create());
-    insert(u"multipart/encrypted"_s, MultiPartEncryptedBodyPartFormatter::create());
-    insert(u"multipart/signed"_s, MultiPartSignedBodyPartFormatter::create());
-    insert(u"multipart/mixed"_s, MultiPartMixedBodyPartFormatter::create());
+    insert(u"application/pkcs7-mime"_s, std::make_unique<ApplicationPkcs7MimeBodyPartFormatter>());
+    insert(u"application/x-pkcs7-mime"_s, std::make_unique<ApplicationPkcs7MimeBodyPartFormatter>());
+    insert(u"application/pgp-encrypted"_s, std::make_unique<ApplicationPGPEncryptedBodyPartFormatter>());
+
+    insert(u"application/octet-stream"_s, std::make_unique<ApplicationPkcs7MimeBodyPartFormatter>());
+    insert(u"application/octet-stream"_s, std::make_unique<EncryptedBodyPartFormatter>(EncryptedBodyPartFormatter::AutoPGP));
+    insert(u"application/octet-stream"_s, std::make_unique<AnyTypeBodyPartFormatter>());
+
+    insert(u"text/pgp"_s, std::make_unique<EncryptedBodyPartFormatter>(EncryptedBodyPartFormatter::ForcePGP));
+    insert(u"text/html"_s, std::make_unique<TextHtmlBodyPartFormatter>());
+    insert(u"text/rtf"_s, std::make_unique<AnyTypeBodyPartFormatter>());
+    insert(u"text/plain"_s, std::make_unique<MailmanBodyPartFormatter>());
+    insert(u"text/plain"_s, std::make_unique<TextPlainBodyPartFormatter>());
+
+    insert(u"image/png"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+    insert(u"image/jpeg"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+    insert(u"image/gif"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+    insert(u"image/svg+xml"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+    insert(u"image/bmp"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+    insert(u"image/vnd.microsoft.icon"_s, std::make_unique<ImageTypeBodyPartFormatter>());
+
+    insert(u"message/rfc822"_s, std::make_unique<MessageRfc822BodyPartFormatter>());
+
+    insert(u"multipart/alternative"_s, std::make_unique<MultiPartAlternativeBodyPartFormatter>());
+    insert(u"multipart/encrypted"_s, std::make_unique<MultiPartEncryptedBodyPartFormatter>());
+    insert(u"multipart/signed"_s, std::make_unique<MultiPartSignedBodyPartFormatter>());
+    insert(u"multipart/mixed"_s, std::make_unique<MultiPartMixedBodyPartFormatter>());
 }
diff --git a/mimetreeparser/src/bodypartformatterfactory.cpp b/mimetreeparser/src/bodypartformatterfactory.cpp
index 9627e8b5a..23d5548ec 100644
--- a/mimetreeparser/src/bodypartformatterfactory.cpp
+++ b/mimetreeparser/src/bodypartformatterfactory.cpp
@@ -29,15 +29,7 @@ BodyPartFormatterFactoryPrivate::BodyPartFormatterFactoryPrivate(BodyPartFormatt
 {
 }
 
-BodyPartFormatterFactoryPrivate::~BodyPartFormatterFactoryPrivate()
-{
-    QHashIterator<QString, std::vector<FormatterInfo>> i(registry);
-    while (i.hasNext()) {
-        i.next();
-        auto formatterInfo = i.value();
-        formatterInfo.erase(formatterInfo.begin(), formatterInfo.end());
-    }
-}
+BodyPartFormatterFactoryPrivate::~BodyPartFormatterFactoryPrivate() = default;
 
 void BodyPartFormatterFactoryPrivate::setup()
 {
@@ -48,7 +40,7 @@ void BodyPartFormatterFactoryPrivate::setup()
     assert(!registry.empty());
 }
 
-void BodyPartFormatterFactoryPrivate::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
+void BodyPartFormatterFactoryPrivate::insert(const QString &mimeType, std::unique_ptr<const Interface::BodyPartFormatter> &&formatter, int priority)
 {
     if (mimeType.isEmpty() || !formatter) {
         return;
@@ -57,24 +49,24 @@ void BodyPartFormatterFactoryPrivate::insert(const QString &mimeType, const Inte
     QMimeDatabase db;
     const auto mt = db.mimeTypeForName(mimeType);
     FormatterInfo info;
-    info.formatter = formatter;
+    info.formatter = std::move(formatter);
     info.priority = priority;
 
     auto &v = registry[mt.isValid() ? mt.name() : mimeType];
-    v.push_back(info);
-    std::stable_sort(v.begin(), v.end(), [](FormatterInfo lhs, FormatterInfo rhs) {
+    v.push_back(std::move(info));
+    std::stable_sort(v.begin(), v.end(), [](const FormatterInfo &lhs, const FormatterInfo &rhs) {
         return lhs.priority > rhs.priority;
     });
 }
 
 void BodyPartFormatterFactoryPrivate::appendFormattersForType(const QString &mimeType, QList<const Interface::BodyPartFormatter *> &formatters)
 {
-    const auto it = registry.constFind(mimeType);
-    if (it == registry.constEnd()) {
+    const auto it = registry.find(mimeType);
+    if (it == registry.end()) {
         return;
     }
-    for (const auto &f : it.value()) {
-        formatters.push_back(f.formatter);
+    for (const auto &f : (*it).second) {
+        formatters.push_back(f.formatter.get());
     }
 }
 
@@ -91,9 +83,9 @@ BodyPartFormatterFactory *BodyPartFormatterFactory::instance()
     return &s_instance;
 }
 
-void BodyPartFormatterFactory::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
+void BodyPartFormatterFactory::insert(const QString &mimeType, std::unique_ptr<const Interface::BodyPartFormatter> &&formatter, int priority)
 {
-    d->insert(mimeType.toLower(), formatter, priority);
+    d->insert(mimeType.toLower(), std::move(formatter), priority);
 }
 
 QList<const Interface::BodyPartFormatter *> BodyPartFormatterFactory::formattersForType(const QString &mimeType) const
@@ -151,7 +143,7 @@ void BodyPartFormatterFactory::loadPlugins()
             continue;
         }
 
-        const MimeTreeParser::Interface::BodyPartFormatter *bfp = nullptr;
+        std::unique_ptr<const MimeTreeParser::Interface::BodyPartFormatter> bfp;
         for (int i = 0; (bfp = plugin->bodyPartFormatter(i)) && i < formatterData.size(); ++i) {
             const auto metaData = formatterData.at(i).toObject();
             const auto mimetype = metaData.value(QLatin1StringView("mimetype")).toString();
@@ -162,7 +154,7 @@ void BodyPartFormatterFactory::loadPlugins()
             // priority should always be higher than the built-in ones, otherwise what's the point?
             const auto priority = metaData.value(QLatin1StringView("priority")).toInt() + 100;
             qCDebug(MIMETREEPARSER_LOG) << "plugin for " << mimetype << priority;
-            insert(mimetype, bfp, priority);
+            insert(mimetype, std::move(bfp), priority);
         }
     }
 }
diff --git a/mimetreeparser/src/bodypartformatterfactory.h b/mimetreeparser/src/bodypartformatterfactory.h
index 8bb313461..94c7df891 100644
--- a/mimetreeparser/src/bodypartformatterfactory.h
+++ b/mimetreeparser/src/bodypartformatterfactory.h
@@ -65,7 +65,7 @@ protected:
      * \param formatter The BodyPartFormatter to insert.
      * \param priority The priority of the formatter.
      */
-    void insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority);
+    void insert(const QString &mimeType, std::unique_ptr<const Interface::BodyPartFormatter> &&formatter, int priority);
     /*!
      */
     virtual void loadPlugins();
diff --git a/mimetreeparser/src/bodypartformatterfactory_p.h b/mimetreeparser/src/bodypartformatterfactory_p.h
index e634f4ac1..7f7215a0f 100644
--- a/mimetreeparser/src/bodypartformatterfactory_p.h
+++ b/mimetreeparser/src/bodypartformatterfactory_p.h
@@ -10,13 +10,18 @@
 
 #pragma once
 
-#include <QHash>
+#include <unordered_map>
 #include <vector>
 
 namespace MimeTreeParser
 {
 class BodyPartFormatterFactory;
 
+namespace Interface
+{
+class BodyPartFormatter;
+}
+
 class BodyPartFormatterFactoryPrivate
 {
 public:
@@ -25,14 +30,14 @@ public:
 
     void setup();
     void messageviewer_create_builtin_bodypart_formatters(); // defined in bodypartformatter.cpp
-    void insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority = 0);
+    void insert(const QString &mimeType, std::unique_ptr<const Interface::BodyPartFormatter> &&formatter, int priority = 0);
     void appendFormattersForType(const QString &mimeType, QList<const Interface::BodyPartFormatter *> &formatters);
 
     BodyPartFormatterFactory *const q;
     struct FormatterInfo {
-        const Interface::BodyPartFormatter *formatter = nullptr;
+        std::unique_ptr<const Interface::BodyPartFormatter> formatter;
         int priority = 0;
     };
-    QHash<QString, std::vector<FormatterInfo>> registry;
+    std::unordered_map<QString, std::vector<FormatterInfo>> registry;
 };
 }
diff --git a/mimetreeparser/src/interfaces/bodypartformatter.h b/mimetreeparser/src/interfaces/bodypartformatter.h
index 97cb5c258..5b4f43a95 100644
--- a/mimetreeparser/src/interfaces/bodypartformatter.h
+++ b/mimetreeparser/src/interfaces/bodypartformatter.h
@@ -66,9 +66,8 @@ public:
      */
     virtual ~BodyPartFormatterPlugin();
 
-    /*!
-     */
-    virtual const BodyPartFormatter *bodyPartFormatter(int idx) const = 0;
+    /*! Return a formatter instance for the Nth entry in the plugin metadata. */
+    [[nodiscard]] virtual std::unique_ptr<const BodyPartFormatter> bodyPartFormatter(int idx) const = 0;
 };
 } // namespace Interface
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.