Skip to content

Commit c6e7fce

Browse files
committed
atom: force a fresh instance instead of using portage's intern cache
portage.dep.Atom.__new__ now interns instances in a module-level cache keyed only by the atom string and parse flags, not by the constructor's class. Constructing gentoolkit.atom.Atom with a string already interned elsewhere as a plain portage.dep.Atom silently returns that cached instance instead of one of our subclass, so methods we override (like intersects()) resolve to portage's implementation instead of ours. Reproduced with portage d52efac67: gentoolkit.atom.Atom() returned a plain portage.dep.Atom whenever the same atom string had already been interned by portage itself, which happens constantly in normal use. This also explains why equery d was under-reporting some reverse dependencies: portage.dep.Atom.intersects() and our own implementation don't always agree. Always calling object.__new__(cls) to bypass the cache broke CI, which pins portage 3.0.70: through 3.0.81.3, portage.dep.Atom subclasses str instead of using an intern cache, so object.__new__(cls) raised "TypeError: object.__new__(Atom) is not safe, use str.__new__()". Atom.__new__ now checks whether portage.dep.Atom is still a str subclass and, if so, allocates via str.__new__(cls, atom) instead; that version has no cache to bypass, so this is just correct allocation, not a workaround. Bug: https://bugs.gentoo.org/981521 Signed-off-by: Matt Turner <mattst88@gentoo.org>
1 parent de9169f commit c6e7fce

1 file changed

Lines changed: 14 additions & 0 deletions

File tree

pym/gentoolkit/atom.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ class Atom(portage.dep.Atom, CPV):
4545
# Necessary for Portage versions < 2.1.7
4646
_atoms = weakref.WeakValueDictionary()
4747

48+
def __new__(cls, *args, **kwargs):
49+
# portage.dep.Atom.__new__ interns instances in a module-level
50+
# cache keyed only by the atom string (not by cls), so calling
51+
# this subclass's constructor with an already-interned string can
52+
# silently hand back a plain portage.dep.Atom instead of one of
53+
# ours. Always build a fresh instance so we stay this subclass.
54+
#
55+
# Old portage (<= 3.0.81.3) makes Atom a str subclass instead, with
56+
# no interning cache, so allocation there must go through
57+
# str.__new__ or the instance is unusable as a str.
58+
if issubclass(portage.dep.Atom, str):
59+
return str.__new__(cls, args[0] if args else "")
60+
return object.__new__(cls)
61+
4862
@property
4963
def operator(self):
5064
# Old portage stored operator as a plain instance attribute in

0 commit comments

Comments
 (0)