proj/portage:master commit in: lib/portage/tests/dep/, lib/portage/dep/
"Matt Turner" <[email protected]>
| Newsgroups | gmane.linux.gentoo.cvs |
|---|---|
| Message-ID | <1785963929.34257307e1fe295885f6851a596cc17b53cee705.mattst88@gentoo> |
commit: 34257307e1fe295885f6851a596cc17b53cee705
Author: Matt Turner <mattst88 <AT> gentoo <DOT> org>
AuthorDate: Sun Aug 2 20:22:10 2026 +0000
Commit: Matt Turner <mattst88 <AT> gentoo <DOT> org>
CommitDate: Wed Aug 5 21:05:29 2026 +0000
URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=34257307
dep: widen the Atom C fast path to wildcard-flag and is_valid_flag callers
Instrumenting a profiled @world showed the Atom fast path was reaching
only 37% of the 779651 Atom.__init__ calls, with the guard rejecting
376131 of them (48%) before scan_atom was even tried. The two terms
responsible were unnecessary:
- allow_wildcard: __init__ always tries the strict regex first and
only uses the wildcard regex when it fails. scan_atom accepts
exactly the strict-valid atoms, so a real wildcard/extended atom
fails scan_atom and falls back on its own; a non-wildcard atom
parses identically whether or not allow_wildcard is set.
(allow_wildcard blocked only 6556 calls anyway.)
- is_valid_flag: this only drives a USE-conditional-flag-in-IUSE
check, which the fast path can perform itself. It was the real
bottleneck, blocking 369575 calls (47%).
Drop both from the guard and run the is_valid_flag conditional-flag
validation inside _c_fast_init, mirroring the regex path exactly (same
IUSE.missing InvalidAtom). The fast path now serves 663538 of 779655
Atom.__init__ calls (85%) -- nearly every call not excluded by an
injected _use or a virtual-expansion original.
Two differential fuzzes (~1.3M and ~1.25M comparisons) covering wildcard
atoms and is_valid_flag callbacks across EAPIs {5,8,None} found no
differences in any Atom field, str(), without_use, or raised exception.
Profiled cProfile emerge -uDNp @world: Atom.__init__ 10.68s -> 6.98s cum
(_c_fast_init 288281 -> 663538 calls); profiled resolution 145.7s ->
143.3s. Unprofiled, the whole series takes emerge -uDNp @world from
54.3s on the pure-Python path to 50.1s (best of three). @world merge
list unchanged.
Extends TestScanAtom with cases for the is_valid_flag validation
(accepting and rejecting flags, and that non-conditional flags are not
validated) and for extended/wildcard atoms (*/*, cat/*, =cat/pkg-*1*),
which fail scan_atom and fall back to the regex path.
Signed-off-by: Matt Turner <mattst88 <AT> gentoo.org>
lib/portage/dep/__init__.py | 36 ++++++++++++++++++++++++----------
lib/portage/tests/dep/test_c_parser.py | 21 ++++++++++++++++++++
2 files changed, 47 insertions(+), 10 deletions(-)
diff --git a/lib/portage/dep/__init__.py b/lib/portage/dep/__init__.py
index 1556c1a62..dac8b1987 100644
--- a/lib/portage/dep/__init__.py
+++ b/lib/portage/dep/__init__.py
@@ -1826,7 +1826,23 @@ class Atom:
def __init__(self, forbid_overlap=False):
self.overlap = self._overlap(forbid=forbid_overlap)
- def _c_fast_init(self, catom, eapi, eapi_attrs, unevaluated_atom):
+ def _validate_conditional_flags(self, is_valid_flag):
+ """Raise if a USE-conditional flag in this atom is not in IUSE.
+
+ Mirrors the check the regex path performs in __init__.
+ """
+ for conditional_type, flags in self._use.conditional.items():
+ for flag in flags:
+ if is_valid_flag(flag):
+ continue
+ conditional_str = _use_dep._conditional_strings[conditional_type]
+ raise InvalidAtom(
+ f"USE flag '{flag}' referenced in conditional "
+ f"'{conditional_str % flag}' in atom '{self}' is not in IUSE",
+ category="IUSE.missing",
+ )
+
+ def _c_fast_init(self, catom, eapi, eapi_attrs, is_valid_flag, unevaluated_atom):
"""Populate this Atom from a C scan_atom result.
Raises InvalidAtom/TypeError for the same cases as the regex path.
@@ -1868,6 +1884,8 @@ class Atom:
f"Use dep defaults are not allowed in EAPI {eapi}: '{self}'",
category="EAPI.incompatible",
)
+ if is_valid_flag is not None and self._use.conditional:
+ self._validate_conditional_flags(is_valid_flag)
if (
self._blocker_obj
@@ -1918,17 +1936,13 @@ class Atom:
if allow_build_id is None:
allow_build_id = True
- # Fast path: parse the atom in C for the common case. scan_atom is
- # byte-exact with the regex path or raises ValueError (repo specs,
- # build-ids, wildcards, anything invalid), in which case we fall
- # through to the pure-Python regex path below. Wildcards, an injected
- # _use, flag validation, and virtual-expansion originals are handled
- # only by the regex path.
+ # Try the C fast path. scan_atom raises ValueError for anything it
+ # can't handle (wildcards, repo specs, build-ids); we fall through to
+ # the regex path. allow_wildcard is safe: __init__ tries strict regex
+ # first, so actual wildcard atoms fail scan_atom and fall back naturally.
if (
_c_dep_parser is not None
- and not allow_wildcard
and _use is None
- and is_valid_flag is None
and orig_atom is None
and (eapi is None or eapi_attrs.slot_operator)
):
@@ -1937,7 +1951,9 @@ class Atom:
except ValueError:
catom = None
if catom is not None:
- self._c_fast_init(catom, eapi, eapi_attrs, unevaluated_atom)
+ self._c_fast_init(
+ catom, eapi, eapi_attrs, is_valid_flag, unevaluated_atom
+ )
return
if s[:1] == "!":
diff --git a/lib/portage/tests/dep/test_c_parser.py b/lib/portage/tests/dep/test_c_parser.py
index 62b6aaec6..e939d4ded 100644
--- a/lib/portage/tests/dep/test_c_parser.py
+++ b/lib/portage/tests/dep/test_c_parser.py
@@ -1001,6 +1001,27 @@ class TestScanAtom(_AtomParityMixin, TestCase):
self._assert_same("cat/pkg[a]", eapi="1") # use deps invalid in EAPI 1
self._assert_same("cat/pkg[a(+)]", eapi="4") # defaults invalid in EAPI 4
+ def test_is_valid_flag(self):
+ # The conditional-flag-in-IUSE check runs in the fast path too.
+ def accept_all(flag):
+ return True
+
+ def reject_x(flag):
+ return not flag.startswith("x")
+
+ self._assert_same("cat/pkg[a?,!b?]", eapi="8", is_valid_flag=accept_all)
+ self._assert_same("cat/pkg[x?]", eapi="8", is_valid_flag=reject_x) # invalid
+ self._assert_same("cat/pkg[x=]", eapi="8", is_valid_flag=reject_x) # invalid
+ # Non-conditional flags are not validated by is_valid_flag.
+ self._assert_same("cat/pkg[x,-y]", eapi="8", is_valid_flag=reject_x)
+
+ def test_wildcard_falls_back(self):
+ # Extended/wildcard atoms fail scan_atom and use the regex path;
+ # results (extended_syntax etc.) must still match.
+ for s in ("*/*", "cat/*", "*/pkg", "dev-*/foo", "=cat/pkg-*1*"):
+ with self.subTest(s=s):
+ self._assert_same(s, allow_wildcard=True)
+
class TestScanAtomNameGrammar(_AtomParityMixin, TestCase):
"""Category, package-name, version and revision edge cases, adapted from