Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/svl/source/items/globalpool.cxx
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <svl/itemset.hxx>
21
#include <svl/itempool.hxx>
22
#include <svl/setitem.hxx>
23
#include <sal/log.hxx>
24
25
static bool g_bDisableItemInstanceManager(getenv("SVL_DISABLE_ITEM_INSTANCE_MANAGER"));
26
static bool g_bShareImmediately(getenv("SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY"));
27
32.3M
#define NUMBER_OF_UNSHARED_INSTANCES (50)
28
29
#ifdef DBG_UTIL
30
31
// <WhichID, <number of entries, typeid_name>>
32
typedef std::unordered_map<sal_uInt16, std::pair<sal_uInt32, const char*>> HightestUsage;
33
static HightestUsage aHightestUsage;
34
35
static void addUsage(const SfxPoolItem& rCandidate)
36
{
37
    HightestUsage::iterator aHit(aHightestUsage.find(rCandidate.Which()));
38
    if (aHit == aHightestUsage.end())
39
    {
40
        aHightestUsage.insert({ rCandidate.Which(), { 1, typeid(rCandidate).name() } });
41
        return;
42
    }
43
    aHit->second.first++;
44
}
45
46
void listSfxPoolItemsWithHighestUsage(sal_uInt16 nNum)
47
{
48
    struct sorted
49
    {
50
        sal_uInt16 nWhich;
51
        sal_uInt32 nUsage;
52
        const char* pType;
53
        sorted(sal_uInt16 _nWhich, sal_uInt32 _nUsage, const char* _pType)
54
            : nWhich(_nWhich)
55
            , nUsage(_nUsage)
56
            , pType(_pType)
57
        {
58
        }
59
        bool operator<(const sorted& rDesc) const { return nUsage > rDesc.nUsage; }
60
    };
61
    std::vector<sorted> aSorted;
62
    aSorted.reserve(aHightestUsage.size());
63
    for (const auto& rEntry : aHightestUsage)
64
        aSorted.emplace_back(rEntry.first, rEntry.second.first, rEntry.second.second);
65
    std::sort(aSorted.begin(), aSorted.end());
66
    sal_uInt16 a(0);
67
    SAL_INFO("svl.items",
68
             "ITEM: List of the " << nNum << " SfxPoolItems with highest non-RefCounted usages:");
69
    for (const auto& rEntry : aSorted)
70
    {
71
        SAL_INFO("svl.items", "  ITEM(" << a << "): Which: " << rEntry.nWhich
72
                                        << " Uses: " << rEntry.nUsage << " Type: " << rEntry.pType);
73
        if (++a >= nNum)
74
            break;
75
    }
76
}
77
78
#endif
79
80
void DefaultItemInstanceManager::add(const SfxPoolItem& rItem)
81
5.73M
{
82
5.73M
    maRegistered[rItem.Which()].insert(&rItem);
83
5.73M
}
84
85
void DefaultItemInstanceManager::remove(const SfxPoolItem& rItem)
86
5.74M
{
87
5.74M
    maRegistered[rItem.Which()].erase(&rItem);
88
5.74M
}
89
90
// Class that implements global Item sharing. It uses rtti to
91
// associate every Item-derivation with a possible incarnation
92
// of a DefaultItemInstanceManager. This is the default, it will
93
// give direct implementations at the Items that overload
94
// getItemInstanceManager() preference. These are expected to
95
// return static instances of a derived implementation of a
96
// ItemInstanceManager.
97
// All in all there are now the following possibilities to support
98
// this for individual Item derivations:
99
// (1) Do nothing:
100
//     In that case, if the Item is shareable, the new mechanism
101
//     will kick in: It will start sharing the Item globally,
102
//     but not immediately: After a defined amount of allowed
103
//     non-shared occurrences (look for NUMBER_OF_UNSHARED_INSTANCES)
104
//     an instance of the default ItemInstanceManager, a
105
//     DefaultItemInstanceManager, will be incarnated and used.
106
//     NOTE: Mixing shared/unshared instances is not a problem (we
107
//     might even implement a kind of 're-hash' when this kicks in,
108
//     but is not really needed).
109
// (2) Overload getItemInstanceManager for SfxPoolItem in a class
110
//     derived from SfxPoolItem and...
111
// (2a) Return a static incarnation of DefaultItemInstanceManager to
112
//      immediately start global sharing of that Item derivation.
113
// (2b) Implement and return your own implementation and static
114
//      incarnation of ItemInstanceManager to do something better/
115
//      faster that the default implementation can do. Example:
116
//      SvxFontItem, uses hashing now.
117
// There are two supported ENVVARs to use:
118
// (a) SVL_DISABLE_ITEM_INSTANCE_MANAGER:
119
//     This disables the mechanism of global Item sharing completely.
120
//     This can be used to test/check speed/memory needs compared with
121
//     using it, but also may come in handy to check if evtl. errors/
122
//     regressions have to do with it.
123
// (b) SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY:
124
//     This internally forces the NUMBER_OF_UNSHARED_INSTANCES to be
125
//     ignored and start sharing ALL Item derivations instantly.
126
class InstanceManagerHelper
127
{
128
    typedef std::unordered_map<SfxItemType, std::pair<sal_uInt16, ItemInstanceManager*>>
129
        managerTypeMap;
130
    managerTypeMap maManagerPerType;
131
132
public:
133
108
    InstanceManagerHelper() {}
134
    ~InstanceManagerHelper()
135
0
    {
136
0
        for (auto& rCandidate : maManagerPerType)
137
0
            if (nullptr != rCandidate.second.second)
138
0
                delete rCandidate.second.second;
139
0
    }
140
141
    ItemInstanceManager* getOrCreateItemInstanceManager(const SfxPoolItem& rItem)
142
167M
    {
143
        // deactivated?
144
167M
        if (g_bDisableItemInstanceManager)
145
0
            return nullptr;
146
147
        // Item cannot be shared?
148
167M
        if (!rItem.isShareable())
149
12.1M
            return nullptr;
150
151
        // Prefer getting an ItemInstanceManager directly from
152
        // the Item: These are the extra implemented (and thus
153
        // hopefully fastest) incarnations
154
154M
        ItemInstanceManager* pManager(rItem.getItemInstanceManager());
155
156
        // Check for correct SfxItemType, there may be derivations of that class.
157
        // Note that Managers from the Items are *not* added to local list,
158
        // they are expected to be static instances at the Items for fastest access
159
154M
        if (nullptr != pManager && pManager->ItemType() == rItem.ItemType())
160
57.5M
            return pManager;
161
162
        // check local memory for existing entry
163
97.3M
        managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType()));
