abi_regex.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env python3
  2. # xxpylint: disable=R0903
  3. # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
  4. # SPDX-License-Identifier: GPL-2.0
  5. """
  6. Convert ABI what into regular expressions
  7. """
  8. import re
  9. import sys
  10. from pprint import pformat
  11. from abi.abi_parser import AbiParser
  12. from abi.helpers import AbiDebug
  13. class AbiRegex(AbiParser):
  14. """
  15. Extends AbiParser to search ABI nodes with regular expressions.
  16. There some optimizations here to allow a quick symbol search:
  17. instead of trying to place all symbols altogether an doing linear
  18. search which is very time consuming, create a tree with one depth,
  19. grouping similar symbols altogether.
  20. Yet, sometimes a full search will be needed, so we have a special branch
  21. on such group tree where other symbols are placed.
  22. """
  23. #: Escape only ASCII visible characters.
  24. escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
  25. #: Special group for other nodes.
  26. leave_others = "others"
  27. # Tuples with regular expressions to be compiled and replacement data
  28. re_whats = [
  29. # Drop escape characters that might exist
  30. (re.compile("\\\\"), ""),
  31. # Temporarily escape dot characters
  32. (re.compile(r"\."), "\xf6"),
  33. # Temporarily change [0-9]+ type of patterns
  34. (re.compile(r"\[0\-9\]\+"), "\xff"),
  35. # Temporarily change [\d+-\d+] type of patterns
  36. (re.compile(r"\[0\-\d+\]"), "\xff"),
  37. (re.compile(r"\[0:\d+\]"), "\xff"),
  38. (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
  39. # Temporarily change [0-9] type of patterns
  40. (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
  41. # Handle multiple option patterns
  42. (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
  43. # Handle wildcards
  44. (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
  45. (re.compile(r"/\*/"), "/.*/"),
  46. (re.compile(r"/\xf6\xf6\xf6"), "/.*"),
  47. (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
  48. (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
  49. (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
  50. (re.compile(r"XX+"), "\\\\w\xf7"),
  51. (re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"),
  52. (re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"),
  53. (re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
  54. # Recover [0-9] type of patterns
  55. (re.compile(r"\xf4"), "["),
  56. (re.compile(r"\xf5"), "]"),
  57. # Remove duplicated spaces
  58. (re.compile(r"\s+"), r" "),
  59. # Special case: drop comparison as in:
  60. # What: foo = <something>
  61. # (this happens on a few IIO definitions)
  62. (re.compile(r"\s*\=.*$"), ""),
  63. # Escape all other symbols
  64. (re.compile(escape_symbols), r"\\\1"),
  65. (re.compile(r"\\\\"), r"\\"),
  66. (re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
  67. (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
  68. (re.compile(r"\xff"), r"\\d+"),
  69. # Special case: IIO ABI which a parenthesis.
  70. (re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
  71. # Simplify regexes with multiple .*
  72. (re.compile(r"(?:\.\*){2,}"), ""),
  73. # Recover dot characters
  74. (re.compile(r"\xf6"), "\\."),
  75. # Recover plus characters
  76. (re.compile(r"\xf7"), "+"),
  77. ]
  78. #: Regex to check if the symbol name has a number on it.
  79. re_has_num = re.compile(r"\\d")
  80. #: Symbol name after escape_chars that are considered a devnode basename.
  81. re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
  82. #: List of popular group names to be skipped to minimize regex group size
  83. #: Use AbiDebug.SUBGROUP_SIZE to detect those.
  84. skip_names = set(["devices", "hwmon"])
  85. def regex_append(self, what, new):
  86. """
  87. Get a search group for a subset of regular expressions.
  88. As ABI may have thousands of symbols, using a for to search all
  89. regular expressions is at least O(n^2). When there are wildcards,
  90. the complexity increases substantially, eventually becoming exponential.
  91. To avoid spending too much time on them, use a logic to split
  92. them into groups. The smaller the group, the better, as it would
  93. mean that searches will be confined to a small number of regular
  94. expressions.
  95. The conversion to a regex subset is tricky, as we need something
  96. that can be easily obtained from the sysfs symbol and from the
  97. regular expression. So, we need to discard nodes that have
  98. wildcards.
  99. If it can't obtain a subgroup, place the regular expression inside
  100. a special group (self.leave_others).
  101. """
  102. search_group = None
  103. for search_group in reversed(new.split("/")):
  104. if not search_group or search_group in self.skip_names:
  105. continue
  106. if self.re_symbol_name.match(search_group):
  107. break
  108. if not search_group:
  109. search_group = self.leave_others
  110. if self.debug & AbiDebug.SUBGROUP_MAP:
  111. self.log.debug("%s: mapped as %s", what, search_group)
  112. try:
  113. if search_group not in self.regex_group:
  114. self.regex_group[search_group] = []
  115. self.regex_group[search_group].append(re.compile(new))
  116. if self.search_string:
  117. if what.find(self.search_string) >= 0:
  118. print(f"What: {what}")
  119. except re.PatternError:
  120. self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
  121. " '%s'", what, new)
  122. def get_regexes(self, what):
  123. """
  124. Given an ABI devnode, return a list of all regular expressions that
  125. may match it, based on the sub-groups created by regex_append().
  126. """
  127. re_list = []
  128. patches = what.split("/")
  129. patches.reverse()
  130. patches.append(self.leave_others)
  131. for search_group in patches:
  132. if search_group in self.regex_group:
  133. re_list += self.regex_group[search_group]
  134. return re_list
  135. def __init__(self, *args, **kwargs):
  136. """
  137. Override init method to get verbose argument
  138. """
  139. self.regex_group = None
  140. self.search_string = None
  141. self.re_string = None
  142. if "search_string" in kwargs:
  143. self.search_string = kwargs.get("search_string")
  144. del kwargs["search_string"]
  145. if self.search_string:
  146. try:
  147. self.re_string = re.compile(self.search_string)
  148. except re.PatternError as e:
  149. msg = f"{self.search_string} is not a valid regular expression"
  150. raise ValueError(msg) from e
  151. super().__init__(*args, **kwargs)
  152. def parse_abi(self, *args, **kwargs):
  153. super().parse_abi(*args, **kwargs)
  154. self.regex_group = {}
  155. print("Converting ABI What fields into regexes...", file=sys.stderr)
  156. for t in sorted(self.data.items(), key=lambda x: x[0]):
  157. v = t[1]
  158. if v.get("type") == "File":
  159. continue
  160. v["regex"] = []
  161. for what in v.get("what", []):
  162. if not what.startswith("/sys"):
  163. continue
  164. new = what
  165. for r, s in self.re_whats:
  166. try:
  167. new = r.sub(s, new)
  168. except re.PatternError as e:
  169. # Help debugging troubles with new regexes
  170. raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
  171. v["regex"].append(new)
  172. if self.debug & AbiDebug.REGEX:
  173. self.log.debug("%-90s <== %s", new, what)
  174. # Store regex into a subgroup to speedup searches
  175. self.regex_append(what, new)
  176. if self.debug & AbiDebug.SUBGROUP_DICT:
  177. self.log.debug("%s", pformat(self.regex_group))
  178. if self.debug & AbiDebug.SUBGROUP_SIZE:
  179. biggestd_keys = sorted(self.regex_group.keys(),
  180. key= lambda k: len(self.regex_group[k]),
  181. reverse=True)
  182. print("Top regex subgroups:", file=sys.stderr)
  183. for k in biggestd_keys[:10]:
  184. print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)