From patchwork Thu Mar 30 09:56:09 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cheng-Hao Yang X-Patchwork-Id: 18494 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id A399BC0F2A for ; Thu, 30 Mar 2023 10:18:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D6D966274E; Thu, 30 Mar 2023 12:18:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1680171504; bh=B712IkL4lhxYhzmJXV3f8StKSwghSiJbYYg1ERX4yCY=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=F4oKsd1xdMR2L/G8+eLzTquUwU3H+oKF8qcLZOhSqmBCtJnl93jEFyWl/IzEG4iMc cNRV+i+58yYT/TiPzt1Pj4kw6oHM0un3rkNDFqJ/u/QeMnIdDXFolqQ6c4wNggkoqn Juyz12CNNG+pP2iH0SOd/9lIU/a/gzUgGMblSJTxNEDM9J6AmjBUF6XXQDdEeqHxEe hiYcpLPg6eT3nq3KkISd2zWGUOCN0tD66i8a2eoNIfr0uUyGSf0AiguwbkAb1nukRH 70BbzwiV7ofVo4fswayiqaw5p4nw1MkziczivAc0smF0+h+sKKK8gdrD7kgmed0N8I VpiivXtS3qvHA== Received: from mail-pl1-x634.google.com (mail-pl1-x634.google.com [IPv6:2607:f8b0:4864:20::634]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A8DC26273D for ; Thu, 30 Mar 2023 11:56:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="VHVwBAhO"; dkim-atps=neutral Received: by mail-pl1-x634.google.com with SMTP id ix20so17584183plb.3 for ; Thu, 30 Mar 2023 02:56:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1680170179; x=1682762179; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ZXGojA4cwxlkbX0VLGcEhXWCeWtr6aAfFfrtK7rIOU4=; b=VHVwBAhOyjWIcf1IIEyluZkcdiZjK6AZmHMnF7/GEmse6y2b1SeHhYxYjPipt6Oo+i j6+eLmyTIPoDLQ+3S+iUDKJPXQOcfuNgzV3FClOnTXMD/x3LyXNgZOZW19kIq9NLSiuy veHRSZENU45s8j4ZcgwKiTVz6OZGd2A3uaRI8= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1680170179; x=1682762179; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ZXGojA4cwxlkbX0VLGcEhXWCeWtr6aAfFfrtK7rIOU4=; b=Z66lnIEylt5F8oLBBXiV20qP3T+Gbauc704isHFK0GO6Thy07ce0fAG4h8gDgaWOjf q2IzU2+uBEnCkFFFbUa488/0IRHzVpHMhf9FnIIO0tw/qNhkffpJ/Nvv9M/G9kvI4+Fq V90Goo7DdGGHoSeZJNE3djSeiJQ9wR0tDTTgT54XSypByy+Oz/LMYXooJpjA2A3Nrigk lyd0zunDII8AFUwQZMbptjA2Y7nSS2A36bquUAdhMkgnWMH4vBcJ1SCgrA9a/fR6ufAu ZMHGWbqQ5mlFM9SzrRGieNkths8jzobrbIdOBfY8j4g5+9Q4AEoRjqZoI8ff7PJsvyTG nsGA== X-Gm-Message-State: AO0yUKX8Y0typGvoysBBuXm2iZhx+nuh4UrqwO4A+izE/NrBDA5d+Aas wRAVHuyHv3ZEytrdu40wUZoJVRuMvn5wy9GOtPQ= X-Google-Smtp-Source: AK7set9zqZY8f3CuDQrfQiY/kGlFEkli88wNOt84IOsEgD0WFm2ajlJlFQEGZS5+71cLzjoTniXl/A== X-Received: by 2002:a05:6a20:af94:b0:da:66f4:5aea with SMTP id ds20-20020a056a20af9400b000da66f45aeamr17398724pzb.51.1680170176368; Thu, 30 Mar 2023 02:56:16 -0700 (PDT) Received: from chenghaoyang-low.c.googlers.com.com (112.157.221.35.bc.googleusercontent.com. [35.221.157.112]) by smtp.gmail.com with ESMTPSA id 14-20020aa7914e000000b005e099d7c30bsm24422752pfi.205.2023.03.30.02.56.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Mar 2023 02:56:15 -0700 (PDT) X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Mar 2023 09:56:09 +0000 Message-Id: <20230330095609.3341579-2-chenghaoyang@google.com> X-Mailer: git-send-email 2.40.0.348.gf938b09366-goog In-Reply-To: <20230330095609.3341579-1-chenghaoyang@google.com> References: <20230330095609.3341579-1-chenghaoyang@google.com> MIME-Version: 1.0 X-Mailman-Approved-At: Thu, 30 Mar 2023 12:18:24 +0200 Subject: [libcamera-devel] [PATCH v1 1/1] utils: ipc: Update mojo X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Harvey Yang via libcamera-devel From: Cheng-Hao Yang Reply-To: Harvey Yang Cc: Harvey Yang , Harvey Yang Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Update mojo from the Chromium repository. The commit from which this was taken is: [white-space] Change DOM/HTML/SVG to set longhands of `white-space` The update-mojo.sh script was used for this update. Signed-off-by: Harvey Yang --- utils/ipc/mojo/README | 2 +- utils/ipc/mojo/public/LICENSE | 2 +- utils/ipc/mojo/public/tools/BUILD.gn | 8 +- utils/ipc/mojo/public/tools/bindings/BUILD.gn | 34 +- .../ipc/mojo/public/tools/bindings/README.md | 148 ++- .../public/tools/bindings/checks/__init__.py | 0 .../bindings/checks/mojom_attributes_check.py | 168 ++++ .../checks/mojom_attributes_check_unittest.py | 186 ++++ .../checks/mojom_definitions_check.py | 34 + .../checks/mojom_restrictions_check.py | 102 +++ .../mojom_restrictions_checks_unittest.py | 254 ++++++ .../tools/bindings/concatenate-files.py | 5 +- ...concatenate_and_replace_closure_exports.py | 8 +- .../tools/bindings/gen_data_files_list.py | 2 +- .../tools/bindings/generate_type_mappings.py | 3 +- .../tools/bindings/minify_with_terser.py | 47 + .../ipc/mojo/public/tools/bindings/mojom.gni | 860 ++++++++++-------- .../bindings/mojom_bindings_generator.py | 62 +- .../mojom_bindings_generator_unittest.py | 6 +- .../tools/bindings/validate_typemap_config.py | 4 +- utils/ipc/mojo/public/tools/mojom/BUILD.gn | 17 + .../mojom/check_stable_mojom_compatibility.py | 46 +- ...eck_stable_mojom_compatibility_unittest.py | 87 +- .../mojo/public/tools/mojom/const_unittest.py | 2 +- .../mojo/public/tools/mojom/enum_unittest.py | 30 +- .../mojo/public/tools/mojom/mojom/BUILD.gn | 3 +- .../mojo/public/tools/mojom/mojom/error.py | 2 +- .../mojo/public/tools/mojom/mojom/fileutil.py | 2 +- .../tools/mojom/mojom/fileutil_unittest.py | 2 +- .../tools/mojom/mojom/generate/check.py | 26 + .../tools/mojom/mojom/generate/generator.py | 8 +- .../mojom/generate/generator_unittest.py | 2 +- .../tools/mojom/mojom/generate/module.py | 649 ++++++++----- .../mojom/mojom/generate/module_unittest.py | 2 +- .../public/tools/mojom/mojom/generate/pack.py | 125 ++- .../mojom/mojom/generate/pack_unittest.py | 2 +- .../mojom/mojom/generate/template_expander.py | 2 +- .../tools/mojom/mojom/generate/translate.py | 408 ++++++++- .../mojom/generate/translate_unittest.py | 39 +- .../public/tools/mojom/mojom/parse/ast.py | 117 +-- .../tools/mojom/mojom/parse/ast_unittest.py | 6 +- .../mojom/mojom/parse/conditional_features.py | 14 +- .../parse/conditional_features_unittest.py | 130 ++- .../public/tools/mojom/mojom/parse/lexer.py | 6 +- .../tools/mojom/mojom/parse/lexer_unittest.py | 3 +- .../public/tools/mojom/mojom/parse/parser.py | 24 +- .../mojom/mojom/parse/parser_unittest.py | 34 +- .../mojo/public/tools/mojom/mojom_parser.py | 119 ++- .../tools/mojom/mojom_parser_test_case.py | 4 +- .../tools/mojom/mojom_parser_unittest.py | 31 +- .../tools/mojom/stable_attribute_unittest.py | 2 +- .../mojo/public/tools/mojom/union_unittest.py | 44 + .../mojom/version_compatibility_unittest.py | 66 +- .../public/tools/run_all_python_unittests.py | 8 +- utils/ipc/tools/README | 2 +- utils/ipc/tools/diagnosis/crbug_1001171.py | 2 +- 56 files changed, 3061 insertions(+), 940 deletions(-) create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README index d5c24fc3..9a2979d3 100644 --- a/utils/ipc/mojo/README +++ b/utils/ipc/mojo/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from 9c138d992bfc of Chromium. Do not +Files in this directory are imported from e2b2277a00e37 of Chromium. Do not modify them manually. diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE index 972bb2ed..513e8a6a 100644 --- a/utils/ipc/mojo/public/LICENSE +++ b/utils/ipc/mojo/public/LICENSE @@ -1,4 +1,4 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. +// Copyright 2014 The Chromium Authors // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn index eb6391a6..5328a34a 100644 --- a/utils/ipc/mojo/public/tools/BUILD.gn +++ b/utils/ipc/mojo/public/tools/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,7 +10,11 @@ group("mojo_python_unittests") { "run_all_python_unittests.py", "//testing/scripts/run_isolated_script_test.py", ] - deps = [ "//mojo/public/tools/mojom/mojom:tests" ] + deps = [ + "//mojo/public/tools/bindings:tests", + "//mojo/public/tools/mojom:tests", + "//mojo/public/tools/mojom/mojom:tests", + ] data_deps = [ "//testing:test_scripts_shared", "//third_party/catapult/third_party/typ/", diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn index 3e242532..203e476c 100644 --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn @@ -1,13 +1,11 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. +# Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/python.gni") import("//mojo/public/tools/bindings/mojom.gni") import("//third_party/jinja2/jinja2.gni") -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. -python2_action("precompile_templates") { +action("precompile_templates") { sources = mojom_generator_sources sources += [ "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", @@ -26,7 +24,6 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl", - "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module.h.tmpl", @@ -65,9 +62,6 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/java_templates/struct.java.tmpl", "$mojom_generator_root/generators/java_templates/union.java.tmpl", "$mojom_generator_root/generators/js_templates/enum_definition.tmpl", - "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl", - "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl", - "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl", "$mojom_generator_root/generators/js_templates/fuzzing.tmpl", "$mojom_generator_root/generators/js_templates/interface_definition.tmpl", "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl", @@ -93,8 +87,14 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl", "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl", "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl", + "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl", "$mojom_generator_root/generators/ts_templates/module_definition.tmpl", - "$mojom_generator_root/generators/ts_templates/mojom.tmpl", + "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/union_definition.tmpl", + "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.cc.tmpl", + "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.h.tmpl", + "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_macros.tmpl", ] script = mojom_generator_script @@ -102,8 +102,9 @@ python2_action("precompile_templates") { outputs = [ "$target_gen_dir/cpp_templates.zip", "$target_gen_dir/java_templates.zip", - "$target_gen_dir/mojolpm_templates.zip", + "$target_gen_dir/webui_js_bridge_templates.zip", "$target_gen_dir/js_templates.zip", + "$target_gen_dir/mojolpm_templates.zip", "$target_gen_dir/ts_templates.zip", ] args = [ @@ -113,3 +114,16 @@ python2_action("precompile_templates") { "precompile", ] } + +group("tests") { + data = [ + mojom_generator_script, + "checks/mojom_attributes_check_unittest.py", + "checks/mojom_restrictions_checks_unittest.py", + "mojom_bindings_generator_unittest.py", + "//tools/diagnosis/crbug_1001171.py", + "//third_party/markupsafe/", + ] + data += mojom_generator_sources + data += jinja2_sources +} diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md index 43882450..683aa2f0 100644 --- a/utils/ipc/mojo/public/tools/bindings/README.md +++ b/utils/ipc/mojo/public/tools/bindings/README.md @@ -188,8 +188,8 @@ struct StringPair { }; enum AnEnum { - YES, - NO + kYes, + kNo }; interface SampleInterface { @@ -209,7 +209,7 @@ struct AllTheThings { uint64 unsigned_64bit_value; float float_value_32bit; double float_value_64bit; - AnEnum enum_value = AnEnum.YES; + AnEnum enum_value = AnEnum.kYes; // Strings may be nullable. string? maybe_a_string_maybe_not; @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface: module business.mojom; enum Department { - SALES = 0, - DEV, + kSales = 0, + kDev, }; struct Employee { enum Type { - FULL_TIME, - PART_TIME, + kFullTime, + kPartTime, }; Type type; @@ -315,6 +315,9 @@ struct Employee { }; ``` +C++ constant-style enum value names are preferred as specified in the +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names). + Similar to C-style enums, individual values may be explicitly assigned within an enum definition. By default, values are based at zero and increment by 1 sequentially. @@ -336,8 +339,8 @@ struct Employee { const uint64 kInvalidId = 0; enum Type { - FULL_TIME, - PART_TIME, + kFullTime, + kPartTime, }; uint64 id = kInvalidId; @@ -396,20 +399,33 @@ interesting attributes supported today. extreme caution, because it can lead to deadlocks otherwise. * **`[Default]`**: - The `Default` attribute may be used to specify an enumerator value that - will be used if an `Extensible` enumeration does not deserialize to a known - value on the receiver side, i.e. the sender is using a newer version of the - enum. This allows unknown values to be mapped to a well-defined value that can - be appropriately handled. + The `Default` attribute may be used to specify an enumerator value or union + field that will be used if an `Extensible` enumeration or union does not + deserialize to a known value on the receiver side, i.e. the sender is using a + newer version of the enum or union. This allows unknown values to be mapped to + a well-defined value that can be appropriately handled. + + Note: The `Default` field for a union must be of nullable or integral type. + When a union is defaulted to this field, the field takes on the default value + for its type: null for nullable types, and zero/false for integral types. * **`[Extensible]`**: - The `Extensible` attribute may be specified for any enum definition. This - essentially disables builtin range validation when receiving values of the - enum type in a message, allowing older bindings to tolerate unrecognized - values from newer versions of the enum. + The `Extensible` attribute may be specified for any enum or union definition. + For enums, this essentially disables builtin range validation when receiving + values of the enum type in a message, allowing older bindings to tolerate + unrecognized values from newer versions of the enum. + + If an enum value within an extensible enum definition is affixed with the + `Default` attribute, out-of-range values for the enum will deserialize to that + default value. Only one enum value may be designated as the `Default`. - Note: in the future, an `Extensible` enumeration will require that a `Default` - enumerator value also be specified. + Similarly, a union marked `Extensible` will deserialize to its `Default` field + when an unrecognized field is received. Extensible unions MUST specify exactly + one `Default` field, and the field must be of nullable or integral type. When + defaulted to this field, the value is always null/zero/false as appropriate. + + An `Extensible` enumeration REQUIRES that a `Default` value be specified, + so all new extensible enums should specify one. * **`[Native]`**: The `Native` attribute may be specified for an empty struct declaration to @@ -422,7 +438,10 @@ interesting attributes supported today. * **`[MinVersion=N]`**: The `MinVersion` attribute is used to specify the version at which a given field, enum value, interface method, or method parameter was introduced. - See [Versioning](#Versioning) for more details. + See [Versioning](#Versioning) for more details. `MinVersion` does not apply + to interfaces, structs or enums, but to the fields of those types. + `MinVersion` is not a module-global value, but it is ok to pretend it is by + skipping versions when adding fields or parameters. * **`[Stable]`**: The `Stable` attribute specifies that a given mojom type or interface @@ -448,7 +467,50 @@ interesting attributes supported today. matching `value` in the list of `enabled_features`, the definition will be disabled. This is useful for mojom definitions that only make sense on one platform. Note that the `EnableIf` attribute can only be set once per - definition. + definition and cannot be set at the same time as `EnableIfNot`. Also be aware + that only one condition can be tested, `EnableIf=value,xyz` introduces a new + `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends + only on the feature `value`. Complex conditions can be introduced via + enabled_features in `build.gn` files. + +* **`[EnableIfNot=value]`**: + The `EnableIfNot` attribute is used to conditionally enable definitions when + the mojom is parsed. If the `mojom` target in the GN file includes the + matching `value` in the list of `enabled_features`, the definition will be + disabled. This is useful for mojom definitions that only make sense on all but + one platform. Note that the `EnableIfNot` attribute can only be set once per + definition and cannot be set at the same time as `EnableIf`. + +* **`[ServiceSandbox=value]`**: + The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a + service hosting an implementation of interface will be launched in. This only + applies to `C++` bindings. `value` should match a constant defined in an + imported `sandbox.mojom.Sandbox` enum (for Chromium this is + `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`. + +* **`[RequireContext=enum]`**: + The `RequireContext` attribute is used in Chromium to tag interfaces that + should be passed (as remotes or receivers) only to privileged process + contexts. The process context must be an enum that is imported into the + mojom that defines the tagged interface. `RequireContext` may be used in + future to DCHECK or CHECK if remotes are made available in contexts that + conflict with the one provided in the interface definition. Process contexts + are not the same as the sandbox a process is running in, but will reflect + the set of capabilities provided to the service. + +* **`[AllowedContext=enum]`**: + The `AllowedContext` attribute is used in Chromium to tag methods that pass + remotes or receivers of interfaces that are marked with a `RequireContext` + attribute. The enum provided on the method must be equal or better (lower + numerically) than the one required on the interface being passed. At present + failing to specify an adequate `AllowedContext` value will cause mojom + generation to fail at compile time. In future DCHECKs or CHECKs might be + added to enforce that method is only called from a process context that meets + the given `AllowedContext` value. The enum must of the same type as that + specified in the interface's `RequireContext` attribute. Adding an + `AllowedContext` attribute to a method is a strong indication that you need + a detailed security review of your design - please reach out to the security + team. ## Generated Code For Target Languages @@ -495,9 +557,9 @@ values. For example if a Mojom declares the enum: ``` cpp enum AdvancedBoolean { - TRUE = 0, - FALSE = 1, - FILE_NOT_FOUND = 2, + kTrue = 0, + kFalse = 1, + kFileNotFound = 2, }; ``` @@ -550,10 +612,16 @@ See the documentation for *** note **NOTE:** You don't need to worry about versioning if you don't care about -backwards compatibility. Specifically, all parts of Chrome are updated -atomically today and there is not yet any possibility of any two Chrome -processes communicating with two different versions of any given Mojom -interface. +backwards compatibility. Today, all parts of the Chrome browser are +updated atomically and there is not yet any possibility of any two +Chrome processes communicating with two different versions of any given Mojom +interface. On Chrome OS, there are several places where versioning is required. +For example, +[ARC++](https://developer.android.com/chrome-os/intro) +uses versioned mojo to send IPC to the Android container. +Likewise, the +[Lacros](/docs/lacros.md) +browser uses versioned mojo to talk to the ash system UI. *** Services extend their interfaces to support new features over time, and clients @@ -593,8 +661,8 @@ struct Employee { *** note **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be -optional (nullable). See [Primitive Types](#Primitive-Types) for details on -nullable values. +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for +details on nullable values. *** By default, fields belong to version 0. New fields must be appended to the @@ -624,10 +692,10 @@ the following hard constraints: * For any given struct or interface, if any field or method explicitly specifies an ordinal value, all fields or methods must explicitly specify an ordinal value. -* For an *N*-field struct or *N*-method interface, the set of explicitly - assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces - should include placeholder methods to fill the ordinal positions of removed - methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc). +* For an *N*-field struct, the set of explicitly assigned ordinal values must be + limited to the range *[0, N-1]*. Structs should include placeholder fields + to fill the ordinal positions of removed fields (for example "Unused_Field" + or "RemovedField", etc). You may reorder fields, but you must ensure that the ordinal values of existing fields remain unchanged. For example, the following struct remains @@ -712,8 +780,8 @@ If you want an enum to be extensible in the future, you can apply the ``` cpp [Extensible] enum Department { - SALES, - DEV, + kSales, + kDev, }; ``` @@ -722,9 +790,9 @@ And later you can extend this enum without breaking backwards compatibility: ``` cpp [Extensible] enum Department { - SALES, - DEV, - [MinVersion=1] RESEARCH, + kSales, + kDev, + [MinVersion=1] kResearch, }; ``` diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py new file mode 100644 index 00000000..3a2d2a3b --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py @@ -0,0 +1,168 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Validate mojo attributes are allowed in Chrome before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + +_COMMON_ATTRIBUTES = { + 'EnableIf', + 'EnableIfNot', +} + +# For struct, union & parameter lists. +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'MinVersion', + 'RenamedFrom', +} + +# Note: `Default`` goes on the default _value_, not on the enum. +# Note: [Stable] without [Extensible] is not allowed. +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Extensible', + 'Native', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal. +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Default', + 'MinVersion', +} + +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'WebUIJsBridge', + 'RenamedFrom', + 'RequireContext', + 'ServiceSandbox', + 'Stable', + 'Uuid', +} + +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'AllowedContext', + 'MinVersion', + 'NoInterrupt', + 'Sync', + 'UnlimitedSize', +} + +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'JavaConstantsClassName', + 'JavaPackage', +} + +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES + +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'CustomSerializer', + 'JavaClassName', + 'Native', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES + +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Extensible', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | { + 'Default', +} + +# TODO(https://crbug.com/1193875) empty this set and remove the allowlist. +_STABLE_ONLY_ALLOWLISTED_ENUMS = { + 'crosapi.mojom.OptionalBool', + 'crosapi.mojom.TriState', +} + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + def _Respell(self, allowed, attribute): + for a in allowed: + if a.lower() == attribute.lower(): + return f" - Did you mean: {a}?" + return "" + + def _CheckAttributes(self, context, allowed, attributes): + if not attributes: + return + for attribute in attributes: + if not attribute in allowed: + # Is there a close misspelling? + hint = self._Respell(allowed, attribute) + raise check.CheckException( + self.module, + f"attribute {attribute} not allowed on {context}{hint}") + + def _CheckEnumAttributes(self, enum): + if enum.attributes: + self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes) + if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes: + full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}" + if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS: + raise check.CheckException( + self.module, + f"[Extensible] required on [Stable] enum {full_name}") + for enumval in enum.fields: + self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES, + enumval.attributes) + + def _CheckInterfaceAttributes(self, interface): + self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES, + interface.attributes) + for method in interface.methods: + self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes) + for param in method.parameters: + self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES, + param.attributes) + if method.response_parameters: + for param in method.response_parameters: + self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES, + param.attributes) + for enum in interface.enums: + self._CheckEnumAttributes(enum) + + def _CheckModuleAttributes(self): + self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes) + + def _CheckStructAttributes(self, struct): + self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes) + for field in struct.fields: + self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES, + field.attributes) + for enum in struct.enums: + self._CheckEnumAttributes(enum) + + def _CheckUnionAttributes(self, union): + self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes) + for field in union.fields: + self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES, + field.attributes) + + def CheckModule(self): + """Note that duplicate attributes are forbidden at the parse phase. + We also do not need to look at the types of any parameters, as they will be + checked where they are defined. Consts do not have attributes so can be + skipped.""" + self._CheckModuleAttributes() + for interface in self.module.interfaces: + self._CheckInterfaceAttributes(interface) + for enum in self.module.enums: + self._CheckEnumAttributes(enum) + for struct in self.module.structs: + self._CheckStructAttributes(struct) + for union in self.module.unions: + self._CheckUnionAttributes(union) diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py new file mode 100644 index 00000000..8c7f3c2c --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py @@ -0,0 +1,186 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'attributes' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def _testValid(self, filename, content): + self.WriteFile(filename, content) + self._ParseAndGenerate([filename]) + + def _testThrows(self, filename, content, regexp): + mojoms = [] + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegexp(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('attributes') + self.assertTrue(check_modules['attributes']) + + def testNoAnnotations(self): + # Undecorated mojom should be fine. + self._testValid( + "a.mojom", """ + module a; + struct Bar { int32 a; }; + enum Hello { kValue }; + union Thingy { Bar b; Hello hi; }; + interface Foo { + Foo(int32 a, Hello hi, Thingy t) => (Bar b); + }; + """) + + def testValidAnnotations(self): + # Obviously this is meaningless and won't generate, but it should pass + # the attribute check's validation. + self._testValid( + "a.mojom", """ + [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"] + module a; + [Stable, Extensible] + enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 }; + [Native] + enum NativeEnum {}; + [Stable,Extensible] + union Thingy { Bar b; [Default]int32 c; Hello hi; }; + + [Stable,RenamedFrom="module.other.Foo", + Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"] + struct Structure { Hello hi; }; + + [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable, + Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"] + interface Foo { + [AllowedContext=Hello.kValue] + Foo@0(int32 a) => (int32 b); + [MinVersion=2,Sync,UnlimitedSize,NoInterrupt] + Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c); + }; + """) + + def testWrongModuleStable(self): + contents = """ + // err: module cannot be Stable + [Stable] + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Stable not allowed on module') + + def testWrongEnumDefault(self): + contents = """ + module a; + // err: default should go on EnumValue not Enum. + [Default=kValue] + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Default not allowed on enum') + + def testWrongStructMinVersion(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + // err: struct cannot have MinVersion. + [MinVersion=2] + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute MinVersion not allowed on struct') + + def testWrongMethodRequireContext(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + // err: RequireContext is for interfaces. + [RequireContext=Hello.kValue] + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext not allowed on method') + + def testWrongMethodRequireContext(self): + # crbug.com/1230122 + contents = """ + module a; + interface Foo { + // err: sync not Sync. + [sync] + Foo(int32 a) => (int32 b); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute sync not allowed.*Did you mean: Sync') + + def testStableExtensibleEnum(self): + # crbug.com/1193875 + contents = """ + module a; + [Stable] + enum Foo { + kDefaultVal, + kOtherVal = 2, + }; + """ + self._testThrows('a.mojom', contents, + 'Extensible.*?required.*?Stable.*?enum') diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py new file mode 100644 index 00000000..702d41c3 --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py @@ -0,0 +1,34 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Ensure no duplicate type definitions before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + def CheckModule(self): + kinds = dict() + for module in self.module.imports: + for kind in module.enums + module.structs + module.unions: + kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}' + if kind_name in kinds: + previous_module = kinds[kind_name] + if previous_module.path != module.path: + raise check.CheckException( + self.module, f"multiple-definition for type {kind_name}" + + f"(defined in both {previous_module} and {module})") + kinds[kind_name] = kind.module + + for kind in self.module.enums + self.module.structs + self.module.unions: + kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}' + if kind_name in kinds: + previous_module = kinds[kind_name] + raise check.CheckException( + self.module, f"multiple-definition for type {kind_name}" + + f"(previous definition in {previous_module})") + return True diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py new file mode 100644 index 00000000..d570e26c --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py @@ -0,0 +1,102 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Validate RequireContext and AllowedContext annotations before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + self.kind_to_interfaces = dict() + super(Check, self).__init__(*args, **kwargs) + + def _IsPassedInterface(self, candidate): + if isinstance( + candidate.kind, + (module.PendingReceiver, module.PendingRemote, + module.PendingAssociatedReceiver, module.PendingAssociatedRemote)): + return True + return False + + def _CheckInterface(self, method, param): + # |param| is a pending_x so need .kind.kind to get Interface. + interface = param.kind.kind + if interface.require_context: + if method.allowed_context is None: + raise check.CheckException( + self.module, "method `{}` has parameter `{}` which passes interface" + " `{}` that requires an AllowedContext annotation but none exists.". + format( + method.mojom_name, + param.mojom_name, + interface.mojom_name, + )) + # If a string was provided, or if an enum was not imported, this will + # be a string and we cannot validate that it is in range. + if not isinstance(method.allowed_context, module.EnumValue): + raise check.CheckException( + self.module, + "method `{}` has AllowedContext={} which is not a valid enum value." + .format(method.mojom_name, method.allowed_context)) + # EnumValue must be from the same enum to be compared. + if interface.require_context.enum != method.allowed_context.enum: + raise check.CheckException( + self.module, "method `{}` has parameter `{}` which passes interface" + " `{}` that requires AllowedContext={} but one of kind `{}` was " + "provided.".format( + method.mojom_name, + param.mojom_name, + interface.mojom_name, + interface.require_context.enum, + method.allowed_context.enum, + )) + # RestrictContext enums have most privileged field first (lowest value). + interface_value = interface.require_context.field.numeric_value + method_value = method.allowed_context.field.numeric_value + if interface_value < method_value: + raise check.CheckException( + self.module, "RequireContext={} > AllowedContext={} for method " + "`{}` which passes interface `{}`.".format( + interface.require_context.GetSpec(), + method.allowed_context.GetSpec(), method.mojom_name, + interface.mojom_name)) + return True + + def _GatherReferencedInterfaces(self, field): + key = field.kind.spec + # structs/unions can nest themselves so we need to bookkeep. + if not key in self.kind_to_interfaces: + # Might reference ourselves so have to create the list first. + self.kind_to_interfaces[key] = set() + for param in field.kind.fields: + if self._IsPassedInterface(param): + self.kind_to_interfaces[key].add(param) + elif isinstance(param.kind, (module.Struct, module.Union)): + for iface in self._GatherReferencedInterfaces(param): + self.kind_to_interfaces[key].add(iface) + return self.kind_to_interfaces[key] + + def _CheckParams(self, method, params): + # Note: we have to repeat _CheckParams for each method as each might have + # different AllowedContext= attributes. We cannot memoize this function, + # but can do so for gathering referenced interfaces as their RequireContext + # attributes do not change. + for param in params: + if self._IsPassedInterface(param): + self._CheckInterface(method, param) + elif isinstance(param.kind, (module.Struct, module.Union)): + for interface in self._GatherReferencedInterfaces(param): + self._CheckInterface(method, interface) + + def _CheckMethod(self, method): + if method.parameters: + self._CheckParams(method, method.parameters) + if method.response_parameters: + self._CheckParams(method, method.response_parameters) + + def CheckModule(self): + for interface in self.module.interfaces: + for method in interface.methods: + self._CheckMethod(method) diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py new file mode 100644 index 00000000..a6cd71e2 --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py @@ -0,0 +1,254 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + +# Mojoms that we will use in multiple tests. +basic_mojoms = { + 'level.mojom': + """ + module level; + enum Level { + kHighest, + kMiddle, + kLowest, + }; + """, + 'interfaces.mojom': + """ + module interfaces; + import "level.mojom"; + struct Foo {int32 bar;}; + [RequireContext=level.Level.kHighest] + interface High { + DoFoo(Foo foo); + }; + [RequireContext=level.Level.kMiddle] + interface Mid { + DoFoo(Foo foo); + }; + [RequireContext=level.Level.kLowest] + interface Low { + DoFoo(Foo foo); + }; + """ +} + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'restrictions' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + def _WriteBasicMojoms(self): + for filename, contents in basic_mojoms.items(): + self.WriteFile(filename, contents) + return list(basic_mojoms.keys()) + + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('restrictions') + self.assertTrue(check_modules['restrictions']) + + def testValidAnnotations(self): + mojoms = self._WriteBasicMojoms() + + a = 'a.mojom' + self.WriteFile( + a, """ + module a; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + [AllowedContext=level.Level.kHighest] + DoHigh(pending_receiver hi); + }; + interface PassesMedium { + [AllowedContext=level.Level.kMiddle] + DoMedium(pending_receiver hi); + [AllowedContext=level.Level.kMiddle] + DoMediumRem(pending_remote hi); + [AllowedContext=level.Level.kMiddle] + DoMediumAssoc(pending_associated_receiver hi); + [AllowedContext=level.Level.kMiddle] + DoMediumAssocRem(pending_associated_remote hi); + }; + interface PassesLow { + [AllowedContext=level.Level.kLowest] + DoLow(pending_receiver hi); + }; + + struct One { pending_receiver hi; }; + struct Two { One one; }; + interface PassesNestedHigh { + [AllowedContext=level.Level.kHighest] + DoNestedHigh(Two two); + }; + + // Allowed as PassesHigh is not itself restricted. + interface PassesPassesHigh { + DoPass(pending_receiver hiho); + }; + """) + mojoms.append(a) + self._ParseAndGenerate(mojoms) + + def _testThrows(self, filename, content, regexp): + mojoms = self._WriteBasicMojoms() + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegexp(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testMissingAnnotation(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + // err: missing annotation. + DoHigh(pending_receiver hi); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testAllowTooLow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + // err: level is worse than required. + [AllowedContext=level.Level.kMiddle] + DoHigh(pending_receiver hi); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle') + + def testWrongEnumInAllow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + enum Blah { + kZero, + }; + interface PassesHigh { + // err: different enums. + [AllowedContext=Blah.kZero] + DoHigh(pending_receiver hi); + }; + """ + self._testThrows('b.mojom', contents, 'but one of kind') + + def testNotAnEnumInAllow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + interface PassesHigh { + // err: not an enum. + [AllowedContext=doopdedoo.mojom.kWhatever] + DoHigh(pending_receiver hi); + }; + """ + self._testThrows('b.mojom', contents, 'not a valid enum value') + + def testMissingAllowedForNestedStructs(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver hi; }; + struct Two { One one; }; + interface PassesNestedHigh { + // err: missing annotation. + DoNestedHigh(Two two); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testMissingAllowedForNestedUnions(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver hi; }; + struct Two { One one; }; + union Three {One one; Two two; }; + interface PassesNestedHigh { + // err: missing annotation. + DoNestedHigh(Three three); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testMultipleInterfacesThrows(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver hi; }; + interface PassesMultipleInterfaces { + [AllowedContext=level.Level.kMiddle] + DoMultiple( + pending_remote mid, + pending_receiver hi, + One one + ); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle') + + def testMultipleInterfacesAllowed(self): + """Multiple interfaces can be passed, all satisfy the level.""" + mojoms = self._WriteBasicMojoms() + + b = "b.mojom" + self.WriteFile( + b, """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver hi; }; + interface PassesMultipleInterfaces { + [AllowedContext=level.Level.kHighest] + DoMultiple( + pending_receiver hi, + pending_remote mid, + One one + ); + }; + """) + mojoms.append(b) + self._ParseAndGenerate(mojoms) diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py index 48bc66fd..4dd26d4a 100755 --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2019 The Chromium Authors. All rights reserved. +# Copyright 2019 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # @@ -15,6 +15,7 @@ from __future__ import print_function import optparse +import sys def Concatenate(filenames): @@ -47,7 +48,7 @@ def main(): parser.set_usage("""Concatenate several files into one. Equivalent to: cat file1 ... > target.""") (_options, args) = parser.parse_args() - exit(0 if Concatenate(args) else 1) + sys.exit(0 if Concatenate(args) else 1) if __name__ == "__main__": diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py index be8985ce..770081e1 100755 --- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py +++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -20,6 +20,7 @@ from __future__ import print_function import optparse import re +import sys _MOJO_INTERNAL_MODULE_NAME = "mojo.internal" @@ -34,7 +35,7 @@ def FilterLine(filename, line, output): match = re.match("goog.provide\('([^']+)'\);", line) if not match: print("Invalid goog.provide line in %s:\n%s" % (filename, line)) - exit(1) + sys.exit(1) module_name = match.group(1) if module_name == _MOJO_INTERNAL_MODULE_NAME: @@ -67,7 +68,8 @@ def main(): Concatenate several files into one, stripping Closure provide and require directives along the way.""") (_, args) = parser.parse_args() - exit(0 if ConcatenateAndReplaceExports(args) else 1) + sys.exit(0 if ConcatenateAndReplaceExports(args) else 1) + if __name__ == "__main__": main() diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py index 8b78d092..c6daff03 100644 --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py @@ -1,4 +1,4 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. +# Copyright 2017 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a list of all files in a directory. diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py index a0096649..d73c6ea4 100755 --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2016 The Chromium Authors. All rights reserved. +# Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a JSON typemap from its command-line arguments and dependencies. @@ -82,6 +82,7 @@ def LoadCppTypemapConfig(path): for entry in config['types']: configs[entry['mojom']] = { 'typename': entry['cpp'], + 'forward_declaration': entry.get('forward_declaration', None), 'public_headers': config.get('traits_headers', []), 'traits_headers': config.get('traits_private_headers', []), 'copyable_pass_by_value': entry.get('copyable_pass_by_value', diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py new file mode 100755 index 00000000..cefee7a4 --- /dev/null +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This utility minifies JS files with terser. +# +# Instance of 'node' has no 'RunNode' member (no-member) +# pylint: disable=no-member + +import argparse +import os +import sys + +_HERE_PATH = os.path.dirname(__file__) +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..')) +_CWD = os.getcwd() +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node')) +import node +import node_modules + + +def MinifyFile(input_file, output_file): + node.RunNode([ + node_modules.PathToTerser(), input_file, '--mangle', '--compress', + '--comments', 'false', '--output', output_file + ]) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--input', required=True) + parser.add_argument('--output', required=True) + args = parser.parse_args(argv) + + # Delete the output file if it already exists. It may be a sym link to the + # input, because in non-optimized/pre-Terser builds the input file is copied + # to the output location with gn copy(). + out_path = os.path.join(_CWD, args.output) + if (os.path.exists(out_path)): + os.remove(out_path) + + MinifyFile(os.path.join(_CWD, args.input), out_path) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni index fe2a1da3..ded53259 100644 --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni @@ -1,11 +1,11 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/python.gni") import("//third_party/closure_compiler/closure_args.gni") import("//third_party/closure_compiler/compile_js.gni") import("//third_party/protobuf/proto_library.gni") +import("//ui/webui/resources/tools/generate_grd.gni") import("//ui/webui/webui_features.gni") # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're @@ -16,10 +16,12 @@ import("//ui/webui/webui_features.gni") import("//build/config/chrome_build.gni") import("//build/config/chromecast_build.gni") import("//build/config/chromeos/ui_mode.gni") +import("//build/config/features.gni") import("//build/config/nacl/config.gni") import("//build/toolchain/kythe.gni") import("//components/nacl/features.gni") import("//third_party/jinja2/jinja2.gni") +import("//third_party/ply/ply.gni") import("//tools/ipc_fuzzer/ipc_fuzzer.gni") declare_args() { # Indicates whether typemapping should be supported in this build @@ -38,17 +40,25 @@ declare_args() { # scrambling on all platforms. enable_mojom_message_id_scrambling = true + # Enables generating javascript fuzzing-related code and the bindings for the + # MojoLPM fuzzer targets. Off by default. + enable_mojom_fuzzer = false + # Enables Closure compilation of generated JS lite bindings. In environments # where compilation is supported, any mojom target "foo" will also have a # corresponding "foo_js_library_for_compile" target generated. - enable_mojom_closure_compile = enable_js_type_check && optimize_webui - - # Enables generating Typescript bindings and compiling them to JS bindings. - enable_typescript_bindings = false + if (is_chromeos_ash) { + enable_mojom_closure_compile = enable_js_type_check && optimize_webui + } +} - # Enables generating javascript fuzzing-related code and the bindings for the - # MojoLPM fuzzer targets. Off by default. - enable_mojom_fuzzer = false +# Closure libraries are needed for mojom_closure_compile, and when +# js_type_check is enabled on Ash. +if (is_chromeos_ash) { + generate_mojom_closure_libraries = + enable_mojom_closure_compile || enable_js_type_check +} else { + generate_mojom_closure_libraries = false } # NOTE: We would like to avoid scrambling message IDs where it doesn't add @@ -69,9 +79,8 @@ declare_args() { # lacros-chrome switches to target_os="chromeos" enable_scrambled_message_ids = enable_mojom_message_id_scrambling && - (is_mac || is_win || - (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) || - ((enable_nacl || is_nacl || is_nacl_nonsfi) && + (is_mac || is_win || (is_linux && !is_castos) || + ((enable_nacl || is_nacl) && (target_os != "chromeos" && !chromeos_is_browser_only))) _mojom_tools_root = "//mojo/public/tools" @@ -80,7 +89,9 @@ mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py" mojom_parser_sources = [ "$_mojom_library_root/__init__.py", "$_mojom_library_root/error.py", + "$_mojom_library_root/fileutil.py", "$_mojom_library_root/generate/__init__.py", + "$_mojom_library_root/generate/check.py", "$_mojom_library_root/generate/generator.py", "$_mojom_library_root/generate/module.py", "$_mojom_library_root/generate/pack.py", @@ -88,20 +99,28 @@ mojom_parser_sources = [ "$_mojom_library_root/generate/translate.py", "$_mojom_library_root/parse/__init__.py", "$_mojom_library_root/parse/ast.py", + "$_mojom_library_root/parse/conditional_features.py", "$_mojom_library_root/parse/lexer.py", "$_mojom_library_root/parse/parser.py", + "//tools/diagnosis/crbug_1001171.py", ] mojom_generator_root = "$_mojom_tools_root/bindings" mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py" mojom_generator_sources = mojom_parser_sources + [ + "$mojom_generator_root/checks/__init__.py", + "$mojom_generator_root/checks/mojom_attributes_check.py", + "$mojom_generator_root/checks/mojom_definitions_check.py", + "$mojom_generator_root/checks/mojom_restrictions_check.py", + "$mojom_generator_root/generators/__init__.py", "$mojom_generator_root/generators/cpp_util.py", "$mojom_generator_root/generators/mojom_cpp_generator.py", "$mojom_generator_root/generators/mojom_java_generator.py", - "$mojom_generator_root/generators/mojom_mojolpm_generator.py", "$mojom_generator_root/generators/mojom_js_generator.py", + "$mojom_generator_root/generators/mojom_mojolpm_generator.py", "$mojom_generator_root/generators/mojom_ts_generator.py", + "$mojom_generator_root/generators/mojom_webui_js_bridge_generator.py", "$mojom_generator_script", ] @@ -243,12 +262,16 @@ if (enable_scrambled_message_ids) { # |cpp_only| is set to true, it overrides this to prevent generation of # Java bindings. # -# enable_fuzzing (optional) +# enable_js_fuzzing (optional) +# Enables generation of javascript fuzzing sources for the target if the +# global build arg |enable_mojom_fuzzer| is also set to |true|. +# Defaults to |true|. If JS fuzzing generation is enabled for a target, +# the target will always generate JS bindings even if |cpp_only| is set to +# |true|. See note above. +# +# enable_mojolpm_fuzzing (optional) # Enables generation of fuzzing sources for the target if the global build -# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If -# fuzzing generation is enabled for a target, the target will always -# generate JS bindings even if |cpp_only| is set to |true|. See note -# above. +# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. # # support_lazy_serialization (optional) # If set to |true|, generated C++ bindings will effectively prefer to @@ -313,6 +336,16 @@ if (enable_scrambled_message_ids) { # use_typescript_sources (optional) # Uses the Typescript generator to generate JavaScript bindings. # +# generate_legacy_js_bindings (optional) +# Generate js_data_deps target containing legacy JavaScript bindings files +# for Blink tests and other non-WebUI users when generating TypeScript +# bindings for WebUI. Ignored if use_typescript_sources is not set to +# true. +# +# webui_js_bridge_config (optional) +# Prefer to use the `mojom_with_webui_js_bridge` target below instead +# of using this argument directly. +# # js_generate_struct_deserializers (optional) # Generates JS deerialize methods for structs. # @@ -402,6 +435,12 @@ if (enable_scrambled_message_ids) { # should be mapped in generated bindings. This is a string like # "::base::Value" or "std::vector<::base::Value>". # +# forward_declaration (optional) +# A forward declaration of the C++ type, which bindings that don't +# need the full type definition can use to reduce the size of +# the generated code. This is a string like +# "namespace base { class Value; }". +# # move_only (optional) # A boolean value (default false) which indicates whether the C++ # type is move-only. If true, generated bindings will pass the type @@ -621,20 +660,26 @@ template("mojom") { build_metadata_filename = "$target_gen_dir/$target_name.build_metadata" build_metadata = { } - build_metadata.sources = rebase_path(sources_list) + build_metadata.sources = rebase_path(sources_list, target_gen_dir) build_metadata.deps = [] foreach(dep, all_deps) { dep_target_gen_dir = get_label_info(dep, "target_gen_dir") dep_name = get_label_info(dep, "name") build_metadata.deps += - [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ] + [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata", + target_gen_dir) ] } write_file(build_metadata_filename, build_metadata, "json") - generate_fuzzing = - (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) && + generate_js_fuzzing = + (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) && enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly) + generate_mojolpm_fuzzing = + (!defined(invoker.enable_mojolpm_fuzzing) || + invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer && + (!defined(invoker.testonly) || !invoker.testonly) + parser_target_name = "${target_name}__parser" parser_deps = [] foreach(dep, all_deps) { @@ -683,12 +728,17 @@ template("mojom") { enabled_features += [ "is_win" ] } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(parser_target_name) { + if (is_apple) { + enabled_features += [ "is_apple" ] + } + + action(parser_target_name) { + allow_remote = true + custom_processor = "mojom_parser" script = mojom_parser_script - inputs = mojom_parser_sources + [ build_metadata_filename ] + inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ] sources = sources_list - deps = parser_deps + public_deps = parser_deps outputs = [] foreach(base_path, output_file_base_paths) { filename = get_path_info(base_path, "file") @@ -698,31 +748,35 @@ template("mojom") { filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path(source) ] + filelist += [ rebase_path(source, root_build_dir) ] } - response_file_contents = filelist + + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args = [ # Resolve relative input mojom paths against both the root src dir and # the root gen dir. "--input-root", - rebase_path("//."), + rebase_path("//.", root_build_dir), "--input-root", - rebase_path(root_gen_dir), + rebase_path(root_gen_dir, root_build_dir), "--output-root", - rebase_path(root_gen_dir), + rebase_path(root_gen_dir, root_build_dir), - "--mojom-file-list={{response_file_name}}", + "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir), "--check-imports", - rebase_path(build_metadata_filename), + rebase_path(build_metadata_filename, root_build_dir), ] if (defined(invoker.input_root_override)) { args += [ "--input-root", - rebase_path(invoker.input_root_override), + rebase_path(invoker.input_root_override, root_build_dir), ] } @@ -738,6 +792,9 @@ template("mojom") { "--add-module-metadata", "webui_module_path=${invoker.webui_module_path}", ] + if (defined(invoker.generate_legacy_js_bindings)) { + args += [ "legacy_js_only=${invoker.generate_legacy_js_bindings}" ] + } } } } @@ -819,11 +876,12 @@ template("mojom") { } } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_cpp_message_ids_target_name) { + action(generator_cpp_message_ids_target_name) { + allow_remote = true script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -835,16 +893,22 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ] } - response_file_contents = filelist + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "--generate_message_ids", "-g", @@ -860,12 +924,13 @@ template("mojom") { generator_shared_target_name = "${target_name}_shared__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_shared_target_name) { + action(generator_shared_target_name) { + allow_remote = true visibility = [ ":*" ] script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -878,9 +943,14 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + # Need the mojom-module as an input to this action. + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] + outputs += [ "$root_gen_dir/$base_path-params-data.h", "$root_gen_dir/$base_path-shared-internal.h", @@ -889,10 +959,13 @@ template("mojom") { ] } - response_file_contents = filelist + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "-g", "c++", @@ -972,7 +1045,7 @@ template("mojom") { } } - if (generate_fuzzing) { + if (generate_mojolpm_fuzzing) { # This block generates the proto files used for the MojoLPM fuzzer, # and the corresponding proto targets that will be linked in the fuzzer # targets. These are independent of the typemappings, and can be done @@ -981,11 +1054,15 @@ template("mojom") { generator_mojolpm_proto_target_name = "${target_name}_mojolpm_proto_generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_mojolpm_proto_target_name) { + action(generator_mojolpm_proto_target_name) { + allow_remote = true script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = invoker.sources + sources = + invoker.sources + [ + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip", + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip", + ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -994,15 +1071,37 @@ template("mojom") { outputs = [] args = common_generator_args filelist = [] - foreach(source, invoker.sources) { - filelist += [ rebase_path("$source", root_build_dir) ] + + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ]) + + foreach(source, non_gen_sources) { + filelist += [ rebase_path(source, root_build_dir) ] + inputs += [ "$target_gen_dir/$source-module" ] outputs += [ "$target_gen_dir/$source.mojolpm.proto" ] } - response_file_contents = filelist + foreach(source, gen_sources) { + filelist += [ rebase_path(source, root_build_dir) ] + + # For generated files, we assume they're in the target_gen_dir or a + # sub-folder of it. Rebase the path so we can get the relative location. + source_file = rebase_path(source, target_gen_dir) + inputs += [ "$target_gen_dir/$source_file-module" ] + outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ] + } + + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "-g", "mojolpm", @@ -1014,9 +1113,20 @@ template("mojom") { proto_library(mojolpm_proto_target_name) { testonly = true generate_python = false + + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ]) sources = process_file_template( - invoker.sources, + non_gen_sources, [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ]) + sources += process_file_template( + gen_sources, + [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ]) + import_dirs = [ "//" ] proto_in_dir = "${root_gen_dir}" proto_out_dir = "." @@ -1055,7 +1165,7 @@ template("mojom") { component_macro_suffix = "" } if ((!defined(invoker.disable_variants) || !invoker.disable_variants) && - !is_ios) { + use_blink) { blink_variant = { variant = "blink" component_macro_suffix = "_BLINK" @@ -1149,39 +1259,6 @@ template("mojom") { "${bindings_configuration.component_macro_suffix}_IMPL" ] } - export_args = [] - export_args_overridden = false - if (defined(bindings_configuration.for_blink) && - bindings_configuration.for_blink) { - if (defined(invoker.export_class_attribute_blink)) { - export_args_overridden = true - export_args += [ - "--export_attribute", - invoker.export_class_attribute_blink, - "--export_header", - invoker.export_header_blink, - ] - } - } else if (defined(invoker.export_class_attribute)) { - export_args_overridden = true - export_args += [ - "--export_attribute", - invoker.export_class_attribute, - "--export_header", - invoker.export_header, - ] - } - - if (!export_args_overridden && defined(invoker.component_macro_prefix)) { - export_args += [ - "--export_attribute", - "COMPONENT_EXPORT(${invoker.component_macro_prefix}" + - "${bindings_configuration.component_macro_suffix})", - "--export_header", - "base/component_export.h", - ] - } - generate_java = false if (!cpp_only && defined(invoker.generate_java)) { generate_java = invoker.generate_java @@ -1190,6 +1267,38 @@ template("mojom") { type_mappings_path = "$target_gen_dir/${target_name}${variant_suffix}__type_mappings" if (sources_list != []) { + export_args = [] + export_args_overridden = false + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.export_class_attribute_blink)) { + export_args_overridden = true + export_args += [ + "--export_attribute", + invoker.export_class_attribute_blink, + "--export_header", + invoker.export_header_blink, + ] + } + } else if (defined(invoker.export_class_attribute)) { + export_args_overridden = true + export_args += [ + "--export_attribute", + invoker.export_class_attribute, + "--export_header", + invoker.export_header, + ] + } + if (!export_args_overridden && defined(invoker.component_macro_prefix)) { + export_args += [ + "--export_attribute", + "COMPONENT_EXPORT(${invoker.component_macro_prefix}" + + "${bindings_configuration.component_macro_suffix})", + "--export_header", + "base/component_export.h", + ] + } + generator_cpp_output_suffixes = [] variant_dash_suffix = "" if (defined(variant)) { @@ -1198,7 +1307,6 @@ template("mojom") { generator_cpp_output_suffixes += [ "${variant_dash_suffix}-forward.h", "${variant_dash_suffix}-import-headers.h", - "${variant_dash_suffix}-test-utils.cc", "${variant_dash_suffix}-test-utils.h", "${variant_dash_suffix}.cc", "${variant_dash_suffix}.h", @@ -1207,16 +1315,28 @@ template("mojom") { generator_target_name = "${target_name}${variant_suffix}__generator" # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_target_name) { + action(generator_target_name) { + allow_remote = true visibility = [ ":*" ] script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = + sources_list + [ + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip", + type_mappings_path, + ] + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { + sources += [ + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip", + ] + } deps = [ ":$parser_target_name", ":$type_mappings_target_name", "//mojo/public/tools/bindings:precompile_templates", ] + if (defined(invoker.parser_deps)) { deps += invoker.parser_deps } @@ -1224,18 +1344,22 @@ template("mojom") { args = common_generator_args + export_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] + outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h", "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h", - "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h", "$root_gen_dir/${base_path}${variant_dash_suffix}.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}.h", ] - if (generate_fuzzing && !defined(bindings_configuration.variant)) { + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h", @@ -1243,14 +1367,17 @@ template("mojom") { } } - response_file_contents = filelist - + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path("$rsp_file", root_build_dir), "-g", ] - if (generate_fuzzing && !defined(bindings_configuration.variant)) { + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { args += [ "c++,mojolpm" ] } else { args += [ "c++" ] @@ -1294,6 +1421,8 @@ template("mojom") { "--extra_cpp_template_paths", rebase_path(extra_cpp_template, root_build_dir), ] + inputs += [ extra_cpp_template ] + assert( get_path_info(extra_cpp_template, "extension") == "tmpl", "--extra_cpp_template_paths only accepts template files ending in extension .tmpl") @@ -1306,62 +1435,6 @@ template("mojom") { } } - if (generate_fuzzing && !defined(variant)) { - # This block contains the C++ targets for the MojoLPM fuzzer, we need to - # do this here so that we can use the typemap configuration for the - # empty-variant Mojo target. - - mojolpm_target_name = "${target_name}_mojolpm" - mojolpm_generator_target_name = "${target_name}__generator" - source_set(mojolpm_target_name) { - # There are still a few missing header dependencies between mojo targets - # with typemaps and the dependencies of their typemap headers. It would - # be good to enable include checking for these in the future though. - check_includes = false - testonly = true - if (defined(invoker.sources)) { - sources = process_file_template( - invoker.sources, - [ - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc", - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h", - ]) - deps = [] - } else { - sources = [] - deps = [] - } - - public_deps = [ - ":$generator_shared_target_name", - - # NB: hardcoded dependency on the no-variant variant generator, since - # mojolpm only uses the no-variant type. - ":$mojolpm_generator_target_name", - ":$mojolpm_proto_target_name", - "//base", - "//mojo/public/tools/fuzzers:mojolpm", - ] - - foreach(d, all_deps) { - # Resolve the name, so that a target //mojo/something becomes - # //mojo/something:something and we can append variant_suffix to - # get the cpp dependency name. - full_name = get_label_info("$d", "label_no_toolchain") - public_deps += [ "${full_name}_mojolpm" ] - } - - foreach(config, cpp_typemap_configs) { - if (defined(config.traits_deps)) { - deps += config.traits_deps - } - if (defined(config.traits_public_deps)) { - public_deps += config.traits_public_deps - } - } - } - } - # Write the typemapping configuration for this target out to a file to be # validated by a Python script. This helps catch mistakes that can't # be caught by logic in GN. @@ -1389,20 +1462,20 @@ template("mojom") { write_file(_typemap_config_filename, _rebased_typemap_configs, "json") _mojom_target_name = target_name - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(_typemap_validator_target_name) { + action(_typemap_validator_target_name) { + allow_remote = true script = "$mojom_generator_root/validate_typemap_config.py" inputs = [ _typemap_config_filename ] outputs = [ _typemap_stamp_filename ] args = [ get_label_info(_mojom_target_name, "label_no_toolchain"), - rebase_path(_typemap_config_filename), - rebase_path(_typemap_stamp_filename), + rebase_path(_typemap_config_filename, root_build_dir), + rebase_path(_typemap_stamp_filename, root_build_dir), ] } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(type_mappings_target_name) { + action(type_mappings_target_name) { + allow_remote = true inputs = mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ] outputs = [ type_mappings_path ] @@ -1413,6 +1486,7 @@ template("mojom") { rebase_path(type_mappings_path, root_build_dir), ] + sources = [] foreach(d, all_deps) { name = get_label_info(d, "label_no_toolchain") toolchain = get_label_info(d, "toolchain") @@ -1422,12 +1496,11 @@ template("mojom") { dependency_output_dir = get_label_info(dependency_output, "target_gen_dir") dependency_name = get_label_info(dependency_output, "name") - dependency_path = - rebase_path("$dependency_output_dir/${dependency_name}", - root_build_dir) + dependency_path = "$dependency_output_dir/${dependency_name}" + sources += [ dependency_path ] args += [ "--dependency", - dependency_path, + rebase_path(dependency_path, root_build_dir), ] } @@ -1485,11 +1558,15 @@ template("mojom") { if (defined(output_name_override)) { output_name = output_name_override } - visibility = output_visibility + [ ":$output_target_name" ] + visibility = output_visibility + [ + ":$output_target_name", + ":${target_name}_mojolpm", + ] if (defined(invoker.testonly)) { testonly = invoker.testonly } defines = export_defines + configs += [ "//build/config/compiler:wexit_time_destructors" ] configs += extra_configs if (output_file_base_paths != []) { sources = [] @@ -1578,13 +1655,81 @@ template("mojom") { } } + if (generate_mojolpm_fuzzing && !defined(variant)) { + # This block contains the C++ targets for the MojoLPM fuzzer, we need to + # do this here so that we can use the typemap configuration for the + # empty-variant Mojo target. + + mojolpm_target_name = "${target_name}_mojolpm" + mojolpm_generator_target_name = "${target_name}__generator" + source_set(mojolpm_target_name) { + # There are still a few missing header dependencies between mojo targets + # with typemaps and the dependencies of their typemap headers. It would + # be good to enable include checking for these in the future though. + check_includes = false + testonly = true + if (defined(invoker.sources)) { + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = + filter_include(invoker.sources, [ gen_dir_path_wildcard ]) + sources = process_file_template( + non_gen_sources, + [ + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc", + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h", + ]) + sources += process_file_template( + gen_sources, + [ + "{{source_dir}}/{{source_file_part}}-mojolpm.cc", + "{{source_dir}}/{{source_file_part}}-mojolpm.h", + ]) + deps = [ ":$output_target_name" ] + } else { + sources = [] + deps = [] + } + + public_deps = [ + ":$generator_shared_target_name", + + # NB: hardcoded dependency on the no-variant variant generator, since + # mojolpm only uses the no-variant type. + ":$mojolpm_generator_target_name", + ":$mojolpm_proto_target_name", + "//base", + "//mojo/public/tools/fuzzers:mojolpm", + ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix to + # get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_mojolpm" ] + } + + foreach(config, cpp_typemap_configs) { + if (defined(config.traits_deps)) { + deps += config.traits_deps + } + if (defined(config.traits_public_deps)) { + public_deps += config.traits_public_deps + } + } + } + } + if (generate_java && is_android) { import("//build/config/android/rules.gni") java_generator_target_name = target_name + "_java__generator" if (sources_list != []) { - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(java_generator_target_name) { + action(java_generator_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1597,7 +1742,7 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { outputs += [ "$root_gen_dir/$base_path.srcjar" ] @@ -1624,8 +1769,7 @@ template("mojom") { java_srcjar_target_name = target_name + "_java_sources" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(java_srcjar_target_name) { + action(java_srcjar_target_name) { script = "//build/android/gyp/zip.py" inputs = [] if (output_file_base_paths != []) { @@ -1651,7 +1795,6 @@ template("mojom") { android_library(java_target_name) { forward_variables_from(invoker, [ "enable_bytecode_checks" ]) deps = [ - "//base:base_java", "//mojo/public/java:bindings_java", "//mojo/public/java:system_java", "//third_party/androidx:androidx_annotation_annotation_java", @@ -1673,21 +1816,103 @@ template("mojom") { } } + if (defined(invoker.webui_js_bridge_config)) { + if (sources_list != []) { + bridge_config = invoker.webui_js_bridge_config + + generator_webui_js_bridge_impl_target_name = + "${target_name}_webui_js_bridge_impl__generator" + action(generator_webui_js_bridge_impl_target_name) { + visibility = [ ":*" ] + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + + filelist = [] + foreach(source, sources_list) { + filelist += [ rebase_path(source, root_build_dir) ] + } + + outputs = [] + foreach(base_path, output_file_base_paths) { + outputs += [ + "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc", + "$root_gen_dir/${base_path}-webui-js-bridge-impl.h", + ] + } + + response_file_contents = filelist + + args = common_generator_args + export_args + args += [ + "--filelist={{response_file_name}}", + "-g", + "webui_js_bridge", + ] + + rebased_webui_controller_header = + rebase_path(bridge_config.webui_controller_header, "//") + + # Use a single string for the argument to avoid it being parsed as a + # filename by the mojom_bindings_generator.py argument parser. + config_arg = + "--webui_js_bridge_config=" + bridge_config.webui_controller + "=" + + rebased_webui_controller_header + + args += [ config_arg ] + } + + cpp_target_name = target_name + source_set("${target_name}_webui_js_bridge_impl") { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + + sources = [] + foreach(base_path, output_file_base_paths) { + sources += [ + "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc", + "$root_gen_dir/${base_path}-webui-js-bridge-impl.h", + ] + } + deps = [ + ":$cpp_target_name", + ":$generator_webui_js_bridge_impl_target_name", + ] + + deps += bridge_config.webui_controller_deps + } + } + } + use_typescript_for_target = - enable_typescript_bindings && defined(invoker.use_typescript_sources) && - invoker.use_typescript_sources + defined(invoker.use_typescript_sources) && + invoker.use_typescript_sources && defined(invoker.webui_module_path) if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) { not_needed(invoker, [ "use_typescript_sources" ]) } - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) && - !use_typescript_for_target) { + generate_legacy_js = !use_typescript_for_target || + (defined(invoker.generate_legacy_js_bindings) && + invoker.generate_legacy_js_bindings) + + # Targets needed by both TS and JS bindings targets. These are needed + # unconditionally for JS bindings targets, and are needed for TS bindings + # targets when generate_legacy_js_bindings is true. This option is provided + # since the legacy bindings are needed by Blink tests and non-Chromium users, + # which are not expected to migrate to modules or TypeScript. + if (generate_legacy_js && (generate_js_fuzzing || + !defined(invoker.cpp_only) || !invoker.cpp_only)) { if (sources_list != []) { generator_js_target_name = "${target_name}_js__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_js_target_name) { + action(generator_js_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1702,19 +1927,18 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { outputs += [ "$root_gen_dir/$base_path.js", - "$root_gen_dir/$base_path.externs.js", "$root_gen_dir/$base_path.m.js", "$root_gen_dir/$base_path-lite.js", - "$root_gen_dir/$base_path.html", "$root_gen_dir/$base_path-lite-for-compile.js", ] - if (defined(invoker.webui_module_path)) { + if (defined(invoker.webui_module_path) && + !use_typescript_for_target) { outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ] } } @@ -1725,7 +1949,6 @@ template("mojom") { "--filelist={{response_file_name}}", "-g", "javascript", - "--js_bindings_mode=new", ] if (defined(invoker.js_generate_struct_deserializers) && @@ -1739,7 +1962,7 @@ template("mojom") { args += message_scrambling_args } - if (generate_fuzzing) { + if (generate_js_fuzzing) { args += [ "--generate_fuzzing" ] } } @@ -1783,31 +2006,13 @@ template("mojom") { data_deps += [ "${full_name}_js_data_deps" ] } } + } - js_library_target_name = "${target_name}_js_library" - if (sources_list != []) { - js_library(js_library_target_name) { - extra_public_deps = [ ":$generator_js_target_name" ] - sources = [] - foreach(base_path, output_file_base_paths) { - sources += [ "$root_gen_dir/${base_path}-lite.js" ] - } - externs_list = [ - "${externs_path}/mojo_core.js", - "${externs_path}/pending.js", - ] - - deps = [] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - deps += [ "${full_name}_js_library" ] - } - } - } else { - group(js_library_target_name) { - } - } - + # js_library() closure compiler targets, primarily used on ChromeOS. Only + # generate these targets if the mojom target is not C++ only and is not using + # TypeScript. + if (generate_mojom_closure_libraries && + (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) { js_library_for_compile_target_name = "${target_name}_js_library_for_compile" if (sources_list != []) { js_library(js_library_for_compile_target_name) { @@ -1834,35 +2039,9 @@ template("mojom") { } } - js_modules_target_name = "${target_name}_js_modules" - if (sources_list != []) { - js_library(js_modules_target_name) { - extra_public_deps = [ ":$generator_js_target_name" ] - sources = [] - foreach(base_path, output_file_base_paths) { - sources += [ "$root_gen_dir/${base_path}.m.js" ] - } - externs_list = [ - "${externs_path}/mojo_core.js", - "${externs_path}/pending.js", - ] - if (defined(invoker.disallow_native_types) && - invoker.disallow_native_types) { - deps = [] - } else { - deps = [ "//mojo/public/js:bindings_uncompiled" ] - } - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - deps += [ "${full_name}_js_modules" ] - } - } - } else { - group(js_modules_target_name) { - } - } - - if (defined(invoker.webui_module_path)) { + # WebUI specific closure targets, not needed by targets that are generating + # TypeScript WebUI bindings or by legacy-only targets. + if (defined(invoker.webui_module_path) && !use_typescript_for_target) { webui_js_target_name = "${target_name}_webui_js" if (sources_list != []) { js_library(webui_js_target_name) { @@ -1890,46 +2069,38 @@ template("mojom") { group(webui_js_target_name) { } } - } - } - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) && - use_typescript_for_target) { - generator_js_target_names = [] - source_filelist = [] - foreach(source, sources_list) { - source_filelist += [ rebase_path("$source", root_build_dir) ] - } - dependency_types = [ - { - name = "regular" - ts_extension = ".ts" - js_extension = ".js" - }, - { - name = "es_modules" - ts_extension = ".m.ts" - js_extension = ".m.js" - }, - ] + webui_grdp_target_name = "${target_name}_webui_grdp" + out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp" + grd_prefix = "${target_name}_webui" + generate_grd(webui_grdp_target_name) { + grd_prefix = grd_prefix + out_grd = out_grd - foreach(dependency_type, dependency_types) { - ts_outputs = [] - js_outputs = [] + deps = [ ":$webui_js_target_name" ] - foreach(base_path, output_file_base_paths) { - ts_outputs += - [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ] - js_outputs += - [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ] + input_files = [] + foreach(base_path, output_file_base_paths) { + input_files += [ "${base_path}-webui.js" ] + } + + input_files_base_dir = + rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir") + } + } + } + if ((generate_js_fuzzing || !defined(invoker.cpp_only) || + !invoker.cpp_only) && use_typescript_for_target) { + if (sources_list != []) { + source_filelist = [] + foreach(source, sources_list) { + source_filelist += [ rebase_path(source, root_build_dir) ] } # Generate Typescript bindings. - generator_ts_target_name = - "${target_name}_${dependency_type.name}__ts__generator" + generator_ts_target_name = "${target_name}_ts__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_ts_target_name) { + action(generator_ts_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1938,7 +2109,10 @@ template("mojom") { "//mojo/public/tools/bindings:precompile_templates", ] - outputs = ts_outputs + outputs = [] + foreach(base_path, output_file_base_paths) { + outputs += [ "$root_gen_dir/$base_path-webui.ts" ] + } args = common_generator_args response_file_contents = source_filelist @@ -1948,98 +2122,16 @@ template("mojom") { "typescript", ] - if (dependency_type.name == "es_modules") { - args += [ "--ts_use_es_modules" ] + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args } - # TODO(crbug.com/1007587): Support scramble_message_ids. + # TODO(crbug.com/1007587): Support scramble_message_ids if above is + # insufficient. # TODO(crbug.com/1007591): Support generate_fuzzing. } - - # Create tsconfig.json for the generated Typescript. - tsconfig_filename = - "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json" - tsconfig = { - } - tsconfig.compilerOptions = { - composite = true - target = "es6" - module = "es6" - lib = [ - "es6", - "esnext.bigint", - ] - strict = true - } - tsconfig.files = [] - foreach(base_path, output_file_base_paths) { - tsconfig.files += [ rebase_path( - "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}", - target_gen_dir, - root_gen_dir) ] - } - tsconfig.references = [] - - # Get tsconfigs for deps. - foreach(d, all_deps) { - dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir")) - dep_name = get_label_info(d, "name") - reference = { - } - reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json" - tsconfig.references += [ reference ] - } - write_file(tsconfig_filename, tsconfig, "json") - - # Compile previously generated Typescript to Javascript. - generator_js_target_name = - "${target_name}_${dependency_type.name}__js__generator" - generator_js_target_names += [ generator_js_target_name ] - - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_js_target_name) { - script = "$mojom_generator_root/compile_typescript.py" - sources = ts_outputs - outputs = js_outputs - public_deps = [ ":$generator_ts_target_name" ] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - public_deps += - [ "${full_name}_${dependency_type.name}__js__generator" ] - } - - absolute_tsconfig_path = - rebase_path(tsconfig_filename, "", target_gen_dir) - args = [ "--tsconfig_path=$absolute_tsconfig_path" ] - } - } - - js_target_name = target_name + "_js" - group(js_target_name) { - public_deps = [] - if (sources_list != []) { - foreach(generator_js_target_name, generator_js_target_names) { - public_deps += [ ":$generator_js_target_name" ] - } - } - - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - public_deps += [ "${full_name}_js" ] - } - } - - group(js_data_deps_target_name) { - data = js_outputs - deps = [] - foreach(generator_js_target_name, generator_js_target_names) { - deps += [ ":$generator_js_target_name" ] - } - data_deps = [] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - data_deps += [ "${full_name}_js_data_deps" ] - } } } } @@ -2063,3 +2155,53 @@ template("mojom_component") { component_macro_prefix = invoker.macro_prefix } } + +# A helper for the mojom() template above for defining a WebUIJsBridge. +# Supports all the same arguments as mojom() except for `sources` and adds +# `source`, `webui_controller`, `webui_controller_header`, and +# `webui_controller_deps` which are *required*. See below for more details. +# +# A WebUIJsBridge is a Mojo interface that contains binders for all interfaces +# that can be bound from a WebUI's JavaScript. This taret +# will generate a C++ implementation of the WebUIJsBridge interface +# and a {target_name}_webui_js_bridge_impl target that can be depended +# on. +# +# The following arguments are required: +# - `source`: The mojom file that defines the WebUIJsBridge interface. It's +# allowed to define other interfaces. +# - `webui_controller`: The name of the WebUIController associated with the +# WebUIJsBridge. +# - `webui_controller_header`: The header where the WebUIController +# is declared. +# - `webui_controller_deps`: The target containing the WebUIController. +template("mojom_with_webui_js_bridge") { + assert(defined(invoker.source)) + assert(defined(invoker.webui_controller), + "mojom_with_webui_js_bridge should have a " + + "`webui_controller` parameter e.g. `foo::FooUI`.") + assert(defined(invoker.webui_controller_header), + "mojom_with_webui_js_bridge should have a " + + "`webui_controller_header` parameter.") + assert(defined(invoker.webui_controller_deps), + "mojom_with_webui_js_bridge should have a " + + "`webui_controller_deps` parameter.") + + mojom(target_name) { + forward_variables_from(invoker, + "*", + [ + "source", + "webui_controller", + "webui_controller_header", + "webui_controller_deps", + ]) + sources = [ invoker.source ] + + webui_js_bridge_config = { + webui_controller = invoker.webui_controller + webui_controller_header = invoker.webui_controller_header + webui_controller_deps = invoker.webui_controller_deps + } + } +} diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py index da9efc71..98cac149 100755 --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -51,16 +51,23 @@ import crbug_1001171 _BUILTIN_GENERATORS = { "c++": "mojom_cpp_generator", + "webui_js_bridge": "mojom_webui_js_bridge_generator", "javascript": "mojom_js_generator", "java": "mojom_java_generator", "mojolpm": "mojom_mojolpm_generator", "typescript": "mojom_ts_generator", } +_BUILTIN_CHECKS = { + "attributes": "mojom_attributes_check", + "definitions": "mojom_definitions_check", + "restrictions": "mojom_restrictions_check", +} + def LoadGenerators(generators_string): if not generators_string: - return [] # No generators. + return {} # No generators. generators = {} for generator_name in [s.strip() for s in generators_string.split(",")]: @@ -74,6 +81,21 @@ def LoadGenerators(generators_string): return generators +def LoadChecks(checks_string): + if not checks_string: + return {} # No checks. + + checks = {} + for check_name in [s.strip() for s in checks_string.split(",")]: + check = check_name.lower() + if check not in _BUILTIN_CHECKS: + print("Unknown check name %s" % check_name) + sys.exit(1) + check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check]) + checks[check] = check_module + return checks + + def MakeImportStackMessage(imported_filename_stack): """Make a (human-readable) message listing a chain of imports. (Returned string begins with a newline (if nonempty) and does not end with one.)""" @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack): zip(imported_filename_stack[1:], imported_filename_stack)])) -class RelativePath(object): +class RelativePath: """Represents a path relative to the source tree or generated output dir.""" def __init__(self, path, source_root, output_dir): @@ -142,7 +164,7 @@ def ReadFileContents(filename): return f.read() -class MojomProcessor(object): +class MojomProcessor: """Takes parsed mojom modules and generates language bindings from them. Attributes: @@ -169,8 +191,8 @@ class MojomProcessor(object): if 'c++' in self._typemap: self._typemap['mojolpm'] = self._typemap['c++'] - def _GenerateModule(self, args, remaining_args, generator_modules, - rel_filename, imported_filename_stack): + def _GenerateModule(self, args, remaining_args, check_modules, + generator_modules, rel_filename, imported_filename_stack): # Return the already-generated module. if rel_filename.path in self._processed_files: return self._processed_files[rel_filename.path] @@ -190,12 +212,16 @@ class MojomProcessor(object): ScrambleMethodOrdinals(module.interfaces, salt) if self._should_generate(rel_filename.path): + # Run checks on module first. + for check_module in check_modules.values(): + checker = check_module.Check(module) + checker.CheckModule() + # Then run generation. for language, generator_module in generator_modules.items(): generator = generator_module.Generator( module, args.output_dir, typemap=self._typemap.get(language, {}), variant=args.variant, bytecode_path=args.bytecode_path, for_blink=args.for_blink, - js_bindings_mode=args.js_bindings_mode, js_generate_struct_deserializers=\ args.js_generate_struct_deserializers, export_attribute=args.export_attribute, @@ -234,6 +260,7 @@ def _Generate(args, remaining_args): args.import_directories[idx] = RelativePath(tokens[0], args.depth, args.output_dir) generator_modules = LoadGenerators(args.generators_string) + check_modules = LoadChecks(args.checks_string) fileutil.EnsureDirectoryExists(args.output_dir) @@ -246,7 +273,7 @@ def _Generate(args, remaining_args): for filename in args.filename: processor._GenerateModule( - args, remaining_args, generator_modules, + args, remaining_args, check_modules, generator_modules, RelativePath(filename, args.depth, args.output_dir), []) return 0 @@ -286,6 +313,12 @@ def main(): metavar="GENERATORS", default="c++,javascript,java,mojolpm", help="comma-separated list of generators") + generate_parser.add_argument("-c", + "--checks", + dest="checks_string", + metavar="CHECKS", + default="attributes,definitions,restrictions", + help="comma-separated list of checks") generate_parser.add_argument( "--gen_dir", dest="gen_directories", action="append", metavar="directory", default=[], help="add a directory to be searched for the syntax trees.") @@ -308,11 +341,6 @@ def main(): generate_parser.add_argument("--for_blink", action="store_true", help="Use WTF types as generated types for mojo " "string/array/map.") - generate_parser.add_argument( - "--js_bindings_mode", choices=["new", "old"], default="old", - help="This option only affects the JavaScript bindings. The value could " - "be \"new\" to generate new-style lite JS bindings in addition to the " - "old, or \"old\" to only generate old bindings.") generate_parser.add_argument( "--js_generate_struct_deserializers", action="store_true", help="Generate javascript deserialize methods for structs in " @@ -387,4 +415,10 @@ def main(): if __name__ == "__main__": with crbug_1001171.DumpStateOnLookupError(): - sys.exit(main()) + ret = main() + # Exit without running GC, which can save multiple seconds due to the large + # number of object created. But flush is necessary as os._exit doesn't do + # that. + sys.stdout.flush() + sys.stderr.flush() + os._exit(ret) diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py index bddbe3f4..761922b6 100644 --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage from mojom_bindings_generator import ScrambleMethodOrdinals -class FakeIface(object): +class FakeIface: def __init__(self): self.mojom_name = None self.methods = None -class FakeMethod(object): +class FakeMethod: def __init__(self, explicit_ordinal=None): self.explicit_ordinal = explicit_ordinal self.ordinal = explicit_ordinal diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py index f1783d59..1d940a21 100755 --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -17,7 +17,7 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename): ]) _SUPPORTED_TYPE_KEYS = set([ 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable', - 'move_only', 'nullable_is_same_type' + 'move_only', 'nullable_is_same_type', 'forward_declaration' ]) with open(config_filename, 'r') as f: for config in json.load(f): diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn new file mode 100644 index 00000000..4e456c0e --- /dev/null +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +group("tests") { + data = [ + "check_stable_mojom_compatibility_unittest.py", + "check_stable_mojom_compatibility.py", + "const_unittest.py", + "enum_unittest.py", + "mojom_parser_test_case.py", + "mojom_parser_unittest.py", + "mojom_parser.py", + "stable_attribute_unittest.py", + "version_compatibility_unittest.py", + ] +} diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py index 08bd672f..3c1ac42c 100755 --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Verifies backward-compatibility of mojom type changes. @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making breaking changes to stable mojoms.""" import argparse -import errno import io import json import os import os.path -import shutil -import six import sys -import tempfile from mojom.generate import module from mojom.generate import translate from mojom.parse import parser +# pylint: disable=raise-missing-from + class ParseError(Exception): pass @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta): transitive closure of a mojom's input dependencies all at once. """ + translate.is_running_backwards_compatibility_check_hack = True + # First build a map of all files covered by the delta affected_files = set() old_files = {} @@ -73,11 +73,33 @@ def _ValidateDelta(root, delta): try: ast = parser.Parse(contents, mojom) except Exception as e: - six.reraise( - ParseError, - 'encountered exception {0} while parsing {1}'.format(e, mojom), - sys.exc_info()[2]) + raise ParseError('encountered exception {0} while parsing {1}'.format( + e, mojom)) + + # Files which are generated at compile time can't be checked by this script + # (at the moment) since they may not exist in the output directory. + generated_files_to_skip = { + ('third_party/blink/public/mojom/runtime_feature_state/' + 'runtime_feature_state.mojom'), + } + + ast.import_list.items = [ + x for x in ast.import_list.items + if x.import_filename not in generated_files_to_skip + ] + for imp in ast.import_list: + if (not file_overrides.get(imp.import_filename) + and not os.path.exists(os.path.join(root, imp.import_filename))): + # Speculatively construct a path prefix to locate the import_filename + mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep) + test_prefix = '' + for path_component in mojom_path: + test_prefix = os.path.join(test_prefix, path_component) + test_import_filename = os.path.join(test_prefix, imp.import_filename) + if os.path.exists(os.path.join(root, test_import_filename)): + imp.import_filename = test_import_filename + break parseMojom(imp.import_filename, file_overrides, override_modules) # Now that the transitive set of dependencies has been imported and parsed @@ -89,10 +111,10 @@ def _ValidateDelta(root, delta): modules[mojom] = translate.OrderedModule(ast, mojom, all_modules) old_modules = {} - for mojom in old_files.keys(): + for mojom in old_files: parseMojom(mojom, old_files, old_modules) new_modules = {} - for mojom in new_files.keys(): + for mojom in new_files: parseMojom(mojom, new_files, new_modules) # At this point we have a complete set of translated Modules from both the diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py index 9f51ea77..06769c95 100755 --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility from mojom.generate import module -class Change(object): +class Change: """Helper to clearly define a mojom file delta to be analyzed.""" def __init__(self, filename, old=None, new=None): @@ -28,7 +28,7 @@ class Change(object): class UnchangedFile(Change): def __init__(self, filename, contents): - super(UnchangedFile, self).__init__(filename, old=contents, new=contents) + super().__init__(filename, old=contents, new=contents) class CheckStableMojomCompatibilityTest(unittest.TestCase): @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase): [Stable] struct T { foo.S s; int32 x; }; """) ]) + + def testWithPartialImport(self): + """The compatibility checking tool correctly parses imports with partial + paths.""" + self.assertBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('foo/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('foo/bar.mojom', + old="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertNotBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertNotBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + def testNewEnumDefault(self): + # Should be backwards compatible since it does not affect the wire format. + # This specific case also checks that the backwards compatibility checker + # does not throw an error due to the older version of the enum not + # specifying [Default]. + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Extensible] enum E { One };', + new='[Extensible] enum E { [Default] One };') + ]) + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Extensible] enum E { [Default] One, Two, };', + new='[Extensible] enum E { One, [Default] Two, };') + ]) diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py index cb42dfac..e8ed36a7 100644 --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py index d9005078..9269cde5 100644 --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase): self.assertEqual('F', b.enums[0].mojom_name) self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name) self.assertEqual(37, b.enums[0].fields[0].numeric_value) + + def testEnumAttributesAreEnums(self): + """Verifies that enum values in attributes are really enum types.""" + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };') + b_mojom = 'b.mojom' + self.WriteFile( + b_mojom, 'module b;' + 'import "a.mojom";' + '[MooCow=a.E.kFoo]' + 'interface Foo { Foo(); };') + self.ParseMojoms([a_mojom, b_mojom]) + b = self.LoadModule(b_mojom) + self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo') + + def testConstantAttributes(self): + """Verifies that constants as attributes are translated to the constant.""" + a_mojom = 'a.mojom' + self.WriteFile( + a_mojom, 'module a;' + 'enum E { kFoo, kBar };' + 'const E kB = E.kFoo;' + '[Attr=kB] interface Hello { Foo(); };') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB') + self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name, + 'kFoo') diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn index 51facc0c..a0edf0eb 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,6 +8,7 @@ group("mojom") { "error.py", "fileutil.py", "generate/__init__.py", + "generate/check.py", "generate/generator.py", "generate/module.py", "generate/pack.py", diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py index 8a1e03da..dd53b835 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py index bf626f54..29daec36 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py @@ -1,4 +1,4 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. +# Copyright 2015 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py index ff5753a2..48eaf4ec 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. +# Copyright 2015 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py new file mode 100644 index 00000000..1efe2022 --- /dev/null +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py @@ -0,0 +1,26 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Code shared by the various pre-generation mojom checkers.""" + + +class CheckException(Exception): + def __init__(self, module, message): + self.module = module + self.message = message + super().__init__(self.message) + + def __str__(self): + return "Failed mojo pre-generation check for {}:\n{}".format( + self.module.path, self.message) + + +class Check: + def __init__(self, module): + self.module = module + + def CheckModule(self): + """ Subclass should return True if its Checks pass, and throw an + exception otherwise. CheckModule will be called immediately before + mojom.generate.Generator.GenerateFiles()""" + raise NotImplementedError("Subclasses must override/implement this method") diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py index 4a1c73fc..31cacd0e 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Code shared by the various language-specific code generators.""" @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier): return _ToSnakeCase(identifier, upper=False) -class Stylizer(object): +class Stylizer: """Stylizers specify naming rules to map mojom names to names in generated code. For example, if you would like method_name in mojom to be mapped to MethodName in the generated code, you need to define a subclass of Stylizer @@ -233,7 +233,7 @@ def AddComputedData(module): _AddInterfaceComputedData(interface) -class Generator(object): +class Generator: # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all # files to stdout. def __init__(self, @@ -243,7 +243,6 @@ class Generator(object): variant=None, bytecode_path=None, for_blink=False, - js_bindings_mode="new", js_generate_struct_deserializers=False, export_attribute=None, export_header=None, @@ -262,7 +261,6 @@ class Generator(object): self.variant = variant self.bytecode_path = bytecode_path self.for_blink = for_blink - self.js_bindings_mode = js_bindings_mode self.js_generate_struct_deserializers = js_generate_struct_deserializers self.export_attribute = export_attribute self.export_header = export_header diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py index 32c884a8..76cda398 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py index 9bdb28e0..f0664b31 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,15 +12,14 @@ # method = interface.AddMethod('Tat', 0) # method.AddParameter('baz', 0, mojom.INT32) -import sys -if sys.version_info.major == 2: - import cPickle as pickle -else: - import pickle +import pickle +from collections import OrderedDict from uuid import UUID +# pylint: disable=raise-missing-from -class BackwardCompatibilityChecker(object): + +class BackwardCompatibilityChecker: """Used for memoization while recursively checking two type definitions for backward-compatibility.""" @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True): return obj.Repr(as_ref=as_ref) # Since we cannot implement Repr for existing container types, we # handle them here. - elif isinstance(obj, list): + if isinstance(obj, list): if not obj: return '[]' - else: - return ('[\n%s\n]' % (',\n'.join( - ' %s' % Repr(elem, as_ref).replace('\n', '\n ') - for elem in obj))) - elif isinstance(obj, dict): + return ('[\n%s\n]' % + (',\n'.join(' %s' % Repr(elem, as_ref).replace('\n', '\n ') + for elem in obj))) + if isinstance(obj, dict): if not obj: return '{}' - else: - return ('{\n%s\n}' % (',\n'.join( - ' %s: %s' % (Repr(key, as_ref).replace('\n', '\n '), - Repr(val, as_ref).replace('\n', '\n ')) - for key, val in obj.items()))) - else: - return repr(obj) + return ('{\n%s\n}' % (',\n'.join(' %s: %s' % + (Repr(key, as_ref).replace('\n', '\n '), + Repr(val, as_ref).replace('\n', '\n ')) + for key, val in obj.items()))) + return repr(obj) def GenericRepr(obj, names): @@ -104,7 +100,7 @@ def GenericRepr(obj, names): ReprIndent(name, as_ref) for (name, as_ref) in names.items())) -class Kind(object): +class Kind: """Kind represents a type (e.g. int8, string). Attributes: @@ -112,16 +108,43 @@ class Kind(object): module: {Module} The defining module. Set to None for built-in types. parent_kind: The enclosing type. For example, an enum defined inside an interface has that interface as its parent. May be None. + is_nullable: True if the type is nullable. """ - def __init__(self, spec=None, module=None): + def __init__(self, spec=None, is_nullable=False, module=None): self.spec = spec self.module = module self.parent_kind = None + self.is_nullable = is_nullable + self.shared_definition = {} + + @classmethod + def AddSharedProperty(cls, name): + """Adds a property |name| to |cls|, which accesses the corresponding item in + |shared_definition|. + + The reason of adding such indirection is to enable sharing definition + between a reference kind and its nullable variation. For example: + a = Struct('test_struct_1') + b = a.MakeNullableKind() + a.name = 'test_struct_2' + print(b.name) # Outputs 'test_struct_2'. + """ + def Get(self): + try: + return self.shared_definition[name] + except KeyError: # Must raise AttributeError if property doesn't exist. + raise AttributeError + + def Set(self, value): + self.shared_definition[name] = value + + setattr(cls, name, property(Get, Set)) def Repr(self, as_ref=True): # pylint: disable=unused-argument - return '<%s spec=%r>' % (self.__class__.__name__, self.spec) + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, + self.is_nullable) def __repr__(self): # Gives us a decent __repr__ for all kinds. @@ -130,7 +153,8 @@ class Kind(object): def __eq__(self, rhs): # pylint: disable=unidiomatic-typecheck return (type(self) == type(rhs) - and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind)) + and (self.spec, self.parent_kind, self.is_nullable) + == (rhs.spec, rhs.parent_kind, rhs.is_nullable)) def __hash__(self): # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind @@ -138,32 +162,113 @@ class Kind(object): # some primitive Kinds as dict keys. The default hash (object identity) # breaks these dicts when a pickled Module instance is unpickled and used # during a subsequent run of the parser. - return hash((self.spec, self.parent_kind)) + return hash((self.spec, self.parent_kind, self.is_nullable)) # pylint: disable=unused-argument def IsBackwardCompatible(self, rhs, checker): return self == rhs +class ValueKind(Kind): + """ValueKind represents values that aren't reference kinds. + + The primary difference is the wire representation for nullable value kinds + still reserves space for the value type itself, even if that value itself + is logically null. + """ + def __init__(self, spec=None, is_nullable=False, module=None): + assert spec is None or is_nullable == spec.startswith('?') + Kind.__init__(self, spec, is_nullable, module) + + def MakeNullableKind(self): + assert not self.is_nullable + + if self == BOOL: + return NULLABLE_BOOL + if self == INT8: + return NULLABLE_INT8 + if self == INT16: + return NULLABLE_INT16 + if self == INT32: + return NULLABLE_INT32 + if self == INT64: + return NULLABLE_INT64 + if self == UINT8: + return NULLABLE_UINT8 + if self == UINT16: + return NULLABLE_UINT16 + if self == UINT32: + return NULLABLE_UINT32 + if self == UINT64: + return NULLABLE_UINT64 + if self == FLOAT: + return NULLABLE_FLOAT + if self == DOUBLE: + return NULLABLE_DOUBLE + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = '?' + self.spec + nullable_kind.is_nullable = True + nullable_kind.parent_kind = self.parent_kind + nullable_kind.module = self.module + + return nullable_kind + + def MakeUnnullableKind(self): + assert self.is_nullable + + if self == NULLABLE_BOOL: + return BOOL + if self == NULLABLE_INT8: + return INT8 + if self == NULLABLE_INT16: + return INT16 + if self == NULLABLE_INT32: + return INT32 + if self == NULLABLE_INT64: + return INT64 + if self == NULLABLE_UINT8: + return UINT8 + if self == NULLABLE_UINT16: + return UINT16 + if self == NULLABLE_UINT32: + return UINT32 + if self == NULLABLE_UINT64: + return UINT64 + if self == NULLABLE_FLOAT: + return FLOAT + if self == NULLABLE_DOUBLE: + return DOUBLE + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = self.spec[1:] + nullable_kind.is_nullable = False + nullable_kind.parent_kind = self.parent_kind + nullable_kind.module = self.module + + return nullable_kind + + def __eq__(self, rhs): + return (isinstance(rhs, ValueKind) and super().__eq__(rhs)) + + def __hash__(self): # pylint: disable=useless-super-delegation + return super().__hash__() + + class ReferenceKind(Kind): """ReferenceKind represents pointer and handle types. A type is nullable if null (for pointer types) or invalid handle (for handle types) is a legal value for the type. - - Attributes: - is_nullable: True if the type is nullable. """ def __init__(self, spec=None, is_nullable=False, module=None): assert spec is None or is_nullable == spec.startswith('?') - Kind.__init__(self, spec, module) - self.is_nullable = is_nullable - self.shared_definition = {} - - def Repr(self, as_ref=True): - return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, - self.is_nullable) + Kind.__init__(self, spec, is_nullable, module) def MakeNullableKind(self): assert not self.is_nullable @@ -193,55 +298,36 @@ class ReferenceKind(Kind): return nullable_kind - @classmethod - def AddSharedProperty(cls, name): - """Adds a property |name| to |cls|, which accesses the corresponding item in - |shared_definition|. - - The reason of adding such indirection is to enable sharing definition - between a reference kind and its nullable variation. For example: - a = Struct('test_struct_1') - b = a.MakeNullableKind() - a.name = 'test_struct_2' - print(b.name) # Outputs 'test_struct_2'. - """ - - def Get(self): - try: - return self.shared_definition[name] - except KeyError: # Must raise AttributeError if property doesn't exist. - raise AttributeError - - def Set(self, value): - self.shared_definition[name] = value - - setattr(cls, name, property(Get, Set)) - def __eq__(self, rhs): - return (isinstance(rhs, ReferenceKind) - and super(ReferenceKind, self).__eq__(rhs) - and self.is_nullable == rhs.is_nullable) - - def __hash__(self): - return hash((super(ReferenceKind, self).__hash__(), self.is_nullable)) + return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs)) - def IsBackwardCompatible(self, rhs, checker): - return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker) - and self.is_nullable == rhs.is_nullable) + def __hash__(self): # pylint: disable=useless-super-delegation + return super().__hash__() # Initialize the set of primitive types. These can be accessed by clients. -BOOL = Kind('b') -INT8 = Kind('i8') -INT16 = Kind('i16') -INT32 = Kind('i32') -INT64 = Kind('i64') -UINT8 = Kind('u8') -UINT16 = Kind('u16') -UINT32 = Kind('u32') -UINT64 = Kind('u64') -FLOAT = Kind('f') -DOUBLE = Kind('d') +BOOL = ValueKind('b') +INT8 = ValueKind('i8') +INT16 = ValueKind('i16') +INT32 = ValueKind('i32') +INT64 = ValueKind('i64') +UINT8 = ValueKind('u8') +UINT16 = ValueKind('u16') +UINT32 = ValueKind('u32') +UINT64 = ValueKind('u64') +FLOAT = ValueKind('f') +DOUBLE = ValueKind('d') +NULLABLE_BOOL = ValueKind('?b', True) +NULLABLE_INT8 = ValueKind('?i8', True) +NULLABLE_INT16 = ValueKind('?i16', True) +NULLABLE_INT32 = ValueKind('?i32', True) +NULLABLE_INT64 = ValueKind('?i64', True) +NULLABLE_UINT8 = ValueKind('?u8', True) +NULLABLE_UINT16 = ValueKind('?u16', True) +NULLABLE_UINT32 = ValueKind('?u32', True) +NULLABLE_UINT64 = ValueKind('?u64', True) +NULLABLE_FLOAT = ValueKind('?f', True) +NULLABLE_DOUBLE = ValueKind('?d', True) STRING = ReferenceKind('s') HANDLE = ReferenceKind('h') DCPIPE = ReferenceKind('h:d:c') @@ -270,6 +356,17 @@ PRIMITIVES = ( UINT64, FLOAT, DOUBLE, + NULLABLE_BOOL, + NULLABLE_INT8, + NULLABLE_INT16, + NULLABLE_INT32, + NULLABLE_INT64, + NULLABLE_UINT8, + NULLABLE_UINT16, + NULLABLE_UINT32, + NULLABLE_UINT64, + NULLABLE_FLOAT, + NULLABLE_DOUBLE, STRING, HANDLE, DCPIPE, @@ -294,9 +391,12 @@ ATTRIBUTE_STABLE = 'Stable' ATTRIBUTE_SYNC = 'Sync' ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize' ATTRIBUTE_UUID = 'Uuid' +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox' +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext' +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext' -class NamedValue(object): +class NamedValue: def __init__(self, module, parent_kind, mojom_name): self.module = module self.parent_kind = parent_kind @@ -316,7 +416,7 @@ class NamedValue(object): return hash((self.parent_kind, self.mojom_name)) -class BuiltinValue(object): +class BuiltinValue: def __init__(self, value): self.value = value @@ -350,7 +450,7 @@ class EnumValue(NamedValue): return self.field.name -class Constant(object): +class Constant: def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None): self.mojom_name = mojom_name self.name = None @@ -368,7 +468,7 @@ class Constant(object): rhs.parent_kind)) -class Field(object): +class Field: def __init__(self, mojom_name=None, kind=None, @@ -414,7 +514,18 @@ class StructField(Field): class UnionField(Field): - pass + def __init__(self, + mojom_name=None, + kind=None, + ordinal=None, + default=None, + attributes=None): + Field.__init__(self, mojom_name, kind, ordinal, default, attributes) + + @property + def is_default(self): + return self.attributes.get(ATTRIBUTE_DEFAULT, False) \ + if self.attributes else False def _IsFieldBackwardCompatible(new_field, old_field, checker): @@ -441,14 +552,14 @@ class Struct(ReferenceKind): if it's a native struct. """ - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('native_only') - ReferenceKind.AddSharedProperty('custom_serializer') - ReferenceKind.AddSharedProperty('fields') - ReferenceKind.AddSharedProperty('enums') - ReferenceKind.AddSharedProperty('constants') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('native_only') + Kind.AddSharedProperty('custom_serializer') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('enums') + Kind.AddSharedProperty('constants') + Kind.AddSharedProperty('attributes') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -470,12 +581,11 @@ class Struct(ReferenceKind): return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__, self.mojom_name, Repr(self.module, as_ref=True)) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'fields': False, - 'module': True - }) + return GenericRepr(self, { + 'mojom_name': False, + 'fields': False, + 'module': True + }) def AddField(self, mojom_name, @@ -496,13 +606,13 @@ class Struct(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) - def IsBackwardCompatible(self, older_struct, checker): - """This struct is backward-compatible with older_struct if and only if all - of the following conditions hold: + def IsBackwardCompatible(self, rhs, checker): + """This struct is backward-compatible with rhs (older_struct) if and only if + all of the following conditions hold: - Any newly added field is tagged with a [MinVersion] attribute specifying a version number greater than all previously used [MinVersion] attributes within the struct. - - All fields present in older_struct remain present in the new struct, + - All fields present in rhs remain present in the new struct, with the same ordinal position, same optional or non-optional status, same (or backward-compatible) type and where applicable, the same [MinVersion] attribute value. @@ -521,7 +631,7 @@ class Struct(ReferenceKind): return fields_by_ordinal new_fields = buildOrdinalFieldMap(self) - old_fields = buildOrdinalFieldMap(older_struct) + old_fields = buildOrdinalFieldMap(rhs) if len(new_fields) < len(old_fields): # At least one field was removed, which is not OK. return False @@ -574,11 +684,18 @@ class Struct(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.native_only, self.fields, self.constants, + self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Struct) and - (self.mojom_name, self.native_only, self.fields, self.constants, - self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields, - rhs.constants, rhs.attributes)) + return isinstance(rhs, Struct) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) @@ -595,10 +712,11 @@ class Union(ReferenceKind): which Java class name to use to represent it in the generated bindings. """ - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('fields') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('attributes') + Kind.AddSharedProperty('default_field') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -610,14 +728,14 @@ class Union(ReferenceKind): self.name = None self.fields = [] self.attributes = attributes + self.default_field = None def Repr(self, as_ref=True): if as_ref: return '<%s spec=%r is_nullable=%r fields=%s>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr( self.fields)) - else: - return GenericRepr(self, {'fields': True, 'is_nullable': False}) + return GenericRepr(self, {'fields': True, 'is_nullable': False}) def AddField(self, mojom_name, kind, ordinal=None, attributes=None): field = UnionField(mojom_name, kind, ordinal, None, attributes) @@ -629,13 +747,13 @@ class Union(ReferenceKind): for field in self.fields: field.Stylize(stylizer) - def IsBackwardCompatible(self, older_union, checker): - """This union is backward-compatible with older_union if and only if all - of the following conditions hold: + def IsBackwardCompatible(self, rhs, checker): + """This union is backward-compatible with rhs (older_union) if and only if + all of the following conditions hold: - Any newly added field is tagged with a [MinVersion] attribute specifying a version number greater than all previously used [MinVersion] attributes within the union. - - All fields present in older_union remain present in the new union, + - All fields present in rhs remain present in the new union, with the same ordinal value, same optional or non-optional status, same (or backward-compatible) type, and where applicable, the same [MinVersion] attribute value. @@ -651,7 +769,7 @@ class Union(ReferenceKind): return fields_by_ordinal new_fields = buildOrdinalFieldMap(self) - old_fields = buildOrdinalFieldMap(older_union) + old_fields = buildOrdinalFieldMap(rhs) if len(new_fields) < len(old_fields): # At least one field was removed, which is not OK. return False @@ -677,6 +795,11 @@ class Union(ReferenceKind): return True + @property + def extensible(self): + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ + if self.attributes else False + @property def stable(self): return self.attributes.get(ATTRIBUTE_STABLE, False) \ @@ -690,10 +813,17 @@ class Union(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.fields, self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Union) and - (self.mojom_name, self.fields, - self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes)) + return isinstance(rhs, Union) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) @@ -707,8 +837,8 @@ class Array(ReferenceKind): length: The number of elements. None if unknown. """ - ReferenceKind.AddSharedProperty('kind') - ReferenceKind.AddSharedProperty('length') + Kind.AddSharedProperty('kind') + Kind.AddSharedProperty('length') def __init__(self, kind=None, length=None): if kind is not None: @@ -728,12 +858,11 @@ class Array(ReferenceKind): return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr( self.kind), self.length) - else: - return GenericRepr(self, { - 'kind': True, - 'length': False, - 'is_nullable': False - }) + return GenericRepr(self, { + 'kind': True, + 'length': False, + 'is_nullable': False + }) def __eq__(self, rhs): return (isinstance(rhs, Array) @@ -754,8 +883,8 @@ class Map(ReferenceKind): key_kind: {Kind} The type of the keys. May be None. value_kind: {Kind} The type of the elements. May be None. """ - ReferenceKind.AddSharedProperty('key_kind') - ReferenceKind.AddSharedProperty('value_kind') + Kind.AddSharedProperty('key_kind') + Kind.AddSharedProperty('value_kind') def __init__(self, key_kind=None, value_kind=None): if (key_kind is not None and value_kind is not None): @@ -780,8 +909,7 @@ class Map(ReferenceKind): return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr(self.key_kind), Repr(self.value_kind)) - else: - return GenericRepr(self, {'key_kind': True, 'value_kind': True}) + return GenericRepr(self, {'key_kind': True, 'value_kind': True}) def __eq__(self, rhs): return (isinstance(rhs, Map) and @@ -797,7 +925,7 @@ class Map(ReferenceKind): class PendingRemote(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -822,7 +950,7 @@ class PendingRemote(ReferenceKind): class PendingReceiver(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -847,7 +975,7 @@ class PendingReceiver(ReferenceKind): class PendingAssociatedRemote(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -873,7 +1001,7 @@ class PendingAssociatedRemote(ReferenceKind): class PendingAssociatedReceiver(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -899,7 +1027,7 @@ class PendingAssociatedReceiver(ReferenceKind): class InterfaceRequest(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -923,7 +1051,7 @@ class InterfaceRequest(ReferenceKind): class AssociatedInterfaceRequest(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -949,7 +1077,7 @@ class AssociatedInterfaceRequest(ReferenceKind): self.kind, rhs.kind) -class Parameter(object): +class Parameter: def __init__(self, mojom_name=None, kind=None, @@ -983,7 +1111,7 @@ class Parameter(object): rhs.default, rhs.attributes)) -class Method(object): +class Method: def __init__(self, interface, mojom_name, ordinal=None, attributes=None): self.interface = interface self.mojom_name = mojom_name @@ -999,12 +1127,11 @@ class Method(object): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'parameters': True, - 'response_parameters': True - }) + return GenericRepr(self, { + 'mojom_name': False, + 'parameters': True, + 'response_parameters': True + }) def AddParameter(self, mojom_name, @@ -1061,21 +1188,32 @@ class Method(object): return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \ if self.attributes else False + @property + def allowed_context(self): + return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \ + if self.attributes else None + + def _tuple(self): + return (self.mojom_name, self.ordinal, self.parameters, + self.response_parameters, self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Method) and - (self.mojom_name, self.ordinal, self.parameters, - self.response_parameters, - self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters, - rhs.response_parameters, rhs.attributes)) + return isinstance(rhs, Method) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() class Interface(ReferenceKind): - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('methods') - ReferenceKind.AddSharedProperty('enums') - ReferenceKind.AddSharedProperty('constants') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('methods') + Kind.AddSharedProperty('enums') + Kind.AddSharedProperty('constants') + Kind.AddSharedProperty('attributes') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -1093,12 +1231,11 @@ class Interface(ReferenceKind): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'attributes': False, - 'methods': False - }) + return GenericRepr(self, { + 'mojom_name': False, + 'attributes': False, + 'methods': False + }) def AddMethod(self, mojom_name, ordinal=None, attributes=None): method = Method(self, mojom_name, ordinal, attributes) @@ -1114,10 +1251,10 @@ class Interface(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) - def IsBackwardCompatible(self, older_interface, checker): - """This interface is backward-compatible with older_interface if and only - if all of the following conditions hold: - - All defined methods in older_interface (when identified by ordinal) have + def IsBackwardCompatible(self, rhs, checker): + """This interface is backward-compatible with rhs (older_interface) if and + only if all of the following conditions hold: + - All defined methods in rhs (when identified by ordinal) have backward-compatible definitions in this interface. For each method this means: - The parameter list is backward-compatible, according to backward- @@ -1131,7 +1268,7 @@ class Interface(ReferenceKind): rules for structs. - All newly introduced methods in this interface have a [MinVersion] attribute specifying a version greater than any method in - older_interface. + rhs. """ def buildOrdinalMethodMap(interface): @@ -1144,7 +1281,7 @@ class Interface(ReferenceKind): return methods_by_ordinal new_methods = buildOrdinalMethodMap(self) - old_methods = buildOrdinalMethodMap(older_interface) + old_methods = buildOrdinalMethodMap(rhs) max_old_min_version = 0 for ordinal, old_method in old_methods.items(): new_method = new_methods.get(ordinal) @@ -1186,6 +1323,27 @@ class Interface(ReferenceKind): return True + @property + def service_sandbox(self): + if not self.attributes: + return None + service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None) + if service_sandbox is None: + return None + # Constants are only allowed to refer to an enum here, so replace. + if isinstance(service_sandbox, Constant): + service_sandbox = service_sandbox.value + if not isinstance(service_sandbox, EnumValue): + raise Exception("ServiceSandbox attribute on %s must be an enum value." % + self.module.name) + return service_sandbox + + @property + def require_context(self): + if not self.attributes: + return None + return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None) + @property def stable(self): return self.attributes.get(ATTRIBUTE_STABLE, False) \ @@ -1199,11 +1357,18 @@ class Interface(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.methods, self.enums, self.constants, + self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Interface) - and (self.mojom_name, self.methods, self.enums, self.constants, - self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums, - rhs.constants, rhs.attributes)) + return isinstance(rhs, Interface) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() @property def uuid(self): @@ -1224,7 +1389,7 @@ class Interface(ReferenceKind): class AssociatedInterface(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -1249,7 +1414,7 @@ class AssociatedInterface(ReferenceKind): self.kind, rhs.kind) -class EnumField(object): +class EnumField: def __init__(self, mojom_name=None, value=None, @@ -1281,16 +1446,25 @@ class EnumField(object): rhs.attributes, rhs.numeric_value)) -class Enum(Kind): +class Enum(ValueKind): + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('native_only') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('attributes') + Kind.AddSharedProperty('min_value') + Kind.AddSharedProperty('max_value') + Kind.AddSharedProperty('default_field') + def __init__(self, mojom_name=None, module=None, attributes=None): - self.mojom_name = mojom_name - self.name = None - self.native_only = False if mojom_name is not None: spec = 'x:' + mojom_name else: spec = None - Kind.__init__(self, spec, module) + ValueKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.native_only = False self.fields = [] self.attributes = attributes self.min_value = None @@ -1300,8 +1474,7 @@ class Enum(Kind): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, {'mojom_name': False, 'fields': False}) + return GenericRepr(self, {'mojom_name': False, 'fields': False}) def Stylize(self, stylizer): self.name = stylizer.StylizeEnum(self.mojom_name) @@ -1327,14 +1500,14 @@ class Enum(Kind): return '%s%s' % (prefix, self.mojom_name) # pylint: disable=unused-argument - def IsBackwardCompatible(self, older_enum, checker): - """This enum is backward-compatible with older_enum if and only if one of - the following conditions holds: + def IsBackwardCompatible(self, rhs, checker): + """This enum is backward-compatible with rhs (older_enum) if and only if one + of the following conditions holds: - Neither enum is [Extensible] and both have the exact same set of valid numeric values. Field names and aliases for the same numeric value do not affect compatibility. - - older_enum is [Extensible], and for every version defined by - older_enum, this enum has the exact same set of valid numeric values. + - rhs is [Extensible], and for every version defined by + rhs, this enum has the exact same set of valid numeric values. """ def buildVersionFieldMap(enum): @@ -1345,10 +1518,10 @@ class Enum(Kind): fields_by_min_version[field.min_version].add(field.numeric_value) return fields_by_min_version - old_fields = buildVersionFieldMap(older_enum) + old_fields = buildVersionFieldMap(rhs) new_fields = buildVersionFieldMap(self) - if new_fields.keys() != old_fields.keys() and not older_enum.extensible: + if new_fields.keys() != old_fields.keys() and not rhs.extensible: return False for min_version, valid_values in old_fields.items(): @@ -1358,19 +1531,24 @@ class Enum(Kind): return True + def _tuple(self): + return (self.mojom_name, self.native_only, self.fields, self.attributes, + self.min_value, self.max_value, self.default_field) + def __eq__(self, rhs): - return (isinstance(rhs, Enum) and - (self.mojom_name, self.native_only, self.fields, self.attributes, - self.min_value, self.max_value, - self.default_field) == (rhs.mojom_name, rhs.native_only, - rhs.fields, rhs.attributes, rhs.min_value, - rhs.max_value, rhs.default_field)) + return isinstance(rhs, Enum) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) -class Module(object): +class Module: def __init__(self, path=None, mojom_namespace=None, attributes=None): self.path = path self.mojom_namespace = mojom_namespace @@ -1380,11 +1558,11 @@ class Module(object): self.interfaces = [] self.enums = [] self.constants = [] - self.kinds = {} + self.kinds = OrderedDict() self.attributes = attributes self.imports = [] - self.imported_kinds = {} - self.metadata = {} + self.imported_kinds = OrderedDict() + self.metadata = OrderedDict() def __repr__(self): # Gives us a decent __repr__ for modules. @@ -1405,16 +1583,15 @@ class Module(object): if as_ref: return '<%s path=%r mojom_namespace=%r>' % ( self.__class__.__name__, self.path, self.mojom_namespace) - else: - return GenericRepr( - self, { - 'path': False, - 'mojom_namespace': False, - 'attributes': False, - 'structs': False, - 'interfaces': False, - 'unions': False - }) + return GenericRepr( + self, { + 'path': False, + 'mojom_namespace': False, + 'attributes': False, + 'structs': False, + 'interfaces': False, + 'unions': False + }) def GetNamespacePrefix(self): return '%s.' % self.mojom_namespace if self.mojom_namespace else '' @@ -1451,7 +1628,7 @@ class Module(object): imported_module.Stylize(stylizer) def Dump(self, f): - pickle.dump(self, f, 2) + pickle.dump(self, f) @classmethod def Load(cls, f): @@ -1461,15 +1638,15 @@ class Module(object): def IsBoolKind(kind): - return kind.spec == BOOL.spec + return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec def IsFloatKind(kind): - return kind.spec == FLOAT.spec + return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec def IsDoubleKind(kind): - return kind.spec == DOUBLE.spec + return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec def IsIntegralKind(kind): @@ -1477,7 +1654,14 @@ def IsIntegralKind(kind): or kind.spec == INT16.spec or kind.spec == INT32.spec or kind.spec == INT64.spec or kind.spec == UINT8.spec or kind.spec == UINT16.spec or kind.spec == UINT32.spec - or kind.spec == UINT64.spec) + or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec + or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec + or kind.spec == NULLABLE_INT32.spec + or kind.spec == NULLABLE_INT64.spec + or kind.spec == NULLABLE_UINT8.spec + or kind.spec == NULLABLE_UINT16.spec + or kind.spec == NULLABLE_UINT32.spec + or kind.spec == NULLABLE_UINT64.spec) def IsStringKind(kind): @@ -1563,7 +1747,7 @@ def IsReferenceKind(kind): def IsNullableKind(kind): - return IsReferenceKind(kind) and kind.is_nullable + return kind.is_nullable def IsMapKind(kind): @@ -1664,11 +1848,8 @@ def MethodPassesInterfaces(method): return _AnyMethodParameterRecursive(method, IsInterfaceKind) -def HasSyncMethods(interface): - for method in interface.methods: - if method.sync: - return True - return False +def GetSyncMethodOrdinals(interface): + return [method.ordinal for method in interface.methods if method.sync] def HasUninterruptableMethods(interface): @@ -1700,18 +1881,17 @@ def ContainsHandlesOrInterfaces(kind): checked.add(kind.spec) if IsStructKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsUnionKind(kind): + if IsUnionKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsAnyHandleKind(kind): + if IsAnyHandleKind(kind): return True - elif IsAnyInterfaceKind(kind): + if IsAnyInterfaceKind(kind): return True - elif IsArrayKind(kind): + if IsArrayKind(kind): return Check(kind.kind) - elif IsMapKind(kind): + if IsMapKind(kind): return Check(kind.key_kind) or Check(kind.value_kind) - else: - return False + return False return Check(kind) @@ -1738,21 +1918,20 @@ def ContainsNativeTypes(kind): checked.add(kind.spec) if IsEnumKind(kind): return kind.native_only - elif IsStructKind(kind): + if IsStructKind(kind): if kind.native_only: return True if any(enum.native_only for enum in kind.enums): return True return any(Check(field.kind) for field in kind.fields) - elif IsUnionKind(kind): + if IsUnionKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsInterfaceKind(kind): + if IsInterfaceKind(kind): return any(enum.native_only for enum in kind.enums) - elif IsArrayKind(kind): + if IsArrayKind(kind): return Check(kind.kind) - elif IsMapKind(kind): + if IsMapKind(kind): return Check(kind.key_kind) or Check(kind.value_kind) - else: - return False + return False return Check(kind) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py index e8fd4936..2a4e852c 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py index 88b77c98..71011109 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py @@ -1,7 +1,8 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import copy from mojom.generate import module as mojom # This module provides a mechanism for determining the packed order and offsets @@ -15,7 +16,7 @@ from mojom.generate import module as mojom HEADER_SIZE = 8 -class PackedField(object): +class PackedField: kind_to_size = { mojom.BOOL: 1, mojom.INT8: 1, @@ -75,18 +76,55 @@ class PackedField(object): return 8 return cls.GetSizeForKind(kind) - def __init__(self, field, index, ordinal): + def __init__(self, + field, + index, + ordinal, + original_field=None, + sub_ordinal=None, + linked_value_packed_field=None): """ Args: field: the original field. index: the position of the original field in the struct. ordinal: the ordinal of the field for serialization. + original_field: See below. + sub_ordinal: See below. + linked_value_packed_field: See below. + + original_field, sub_ordinal, and linked_value_packed_field are used to + support nullable ValueKind fields. For legacy reasons, nullable ValueKind + fields actually generate two PackedFields. This allows: + + - backwards compatibility prior to Mojo support for nullable ValueKinds. + - correct packing of fields for the aforementioned backwards compatibility. + + When translating Fields to PackedFields, the original field is turned into + two PackedFields: the first PackedField always has type mojom.BOOL, while + the second PackedField has the non-nullable version of the field's kind. + + When constructing these PackedFields, original_field references the field + as defined in the mojom; the name as defined in the mojom will be used for + all layers above the wire/data layer. + + sub_ordinal is used to sort the two PackedFields correctly with respect to + each other: the first mojom.BOOL field always has sub_ordinal 0, while the + second field always has sub_ordinal 1. + + Finally, linked_value_packed_field is used by the serialization and + deserialization helpers, which generally just iterate over a PackedStruct's + PackedField's in ordinal order. This allows the helpers to easily reference + any related PackedFields rather than having to lookup related PackedFields + by index while iterating. """ self.field = field self.index = index self.ordinal = ordinal - self.size = self.GetSizeForKind(field.kind) - self.alignment = self.GetAlignmentForKind(field.kind) + self.original_field = original_field + self.sub_ordinal = sub_ordinal + self.linked_value_packed_field = linked_value_packed_field + self.size = self.GetSizeForKind(self.field.kind) + self.alignment = self.GetAlignmentForKind(self.field.kind) self.offset = None self.bit = None self.min_version = None @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field): return offset + pad -class PackedStruct(object): +def IsNullableValueKindPackedField(field): + """Returns true if `field` is derived from a nullable ValueKind field. + + Nullable ValueKind fields often require special handling in the bindings due + to the way the implementation is constrained for wire compatibility. + """ + assert isinstance(field, PackedField) + return field.sub_ordinal is not None + + +def IsPrimaryNullableValueKindPackedField(field): + """Returns true if `field` is derived from a nullable ValueKind mojom field + and is the "primary" field. + + The primary field is a bool PackedField that controls if the field should be + considered as present or not; it will have a reference to the PackedField that + holds the actual value representation if considered present. + + Bindings code that translates between the wire protocol and the higher layers + can use this to simplify mapping multiple PackedFields to the single field + that is logically exposed to bindings consumers. + """ + assert isinstance(field, PackedField) + return field.linked_value_packed_field is not None + + +class PackedStruct: def __init__(self, struct): self.struct = struct # |packed_fields| contains all the fields, in increasing offset order. @@ -139,9 +203,41 @@ class PackedStruct(object): for index, field in enumerate(struct.fields): if field.ordinal is not None: ordinal = field.ordinal - src_fields.append(PackedField(field, index, ordinal)) + # Nullable value types are a bit weird: they generate two PackedFields + # despite being a single ValueKind. This is for wire compatibility to + # ease the transition from legacy mojom syntax where nullable value types + # were not supported. + if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable: + # The suffixes intentionally use Unicode codepoints which are considered + # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in + # actual user code. + has_value_field = copy.copy(field) + has_value_field.name = f'{field.mojom_name}_$flag' + has_value_field.kind = mojom.BOOL + + value_field = copy.copy(field) + value_field.name = f'{field.mojom_name}_$value' + value_field.kind = field.kind.MakeUnnullableKind() + + value_packed_field = PackedField(value_field, + index, + ordinal, + original_field=field, + sub_ordinal=1, + linked_value_packed_field=None) + has_value_packed_field = PackedField( + has_value_field, + index, + ordinal, + original_field=field, + sub_ordinal=0, + linked_value_packed_field=value_packed_field) + src_fields.append(has_value_packed_field) + src_fields.append(value_packed_field) + else: + src_fields.append(PackedField(field, index, ordinal)) ordinal += 1 - src_fields.sort(key=lambda field: field.ordinal) + src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal)) # Set |min_version| for each field. next_min_version = 0 @@ -156,10 +252,11 @@ class PackedStruct(object): if (packed_field.min_version != 0 and mojom.IsReferenceKind(packed_field.field.kind) and not packed_field.field.kind.is_nullable): - raise Exception("Non-nullable fields are only allowed in version 0 of " - "a struct. %s.%s is defined with [MinVersion=%d]." % - (self.struct.name, packed_field.field.name, - packed_field.min_version)) + raise Exception( + "Non-nullable reference fields are only allowed in version 0 of a " + "struct. %s.%s is defined with [MinVersion=%d]." % + (self.struct.name, packed_field.field.name, + packed_field.min_version)) src_field = src_fields[0] src_field.offset = 0 @@ -186,7 +283,7 @@ class PackedStruct(object): dst_fields.append(src_field) -class ByteInfo(object): +class ByteInfo: def __init__(self): self.is_padding = False self.packed_fields = [] @@ -214,7 +311,7 @@ def GetByteLayout(packed_struct): return byte_info -class VersionInfo(object): +class VersionInfo: def __init__(self, version, num_fields, num_bytes): self.version = version self.num_fields = num_fields diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py index 98c705ad..5c6c36d5 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py index 0da90058..807e2a4f 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py index 7580b780..71ce3c03 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Convert parse tree to AST. @@ -12,17 +12,296 @@ already been parsed and converted to ASTs before. import itertools import os import re -import sys +from collections import OrderedDict from mojom.generate import generator from mojom.generate import module as mojom from mojom.parse import ast -def _IsStrOrUnicode(x): - if sys.version_info[0] < 3: - return isinstance(x, (unicode, str)) - return isinstance(x, str) +is_running_backwards_compatibility_check_hack = False + +### DO NOT ADD ENTRIES TO THIS LIST. ### +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = ( + 'x:arc.keymaster.mojom.Algorithm', + 'x:arc.keymaster.mojom.Digest', + 'x:arc.keymaster.mojom.SignatureResult', + 'x:arc.mojom.AccessibilityActionType', + 'x:arc.mojom.AccessibilityBooleanProperty', + 'x:arc.mojom.AccessibilityEventIntListProperty', + 'x:arc.mojom.AccessibilityEventIntProperty', + 'x:arc.mojom.AccessibilityEventStringProperty', + 'x:arc.mojom.AccessibilityEventType', + 'x:arc.mojom.AccessibilityFilterType', + 'x:arc.mojom.AccessibilityIntListProperty', + 'x:arc.mojom.AccessibilityIntProperty', + 'x:arc.mojom.AccessibilityLiveRegionType', + 'x:arc.mojom.AccessibilityNotificationStateType', + 'x:arc.mojom.AccessibilityRangeType', + 'x:arc.mojom.AccessibilitySelectionMode', + 'x:arc.mojom.AccessibilityStringListProperty', + 'x:arc.mojom.AccessibilityStringProperty', + 'x:arc.mojom.AccessibilityWindowBooleanProperty', + 'x:arc.mojom.AccessibilityWindowIntListProperty', + 'x:arc.mojom.AccessibilityWindowIntProperty', + 'x:arc.mojom.AccessibilityWindowStringProperty', + 'x:arc.mojom.AccessibilityWindowType', + 'x:arc.mojom.AccountCheckStatus', + 'x:arc.mojom.AccountUpdateType', + 'x:arc.mojom.ActionType', + 'x:arc.mojom.Algorithm', + 'x:arc.mojom.AndroidIdSource', + 'x:arc.mojom.AnrSource', + 'x:arc.mojom.AnrType', + 'x:arc.mojom.AppDiscoveryRequestState', + 'x:arc.mojom.AppKillType', + 'x:arc.mojom.AppPermission', + 'x:arc.mojom.AppPermissionGroup', + 'x:arc.mojom.AppReinstallState', + 'x:arc.mojom.AppShortcutItemType', + 'x:arc.mojom.ArcAuthCodeStatus', + 'x:arc.mojom.ArcClipboardDragDropEvent', + 'x:arc.mojom.ArcCorePriAbiMigEvent', + 'x:arc.mojom.ArcDnsQuery', + 'x:arc.mojom.ArcImageCopyPasteCompatAction', + 'x:arc.mojom.ArcNetworkError', + 'x:arc.mojom.ArcNetworkEvent', + 'x:arc.mojom.ArcNotificationEvent', + 'x:arc.mojom.ArcNotificationExpandState', + 'x:arc.mojom.ArcNotificationPriority', + 'x:arc.mojom.ArcNotificationRemoteInputState', + 'x:arc.mojom.ArcNotificationShownContents', + 'x:arc.mojom.ArcNotificationStyle', + 'x:arc.mojom.ArcNotificationType', + 'x:arc.mojom.ArcPipEvent', + 'x:arc.mojom.ArcResizeLockState', + 'x:arc.mojom.ArcSignInSuccess', + 'x:arc.mojom.ArcTimerResult', + 'x:arc.mojom.AudioSwitch', + 'x:arc.mojom.BluetoothAclState', + 'x:arc.mojom.BluetoothAdapterState', + 'x:arc.mojom.BluetoothAdvertisingDataType', + 'x:arc.mojom.BluetoothBondState', + 'x:arc.mojom.BluetoothDeviceType', + 'x:arc.mojom.BluetoothDiscoveryState', + 'x:arc.mojom.BluetoothGattDBAttributeType', + 'x:arc.mojom.BluetoothGattStatus', + 'x:arc.mojom.BluetoothPropertyType', + 'x:arc.mojom.BluetoothScanMode', + 'x:arc.mojom.BluetoothSdpAttributeType', + 'x:arc.mojom.BluetoothSocketType', + 'x:arc.mojom.BluetoothStatus', + 'x:arc.mojom.BootType', + 'x:arc.mojom.CaptionTextShadowType', + 'x:arc.mojom.ChangeType', + 'x:arc.mojom.ChromeAccountType', + 'x:arc.mojom.ChromeApp', + 'x:arc.mojom.ChromePage', + 'x:arc.mojom.ClockId', + 'x:arc.mojom.CloudProvisionFlowError', + 'x:arc.mojom.CommandResultType', + 'x:arc.mojom.CompanionLibApiId', + 'x:arc.mojom.ConnectionStateType', + 'x:arc.mojom.ContentChangeType', + 'x:arc.mojom.CpuRestrictionState', + 'x:arc.mojom.CursorCoordinateSpace', + 'x:arc.mojom.DataRestoreStatus', + 'x:arc.mojom.DecoderStatus', + 'x:arc.mojom.DeviceType', + 'x:arc.mojom.Digest', + 'x:arc.mojom.DisplayWakeLockType', + 'x:arc.mojom.EapMethod', + 'x:arc.mojom.EapPhase2Method', + 'x:arc.mojom.FileSelectorEventType', + 'x:arc.mojom.GMSCheckInError', + 'x:arc.mojom.GMSSignInError', + 'x:arc.mojom.GeneralSignInError', + 'x:arc.mojom.GetNetworksRequestType', + 'x:arc.mojom.HalPixelFormat', + 'x:arc.mojom.IPAddressType', + 'x:arc.mojom.InstallErrorReason', + 'x:arc.mojom.KeyFormat', + 'x:arc.mojom.KeyManagement', + 'x:arc.mojom.KeyPurpose', + 'x:arc.mojom.KeymasterError', + 'x:arc.mojom.MainAccountHashMigrationStatus', + 'x:arc.mojom.MainAccountResolutionStatus', + 'x:arc.mojom.ManagementChangeStatus', + 'x:arc.mojom.ManagementState', + 'x:arc.mojom.MessageCenterVisibility', + 'x:arc.mojom.MetricsType', + 'x:arc.mojom.MountEvent', + 'x:arc.mojom.NativeBridgeType', + 'x:arc.mojom.NetworkResult', + 'x:arc.mojom.NetworkType', + 'x:arc.mojom.OemCryptoAlgorithm', + 'x:arc.mojom.OemCryptoCipherMode', + 'x:arc.mojom.OemCryptoHdcpCapability', + 'x:arc.mojom.OemCryptoLicenseType', + 'x:arc.mojom.OemCryptoPrivateKey', + 'x:arc.mojom.OemCryptoProvisioningMethod', + 'x:arc.mojom.OemCryptoResult', + 'x:arc.mojom.OemCryptoRsaPaddingScheme', + 'x:arc.mojom.OemCryptoUsageEntryStatus', + 'x:arc.mojom.Padding', + 'x:arc.mojom.PaiFlowState', + 'x:arc.mojom.PatternType', + 'x:arc.mojom.PressureLevel', + 'x:arc.mojom.PrintColorMode', + 'x:arc.mojom.PrintContentType', + 'x:arc.mojom.PrintDuplexMode', + 'x:arc.mojom.PrinterStatus', + 'x:arc.mojom.ProcessState', + 'x:arc.mojom.PurchaseState', + 'x:arc.mojom.ReauthReason', + 'x:arc.mojom.ScaleFactor', + 'x:arc.mojom.SecurityType', + 'x:arc.mojom.SegmentStyle', + 'x:arc.mojom.SelectFilesActionType', + 'x:arc.mojom.SetNativeChromeVoxResponse', + 'x:arc.mojom.ShareFiles', + 'x:arc.mojom.ShowPackageInfoPage', + 'x:arc.mojom.SpanType', + 'x:arc.mojom.SupportedLinkChangeSource', + 'x:arc.mojom.TetheringClientState', + 'x:arc.mojom.TextInputType', + 'x:arc.mojom.TtsEventType', + 'x:arc.mojom.VideoCodecProfile', + 'x:arc.mojom.VideoDecodeAccelerator.Result', + 'x:arc.mojom.VideoEncodeAccelerator.Error', + 'x:arc.mojom.VideoFrameStorageType', + 'x:arc.mojom.VideoPixelFormat', + 'x:arc.mojom.WakefulnessMode', + 'x:arc.mojom.WebApkInstallResult', + 'x:ash.ime.mojom.InputFieldType', + 'x:ash.ime.mojom.PersonalizationMode', + 'x:ash.language.mojom.FeatureId', + 'x:blink.mojom.ScrollRestorationType', + 'x:chrome_cleaner.mojom.PromptAcceptance', + 'x:chromeos.cdm.mojom.CdmKeyStatus', + 'x:chromeos.cdm.mojom.CdmMessageType', + 'x:chromeos.cdm.mojom.CdmSessionType', + 'x:chromeos.cdm.mojom.DecryptStatus', + 'x:chromeos.cdm.mojom.EmeInitDataType', + 'x:chromeos.cdm.mojom.EncryptionScheme', + 'x:chromeos.cdm.mojom.HdcpVersion', + 'x:chromeos.cdm.mojom.OutputProtection.LinkType', + 'x:chromeos.cdm.mojom.OutputProtection.ProtectionType', + 'x:chromeos.cdm.mojom.PromiseException', + 'x:chromeos.cfm.mojom.EnqueuePriority', + 'x:chromeos.cfm.mojom.LoggerErrorCode', + 'x:chromeos.cfm.mojom.LoggerState', + 'x:chromeos.cros_healthd.mojom.CryptoAlgorithm', + 'x:chromeos.cros_healthd.mojom.EncryptionState', + 'x:chromeos.machine_learning.mojom.AnnotationUsecase', + 'x:chromeos.machine_learning.mojom.BuiltinModelId', + 'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult', + 'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus', + 'x:chromeos.machine_learning.mojom.EndpointReason', + 'x:chromeos.machine_learning.mojom.EndpointerType', + 'x:chromeos.machine_learning.mojom.ExecuteResult', + 'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status', + 'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status', + 'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult', + 'x:chromeos.machine_learning.mojom.LoadModelResult', + 'x:chromeos.machine_learning.mojom.Rotation', + 'x:chromeos.network_config.mojom.ConnectionStateType', + 'x:chromeos.network_config.mojom.DeviceStateType', + 'x:chromeos.network_config.mojom.IPConfigType', + 'x:chromeos.network_config.mojom.NetworkType', + 'x:chromeos.network_config.mojom.OncSource', + 'x:chromeos.network_config.mojom.PolicySource', + 'x:chromeos.network_config.mojom.PortalState', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus', + 'x:cros.mojom.CameraClientType', + 'x:cros.mojom.CameraMetadataSectionStart', + 'x:cros.mojom.CameraMetadataTag', + 'x:cros.mojom.HalPixelFormat', + 'x:crosapi.mojom.AllowedPaths', + 'x:crosapi.mojom.BrowserAppInstanceType', + 'x:crosapi.mojom.CreationResult', + 'x:crosapi.mojom.DeviceAccessResultCode', + 'x:crosapi.mojom.DeviceMode', + 'x:crosapi.mojom.DlpRestrictionLevel', + 'x:crosapi.mojom.ExoImeSupport', + 'x:crosapi.mojom.FullscreenVisibility', + 'x:crosapi.mojom.GoogleServiceAuthError.State', + 'x:crosapi.mojom.IsInstallableResult', + 'x:crosapi.mojom.KeyTag', + 'x:crosapi.mojom.KeystoreSigningAlgorithmName', + 'x:crosapi.mojom.KeystoreType', + 'x:crosapi.mojom.LacrosFeedbackSource', + 'x:crosapi.mojom.MemoryPressureLevel', + 'x:crosapi.mojom.MetricsReportingManaged', + 'x:crosapi.mojom.NotificationType', + 'x:crosapi.mojom.OndeviceHandwritingSupport', + 'x:crosapi.mojom.OpenResult', + 'x:crosapi.mojom.PolicyDomain', + 'x:crosapi.mojom.RegistrationCodeType', + 'x:crosapi.mojom.ScaleFactor', + 'x:crosapi.mojom.SearchResult.OptionalBool', + 'x:crosapi.mojom.SelectFileDialogType', + 'x:crosapi.mojom.SelectFileResult', + 'x:crosapi.mojom.SharesheetResult', + 'x:crosapi.mojom.TouchEventType', + 'x:crosapi.mojom.VideoRotation', + 'x:crosapi.mojom.WallpaperLayout', + 'x:crosapi.mojom.WebAppInstallResultCode', + 'x:crosapi.mojom.WebAppUninstallResultCode', + 'x:device.mojom.HidBusType', + 'x:device.mojom.WakeLockReason', + 'x:device.mojom.WakeLockType', + 'x:drivefs.mojom.DialogReason.Type', + 'x:drivefs.mojom.DriveError.Type', + 'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus', + 'x:drivefs.mojom.FileMetadata.CanPinStatus', + 'x:drivefs.mojom.FileMetadata.Type', + 'x:drivefs.mojom.ItemEventReason', + 'x:drivefs.mojom.MirrorPathStatus', + 'x:drivefs.mojom.MirrorSyncStatus', + 'x:drivefs.mojom.QueryParameters.SortField', + 'x:fuzz.mojom.FuzzEnum', + 'x:media.mojom.FillLightMode', + 'x:media.mojom.MeteringMode', + 'x:media.mojom.PowerLineFrequency', + 'x:media.mojom.RedEyeReduction', + 'x:media.mojom.ResolutionChangePolicy', + 'x:media.mojom.VideoCaptureApi', + 'x:media.mojom.VideoCaptureBufferType', + 'x:media.mojom.VideoCaptureError', + 'x:media.mojom.VideoCaptureFrameDropReason', + 'x:media.mojom.VideoCapturePixelFormat', + 'x:media.mojom.VideoCaptureTransportType', + 'x:media.mojom.VideoFacingMode', + 'x:media_session.mojom.AudioFocusType', + 'x:media_session.mojom.CameraState', + 'x:media_session.mojom.EnforcementMode', + 'x:media_session.mojom.MediaAudioVideoState', + 'x:media_session.mojom.MediaImageBitmapColorType', + 'x:media_session.mojom.MediaPictureInPictureState', + 'x:media_session.mojom.MediaPlaybackState', + 'x:media_session.mojom.MediaSession.SuspendType', + 'x:media_session.mojom.MediaSessionAction', + 'x:media_session.mojom.MediaSessionImageType', + 'x:media_session.mojom.MediaSessionInfo.SessionState', + 'x:media_session.mojom.MicrophoneState', + 'x:ml.model_loader.mojom.ComputeResult', + 'x:ml.model_loader.mojom.CreateModelLoaderResult', + 'x:ml.model_loader.mojom.LoadModelResult', + 'x:mojo.test.AnExtensibleEnum', + 'x:mojo.test.EnumB', + 'x:mojo.test.ExtensibleEmptyEnum', + 'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault', + 'x:network.mojom.WebSandboxFlags', + 'x:payments.mojom.BillingResponseCode', + 'x:payments.mojom.CreateDigitalGoodsResponseCode', + 'x:payments.mojom.ItemType', + 'x:printing.mojom.PrinterType', + 'x:ui.mojom.KeyboardCode', +) +### DO NOT ADD ENTRIES TO THIS LIST. ### def _DuplicateName(values): @@ -98,12 +377,6 @@ def _MapKind(kind): } if kind.endswith('?'): base_kind = _MapKind(kind[0:-1]) - # NOTE: This doesn't rule out enum types. Those will be detected later, when - # cross-reference is established. - reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv', - 'rma', 'rca') - if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds: - raise Exception('A type (spec "%s") cannot be made nullable' % base_kind) return '?' + base_kind if kind.endswith('}'): lbracket = kind.rfind('{') @@ -113,8 +386,6 @@ def _MapKind(kind): lbracket = kind.rfind('[') typename = kind[0:lbracket] return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename) - if kind.endswith('&'): - return 'r:' + _MapKind(kind[0:-1]) if kind.startswith('asso<'): assert kind.endswith('>') return 'asso:' + _MapKind(kind[5:-1]) @@ -135,13 +406,35 @@ def _MapKind(kind): return 'x:' + kind -def _AttributeListToDict(attribute_list): +def _MapValueToEnum(module, value): + # True/False/None + if value is None: + return value + if not isinstance(value, str): + return value + # Otherwise try to find it. + try: + trial = _LookupValue(module, None, None, ('IDENTIFIER', value)) + if isinstance(trial, mojom.ConstantValue): + return trial.constant + if isinstance(trial, mojom.EnumValue): + return trial + except ValueError: + pass + # Return the string if it did not resolve to a constant or enum. + return value + + +def _AttributeListToDict(module, attribute_list): if attribute_list is None: return None assert isinstance(attribute_list, ast.AttributeList) - # TODO(vtl): Check for duplicate keys here. - return dict( - [(attribute.key, attribute.value) for attribute in attribute_list]) + attributes = dict() + for attribute in attribute_list: + if attribute.key in attributes: + raise Exception("Duplicate key (%s) in attribute list" % attribute.key) + attributes[attribute.key] = _MapValueToEnum(module, attribute.value) + return attributes builtin_values = frozenset([ @@ -257,7 +550,8 @@ def _Kind(kinds, spec, scope): return kind if spec.startswith('?'): - kind = _Kind(kinds, spec[1:], scope).MakeNullableKind() + kind = _Kind(kinds, spec[1:], scope) + kind = kind.MakeNullableKind() elif spec.startswith('a:'): kind = mojom.Array(_Kind(kinds, spec[2:], scope)) elif spec.startswith('asso:'): @@ -345,7 +639,7 @@ def _Struct(module, parsed_struct): struct.fields_data.append, }) - struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) + struct.attributes = _AttributeListToDict(module, parsed_struct.attribute_list) # Enforce that a [Native] attribute is set to make native-only struct # declarations more explicit. @@ -377,7 +671,7 @@ def _Union(module, parsed_union): union.fields_data = [] _ProcessElements(parsed_union.mojom_name, parsed_union.body, {ast.UnionField: union.fields_data.append}) - union.attributes = _AttributeListToDict(parsed_union.attribute_list) + union.attributes = _AttributeListToDict(module, parsed_union.attribute_list) return union @@ -398,7 +692,7 @@ def _StructField(module, parsed_field, struct): field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.default = _LookupValue(module, struct, field.kind, parsed_field.default_value) - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, parsed_field.attribute_list) return field @@ -414,11 +708,21 @@ def _UnionField(module, parsed_field, union): """ field = mojom.UnionField() field.mojom_name = parsed_field.mojom_name + # Disallow unions from being self-recursive. + parsed_typename = parsed_field.typename + if parsed_typename.endswith('?'): + parsed_typename = parsed_typename[:-1] + assert parsed_typename != union.mojom_name field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename), (module.mojom_namespace, union.mojom_name)) field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.default = None - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, parsed_field.attribute_list) + if field.is_default and not mojom.IsNullableKind(field.kind) and \ + not mojom.IsIntegralKind(field.kind): + raise Exception( + '[Default] field for union %s must be nullable or integral type.' % + union.mojom_name) return field @@ -439,7 +743,8 @@ def _Parameter(module, parsed_param, interface): parameter.ordinal = (parsed_param.ordinal.value if parsed_param.ordinal else None) parameter.default = None # TODO(tibell): We never have these. Remove field? - parameter.attributes = _AttributeListToDict(parsed_param.attribute_list) + parameter.attributes = _AttributeListToDict(module, + parsed_param.attribute_list) return parameter @@ -464,7 +769,7 @@ def _Method(module, parsed_method, interface): method.response_parameters = list( map(lambda parameter: _Parameter(module, parameter, interface), parsed_method.response_parameter_list)) - method.attributes = _AttributeListToDict(parsed_method.attribute_list) + method.attributes = _AttributeListToDict(module, parsed_method.attribute_list) # Enforce that only methods with response can have a [Sync] attribute. if method.sync and method.response_parameters is None: @@ -492,7 +797,8 @@ def _Interface(module, parsed_iface): interface.mojom_name = parsed_iface.mojom_name interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name module.kinds[interface.spec] = interface - interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) + interface.attributes = _AttributeListToDict(module, + parsed_iface.attribute_list) interface.enums = [] interface.constants = [] interface.methods_data = [] @@ -522,7 +828,7 @@ def _EnumField(module, enum, parsed_field): field = mojom.EnumField() field.mojom_name = parsed_field.mojom_name field.value = _LookupValue(module, enum, None, parsed_field.value) - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, parsed_field.attribute_list) value = mojom.EnumValue(module, enum, field) module.values[value.GetSpec()] = value return field @@ -544,7 +850,7 @@ def _ResolveNumericEnumValues(enum): prev_value += 1 # Integral value (e.g: BEGIN = -0x1). - elif _IsStrOrUnicode(field.value): + elif isinstance(field.value, str): prev_value = int(field.value, 0) # Reference to a previous enum value (e.g: INIT = BEGIN). @@ -588,7 +894,7 @@ def _Enum(module, parsed_enum, parent_kind): mojom_name = parent_kind.mojom_name + '.' + mojom_name enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name) enum.parent_kind = parent_kind - enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) + enum.attributes = _AttributeListToDict(module, parsed_enum.attribute_list) if not enum.native_only: enum.fields = list( @@ -600,11 +906,18 @@ def _Enum(module, parsed_enum, parent_kind): for field in enum.fields: if field.default: if not enum.extensible: - raise Exception('Non-extensible enums may not specify a default') - if enum.default_field is not None: raise Exception( - 'Only one enumerator value may be specified as the default') + f'Non-extensible enum {enum.spec} may not specify a default') + if enum.default_field is not None: + raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}') enum.default_field = field + # While running the backwards compatibility check, ignore errors because the + # old version of the enum might not specify [Default]. + if (enum.extensible and enum.default_field is None + and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT + and not is_running_backwards_compatibility_check_hack): + raise Exception( + f'Extensible enum {enum.spec} must specify a [Default] enumerator') module.kinds[enum.spec] = enum @@ -696,6 +1009,11 @@ def _CollectReferencedKinds(module, all_defined_kinds): for referenced_kind in extract_referenced_user_kinds(param.kind): sanitized_kind = sanitize_kind(referenced_kind) referenced_user_kinds[sanitized_kind.spec] = sanitized_kind + # Consts can reference imported enums. + for const in module.constants: + if not const.kind in mojom.PRIMITIVES: + sanitized_kind = sanitize_kind(const.kind) + referenced_user_kinds[sanitized_kind.spec] = sanitized_kind return referenced_user_kinds @@ -741,6 +1059,16 @@ def _AssertTypeIsStable(kind): assertDependencyIsStable(response_param.kind) +def _AssertStructIsValid(kind): + expected_ordinals = set(range(0, len(kind.fields))) + ordinals = set(map(lambda field: field.ordinal, kind.fields)) + if ordinals != expected_ordinals: + raise Exception( + 'Structs must use contiguous ordinals starting from 0. ' + + '{} is missing the following ordinals: {}.'.format( + kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals)))) + + def _Module(tree, path, imports): """ Args: @@ -810,8 +1138,17 @@ def _Module(tree, path, imports): union.fields = list( map(lambda field: _UnionField(module, field, union), union.fields_data)) _AssignDefaultOrdinals(union.fields) + for field in union.fields: + if field.is_default: + if union.default_field is not None: + raise Exception('Multiple [Default] fields in union %s.' % + union.mojom_name) + union.default_field = field del union.fields_data all_defined_kinds[union.spec] = union + if union.extensible and union.default_field is None: + raise Exception('Extensible union %s must specify a [Default] field' % + union.mojom_name) for interface in module.interfaces: interface.methods = list( @@ -829,8 +1166,8 @@ def _Module(tree, path, imports): all_defined_kinds.values()) imported_kind_specs = set(all_referenced_kinds.keys()).difference( set(all_defined_kinds.keys())) - module.imported_kinds = dict( - (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs) + module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec]) + for spec in sorted(imported_kind_specs)) generator.AddComputedData(module) for iface in module.interfaces: @@ -847,6 +1184,9 @@ def _Module(tree, path, imports): if kind.stable: _AssertTypeIsStable(kind) + for kind in module.structs: + _AssertStructIsValid(kind) + return module diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py index 19905c8a..42593745 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -69,5 +69,38 @@ class TranslateTest(unittest.TestCase): # pylint: disable=W0212 self.assertEquals( translate._MapKind("asso?"), "?asso:x:SomeInterface") - self.assertEquals( - translate._MapKind("asso?"), "?asso:r:x:SomeInterface") + self.assertEquals(translate._MapKind("rca?"), + "?rca:x:SomeInterface") + + def testSelfRecursiveUnions(self): + """Verifies _UnionField() raises when a union is self-recursive.""" + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union("SomeUnion", None, + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) + + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union( + "SomeUnion", None, + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) + + def testDuplicateAttributesException(self): + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union( + "FakeUnion", + ast.AttributeList([ + ast.Attribute("key1", "value"), + ast.Attribute("key1", "value") + ]), + ast.UnionBody([ + ast.UnionField("a", None, None, "int32"), + ast.UnionField("b", None, None, "string") + ])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py index 1f0db200..80e8c657 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Node classes for the AST for a Mojo IDL file.""" @@ -8,17 +8,14 @@ # and lineno). You may also define __repr__() to help with analyzing test # failures, especially for more complex types. +import os.path -import sys +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member) +# pylint: disable=no-member -def _IsStrOrUnicode(x): - if sys.version_info[0] < 3: - return isinstance(x, (unicode, str)) - return isinstance(x, str) - -class NodeBase(object): +class NodeBase: """Base class for nodes in the AST.""" def __init__(self, filename=None, lineno=None): @@ -43,7 +40,7 @@ class NodeListBase(NodeBase): classes, in a tuple) of the members of the list.)""" def __init__(self, item_or_items=None, **kwargs): - super(NodeListBase, self).__init__(**kwargs) + super().__init__(**kwargs) self.items = [] if item_or_items is None: pass @@ -62,7 +59,7 @@ class NodeListBase(NodeBase): return self.items.__iter__() def __eq__(self, other): - return super(NodeListBase, self).__eq__(other) and \ + return super().__eq__(other) and \ self.items == other.items # Implement this so that on failure, we get slightly more sensible output. @@ -96,7 +93,7 @@ class Definition(NodeBase): include parameter definitions.) This class is meant to be subclassed.""" def __init__(self, mojom_name, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) NodeBase.__init__(self, **kwargs) self.mojom_name = mojom_name @@ -108,13 +105,13 @@ class Attribute(NodeBase): """Represents an attribute.""" def __init__(self, key, value, **kwargs): - assert _IsStrOrUnicode(key) - super(Attribute, self).__init__(**kwargs) + assert isinstance(key, str) + super().__init__(**kwargs) self.key = key self.value = value def __eq__(self, other): - return super(Attribute, self).__eq__(other) and \ + return super().__eq__(other) and \ self.key == other.key and \ self.value == other.value @@ -131,17 +128,17 @@ class Const(Definition): def __init__(self, mojom_name, attribute_list, typename, value, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) # The typename is currently passed through as a string. - assert _IsStrOrUnicode(typename) + assert isinstance(typename, str) # The value is either a literal (currently passed through as a string) or a # "wrapped identifier". - assert _IsStrOrUnicode or isinstance(value, tuple) - super(Const, self).__init__(mojom_name, **kwargs) + assert isinstance(value, (tuple, str)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.typename = typename self.value = value def __eq__(self, other): - return super(Const, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.typename == other.typename and \ self.value == other.value @@ -153,12 +150,12 @@ class Enum(Definition): def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert enum_value_list is None or isinstance(enum_value_list, EnumValueList) - super(Enum, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.enum_value_list = enum_value_list def __eq__(self, other): - return super(Enum, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.enum_value_list == other.enum_value_list @@ -170,13 +167,13 @@ class EnumValue(Definition): # The optional value is either an int (which is current a string) or a # "wrapped identifier". assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple) - super(EnumValue, self).__init__(mojom_name, **kwargs) + assert value is None or isinstance(value, (tuple, str)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.value = value def __eq__(self, other): - return super(EnumValue, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.value == other.value @@ -193,13 +190,14 @@ class Import(NodeBase): def __init__(self, attribute_list, import_filename, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert _IsStrOrUnicode(import_filename) - super(Import, self).__init__(**kwargs) + assert isinstance(import_filename, str) + super().__init__(**kwargs) self.attribute_list = attribute_list - self.import_filename = import_filename + # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3. + self.import_filename = os.path.normpath(import_filename).replace('\\', '/') def __eq__(self, other): - return super(Import, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.import_filename == other.import_filename @@ -216,12 +214,12 @@ class Interface(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, InterfaceBody) - super(Interface, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Interface, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body @@ -236,14 +234,14 @@ class Method(Definition): assert isinstance(parameter_list, ParameterList) assert response_parameter_list is None or \ isinstance(response_parameter_list, ParameterList) - super(Method, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.parameter_list = parameter_list self.response_parameter_list = response_parameter_list def __eq__(self, other): - return super(Method, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.parameter_list == other.parameter_list and \ @@ -264,12 +262,12 @@ class Module(NodeBase): # |mojom_namespace| is either none or a "wrapped identifier". assert mojom_namespace is None or isinstance(mojom_namespace, tuple) assert attribute_list is None or isinstance(attribute_list, AttributeList) - super(Module, self).__init__(**kwargs) + super().__init__(**kwargs) self.mojom_namespace = mojom_namespace self.attribute_list = attribute_list def __eq__(self, other): - return super(Module, self).__eq__(other) and \ + return super().__eq__(other) and \ self.mojom_namespace == other.mojom_namespace and \ self.attribute_list == other.attribute_list @@ -281,13 +279,13 @@ class Mojom(NodeBase): assert module is None or isinstance(module, Module) assert isinstance(import_list, ImportList) assert isinstance(definition_list, list) - super(Mojom, self).__init__(**kwargs) + super().__init__(**kwargs) self.module = module self.import_list = import_list self.definition_list = definition_list def __eq__(self, other): - return super(Mojom, self).__eq__(other) and \ + return super().__eq__(other) and \ self.module == other.module and \ self.import_list == other.import_list and \ self.definition_list == other.definition_list @@ -302,11 +300,11 @@ class Ordinal(NodeBase): def __init__(self, value, **kwargs): assert isinstance(value, int) - super(Ordinal, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value def __eq__(self, other): - return super(Ordinal, self).__eq__(other) and \ + return super().__eq__(other) and \ self.value == other.value @@ -314,18 +312,18 @@ class Parameter(NodeBase): """Represents a method request or response parameter.""" def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) - super(Parameter, self).__init__(**kwargs) + assert isinstance(typename, str) + super().__init__(**kwargs) self.mojom_name = mojom_name self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename def __eq__(self, other): - return super(Parameter, self).__eq__(other) and \ + return super().__eq__(other) and \ self.mojom_name == other.mojom_name and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ @@ -344,42 +342,51 @@ class Struct(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, StructBody) or body is None - super(Struct, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Struct, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body + def __repr__(self): + return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % ( + self.mojom_name, self.attribute_list, self.body) + class StructField(Definition): """Represents a struct field definition.""" def __init__(self, mojom_name, attribute_list, ordinal, typename, default_value, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) + assert isinstance(typename, str) # The optional default value is currently either a value as a string or a # "wrapped identifier". - assert default_value is None or _IsStrOrUnicode(default_value) or \ - isinstance(default_value, tuple) - super(StructField, self).__init__(mojom_name, **kwargs) + assert default_value is None or isinstance(default_value, (str, tuple)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename self.default_value = default_value def __eq__(self, other): - return super(StructField, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.typename == other.typename and \ self.default_value == other.default_value + def __repr__(self): + return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, " + "typename = %s, default_value = %s") % ( + self.mojom_name, self.attribute_list, self.ordinal, + self.typename, self.default_value) + # This needs to be declared after |StructField|. class StructBody(NodeListBase): @@ -394,29 +401,29 @@ class Union(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, UnionBody) - super(Union, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Union, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body class UnionField(Definition): def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) - super(UnionField, self).__init__(mojom_name, **kwargs) + assert isinstance(typename, str) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename def __eq__(self, other): - return super(UnionField, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.typename == other.typename diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py index 62798631..c3637671 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,11 +14,11 @@ class _TestNode(ast.NodeBase): """Node type for tests.""" def __init__(self, value, **kwargs): - super(_TestNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value def __eq__(self, other): - return super(_TestNode, self).__eq__(other) and self.value == other.value + return super().__eq__(other) and self.value == other.value class _TestNodeList(ast.NodeListBase): diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py index 3cb73c5d..b7b06bfb 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py @@ -1,4 +1,4 @@ -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Helpers for processing conditionally enabled features in a mojom.""" @@ -17,8 +17,10 @@ class EnableIfError(Error): def _IsEnabled(definition, enabled_features): """Returns true if a definition is enabled. - A definition is enabled if it has no EnableIf attribute, or if the value of - the EnableIf attribute is in enabled_features. + A definition is enabled if it has no EnableIf/EnableIfNot attribute. + It is retained if it has an EnableIf attribute and the attribute is in + enabled_features. It is retained if it has an EnableIfNot attribute and the + attribute is not in enabled features. """ if not hasattr(definition, "attribute_list"): return True @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features): already_defined = False for a in definition.attribute_list: - if a.key == 'EnableIf': + if a.key == 'EnableIf' or a.key == 'EnableIfNot': if already_defined: raise EnableIfError( definition.filename, - "EnableIf attribute may only be defined once per field.", + "EnableIf/EnableIfNot attribute may only be set once per field.", definition.lineno) already_defined = True for attribute in definition.attribute_list: if attribute.key == 'EnableIf' and attribute.value not in enabled_features: return False + if attribute.key == 'EnableIfNot' and attribute.value in enabled_features: + return False return True diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py index aa609be7..5fc58202 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -55,6 +55,48 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(const_source, expected_source) + def testFilterIfNotConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue] + const int kMyConst3 = 3; + [EnableIfNot=blue] + const int kMyConst4 = 4; + [EnableIfNot=purple] + const int kMyConst5 = 5; + """ + expected_source = """ + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue] + const int kMyConst3 = 3; + [EnableIfNot=purple] + const int kMyConst5 = 5; + """ + self.parseAndAssertEqual(const_source, expected_source) + + def testFilterIfNotMultipleConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIfNot=orange] + const int kMyConst3 = 3; + """ + expected_source = """ + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIfNot=orange] + const int kMyConst3 = 3; + """ + self.parseAndAssertEqual(const_source, expected_source) + def testFilterEnum(self): """Test that EnumValues are correctly filtered from an Enum.""" enum_source = """ @@ -91,6 +133,24 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(import_source, expected_source) + def testFilterIfNotImport(self): + """Test that imports are correctly filtered from a Mojom.""" + import_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + [EnableIfNot=green] + import "baz.mojom"; + """ + expected_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + """ + self.parseAndAssertEqual(import_source, expected_source) + def testFilterInterface(self): """Test that definitions are correctly filtered from an Interface.""" interface_source = """ @@ -175,6 +235,50 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(struct_source, expected_source) + def testFilterIfNotStruct(self): + """Test that definitions are correctly filtered from a Struct.""" + struct_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + [EnableIfNot=red] + VALUE2, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + [EnableIfNot=red] + double f; + }; + """ + expected_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + }; + """ + self.parseAndAssertEqual(struct_source, expected_source) + def testFilterUnion(self): """Test that UnionFields are correctly filtered from a Union.""" union_source = """ @@ -228,6 +332,30 @@ class ConditionalFeaturesTest(unittest.TestCase): conditional_features.RemoveDisabledDefinitions, definition, ENABLED_FEATURES) + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIf=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) + + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIfNot=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) + if __name__ == '__main__': unittest.main() diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py index 3e084bbf..73ca15df 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -22,7 +22,7 @@ class LexError(Error): # We have methods which look like they could be functions: # pylint: disable=R0201 -class Lexer(object): +class Lexer: def __init__(self, filename): self.filename = filename @@ -81,7 +81,6 @@ class Lexer(object): # Operators 'MINUS', 'PLUS', - 'AMP', 'QSTN', # Assignment @@ -168,7 +167,6 @@ class Lexer(object): # Operators t_MINUS = r'-' t_PLUS = r'\+' - t_AMP = r'&' t_QSTN = r'\?' # = diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py index eadc6587..ce376da6 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -146,7 +146,6 @@ class LexerTest(unittest.TestCase): self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+")) self.assertEquals( self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-")) - self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&")) self.assertEquals( self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?")) self.assertEquals( diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py index b3b803d6..683ae757 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a syntax tree from a Mojo IDL file.""" @@ -33,7 +33,7 @@ class ParseError(Error): # We have methods which look like they could be functions: # pylint: disable=R0201 -class Parser(object): +class Parser: def __init__(self, lexer, source, filename): self.tokens = lexer.tokens self.source = source @@ -140,11 +140,18 @@ class Parser(object): p[0].Append(p[3]) def p_attribute_1(self, p): + """attribute : NAME EQUALS identifier_wrapped""" + p[0] = ast.Attribute(p[1], + p[3][1], + filename=self.filename, + lineno=p.lineno(1)) + + def p_attribute_2(self, p): """attribute : NAME EQUALS evaled_literal | NAME EQUALS NAME""" p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) - def p_attribute_2(self, p): + def p_attribute_3(self, p): """attribute : NAME""" p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1)) @@ -271,8 +278,7 @@ class Parser(object): """nonnullable_typename : basictypename | array | fixed_array - | associative_array - | interfacerequest""" + | associative_array""" p[0] = p[1] def p_basictypename(self, p): @@ -342,14 +348,6 @@ class Parser(object): """associative_array : MAP LANGLE identifier COMMA typename RANGLE""" p[0] = p[5] + "{" + p[3] + "}" - def p_interfacerequest(self, p): - """interfacerequest : identifier AMP - | ASSOCIATED identifier AMP""" - if len(p) == 3: - p[0] = p[1] + "&" - else: - p[0] = "asso<" + p[2] + "&>" - def p_ordinal_1(self, p): """ordinal : """ p[0] = None diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py index 6d6b7153..0513343e 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -1086,7 +1086,7 @@ class ParserTest(unittest.TestCase): handle? k; handle? l; handle? m; - some_interface&? n; + pending_receiver? n; handle? o; }; """ @@ -1110,7 +1110,7 @@ class ParserTest(unittest.TestCase): ast.StructField('l', None, None, 'handle?', None), ast.StructField('m', None, None, 'handle?', None), - ast.StructField('n', None, None, 'some_interface&?', None), + ast.StructField('n', None, None, 'rcv?', None), ast.StructField('o', None, None, 'handle?', None) ])) ]) @@ -1138,16 +1138,6 @@ class ParserTest(unittest.TestCase): r" *handle\? a;$"): parser.Parse(source2, "my_file.mojom") - source3 = """\ - struct MyStruct { - some_interface?& a; - }; - """ - with self.assertRaisesRegexp( - parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n" - r" *some_interface\?& a;$"): - parser.Parse(source3, "my_file.mojom") - def testSimpleUnion(self): """Tests a simple .mojom source that just defines a union.""" source = """\ @@ -1317,9 +1307,9 @@ class ParserTest(unittest.TestCase): source1 = """\ struct MyStruct { associated MyInterface a; - associated MyInterface& b; + pending_associated_receiver b; associated MyInterface? c; - associated MyInterface&? d; + pending_associated_receiver? d; }; """ expected1 = ast.Mojom(None, ast.ImportList(), [ @@ -1327,16 +1317,16 @@ class ParserTest(unittest.TestCase): 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'asso', None), - ast.StructField('b', None, None, 'asso', None), + ast.StructField('b', None, None, 'rca', None), ast.StructField('c', None, None, 'asso?', None), - ast.StructField('d', None, None, 'asso?', None) + ast.StructField('d', None, None, 'rca?', None) ])) ]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) source2 = """\ interface MyInterface { - MyMethod(associated A a) =>(associated B& b); + MyMethod(associated A a) =>(pending_associated_receiver b); };""" expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( @@ -1344,10 +1334,10 @@ class ParserTest(unittest.TestCase): ast.InterfaceBody( ast.Method( 'MyMethod', None, None, - ast.ParameterList( - ast.Parameter('a', None, None, 'asso')), - ast.ParameterList( - ast.Parameter('b', None, None, 'asso'))))) + ast.ParameterList(ast.Parameter('a', None, None, + 'asso')), + ast.ParameterList(ast.Parameter('b', None, None, + 'rca'))))) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py index eb90c825..9693090e 100755 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Parses mojom IDL files. @@ -11,6 +11,7 @@ generate usable language bindings. """ import argparse +import builtins import codecs import errno import json @@ -19,6 +20,7 @@ import multiprocessing import os import os.path import sys +import traceback from collections import defaultdict from mojom.generate import module @@ -28,16 +30,12 @@ from mojom.parse import conditional_features # Disable this for easier debugging. -# In Python 2, subprocesses just hang when exceptions are thrown :(. -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2 +_ENABLE_MULTIPROCESSING = True -if sys.version_info < (3, 4): - _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux') -else: - # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725 - if __name__ == '__main__' and sys.platform == 'darwin': - multiprocessing.set_start_method('fork') - _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork' +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725 +if __name__ == '__main__' and sys.platform == 'darwin': + multiprocessing.set_start_method('fork') +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork' def _ResolveRelativeImportPath(path, roots): @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots): raise ValueError('"%s" does not exist in any of %s' % (path, roots)) -def _RebaseAbsolutePath(path, roots): +def RebaseAbsolutePath(path, roots): """Rewrites an absolute file path as relative to an absolute directory path in roots. @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts, # Already done. return - for dep_abspath, dep_path in dependencies[mojom_abspath]: + for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]): if dep_abspath not in loaded_modules: _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies, loaded_modules, module_metadata) @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename): def collect(metadata_filename): processed_deps.add(metadata_filename) + + # Paths in the metadata file are relative to the metadata file's dir. + metadata_dir = os.path.abspath(os.path.dirname(metadata_filename)) + + def to_abs(s): + return os.path.normpath(os.path.join(metadata_dir, s)) + with open(metadata_filename) as f: metadata = json.load(f) allowed_imports.update( - map(os.path.normcase, map(os.path.normpath, metadata['sources']))) + [os.path.normcase(to_abs(s)) for s in metadata['sources']]) for dep_metadata in metadata['deps']: + dep_metadata = to_abs(dep_metadata) if dep_metadata not in processed_deps: collect(dep_metadata) @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename): # multiprocessing helper. -def _ParseAstHelper(args): - mojom_abspath, enabled_features = args +def _ParseAstHelper(mojom_abspath, enabled_features): with codecs.open(mojom_abspath, encoding='utf-8') as f: ast = parser.Parse(f.read(), mojom_abspath) conditional_features.RemoveDisabledDefinitions(ast, enabled_features) @@ -181,8 +186,7 @@ def _ParseAstHelper(args): # multiprocessing helper. -def _SerializeHelper(args): - mojom_abspath, mojom_path = args +def _SerializeHelper(mojom_abspath, mojom_path): module_path = os.path.join(_SerializeHelper.output_root_path, _GetModuleFilename(mojom_path)) module_dir = os.path.dirname(module_path) @@ -199,12 +203,33 @@ def _SerializeHelper(args): _SerializeHelper.loaded_modules[mojom_abspath].Dump(f) -def _Shard(target_func, args, processes=None): - args = list(args) +class _ExceptionWrapper: + def __init__(self): + # Do not capture exception object to ensure pickling works. + self.formatted_trace = traceback.format_exc() + + +class _FuncWrapper: + """Marshals exceptions and spreads args.""" + + def __init__(self, func): + self._func = func + + def __call__(self, args): + # multiprocessing does not gracefully handle excptions. + # https://crbug.com/1219044 + try: + return self._func(*args) + except: # pylint: disable=bare-except + return _ExceptionWrapper() + + +def _Shard(target_func, arg_list, processes=None): + arg_list = list(arg_list) if processes is None: processes = multiprocessing.cpu_count() # Seems optimal to have each process perform at least 2 tasks. - processes = min(processes, len(args) // 2) + processes = min(processes, len(arg_list) // 2) if sys.platform == 'win32': # TODO(crbug.com/1190269) - we can't use more than 56 @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None): # Don't spin up processes unless there is enough work to merit doing so. if not _ENABLE_MULTIPROCESSING or processes < 2: - for result in map(target_func, args): - yield result + for arg_tuple in arg_list: + yield target_func(*arg_tuple) return pool = multiprocessing.Pool(processes=processes) try: - for result in pool.imap_unordered(target_func, args): + wrapped_func = _FuncWrapper(target_func) + for result in pool.imap_unordered(wrapped_func, arg_list): + if isinstance(result, _ExceptionWrapper): + sys.stderr.write(result.formatted_trace) + sys.exit(1) yield result finally: pool.close() @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None): def _ParseMojoms(mojom_files, input_root_paths, output_root_path, + module_root_paths, enabled_features, module_metadata, allowed_imports=None): @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files, are based on the mojom's relative path, rebased onto this path. Additionally, the script expects this root to contain already-generated modules for any transitive dependencies not listed in mojom_files. + module_root_paths: A list of absolute filesystem paths which contain + already-generated modules for any non-transitive dependencies. enabled_features: A list of enabled feature names, controlling which AST - nodes are filtered by [EnableIf] attributes. + nodes are filtered by [EnableIf] or [EnableIfNot] attributes. module_metadata: A list of 2-tuples representing metadata key-value pairs to attach to each compiled module output. @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files, loaded_modules = {} input_dependencies = defaultdict(set) mojom_files_to_parse = dict((os.path.normcase(abs_path), - _RebaseAbsolutePath(abs_path, input_root_paths)) + RebaseAbsolutePath(abs_path, input_root_paths)) for abs_path in mojom_files) abs_paths = dict( (path, abs_path) for abs_path, path in mojom_files_to_parse.items()) @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files, loaded_mojom_asts[mojom_abspath] = ast logging.info('Processing dependencies') - for mojom_abspath, ast in loaded_mojom_asts.items(): + for mojom_abspath, ast in sorted(loaded_mojom_asts.items()): invalid_imports = [] for imp in ast.import_list: import_abspath = _ResolveRelativeImportPath(imp.import_filename, @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files, # be parsed and have a module file sitting in a corresponding output # location. module_path = _GetModuleFilename(imp.import_filename) - module_abspath = _ResolveRelativeImportPath(module_path, - [output_root_path]) + module_abspath = _ResolveRelativeImportPath( + module_path, module_root_paths + [output_root_path]) with open(module_abspath, 'rb') as module_file: loaded_modules[import_abspath] = module.Module.Load(module_file) @@ -370,6 +402,15 @@ already present in the provided output root.""") 'based on the relative input path, rebased onto this root. Note that ' 'ROOT is also searched for existing modules of any transitive imports ' 'which were not included in the set of inputs.') + arg_parser.add_argument( + '--module-root', + default=[], + action='append', + metavar='ROOT', + dest='module_root_paths', + help='Adds ROOT to the set of root paths to search for existing modules ' + 'of non-transitive imports. Provided root paths are always searched in ' + 'order from longest absolute path to shortest.') arg_parser.add_argument( '--mojoms', nargs='+', @@ -396,9 +437,9 @@ already present in the provided output root.""") help='Enables a named feature when parsing the given mojoms. Features ' 'are identified by arbitrary string values. Specifying this flag with a ' 'given FEATURE name will cause the parser to process any syntax elements ' - 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not ' - 'provided for a given FEATURE, such tagged elements are discarded by the ' - 'parser and will not be present in the compiled output.') + 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this ' + 'flag is not provided for a given FEATURE, such tagged elements are ' + 'discarded by the parser and will not be present in the compiled output.') arg_parser.add_argument( '--check-imports', dest='build_metadata_filename', @@ -436,6 +477,7 @@ already present in the provided output root.""") mojom_files = list(map(os.path.abspath, args.mojom_files)) input_roots = list(map(os.path.abspath, args.input_root_paths)) output_root = os.path.abspath(args.output_root_path) + module_roots = list(map(os.path.abspath, args.module_root_paths)) if args.build_metadata_filename: allowed_imports = _CollectAllowedImportsFromBuildMetadata( @@ -445,13 +487,16 @@ already present in the provided output root.""") module_metadata = list( map(lambda kvp: tuple(kvp.split('=')), args.module_metadata)) - _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features, - module_metadata, allowed_imports) + _ParseMojoms(mojom_files, input_roots, output_root, module_roots, + args.enabled_features, module_metadata, allowed_imports) logging.info('Finished') - # Exit without running GC, which can save multiple seconds due the large - # number of object created. - os._exit(0) if __name__ == '__main__': Run(sys.argv[1:]) + # Exit without running GC, which can save multiple seconds due to the large + # number of object created. But flush is necessary as os._exit doesn't do + # that. + sys.stdout.flush() + sys.stderr.flush() + os._exit(0) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py index e213fbfa..45803ebe 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase): resolution, and module serialization and deserialization.""" def __init__(self, method_name): - super(MojomParserTestCase, self).__init__(method_name) + super().__init__(method_name) self._temp_dir = None def setUp(self): diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py index a93f34ba..353a2b6e 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py @@ -1,7 +1,9 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import json + from mojom_parser_test_case import MojomParserTestCase @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase): c = 'c.mojom' c_metadata = 'out/c.build_metadata' self.WriteFile(a_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) + json.dumps({ + "sources": [self.GetPath(a)], + "deps": [] + })) self.WriteFile( b_metadata, - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b), - self.GetPath(a_metadata))) + json.dumps({ + "sources": [self.GetPath(b)], + "deps": [self.GetPath(a_metadata)] + })) self.WriteFile( c_metadata, - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c), - self.GetPath(b_metadata))) + json.dumps({ + "sources": [self.GetPath(c)], + "deps": [self.GetPath(b_metadata)] + })) self.WriteFile(a, """\ module a; struct Bar {};""") @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase): b = 'b.mojom' b_metadata = 'out/b.build_metadata' self.WriteFile(a_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) + json.dumps({ + "sources": [self.GetPath(a)], + "deps": [] + })) self.WriteFile(b_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b)) + json.dumps({ + "sources": [self.GetPath(b)], + "deps": [] + })) self.WriteFile(a, """\ module a; struct Bar {};""") diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py index d45ec586..d10d69c6 100644 --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py new file mode 100644 index 00000000..6b2525e5 --- /dev/null +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py @@ -0,0 +1,44 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + + +class UnionTest(MojomParserTestCase): + """Tests union parsing behavior.""" + + def testExtensibleMustHaveDefault(self): + """Verifies that extensible unions must have a default field.""" + mojom = 'foo.mojom' + self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };') + with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'): + self.ParseMojoms([mojom]) + + def testExtensibleSingleDefault(self): + """Verifies that extensible unions must not have multiple default fields.""" + mojom = 'foo.mojom' + self.WriteFile( + mojom, """\ + module foo; + [Extensible] union U { + [Default] bool x; + [Default] bool y; + }; + """) + with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'): + self.ParseMojoms([mojom]) + + def testExtensibleDefaultTypeValid(self): + """Verifies that an extensible union's default field must be nullable or + integral type.""" + mojom = 'foo.mojom' + self.WriteFile( + mojom, """\ + module foo; + [Extensible] union U { + [Default] handle p; + }; + """) + with self.assertRaisesRegexp(Exception, 'must be nullable or integral'): + self.ParseMojoms([mojom]) diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py index 65db4dc9..7b71ce65 100644 --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -23,7 +23,7 @@ class VersionCompatibilityTest(MojomParserTestCase): checker = module.BackwardCompatibilityChecker() compatibility_map = {} - for name in old.keys(): + for name in old: compatibility_map[name] = checker.IsBackwardCompatible( new[name], old[name]) return compatibility_map @@ -60,40 +60,48 @@ class VersionCompatibilityTest(MojomParserTestCase): """Adding a value to an existing version is not allowed, even if the old enum was marked [Extensible]. Note that it is irrelevant whether or not the new enum is marked [Extensible].""" - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', - 'enum E { kFoo, kBar, kBaz };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kFoo, kBar };', - '[Extensible] enum E { kFoo, kBar, kBaz };') + '[Extensible] enum E { [Default] kFoo, kBar };', + 'enum E { kFoo, kBar, kBaz };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { [Default] kFoo, kBar };', + '[Extensible] enum E { [Default] kFoo, kBar, kBaz };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kFoo, [MinVersion=1] kBar };', + '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };', 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };') def testEnumValueRemoval(self): """Removal of an enum value is never valid even for [Extensible] enums.""" self.assertNotBackwardCompatible('enum E { kFoo, kBar };', 'enum E { kFoo };') - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', - '[Extensible] enum E { kFoo };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB };', - '[Extensible] enum E { kA, };') + '[Extensible] enum E { [Default] kFoo, kBar };', + '[Extensible] enum E { [Default] kFoo };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };', + '[Extensible] enum E { [Default] kA, };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };', - '[Extensible] enum E { kA, [MinVersion=1] kB };') + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB, + [MinVersion=1] kZ };""", + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };') def testNewExtensibleEnumValueWithMinVersion(self): """Adding a new and properly [MinVersion]'d value to an [Extensible] enum is a backward-compatible change. Note that it is irrelevant whether or not the new enum is marked [Extensible].""" - self.assertBackwardCompatible('[Extensible] enum E { kA, kB };', + self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };', 'enum E { kA, kB, [MinVersion=1] kC };') self.assertBackwardCompatible( - '[Extensible] enum E { kA, kB };', - '[Extensible] enum E { kA, kB, [MinVersion=1] kC };') + '[Extensible] enum E { [Default] kA, kB };', + '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };') self.assertBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB };', - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };') + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB, + [MinVersion=2] kC };""") def testRenameEnumValue(self): """Renaming an enum value does not affect backward-compatibility. Only @@ -161,14 +169,17 @@ class VersionCompatibilityTest(MojomParserTestCase): 'struct S {}; struct T { S s; };', 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };') self.assertBackwardCompatible( - '[Extensible] enum E { kA }; struct S { E e; };', - '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };') + '[Extensible] enum E { [Default] kA }; struct S { E e; };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB }; + struct S { E e; };""") self.assertNotBackwardCompatible( 'struct S {}; struct T { S s; };', 'struct S { int32 x; }; struct T { S s; };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA }; struct S { E e; };', - '[Extensible] enum E { kA, kB }; struct S { E e; };') + '[Extensible] enum E { [Default] kA }; struct S { E e; };', + '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };') def testNewStructFieldWithInvalidMinVersion(self): """Adding a new field using an existing MinVersion breaks backward- @@ -305,14 +316,17 @@ class VersionCompatibilityTest(MojomParserTestCase): 'struct S {}; union U { S s; };', 'struct S { [MinVersion=1] int32 x; }; union U { S s; };') self.assertBackwardCompatible( - '[Extensible] enum E { kA }; union U { E e; };', - '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };') + '[Extensible] enum E { [Default] kA }; union U { E e; };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB }; + union U { E e; };""") self.assertNotBackwardCompatible( 'struct S {}; union U { S s; };', 'struct S { int32 x; }; union U { S s; };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA }; union U { E e; };', - '[Extensible] enum E { kA, kB }; union U { E e; };') + '[Extensible] enum E { [Default] kA }; union U { E e; };', + '[Extensible] enum E { [Default] kA, kB }; union U { E e; };') def testNewUnionFieldWithInvalidMinVersion(self): """Adding a new field using an existing MinVersion breaks backward- diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py index b2010958..98bce18c 100755 --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,11 +8,13 @@ import sys _TOOLS_DIR = os.path.dirname(__file__) _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom') +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings') _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir, os.path.pardir) # Ensure that the mojom library is discoverable. sys.path.append(_MOJOM_DIR) +sys.path.append(_BINDINGS_DIR) # Help Python find typ in //third_party/catapult/third_party/typ/ sys.path.append( @@ -21,7 +23,7 @@ import typ def Main(): - return typ.main(top_level_dir=_MOJOM_DIR) + return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR]) if __name__ == '__main__': diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README index d5c24fc3..9a2979d3 100644 --- a/utils/ipc/tools/README +++ b/utils/ipc/tools/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from 9c138d992bfc of Chromium. Do not +Files in this directory are imported from e2b2277a00e37 of Chromium. Do not modify them manually. diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py index 478fb8c1..40900d10 100644 --- a/utils/ipc/tools/diagnosis/crbug_1001171.py +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Chromium Authors. All rights reserved. +# Copyright 2019 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file.