164
165
        // no instance yet
166
97.3M
        if (aHit == maManagerPerType.end())
167
1.21k
        {
168
            // create a default one to start usage-counting
169
1.21k
            if (g_bShareImmediately)
170
0
            {
171
                // create, insert locally and immediately start sharing
172
0
                ItemInstanceManager* pNew;
173
0
                if (rItem.supportsHashCode())
174
0
                    pNew = new HashedItemInstanceManager(rItem.ItemType());
175
0
                else
176
0
                    pNew = new DefaultItemInstanceManager(rItem.ItemType());
177
0
                maManagerPerType.insert({ rItem.ItemType(), std::make_pair(0, pNew) });
178
0
                return pNew;
179
0
            }
180
181
            // start countdown from NUMBER_OF_UNSHARED_INSTANCES until zero is reached
182
1.21k
            maManagerPerType.insert(
183
1.21k
                { rItem.ItemType(), std::make_pair(NUMBER_OF_UNSHARED_INSTANCES, nullptr) });
184
1.21k
            return nullptr;
185
1.21k
        }
186
187
        // if there is already an ItemInstanceManager incarnated, return it
188
97.2M
        if (nullptr != aHit->second.second)
189
64.9M
            return aHit->second.second;
190
191
32.3M
        if (aHit->second.first > 0)
