mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 01:43:47 -04:00
While validating recent CXL ABI documentation updates with get_abi.py, every decoderX.Y entry was reported as undocumented. The placeholder conversion mishandles adjacent index placeholders, producing patterns that cannot match the corresponding sysfs paths. As a result, valid ABI entries are reported as undocumented. Handle adjacent placeholders independently so generated patterns match the documented paths. This fixes decoderX.Y entries in the CXL ABI and other ABI documentation that uses the same naming convention. Signed-off-by: Alison Schofield <alison.schofield@intel.com> Signed-off-by: Jonathan Corbet <corbet@lwn.net> Message-ID: <cc893dca3aaa3ec833ba70d1f32c3e7342b7faf2.1786139549.git.alison.schofield@intel.com>
248 lines
8.1 KiB
Python
248 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
# xxpylint: disable=R0903
|
|
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
"""
|
|
Convert ABI what into regular expressions
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
from pprint import pformat
|
|
|
|
from abi.abi_parser import AbiParser
|
|
from abi.helpers import AbiDebug
|
|
|
|
class AbiRegex(AbiParser):
|
|
"""
|
|
Extends AbiParser to search ABI nodes with regular expressions.
|
|
|
|
There some optimizations here to allow a quick symbol search:
|
|
instead of trying to place all symbols altogether an doing linear
|
|
search which is very time consuming, create a tree with one depth,
|
|
grouping similar symbols altogether.
|
|
|
|
Yet, sometimes a full search will be needed, so we have a special branch
|
|
on such group tree where other symbols are placed.
|
|
"""
|
|
|
|
#: Escape only ASCII visible characters.
|
|
escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
|
|
|
|
#: Special group for other nodes.
|
|
leave_others = "others"
|
|
|
|
# Tuples with regular expressions to be compiled and replacement data
|
|
re_whats = [
|
|
# Drop escape characters that might exist
|
|
(re.compile("\\\\"), ""),
|
|
|
|
# Temporarily escape dot characters
|
|
(re.compile(r"\."), "\xf6"),
|
|
|
|
# Temporarily change [0-9]+ type of patterns
|
|
(re.compile(r"\[0\-9\]\+"), "\xff"),
|
|
|
|
# Temporarily change [\d+-\d+] type of patterns
|
|
(re.compile(r"\[0\-\d+\]"), "\xff"),
|
|
(re.compile(r"\[0:\d+\]"), "\xff"),
|
|
(re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
|
|
|
|
# Temporarily change [0-9] type of patterns
|
|
(re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
|
|
|
|
# Handle multiple option patterns
|
|
(re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
|
|
|
|
# Handle wildcards
|
|
(re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
|
|
(re.compile(r"/\*/"), "/.*/"),
|
|
(re.compile(r"/\xf6\xf6\xf6"), "/.*"),
|
|
(re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
|
|
(re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
|
|
(re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
|
|
|
|
(re.compile(r"XX+"), "\\\\w\xf7"),
|
|
(re.compile(r"(?<![A-Z])[XYZ](?![A-Z])"), "\\\\w\xf7"),
|
|
(re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
|
|
|
|
# Recover [0-9] type of patterns
|
|
(re.compile(r"\xf4"), "["),
|
|
(re.compile(r"\xf5"), "]"),
|
|
|
|
# Remove duplicated spaces
|
|
(re.compile(r"\s+"), r" "),
|
|
|
|
# Special case: drop comparison as in:
|
|
# What: foo = <something>
|
|
# (this happens on a few IIO definitions)
|
|
(re.compile(r"\s*\=.*$"), ""),
|
|
|
|
# Escape all other symbols
|
|
(re.compile(escape_symbols), r"\\\1"),
|
|
(re.compile(r"\\\\"), r"\\"),
|
|
(re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
|
|
(re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
|
|
|
|
(re.compile(r"\xff"), r"\\d+"),
|
|
|
|
# Special case: IIO ABI which a parenthesis.
|
|
(re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
|
|
|
|
# Simplify regexes with multiple .*
|
|
(re.compile(r"(?:\.\*){2,}"), ""),
|
|
|
|
# Recover dot characters
|
|
(re.compile(r"\xf6"), "\\."),
|
|
# Recover plus characters
|
|
(re.compile(r"\xf7"), "+"),
|
|
]
|
|
|
|
#: Regex to check if the symbol name has a number on it.
|
|
re_has_num = re.compile(r"\\d")
|
|
|
|
#: Symbol name after escape_chars that are considered a devnode basename.
|
|
re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
|
|
|
|
#: List of popular group names to be skipped to minimize regex group size
|
|
#: Use AbiDebug.SUBGROUP_SIZE to detect those.
|
|
skip_names = set(["devices", "hwmon"])
|
|
|
|
def regex_append(self, what, new):
|
|
"""
|
|
Get a search group for a subset of regular expressions.
|
|
|
|
As ABI may have thousands of symbols, using a for to search all
|
|
regular expressions is at least O(n^2). When there are wildcards,
|
|
the complexity increases substantially, eventually becoming exponential.
|
|
|
|
To avoid spending too much time on them, use a logic to split
|
|
them into groups. The smaller the group, the better, as it would
|
|
mean that searches will be confined to a small number of regular
|
|
expressions.
|
|
|
|
The conversion to a regex subset is tricky, as we need something
|
|
that can be easily obtained from the sysfs symbol and from the
|
|
regular expression. So, we need to discard nodes that have
|
|
wildcards.
|
|
|
|
If it can't obtain a subgroup, place the regular expression inside
|
|
a special group (self.leave_others).
|
|
"""
|
|
|
|
search_group = None
|
|
|
|
for search_group in reversed(new.split("/")):
|
|
if not search_group or search_group in self.skip_names:
|
|
continue
|
|
if self.re_symbol_name.match(search_group):
|
|
break
|
|
|
|
if not search_group:
|
|
search_group = self.leave_others
|
|
|
|
if self.debug & AbiDebug.SUBGROUP_MAP:
|
|
self.log.debug("%s: mapped as %s", what, search_group)
|
|
|
|
try:
|
|
if search_group not in self.regex_group:
|
|
self.regex_group[search_group] = []
|
|
|
|
self.regex_group[search_group].append(re.compile(new))
|
|
if self.search_string:
|
|
if what.find(self.search_string) >= 0:
|
|
print(f"What: {what}")
|
|
except re.error:
|
|
self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
|
|
" '%s'", what, new)
|
|
|
|
def get_regexes(self, what):
|
|
"""
|
|
Given an ABI devnode, return a list of all regular expressions that
|
|
may match it, based on the sub-groups created by regex_append().
|
|
"""
|
|
|
|
re_list = []
|
|
|
|
patches = what.split("/")
|
|
patches.reverse()
|
|
patches.append(self.leave_others)
|
|
|
|
for search_group in patches:
|
|
if search_group in self.regex_group:
|
|
re_list += self.regex_group[search_group]
|
|
|
|
return re_list
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
Override init method to get verbose argument
|
|
"""
|
|
|
|
self.regex_group = None
|
|
self.search_string = None
|
|
self.re_string = None
|
|
|
|
if "search_string" in kwargs:
|
|
self.search_string = kwargs.get("search_string")
|
|
del kwargs["search_string"]
|
|
|
|
if self.search_string:
|
|
|
|
try:
|
|
self.re_string = re.compile(self.search_string)
|
|
except re.error as e:
|
|
msg = f"{self.search_string} is not a valid regular expression"
|
|
raise ValueError(msg) from e
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def parse_abi(self, *args, **kwargs):
|
|
|
|
super().parse_abi(*args, **kwargs)
|
|
|
|
self.regex_group = {}
|
|
|
|
print("Converting ABI What fields into regexes...", file=sys.stderr)
|
|
|
|
for t in sorted(self.data.items(), key=lambda x: x[0]):
|
|
v = t[1]
|
|
if v.get("type") == "File":
|
|
continue
|
|
|
|
v["regex"] = []
|
|
|
|
for what in v.get("what", []):
|
|
if not what.startswith("/sys"):
|
|
continue
|
|
|
|
new = what
|
|
for r, s in self.re_whats:
|
|
try:
|
|
new = r.sub(s, new)
|
|
except re.error as e:
|
|
# Help debugging troubles with new regexes
|
|
raise re.error(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
|
|
|
|
v["regex"].append(new)
|
|
|
|
if self.debug & AbiDebug.REGEX:
|
|
self.log.debug("%-90s <== %s", new, what)
|
|
|
|
# Store regex into a subgroup to speedup searches
|
|
self.regex_append(what, new)
|
|
|
|
if self.debug & AbiDebug.SUBGROUP_DICT:
|
|
self.log.debug("%s", pformat(self.regex_group))
|
|
|
|
if self.debug & AbiDebug.SUBGROUP_SIZE:
|
|
biggestd_keys = sorted(self.regex_group.keys(),
|
|
key= lambda k: len(self.regex_group[k]),
|
|
reverse=True)
|
|
|
|
print("Top regex subgroups:", file=sys.stderr)
|
|
for k in biggestd_keys[:10]:
|
|
print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)
|