tor-browser

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

generate_static_pref_list.py (16216B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 # You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import os
      6 import sys
      7 from collections import defaultdict
      8 from io import StringIO
      9 
     10 import buildconfig
     11 import yaml
     12 from mozbuild.dirutils import ensureParentDir
     13 from mozbuild.preprocessor import Preprocessor
     14 from mozbuild.util import FileAvoidWrite
     15 
     16 VALID_KEYS = {
     17    "name",
     18    "type",
     19    "value",
     20    "mirror",
     21    "do_not_use_directly",
     22    "include",
     23    "rust",
     24    "set_spidermonkey_pref",
     25 }
     26 
     27 # Each key is a C++ type; its value is the equivalent non-atomic C++ type.
     28 VALID_BOOL_TYPES = {
     29    "bool": "bool",
     30    # These ones are defined in StaticPrefsBase.h.
     31    "RelaxedAtomicBool": "bool",
     32    "ReleaseAcquireAtomicBool": "bool",
     33    "SequentiallyConsistentAtomicBool": "bool",
     34 }
     35 
     36 VALID_TYPES = VALID_BOOL_TYPES.copy()
     37 VALID_TYPES.update({
     38    "int32_t": "int32_t",
     39    "uint32_t": "uint32_t",
     40    "float": "float",
     41    # These ones are defined in StaticPrefsBase.h.
     42    "RelaxedAtomicInt32": "int32_t",
     43    "RelaxedAtomicUint32": "uint32_t",
     44    "ReleaseAcquireAtomicInt32": "int32_t",
     45    "ReleaseAcquireAtomicUint32": "uint32_t",
     46    "SequentiallyConsistentAtomicInt32": "int32_t",
     47    "SequentiallyConsistentAtomicUint32": "uint32_t",
     48    "AtomicFloat": "float",
     49    "String": None,
     50    "DataMutexString": "nsACString",
     51 })
     52 
     53 # Map non-atomic C++ types to equivalent Rust types.
     54 RUST_TYPES = {
     55    "bool": "bool",
     56    "int32_t": "i32",
     57    "uint32_t": "u32",
     58    "float": "f32",
     59    "DataMutexString": "nsCString",
     60 }
     61 
     62 HEADER_LINE = (
     63    "// This file was generated by generate_static_pref_list.py from {input_filenames}."
     64    " DO NOT EDIT."
     65 )
     66 
     67 MIRROR_TEMPLATES = {
     68    "never": """\
     69 NEVER_PREF("{name}", {typ}, {value})
     70 """,
     71    "once": """\
     72 ONCE_PREF(
     73  "{name}",
     74   {base_id},
     75   {full_id},
     76  {typ}, {value}
     77 )
     78 """,
     79    "always": """\
     80 ALWAYS_PREF(
     81  "{name}",
     82   {base_id},
     83   {full_id},
     84  {typ}, {value}
     85 )
     86 """,
     87    "always_datamutex": """\
     88 ALWAYS_DATAMUTEX_PREF(
     89  "{name}",
     90   {base_id},
     91   {full_id},
     92  {typ}, {value}
     93 )
     94 """,
     95 }
     96 
     97 STATIC_PREFS_GROUP_H_TEMPLATE1 = """\
     98 // Include it to gain access to StaticPrefs::{group}_*.
     99 
    100 #ifndef mozilla_StaticPrefs_{group}_h
    101 #define mozilla_StaticPrefs_{group}_h
    102 """
    103 
    104 STATIC_PREFS_GROUP_H_TEMPLATE2 = """\
    105 #include "mozilla/StaticPrefListBegin.h"
    106 #include "mozilla/StaticPrefList_{group}.h"
    107 #include "mozilla/StaticPrefListEnd.h"
    108 
    109 #endif  // mozilla_StaticPrefs_{group}_h
    110 """
    111 
    112 STATIC_PREFS_C_GETTERS_TEMPLATE = """\
    113 extern "C" {typ} StaticPrefs_{full_id}() {{
    114  return mozilla::StaticPrefs::{full_id}();
    115 }}
    116 """
    117 
    118 STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE = """\
    119 extern "C" void StaticPrefs_{full_id}(nsACString *result) {{
    120  const auto preflock = mozilla::StaticPrefs::{full_id}();
    121  result->Append(*preflock);
    122 }}
    123 """
    124 
    125 
    126 def error(msg):
    127    raise ValueError(msg)
    128 
    129 
    130 def mk_id(name):
    131    "Replace '.' and '-' with '_', e.g. 'foo.bar-baz' becomes 'foo_bar_baz'."
    132    return name.replace(".", "_").replace("-", "_")
    133 
    134 
    135 def mk_group(pref):
    136    name = pref["name"]
    137    return mk_id(name.split(".", 1)[0])
    138 
    139 
    140 def check_pref_list(pref_list):
    141    # Pref names seen so far. Used to detect any duplicates.
    142    seen_names = set()
    143 
    144    # The previous pref. Used to detect mis-ordered prefs.
    145    prev_pref = None
    146 
    147    for pref in pref_list:
    148        # Check all given keys are known ones.
    149        for key in pref:
    150            if key not in VALID_KEYS:
    151                error(f"invalid key `{key}`")
    152 
    153        # 'name' must be present, valid, and in the right section.
    154        if "name" not in pref:
    155            error("missing `name` key")
    156        name = pref["name"]
    157        if type(name) is not str:
    158            error(f"non-string `name` value `{name}`")
    159        if "." not in name:
    160            error(f"`name` value `{name}` lacks a '.'")
    161        if name in seen_names:
    162            error(f"`{name}` pref is defined more than once")
    163        seen_names.add(name)
    164 
    165        # Prefs must be ordered appropriately.
    166        if prev_pref:
    167            if mk_group(prev_pref) > mk_group(pref):
    168                error(
    169                    "`{}` pref must come before `{}` pref".format(
    170                        name, prev_pref["name"]
    171                    )
    172                )
    173 
    174        # 'type' must be present and valid.
    175        if "type" not in pref:
    176            error(f"missing `type` key for pref `{name}`")
    177        typ = pref["type"]
    178        if typ not in VALID_TYPES:
    179            error(f"invalid `type` value `{typ}` for pref `{name}`")
    180 
    181        # 'value' must be present and valid.
    182        if "value" not in pref:
    183            error(f"missing `value` key for pref `{name}`")
    184        value = pref["value"]
    185        if typ in {"String", "DataMutexString"}:
    186            if type(value) is not str:
    187                error(
    188                    f"non-string `value` value `{value}` for `{typ}` pref `{name}`; "
    189                    "add double quotes"
    190                )
    191        elif typ in VALID_BOOL_TYPES:
    192            if value not in (True, False):
    193                error(f"invalid boolean value `{value}` for pref `{name}`")
    194 
    195        # 'mirror' must be present and valid.
    196        if "mirror" not in pref:
    197            error(f"missing `mirror` key for pref `{name}`")
    198        mirror = pref["mirror"]
    199        if typ.startswith("DataMutex"):
    200            mirror += "_datamutex"
    201        if mirror not in MIRROR_TEMPLATES:
    202            error(f"invalid `mirror` value `{mirror}` for pref `{name}`")
    203 
    204        # Check 'do_not_use_directly' if present.
    205        if "do_not_use_directly" in pref:
    206            do_not_use_directly = pref["do_not_use_directly"]
    207            if type(do_not_use_directly) is not bool:
    208                error(
    209                    f"non-boolean `do_not_use_directly` value `{do_not_use_directly}` for pref "
    210                    f"`{name}`"
    211                )
    212            if do_not_use_directly and mirror == "never":
    213                error(
    214                    "`do_not_use_directly` uselessly set with `mirror` value "
    215                    "`never` for pref `{}`".format(pref["name"])
    216                )
    217 
    218        # Check 'include' if present.
    219        if "include" in pref:
    220            include = pref["include"]
    221            if type(include) is not str:
    222                error(f"non-string `include` value `{include}` for pref `{name}`")
    223            if include.startswith("<") and not include.endswith(">"):
    224                error(
    225                    f"`include` value `{include}` starts with `<` but does not "
    226                    f"end with `>` for pref `{name}`"
    227                )
    228 
    229        # Check 'rust' if present.
    230        if "rust" in pref:
    231            rust = pref["rust"]
    232            if type(rust) is not bool:
    233                error(f"non-boolean `rust` value `{rust}` for pref `{name}`")
    234            if rust and mirror == "never":
    235                error(
    236                    "`rust` uselessly set with `mirror` value `never` for "
    237                    "pref `{}`".format(pref["name"])
    238                )
    239 
    240        prev_pref = pref
    241 
    242 
    243 def generate_code(pref_list, input_filenames):
    244    first_line = HEADER_LINE.format(input_filenames=", ".join(input_filenames))
    245 
    246    # The required includes for StaticPrefs_<group>.h.
    247    includes = defaultdict(set)
    248 
    249    # StaticPrefList_<group>.h contains all the pref definitions for this
    250    # group.
    251    static_pref_list_group_h = defaultdict(lambda: [first_line, ""])
    252 
    253    # StaticPrefsCGetters.cpp contains C getters for all the mirrored prefs,
    254    # for use by Rust code.
    255    static_prefs_c_getters_cpp = [first_line, ""]
    256 
    257    # static_prefs.rs contains C getter declarations and a macro.
    258    static_prefs_rs_decls = []
    259    static_prefs_rs_macro = []
    260 
    261    # Generate the per-pref code (spread across multiple files).
    262    for pref in pref_list:
    263        name = pref["name"]
    264        typ = pref["type"]
    265        value = pref["value"]
    266        mirror = pref["mirror"]
    267        do_not_use_directly = pref.get("do_not_use_directly")
    268        include = pref.get("include")
    269        rust = pref.get("rust")
    270 
    271        base_id = mk_id(pref["name"])
    272        full_id = base_id
    273        if mirror == "once":
    274            full_id += "_AtStartup"
    275        if do_not_use_directly:
    276            full_id += "_DoNotUseDirectly"
    277        if typ.startswith("DataMutex"):
    278            mirror += "_datamutex"
    279 
    280        group = mk_group(pref)
    281 
    282        if include:
    283            if not include.startswith("<"):
    284                # It's not a system header. Add double quotes.
    285                include = f'"{include}"'
    286            includes[group].add(include)
    287 
    288        if typ == "String":
    289            # Quote string literals, and escape double-quote chars.
    290            value = '"{}"'.format(value.replace('"', '\\"'))
    291        elif typ == "DataMutexString":
    292            # Quote string literals, and escape double-quote chars.
    293            value = '"{}"_ns'.format(value.replace('"', '\\"'))
    294        elif typ in VALID_BOOL_TYPES:
    295            # Convert Python bools to C++ bools.
    296            if value is True:
    297                value = "true"
    298            elif value is False:
    299                value = "false"
    300 
    301        # Append the C++ definition to the relevant output file's code.
    302        static_pref_list_group_h[group].append(
    303            MIRROR_TEMPLATES[mirror].format(
    304                name=name,
    305                base_id=base_id,
    306                full_id=full_id,
    307                typ=typ,
    308                value=value,
    309            )
    310        )
    311 
    312        if rust:
    313            passed_type = VALID_TYPES[typ]
    314            if passed_type == "nsACString":
    315                # Generate the C getter.
    316                static_prefs_c_getters_cpp.append(
    317                    STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE.format(full_id=full_id)
    318                )
    319 
    320                # Generate the C getter declaration, in Rust.
    321                decl = "    pub fn StaticPrefs_{full_id}(result: *mut nsstring::nsACString);"
    322                static_prefs_rs_decls.append(decl.format(full_id=full_id))
    323 
    324                # Generate the Rust macro entry.
    325                macro = '    ("{name}") => (unsafe {{ let mut result = $crate::nsCString::new(); $crate::StaticPrefs_{full_id}(&mut *result); result }});'
    326                static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id))
    327 
    328            else:
    329                # Generate the C getter.
    330                static_prefs_c_getters_cpp.append(
    331                    STATIC_PREFS_C_GETTERS_TEMPLATE.format(
    332                        typ=passed_type, full_id=full_id
    333                    )
    334                )
    335 
    336                # Generate the C getter declaration, in Rust.
    337                decl = "    pub fn StaticPrefs_{full_id}() -> {typ};"
    338                static_prefs_rs_decls.append(
    339                    decl.format(full_id=full_id, typ=RUST_TYPES[passed_type])
    340                )
    341 
    342                # Generate the Rust macro entry.
    343                macro = (
    344                    '    ("{name}") => (unsafe {{ $crate::StaticPrefs_{full_id}() }});'
    345                )
    346                static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id))
    347 
    348        # Delete this so that `group` can be reused below without Flake8
    349        # complaining.
    350        del group
    351 
    352    # StaticPrefListAll.h contains one `#include "mozilla/StaticPrefList_X.h`
    353    # line per pref group.
    354    static_pref_list_all_h = [first_line, ""]
    355    static_pref_list_all_h.extend(
    356        f'#include "mozilla/StaticPrefList_{group}.h"'
    357        for group in sorted(static_pref_list_group_h)
    358    )
    359    static_pref_list_all_h.append("")
    360 
    361    # StaticPrefsAll.h contains one `#include "mozilla/StaticPrefs_X.h` line per
    362    # pref group.
    363    static_prefs_all_h = [first_line, ""]
    364    static_prefs_all_h.extend(
    365        f'#include "mozilla/StaticPrefs_{group}.h"'
    366        for group in sorted(static_pref_list_group_h)
    367    )
    368    static_prefs_all_h.append("")
    369 
    370    # StaticPrefs_<group>.h wraps StaticPrefList_<group>.h. It is the header
    371    # used directly by application code.
    372    static_prefs_group_h = defaultdict(list)
    373    for group in sorted(static_pref_list_group_h):
    374        static_prefs_group_h[group] = [first_line]
    375        static_prefs_group_h[group].append(
    376            STATIC_PREFS_GROUP_H_TEMPLATE1.format(group=group)
    377        )
    378        if group in includes:
    379            # Add any necessary includes, from 'h_include' values.
    380            for include in sorted(includes[group]):
    381                static_prefs_group_h[group].append(f"#include {include}")
    382            static_prefs_group_h[group].append("")
    383        static_prefs_group_h[group].append(
    384            STATIC_PREFS_GROUP_H_TEMPLATE2.format(group=group)
    385        )
    386 
    387    # static_prefs.rs contains the Rust macro getters.
    388    static_prefs_rs = [first_line, "", "pub use nsstring::nsCString;", 'extern "C" {']
    389    static_prefs_rs.extend(static_prefs_rs_decls)
    390    static_prefs_rs.extend(["}", "", "#[macro_export]", "macro_rules! pref {"])
    391    static_prefs_rs.extend(static_prefs_rs_macro)
    392    static_prefs_rs.extend(["}", ""])
    393 
    394    def fold(lines):
    395        return "\n".join(lines)
    396 
    397    return {
    398        "static_pref_list_all_h": fold(static_pref_list_all_h),
    399        "static_prefs_all_h": fold(static_prefs_all_h),
    400        "static_pref_list_group_h": {
    401            k: fold(v) for k, v in static_pref_list_group_h.items()
    402        },
    403        "static_prefs_group_h": {k: fold(v) for k, v in static_prefs_group_h.items()},
    404        "static_prefs_c_getters_cpp": fold(static_prefs_c_getters_cpp),
    405        "static_prefs_rs": fold(static_prefs_rs),
    406    }
    407 
    408 
    409 def emit_code(fd, *pref_list_filenames):
    410    pp = Preprocessor()
    411    pp.context.update(buildconfig.defines["ALLDEFINES"])
    412 
    413    # A necessary hack until MOZ_DEBUG_FLAGS are part of buildconfig.defines.
    414    if buildconfig.substs.get("MOZ_DEBUG"):
    415        pp.context["DEBUG"] = "1"
    416 
    417    if buildconfig.substs.get("TARGET_CPU") == "aarch64":
    418        pp.context["MOZ_AARCH64"] = True
    419 
    420    if buildconfig.substs.get("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"):
    421        pp.context["MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"] = True
    422 
    423    pref_list = []
    424    input_files = []
    425    for this_filename in pref_list_filenames:
    426        pp.out = StringIO()
    427        pp.do_filter("substitution")
    428        pp.do_include(this_filename)
    429 
    430        try:
    431            this_pref_list = yaml.safe_load(pp.out.getvalue())
    432            check_pref_list(this_pref_list)
    433            pref_list.extend(this_pref_list)
    434            input_files.append(
    435                os.path.relpath(
    436                    this_filename,
    437                    os.environ.get("GECKO_PATH", os.environ.get("TOPSRCDIR")),
    438                )
    439            )
    440        except (OSError, ValueError) as e:
    441            print(f"{this_filename}: error:\n  {e}\n")
    442            sys.exit(1)
    443 
    444    code = generate_code(pref_list, input_files)
    445    # When generating multiple files from a script, the build system treats the
    446    # first named output file (StaticPrefListAll.h in this case) specially -- it
    447    # is created elsewhere, and written to via `fd`.
    448    fd.write(code["static_pref_list_all_h"])
    449 
    450    # We must create the remaining output files ourselves. This requires
    451    # creating the output directory directly if it doesn't already exist.
    452    ensureParentDir(fd.name)
    453    init_dirname = os.path.dirname(fd.name)
    454    dirname = os.path.dirname(init_dirname)
    455 
    456    with FileAvoidWrite(os.path.join(dirname, "StaticPrefsAll.h")) as output_file:
    457        output_file.write(code["static_prefs_all_h"])
    458 
    459    for group, text in sorted(code["static_pref_list_group_h"].items()):
    460        filename = f"StaticPrefList_{group}.h"
    461        with FileAvoidWrite(os.path.join(init_dirname, filename)) as group_file:
    462            group_file.write(text)
    463 
    464    for group, text in sorted(code["static_prefs_group_h"].items()):
    465        filename = f"StaticPrefs_{group}.h"
    466        with FileAvoidWrite(os.path.join(dirname, filename)) as prefs_file:
    467            prefs_file.write(text)
    468 
    469    with FileAvoidWrite(
    470        os.path.join(init_dirname, "StaticPrefsCGetters.cpp")
    471    ) as cpp_file:
    472        cpp_file.write(code["static_prefs_c_getters_cpp"])
    473 
    474    with FileAvoidWrite(os.path.join(dirname, "static_prefs.rs")) as rust_file:
    475        rust_file.write(code["static_prefs_rs"])