Skip to content

Commit 4cd420d

Browse files
committed
fix(masters): harden the two parallel sldLayoutId allocators (Cato audit)
Cato cross-vendor audit flagged defect-instance vs defect-class scoping: the id-pool fix landed only in CT_SlideLayoutIdList._next_id (the reproduced path). The two parallel allocators used by the cross-package append_from / SF7 path were correct only incidentally: - _add_sldLayoutId_to_master: max(used+[2147483647])+1 — right floor by luck, no uint32 ceiling, no exhaustion handling. - _renumber_sldLayoutIds: right floor, no ceiling guard in its loop. Both now: floor at _OOXML_LAYOUT_ID_FLOOR (explicit, not magic 2147483647), new shared _UINT32_MAX ceiling, scan-fallback on exhaustion — matching CT_SlideLayoutIdList._next_id. Closes the defect class, not just the instance the failing screenshot exposed. Trinity still green: pytest 3708, ruff clean, behave 0 failed; append_from/port/layout paths (304 tests) green. Refs #19
1 parent c68c890 commit 4cd420d

1 file changed

Lines changed: 20 additions & 1 deletion

File tree

src/pptx/parts/slide.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ def duplicate(self) -> SlidePart:
330330
_P_NS = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
331331
_P14_NS = "{http://schemas.microsoft.com/office/powerpoint/2010/main}"
332332
_OOXML_LAYOUT_ID_FLOOR = 2147483648 # uint32 floor per ECMA-376 sec 19.2.1.27.
333+
_UINT32_MAX = 4294967295 # ceiling shared by all slide-master/layout id allocators
333334

334335
# Reltypes filtered out during slide duplication. NOTES_SLIDE is wired
335336
# explicitly by |Slides.duplicate| so the new notes-slide back-references
@@ -903,6 +904,13 @@ def _renumber_sldLayoutIds(element, used_ids: set[int]) -> None:
903904
return
904905
next_id = max(used_ids | {_OOXML_LAYOUT_ID_FLOOR - 1}) + 1
905906
for sli in sldLayoutIdLst.findall(f"{_P_NS}sldLayoutId"):
907+
if next_id > _UINT32_MAX:
908+
next_id = next(
909+
(n for n in range(_OOXML_LAYOUT_ID_FLOOR, _UINT32_MAX + 1) if n not in used_ids),
910+
None,
911+
)
912+
if next_id is None:
913+
raise ValueError("slide-layout id pool exhausted")
906914
sli.set("id", str(next_id))
907915
used_ids.add(next_id)
908916
next_id += 1
@@ -964,7 +972,18 @@ def _add_sldLayoutId_to_master(master_part, rId: str) -> None:
964972
if raw is not None:
965973
with contextlib.suppress(ValueError):
966974
used_ids.append(int(raw))
967-
next_id = max(used_ids + [2147483647]) + 1
975+
# ---high-range allocation consistent with CT_SlideLayoutIdList._next_id:
976+
# floor at _OOXML_LAYOUT_ID_FLOOR so the id is disjoint from the low
977+
# p:sldId pool; ceiling-guarded at uint32 max with a scan fallback.---
978+
next_id = max(used_ids + [_OOXML_LAYOUT_ID_FLOOR - 1]) + 1
979+
if next_id > _UINT32_MAX:
980+
seen = set(used_ids)
981+
next_id = next(
982+
(n for n in range(_OOXML_LAYOUT_ID_FLOOR, _UINT32_MAX + 1) if n not in seen),
983+
None,
984+
)
985+
if next_id is None:
986+
raise ValueError("slide-layout id pool exhausted")
968987
sldLayoutId = sldLayoutIdLst._add_sldLayoutId()
969988
sldLayoutId.rId = rId
970989
sldLayoutId.set("id", str(next_id))

0 commit comments

Comments
 (0)