| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 |
|
| 6 | |
| 7 | Convert ABI what into regular expressions
|
| 8 |
|
| 9 |
|
| 10 | import re
|
| 11 | import sys
|
| 12 |
|
| 13 | from pprint import pformat
|
| 14 |
|
| 15 | from abi.abi_parser import AbiParser
|
| 16 | from abi.helpers import AbiDebug
|
| 17 |
|
| 18 | class AbiRegex(AbiParser):
|
| 19 | |
| 20 | Extends AbiParser to search ABI nodes with regular expressions.
|
| 21 |
|
| 22 | There some optimizations here to allow a quick symbol search:
|
| 23 | instead of trying to place all symbols altogether an doing linear
|
| 24 | search which is very time consuming, create a tree with one depth,
|
| 25 | grouping similar symbols altogether.
|
| 26 |
|
| 27 | Yet, sometimes a full search will be needed, so we have a special branch
|
| 28 | on such group tree where other symbols are placed.
|
| 29 |
|
| 30 |
|
| 31 | |
| 32 | escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
|
| 33 |
|
| 34 | |
| 35 | leave_others = "others"
|
| 36 |
|
| 37 | |
| 38 | re_whats = [
|
| 39 | |
| 40 | (re.compile("\\\\"), ""),
|
| 41 |
|
| 42 | |
| 43 | (re.compile(r"\."), "\xf6"),
|
| 44 |
|
| 45 | |
| 46 | (re.compile(r"\[0\-9\]\+"), "\xff"),
|
| 47 |
|
| 48 | |
| 49 | (re.compile(r"\[0\-\d+\]"), "\xff"),
|
| 50 | (re.compile(r"\[0:\d+\]"), "\xff"),
|
| 51 | (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
|
| 52 |
|
| 53 | |
| 54 | (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
|
| 55 |
|
| 56 | |
| 57 | (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
|
| 58 |
|
| 59 | |
| 60 | (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
|
| 61 | (re.compile(r"/\*/"), "/.*/"),
|
| 62 | (re.compile(r"/\xf6\xf6\xf6"), "/.*"),
|
| 63 | (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
|
| 64 | (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
|
| 65 | (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
|
| 66 |
|
| 67 | (re.compile(r"XX+"), "\\\\w\xf7"),
|
| 68 | (re.compile(r"(?<![A-Z])[XYZ](?![A-Z])"), "\\\\w\xf7"),
|
| 69 | (re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
|
| 70 |
|
| 71 | |
| 72 | (re.compile(r"\xf4"), "["),
|
| 73 | (re.compile(r"\xf5"), "]"),
|
| 74 |
|
| 75 | |
| 76 | (re.compile(r"\s+"), r" "),
|
| 77 |
|
| 78 | |
| 79 | |
| 80 | |
| 81 | (re.compile(r"\s*\=.*$"), ""),
|
| 82 |
|
| 83 | |
| 84 | (re.compile(escape_symbols), r"\\\1"),
|
| 85 | (re.compile(r"\\\\"), r"\\"),
|
| 86 | (re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
|
| 87 | (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
|
| 88 |
|
| 89 | (re.compile(r"\xff"), r"\\d+"),
|
| 90 |
|
| 91 | |
| 92 | (re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
|
| 93 |
|
| 94 | |
| 95 | (re.compile(r"(?:\.\*){2,}"), ""),
|
| 96 |
|
| 97 | |
| 98 | (re.compile(r"\xf6"), "\\."),
|
| 99 | |
| 100 | (re.compile(r"\xf7"), "+"),
|
| 101 | ]
|
| 102 |
|
| 103 | |
| 104 | re_has_num = re.compile(r"\\d")
|
| 105 |
|
| 106 | |
| 107 | re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
|
| 108 |
|
| 109 | |
| 110 | |
| 111 | skip_names = set(["devices", "hwmon"])
|
| 112 |
|
| 113 | def regex_append(self, what, new):
|
| 114 | |
| 115 | Get a search group for a subset of regular expressions.
|
| 116 |
|
| 117 | As ABI may have thousands of symbols, using a for to search all
|
| 118 | regular expressions is at least O(n^2). When there are wildcards,
|
| 119 | the complexity increases substantially, eventually becoming exponential.
|
| 120 |
|
| 121 | To avoid spending too much time on them, use a logic to split
|
| 122 | them into groups. The smaller the group, the better, as it would
|
| 123 | mean that searches will be confined to a small number of regular
|
| 124 | expressions.
|
| 125 |
|
| 126 | The conversion to a regex subset is tricky, as we need something
|
| 127 | that can be easily obtained from the sysfs symbol and from the
|
| 128 | regular expression. So, we need to discard nodes that have
|
| 129 | wildcards.
|
| 130 |
|
| 131 | If it can't obtain a subgroup, place the regular expression inside
|
| 132 | a special group (self.leave_others).
|
| 133 |
|
| 134 |
|
| 135 | search_group = None
|
| 136 |
|
| 137 | for search_group in reversed(new.split("/")):
|
| 138 | if not search_group or search_group in self.skip_names:
|
| 139 | continue
|
| 140 | if self.re_symbol_name.match(search_group):
|
| 141 | break
|
| 142 |
|
| 143 | if not search_group:
|
| 144 | search_group = self.leave_others
|
| 145 |
|
| 146 | if self.debug & AbiDebug.SUBGROUP_MAP:
|
| 147 | self.log.debug("%s: mapped as %s", what, search_group)
|
| 148 |
|
| 149 | try:
|
| 150 | if search_group not in self.regex_group:
|
| 151 | self.regex_group[search_group] = []
|
| 152 |
|
| 153 | self.regex_group[search_group].append(re.compile(new))
|
| 154 | if self.search_string:
|
| 155 | if what.find(self.search_string) >= 0:
|
| 156 | print(f"What: {what}")
|
| 157 | except re.error:
|
| 158 | self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
|
| 159 | " '%s'", what, new)
|
| 160 |
|
| 161 | def get_regexes(self, what):
|
| 162 | |
| 163 | Given an ABI devnode, return a list of all regular expressions that
|
| 164 | may match it, based on the sub-groups created by regex_append().
|
| 165 |
|
| 166 |
|
| 167 | re_list = []
|
| 168 |
|
| 169 | patches = what.split("/")
|
| 170 | patches.reverse()
|
| 171 | patches.append(self.leave_others)
|
| 172 |
|
| 173 | for search_group in patches:
|
| 174 | if search_group in self.regex_group:
|
| 175 | re_list += self.regex_group[search_group]
|
| 176 |
|
| 177 | return re_list
|
| 178 |
|
| 179 | def __init__(self, *args, **kwargs):
|
| 180 | |
| 181 | Override init method to get verbose argument
|
| 182 |
|
| 183 |
|
| 184 | self.regex_group = None
|
| 185 | self.search_string = None
|
| 186 | self.re_string = None
|
| 187 |
|
| 188 | if "search_string" in kwargs:
|
| 189 | self.search_string = kwargs.get("search_string")
|
| 190 | del kwargs["search_string"]
|
| 191 |
|
| 192 | if self.search_string:
|
| 193 |
|
| 194 | try:
|
| 195 | self.re_string = re.compile(self.search_string)
|
| 196 | except re.error as e:
|
| 197 | msg = f"{self.search_string} is not a valid regular expression"
|
| 198 | raise ValueError(msg) from e
|
| 199 |
|
| 200 | super().__init__(*args, **kwargs)
|
| 201 |
|
| 202 | def parse_abi(self, *args, **kwargs):
|
| 203 |
|
| 204 | super().parse_abi(*args, **kwargs)
|
| 205 |
|
| 206 | self.regex_group = {}
|
| 207 |
|
| 208 | print("Converting ABI What fields into regexes...", file=sys.stderr)
|
| 209 |
|
| 210 | for t in sorted(self.data.items(), key=lambda x: x[0]):
|
| 211 | v = t[1]
|
| 212 | if v.get("type") == "File":
|
| 213 | continue
|
| 214 |
|
| 215 | v["regex"] = []
|
| 216 |
|
| 217 | for what in v.get("what", []):
|
| 218 | if not what.startswith("/sys"):
|
| 219 | continue
|
| 220 |
|
| 221 | new = what
|
| 222 | for r, s in self.re_whats:
|
| 223 | try:
|
| 224 | new = r.sub(s, new)
|
| 225 | except re.error as e:
|
| 226 | |
| 227 | raise re.error(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
|
| 228 |
|
| 229 | v["regex"].append(new)
|
| 230 |
|
| 231 | if self.debug & AbiDebug.REGEX:
|
| 232 | self.log.debug("%-90s <== %s", new, what)
|
| 233 |
|
| 234 | |
| 235 | self.regex_append(what, new)
|
| 236 |
|
| 237 | if self.debug & AbiDebug.SUBGROUP_DICT:
|
| 238 | self.log.debug("%s", pformat(self.regex_group))
|
| 239 |
|
| 240 | if self.debug & AbiDebug.SUBGROUP_SIZE:
|
| 241 | biggestd_keys = sorted(self.regex_group.keys(),
|
| 242 | key= lambda k: len(self.regex_group[k]),
|
| 243 | reverse=True)
|
| 244 |
|
| 245 | print("Top regex subgroups:", file=sys.stderr)
|
| 246 | for k in biggestd_keys[:10]:
|
| 247 | print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)
|