192
32.3M
        {
193
            // still not the needed number of hits, countdown & return nullptr
194
32.3M
            aHit->second.first--;
195
32.3M
            return nullptr;
196
32.3M
        }
197
198
        // here the countdown is zero and there is not yet a ItemInstanceManager
199
        // incarnated. Do so, register and return it
200
398
        assert(nullptr == aHit->second.second);
201
398
        ItemInstanceManager* pNew;
202
398
        if (rItem.supportsHashCode())
203
237
            pNew = new HashedItemInstanceManager(rItem.ItemType());
204
161
        else
205
161
            pNew = new DefaultItemInstanceManager(rItem.ItemType());
206
398
        aHit->second.second = pNew;
207
208
398
        return pNew;
209
32.3M
    }
210
211
    ItemInstanceManager* getExistingItemInstanceManager(const SfxPoolItem& rItem)
212
61.6M
    {
213
        // deactivated?
214
61.6M
        if (g_bDisableItemInstanceManager)
215
0
            return nullptr;
216
217
        // Item cannot be shared?
218
61.6M
        if (!rItem.isShareable())
219
8.38M
            return nullptr;
220
221
        // Prefer getting an ItemInstanceManager directly from
222
        // the Item: These are the extra implemented (and thus
223
        // hopefully fastest) incarnations
224
53.2M
        ItemInstanceManager* pManager(rItem.getItemInstanceManager());
225
226
        // Check for correct SfxItemType, there may be derivations of that class.
227
        // Note that Managers from the Items are *not* added to local list,
228
        // they are expected to be static instances at the Items
229
53.2M
        if (nullptr != pManager && pManager->ItemType() == rItem.ItemType())
230
10.8M
            return pManager;
231
232
        // check local memory for existing entry
233
42.3M
        managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType()));
234
235
42.3M
        if (aHit == maManagerPerType.end())
236
            // no instance yet, return nullptr
237
3.73M
            return nullptr;
238
239
        // if there is already a ItemInstanceManager incarnated, return it
240
38.6M
        if (nullptr != aHit->second.second)
241
6.27M
            return aHit->second.second;
242
243
        // count-up needed number of hits again if item is released
244
32.3M
        if (aHit->second.first < NUMBER_OF_UNSHARED_INSTANCES)
245
32.3M
            aHit->second.first++;
246
247
32.3M
        return nullptr;
248
38.6M
    }
249
};
250
251
// the single static instance that takes over that global Item sharing
252
static InstanceManagerHelper aInstanceManagerHelper;
253
254
SfxPoolItem const* implCreateItemEntry(const SfxItemPool& rPool, SfxPoolItem const* pSource,
255
                                       bool bPassingOwnership)
