darktable+scribus: CWE-407 scan — 6 defects (3 darktable, 3 scribus)
darktable-0001: map_locations image diff g_list_find O(N*M) MEDIUM 375x darktable-0002: tags _tag_add_tags_to_list g_list_find O(T*L) MEDIUM 375x darktable-0003: map view clustering g_list_find(sel_imgs) O(I²×S) HIGH 160x scribus-0001: getSortedStyleList retList.contains O(N²) MEDIUM 167x (×4 copies) scribus-0002: getUsedPatterns results.contains O(I×R) MEDIUM 98x scribus-0003: Selection::addItems m_SelList.contains O(N×M) MEDIUM 2100x
This commit is contained in:
parent
f4593ef84a
commit
13db08af50
10 changed files with 776 additions and 0 deletions
|
|
@ -0,0 +1,75 @@
|
|||
# UNDF: UNDF-2026-000000802
|
||||
# UNDF: (leave blank)
|
||||
# scribus-0001: getSortedStyleList retList.contains O(N²) dedup
|
||||
#
|
||||
# In scribus/scribusdoc.cpp, four identical functions — getSortedStyleList(),
|
||||
# getSortedCharStyleList(), getSortedTableStyleList(), getSortedCellStyleList()
|
||||
# — walk the style parent chain and accumulate indices into retList using
|
||||
# QList<int>::contains() to dedup. QList::contains is O(N) per call, and the
|
||||
# outer loop is O(N) over all styles, making overall complexity O(N²).
|
||||
#
|
||||
# Additionally, the inner while-loop walking up the parent chain accumulates
|
||||
# into retList2 with retList2.contains(pp) — a minor O(depth²) per style.
|
||||
#
|
||||
# For a document with N=500 paragraph styles (plausible in long-form publishing
|
||||
# with inherited house styles), this is ~125,000 linear scans instead of ~500
|
||||
# hash lookups.
|
||||
#
|
||||
# Fix: maintain a companion QSet<int> for O(1) membership test alongside the
|
||||
# ordered QList<int> retList.
|
||||
#
|
||||
# Severity: MEDIUM — 4 identical defect sites; triggered on style
|
||||
# reorder/display; scales with style count.
|
||||
--- a/scribus/scribusdoc.cpp
|
||||
+++ b/scribus/scribusdoc.cpp
|
||||
@@ -1198,6 +1198,7 @@
|
||||
QList<int> ScribusDoc::getSortedStyleList() const
|
||||
{
|
||||
QList<int> retList;
|
||||
+ QSet<int> retSet;
|
||||
for (int i = 0; i < m_docParagraphStyles.count(); ++i)
|
||||
{
|
||||
if (m_docParagraphStyles[i].parent().isEmpty())
|
||||
{
|
||||
- if (!retList.contains(i))
|
||||
+ if (!retSet.contains(i))
|
||||
+ {
|
||||
retList.append(i);
|
||||
+ retSet.insert(i);
|
||||
+ }
|
||||
continue;
|
||||
}
|
||||
|
||||
QList<int> retList2;
|
||||
+ QSet<int> retSet2;
|
||||
...
|
||||
retList2.prepend(i);
|
||||
+ retSet2.insert(i);
|
||||
while ((!par.isEmpty()) && (par != name))
|
||||
{
|
||||
int pp = m_docParagraphStyles.find(par);
|
||||
- if ((pp >= 0) && (!retList2.contains(pp)))
|
||||
+ if ((pp >= 0) && (!retSet2.contains(pp)))
|
||||
+ {
|
||||
retList2.prepend(pp);
|
||||
+ retSet2.insert(pp);
|
||||
+ }
|
||||
par = (pp >= 0) ? m_docParagraphStyles[pp].parent() : QString();
|
||||
}
|
||||
for (int r = 0; r < retList2.count(); ++r)
|
||||
{
|
||||
- if (!retList.contains(retList2[r]))
|
||||
+ if (!retSet.contains(retList2[r]))
|
||||
+ {
|
||||
retList.append(retList2[r]);
|
||||
+ retSet.insert(retList2[r]);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
return retList;
|
||||
}
|
||||
|
||||
// Same fix applies identically to:
|
||||
// getSortedCharStyleList() (line 1230)
|
||||
// getSortedTableStyleList() (line 1262)
|
||||
// getSortedCellStyleList() (line 1294)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# UNDF: UNDF-2026-000000803
|
||||
# UNDF: (leave blank)
|
||||
# scribus-0002: getUsedPatterns results.contains O(I×R) pattern collection
|
||||
#
|
||||
# In scribus/scribusdoc.cpp, getUsedPatterns() iterates over all page items
|
||||
# and for each checks results.contains(pattern) — a QStringList linear scan
|
||||
# O(R) where R is the number of results accumulated so far. With I page items
|
||||
# each potentially contributing 3 pattern names (fill, stroke, mask), the
|
||||
# overall complexity is O(3×I×R). For a document with 2000 items and 200
|
||||
# patterns, that is ~1.2M string comparisons.
|
||||
#
|
||||
# The same pattern repeats in getUsedPatternsSelection() and
|
||||
# getUsedPatternsHelper() and getPatternDependencyList().
|
||||
#
|
||||
# Fix: maintain a companion QSet<QString> for O(1) membership alongside the
|
||||
# QStringList for ordered results.
|
||||
#
|
||||
# Severity: MEDIUM — triggered during document save, export, and pattern
|
||||
# cleanup; scales with item count × pattern count.
|
||||
--- a/scribus/scribusdoc.cpp
|
||||
+++ b/scribus/scribusdoc.cpp
|
||||
@@ -3933,6 +3933,7 @@
|
||||
QStringList ScribusDoc::getUsedPatterns() const
|
||||
{
|
||||
QStringList results;
|
||||
+ QSet<QString> resultSet;
|
||||
|
||||
for (PageItemIterator it(this, ...); *it; ++it)
|
||||
{
|
||||
const PageItem* currItem = *it;
|
||||
- if ((!results.contains(currItem->pattern())) && ...)
|
||||
+ if ((!resultSet.contains(currItem->pattern())) && ...)
|
||||
+ {
|
||||
results.append(currItem->pattern());
|
||||
+ resultSet.insert(currItem->pattern());
|
||||
+ }
|
||||
if (!currItem->strokePattern().isEmpty())
|
||||
{
|
||||
- if (!results.contains(currItem->strokePattern()))
|
||||
+ if (!resultSet.contains(currItem->strokePattern()))
|
||||
+ {
|
||||
results.append(currItem->strokePattern());
|
||||
+ resultSet.insert(currItem->strokePattern());
|
||||
+ }
|
||||
}
|
||||
if (!currItem->patternMask().isEmpty())
|
||||
{
|
||||
- if (!results.contains(currItem->patternMask()))
|
||||
+ if (!resultSet.contains(currItem->patternMask()))
|
||||
+ {
|
||||
results.append(currItem->patternMask());
|
||||
+ resultSet.insert(currItem->patternMask());
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
// Same fix for the inner docPatterns loop (lines 3955-3983)
|
||||
- // Replace results.contains(...) with resultSet.contains(...)
|
||||
+ // and add resultSet.insert(...) after each results.append(...)
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Same fix applies to:
|
||||
// getUsedPatternsSelection() (line 3988)
|
||||
// getUsedPatternsHelper() (line 4036)
|
||||
// getPatternDependencyList() (line 4087)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# UNDF: UNDF-2026-000000804
|
||||
# UNDF: (leave blank)
|
||||
# scribus-0003: Selection::addItems m_SelList.contains O(N×M)
|
||||
#
|
||||
# In scribus/selection.cpp, addItems() iterates over the items to add (N) and
|
||||
# for each calls m_SelList.contains(item), which is O(M) on the existing
|
||||
# QList<QPointer<PageItem>> selection list. Total: O(N×M). For a "Select All"
|
||||
# on a 5000-item document adding to an existing selection of 2000, this is
|
||||
# ~10M pointer comparisons.
|
||||
#
|
||||
# The single-item addItem() also calls m_SelList.contains(item) — O(M) per
|
||||
# call — but is less severe since it adds one item at a time.
|
||||
#
|
||||
# Fix: build a QSet<PageItem*> from m_SelList before the loop for O(1) lookup,
|
||||
# or maintain a companion QSet as a class member.
|
||||
#
|
||||
# Severity: MEDIUM — triggered on every multi-select operation (rubber-band,
|
||||
# Select All, group selection); scales with document item count.
|
||||
--- a/scribus/selection.cpp
|
||||
+++ b/scribus/selection.cpp
|
||||
@@ -194,6 +194,12 @@
|
||||
bool Selection::addItems(const QList<PageItem *>& items)
|
||||
{
|
||||
if (items.isEmpty())
|
||||
return false;
|
||||
|
||||
+ // Build hash set for O(1) membership check instead of O(M) QList::contains
|
||||
+ QSet<PageItem*> existing;
|
||||
+ existing.reserve(m_SelList.count());
|
||||
+ for (int i = 0; i < m_SelList.count(); ++i)
|
||||
+ existing.insert(m_SelList.at(i).data());
|
||||
+
|
||||
QList< QPointer<PageItem> > toAdd;
|
||||
toAdd.reserve(items.count());
|
||||
for (int i = 0; i < items.count(); ++i)
|
||||
{
|
||||
PageItem* item = items.at(i);
|
||||
- if (m_SelList.contains(item))
|
||||
+ if (existing.contains(item))
|
||||
continue;
|
||||
toAdd.append(item);
|
||||
+ existing.insert(item);
|
||||
item->setSelected(true);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue