No Bug, mozilla-central repo-update HSTS HPKP remote-settings tld-suffixes ct-logs...
[gecko.git] / config / check_macroassembler_style.py
blobc10ef9789d2583c9a086f8b6077fc9dbcb2c4d6e
1 # vim: set ts=8 sts=4 et sw=4 tw=99:
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ----------------------------------------------------------------------------
7 # This script checks that SpiderMonkey MacroAssembler methods are properly
8 # annotated.
10 # The MacroAssembler has one interface for all platforms, but it might have one
11 # definition per platform. The code of the MacroAssembler use a macro to
12 # annotate the method declarations, in order to delete the function if it is not
13 # present on the current platform, and also to locate the files in which the
14 # methods are defined.
16 # This script scans the MacroAssembler.h header, for method declarations.
17 # It also scans MacroAssembler-/arch/.cpp, MacroAssembler-/arch/-inl.h, and
18 # MacroAssembler-inl.h for method definitions. The result of both scans are
19 # uniformized, and compared, to determine if the MacroAssembler.h header as
20 # proper methods annotations.
21 # ----------------------------------------------------------------------------
23 import difflib
24 import os
25 import re
26 import sys
28 architecture_independent = set(["generic"])
29 all_unsupported_architectures_names = set(["mips32", "mips64", "mips_shared"])
30 all_architecture_names = set(
31 ["x86", "x64", "arm", "arm64", "loong64", "riscv64", "wasm32"]
33 all_shared_architecture_names = set(
34 ["x86_shared", "arm", "arm64", "loong64", "riscv64", "wasm32"]
37 reBeforeArg = r"(?<=[(,\s])"
38 reArgType = r"(?P<type>[\w\s:*&<>]+)"
39 reArgName = r"(?P<name>\s\w+)"
40 reArgDefault = r"(?P<default>(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
41 reAfterArg = "(?=[,)])"
42 reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
45 def get_normalized_signatures(signature, fileAnnot=None):
46 # Remove static
47 signature = signature.replace("static", "")
48 # Remove semicolon.
49 signature = signature.replace(";", " ")
50 # Normalize spaces.
51 signature = re.sub(r"\s+", " ", signature).strip()
52 # Remove new-line induced spaces after opening braces.
53 signature = re.sub(r"\(\s+", "(", signature).strip()
54 # Match arguments, and keep only the type.
55 signature = reMatchArg.sub(r"\g<type>", signature)
56 # Remove class name
57 signature = signature.replace("MacroAssembler::", "")
59 # Extract list of architectures
60 archs = ["generic"]
61 if fileAnnot:
62 archs = [fileAnnot["arch"]]
64 if "DEFINED_ON(" in signature:
65 archs = re.sub(
66 r".*DEFINED_ON\((?P<archs>[^()]*)\).*", r"\g<archs>", signature
67 ).split(",")
68 archs = [a.strip() for a in archs]
69 signature = re.sub(r"\s+DEFINED_ON\([^()]*\)", "", signature)
71 elif "PER_ARCH" in signature:
72 archs = all_architecture_names
73 signature = re.sub(r"\s+PER_ARCH", "", signature)
75 elif "PER_SHARED_ARCH" in signature:
76 archs = all_shared_architecture_names
77 signature = re.sub(r"\s+PER_SHARED_ARCH", "", signature)
79 elif "OOL_IN_HEADER" in signature:
80 assert archs == ["generic"]
81 signature = re.sub(r"\s+OOL_IN_HEADER", "", signature)
83 else:
84 # No signature annotation, the list of architectures remains unchanged.
85 pass
87 # Extract inline annotation
88 inline = False
89 if fileAnnot:
90 inline = fileAnnot["inline"]
92 if "inline " in signature:
93 signature = re.sub(r"inline\s+", "", signature)
94 inline = True
96 inlinePrefx = ""
97 if inline:
98 inlinePrefx = "inline "
99 signatures = [{"arch": a, "sig": inlinePrefx + signature} for a in archs]
101 return signatures
104 file_suffixes = set(
106 a.replace("_", "-")
107 for a in all_architecture_names.union(all_shared_architecture_names).union(
108 all_unsupported_architectures_names
114 def get_file_annotation(filename):
115 origFilename = filename
116 filename = filename.split("/")[-1]
118 inline = False
119 if filename.endswith(".cpp"):
120 filename = filename[: -len(".cpp")]
121 elif filename.endswith("-inl.h"):
122 inline = True
123 filename = filename[: -len("-inl.h")]
124 elif filename.endswith(".h"):
125 # This allows the definitions block in MacroAssembler.h to be
126 # style-checked.
127 inline = True
128 filename = filename[: -len(".h")]
129 else:
130 raise Exception("unknown file name", origFilename)
132 arch = "generic"
133 for suffix in file_suffixes:
134 if filename == "MacroAssembler-" + suffix:
135 arch = suffix
136 break
138 return {"inline": inline, "arch": arch.replace("-", "_")}
141 def get_macroassembler_definitions(filename):
142 try:
143 fileAnnot = get_file_annotation(filename)
144 except Exception:
145 return []
147 style_section = False
148 lines = ""
149 signatures = []
150 with open(filename, encoding="utf-8") as f:
151 for line in f:
152 if "//{{{ check_macroassembler_style" in line:
153 if style_section:
154 raise "check_macroassembler_style section already opened."
155 style_section = True
156 braces_depth = 0
157 elif "//}}} check_macroassembler_style" in line:
158 style_section = False
159 if not style_section:
160 continue
162 # Ignore preprocessor directives.
163 if line.startswith("#"):
164 continue
166 # Remove comments from the processed line.
167 line = re.sub(r"//.*", "", line)
169 # Locate and count curly braces.
170 open_curly_brace = line.find("{")
171 was_braces_depth = braces_depth
172 braces_depth = braces_depth + line.count("{") - line.count("}")
174 # Raise an error if the check_macroassembler_style macro is used
175 # across namespaces / classes scopes.
176 if braces_depth < 0:
177 raise "check_macroassembler_style annotations are not well scoped."
179 # If the current line contains an opening curly brace, check if
180 # this line combines with the previous one can be identified as a
181 # MacroAssembler function signature.
182 if open_curly_brace != -1 and was_braces_depth == 0:
183 lines = lines + line[:open_curly_brace]
184 if "MacroAssembler::" in lines:
185 signatures.extend(get_normalized_signatures(lines, fileAnnot))
186 lines = ""
187 continue
189 # We do not aggregate any lines if we are scanning lines which are
190 # in-between a set of curly braces.
191 if braces_depth > 0:
192 continue
193 if was_braces_depth != 0:
194 line = line[line.rfind("}") + 1 :]
196 # This logic is used to remove template instantiation, static
197 # variable definitions and function declaration from the next
198 # function definition.
199 last_semi_colon = line.rfind(";")
200 if last_semi_colon != -1:
201 lines = ""
202 line = line[last_semi_colon + 1 :]
204 # Aggregate lines of non-braced text, which corresponds to the space
205 # where we are expecting to find function definitions.
206 lines = lines + line
208 return signatures
211 def get_macroassembler_declaration(filename):
212 style_section = False
213 lines = ""
214 signatures = []
215 with open(filename, encoding="utf-8") as f:
216 for line in f:
217 if "//{{{ check_macroassembler_decl_style" in line:
218 style_section = True
219 elif "//}}} check_macroassembler_decl_style" in line:
220 style_section = False
221 if not style_section:
222 continue
224 # Ignore preprocessor directives.
225 if line.startswith("#"):
226 continue
228 line = re.sub(r"//.*", "", line)
229 if len(line.strip()) == 0 or "public:" in line or "private:" in line:
230 lines = ""
231 continue
233 lines = lines + line
235 # Continue until we have a complete declaration
236 if ";" not in lines:
237 continue
239 # Skip member declarations: which are lines ending with a
240 # semi-colon without any list of arguments.
241 if ")" not in lines:
242 lines = ""
243 continue
245 signatures.extend(get_normalized_signatures(lines))
246 lines = ""
248 return signatures
251 def append_signatures(d, sigs):
252 for s in sigs:
253 if s["sig"] not in d:
254 d[s["sig"]] = []
255 d[s["sig"]].append(s["arch"])
256 return d
259 def generate_file_content(signatures):
260 output = []
261 for s in sorted(signatures.keys()):
262 archs = set(sorted(signatures[s]))
263 archs -= all_unsupported_architectures_names
264 if len(archs.symmetric_difference(architecture_independent)) == 0:
265 output.append(s + ";\n")
266 if s.startswith("inline"):
267 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
268 # functions. (Such annotation is already removed by the time
269 # this function sees the signature here.)
270 output.append(" is defined in MacroAssembler-inl.h\n")
271 else:
272 output.append(" is defined in MacroAssembler.cpp\n")
273 else:
274 if len(archs.symmetric_difference(all_architecture_names)) == 0:
275 output.append(s + " PER_ARCH;\n")
276 elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
277 output.append(s + " PER_SHARED_ARCH;\n")
278 else:
279 output.append(s + " DEFINED_ON(" + ", ".join(sorted(archs)) + ");\n")
280 for a in sorted(archs):
281 a = a.replace("_", "-")
282 masm = "%s/MacroAssembler-%s" % (a, a)
283 if s.startswith("inline"):
284 output.append(" is defined in %s-inl.h\n" % masm)
285 else:
286 output.append(" is defined in %s.cpp\n" % masm)
287 return output
290 def check_style():
291 # We read from the header file the signature of each function.
292 decls = dict() # type: dict(signature => ['x86', 'x64'])
294 # We infer from each file the signature of each MacroAssembler function.
295 defs = dict() # type: dict(signature => ['x86', 'x64'])
297 root_dir = os.path.join("js", "src", "jit")
298 for dirpath, dirnames, filenames in os.walk(root_dir):
299 for filename in filenames:
300 if "MacroAssembler" not in filename:
301 continue
303 filepath = os.path.join(dirpath, filename).replace("\\", "/")
305 if filepath.endswith("MacroAssembler.h"):
306 decls = append_signatures(
307 decls, get_macroassembler_declaration(filepath)
309 defs = append_signatures(defs, get_macroassembler_definitions(filepath))
311 if not decls or not defs:
312 raise Exception("Did not find any definitions or declarations")
314 # Compare declarations and definitions output.
315 difflines = difflib.unified_diff(
316 generate_file_content(decls),
317 generate_file_content(defs),
318 fromfile="check_macroassembler_style.py declared syntax",
319 tofile="check_macroassembler_style.py found definitions",
321 ok = True
322 for diffline in difflines:
323 ok = False
324 print(diffline, end="")
325 return ok
328 def main():
329 ok = check_style()
331 if ok:
332 print("TEST-PASS | check_macroassembler_style.py | ok")
333 else:
334 print(
335 "TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above" # noqa: E501
338 sys.exit(0 if ok else 1)
341 if __name__ == "__main__":
342 main()