256
1.23G
{
257
1.23G
    if (nullptr == pSource)
258
        // SfxItemState::UNKNOWN aka current default (nullptr)
259
        // just use/return nullptr
260
0
        return nullptr;
261
262
1.23G
    if (pSource->isStaticDefault())
263
        // static default Items can just be used without RefCounting
264
        // NOTE: This now includes IsInvalidItem/IsDisabledItem
265
103M
        return pSource;
266
267
1.12G
    if (0 == pSource->Which())
268
0
    {
269
        // There should be no Items with 0 == WhichID, but there are some
270
        // constructed for dialog return values AKA result (look for SetReturnValue)
271
        // these need to be cloned (currently...)
272
0
        if (bPassingOwnership)
273
0
            return pSource;
274
0
        return pSource->Clone();
275
0
    }
276
277
1.12G
    if (pSource->isDynamicDefault() && rPool.GetPoolDefaultItem(pSource->Which()) == pSource)
278
        // dynamic defaults are not allowed to 'leave' the Pool they are
279
        // defined for. We can check by comparing the PoolDefault (the
280
        // PoolDefaultItem) to pSource by ptr compare (instance). When
281
        // same Item we can use without RefCount. Else it will be cloned
282
        // below the standard way.
283
3.31M
        return pSource;
284
285
#ifdef DBG_UTIL
286
    // remember WhichID due to being able to assert Clone() error(s)
287
    const sal_uInt16 nWhich(pSource->Which());
288
#endif
289
290
1.12G
    if (SfxItemPool::IsSlot(pSource->Which()))
291
15.4M
    {
292
        // SlotItems were always cloned in original (even when bPassingOwnership),
293
        // so do that here, too (but without bPassingOwnership).
294
        // They do not need to be registered at pool (actually impossible, pools
295
        // do not have entries for SlotItems) so handle here early
296
15.4M
        if (!bPassingOwnership)
297
15.3M
        {
298
15.3M
            pSource = pSource->Clone(rPool.GetMasterPool());
299
            // ARGH! Found out that *some* ::Clone implementations fail to also clone the
300
            // WhichID set at the original Item, e.g. SfxFrameItem. Assert, this is an error
301
#ifdef DBG_UTIL
302
            assert(pSource->Which() == nWhich
303
                   && "ITEM: Clone of Item did NOT copy/set WhichID (!)");
304
#endif
305
15.3M
        }
306
307
15.4M
        return pSource;
308
15.4M
    }
309
310
    // get the pool with which ItemSets have to work, plus get the
311
    // pool at which the WhichID is defined, so calls to it do not
312
    // have to do this repeatedly
313
1.11G
    SfxItemPool* pMasterPool(rPool.GetMasterPool());
314
1.11G
    assert(nullptr != pMasterPool);
315
316
    // The Item itself is shareable when it is used/added at an instance
317
    // that RefCounts the Item, SfxItemPool or SfxPoolItemHolder. Try
318
    // to share items that are already shared
319
1.11G
    if (pSource->GetRefCount() > 0)
320
949M
    {
321
949M
        if (pSource->isShareable())
322
943M
        {
323
            // SfxSetItems cannot be shared if they are in/use another pool
324
943M
            if (!pSource->isSetItem()
325
943M
                || static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() == pMasterPool)
326
943M
            {
327
                // If we get here we can share the Item
328
943M
                pSource->AddRef();
329
943M
                return pSource;
330
943M
            }
331
943M
        }
332
949M
    }
333
334
    // try to get an ItemInstanceManager for global Item instance sharing
335
167M
    ItemInstanceManager* pManager(aInstanceManagerHelper.getOrCreateItemInstanceManager(*pSource));
336
337
    // check if we can globally share the Item using an ItemInstanceManager
338
167M
    if (pManager)
339
122M
    {
340
122M
        const SfxPoolItem* pAlternative(pManager->find(*pSource));
341
122M
        if (pAlternative)
342
105M
        {
343
            // Here we do *not* need to check if it is an SfxSetItem
344
            // and cannot be shared if they are in/use another pool:
345
            // The SfxItemSet::operator== will check for SfxItemPools
346
            // being equal, thus when found in global share the Pool
347
            // cannot be equal
348
349
            // need to delete evtl. handed over ownership change Item
350
105M
            if (bPassingOwnership)
351
13.7M
                delete pSource;
352
353
            // If we get here we can share the Item
354
105M
            pAlternative->AddRef();
355
105M
            return pAlternative;
356
105M
        }
357
122M
    }
358
359
    // check if the handed over and to be directly used item is a
360
    // SfxSetItem, that would make it pool-dependent. It then must have
361
    // the same target-pool, ensure that by the cost of cloning it
362
    // (should not happen)
363
61.6M
    if (bPassingOwnership && pSource->isSetItem()
364
61.6M
        && static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() != pMasterPool)
365
0
    {
366
0
        const SfxPoolItem* pOld(pSource);
367
0
        pSource = pSource->Clone(pMasterPool);
368
#ifdef DBG_UTIL
369
        assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)");
370
#endif
371
0
        delete pOld;
372
0
    }
