| #!/usr/bin/env python3 |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import datetime |
| import os |
| import platform |
| import re |
| import subprocess |
| import sys |
| |
| from typing import List |
| |
| # Where is this script? |
| DIR = os.path.dirname(os.path.realpath(__file__)) |
| |
| # What platform are we on? |
| HOST_PLARFORM = "{}-{}".format( |
| platform.system().lower().replace("darwin", "mac"), |
| { |
| "x86_64": "x64", |
| "aarch64": "arm64", |
| }[platform.machine()], |
| ) |
| |
| # Where is gn? |
| GN = os.path.realpath( |
| os.path.join( |
| DIR, '../../../../prebuilt/third_party/gn', HOST_PLARFORM, 'gn')) |
| FUCHSIA_DIR = os.environ.get('FUCHSIA_DIR') |
| FUCHSIA_BUILD_DIR = os.environ.get('FUCHSIA_BUILD_DIR') |
| if FUCHSIA_BUILD_DIR is None: |
| print('Run "fx exec %s".' % sys.argv[0]) |
| sys.exit(1) |
| FIDL_FORMAT = os.path.join(FUCHSIA_BUILD_DIR, 'host_x64', 'fidl-format') |
| RUST_FORMAT = os.path.join(FUCHSIA_DIR, 'prebuilt', 'third_party', 'rust_tools', 'linux-x64', 'bin', 'rustfmt') |
| |
| |
| # IdentifierDef represents individual parts of an identifier, along with a tag. |
| # An identifier definition produces an `Identifier` when styled. |
| # |
| # For instance, the defintion |
| # |
| # my_super_identifier:8 |
| # |
| # has three parts [my, super, identifer] and tag 8. |
| class IdentifierDef: |
| |
| def __init__(self, parts: List[str], tag: int, bindings_denylist: str): |
| self.parts = parts |
| self.tag = tag |
| self.bindings_denylist = bindings_denylist |
| |
| |
| class Identifier: |
| |
| def __init__(self, ident: str, tag: int, bindings_denylist: str): |
| self.ident = ident |
| self.tag = tag |
| self.bindings_denylist = bindings_denylist |
| |
| |
| # Define ways that identifiers may be rendered |
| STYLES = [] |
| |
| |
| def style(func): |
| STYLES.append((func.__name__, func)) |
| |
| |
| @style |
| def lower(ident): |
| return '_'.join(w.lower() for w in ident) |
| |
| |
| @style |
| def upper(ident): |
| return '_'.join(w.upper() for w in ident) |
| |
| |
| @style |
| def camel(ident): |
| return ''.join(w.capitalize() for w in ident) |
| |
| |
| # Define places that identifiers may appear in a FIDL library: |
| USES = [] |
| |
| |
| def use(func): |
| USES.append((func.__name__.replace('_', '.'), func)) |
| |
| |
| @use |
| def constants(f, idents: List[Identifier]): |
| for ident in idents: |
| # TODO(fxbug.dev/38124): Enable this case once we've clarified these edge cases |
| # and chosen a way to unambiguously reference the root library. Currently, |
| # "const uint32 uint32 = 1;" will fail with an includes-cycle fidlc error. |
| if ident.ident == "uint32": |
| continue |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('const uint32 %s = 1;\n' % ident.ident) |
| |
| |
| @use |
| def using(f, idents: List[Identifier]): |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('using %s = vector;\n' % ident.ident) |
| |
| |
| # TODO(ianloic): Make this test work. It requires N libraries to import for N |
| # identifiers. That doesn't fit well into the model of this test. |
| #@use |
| #def using_as(f, idents): |
| # for ident in idents: |
| # f.write('using fuchsia.mem as %s;\n' % ident) |
| |
| |
| @use |
| def enums(f, idents: List[Identifier]): |
| # enums with every dangerous name |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('enum %s { MEMBER = 1; };\n' % ident.ident) |
| |
| # enum with every dangerous field name |
| f.write('enum DangerousMembers {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' %s = %d;\n' % (ident.ident, ident.tag - 1)) |
| f.write('};\n') |
| |
| |
| @use |
| def struct_types(f, idents: List[Identifier]): |
| # structs with every dangerous name |
| f.write('using membertype = uint32;\n') |
| for ident in idents: |
| # TODO(fxbug.dev/8042): Having a declaration with same same name as what is |
| # aliased causes a cycle. |
| if ident.ident == "uint32": |
| continue |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('struct %s { membertype member = 1; };\n' % ident.ident) |
| |
| # a struct with every dangerous name as the field type |
| f.write('struct DangerousMembers {\n') |
| for ident in idents: |
| # dangerous field type |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' %s f%d;\n' % (ident.ident, ident.tag - 1)) |
| f.write('};\n') |
| |
| |
| @use |
| def struct_names(f, idents: List[Identifier]): |
| # a struct with every dangerous name as the field name |
| f.write('struct DangerousMembers {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' uint32 %s;\n' % ident.ident) |
| f.write('};\n') |
| |
| |
| # TODO(fxbug.dev/8081) |
| # Temporarily disabled due to superlinear compiler time and peak memory usage. |
| # @use |
| # def union_names(f, idents): |
| # # unions with every dangerous name |
| # f.write('using membertype = uint32;\n') |
| # for ident in idents: |
| # # TODO(fxbug.dev/8042): Having a declaration with same same name as what is |
| # # aliased causes a cycle. |
| # if ident == "uint32": |
| # continue |
| # f.write('union %s { membertype member; };\n' % ident) |
| # |
| # # a union with every dangerous name as the field type |
| # f.write('union DangerousMembers {\n') |
| # for i, ident in enumerate(idents): |
| # # dangerous field type |
| # f.write(' %s f%d;\n' % (ident, i)) |
| # f.write('};\n') |
| # |
| # |
| # @use |
| # def union_types(f, idents): |
| # # a union with every dangerous name as the field name |
| # f.write('union DangerousMembers {\n') |
| # for i, ident in enumerate(idents): |
| # f.write(' uint32 %s;\n' % (ident)) |
| # f.write('};\n') |
| |
| |
| @use |
| def table_names(f, idents: List[Identifier]): |
| # tables with every dangerous name |
| f.write('using membertype = uint32;\n') |
| for ident in idents: |
| # TODO(fxbug.dev/8042): Having a declaration with same same name as what is |
| # aliased causes a cycle. |
| if ident.ident == "uint32": |
| continue |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('table %s { 1: membertype member; };\n' % ident.ident) |
| # a table with every dangerous name as the field type |
| f.write('table DangerousMembers {\n') |
| for ident in idents: |
| # dangerous field type |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' %s: %s f%s;\n' % (ident.tag, ident.ident, ident.tag - 1)) |
| f.write('};\n') |
| |
| |
| @use |
| def table_fields(f, idents: List[Identifier]): |
| # a table with every dangerous name as the field name |
| f.write('table DangerousMembers {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' %d: uint32 %s;\n' % (ident.tag, ident.ident)) |
| f.write('};\n') |
| |
| |
| @use |
| def protocol_names(f, idents: List[Identifier]): |
| # a protocols with every dangerous name |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write('protocol %s { JustOneMethod(); };\n' % ident.ident) |
| |
| |
| @use |
| def method_names(f, idents: List[Identifier]): |
| # a protocol with every dangerous name as a method name |
| f.write('protocol DangerousMethods {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' %s();\n' % ident.ident) |
| f.write('};\n') |
| |
| |
| @use |
| def event_names(f, idents: List[Identifier]): |
| # a protocol with every dangerous name as an event name |
| f.write('protocol DangerousEvents {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' -> %s();\n' % ident.ident) |
| f.write('};\n') |
| |
| |
| @use |
| def method_request_arguments(f, idents: List[Identifier]): |
| # a protocol with every dangerous name as a request argument |
| f.write('using argtype = uint32;\n') |
| f.write('protocol DangerousRequestArguments {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' Method%d(argtype %s);\n' % (ident.tag - 1, ident.ident)) |
| f.write('};\n') |
| |
| |
| @use |
| def method_response_arguments(f, idents: List[Identifier]): |
| # a protocol with every dangerous name as a response argument |
| f.write('using argtype = uint32;\n') |
| f.write('protocol DangerousResponseArguments {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write( |
| ' Method%d() -> (argtype %s);\n' % (ident.tag - 1, ident.ident)) |
| f.write('};\n') |
| |
| |
| @use |
| def method_event_arguments(f, idents: List[Identifier]): |
| # a protocol with every dangerous name as a event argument |
| f.write('using argtype = uint32;\n') |
| f.write('protocol DangerousResponseArguments {\n') |
| for ident in idents: |
| if ident.bindings_denylist: |
| f.write('[BindingsDenylist="%s"]\n' % ident.bindings_denylist) |
| f.write(' -> Event%d(argtype %s);\n' % (ident.tag - 1, ident.ident)) |
| f.write('};\n') |
| |
| |
| def generated(prefix): |
| """Return a header line indicating that this is a generated file.""" |
| return """{prefix} Copyright 2019 The Fuchsia Authors. All rights reserved. |
| {prefix} Use of this source code is governed by a BSD-style license that can be |
| {prefix} found in the LICENSE file. |
| {prefix} Generated by {generator}. |
| """.format( |
| prefix=prefix, |
| year=datetime.datetime.now().year, |
| generator=os.path.basename(__file__)) |
| |
| |
| def library_target(library_name): |
| return '//src/tests/fidl/dangerous_identifiers/fidl:%s' % library_name |
| |
| |
| def dangerous_identifiers() -> List[IdentifierDef]: |
| """Load a list of dangerous identifiers definitions from the source tree. |
| |
| Verifies that all definitions are well formed, and that their tags are unique. |
| """ |
| file_path = os.path.join(DIR, 'dangerous_identifiers.txt') |
| lines = ( |
| line.strip() |
| for line in open(file_path).readlines() |
| if not line.startswith('#')) |
| |
| failed = False |
| idents = [] |
| tags_seen = set() |
| for line in lines: |
| line_search = re.match( |
| r'^([a-z0-9_]+):([0-9]+)(\s+BindingsDenylist=(.+))?$', line) |
| if line_search: |
| parts = line_search.group(1).split('_') |
| tag = int(line_search.group(2)) |
| bindings_denylist = "" |
| if line_search.group(4): |
| bindings_denylist = line_search.group(4) |
| if tag not in tags_seen: |
| tags_seen.add(tag) |
| idents.append(IdentifierDef(parts, tag, bindings_denylist)) |
| else: |
| failed = True |
| print("line '%s' has duplicate tag" % line) |
| else: |
| failed = True |
| print("line '%s' is malformed" % line) |
| |
| if failed: |
| exit(1) |
| return idents |
| |
| |
| def generate_fidl(identifier_defs: List[IdentifierDef]) -> List[str]: |
| """Generate FIDL libraries for the specified identifier definitions. |
| |
| Return the list of library names. |
| """ |
| directory = os.path.join(DIR, 'fidl') |
| os.makedirs(directory, exist_ok=True) |
| prefix = 'fidl.test.dangerous' |
| # generate FIDL libraries |
| library_names = [] |
| for style_name, style_func in STYLES: |
| for use_name, use_func in USES: |
| library_name = '%s.%s.%s' % (prefix, use_name, style_name) |
| fidl_file = os.path.join(directory, '%s.test.fidl' % library_name) |
| with open(fidl_file, 'w') as f: |
| f.write(generated('//')) |
| f.write('library %s;\n' % library_name) |
| use_func( |
| f, [ |
| Identifier( |
| style_func(r.parts), r.tag, r.bindings_denylist) |
| for r in identifier_defs |
| ]) |
| subprocess.check_output([FIDL_FORMAT, '-i', fidl_file]) |
| library_names.append(library_name) |
| |
| # generate BUILD.gn for FIDL libraries |
| build_file = os.path.join(directory, 'BUILD.gn') |
| with open(build_file, 'w') as build_gn: |
| build_gn.write(generated('#')) |
| build_gn.write('import("//build/fidl/fidl.gni")\n\n') |
| build_gn.write('group("fidl") {\ndeps=[\n') |
| for library_name in library_names: |
| build_gn.write(' ":%s",\n' % library_name) |
| build_gn.write(']}\n') |
| for library_name in library_names: |
| build_gn.write( |
| 'fidl("%s") {\n sources = [ "%s.test.fidl" ] }\n\n' % |
| (library_name, library_name)) |
| subprocess.check_output([GN, 'format', build_file]) |
| |
| return library_names |
| |
| |
| def generate_cpp(libraries: List[str]) -> None: |
| directory = os.path.join(DIR, 'cpp') |
| os.makedirs(directory, exist_ok=True) |
| |
| # generate BUILD.gn for C++ test |
| build_file = os.path.join(directory, 'BUILD.gn') |
| with open(build_file, 'w') as build_gn: |
| build_gn.write(generated('#')) |
| for library_name in libraries: |
| build_gn.write( |
| """source_set("%s_cpp") { |
| output_name = "cpp_fidl_dangerous_identifiers_test_%s" |
| sources = [ "%s_test.cc" ] |
| deps = [ |
| """ % (library_name, library_name, library_name)) |
| build_gn.write(' "%s",\n' % library_target(library_name)) |
| build_gn.write(' ]\n}\n') |
| build_gn.write("""group("cpp") { |
| deps = [""") |
| for library_name in libraries: |
| build_gn.write(""" |
| ":%s_cpp", |
| """ % (library_name)) |
| build_gn.write(""" |
| ] |
| }""") |
| |
| subprocess.check_output([GN, 'format', build_file]) |
| |
| |
| def generate_rust(libraries: List[str]) -> None: |
| os.makedirs(os.path.join(DIR, 'rust', 'src'), exist_ok=True) |
| |
| # Allowlist of libraries we can compile in Rust |
| # TODO(fxbug.dev/60219): Make all libraries pass. |
| allowed_libraries = { |
| 'fidl.test.dangerous.constants.lower', |
| 'fidl.test.dangerous.constants.camel', |
| 'fidl.test.dangerous.constants.upper', |
| 'fidl.test.dangerous.using.lower', |
| 'fidl.test.dangerous.using.camel', |
| 'fidl.test.dangerous.using.upper', |
| 'fidl.test.dangerous.enums.lower', |
| 'fidl.test.dangerous.struct.types.lower', |
| 'fidl.test.dangerous.struct.types.upper', |
| 'fidl.test.dangerous.table.names.lower', |
| 'fidl.test.dangerous.table.names.camel', |
| 'fidl.test.dangerous.table.names.upper', |
| } |
| |
| # BUILD.gn |
| build_file = os.path.join(DIR, 'rust', 'BUILD.gn') |
| with open(build_file, 'w') as f: |
| f.write(generated('#')) |
| f.write('\n') |
| f.write('import("//build/rust/rustc_test.gni")\n') |
| f.write('import("//tools/fidl/measure-tape/measure_tape.gni")\n') |
| f.write('\n') |
| f.write('rustc_test("rust") {\n') |
| f.write(' sources = [ "src/lib.rs" ]\n') |
| f.write(' deps = [\n') |
| for library_name in libraries: |
| f.write(' ') |
| if library_name not in allowed_libraries: |
| f.write('# ') |
| f.write('"%s-rustc",\n' % library_target(library_name)) |
| f.write(' ]\n') |
| f.write('}\n') |
| subprocess.check_output([GN, 'format', build_file]) |
| |
| # lib.rs |
| lib_rs = os.path.join(DIR, 'rust', 'src', 'lib.rs') |
| with open(lib_rs, 'w') as f: |
| f.write(generated('//')) |
| f.write('#![cfg(test)]\n') |
| f.write('#![allow(unused_imports)]\n') |
| f.write('use {\n') |
| for library_name in libraries: |
| f.write(' ') |
| if library_name not in allowed_libraries: |
| f.write('// ') |
| f.write('fidl_%s,\n' % library_name.replace('.', '_')) |
| f.write('};\n') |
| subprocess.check_output([RUST_FORMAT, lib_rs]) |
| |
| |
| if __name__ == '__main__': |
| identifier_defs = dangerous_identifiers() |
| library_names = generate_fidl(identifier_defs) |
| generate_cpp(library_names) |
| generate_rust(library_names) |