tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

check_macroassembler_style.py (11926B)


      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/.
      5 
      6 # ----------------------------------------------------------------------------
      7 # This script checks that SpiderMonkey MacroAssembler methods are properly
      8 # annotated.
      9 #
     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.
     15 #
     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 # ----------------------------------------------------------------------------
     22 
     23 import difflib
     24 import os
     25 import re
     26 import sys
     27 
     28 architecture_independent = set(["generic"])
     29 all_unsupported_architectures_names = set(["mips64", "mips_shared"])
     30 all_architecture_names = set([
     31    "x86",
     32    "x64",
     33    "arm",
     34    "arm64",
     35    "loong64",
     36    "riscv64",
     37    "wasm32",
     38 ])
     39 all_shared_architecture_names = set([
     40    "x86_shared",
     41    "arm",
     42    "arm64",
     43    "loong64",
     44    "riscv64",
     45    "wasm32",
     46 ])
     47 
     48 reBeforeArg = r"(?<=[(,\s])"
     49 reArgType = r"(?P<type>[\w\s:*&<>]+)"
     50 reArgName = r"(?P<name>\s\w+)"
     51 reArgDefault = r"(?P<default>(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
     52 reAfterArg = "(?=[,)])"
     53 reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
     54 
     55 
     56 def get_normalized_signatures(signature, fileAnnot=None):
     57    # Remove static
     58    signature = signature.replace("static", "")
     59    # Remove semicolon.
     60    signature = signature.replace(";", " ")
     61    # Normalize spaces.
     62    signature = re.sub(r"\s+", " ", signature).strip()
     63    # Remove new-line induced spaces after opening braces.
     64    signature = re.sub(r"\(\s+", "(", signature).strip()
     65    # Match arguments, and keep only the type.
     66    signature = reMatchArg.sub(r"\g<type>", signature)
     67    # Remove class name
     68    signature = signature.replace("MacroAssembler::", "")
     69 
     70    # Extract list of architectures
     71    archs = ["generic"]
     72    if fileAnnot:
     73        archs = [fileAnnot["arch"]]
     74 
     75    if "DEFINED_ON(" in signature:
     76        archs = re.sub(
     77            r".*DEFINED_ON\((?P<archs>[^()]*)\).*", r"\g<archs>", signature
     78        ).split(",")
     79        archs = [a.strip() for a in archs]
     80        signature = re.sub(r"\s+DEFINED_ON\([^()]*\)", "", signature)
     81 
     82    elif "PER_ARCH" in signature:
     83        archs = all_architecture_names
     84        signature = re.sub(r"\s+PER_ARCH", "", signature)
     85 
     86    elif "PER_SHARED_ARCH" in signature:
     87        archs = all_shared_architecture_names
     88        signature = re.sub(r"\s+PER_SHARED_ARCH", "", signature)
     89 
     90    elif "OOL_IN_HEADER" in signature:
     91        assert archs == ["generic"]
     92        signature = re.sub(r"\s+OOL_IN_HEADER", "", signature)
     93 
     94    else:
     95        # No signature annotation, the list of architectures remains unchanged.
     96        pass
     97 
     98    # Extract inline annotation
     99    inline = False
    100    if fileAnnot:
    101        inline = fileAnnot["inline"]
    102 
    103    if "inline " in signature:
    104        signature = re.sub(r"inline\s+", "", signature)
    105        inline = True
    106 
    107    inlinePrefx = ""
    108    if inline:
    109        inlinePrefx = "inline "
    110    signatures = [{"arch": a, "sig": inlinePrefx + signature} for a in archs]
    111 
    112    return signatures
    113 
    114 
    115 file_suffixes = set([
    116    a.replace("_", "-")
    117    for a in all_architecture_names.union(all_shared_architecture_names).union(
    118        all_unsupported_architectures_names
    119    )
    120 ])
    121 
    122 
    123 def get_file_annotation(filename):
    124    origFilename = filename
    125    filename = filename.split("/")[-1]
    126 
    127    inline = False
    128    if filename.endswith(".cpp"):
    129        filename = filename[: -len(".cpp")]
    130    elif filename.endswith("-inl.h"):
    131        inline = True
    132        filename = filename[: -len("-inl.h")]
    133    elif filename.endswith(".h"):
    134        # This allows the definitions block in MacroAssembler.h to be
    135        # style-checked.
    136        inline = True
    137        filename = filename[: -len(".h")]
    138    else:
    139        raise Exception("unknown file name", origFilename)
    140 
    141    arch = "generic"
    142    for suffix in file_suffixes:
    143        if filename == "MacroAssembler-" + suffix:
    144            arch = suffix
    145            break
    146 
    147    return {"inline": inline, "arch": arch.replace("-", "_")}
    148 
    149 
    150 def get_macroassembler_definitions(filename):
    151    try:
    152        fileAnnot = get_file_annotation(filename)
    153    except Exception:
    154        return []
    155 
    156    style_section = False
    157    lines = ""
    158    signatures = []
    159    with open(filename, encoding="utf-8") as f:
    160        for line in f:
    161            if "//{{{ check_macroassembler_style" in line:
    162                if style_section:
    163                    raise "check_macroassembler_style section already opened."
    164                style_section = True
    165                braces_depth = 0
    166            elif "//}}} check_macroassembler_style" in line:
    167                style_section = False
    168            if not style_section:
    169                continue
    170 
    171            # Ignore preprocessor directives.
    172            if line.startswith("#"):
    173                continue
    174 
    175            # Remove comments from the processed line.
    176            line = re.sub(r"//.*", "", line)
    177 
    178            # Locate and count curly braces.
    179            open_curly_brace = line.find("{")
    180            was_braces_depth = braces_depth
    181            braces_depth = braces_depth + line.count("{") - line.count("}")
    182 
    183            # Raise an error if the check_macroassembler_style macro is used
    184            # across namespaces / classes scopes.
    185            if braces_depth < 0:
    186                raise "check_macroassembler_style annotations are not well scoped."
    187 
    188            # If the current line contains an opening curly brace, check if
    189            # this line combines with the previous one can be identified as a
    190            # MacroAssembler function signature.
    191            if open_curly_brace != -1 and was_braces_depth == 0:
    192                lines = lines + line[:open_curly_brace]
    193                if "MacroAssembler::" in lines:
    194                    signatures.extend(get_normalized_signatures(lines, fileAnnot))
    195                lines = ""
    196                continue
    197 
    198            # We do not aggregate any lines if we are scanning lines which are
    199            # in-between a set of curly braces.
    200            if braces_depth > 0:
    201                continue
    202            if was_braces_depth != 0:
    203                line = line[line.rfind("}") + 1 :]
    204 
    205            # This logic is used to remove template instantiation, static
    206            # variable definitions and function declaration from the next
    207            # function definition.
    208            last_semi_colon = line.rfind(";")
    209            if last_semi_colon != -1:
    210                lines = ""
    211                line = line[last_semi_colon + 1 :]
    212 
    213            # Aggregate lines of non-braced text, which corresponds to the space
    214            # where we are expecting to find function definitions.
    215            lines = lines + line
    216 
    217    return signatures
    218 
    219 
    220 def get_macroassembler_declaration(filename):
    221    style_section = False
    222    lines = ""
    223    signatures = []
    224    with open(filename, encoding="utf-8") as f:
    225        for line in f:
    226            if "//{{{ check_macroassembler_decl_style" in line:
    227                style_section = True
    228            elif "//}}} check_macroassembler_decl_style" in line:
    229                style_section = False
    230            if not style_section:
    231                continue
    232 
    233            # Ignore preprocessor directives.
    234            if line.startswith("#"):
    235                continue
    236 
    237            line = re.sub(r"//.*", "", line)
    238            if len(line.strip()) == 0 or "public:" in line or "private:" in line:
    239                lines = ""
    240                continue
    241 
    242            lines = lines + line
    243 
    244            # Continue until we have a complete declaration
    245            if ";" not in lines:
    246                continue
    247 
    248            # Skip member declarations: which are lines ending with a
    249            # semi-colon without any list of arguments.
    250            if ")" not in lines:
    251                lines = ""
    252                continue
    253 
    254            signatures.extend(get_normalized_signatures(lines))
    255            lines = ""
    256 
    257    return signatures
    258 
    259 
    260 def append_signatures(d, sigs):
    261    for s in sigs:
    262        if s["sig"] not in d:
    263            d[s["sig"]] = []
    264        d[s["sig"]].append(s["arch"])
    265    return d
    266 
    267 
    268 def generate_file_content(signatures):
    269    output = []
    270    for s in sorted(signatures.keys()):
    271        archs = set(sorted(signatures[s]))
    272        archs -= all_unsupported_architectures_names
    273        if len(archs.symmetric_difference(architecture_independent)) == 0:
    274            output.append(s + ";\n")
    275            if s.startswith("inline"):
    276                # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
    277                # functions.  (Such annotation is already removed by the time
    278                # this function sees the signature here.)
    279                output.append("    is defined in MacroAssembler-inl.h\n")
    280            else:
    281                output.append("    is defined in MacroAssembler.cpp\n")
    282        else:
    283            if len(archs.symmetric_difference(all_architecture_names)) == 0:
    284                output.append(s + " PER_ARCH;\n")
    285            elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
    286                output.append(s + " PER_SHARED_ARCH;\n")
    287            else:
    288                output.append(s + " DEFINED_ON(" + ", ".join(sorted(archs)) + ");\n")
    289            for a in sorted(archs):
    290                a = a.replace("_", "-")
    291                masm = "%s/MacroAssembler-%s" % (a, a)
    292                if s.startswith("inline"):
    293                    output.append("    is defined in %s-inl.h\n" % masm)
    294                else:
    295                    output.append("    is defined in %s.cpp\n" % masm)
    296    return output
    297 
    298 
    299 def check_style():
    300    # We read from the header file the signature of each function.
    301    decls = dict()  # type: dict(signature => ['x86', 'x64'])
    302 
    303    # We infer from each file the signature of each MacroAssembler function.
    304    defs = dict()  # type: dict(signature => ['x86', 'x64'])
    305 
    306    root_dir = os.path.join("js", "src", "jit")
    307    for dirpath, dirnames, filenames in os.walk(root_dir):
    308        for filename in filenames:
    309            if "MacroAssembler" not in filename:
    310                continue
    311 
    312            filepath = os.path.join(dirpath, filename).replace("\\", "/")
    313 
    314            if filepath.endswith("MacroAssembler.h"):
    315                decls = append_signatures(
    316                    decls, get_macroassembler_declaration(filepath)
    317                )
    318            defs = append_signatures(defs, get_macroassembler_definitions(filepath))
    319 
    320    if not decls or not defs:
    321        raise Exception("Did not find any definitions or declarations")
    322 
    323    # Compare declarations and definitions output.
    324    difflines = difflib.unified_diff(
    325        generate_file_content(decls),
    326        generate_file_content(defs),
    327        fromfile="check_macroassembler_style.py declared syntax",
    328        tofile="check_macroassembler_style.py found definitions",
    329    )
    330    ok = True
    331    for diffline in difflines:
    332        ok = False
    333        print(diffline, end="")
    334    return ok
    335 
    336 
    337 def main():
    338    ok = check_style()
    339 
    340    if ok:
    341        print("TEST-PASS | check_macroassembler_style.py | ok")
    342    else:
    343        print(
    344            "TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output;  diff is above"  # noqa: E501
    345        )
    346 
    347    sys.exit(0 if ok else 1)
    348 
    349 
    350 if __name__ == "__main__":
    351    main()