373
374
#ifdef DBG_UTIL
375
    // create statistics for listSfxPoolItemsWithHighestUsage
376
    addUsage(*pSource);
377
#endif
378
379
    // when we reach this line we know that we have to add/create a new item. If
380
    // bPassingOwnership is given just use the item, else clone it
381
61.6M
    if (!bPassingOwnership)
382
57.0M
    {
383
57.0M
        auto pPreviousSource = pSource;
384
57.0M
        pSource = pSource->Clone(pMasterPool);
385
#ifdef DBG_UTIL
386
        assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)");
387
#endif
388
57.0M
        SAL_WARN_IF(typeid(*pPreviousSource) != typeid(*pSource), "svl",
389
57.0M
                    "wrong item from Clone(), expected " << typeid(*pPreviousSource).name()
390
57.0M
                                                         << " but got " << typeid(*pSource).name());
391
57.0M
        assert(typeid(*pPreviousSource) == typeid(*pSource) && "wrong item from Clone()");
392
57.0M
    }
393
394
    // increase RefCnt 0->1
395
61.6M
    pSource->AddRef();
396
397
    // check if we should register this Item for the global
398
    // ItemInstanceManager mechanism (only for shareable Items)
399
61.6M
    if (nullptr != pManager)
400
17.1M
        pManager->add(*pSource);
401
402
61.6M
    return pSource;
403
61.6M
}
404
405
void implCleanupItemEntry(const SfxPoolItem* pSource)
406
1.91G
{
407
1.91G
    if (nullptr == pSource)
408
        // no entry, done
409
683M
        return;
410
411
1.23G
    if (pSource->isStaticDefault())
412
        // static default Items can just be used without RefCounting
413
        // NOTE: This now includes IsInvalidItem/IsDisabledItem
414
103M
        return;
415
416
1.12G
    if (0 == pSource->Which())
417
0
    {
418
        // There should be no Items with 0 == WhichID, but there are some
419
        // constructed for dialog return values AKA result (look for SetReturnValue)
420
        // and need to be deleted
421
0
        delete pSource;
422
0
        return;
423
0
    }
424
425
1.12G
    if (pSource->isDynamicDefault())
426
        // dynamic default Items can only be used without RefCounting
427
        // when same pool. this is already checked at implCreateItemEntry,
428
        // so it would have been cloned (and would no longer have this
429
        // flag). So we can just return here
430
3.31M
        return;
431
432
1.12G
    if (SfxItemPool::IsSlot(pSource->Which()))
433
15.4M
    {
434
        // SlotItems are cloned, so delete
435
15.4M
        delete pSource;
436
15.4M
        return;
437
15.4M
    }
438
439
1.11G
    if (1 < pSource->GetRefCount())
440
1.04G
    {
441
        // Still multiple references present, so just alter the RefCount
442
1.04G
        pSource->ReleaseRef();
443
1.04G
        return;
444
1.04G
    }
445
446
    // try to get an ItemInstanceManager for global Item instance sharing
447
61.6M
    ItemInstanceManager* pManager(aInstanceManagerHelper.getExistingItemInstanceManager(*pSource));
448
449
    // check if we should/can remove this Item from the global
450
    // ItemInstanceManager mechanism
451
61.6M
    if (nullptr != pManager)
452
17.1M
        pManager->remove(*pSource);
453
454
    // decrease RefCnt before deleting (destructor asserts for it and that's
455
    // good to find other errors)
456
61.6M
    pSource->ReleaseRef();
457
458
    // delete Item
459
61.6M
    delete pSource;
460
61.6M
}
461
462
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */