utils: ipc: Update mojo

Update mojo from commit

9be4263648d7d1a04bb78be75df53f56449a5e3a "Updating trunk VERSION from 6225.0 to 6226.0"

from the Chromium repository.

The update-mojo.sh script was used for this update.

Bug: https://bugs.libcamera.org/show_bug.cgi?id=206
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2024-01-04 17:15:48 +02:00 committed by Kieran Bingham
parent 8ac367fe0c
commit d17de86904
64 changed files with 3830 additions and 1416 deletions

View file

@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0 # 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 9be4263648d7 of Chromium. Do not
modify them manually. modify them manually.

View file

@ -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 // Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are // modification, are permitted provided that the following conditions are

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -10,7 +10,11 @@ group("mojo_python_unittests") {
"run_all_python_unittests.py", "run_all_python_unittests.py",
"//testing/scripts/run_isolated_script_test.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 = [ data_deps = [
"//testing:test_scripts_shared", "//testing:test_scripts_shared",
"//third_party/catapult/third_party/typ/", "//third_party/catapult/third_party/typ/",

View file

@ -1,24 +1,27 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import("//build/config/python.gni")
import("//mojo/public/tools/bindings/mojom.gni") import("//mojo/public/tools/bindings/mojom.gni")
import("//third_party/jinja2/jinja2.gni") import("//third_party/jinja2/jinja2.gni")
# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. action("precompile_templates") {
python2_action("precompile_templates") {
sources = mojom_generator_sources sources = mojom_generator_sources
sources += [ sources += [
"$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl",
@ -26,7 +29,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-message-ids.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.cc.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-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-test-utils.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
@ -65,9 +67,6 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/java_templates/struct.java.tmpl", "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
"$mojom_generator_root/generators/java_templates/union.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/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/fuzzing.tmpl",
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl", "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl", "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
@ -93,8 +92,11 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl", "$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_to_proto_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_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/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",
] ]
script = mojom_generator_script script = mojom_generator_script
@ -102,8 +104,8 @@ python2_action("precompile_templates") {
outputs = [ outputs = [
"$target_gen_dir/cpp_templates.zip", "$target_gen_dir/cpp_templates.zip",
"$target_gen_dir/java_templates.zip", "$target_gen_dir/java_templates.zip",
"$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/js_templates.zip", "$target_gen_dir/js_templates.zip",
"$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/ts_templates.zip", "$target_gen_dir/ts_templates.zip",
] ]
args = [ args = [
@ -113,3 +115,17 @@ python2_action("precompile_templates") {
"precompile", "precompile",
] ]
} }
group("tests") {
data = [
mojom_generator_script,
"checks/mojom_attributes_check_unittest.py",
"checks/mojom_interface_feature_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
}

View file

@ -96,7 +96,7 @@ for message parameters.
| `string` | UTF-8 encoded string. | `string` | UTF-8 encoded string.
| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`. | `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant. | `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. | `map<S, T>` | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle. | `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
| `handle<message_pipe>` | Generic message pipe handle. | `handle<message_pipe>` | Generic message pipe handle.
| `handle<shared_buffer>` | Shared buffer handle. | `handle<shared_buffer>` | Shared buffer handle.
@ -188,8 +188,8 @@ struct StringPair {
}; };
enum AnEnum { enum AnEnum {
YES, kYes,
NO kNo
}; };
interface SampleInterface { interface SampleInterface {
@ -209,7 +209,7 @@ struct AllTheThings {
uint64 unsigned_64bit_value; uint64 unsigned_64bit_value;
float float_value_32bit; float float_value_32bit;
double float_value_64bit; double float_value_64bit;
AnEnum enum_value = AnEnum.YES; AnEnum enum_value = AnEnum.kYes;
// Strings may be nullable. // Strings may be nullable.
string? maybe_a_string_maybe_not; 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; module business.mojom;
enum Department { enum Department {
SALES = 0, kSales = 0,
DEV, kDev,
}; };
struct Employee { struct Employee {
enum Type { enum Type {
FULL_TIME, kFullTime,
PART_TIME, kPartTime,
}; };
Type type; 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 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 enum definition. By default, values are based at zero and increment by
1 sequentially. 1 sequentially.
@ -336,8 +339,8 @@ struct Employee {
const uint64 kInvalidId = 0; const uint64 kInvalidId = 0;
enum Type { enum Type {
FULL_TIME, kFullTime,
PART_TIME, kPartTime,
}; };
uint64 id = kInvalidId; uint64 id = kInvalidId;
@ -348,6 +351,37 @@ struct Employee {
The effect of nested definitions on generated bindings varies depending on the The effect of nested definitions on generated bindings varies depending on the
target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages). target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).
### Features
Features can be declared with a `name` and `default_state` and can be attached
in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the
feature is disabled at runtime, the method will crash and the interface will
refuse to be bound / instantiated. Features cannot be serialized to be sent over
IPC at this time.
```
module experimental.mojom;
feature kUseElevators {
const string name = "UseElevators";
const bool default_state = false;
}
[RuntimeFeature=kUseElevators]
interface Elevator {
// This interface cannot be bound or called if the feature is disabled.
}
interface Building {
// This method cannot be called if the feature is disabled.
[RuntimeFeature=kUseElevators]
CallElevator(int floor);
// This method can be called.
RingDoorbell(int volume);
}
```
### Interfaces ### Interfaces
An **interface** is a logical bundle of parameterized request messages. Each An **interface** is a logical bundle of parameterized request messages. Each
@ -396,20 +430,33 @@ interesting attributes supported today.
extreme caution, because it can lead to deadlocks otherwise. extreme caution, because it can lead to deadlocks otherwise.
* **`[Default]`**: * **`[Default]`**:
The `Default` attribute may be used to specify an enumerator value that The `Default` attribute may be used to specify an enumerator value or union
will be used if an `Extensible` enumeration does not deserialize to a known field that will be used if an `Extensible` enumeration or union does not
value on the receiver side, i.e. the sender is using a newer version of the deserialize to a known value on the receiver side, i.e. the sender is using a
enum. This allows unknown values to be mapped to a well-defined value that can newer version of the enum or union. This allows unknown values to be mapped to
be appropriately handled. 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]`**: * **`[Extensible]`**:
The `Extensible` attribute may be specified for any enum definition. This The `Extensible` attribute may be specified for any enum or union definition.
essentially disables builtin range validation when receiving values of the For enums, this essentially disables builtin range validation when receiving
enum type in a message, allowing older bindings to tolerate unrecognized values of the enum type in a message, allowing older bindings to tolerate
values from newer versions of the enum. unrecognized values from newer versions of the enum.
Note: in the future, an `Extensible` enumeration will require that a `Default` If an enum value within an extensible enum definition is affixed with the
enumerator value also be specified. `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`.
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]`**: * **`[Native]`**:
The `Native` attribute may be specified for an empty struct declaration to The `Native` attribute may be specified for an empty struct declaration to
@ -422,7 +469,10 @@ interesting attributes supported today.
* **`[MinVersion=N]`**: * **`[MinVersion=N]`**:
The `MinVersion` attribute is used to specify the version at which a given The `MinVersion` attribute is used to specify the version at which a given
field, enum value, interface method, or method parameter was introduced. 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]`**: * **`[Stable]`**:
The `Stable` attribute specifies that a given mojom type or interface The `Stable` attribute specifies that a given mojom type or interface
@ -442,13 +492,73 @@ interesting attributes supported today.
string representation as specified by RFC 4122. New UUIDs can be generated string representation as specified by RFC 4122. New UUIDs can be generated
with common tools such as `uuidgen`. with common tools such as `uuidgen`.
* **`[RuntimeFeature=feature]`**
The `RuntimeFeature` attribute should reference a mojo `feature`. If this
feature is enabled (e.g. using `--enable-features={feature.name}`) then the
interface behaves entirely as expected. If the feature is not enabled the
interface cannot be bound to a concrete receiver or remote - attempting to do
so will result in the receiver or remote being reset() to an unbound state.
Note that this is a different concept to the build-time `EnableIf` directive.
`RuntimeFeature` is currently only supported for C++ bindings and has no
effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).
* **`[EnableIf=value]`**: * **`[EnableIf=value]`**:
The `EnableIf` attribute is used to conditionally enable definitions when the The `EnableIf` attribute is used to conditionally enable definitions when the
mojom is parsed. If the `mojom` target in the GN file does not include the mojom is parsed. If the `mojom` target in the GN file does not include the
matching `value` in the list of `enabled_features`, the definition will be 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 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 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.
* **`[SupportsUrgent]`**:
The `SupportsUrgent` attribute is used in conjunction with
`mojo::UrgentMessageScope` in Chromium to tag messages as having high
priority. The IPC layer notifies the underlying scheduler upon both receiving
and processing an urgent message. At present, this attribute only affects
channel associated messages in the renderer process.
## Generated Code For Target Languages ## Generated Code For Target Languages
@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:
``` cpp ``` cpp
enum AdvancedBoolean { enum AdvancedBoolean {
TRUE = 0, kTrue = 0,
FALSE = 1, kFalse = 1,
FILE_NOT_FOUND = 2, kFileNotFound = 2,
}; };
``` ```
@ -550,10 +660,16 @@ See the documentation for
*** note *** note
**NOTE:** You don't need to worry about versioning if you don't care about **NOTE:** You don't need to worry about versioning if you don't care about
backwards compatibility. Specifically, all parts of Chrome are updated backwards compatibility. Today, all parts of the Chrome browser are
atomically today and there is not yet any possibility of any two Chrome updated atomically and there is not yet any possibility of any two
processes communicating with two different versions of any given Mojom Chrome processes communicating with two different versions of any given Mojom
interface. 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 Services extend their interfaces to support new features over time, and clients
@ -593,8 +709,8 @@ struct Employee {
*** note *** note
**NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
optional (nullable). See [Primitive Types](#Primitive-Types) for details on optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
nullable values. details on nullable values.
*** ***
By default, fields belong to version 0. New fields must be appended to the By default, fields belong to version 0. New fields must be appended to the
@ -624,10 +740,10 @@ the following hard constraints:
* For any given struct or interface, if any field or method explicitly specifies * 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 an ordinal value, all fields or methods must explicitly specify an ordinal
value. value.
* For an *N*-field struct or *N*-method interface, the set of explicitly * For an *N*-field struct, the set of explicitly assigned ordinal values must be
assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces limited to the range *[0, N-1]*. Structs should include placeholder fields
should include placeholder methods to fill the ordinal positions of removed to fill the ordinal positions of removed fields (for example "Unused_Field"
methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc). or "RemovedField", etc).
You may reorder fields, but you must ensure that the ordinal values of existing You may reorder fields, but you must ensure that the ordinal values of existing
fields remain unchanged. For example, the following struct remains fields remain unchanged. For example, the following struct remains
@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended
that the version number is scoped to the whole interface rather than to any that the version number is scoped to the whole interface rather than to any
individual parameter list. individual parameter list.
``` cpp
// Old version:
interface HumanResourceDatabase {
QueryEmployee(uint64 id) => (Employee? employee);
};
// New version:
interface HumanResourceDatabase {
QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
=> (Employee? employee,
[MinVersion=1] array<uint8>? finger_print);
};
```
Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
list of a request or response method to a destination using an older version of
an interface, unrecognized fields are silently discarded.
Please note that adding a response to a message which did not previously Please note that adding a response to a message which did not previously
expect a response is a not a backwards-compatible change. expect a response is a not a backwards-compatible change.
@ -664,17 +798,12 @@ For example:
``` cpp ``` cpp
// Old version: // Old version:
interface HumanResourceDatabase { interface HumanResourceDatabase {
AddEmployee(Employee employee) => (bool success);
QueryEmployee(uint64 id) => (Employee? employee); QueryEmployee(uint64 id) => (Employee? employee);
}; };
// New version: // New version:
interface HumanResourceDatabase { interface HumanResourceDatabase {
AddEmployee(Employee employee) => (bool success); QueryEmployee(uint64 id) => (Employee? employee);
QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
=> (Employee? employee,
[MinVersion=1] array<uint8>? finger_print);
[MinVersion=1] [MinVersion=1]
AttachFingerPrint(uint64 id, array<uint8> finger_print) AttachFingerPrint(uint64 id, array<uint8> finger_print)
@ -682,10 +811,7 @@ interface HumanResourceDatabase {
}; };
``` ```
Similar to [versioned structs](#Versioned-Structs), when you pass the parameter If a method call is not recognized, it is considered a validation error and the
list of a request or response method to a destination using an older version of
an interface, unrecognized fields are silently discarded. However, if the method
call itself is not recognized, it is considered a validation error and the
receiver will close its end of the interface pipe. For example, if a client on receiver will close its end of the interface pipe. For example, if a client on
version 1 of the above interface sends an `AttachFingerPrint` request to an version 1 of the above interface sends an `AttachFingerPrint` request to an
implementation of version 0, the client will be disconnected. implementation of version 0, the client will be disconnected.
@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the
``` cpp ``` cpp
[Extensible] [Extensible]
enum Department { enum Department {
SALES, kSales,
DEV, kDev,
}; };
``` ```
@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:
``` cpp ``` cpp
[Extensible] [Extensible]
enum Department { enum Department {
SALES, kSales,
DEV, kDev,
[MinVersion=1] RESEARCH, [MinVersion=1] kResearch,
}; };
``` ```
@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition
ModuleStatement = AttributeSection "module" Identifier ";" ModuleStatement = AttributeSection "module" Identifier ";"
ImportStatement = "import" StringLiteral ";" ImportStatement = "import" StringLiteral ";"
Definition = Struct Union Interface Enum Const Definition = Struct Union Interface Enum Feature Const
AttributeSection = <empty> | "[" AttributeList "]" AttributeSection = <empty> | "[" AttributeList "]"
AttributeList = <empty> | NonEmptyAttributeList AttributeList = <empty> | NonEmptyAttributeList
@ -809,7 +935,7 @@ InterfaceBody = <empty>
| InterfaceBody Const | InterfaceBody Const
| InterfaceBody Enum | InterfaceBody Enum
| InterfaceBody Method | InterfaceBody Method
Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";" Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";"
ParameterList = <empty> | NonEmptyParameterList ParameterList = <empty> | NonEmptyParameterList
NonEmptyParameterList = Parameter NonEmptyParameterList = Parameter
| Parameter "," NonEmptyParameterList | Parameter "," NonEmptyParameterList
@ -847,6 +973,13 @@ EnumValue = AttributeSection Name
| AttributeSection Name "=" Integer | AttributeSection Name "=" Integer
| AttributeSection Name "=" Identifier | AttributeSection Name "=" Identifier
; Note: `feature` is a weak keyword and can appear as, say, a struct field name.
Feature = AttributeSection "feature" Name "{" FeatureBody "}" ";"
| AttributeSection "feature" Name ";"
FeatureBody = <empty>
| FeatureBody FeatureField
FeatureField = AttributeSection TypeSpec Name Default ";"
Const = "const" TypeSpec Name "=" Constant ";" Const = "const" TypeSpec Name "=" Constant ";"
Constant = Literal | Identifier ";" Constant = Literal | Identifier ";"

View file

@ -0,0 +1,170 @@
# 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 | {
'RenamedFrom',
'RequireContext',
'RuntimeFeature',
'ServiceSandbox',
'Stable',
'Uuid',
}
_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
'AllowedContext',
'MinVersion',
'NoInterrupt',
'RuntimeFeature',
'SupportsUrgent',
'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)

View file

@ -0,0 +1,194 @@
# 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);
};
[RuntimeFeature=test.mojom.FeatureName]
interface FooFeatureControlled {};
interface FooMethodFeatureControlled {
[RuntimeFeature=test.mojom.FeatureName]
MethodWithFeature() => (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')

View file

@ -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

View file

@ -0,0 +1,62 @@
# 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.
"""Validate mojo runtime feature guarded interfaces are nullable."""
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)
# `param` is an Interface of some sort.
def _CheckNonNullableFeatureGuardedInterface(self, kind):
# Only need to validate interface if it has a RuntimeFeature
if not kind.kind.runtime_feature:
return
# Nullable (optional) is ok as the interface expects they might not be sent.
if kind.is_nullable:
return
interface = kind.kind.mojom_name
raise check.CheckException(
self.module,
f"interface {interface} has a RuntimeFeature but is not nullable")
# `param` can be a lot of things so check if it is a remote/receiver.
# Array/Map must be recursed into.
def _CheckFieldOrParam(self, kind):
if module.IsAnyInterfaceKind(kind):
self._CheckNonNullableFeatureGuardedInterface(kind)
if module.IsArrayKind(kind):
self._CheckFieldOrParam(kind.kind)
if module.IsMapKind(kind):
self._CheckFieldOrParam(kind.key_kind)
self._CheckFieldOrParam(kind.value_kind)
def _CheckInterfaceFeatures(self, interface):
for method in interface.methods:
for param in method.parameters:
self._CheckFieldOrParam(param.kind)
if method.response_parameters:
for param in method.response_parameters:
self._CheckFieldOrParam(param.kind)
def _CheckStructFeatures(self, struct):
for field in struct.fields:
self._CheckFieldOrParam(field.kind)
def _CheckUnionFeatures(self, union):
for field in union.fields:
self._CheckFieldOrParam(field.kind)
def CheckModule(self):
"""Validate that any runtime feature guarded interfaces that might be passed
over mojo are nullable."""
for interface in self.module.interfaces:
self._CheckInterfaceFeatures(interface)
for struct in self.module.structs:
self._CheckStructFeatures(struct)
for union in self.module.unions:
self._CheckUnionFeatures(union)

View file

@ -0,0 +1,173 @@
# 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.
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 = 'features'
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 assertValid(self, filename, content):
self.WriteFile(filename, content)
self._ParseAndGenerate([filename])
def assertThrows(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('features')
self.assertTrue(check_modules['features'])
def testNullableOk(self):
self.assertValid(
"a.mojom", """
module a;
// Scaffolding.
feature kFeature {
const string name = "Hello";
const bool enabled_state = false;
};
[RuntimeFeature=kFeature]
interface Guarded {
};
// Unguarded interfaces should be ok everywhere.
interface NotGuarded { };
// Optional (nullable) interfaces should be ok everywhere:
struct Bar {
pending_remote<Guarded>? remote;
pending_receiver<Guarded>? receiver;
};
union Thingy {
pending_remote<Guarded>? remote;
pending_receiver<Guarded>? receiver;
};
interface Foo {
Foo(
pending_remote<Guarded>? remote,
pending_receiver<Guarded>? receiver,
pending_associated_remote<Guarded>? a_remote,
pending_associated_receiver<Guarded>? a_receiver,
// Unguarded interfaces do not have to be nullable.
pending_remote<NotGuarded> remote,
pending_receiver<NotGuarded> receiver,
pending_associated_remote<NotGuarded> a_remote,
pending_associated_receiver<NotGuarded> a_receiver
) => (
pending_remote<Guarded>? remote,
pending_receiver<Guarded>? receiver
);
Bar(array<pending_remote<Guarded>?> remote)
=> (map<string, pending_receiver<Guarded>?> a);
};
""")
def testMethodParamsMustBeNullable(self):
prelude = """
module a;
// Scaffolding.
feature kFeature {
const string name = "Hello";
const bool enabled_state = false;
};
[RuntimeFeature=kFeature]
interface Guarded { };
"""
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(pending_remote<Guarded> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(bool foo) => (pending_receiver<Guarded> a);
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(pending_receiver<Guarded> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(pending_associated_remote<Guarded> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(pending_associated_receiver<Guarded> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(array<pending_associated_receiver<Guarded>> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
interface Trial {
Method(map<string, pending_associated_receiver<Guarded>> a) => ();
};
""", 'interface Guarded has a RuntimeFeature')
def testStructUnionMembersMustBeNullable(self):
prelude = """
module a;
// Scaffolding.
feature kFeature {
const string name = "Hello";
const bool enabled_state = false;
};
[RuntimeFeature=kFeature]
interface Guarded { };
"""
self.assertThrows(
'a.mojom', prelude + """
struct Trial {
pending_remote<Guarded> a;
};
""", 'interface Guarded has a RuntimeFeature')
self.assertThrows(
'a.mojom', prelude + """
union Trial {
pending_remote<Guarded> a;
};
""", 'interface Guarded has a RuntimeFeature')

View file

@ -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<Interface> 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)

View file

@ -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<interfaces.High> hi);
};
interface PassesMedium {
[AllowedContext=level.Level.kMiddle]
DoMedium(pending_receiver<interfaces.Mid> hi);
[AllowedContext=level.Level.kMiddle]
DoMediumRem(pending_remote<interfaces.Mid> hi);
[AllowedContext=level.Level.kMiddle]
DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
[AllowedContext=level.Level.kMiddle]
DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
};
interface PassesLow {
[AllowedContext=level.Level.kLowest]
DoLow(pending_receiver<interfaces.Low> hi);
};
struct One { pending_receiver<interfaces.High> 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<PassesHigh> 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<interfaces.High> 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<interfaces.High> 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<interfaces.High> 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<interfaces.High> 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<interfaces.High> 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<interfaces.High> 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<interfaces.High> hi; };
interface PassesMultipleInterfaces {
[AllowedContext=level.Level.kMiddle]
DoMultiple(
pending_remote<interfaces.Mid> mid,
pending_receiver<interfaces.High> 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<interfaces.High> hi; };
interface PassesMultipleInterfaces {
[AllowedContext=level.Level.kHighest]
DoMultiple(
pending_receiver<interfaces.High> hi,
pending_remote<interfaces.Mid> mid,
One one
);
};
""")
mojoms.append(b)
self._ParseAndGenerate(mojoms)

View file

@ -1,51 +0,0 @@
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
_typemap_imports = [
"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni",
"//chrome/common/importer/typemaps.gni",
"//chrome/common/media_router/mojom/typemaps.gni",
"//chrome/typemaps.gni",
"//chromecast/typemaps.gni",
"//chromeos/typemaps.gni",
"//chromeos/components/multidevice/mojom/typemaps.gni",
"//chromeos/services/cros_healthd/public/mojom/typemaps.gni",
"//chromeos/services/device_sync/public/mojom/typemaps.gni",
"//chromeos/services/network_config/public/mojom/typemaps.gni",
"//chromeos/services/secure_channel/public/mojom/typemaps.gni",
"//components/arc/mojom/typemaps.gni",
"//components/chromeos_camera/common/typemaps.gni",
"//components/services/storage/public/cpp/filesystem/typemaps.gni",
"//components/sync/mojom/typemaps.gni",
"//components/typemaps.gni",
"//content/browser/typemaps.gni",
"//content/public/common/typemaps.gni",
"//sandbox/mac/mojom/typemaps.gni",
"//services/media_session/public/cpp/typemaps.gni",
"//services/proxy_resolver/public/cpp/typemaps.gni",
"//services/resource_coordinator/public/cpp/typemaps.gni",
"//services/service_manager/public/cpp/typemaps.gni",
"//services/tracing/public/mojom/typemaps.gni",
]
_typemaps = []
foreach(typemap_import, _typemap_imports) {
# Avoid reassignment error by assigning to empty scope first.
_imported = {
}
_imported = read_file(typemap_import, "scope")
_typemaps += _imported.typemaps
}
typemaps = []
foreach(typemap, _typemaps) {
typemaps += [
{
filename = typemap
config = read_file(typemap, "scope")
},
]
}
component_macro_suffix = ""

View file

@ -1,27 +0,0 @@
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
import argparse
_HERE_PATH = os.path.dirname(__file__)
_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
import node
import node_modules
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('--tsconfig_path', required=True)
args = parser.parse_args(argv)
result = node.RunNode([node_modules.PathToTypescript()] +
['--project', args.tsconfig_path])
if len(result) != 0:
raise RuntimeError('Failed to compile Typescript: \n%s' % result)
if __name__ == '__main__':
main(sys.argv[1:])

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
# #
@ -15,6 +15,7 @@
from __future__ import print_function from __future__ import print_function
import optparse import optparse
import sys
def Concatenate(filenames): def Concatenate(filenames):
@ -47,7 +48,7 @@ def main():
parser.set_usage("""Concatenate several files into one. parser.set_usage("""Concatenate several files into one.
Equivalent to: cat file1 ... > target.""") Equivalent to: cat file1 ... > target.""")
(_options, args) = parser.parse_args() (_options, args) = parser.parse_args()
exit(0 if Concatenate(args) else 1) sys.exit(0 if Concatenate(args) else 1)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -20,6 +20,7 @@ from __future__ import print_function
import optparse import optparse
import re import re
import sys
_MOJO_INTERNAL_MODULE_NAME = "mojo.internal" _MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
@ -31,10 +32,10 @@ def FilterLine(filename, line, output):
return return
if line.startswith("goog.provide"): if line.startswith("goog.provide"):
match = re.match("goog.provide\('([^']+)'\);", line) match = re.match(r"goog.provide\('([^']+)'\);", line)
if not match: if not match:
print("Invalid goog.provide line in %s:\n%s" % (filename, line)) print("Invalid goog.provide line in %s:\n%s" % (filename, line))
exit(1) sys.exit(1)
module_name = match.group(1) module_name = match.group(1)
if module_name == _MOJO_INTERNAL_MODULE_NAME: if module_name == _MOJO_INTERNAL_MODULE_NAME:
@ -67,7 +68,8 @@ def main():
Concatenate several files into one, stripping Closure provide and Concatenate several files into one, stripping Closure provide and
require directives along the way.""") require directives along the way.""")
(_, args) = parser.parse_args() (_, args) = parser.parse_args()
exit(0 if ConcatenateAndReplaceExports(args) else 1) sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -1,36 +0,0 @@
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import sys
# This utility converts mojom dependencies into their corresponding typemap
# paths and formats them to be consumed by generate_type_mappings.py.
def FormatTypemap(typemap_filename):
# A simple typemap is valid Python with a minor alteration.
with open(typemap_filename) as f:
typemap_content = f.read().replace('=\n', '=')
typemap = {}
exec typemap_content in typemap
for header in typemap.get('public_headers', []):
yield 'public_headers=%s' % header
for header in typemap.get('traits_headers', []):
yield 'traits_headers=%s' % header
for header in typemap.get('type_mappings', []):
yield 'type_mappings=%s' % header
def main():
typemaps = sys.argv[1:]
print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))
for typemap in typemaps))
if __name__ == '__main__':
sys.exit(main())

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Generates a list of all files in a directory. """Generates a list of all files in a directory.

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Generates a JSON typemap from its command-line arguments and dependencies. """Generates a JSON typemap from its command-line arguments and dependencies.
@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):
for entry in config['types']: for entry in config['types']:
configs[entry['mojom']] = { configs[entry['mojom']] = {
'typename': entry['cpp'], 'typename': entry['cpp'],
'forward_declaration': entry.get('forward_declaration', None),
'public_headers': config.get('traits_headers', []), 'public_headers': config.get('traits_headers', []),
'traits_headers': config.get('traits_private_headers', []), 'traits_headers': config.get('traits_private_headers', []),
'copyable_pass_by_value': entry.get('copyable_pass_by_value', 'copyable_pass_by_value': entry.get('copyable_pass_by_value',
False), False),
'default_constructible': entry.get('default_constructible', True),
'force_serialize': entry.get('force_serialize', False), 'force_serialize': entry.get('force_serialize', False),
'hashable': entry.get('hashable', False), 'hashable': entry.get('hashable', False),
'move_only': entry.get('move_only', False), 'move_only': entry.get('move_only', False),

View file

@ -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:])

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {
"typescript": "mojom_ts_generator", "typescript": "mojom_ts_generator",
} }
_BUILTIN_CHECKS = {
"attributes": "mojom_attributes_check",
"definitions": "mojom_definitions_check",
"features": "mojom_interface_feature_check",
"restrictions": "mojom_restrictions_check",
}
def LoadGenerators(generators_string): def LoadGenerators(generators_string):
if not generators_string: if not generators_string:
return [] # No generators. return {} # No generators.
generators = {} generators = {}
for generator_name in [s.strip() for s in generators_string.split(",")]: for generator_name in [s.strip() for s in generators_string.split(",")]:
@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
return generators 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): def MakeImportStackMessage(imported_filename_stack):
"""Make a (human-readable) message listing a chain of imports. (Returned """Make a (human-readable) message listing a chain of imports. (Returned
string begins with a newline (if nonempty) and does not end with one.)""" 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)])) 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.""" """Represents a path relative to the source tree or generated output dir."""
def __init__(self, path, source_root, output_dir): def __init__(self, path, source_root, output_dir):
@ -142,7 +164,7 @@ def ReadFileContents(filename):
return f.read() return f.read()
class MojomProcessor(object): class MojomProcessor:
"""Takes parsed mojom modules and generates language bindings from them. """Takes parsed mojom modules and generates language bindings from them.
Attributes: Attributes:
@ -169,8 +191,8 @@ class MojomProcessor(object):
if 'c++' in self._typemap: if 'c++' in self._typemap:
self._typemap['mojolpm'] = self._typemap['c++'] self._typemap['mojolpm'] = self._typemap['c++']
def _GenerateModule(self, args, remaining_args, generator_modules, def _GenerateModule(self, args, remaining_args, check_modules,
rel_filename, imported_filename_stack): generator_modules, rel_filename, imported_filename_stack):
# Return the already-generated module. # Return the already-generated module.
if rel_filename.path in self._processed_files: if rel_filename.path in self._processed_files:
return self._processed_files[rel_filename.path] return self._processed_files[rel_filename.path]
@ -190,12 +212,16 @@ class MojomProcessor(object):
ScrambleMethodOrdinals(module.interfaces, salt) ScrambleMethodOrdinals(module.interfaces, salt)
if self._should_generate(rel_filename.path): 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(): for language, generator_module in generator_modules.items():
generator = generator_module.Generator( generator = generator_module.Generator(
module, args.output_dir, typemap=self._typemap.get(language, {}), module, args.output_dir, typemap=self._typemap.get(language, {}),
variant=args.variant, bytecode_path=args.bytecode_path, variant=args.variant, bytecode_path=args.bytecode_path,
for_blink=args.for_blink, for_blink=args.for_blink,
js_bindings_mode=args.js_bindings_mode,
js_generate_struct_deserializers=\ js_generate_struct_deserializers=\
args.js_generate_struct_deserializers, args.js_generate_struct_deserializers,
export_attribute=args.export_attribute, export_attribute=args.export_attribute,
@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
args.import_directories[idx] = RelativePath(tokens[0], args.depth, args.import_directories[idx] = RelativePath(tokens[0], args.depth,
args.output_dir) args.output_dir)
generator_modules = LoadGenerators(args.generators_string) generator_modules = LoadGenerators(args.generators_string)
check_modules = LoadChecks(args.checks_string)
fileutil.EnsureDirectoryExists(args.output_dir) fileutil.EnsureDirectoryExists(args.output_dir)
@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
for filename in args.filename: for filename in args.filename:
processor._GenerateModule( processor._GenerateModule(
args, remaining_args, generator_modules, args, remaining_args, check_modules, generator_modules,
RelativePath(filename, args.depth, args.output_dir), []) RelativePath(filename, args.depth, args.output_dir), [])
return 0 return 0
@ -286,6 +313,12 @@ def main():
metavar="GENERATORS", metavar="GENERATORS",
default="c++,javascript,java,mojolpm", default="c++,javascript,java,mojolpm",
help="comma-separated list of generators") help="comma-separated list of generators")
generate_parser.add_argument("-c",
"--checks",
dest="checks_string",
metavar="CHECKS",
default=",".join(_BUILTIN_CHECKS.keys()),
help="comma-separated list of checks")
generate_parser.add_argument( generate_parser.add_argument(
"--gen_dir", dest="gen_directories", action="append", metavar="directory", "--gen_dir", dest="gen_directories", action="append", metavar="directory",
default=[], help="add a directory to be searched for the syntax trees.") 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", generate_parser.add_argument("--for_blink", action="store_true",
help="Use WTF types as generated types for mojo " help="Use WTF types as generated types for mojo "
"string/array/map.") "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( generate_parser.add_argument(
"--js_generate_struct_deserializers", action="store_true", "--js_generate_struct_deserializers", action="store_true",
help="Generate javascript deserialize methods for structs in " help="Generate javascript deserialize methods for structs in "
@ -387,4 +415,10 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
with crbug_1001171.DumpStateOnLookupError(): 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)

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
from mojom_bindings_generator import ScrambleMethodOrdinals from mojom_bindings_generator import ScrambleMethodOrdinals
class FakeIface(object): class FakeIface:
def __init__(self): def __init__(self):
self.mojom_name = None self.mojom_name = None
self.methods = None self.methods = None
class FakeMethod(object): class FakeMethod:
def __init__(self, explicit_ordinal=None): def __init__(self, explicit_ordinal=None):
self.explicit_ordinal = explicit_ordinal self.explicit_ordinal = explicit_ordinal
self.ordinal = explicit_ordinal self.ordinal = explicit_ordinal

View file

@ -1,119 +0,0 @@
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Downgrades *.mojom files to the old mojo types for remotes and receivers."""
import argparse
import fnmatch
import os
import re
import shutil
import sys
import tempfile
# List of patterns and replacements to match and use against the contents of a
# mojo file. Each replacement string will be used with Python string's format()
# function, so the '{}' substring is used to mark where the mojo type should go.
_MOJO_REPLACEMENTS = {
r'pending_remote': r'{}',
r'pending_receiver': r'{}&',
r'pending_associated_remote': r'associated {}',
r'pending_associated_receiver': r'associated {}&',
}
# Pre-compiled regular expression that matches against any of the replacements.
_REGEXP_PATTERN = re.compile(
r'|'.join(
['{}\s*<\s*(.*?)\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),
flags=re.DOTALL)
def ReplaceFunction(match_object):
"""Returns the right replacement for the string matched against the regexp."""
for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):
if match_object.group(0).startswith(match):
return repl.format(match_object.group(index))
def DowngradeFile(path, output_dir=None):
"""Downgrades the mojom file specified by |path| to the old mojo types.
Optionally pass |output_dir| to place the result under a separate output
directory, preserving the relative path to the file included in |path|.
"""
# Use a temporary file to dump the new contents after replacing the patterns.
with open(path) as src_mojo_file:
with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:
tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())
tmp_mojo_file.write(tmp_contents)
# Files should be placed in the desired output directory
if output_dir:
output_filepath = os.path.join(output_dir, os.path.basename(path))
if not os.path.exists(output_dir):
os.makedirs(output_dir)
else:
output_filepath = path
# Write the new contents preserving the original file's attributes.
shutil.copystat(path, tmp_mojo_file.name)
shutil.move(tmp_mojo_file.name, output_filepath)
# Make sure to "touch" the new file so that access, modify and change times
# are always newer than the source file's, otherwise Modify time will be kept
# as per the call to shutil.copystat(), causing unnecessary generations of the
# output file in subsequent builds due to ninja considering it dirty.
os.utime(output_filepath, None)
def DowngradeDirectory(path, output_dir=None):
"""Downgrades mojom files inside directory |path| to the old mojo types.
Optionally pass |output_dir| to place the result under a separate output
directory, preserving the relative path to the file included in |path|.
"""
# We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7
mojom_filepaths = []
for dir_path, _, filenames in os.walk(path):
for filename in fnmatch.filter(filenames, "*mojom"):
mojom_filepaths.append(os.path.join(dir_path, filename))
for path in mojom_filepaths:
absolute_dirpath = os.path.dirname(os.path.abspath(path))
if output_dir:
dest_dirpath = output_dir + absolute_dirpath
else:
dest_dirpath = absolute_dirpath
DowngradeFile(path, dest_dirpath)
def DowngradePath(src_path, output_dir=None):
"""Downgrades the mojom files pointed by |src_path| to the old mojo types.
Optionally pass |output_dir| to place the result under a separate output
directory, preserving the relative path to the file included in |path|.
"""
if os.path.isdir(src_path):
DowngradeDirectory(src_path, output_dir)
elif os.path.isfile(src_path):
DowngradeFile(src_path, output_dir)
else:
print(">>> {} not pointing to a valid file or directory".format(src_path))
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description="Downgrade *.mojom files to use the old mojo types.")
parser.add_argument(
"srcpath", help="path to the file or directory to apply the conversion")
parser.add_argument(
"--outdir", help="the directory to place the converted file(s) under")
args = parser.parse_args()
DowngradePath(args.srcpath, args.outdir)
if __name__ == "__main__":
sys.exit(main())

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
]) ])
_SUPPORTED_TYPE_KEYS = set([ _SUPPORTED_TYPE_KEYS = set([
'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable', 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
'move_only', 'nullable_is_same_type' 'move_only', 'nullable_is_same_type', 'forward_declaration',
'default_constructible'
]) ])
with open(config_filename, 'r') as f: with open(config_filename, 'r') as f:
for config in json.load(f): for config in json.load(f):

View file

@ -0,0 +1,18 @@
# 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",
"feature_unittest.py",
"mojom_parser_test_case.py",
"mojom_parser_unittest.py",
"mojom_parser.py",
"stable_attribute_unittest.py",
"version_compatibility_unittest.py",
]
}

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Verifies backward-compatibility of mojom type changes. """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.""" breaking changes to stable mojoms."""
import argparse import argparse
import errno
import io import io
import json import json
import os import os
import os.path import os.path
import shutil
import six
import sys import sys
import tempfile
from mojom.generate import module from mojom.generate import module
from mojom.generate import translate from mojom.generate import translate
from mojom.parse import parser from mojom.parse import parser
# pylint: disable=raise-missing-from
class ParseError(Exception): class ParseError(Exception):
pass pass
@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
transitive closure of a mojom's input dependencies all at once. 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 # First build a map of all files covered by the delta
affected_files = set() affected_files = set()
old_files = {} old_files = {}
@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):
try: try:
ast = parser.Parse(contents, mojom) ast = parser.Parse(contents, mojom)
except Exception as e: except Exception as e:
six.reraise( raise ParseError('encountered exception {0} while parsing {1}'.format(
ParseError, e, mojom))
'encountered exception {0} while parsing {1}'.format(e, mojom),
sys.exc_info()[2]) # 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.mojom'),
('third_party/blink/public/mojom/origin_trial_feature/'
'origin_trial_feature.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: 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) parseMojom(imp.import_filename, file_overrides, override_modules)
# Now that the transitive set of dependencies has been imported and parsed # Now that the transitive set of dependencies has been imported and parsed
@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):
modules[mojom] = translate.OrderedModule(ast, mojom, all_modules) modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
old_modules = {} old_modules = {}
for mojom in old_files.keys(): for mojom in old_files:
parseMojom(mojom, old_files, old_modules) parseMojom(mojom, old_files, old_modules)
new_modules = {} new_modules = {}
for mojom in new_files.keys(): for mojom in new_files:
parseMojom(mojom, new_files, new_modules) parseMojom(mojom, new_files, new_modules)
# At this point we have a complete set of translated Modules from both the # At this point we have a complete set of translated Modules from both the
@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):
'can be deleted by a subsequent change.' % qualified_name) 'can be deleted by a subsequent change.' % qualified_name)
checker = module.BackwardCompatibilityChecker() checker = module.BackwardCompatibilityChecker()
if not checker.IsBackwardCompatible(new_types[new_name], kind): try:
raise Exception('Stable type %s appears to have changed in a way which ' if not checker.IsBackwardCompatible(new_types[new_name], kind):
'breaks backward-compatibility. Please fix!\n\nIf you ' raise Exception(
'believe this assessment to be incorrect, please file a ' 'Stable type %s appears to have changed in a way which '
'Chromium bug against the "Internals>Mojo>Bindings" ' 'breaks backward-compatibility. Please fix!\n\nIf you '
'component.' % qualified_name) 'believe this assessment to be incorrect, please file a '
'Chromium bug against the "Internals>Mojo>Bindings" '
'component.' % qualified_name)
except Exception as e:
raise Exception(
'Stable type %s appears to have changed in a way which '
'breaks backward-compatibility: \n\n%s.\nPlease fix!\n\nIf you '
'believe this assessment to be incorrect, please file a '
'Chromium bug against the "Internals>Mojo>Bindings" '
'component.' % (qualified_name, e))
def Run(command_line, delta=None): def Run(command_line, delta=None):

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
from mojom.generate import module from mojom.generate import module
class Change(object): class Change:
"""Helper to clearly define a mojom file delta to be analyzed.""" """Helper to clearly define a mojom file delta to be analyzed."""
def __init__(self, filename, old=None, new=None): def __init__(self, filename, old=None, new=None):
@ -28,7 +28,7 @@ class Change(object):
class UnchangedFile(Change): class UnchangedFile(Change):
def __init__(self, filename, contents): 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): class CheckStableMojomCompatibilityTest(unittest.TestCase):
@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
[Stable] struct T { foo.S s; int32 x; }; [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, };')
])

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
self.assertEqual('F', b.enums[0].mojom_name) self.assertEqual('F', b.enums[0].mojom_name)
self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name) self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
self.assertEqual(37, b.enums[0].fields[0].numeric_value) 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')

View file

@ -0,0 +1,84 @@
# 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.
from mojom_parser_test_case import MojomParserTestCase
class FeatureTest(MojomParserTestCase):
"""Tests feature parsing behavior."""
def testFeatureOff(self):
"""Verifies basic parsing of feature types."""
types = self.ExtractTypes("""
// e.g. BASE_DECLARE_FEATURE(kFeature);
[AttributeOne=ValueOne]
feature kFeature {
// BASE_FEATURE(kFeature,"MyFeature",
// base::FEATURE_DISABLED_BY_DEFAULT);
const string name = "MyFeature";
const bool default_state = false;
};
""")
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
self.assertEqual('false', types['kFeature'].constants[1].value)
def testFeatureOn(self):
"""Verifies basic parsing of feature types."""
types = self.ExtractTypes("""
// e.g. BASE_DECLARE_FEATURE(kFeature);
feature kFeature {
// BASE_FEATURE(kFeature,"MyFeature",
// base::FEATURE_ENABLED_BY_DEFAULT);
const string name = "MyFeature";
const bool default_state = true;
};
""")
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
self.assertEqual('true', types['kFeature'].constants[1].value)
def testFeatureWeakKeyword(self):
"""Verifies that `feature` is a weak keyword."""
types = self.ExtractTypes("""
// e.g. BASE_DECLARE_FEATURE(kFeature);
[AttributeOne=ValueOne]
feature kFeature {
// BASE_FEATURE(kFeature,"MyFeature",
// base::FEATURE_DISABLED_BY_DEFAULT);
const string name = "MyFeature";
const bool default_state = false;
};
struct MyStruct {
bool feature = true;
};
interface InterfaceName {
Method(string feature) => (int32 feature);
};
""")
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
self.assertEqual('false', types['kFeature'].constants[1].value)
def testFeatureAttributesAreFeatures(self):
"""Verifies that feature values in attributes are really feature types."""
a_mojom = 'a.mojom'
self.WriteFile(
a_mojom, 'module a;'
'feature F { const string name = "f";'
'const bool default_state = false; };')
b_mojom = 'b.mojom'
self.WriteFile(
b_mojom, 'module b;'
'import "a.mojom";'
'feature G'
'{const string name = "g"; const bool default_state = false;};'
'[Attri=a.F] interface Foo { Foo(); };'
'[Boink=G] interface Bar {};')
self.ParseMojoms([a_mojom, b_mojom])
b = self.LoadModule(b_mojom)
self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')
self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -8,6 +8,7 @@ group("mojom") {
"error.py", "error.py",
"fileutil.py", "fileutil.py",
"generate/__init__.py", "generate/__init__.py",
"generate/check.py",
"generate/generator.py", "generate/generator.py",
"generate/module.py", "generate/module.py",
"generate/pack.py", "generate/pack.py",

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.

View file

@ -1,9 +1,8 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import errno import errno
import imp
import os.path import os.path
import sys import sys

View file

@ -1,20 +1,17 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp
import os.path import os.path
import shutil import shutil
import sys
import tempfile import tempfile
import unittest import unittest
from mojom import fileutil from mojom import fileutil
class FileUtilTest(unittest.TestCase): class FileUtilTest(unittest.TestCase):
def testEnsureDirectoryExists(self): def testEnsureDirectoryExists(self):
"""Test that EnsureDirectoryExists fuctions correctly.""" """Test that EnsureDirectoryExists functions correctly."""
temp_dir = tempfile.mkdtemp() temp_dir = tempfile.mkdtemp()
try: try:

View file

@ -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")

View file

@ -1,93 +0,0 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Resolves the values used for constants and enums."""
from itertools import ifilter
from mojom.generate import module as mojom
def ResolveConstants(module, expression_to_text):
in_progress = set()
computed = set()
def GetResolvedValue(named_value):
assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))
if isinstance(named_value, mojom.EnumValue):
field = next(
ifilter(lambda field: field.name == named_value.name,
named_value.enum.fields), None)
if not field:
raise RuntimeError(
'Unable to get computed value for field %s of enum %s' %
(named_value.name, named_value.enum.name))
if field not in computed:
ResolveEnum(named_value.enum)
return field.resolved_value
else:
ResolveConstant(named_value.constant)
named_value.resolved_value = named_value.constant.resolved_value
return named_value.resolved_value
def ResolveConstant(constant):
if constant in computed:
return
if constant in in_progress:
raise RuntimeError('Circular dependency for constant: %s' % constant.name)
in_progress.add(constant)
if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
resolved_value = GetResolvedValue(constant.value)
else:
resolved_value = expression_to_text(constant.value)
constant.resolved_value = resolved_value
in_progress.remove(constant)
computed.add(constant)
def ResolveEnum(enum):
def ResolveEnumField(enum, field, default_value):
if field in computed:
return
if field in in_progress:
raise RuntimeError('Circular dependency for enum: %s' % enum.name)
in_progress.add(field)
if field.value:
if isinstance(field.value, mojom.EnumValue):
resolved_value = GetResolvedValue(field.value)
elif isinstance(field.value, str):
resolved_value = int(field.value, 0)
else:
raise RuntimeError('Unexpected value: %s' % field.value)
else:
resolved_value = default_value
field.resolved_value = resolved_value
in_progress.remove(field)
computed.add(field)
current_value = 0
for field in enum.fields:
ResolveEnumField(enum, field, current_value)
current_value = field.resolved_value + 1
for constant in module.constants:
ResolveConstant(constant)
for enum in module.enums:
ResolveEnum(enum)
for struct in module.structs:
for constant in struct.constants:
ResolveConstant(constant)
for enum in struct.enums:
ResolveEnum(enum)
for field in struct.fields:
if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
field.default.resolved_value = GetResolvedValue(field.default)
for interface in module.interfaces:
for constant in interface.constants:
ResolveConstant(constant)
for enum in interface.enums:
ResolveEnum(enum)
return module

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Code shared by the various language-specific code generators.""" """Code shared by the various language-specific code generators."""
@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
return _ToSnakeCase(identifier, upper=False) return _ToSnakeCase(identifier, upper=False)
class Stylizer(object): class Stylizer:
"""Stylizers specify naming rules to map mojom names to names in generated """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 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 MethodName in the generated code, you need to define a subclass of Stylizer
@ -130,6 +130,9 @@ class Stylizer(object):
def StylizeEnum(self, mojom_name): def StylizeEnum(self, mojom_name):
return mojom_name return mojom_name
def StylizeFeature(self, mojom_name):
return mojom_name
def StylizeModule(self, mojom_namespace): def StylizeModule(self, mojom_namespace):
return mojom_namespace return mojom_namespace
@ -233,7 +236,7 @@ def AddComputedData(module):
_AddInterfaceComputedData(interface) _AddInterfaceComputedData(interface)
class Generator(object): class Generator:
# Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
# files to stdout. # files to stdout.
def __init__(self, def __init__(self,
@ -243,7 +246,6 @@ class Generator(object):
variant=None, variant=None,
bytecode_path=None, bytecode_path=None,
for_blink=False, for_blink=False,
js_bindings_mode="new",
js_generate_struct_deserializers=False, js_generate_struct_deserializers=False,
export_attribute=None, export_attribute=None,
export_header=None, export_header=None,
@ -262,7 +264,6 @@ class Generator(object):
self.variant = variant self.variant = variant
self.bytecode_path = bytecode_path self.bytecode_path = bytecode_path
self.for_blink = for_blink self.for_blink = for_blink
self.js_bindings_mode = js_bindings_mode
self.js_generate_struct_deserializers = js_generate_struct_deserializers self.js_generate_struct_deserializers = js_generate_struct_deserializers
self.export_attribute = export_attribute self.export_attribute = export_attribute
self.export_header = export_header self.export_header = export_header

View file

@ -1,13 +1,12 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp import importlib.util
import os.path import os.path
import sys import sys
import unittest import unittest
def _GetDirAbove(dirname): def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must """Returns the directory "above" this file containing |dirname| (which must
also be "above" this file).""" also be "above" this file)."""
@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
try: try:
imp.find_module("mojom") importlib.util.find_spec("mojom")
except ImportError: except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
from mojom.generate import generator from mojom.generate import generator
class StringManipulationTest(unittest.TestCase): class StringManipulationTest(unittest.TestCase):
"""generator contains some string utilities, this tests only those.""" """generator contains some string utilities, this tests only those."""
@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
self.assertEquals("SNAKE_D3D11_CASE", self.assertEquals("SNAKE_D3D11_CASE",
generator.ToUpperSnakeCase("snakeD3d11Case")) generator.ToUpperSnakeCase("snakeD3d11Case"))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

File diff suppressed because it is too large Load diff

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import copy
from mojom.generate import module as mojom from mojom.generate import module as mojom
# This module provides a mechanism for determining the packed order and offsets # 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 HEADER_SIZE = 8
class PackedField(object): class PackedField:
kind_to_size = { kind_to_size = {
mojom.BOOL: 1, mojom.BOOL: 1,
mojom.INT8: 1, mojom.INT8: 1,
@ -75,18 +76,55 @@ class PackedField(object):
return 8 return 8
return cls.GetSizeForKind(kind) 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: Args:
field: the original field. field: the original field.
index: the position of the original field in the struct. index: the position of the original field in the struct.
ordinal: the ordinal of the field for serialization. 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.field = field
self.index = index self.index = index
self.ordinal = ordinal self.ordinal = ordinal
self.size = self.GetSizeForKind(field.kind) self.original_field = original_field
self.alignment = self.GetAlignmentForKind(field.kind) 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.offset = None
self.bit = None self.bit = None
self.min_version = None self.min_version = None
@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
return offset + pad 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): def __init__(self, struct):
self.struct = struct self.struct = struct
# |packed_fields| contains all the fields, in increasing offset order. # |packed_fields| contains all the fields, in increasing offset order.
@ -139,9 +203,41 @@ class PackedStruct(object):
for index, field in enumerate(struct.fields): for index, field in enumerate(struct.fields):
if field.ordinal is not None: if field.ordinal is not None:
ordinal = field.ordinal 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 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. # Set |min_version| for each field.
next_min_version = 0 next_min_version = 0
@ -156,10 +252,11 @@ class PackedStruct(object):
if (packed_field.min_version != 0 if (packed_field.min_version != 0
and mojom.IsReferenceKind(packed_field.field.kind) and mojom.IsReferenceKind(packed_field.field.kind)
and not packed_field.field.kind.is_nullable): and not packed_field.field.kind.is_nullable):
raise Exception("Non-nullable fields are only allowed in version 0 of " raise Exception(
"a struct. %s.%s is defined with [MinVersion=%d]." % "Non-nullable reference fields are only allowed in version 0 of a "
(self.struct.name, packed_field.field.name, "struct. %s.%s is defined with [MinVersion=%d]." %
packed_field.min_version)) (self.struct.name, packed_field.field.name,
packed_field.min_version))
src_field = src_fields[0] src_field = src_fields[0]
src_field.offset = 0 src_field.offset = 0
@ -186,7 +283,7 @@ class PackedStruct(object):
dst_fields.append(src_field) dst_fields.append(src_field)
class ByteInfo(object): class ByteInfo:
def __init__(self): def __init__(self):
self.is_padding = False self.is_padding = False
self.packed_fields = [] self.packed_fields = []
@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):
return byte_info return byte_info
class VersionInfo(object): class VersionInfo:
def __init__(self, version, num_fields, num_bytes): def __init__(self, version, num_fields, num_packed_fields, num_bytes):
self.version = version self.version = version
self.num_fields = num_fields self.num_fields = num_fields
self.num_packed_fields = num_packed_fields
self.num_bytes = num_bytes self.num_bytes = num_bytes
@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):
versions = [] versions = []
last_version = 0 last_version = 0
last_num_fields = 0 last_num_fields = 0
last_num_packed_fields = 0
last_payload_size = 0 last_payload_size = 0
for packed_field in packed_struct.packed_fields_in_ordinal_order: for packed_field in packed_struct.packed_fields_in_ordinal_order:
if packed_field.min_version != last_version: if packed_field.min_version != last_version:
versions.append( versions.append(
VersionInfo(last_version, last_num_fields, VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE)) last_payload_size + HEADER_SIZE))
last_version = packed_field.min_version last_version = packed_field.min_version
last_num_fields += 1 # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to
# avoid double-counting, only increment if the field is:
# - not used for representing a nullable value kind field, or
# - the primary field representing the nullable value kind field.
last_num_fields += 1 if (
not IsNullableValueKindPackedField(packed_field)
or IsPrimaryNullableValueKindPackedField(packed_field)) else 0
last_num_packed_fields += 1
# The fields are iterated in ordinal order here. However, the size of a # The fields are iterated in ordinal order here. However, the size of a
# version is determined by the last field of that version in pack order, # version is determined by the last field of that version in pack order,
# instead of ordinal order. Therefore, we need to calculate the max value. # instead of ordinal order. Therefore, we need to calculate the max value.
last_payload_size = max( last_payload_size = max(GetPayloadSizeUpToField(packed_field),
GetPayloadSizeUpToField(packed_field), last_payload_size) last_payload_size)
assert len(versions) == 0 or last_num_fields != versions[-1].num_fields assert len(
versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields
versions.append( versions.append(
VersionInfo(last_version, last_num_fields, VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE)) last_payload_size + HEADER_SIZE))
return versions return versions

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):
self.assertEqual(4, versions[2].num_fields) self.assertEqual(4, versions[2].num_fields)
self.assertEqual(32, versions[2].num_bytes) self.assertEqual(32, versions[2].num_bytes)
def testGetVersionInfoPackedStruct(self):
"""Tests that pack.GetVersionInfo() correctly sets version, num_fields,
and num_packed_fields for a packed struct.
"""
struct = mojom.Struct('test')
struct.AddField('field_0', mojom.BOOL, ordinal=0)
struct.AddField('field_1',
mojom.NULLABLE_BOOL,
ordinal=1,
attributes={'MinVersion': 1})
struct.AddField('field_2',
mojom.NULLABLE_BOOL,
ordinal=2,
attributes={'MinVersion': 2})
ps = pack.PackedStruct(struct)
versions = pack.GetVersionInfo(ps)
self.assertEqual(3, len(versions))
self.assertEqual(0, versions[0].version)
self.assertEqual(1, versions[1].version)
self.assertEqual(2, versions[2].version)
self.assertEqual(1, versions[0].num_fields)
self.assertEqual(2, versions[1].num_fields)
self.assertEqual(3, versions[2].num_fields)
self.assertEqual(1, versions[0].num_packed_fields)
self.assertEqual(3, versions[1].num_packed_fields)
self.assertEqual(5, versions[2].num_packed_fields)
def testInterfaceAlignment(self): def testInterfaceAlignment(self):
"""Tests that interfaces are aligned on 4-byte boundaries, although the size """Tests that interfaces are aligned on 4-byte boundaries, although the size
of an interface is 8 bytes. of an interface is 8 bytes.

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Convert parse tree to AST. """Convert parse tree to AST.
@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.
import itertools import itertools
import os import os
import re import re
import sys
from collections import OrderedDict
from mojom.generate import generator from mojom.generate import generator
from mojom.generate import module as mojom from mojom.generate import module as mojom
from mojom.parse import ast from mojom.parse import ast
def _IsStrOrUnicode(x): is_running_backwards_compatibility_check_hack = False
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str)) ### DO NOT ADD ENTRIES TO THIS LIST. ###
return isinstance(x, str) _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.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: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): def _DuplicateName(values):
@ -98,12 +375,6 @@ def _MapKind(kind):
} }
if kind.endswith('?'): if kind.endswith('?'):
base_kind = _MapKind(kind[0:-1]) 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 return '?' + base_kind
if kind.endswith('}'): if kind.endswith('}'):
lbracket = kind.rfind('{') lbracket = kind.rfind('{')
@ -113,8 +384,6 @@ def _MapKind(kind):
lbracket = kind.rfind('[') lbracket = kind.rfind('[')
typename = kind[0:lbracket] typename = kind[0:lbracket]
return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename) return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
if kind.endswith('&'):
return 'r:' + _MapKind(kind[0:-1])
if kind.startswith('asso<'): if kind.startswith('asso<'):
assert kind.endswith('>') assert kind.endswith('>')
return 'asso:' + _MapKind(kind[5:-1]) return 'asso:' + _MapKind(kind[5:-1])
@ -135,13 +404,45 @@ def _MapKind(kind):
return 'x:' + kind return 'x:' + kind
def _AttributeListToDict(attribute_list): def _MapAttributeValue(module, kind, value):
# True/False/None
if value is None:
return value
if not isinstance(value, str):
return value
# Is the attribute value the name of a feature?
try:
# Features cannot be nested in other types, so lookup in the global scope.
trial = _LookupKind(module.kinds, 'x:' + value,
_GetScopeForKind(module, kind))
if isinstance(trial, mojom.Feature):
return trial
except ValueError:
pass
# Is the attribute value a constant or enum value?
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
# If not a referenceable mojo type - return as a string.
return value
def _AttributeListToDict(module, kind, attribute_list):
if attribute_list is None: if attribute_list is None:
return None return None
assert isinstance(attribute_list, ast.AttributeList) assert isinstance(attribute_list, ast.AttributeList)
# TODO(vtl): Check for duplicate keys here. attributes = dict()
return dict( for attribute in attribute_list:
[(attribute.key, attribute.value) for attribute in attribute_list]) if attribute.key in attributes:
raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
attributes[attribute.key] = _MapAttributeValue(module, kind,
attribute.value)
return attributes
builtin_values = frozenset([ builtin_values = frozenset([
@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):
return kind return kind
if spec.startswith('?'): if spec.startswith('?'):
kind = _Kind(kinds, spec[1:], scope).MakeNullableKind() kind = _Kind(kinds, spec[1:], scope)
kind = kind.MakeNullableKind()
elif spec.startswith('a:'): elif spec.startswith('a:'):
kind = mojom.Array(_Kind(kinds, spec[2:], scope)) kind = mojom.Array(_Kind(kinds, spec[2:], scope))
elif spec.startswith('asso:'): elif spec.startswith('asso:'):
@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):
def _Import(module, import_module): def _Import(module, import_module):
# Copy the struct kinds from our imports into the current module. # Copy the struct kinds from our imports into the current module.
importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,
mojom.Feature)
for kind in import_module.kinds.values(): for kind in import_module.kinds.values():
if (isinstance(kind, importable_kinds) if (isinstance(kind, importable_kinds)
and kind.module.path == import_module.path): and kind.module.path == import_module.path):
@ -316,6 +619,32 @@ def _Import(module, import_module):
return import_module return import_module
def _Feature(module, parsed_feature):
"""
Args:
module: {mojom.Module} Module currently being constructed.
parsed_feature: {ast.Feature} Parsed feature.
Returns:
{mojom.Feature} AST feature.
"""
feature = mojom.Feature(module=module)
feature.mojom_name = parsed_feature.mojom_name
feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name
module.kinds[feature.spec] = feature
feature.constants = []
_ProcessElements(
parsed_feature.mojom_name, parsed_feature.body, {
ast.Const:
lambda const: feature.constants.append(
_Constant(module, const, feature)),
})
feature.attributes = _AttributeListToDict(module, feature,
parsed_feature.attribute_list)
return feature
def _Struct(module, parsed_struct): def _Struct(module, parsed_struct):
""" """
Args: Args:
@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):
struct.fields_data.append, struct.fields_data.append,
}) })
struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) struct.attributes = _AttributeListToDict(module, struct,
parsed_struct.attribute_list)
# Enforce that a [Native] attribute is set to make native-only struct # Enforce that a [Native] attribute is set to make native-only struct
# declarations more explicit. # declarations more explicit.
@ -377,7 +707,8 @@ def _Union(module, parsed_union):
union.fields_data = [] union.fields_data = []
_ProcessElements(parsed_union.mojom_name, parsed_union.body, _ProcessElements(parsed_union.mojom_name, parsed_union.body,
{ast.UnionField: union.fields_data.append}) {ast.UnionField: union.fields_data.append})
union.attributes = _AttributeListToDict(parsed_union.attribute_list) union.attributes = _AttributeListToDict(module, union,
parsed_union.attribute_list)
return union return union
@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = _LookupValue(module, struct, field.kind, field.default = _LookupValue(module, struct, field.kind,
parsed_field.default_value) parsed_field.default_value)
field.attributes = _AttributeListToDict(parsed_field.attribute_list) field.attributes = _AttributeListToDict(module, field,
parsed_field.attribute_list)
return field return field
@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):
""" """
field = mojom.UnionField() field = mojom.UnionField()
field.mojom_name = parsed_field.mojom_name 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), field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
(module.mojom_namespace, union.mojom_name)) (module.mojom_namespace, union.mojom_name))
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = None field.default = None
field.attributes = _AttributeListToDict(parsed_field.attribute_list) field.attributes = _AttributeListToDict(module, field,
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 return field
@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):
parameter.ordinal = (parsed_param.ordinal.value parameter.ordinal = (parsed_param.ordinal.value
if parsed_param.ordinal else None) if parsed_param.ordinal else None)
parameter.default = None # TODO(tibell): We never have these. Remove field? parameter.default = None # TODO(tibell): We never have these. Remove field?
parameter.attributes = _AttributeListToDict(parsed_param.attribute_list) parameter.attributes = _AttributeListToDict(module, parameter,
parsed_param.attribute_list)
return parameter return parameter
@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):
method.response_parameters = list( method.response_parameters = list(
map(lambda parameter: _Parameter(module, parameter, interface), map(lambda parameter: _Parameter(module, parameter, interface),
parsed_method.response_parameter_list)) parsed_method.response_parameter_list))
method.attributes = _AttributeListToDict(parsed_method.attribute_list) method.attributes = _AttributeListToDict(module, method,
parsed_method.attribute_list)
# Enforce that only methods with response can have a [Sync] attribute. # Enforce that only methods with response can have a [Sync] attribute.
if method.sync and method.response_parameters is None: if method.sync and method.response_parameters is None:
@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):
interface.mojom_name = parsed_iface.mojom_name interface.mojom_name = parsed_iface.mojom_name
interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
module.kinds[interface.spec] = interface module.kinds[interface.spec] = interface
interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) interface.attributes = _AttributeListToDict(module, interface,
parsed_iface.attribute_list)
interface.enums = [] interface.enums = []
interface.constants = [] interface.constants = []
interface.methods_data = [] interface.methods_data = []
@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):
field = mojom.EnumField() field = mojom.EnumField()
field.mojom_name = parsed_field.mojom_name field.mojom_name = parsed_field.mojom_name
field.value = _LookupValue(module, enum, None, parsed_field.value) field.value = _LookupValue(module, enum, None, parsed_field.value)
field.attributes = _AttributeListToDict(parsed_field.attribute_list) field.attributes = _AttributeListToDict(module, field,
parsed_field.attribute_list)
value = mojom.EnumValue(module, enum, field) value = mojom.EnumValue(module, enum, field)
module.values[value.GetSpec()] = value module.values[value.GetSpec()] = value
return field return field
@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):
prev_value += 1 prev_value += 1
# Integral value (e.g: BEGIN = -0x1). # Integral value (e.g: BEGIN = -0x1).
elif _IsStrOrUnicode(field.value): elif isinstance(field.value, str):
prev_value = int(field.value, 0) prev_value = int(field.value, 0)
# Reference to a previous enum value (e.g: INIT = BEGIN). # Reference to a previous enum value (e.g: INIT = BEGIN).
@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):
else: else:
raise Exception('Unresolved enum value for %s' % field.value.GetSpec()) raise Exception('Unresolved enum value for %s' % field.value.GetSpec())
#resolved_enum_values[field.mojom_name] = prev_value if prev_value in (-128, -127):
raise Exception(f'{field.mojom_name} in {enum.spec} has the value '
f'{prev_value}, which is reserved for WTF::HashTrait\'s '
'default enum specialization and may not be used.')
field.numeric_value = prev_value field.numeric_value = prev_value
if min_value is None or prev_value < min_value: if min_value is None or prev_value < min_value:
min_value = prev_value min_value = prev_value
@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):
mojom_name = parent_kind.mojom_name + '.' + mojom_name mojom_name = parent_kind.mojom_name + '.' + mojom_name
enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name) enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
enum.parent_kind = parent_kind enum.parent_kind = parent_kind
enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) enum.attributes = _AttributeListToDict(module, enum,
parsed_enum.attribute_list)
if not enum.native_only: if not enum.native_only:
enum.fields = list( enum.fields = list(
@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):
for field in enum.fields: for field in enum.fields:
if field.default: if field.default:
if not enum.extensible: if not enum.extensible:
raise Exception('Non-extensible enums may not specify a default')
if enum.default_field is not None:
raise Exception( 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 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 module.kinds[enum.spec] = enum
@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
for referenced_kind in extract_referenced_user_kinds(param.kind): for referenced_kind in extract_referenced_user_kinds(param.kind):
sanitized_kind = sanitize_kind(referenced_kind) sanitized_kind = sanitize_kind(referenced_kind)
referenced_user_kinds[sanitized_kind.spec] = sanitized_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 return referenced_user_kinds
@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):
assertDependencyIsStable(response_param.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): def _Module(tree, path, imports):
""" """
Args: Args:
@ -778,6 +1151,8 @@ def _Module(tree, path, imports):
module.structs = [] module.structs = []
module.unions = [] module.unions = []
module.interfaces = [] module.interfaces = []
module.features = []
_ProcessElements( _ProcessElements(
filename, tree.definition_list, { filename, tree.definition_list, {
ast.Const: ast.Const:
@ -791,6 +1166,8 @@ def _Module(tree, path, imports):
ast.Interface: ast.Interface:
lambda interface: module.interfaces.append( lambda interface: module.interfaces.append(
_Interface(module, interface)), _Interface(module, interface)),
ast.Feature:
lambda feature: module.features.append(_Feature(module, feature)),
}) })
# Second pass expands fields and methods. This allows fields and parameters # Second pass expands fields and methods. This allows fields and parameters
@ -806,12 +1183,24 @@ def _Module(tree, path, imports):
for enum in struct.enums: for enum in struct.enums:
all_defined_kinds[enum.spec] = enum all_defined_kinds[enum.spec] = enum
for feature in module.features:
all_defined_kinds[feature.spec] = feature
for union in module.unions: for union in module.unions:
union.fields = list( union.fields = list(
map(lambda field: _UnionField(module, field, union), union.fields_data)) map(lambda field: _UnionField(module, field, union), union.fields_data))
_AssignDefaultOrdinals(union.fields) _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 del union.fields_data
all_defined_kinds[union.spec] = union 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: for interface in module.interfaces:
interface.methods = list( interface.methods = list(
@ -829,8 +1218,8 @@ def _Module(tree, path, imports):
all_defined_kinds.values()) all_defined_kinds.values())
imported_kind_specs = set(all_referenced_kinds.keys()).difference( imported_kind_specs = set(all_referenced_kinds.keys()).difference(
set(all_defined_kinds.keys())) set(all_defined_kinds.keys()))
module.imported_kinds = dict( module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
(spec, all_referenced_kinds[spec]) for spec in imported_kind_specs) for spec in sorted(imported_kind_specs))
generator.AddComputedData(module) generator.AddComputedData(module)
for iface in module.interfaces: for iface in module.interfaces:
@ -847,6 +1236,9 @@ def _Module(tree, path, imports):
if kind.stable: if kind.stable:
_AssertTypeIsStable(kind) _AssertTypeIsStable(kind)
for kind in module.structs:
_AssertStructIsValid(kind)
return module return module

View file

@ -1,17 +1,13 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp
import os.path
import sys
import unittest import unittest
from mojom.generate import module as mojom from mojom.generate import module as mojom
from mojom.generate import translate from mojom.generate import translate
from mojom.parse import ast from mojom.parse import ast
class TranslateTest(unittest.TestCase): class TranslateTest(unittest.TestCase):
"""Tests |parser.Parse()|.""" """Tests |parser.Parse()|."""
@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):
# pylint: disable=W0212 # pylint: disable=W0212
self.assertEquals( self.assertEquals(
translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface") translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
self.assertEquals( self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface") "?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", [])
def testEnumWithReservedValues(self):
"""Verifies that assigning reserved values to enumerators fails."""
# -128 is reserved for the empty representation in WTF::HashTraits.
tree = ast.Mojom(None, ast.ImportList(), [
ast.Enum(
"MyEnum", None,
ast.EnumValueList([
ast.EnumValue('kReserved', None, '-128'),
]))
])
with self.assertRaises(Exception) as context:
translate.OrderedModule(tree, "mojom_tree", [])
self.assertIn("reserved for WTF::HashTrait", str(context.exception))
# -127 is reserved for the deleted representation in WTF::HashTraits.
tree = ast.Mojom(None, ast.ImportList(), [
ast.Enum(
"MyEnum", None,
ast.EnumValueList([
ast.EnumValue('kReserved', None, '-127'),
]))
])
with self.assertRaises(Exception) as context:
translate.OrderedModule(tree, "mojom_tree", [])
self.assertIn("reserved for WTF::HashTrait", str(context.exception))
# Implicitly assigning a reserved value should also fail.
tree = ast.Mojom(None, ast.ImportList(), [
ast.Enum(
"MyEnum", None,
ast.EnumValueList([
ast.EnumValue('kNotReserved', None, '-129'),
ast.EnumValue('kImplicitlyReserved', None, None),
]))
])
with self.assertRaises(Exception) as context:
translate.OrderedModule(tree, "mojom_tree", [])
self.assertIn("reserved for WTF::HashTrait", str(context.exception))

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Node classes for the AST for a Mojo IDL 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 # and lineno). You may also define __repr__() to help with analyzing test
# failures, especially for more complex types. # failures, especially for more complex types.
import os.path
import sys
def _IsStrOrUnicode(x): # Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
if sys.version_info[0] < 3: # pylint: disable=no-member
return isinstance(x, (unicode, str))
return isinstance(x, str)
class NodeBase(object): class NodeBase:
"""Base class for nodes in the AST.""" """Base class for nodes in the AST."""
def __init__(self, filename=None, lineno=None): def __init__(self, filename=None, lineno=None):
@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
classes, in a tuple) of the members of the list.)""" classes, in a tuple) of the members of the list.)"""
def __init__(self, item_or_items=None, **kwargs): def __init__(self, item_or_items=None, **kwargs):
super(NodeListBase, self).__init__(**kwargs) super().__init__(**kwargs)
self.items = [] self.items = []
if item_or_items is None: if item_or_items is None:
pass pass
@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
return self.items.__iter__() return self.items.__iter__()
def __eq__(self, other): def __eq__(self, other):
return super(NodeListBase, self).__eq__(other) and \ return super().__eq__(other) and \
self.items == other.items self.items == other.items
# Implement this so that on failure, we get slightly more sensible output. # 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.""" include parameter definitions.) This class is meant to be subclassed."""
def __init__(self, mojom_name, **kwargs): def __init__(self, mojom_name, **kwargs):
assert _IsStrOrUnicode(mojom_name) assert isinstance(mojom_name, str)
NodeBase.__init__(self, **kwargs) NodeBase.__init__(self, **kwargs)
self.mojom_name = mojom_name self.mojom_name = mojom_name
@ -108,13 +105,13 @@ class Attribute(NodeBase):
"""Represents an attribute.""" """Represents an attribute."""
def __init__(self, key, value, **kwargs): def __init__(self, key, value, **kwargs):
assert _IsStrOrUnicode(key) assert isinstance(key, str)
super(Attribute, self).__init__(**kwargs) super().__init__(**kwargs)
self.key = key self.key = key
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
return super(Attribute, self).__eq__(other) and \ return super().__eq__(other) and \
self.key == other.key and \ self.key == other.key and \
self.value == other.value self.value == other.value
@ -131,17 +128,17 @@ class Const(Definition):
def __init__(self, mojom_name, attribute_list, typename, value, **kwargs): def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
# The typename is currently passed through as a string. # 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 # The value is either a literal (currently passed through as a string) or a
# "wrapped identifier". # "wrapped identifier".
assert _IsStrOrUnicode or isinstance(value, tuple) assert isinstance(value, (tuple, str))
super(Const, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.typename = typename self.typename = typename
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
return super(Const, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.typename == other.typename and \ self.typename == other.typename and \
self.value == other.value self.value == other.value
@ -153,12 +150,12 @@ class Enum(Definition):
def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs): def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert enum_value_list is None or isinstance(enum_value_list, EnumValueList) 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.attribute_list = attribute_list
self.enum_value_list = enum_value_list self.enum_value_list = enum_value_list
def __eq__(self, other): def __eq__(self, other):
return super(Enum, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.enum_value_list == other.enum_value_list 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 # The optional value is either an int (which is current a string) or a
# "wrapped identifier". # "wrapped identifier".
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple) assert value is None or isinstance(value, (tuple, str))
super(EnumValue, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
return super(EnumValue, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.value == other.value self.value == other.value
@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):
_list_item_type = EnumValue _list_item_type = EnumValue
class Feature(Definition):
"""Represents a runtime feature definition."""
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, FeatureBody) or body is None
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
def __repr__(self):
return "Feature(mojom_name = %s, attribute_list = %s, body = %s)" % (
self.mojom_name, self.attribute_list, self.body)
# This needs to be declared after `FeatureConst` and `FeatureField`.
class FeatureBody(NodeListBase):
"""Represents the body of (i.e., list of definitions inside) a feature."""
# Features are compile time helpers so all fields are initializers/consts
# for the underlying platform feature type.
_list_item_type = (Const)
class Import(NodeBase): class Import(NodeBase):
"""Represents an import statement.""" """Represents an import statement."""
def __init__(self, attribute_list, import_filename, **kwargs): def __init__(self, attribute_list, import_filename, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert _IsStrOrUnicode(import_filename) assert isinstance(import_filename, str)
super(Import, self).__init__(**kwargs) super().__init__(**kwargs)
self.attribute_list = attribute_list 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): def __eq__(self, other):
return super(Import, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.import_filename == other.import_filename self.import_filename == other.import_filename
@ -216,12 +242,12 @@ class Interface(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs): def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, InterfaceBody) assert isinstance(body, InterfaceBody)
super(Interface, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.body = body self.body = body
def __eq__(self, other): def __eq__(self, other):
return super(Interface, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.body == other.body self.body == other.body
@ -236,14 +262,14 @@ class Method(Definition):
assert isinstance(parameter_list, ParameterList) assert isinstance(parameter_list, ParameterList)
assert response_parameter_list is None or \ assert response_parameter_list is None or \
isinstance(response_parameter_list, ParameterList) isinstance(response_parameter_list, ParameterList)
super(Method, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
self.parameter_list = parameter_list self.parameter_list = parameter_list
self.response_parameter_list = response_parameter_list self.response_parameter_list = response_parameter_list
def __eq__(self, other): def __eq__(self, other):
return super(Method, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \ self.ordinal == other.ordinal and \
self.parameter_list == other.parameter_list and \ self.parameter_list == other.parameter_list and \
@ -264,12 +290,12 @@ class Module(NodeBase):
# |mojom_namespace| is either none or a "wrapped identifier". # |mojom_namespace| is either none or a "wrapped identifier".
assert mojom_namespace is None or isinstance(mojom_namespace, tuple) assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
super(Module, self).__init__(**kwargs) super().__init__(**kwargs)
self.mojom_namespace = mojom_namespace self.mojom_namespace = mojom_namespace
self.attribute_list = attribute_list self.attribute_list = attribute_list
def __eq__(self, other): def __eq__(self, other):
return super(Module, self).__eq__(other) and \ return super().__eq__(other) and \
self.mojom_namespace == other.mojom_namespace and \ self.mojom_namespace == other.mojom_namespace and \
self.attribute_list == other.attribute_list self.attribute_list == other.attribute_list
@ -281,13 +307,13 @@ class Mojom(NodeBase):
assert module is None or isinstance(module, Module) assert module is None or isinstance(module, Module)
assert isinstance(import_list, ImportList) assert isinstance(import_list, ImportList)
assert isinstance(definition_list, list) assert isinstance(definition_list, list)
super(Mojom, self).__init__(**kwargs) super().__init__(**kwargs)
self.module = module self.module = module
self.import_list = import_list self.import_list = import_list
self.definition_list = definition_list self.definition_list = definition_list
def __eq__(self, other): def __eq__(self, other):
return super(Mojom, self).__eq__(other) and \ return super().__eq__(other) and \
self.module == other.module and \ self.module == other.module and \
self.import_list == other.import_list and \ self.import_list == other.import_list and \
self.definition_list == other.definition_list self.definition_list == other.definition_list
@ -302,11 +328,11 @@ class Ordinal(NodeBase):
def __init__(self, value, **kwargs): def __init__(self, value, **kwargs):
assert isinstance(value, int) assert isinstance(value, int)
super(Ordinal, self).__init__(**kwargs) super().__init__(**kwargs)
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
return super(Ordinal, self).__eq__(other) and \ return super().__eq__(other) and \
self.value == other.value self.value == other.value
@ -314,18 +340,18 @@ class Parameter(NodeBase):
"""Represents a method request or response parameter.""" """Represents a method request or response parameter."""
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): 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 attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) assert ordinal is None or isinstance(ordinal, Ordinal)
assert _IsStrOrUnicode(typename) assert isinstance(typename, str)
super(Parameter, self).__init__(**kwargs) super().__init__(**kwargs)
self.mojom_name = mojom_name self.mojom_name = mojom_name
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
self.typename = typename self.typename = typename
def __eq__(self, other): def __eq__(self, other):
return super(Parameter, self).__eq__(other) and \ return super().__eq__(other) and \
self.mojom_name == other.mojom_name and \ self.mojom_name == other.mojom_name and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \ self.ordinal == other.ordinal and \
@ -344,42 +370,51 @@ class Struct(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs): def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, StructBody) or body is None 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.attribute_list = attribute_list
self.body = body self.body = body
def __eq__(self, other): def __eq__(self, other):
return super(Struct, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.body == other.body 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): class StructField(Definition):
"""Represents a struct field definition.""" """Represents a struct field definition."""
def __init__(self, mojom_name, attribute_list, ordinal, typename, def __init__(self, mojom_name, attribute_list, ordinal, typename,
default_value, **kwargs): default_value, **kwargs):
assert _IsStrOrUnicode(mojom_name) assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) 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 # The optional default value is currently either a value as a string or a
# "wrapped identifier". # "wrapped identifier".
assert default_value is None or _IsStrOrUnicode(default_value) or \ assert default_value is None or isinstance(default_value, (str, tuple))
isinstance(default_value, tuple) super().__init__(mojom_name, **kwargs)
super(StructField, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
self.typename = typename self.typename = typename
self.default_value = default_value self.default_value = default_value
def __eq__(self, other): def __eq__(self, other):
return super(StructField, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \ self.ordinal == other.ordinal and \
self.typename == other.typename and \ self.typename == other.typename and \
self.default_value == other.default_value 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|. # This needs to be declared after |StructField|.
class StructBody(NodeListBase): class StructBody(NodeListBase):
@ -394,29 +429,29 @@ class Union(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs): def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, UnionBody) assert isinstance(body, UnionBody)
super(Union, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.body = body self.body = body
def __eq__(self, other): def __eq__(self, other):
return super(Union, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.body == other.body self.body == other.body
class UnionField(Definition): class UnionField(Definition):
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): 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 attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) assert ordinal is None or isinstance(ordinal, Ordinal)
assert _IsStrOrUnicode(typename) assert isinstance(typename, str)
super(UnionField, self).__init__(mojom_name, **kwargs) super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
self.typename = typename self.typename = typename
def __eq__(self, other): def __eq__(self, other):
return super(UnionField, self).__eq__(other) and \ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \ self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \ self.ordinal == other.ordinal and \
self.typename == other.typename self.typename == other.typename

View file

@ -1,32 +1,26 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp
import os.path
import sys
import unittest import unittest
from mojom.parse import ast from mojom.parse import ast
class _TestNode(ast.NodeBase): class _TestNode(ast.NodeBase):
"""Node type for tests.""" """Node type for tests."""
def __init__(self, value, **kwargs): def __init__(self, value, **kwargs):
super(_TestNode, self).__init__(**kwargs) super().__init__(**kwargs)
self.value = value self.value = value
def __eq__(self, other): 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): class _TestNodeList(ast.NodeListBase):
"""Node list type for tests.""" """Node list type for tests."""
_list_item_type = _TestNode _list_item_type = _TestNode
class ASTTest(unittest.TestCase): class ASTTest(unittest.TestCase):
"""Tests various AST classes.""" """Tests various AST classes."""

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Helpers for processing conditionally enabled features in a mojom.""" """Helpers for processing conditionally enabled features in a mojom."""
@ -17,8 +17,10 @@ class EnableIfError(Error):
def _IsEnabled(definition, enabled_features): def _IsEnabled(definition, enabled_features):
"""Returns true if a definition is enabled. """Returns true if a definition is enabled.
A definition is enabled if it has no EnableIf attribute, or if the value of A definition is enabled if it has no EnableIf/EnableIfNot attribute.
the EnableIf attribute is in enabled_features. 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"): if not hasattr(definition, "attribute_list"):
return True return True
@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
already_defined = False already_defined = False
for a in definition.attribute_list: for a in definition.attribute_list:
if a.key == 'EnableIf': if a.key == 'EnableIf' or a.key == 'EnableIfNot':
if already_defined: if already_defined:
raise EnableIfError( raise EnableIfError(
definition.filename, definition.filename,
"EnableIf attribute may only be defined once per field.", "EnableIf/EnableIfNot attribute may only be set once per field.",
definition.lineno) definition.lineno)
already_defined = True already_defined = True
for attribute in definition.attribute_list: for attribute in definition.attribute_list:
if attribute.key == 'EnableIf' and attribute.value not in enabled_features: if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
return False return False
if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
return False
return True return True
@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):
"""Filters definitions with a body.""" """Filters definitions with a body."""
if isinstance(definition, ast.Enum): if isinstance(definition, ast.Enum):
_FilterDisabledFromNodeList(definition.enum_value_list, enabled_features) _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
elif isinstance(definition, ast.Interface):
_FilterDisabledFromNodeList(definition.body, enabled_features)
elif isinstance(definition, ast.Method): elif isinstance(definition, ast.Method):
_FilterDisabledFromNodeList(definition.parameter_list, enabled_features) _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
_FilterDisabledFromNodeList(definition.response_parameter_list, _FilterDisabledFromNodeList(definition.response_parameter_list,
enabled_features) enabled_features)
elif isinstance(definition, ast.Struct): elif isinstance(definition,
_FilterDisabledFromNodeList(definition.body, enabled_features) (ast.Interface, ast.Struct, ast.Union, ast.Feature)):
elif isinstance(definition, ast.Union):
_FilterDisabledFromNodeList(definition.body, enabled_features) _FilterDisabledFromNodeList(definition.body, enabled_features)

View file

@ -1,13 +1,12 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp import importlib.util
import os import os
import sys import sys
import unittest import unittest
def _GetDirAbove(dirname): def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must """Returns the directory "above" this file containing |dirname| (which must
also be "above" this file).""" also be "above" this file)."""
@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
if tail == dirname: if tail == dirname:
return path return path
try: try:
imp.find_module('mojom') importlib.util.find_spec("mojom")
except ImportError: except ImportError:
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib')) sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
import mojom.parse.ast as ast import mojom.parse.ast as ast
@ -29,7 +27,6 @@ import mojom.parse.parser as parser
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'}) ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
class ConditionalFeaturesTest(unittest.TestCase): class ConditionalFeaturesTest(unittest.TestCase):
"""Tests |mojom.parse.conditional_features|.""" """Tests |mojom.parse.conditional_features|."""
@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
""" """
self.parseAndAssertEqual(const_source, expected_source) 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): def testFilterEnum(self):
"""Test that EnumValues are correctly filtered from an Enum.""" """Test that EnumValues are correctly filtered from an Enum."""
enum_source = """ enum_source = """
@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
""" """
self.parseAndAssertEqual(import_source, expected_source) 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): def testFilterInterface(self):
"""Test that definitions are correctly filtered from an Interface.""" """Test that definitions are correctly filtered from an Interface."""
interface_source = """ interface_source = """
@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
""" """
self.parseAndAssertEqual(struct_source, expected_source) 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): def testFilterUnion(self):
"""Test that UnionFields are correctly filtered from a Union.""" """Test that UnionFields are correctly filtered from a Union."""
union_source = """ union_source = """
@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):
""" """
self.parseAndAssertEqual(mojom_source, expected_source) self.parseAndAssertEqual(mojom_source, expected_source)
def testFeaturesWithEnableIf(self):
mojom_source = """
feature Foo {
const string name = "FooFeature";
[EnableIf=red]
const bool default_state = false;
[EnableIf=yellow]
const bool default_state = true;
};
"""
expected_source = """
feature Foo {
const string name = "FooFeature";
[EnableIf=red]
const bool default_state = false;
};
"""
self.parseAndAssertEqual(mojom_source, expected_source)
def testMultipleEnableIfs(self): def testMultipleEnableIfs(self):
source = """ source = """
enum Foo { enum Foo {
@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):
conditional_features.RemoveDisabledDefinitions, conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -1,8 +1,7 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp
import os.path import os.path
import sys import sys
@ -22,7 +21,7 @@ class LexError(Error):
# We have methods which look like they could be functions: # We have methods which look like they could be functions:
# pylint: disable=R0201 # pylint: disable=R0201
class Lexer(object): class Lexer:
def __init__(self, filename): def __init__(self, filename):
self.filename = filename self.filename = filename
@ -56,6 +55,7 @@ class Lexer(object):
'PENDING_RECEIVER', 'PENDING_RECEIVER',
'PENDING_ASSOCIATED_REMOTE', 'PENDING_ASSOCIATED_REMOTE',
'PENDING_ASSOCIATED_RECEIVER', 'PENDING_ASSOCIATED_RECEIVER',
'FEATURE',
) )
keyword_map = {} keyword_map = {}
@ -81,7 +81,6 @@ class Lexer(object):
# Operators # Operators
'MINUS', 'MINUS',
'PLUS', 'PLUS',
'AMP',
'QSTN', 'QSTN',
# Assignment # Assignment
@ -168,7 +167,6 @@ class Lexer(object):
# Operators # Operators
t_MINUS = r'-' t_MINUS = r'-'
t_PLUS = r'\+' t_PLUS = r'\+'
t_AMP = r'&'
t_QSTN = r'\?' t_QSTN = r'\?'
# = # =

View file

@ -1,13 +1,12 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp import importlib.util
import os.path import os.path
import sys import sys
import unittest import unittest
def _GetDirAbove(dirname): def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must """Returns the directory "above" this file containing |dirname| (which must
also be "above" this file).""" also be "above" this file)."""
@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
if tail == dirname: if tail == dirname:
return path return path
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party")) sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex from ply import lex
try: try:
imp.find_module("mojom") importlib.util.find_spec("mojom")
except ImportError: except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.lexer import mojom.parse.lexer
# This (monkey-patching LexToken to make comparison value-based) is evil, but # This (monkey-patching LexToken to make comparison value-based) is evil, but
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing # we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
# for object identity.) # for object identity.)
@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):
self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+")) self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
self.assertEquals( self.assertEquals(
self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-")) self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
self.assertEquals( self.assertEquals(
self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?")) self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
self.assertEquals( self.assertEquals(

View file

@ -1,8 +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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Generates a syntax tree from a Mojo IDL file.""" """Generates a syntax tree from a Mojo IDL file."""
# Breaking parser stanzas is unhelpful so allow longer lines.
# pylint: disable=line-too-long
import os.path import os.path
import sys import sys
@ -33,7 +36,7 @@ class ParseError(Error):
# We have methods which look like they could be functions: # We have methods which look like they could be functions:
# pylint: disable=R0201 # pylint: disable=R0201
class Parser(object): class Parser:
def __init__(self, lexer, source, filename): def __init__(self, lexer, source, filename):
self.tokens = lexer.tokens self.tokens = lexer.tokens
self.source = source self.source = source
@ -111,7 +114,8 @@ class Parser(object):
| union | union
| interface | interface
| enum | enum
| const""" | const
| feature"""
p[0] = p[1] p[0] = p[1]
def p_attribute_section_1(self, p): def p_attribute_section_1(self, p):
@ -140,12 +144,19 @@ class Parser(object):
p[0].Append(p[3]) p[0].Append(p[3])
def p_attribute_1(self, p): def p_attribute_1(self, p):
"""attribute : NAME EQUALS evaled_literal """attribute : name_wrapped EQUALS identifier_wrapped"""
| NAME EQUALS NAME""" p[0] = ast.Attribute(p[1],
p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) p[3][1],
filename=self.filename,
lineno=p.lineno(1))
def p_attribute_2(self, p): def p_attribute_2(self, p):
"""attribute : NAME""" """attribute : name_wrapped EQUALS evaled_literal
| name_wrapped EQUALS name_wrapped"""
p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
def p_attribute_3(self, p):
"""attribute : name_wrapped"""
p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1)) p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
def p_evaled_literal(self, p): def p_evaled_literal(self, p):
@ -161,11 +172,11 @@ class Parser(object):
p[0] = eval(p[1]) p[0] = eval(p[1])
def p_struct_1(self, p): def p_struct_1(self, p):
"""struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI""" """struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI"""
p[0] = ast.Struct(p[3], p[1], p[5]) p[0] = ast.Struct(p[3], p[1], p[5])
def p_struct_2(self, p): def p_struct_2(self, p):
"""struct : attribute_section STRUCT NAME SEMI""" """struct : attribute_section STRUCT name_wrapped SEMI"""
p[0] = ast.Struct(p[3], p[1], None) p[0] = ast.Struct(p[3], p[1], None)
def p_struct_body_1(self, p): def p_struct_body_1(self, p):
@ -180,11 +191,24 @@ class Parser(object):
p[0].Append(p[2]) p[0].Append(p[2])
def p_struct_field(self, p): def p_struct_field(self, p):
"""struct_field : attribute_section typename NAME ordinal default SEMI""" """struct_field : attribute_section typename name_wrapped ordinal default SEMI"""
p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5]) p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])
def p_feature(self, p):
"""feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI"""
p[0] = ast.Feature(p[3], p[1], p[5])
def p_feature_body_1(self, p):
"""feature_body : """
p[0] = ast.FeatureBody()
def p_feature_body_2(self, p):
"""feature_body : feature_body const"""
p[0] = p[1]
p[0].Append(p[2])
def p_union(self, p): def p_union(self, p):
"""union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI""" """union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI"""
p[0] = ast.Union(p[3], p[1], p[5]) p[0] = ast.Union(p[3], p[1], p[5])
def p_union_body_1(self, p): def p_union_body_1(self, p):
@ -197,7 +221,7 @@ class Parser(object):
p[1].Append(p[2]) p[1].Append(p[2])
def p_union_field(self, p): def p_union_field(self, p):
"""union_field : attribute_section typename NAME ordinal SEMI""" """union_field : attribute_section typename name_wrapped ordinal SEMI"""
p[0] = ast.UnionField(p[3], p[1], p[4], p[2]) p[0] = ast.UnionField(p[3], p[1], p[4], p[2])
def p_default_1(self, p): def p_default_1(self, p):
@ -209,8 +233,7 @@ class Parser(object):
p[0] = p[2] p[0] = p[2]
def p_interface(self, p): def p_interface(self, p):
"""interface : attribute_section INTERFACE NAME LBRACE interface_body \ """interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI"""
RBRACE SEMI"""
p[0] = ast.Interface(p[3], p[1], p[5]) p[0] = ast.Interface(p[3], p[1], p[5])
def p_interface_body_1(self, p): def p_interface_body_1(self, p):
@ -233,8 +256,7 @@ class Parser(object):
p[0] = p[3] p[0] = p[3]
def p_method(self, p): def p_method(self, p):
"""method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \ """method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI"""
response SEMI"""
p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7]) p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
def p_parameter_list_1(self, p): def p_parameter_list_1(self, p):
@ -255,7 +277,7 @@ class Parser(object):
p[0].Append(p[3]) p[0].Append(p[3])
def p_parameter(self, p): def p_parameter(self, p):
"""parameter : attribute_section typename NAME ordinal""" """parameter : attribute_section typename name_wrapped ordinal"""
p[0] = ast.Parameter( p[0] = ast.Parameter(
p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3)) p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))
@ -271,8 +293,7 @@ class Parser(object):
"""nonnullable_typename : basictypename """nonnullable_typename : basictypename
| array | array
| fixed_array | fixed_array
| associative_array | associative_array"""
| interfacerequest"""
p[0] = p[1] p[0] = p[1]
def p_basictypename(self, p): def p_basictypename(self, p):
@ -297,18 +318,16 @@ class Parser(object):
p[0] = "rcv<%s>" % p[3] p[0] = "rcv<%s>" % p[3]
def p_associatedremotetype(self, p): def p_associatedremotetype(self, p):
"""associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \ """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE"""
RANGLE"""
p[0] = "rma<%s>" % p[3] p[0] = "rma<%s>" % p[3]
def p_associatedreceivertype(self, p): def p_associatedreceivertype(self, p):
"""associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \ """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE"""
RANGLE"""
p[0] = "rca<%s>" % p[3] p[0] = "rca<%s>" % p[3]
def p_handletype(self, p): def p_handletype(self, p):
"""handletype : HANDLE """handletype : HANDLE
| HANDLE LANGLE NAME RANGLE""" | HANDLE LANGLE name_wrapped RANGLE"""
if len(p) == 2: if len(p) == 2:
p[0] = p[1] p[0] = p[1]
else: else:
@ -342,14 +361,6 @@ class Parser(object):
"""associative_array : MAP LANGLE identifier COMMA typename RANGLE""" """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
p[0] = p[5] + "{" + p[3] + "}" 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): def p_ordinal_1(self, p):
"""ordinal : """ """ordinal : """
p[0] = None p[0] = None
@ -366,15 +377,14 @@ class Parser(object):
p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1)) p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
def p_enum_1(self, p): def p_enum_1(self, p):
"""enum : attribute_section ENUM NAME LBRACE enum_value_list \ """enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI
RBRACE SEMI | attribute_section ENUM name_wrapped LBRACE \
| attribute_section ENUM NAME LBRACE nonempty_enum_value_list \ nonempty_enum_value_list COMMA RBRACE SEMI"""
COMMA RBRACE SEMI"""
p[0] = ast.Enum( p[0] = ast.Enum(
p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2)) p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))
def p_enum_2(self, p): def p_enum_2(self, p):
"""enum : attribute_section ENUM NAME SEMI""" """enum : attribute_section ENUM name_wrapped SEMI"""
p[0] = ast.Enum( p[0] = ast.Enum(
p[3], p[1], None, filename=self.filename, lineno=p.lineno(2)) p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))
@ -396,9 +406,9 @@ class Parser(object):
p[0].Append(p[3]) p[0].Append(p[3])
def p_enum_value(self, p): def p_enum_value(self, p):
"""enum_value : attribute_section NAME """enum_value : attribute_section name_wrapped
| attribute_section NAME EQUALS int | attribute_section name_wrapped EQUALS int
| attribute_section NAME EQUALS identifier_wrapped""" | attribute_section name_wrapped EQUALS identifier_wrapped"""
p[0] = ast.EnumValue( p[0] = ast.EnumValue(
p[2], p[2],
p[1], p[1],
@ -407,7 +417,7 @@ class Parser(object):
lineno=p.lineno(2)) lineno=p.lineno(2))
def p_const(self, p): def p_const(self, p):
"""const : attribute_section CONST typename NAME EQUALS constant SEMI""" """const : attribute_section CONST typename name_wrapped EQUALS constant SEMI"""
p[0] = ast.Const(p[4], p[1], p[3], p[6]) p[0] = ast.Const(p[4], p[1], p[3], p[6])
def p_constant(self, p): def p_constant(self, p):
@ -422,10 +432,16 @@ class Parser(object):
# TODO(vtl): Make this produce a "wrapped" identifier (probably as an # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
# |ast.Identifier|, to be added) and get rid of identifier_wrapped. # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
def p_identifier(self, p): def p_identifier(self, p):
"""identifier : NAME """identifier : name_wrapped
| NAME DOT identifier""" | name_wrapped DOT identifier"""
p[0] = ''.join(p[1:]) p[0] = ''.join(p[1:])
# Allow 'feature' to be a name literal not just a keyword.
def p_name_wrapped(self, p):
"""name_wrapped : NAME
| FEATURE"""
p[0] = p[1]
def p_literal(self, p): def p_literal(self, p):
"""literal : int """literal : int
| float | float
@ -458,6 +474,12 @@ class Parser(object):
# TODO(vtl): Can we figure out what's missing? # TODO(vtl): Can we figure out what's missing?
raise ParseError(self.filename, "Unexpected end of file") raise ParseError(self.filename, "Unexpected end of file")
if e.value == 'feature':
raise ParseError(self.filename,
"`feature` is reserved for a future mojom keyword",
lineno=e.lineno,
snippet=self._GetSnippet(e.lineno))
raise ParseError( raise ParseError(
self.filename, self.filename,
"Unexpected %r:" % e.value, "Unexpected %r:" % e.value,

View file

@ -1,17 +1,13 @@
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import imp
import os.path
import sys
import unittest import unittest
from mojom.parse import ast from mojom.parse import ast
from mojom.parse import lexer from mojom.parse import lexer
from mojom.parse import parser from mojom.parse import parser
class ParserTest(unittest.TestCase): class ParserTest(unittest.TestCase):
"""Tests |parser.Parse()|.""" """Tests |parser.Parse()|."""
@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):
handle<data_pipe_producer>? k; handle<data_pipe_producer>? k;
handle<message_pipe>? l; handle<message_pipe>? l;
handle<shared_buffer>? m; handle<shared_buffer>? m;
some_interface&? n; pending_receiver<some_interface>? n;
handle<platform>? o; handle<platform>? o;
}; };
""" """
@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):
ast.StructField('l', None, None, 'handle<message_pipe>?', None), ast.StructField('l', None, None, 'handle<message_pipe>?', None),
ast.StructField('m', None, None, 'handle<shared_buffer>?', ast.StructField('m', None, None, 'handle<shared_buffer>?',
None), None),
ast.StructField('n', None, None, 'some_interface&?', None), ast.StructField('n', None, None, 'rcv<some_interface>?', None),
ast.StructField('o', None, None, 'handle<platform>?', None) ast.StructField('o', None, None, 'handle<platform>?', None)
])) ]))
]) ])
@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):
r" *handle\?<data_pipe_consumer> a;$"): r" *handle\?<data_pipe_consumer> a;$"):
parser.Parse(source2, "my_file.mojom") 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): def testSimpleUnion(self):
"""Tests a simple .mojom source that just defines a union.""" """Tests a simple .mojom source that just defines a union."""
source = """\ source = """\
@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):
source1 = """\ source1 = """\
struct MyStruct { struct MyStruct {
associated MyInterface a; associated MyInterface a;
associated MyInterface& b; pending_associated_receiver<MyInterface> b;
associated MyInterface? c; associated MyInterface? c;
associated MyInterface&? d; pending_associated_receiver<MyInterface>? d;
}; };
""" """
expected1 = ast.Mojom(None, ast.ImportList(), [ expected1 = ast.Mojom(None, ast.ImportList(), [
@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):
'MyStruct', None, 'MyStruct', None,
ast.StructBody([ ast.StructBody([
ast.StructField('a', None, None, 'asso<MyInterface>', None), ast.StructField('a', None, None, 'asso<MyInterface>', None),
ast.StructField('b', None, None, 'asso<MyInterface&>', None), ast.StructField('b', None, None, 'rca<MyInterface>', None),
ast.StructField('c', None, None, 'asso<MyInterface>?', None), ast.StructField('c', None, None, 'asso<MyInterface>?', None),
ast.StructField('d', None, None, 'asso<MyInterface&>?', None) ast.StructField('d', None, None, 'rca<MyInterface>?', None)
])) ]))
]) ])
self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
source2 = """\ source2 = """\
interface MyInterface { interface MyInterface {
MyMethod(associated A a) =>(associated B& b); MyMethod(associated A a) =>(pending_associated_receiver<B> b);
};""" };"""
expected2 = ast.Mojom(None, ast.ImportList(), [ expected2 = ast.Mojom(None, ast.ImportList(), [
ast.Interface( ast.Interface(
@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):
ast.InterfaceBody( ast.InterfaceBody(
ast.Method( ast.Method(
'MyMethod', None, None, 'MyMethod', None, None,
ast.ParameterList( ast.ParameterList(ast.Parameter('a', None, None,
ast.Parameter('a', None, None, 'asso<A>')), 'asso<A>')),
ast.ParameterList( ast.ParameterList(ast.Parameter('b', None, None,
ast.Parameter('b', None, None, 'asso<B&>'))))) 'rca<B>')))))
]) ])
self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):
r" *associated\? MyInterface& a;$"): r" *associated\? MyInterface& a;$"):
parser.Parse(source3, "my_file.mojom") parser.Parse(source3, "my_file.mojom")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Parses mojom IDL files. """Parses mojom IDL files.
@ -11,6 +11,7 @@ generate usable language bindings.
""" """
import argparse import argparse
import builtins
import codecs import codecs
import errno import errno
import json import json
@ -19,6 +20,7 @@ import multiprocessing
import os import os
import os.path import os.path
import sys import sys
import traceback
from collections import defaultdict from collections import defaultdict
from mojom.generate import module from mojom.generate import module
@ -28,16 +30,12 @@ from mojom.parse import conditional_features
# Disable this for easier debugging. # Disable this for easier debugging.
# In Python 2, subprocesses just hang when exceptions are thrown :(. _ENABLE_MULTIPROCESSING = True
_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
if sys.version_info < (3, 4): # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
_MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux') if __name__ == '__main__' and sys.platform == 'darwin':
else: multiprocessing.set_start_method('fork')
# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725 _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
if __name__ == '__main__' and sys.platform == 'darwin':
multiprocessing.set_start_method('fork')
_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
def _ResolveRelativeImportPath(path, roots): def _ResolveRelativeImportPath(path, roots):
@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
raise ValueError('"%s" does not exist in any of %s' % (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 """Rewrites an absolute file path as relative to an absolute directory path in
roots. roots.
@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
# Already done. # Already done.
return 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: if dep_abspath not in loaded_modules:
_EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies, _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
loaded_modules, module_metadata) loaded_modules, module_metadata)
@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
def collect(metadata_filename): def collect(metadata_filename):
processed_deps.add(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: with open(metadata_filename) as f:
metadata = json.load(f) metadata = json.load(f)
allowed_imports.update( 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']: for dep_metadata in metadata['deps']:
dep_metadata = to_abs(dep_metadata)
if dep_metadata not in processed_deps: if dep_metadata not in processed_deps:
collect(dep_metadata) collect(dep_metadata)
@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
# multiprocessing helper. # multiprocessing helper.
def _ParseAstHelper(args): def _ParseAstHelper(mojom_abspath, enabled_features):
mojom_abspath, enabled_features = args
with codecs.open(mojom_abspath, encoding='utf-8') as f: with codecs.open(mojom_abspath, encoding='utf-8') as f:
ast = parser.Parse(f.read(), mojom_abspath) ast = parser.Parse(f.read(), mojom_abspath)
conditional_features.RemoveDisabledDefinitions(ast, enabled_features) conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
@ -181,8 +186,7 @@ def _ParseAstHelper(args):
# multiprocessing helper. # multiprocessing helper.
def _SerializeHelper(args): def _SerializeHelper(mojom_abspath, mojom_path):
mojom_abspath, mojom_path = args
module_path = os.path.join(_SerializeHelper.output_root_path, module_path = os.path.join(_SerializeHelper.output_root_path,
_GetModuleFilename(mojom_path)) _GetModuleFilename(mojom_path))
module_dir = os.path.dirname(module_path) module_dir = os.path.dirname(module_path)
@ -199,12 +203,33 @@ def _SerializeHelper(args):
_SerializeHelper.loaded_modules[mojom_abspath].Dump(f) _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
def _Shard(target_func, args, processes=None): class _ExceptionWrapper:
args = list(args) 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: if processes is None:
processes = multiprocessing.cpu_count() processes = multiprocessing.cpu_count()
# Seems optimal to have each process perform at least 2 tasks. # 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': if sys.platform == 'win32':
# TODO(crbug.com/1190269) - we can't use more than 56 # 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. # Don't spin up processes unless there is enough work to merit doing so.
if not _ENABLE_MULTIPROCESSING or processes < 2: if not _ENABLE_MULTIPROCESSING or processes < 2:
for result in map(target_func, args): for arg_tuple in arg_list:
yield result yield target_func(*arg_tuple)
return return
pool = multiprocessing.Pool(processes=processes) pool = multiprocessing.Pool(processes=processes)
try: 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 yield result
finally: finally:
pool.close() pool.close()
@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
def _ParseMojoms(mojom_files, def _ParseMojoms(mojom_files,
input_root_paths, input_root_paths,
output_root_path, output_root_path,
module_root_paths,
enabled_features, enabled_features,
module_metadata, module_metadata,
allowed_imports=None): allowed_imports=None):
@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
are based on the mojom's relative path, rebased onto this path. are based on the mojom's relative path, rebased onto this path.
Additionally, the script expects this root to contain already-generated Additionally, the script expects this root to contain already-generated
modules for any transitive dependencies not listed in mojom_files. 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 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 module_metadata: A list of 2-tuples representing metadata key-value pairs to
attach to each compiled module output. attach to each compiled module output.
@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
loaded_modules = {} loaded_modules = {}
input_dependencies = defaultdict(set) input_dependencies = defaultdict(set)
mojom_files_to_parse = dict((os.path.normcase(abs_path), 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) for abs_path in mojom_files)
abs_paths = dict( abs_paths = dict(
(path, abs_path) for abs_path, path in mojom_files_to_parse.items()) (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 loaded_mojom_asts[mojom_abspath] = ast
logging.info('Processing dependencies') 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 = [] invalid_imports = []
for imp in ast.import_list: for imp in ast.import_list:
import_abspath = _ResolveRelativeImportPath(imp.import_filename, 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 # be parsed and have a module file sitting in a corresponding output
# location. # location.
module_path = _GetModuleFilename(imp.import_filename) module_path = _GetModuleFilename(imp.import_filename)
module_abspath = _ResolveRelativeImportPath(module_path, module_abspath = _ResolveRelativeImportPath(
[output_root_path]) module_path, module_root_paths + [output_root_path])
with open(module_abspath, 'rb') as module_file: with open(module_abspath, 'rb') as module_file:
loaded_modules[import_abspath] = module.Module.Load(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 ' 'based on the relative input path, rebased onto this root. Note that '
'ROOT is also searched for existing modules of any transitive imports ' 'ROOT is also searched for existing modules of any transitive imports '
'which were not included in the set of inputs.') '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( arg_parser.add_argument(
'--mojoms', '--mojoms',
nargs='+', nargs='+',
@ -396,9 +437,9 @@ already present in the provided output root.""")
help='Enables a named feature when parsing the given mojoms. Features ' help='Enables a named feature when parsing the given mojoms. Features '
'are identified by arbitrary string values. Specifying this flag with a ' 'are identified by arbitrary string values. Specifying this flag with a '
'given FEATURE name will cause the parser to process any syntax elements ' 'given FEATURE name will cause the parser to process any syntax elements '
'tagged with an [EnableIf=FEATURE] attribute. If this flag is not ' 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
'provided for a given FEATURE, such tagged elements are discarded by the ' 'flag is not provided for a given FEATURE, such tagged elements are '
'parser and will not be present in the compiled output.') 'discarded by the parser and will not be present in the compiled output.')
arg_parser.add_argument( arg_parser.add_argument(
'--check-imports', '--check-imports',
dest='build_metadata_filename', 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)) mojom_files = list(map(os.path.abspath, args.mojom_files))
input_roots = list(map(os.path.abspath, args.input_root_paths)) input_roots = list(map(os.path.abspath, args.input_root_paths))
output_root = os.path.abspath(args.output_root_path) 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: if args.build_metadata_filename:
allowed_imports = _CollectAllowedImportsFromBuildMetadata( allowed_imports = _CollectAllowedImportsFromBuildMetadata(
@ -445,13 +487,16 @@ already present in the provided output root.""")
module_metadata = list( module_metadata = list(
map(lambda kvp: tuple(kvp.split('=')), args.module_metadata)) map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
_ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features, _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
module_metadata, allowed_imports) args.enabled_features, module_metadata, allowed_imports)
logging.info('Finished') 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__': if __name__ == '__main__':
Run(sys.argv[1:]) 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)

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
resolution, and module serialization and deserialization.""" resolution, and module serialization and deserialization."""
def __init__(self, method_name): def __init__(self, method_name):
super(MojomParserTestCase, self).__init__(method_name) super().__init__(method_name)
self._temp_dir = None self._temp_dir = None
def setUp(self): def setUp(self):
@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):
self.ParseMojoms([filename]) self.ParseMojoms([filename])
m = self.LoadModule(filename) m = self.LoadModule(filename)
definitions = {} definitions = {}
for kinds in (m.enums, m.structs, m.unions, m.interfaces): for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):
for kind in kinds: for kind in kinds:
definitions[kind.mojom_name] = kind definitions[kind.mojom_name] = kind
return definitions return definitions

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import json
from mojom_parser_test_case import MojomParserTestCase from mojom_parser_test_case import MojomParserTestCase
@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
c = 'c.mojom' c = 'c.mojom'
c_metadata = 'out/c.build_metadata' c_metadata = 'out/c.build_metadata'
self.WriteFile(a_metadata, self.WriteFile(a_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) json.dumps({
"sources": [self.GetPath(a)],
"deps": []
}))
self.WriteFile( self.WriteFile(
b_metadata, b_metadata,
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b), json.dumps({
self.GetPath(a_metadata))) "sources": [self.GetPath(b)],
"deps": [self.GetPath(a_metadata)]
}))
self.WriteFile( self.WriteFile(
c_metadata, c_metadata,
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c), json.dumps({
self.GetPath(b_metadata))) "sources": [self.GetPath(c)],
"deps": [self.GetPath(b_metadata)]
}))
self.WriteFile(a, """\ self.WriteFile(a, """\
module a; module a;
struct Bar {};""") struct Bar {};""")
@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
b = 'b.mojom' b = 'b.mojom'
b_metadata = 'out/b.build_metadata' b_metadata = 'out/b.build_metadata'
self.WriteFile(a_metadata, self.WriteFile(a_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) json.dumps({
"sources": [self.GetPath(a)],
"deps": []
}))
self.WriteFile(b_metadata, self.WriteFile(b_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(b)) json.dumps({
"sources": [self.GetPath(b)],
"deps": []
}))
self.WriteFile(a, """\ self.WriteFile(a, """\
module a; module a;
struct Bar {};""") struct Bar {};""")

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.

View file

@ -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<message_pipe> p;
};
""")
with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
self.ParseMojoms([mojom])

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):
checker = module.BackwardCompatibilityChecker() checker = module.BackwardCompatibilityChecker()
compatibility_map = {} compatibility_map = {}
for name in old.keys(): for name in old:
compatibility_map[name] = checker.IsBackwardCompatible( try:
new[name], old[name]) compatibility_map[name] = checker.IsBackwardCompatible(
new[name], old[name])
except Exception:
compatibility_map[name] = False
return compatibility_map return compatibility_map
def assertBackwardCompatible(self, old_mojom, new_mojom): def assertBackwardCompatible(self, old_mojom, new_mojom):
@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
"""Adding a value to an existing version is not allowed, even if the old """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 enum was marked [Extensible]. Note that it is irrelevant whether or not the
new enum is marked [Extensible].""" new enum is marked [Extensible]."""
self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
'enum E { kFoo, kBar, kBaz };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kFoo, kBar };', '[Extensible] enum E { [Default] kFoo, kBar };',
'[Extensible] enum E { kFoo, kBar, kBaz };') 'enum E { kFoo, kBar, kBaz };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kFoo, [MinVersion=1] kBar };', '[Extensible] enum E { [Default] kFoo, kBar };',
'[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
self.assertNotBackwardCompatible(
'[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };') 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
def testEnumValueRemoval(self): def testEnumValueRemoval(self):
"""Removal of an enum value is never valid even for [Extensible] enums.""" """Removal of an enum value is never valid even for [Extensible] enums."""
self.assertNotBackwardCompatible('enum E { kFoo, kBar };', self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
'enum E { kFoo };') 'enum E { kFoo };')
self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
'[Extensible] enum E { kFoo };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kA, [MinVersion=1] kB };', '[Extensible] enum E { [Default] kFoo, kBar };',
'[Extensible] enum E { kA, };') '[Extensible] enum E { [Default] kFoo };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };', '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
'[Extensible] enum E { kA, [MinVersion=1] kB };') '[Extensible] enum E { [Default] kA, };')
self.assertNotBackwardCompatible(
"""[Extensible] enum E {
[Default] kA,
[MinVersion=1] kB,
[MinVersion=1] kZ };""",
'[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
def testNewExtensibleEnumValueWithMinVersion(self): def testNewExtensibleEnumValueWithMinVersion(self):
"""Adding a new and properly [MinVersion]'d value to an [Extensible] enum """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 is a backward-compatible change. Note that it is irrelevant whether or not
the new enum is marked [Extensible].""" 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 };') 'enum E { kA, kB, [MinVersion=1] kC };')
self.assertBackwardCompatible( self.assertBackwardCompatible(
'[Extensible] enum E { kA, kB };', '[Extensible] enum E { [Default] kA, kB };',
'[Extensible] enum E { kA, kB, [MinVersion=1] kC };') '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
self.assertBackwardCompatible( self.assertBackwardCompatible(
'[Extensible] enum E { kA, [MinVersion=1] kB };', '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
'[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };') """[Extensible] enum E {
[Default] kA,
[MinVersion=1] kB,
[MinVersion=2] kC };""")
def testRenameEnumValue(self): def testRenameEnumValue(self):
"""Renaming an enum value does not affect backward-compatibility. Only """Renaming an enum value does not affect backward-compatibility. Only
@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
'struct S {}; struct T { S s; };', 'struct S {}; struct T { S s; };',
'struct S { [MinVersion=1] int32 x; }; struct T { S s; };') 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
self.assertBackwardCompatible( self.assertBackwardCompatible(
'[Extensible] enum E { kA }; struct S { E e; };', '[Extensible] enum E { [Default] kA }; struct S { E e; };',
'[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };') """[Extensible] enum E {
[Default] kA,
[MinVersion=1] kB };
struct S { E e; };""")
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'struct S {}; struct T { S s; };', 'struct S {}; struct T { S s; };',
'struct S { int32 x; }; struct T { S s; };') 'struct S { int32 x; }; struct T { S s; };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kA }; struct S { E e; };', '[Extensible] enum E { [Default] kA }; struct S { E e; };',
'[Extensible] enum E { kA, kB }; struct S { E e; };') '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
def testNewStructFieldWithInvalidMinVersion(self): def testNewStructFieldWithInvalidMinVersion(self):
"""Adding a new field using an existing MinVersion breaks backward- """Adding a new field using an existing MinVersion breaks backward-
@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
'struct S {}; union U { S s; };', 'struct S {}; union U { S s; };',
'struct S { [MinVersion=1] int32 x; }; union U { S s; };') 'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
self.assertBackwardCompatible( self.assertBackwardCompatible(
'[Extensible] enum E { kA }; union U { E e; };', '[Extensible] enum E { [Default] kA }; union U { E e; };',
'[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };') """[Extensible] enum E {
[Default] kA,
[MinVersion=1] kB };
union U { E e; };""")
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'struct S {}; union U { S s; };', 'struct S {}; union U { S s; };',
'struct S { int32 x; }; union U { S s; };') 'struct S { int32 x; }; union U { S s; };')
self.assertNotBackwardCompatible( self.assertNotBackwardCompatible(
'[Extensible] enum E { kA }; union U { E e; };', '[Extensible] enum E { [Default] kA }; union U { E e; };',
'[Extensible] enum E { kA, kB }; union U { E e; };') '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
def testNewUnionFieldWithInvalidMinVersion(self): def testNewUnionFieldWithInvalidMinVersion(self):
"""Adding a new field using an existing MinVersion breaks backward- """Adding a new field using an existing MinVersion breaks backward-

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
@ -8,11 +8,13 @@ import sys
_TOOLS_DIR = os.path.dirname(__file__) _TOOLS_DIR = os.path.dirname(__file__)
_MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom') _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, _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
os.path.pardir) os.path.pardir)
# Ensure that the mojom library is discoverable. # Ensure that the mojom library is discoverable.
sys.path.append(_MOJOM_DIR) sys.path.append(_MOJOM_DIR)
sys.path.append(_BINDINGS_DIR)
# Help Python find typ in //third_party/catapult/third_party/typ/ # Help Python find typ in //third_party/catapult/third_party/typ/
sys.path.append( sys.path.append(
@ -21,7 +23,7 @@ import typ
def Main(): def Main():
return typ.main(top_level_dir=_MOJOM_DIR) return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0 # 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 9be4263648d7 of Chromium. Do not
modify them manually. modify them manually.

View file

@ -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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.